182 lines
3.9 KiB
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;
|
|
}
|