#include "config.h" #include "calibrate.h" #include "util.h" #include "adc.h" #include "dac.h" #include "led.h" #include "timer.h" #include "uart.h" //#define DEBUG_CALIBRATION float g_scale; /* delta(DAC) / delta(ADC) */ /* Initialize. Assume some relatively-safe scaling if no calibration is run */ void calibrate_init(void) { g_scale = (DAC_RANGE / 65536.0); /* 1 count at 16-bit DAC * means 1 counts at ADC */ } /* Given the current DAC and ADC values d1 and a1, compute a new DAC value d2 to give the desired ADC value a2 */ uint16_t adc_to_dac(uint16_t d1, int16_t a1, int16_t a2, float scale) { int32_t delta; int32_t d2; delta = (int32_t)((a2 - a1) * scale + 0.5); d2 = d1 + delta; return clamp(DAC_MIN, d2, DAC_MAX); } /* Calculate a new scale factor given two DAC and ADC points */ float calculate_scale(uint16_t d1, float a1, uint16_t d2, float a2) { float scale; float a = a2 - a1; /* Correct for known errors */ d1 = dac_get_actual_float(d1); d2 = dac_get_actual_float(d2); if (a < 0.1 && a > -0.1) scale = 1.0; else { scale = ((float)d2 - d1) / a; if (scale < 0.01) scale = 0.01; if (scale > 20) scale = 20; } return scale; } /* Seek with the DAC to reach a specific ADC value. Uses g_scale as an initial guess for scaling factor, but adjusts it dynamically. */ uint16_t seek(uint16_t starting_dac, int16_t desired_adc) { uint16_t old_dac, dac; int16_t old_adc, adc; float scale = g_scale; int steps = 0; dac = starting_dac; /* goto current location */ dac_write(dac); msleep(1); adc = adc_get(); while (1) { /* give up if we're not making progress */ if (steps++ > SEEK_MAX_STEPS) { // 2 flashes, delay, repeat led_pattern(0b00101000); break; } old_dac = dac; old_adc = adc; /* jump to the desired value */ dac = adc_to_dac(old_dac, old_adc, desired_adc, scale); /* write it out */ dac_write(dac); msleep(1); adc = adc_get(); #ifdef DEBUG_CALIBRATION uart1_put_hex16(dac); uart1_put(' '); uart1_put_hex16(adc); uart1_put(' '); uart1_put_hex16(desired_adc); uart1_put(' '); uart1_put_hex32(*(uint32_t *)&scale); uart1_crlf(); #endif /* if we're close, accept it */ if (abs(adc - desired_adc) <= SEEK_FUZZ_ADC) { led_pattern(0b11111110); break; } /* otherwise, if we were within ADC clamp limits, and the DAC changed a non-trivial amount, readjust scale factor */ if (adc > ADC_CLAMP_MIN && old_adc > ADC_CLAMP_MIN && adc < ADC_CLAMP_MAX && old_adc < ADC_CLAMP_MAX && abs((int32_t)dac - old_dac) >= SEEK_FUZZ_DAC) { scale = calculate_scale(old_dac, old_adc, dac, adc); } /* if we totally overshot the window, cut the scale in half */ if ((adc < ADC_CLAMP_MIN && old_adc > ADC_CLAMP_MAX) || (adc > ADC_CLAMP_MAX && old_adc < ADC_CLAMP_MIN)) { scale *= 0.5; } } return dac; } /* Perform calibration */ uint16_t do_calibrate(void) { uint16_t daczero, x1, x2; float y1, y2; /* Zero ADC */ daczero = seek((DAC_MIN + DAC_MAX) / 2, CALIBRATE_ADC_ZERO); /* Go down take an accurate sample */ x1 = seek(daczero, CALIBRATE_ADC_LOW); y1 = oversample(x1); /* Go up and take an accurate sample */ x2 = seek(daczero, CALIBRATE_ADC_HIGH); y2 = oversample(x2); /* Calculate scale */ g_scale = calculate_scale(x1, y1, x2, y2); #ifdef DEBUG_CALIBRATION uart1_put_string("calibrate x1="); uart1_put_dec(x1); uart1_put_string(" y1="); uart1_put_float(y1); uart1_put_string(" x2="); uart1_put_dec(x2); uart1_put_string(" y2="); uart1_put_float(y2); uart1_put_string(" scale="); uart1_put_float(g_scale); uart1_crlf(); #endif /* Return to zero position */ dac_write(daczero); return daczero; } /* Oversample to get a nice ADC value */ float oversample(uint16_t dac) { int i; int32_t sum = 0; for (i = 0; i < OVERSAMPLE_COUNT; i++) { dac_write(dac); msleep(1); sum += adc_get(); } return (float)sum / (float)OVERSAMPLE_COUNT; }