zoom/firmware/calibrate.c

182 lines
3.9 KiB
C

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