|
- #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;
- }
|