@@ -1,65 +1,65 @@ | |||
#include "config.h" | |||
#include "adc.h" | |||
#include "timer.h" | |||
/* Currently using software SPI because it's easy. | |||
Consider using hardware SPI with DMA on a timer, though. */ | |||
#define TRIS_SDATA TRISFbits.TRISF7 | |||
#define R_SDATA PORTFbits.RF7 | |||
#define TRIS_SCLK TRISFbits.TRISF6 | |||
#define LAT_SCLK LATFbits.LATF6 | |||
#define TRIS_CS TRISBbits.TRISB2 | |||
#define LAT_CS LATBbits.LATB2 | |||
void adc_init(void) | |||
{ | |||
TRIS_SDATA = 1; | |||
LAT_CS = IO_HIGH; | |||
TRIS_CS = 0; | |||
LAT_SCLK = IO_HIGH; | |||
TRIS_SCLK = 0; | |||
} | |||
int16_t adc_get(void) | |||
{ | |||
uint16_t v = 0; | |||
int i; | |||
LAT_CS = IO_LOW; | |||
for (i = 0; i < 16; i++) { | |||
v <<= 1; | |||
if (R_SDATA == IO_HIGH) | |||
v |= 1; | |||
LAT_SCLK = IO_LOW; | |||
nop(); nop(); nop(); | |||
LAT_SCLK = IO_HIGH; | |||
} | |||
LAT_CS = IO_HIGH; | |||
/* Sign-extend the 12-bit value */ | |||
if (v & 0x0800) | |||
v |= 0xF000; | |||
else | |||
v &= ~0xF000; | |||
return (int16_t) v; | |||
} | |||
uint16_t adc_get_raw(void) | |||
{ | |||
uint16_t v = 0; | |||
int i; | |||
LAT_CS = IO_LOW; | |||
for (i = 0; i < 16; i++) { | |||
v <<= 1; | |||
if (R_SDATA == IO_HIGH) | |||
v |= 1; | |||
LAT_SCLK = IO_LOW; | |||
nop(); nop(); nop(); | |||
LAT_SCLK = IO_HIGH; | |||
} | |||
LAT_CS = IO_HIGH; | |||
return (uint16_t) v; | |||
} | |||
#include "config.h" | |||
#include "adc.h" | |||
#include "timer.h" | |||
/* Currently using software SPI because it's easy. | |||
Consider using hardware SPI with DMA on a timer, though. */ | |||
#define TRIS_SDATA TRISFbits.TRISF7 | |||
#define R_SDATA PORTFbits.RF7 | |||
#define TRIS_SCLK TRISFbits.TRISF6 | |||
#define LAT_SCLK LATFbits.LATF6 | |||
#define TRIS_CS TRISBbits.TRISB2 | |||
#define LAT_CS LATBbits.LATB2 | |||
void adc_init(void) | |||
{ | |||
TRIS_SDATA = 1; | |||
LAT_CS = IO_HIGH; | |||
TRIS_CS = 0; | |||
LAT_SCLK = IO_HIGH; | |||
TRIS_SCLK = 0; | |||
} | |||
int16_t adc_get(void) | |||
{ | |||
uint16_t v = 0; | |||
int i; | |||
LAT_CS = IO_LOW; | |||
for (i = 0; i < 16; i++) { | |||
v <<= 1; | |||
if (R_SDATA == IO_HIGH) | |||
v |= 1; | |||
LAT_SCLK = IO_LOW; | |||
nop(); nop(); nop(); | |||
LAT_SCLK = IO_HIGH; | |||
} | |||
LAT_CS = IO_HIGH; | |||
/* Sign-extend the 12-bit value */ | |||
if (v & 0x0800) | |||
v |= 0xF000; | |||
else | |||
v &= ~0xF000; | |||
return (int16_t) v; | |||
} | |||
uint16_t adc_get_raw(void) | |||
{ | |||
uint16_t v = 0; | |||
int i; | |||
LAT_CS = IO_LOW; | |||
for (i = 0; i < 16; i++) { | |||
v <<= 1; | |||
if (R_SDATA == IO_HIGH) | |||
v |= 1; | |||
LAT_SCLK = IO_LOW; | |||
nop(); nop(); nop(); | |||
LAT_SCLK = IO_HIGH; | |||
} | |||
LAT_CS = IO_HIGH; | |||
return (uint16_t) v; | |||
} |
@@ -1,16 +1,16 @@ | |||
#ifndef ADC_H | |||
#define ADC_H | |||
#include "config.h" | |||
/* Initialize external 12-bit ADC (AD7450) */ | |||
void adc_init(void); | |||
/* Trigger conversion and return it. | |||
Result is a signed value (-2048 to +2047) */ | |||
int16_t adc_get(void); | |||
/* Trigger conversion and get raw 16-bit value from ADC */ | |||
uint16_t adc_get_raw(void); | |||
#endif | |||
#ifndef ADC_H | |||
#define ADC_H | |||
#include "config.h" | |||
/* Initialize external 12-bit ADC (AD7450) */ | |||
void adc_init(void); | |||
/* Trigger conversion and return it. | |||
Result is a signed value (-2048 to +2047) */ | |||
int16_t adc_get(void); | |||
/* Trigger conversion and get raw 16-bit value from ADC */ | |||
uint16_t adc_get_raw(void); | |||
#endif |
@@ -1,136 +1,136 @@ | |||
#include "config.h" | |||
#include "adcext.h" | |||
/* The ADC (AD7846) is tricky, as new conversions start | |||
automatically when the previous data read finishes. | |||
To allow more control over start/read, we stretch the | |||
read indefinitely by delaying the final SCK edge. | |||
This means we can't have the hardware do SPI for us. */ | |||
/* External serial clock, 2-wire I/O -- tie /EXT low, | |||
tie ADC SDI for rate selection, tie /CS low, use BUSY for | |||
interrupt notification if desired */ | |||
#define TRIS_SDO TRISBbits.TRISB5 | |||
#define R_SDO PORTBbits.RB5 | |||
#define TRIS_SCK TRISBbits.TRISB4 | |||
#define LAT_SCK LATBbits.LATB4 | |||
#define TRIS_SCS TRISBbits.TRISB3 | |||
#define LAT_SCS LATBbits.LATB3 | |||
/* Short delays */ | |||
#define wait_200ns() do { \ | |||
nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); \ | |||
} while(0) | |||
#define wait_25ns() nop() | |||
/* Initialize ADC */ | |||
void adcext_init(void) | |||
{ | |||
int32_t i; | |||
TRIS_SDO = 1; | |||
LAT_SCK = IO_HIGH; | |||
TRIS_SCK = 0; | |||
LAT_SCS = IO_LOW; | |||
TRIS_SCS = 0; | |||
/* Startup delay CS down to SCK high t4 = 5000ns */ | |||
for (i = 0; i < 5000 / 25; i++) | |||
wait_25ns(); | |||
/* We need to monitor BUSY, I think. With the 2-wire | |||
interface, we never know if we start in the middle of | |||
a data output phase. For now, consider reading broken.. | |||
just return early. XXXX */ | |||
return; | |||
/* Trigger a dummy read so we're prepared for the | |||
next conversion */ | |||
(void) adcext_read(); | |||
} | |||
/* Start a conversion if it hasn't already been started. | |||
Wait for conversion to finish. | |||
Read the result and return the raw 32-bit value. */ | |||
uint32_t adcext_read(void) | |||
{ | |||
uint32_t val; | |||
int i; | |||
/* Start conversion by completing previous read */ | |||
LAT_SCK = IO_LOW; | |||
/* Wait tKQMAX for SCK down to SDO valid */ | |||
wait_200ns(); | |||
/* Wait for conversion to finish */ | |||
while (R_SDO == IO_HIGH) | |||
continue; | |||
/* Read it out */ | |||
val = 0; | |||
for (i = 0; i < 32; i++) { | |||
/* SCK low tLESCK = 25ns */ | |||
wait_25ns(); | |||
LAT_SCK = IO_HIGH; | |||
/* SCK high tHESCK = 25ns, but | |||
we also have SCK down to SDO valid tKQMAX = 200ns? | |||
Probably misspecified but wait tKQMAX anyway. */ | |||
wait_200ns(); | |||
val <<= 1; | |||
if (R_SDO == IO_HIGH) | |||
val |= 1; | |||
/* Leave SCK high on final bit to delay new conversion */ | |||
if (i < 31) | |||
LAT_SCK = IO_LOW; | |||
} | |||
/* Done */ | |||
return val; | |||
} | |||
/* Convert a raw 32-bit value into a signed 32-bit result. | |||
The return value is full int32 range but only the high | |||
24 should be significant: low 3 will always be 0, | |||
and the next 5 will be sub-resolution (see datasheet). */ | |||
int32_t adcext_convert(uint32_t raw) | |||
{ | |||
int sigmsb = (raw >> 28) & 3; | |||
/* If SIG & MSB, it is a positive overflow */ | |||
if (sigmsb == 3) | |||
return (int32_t)0x7FFFFFFFL; | |||
/* If !SIG & !MSB, it is a negative overflow */ | |||
if (sigmsb == 0) | |||
return (int32_t)0x80000000L; | |||
/* Shift over EOC,DMY,SIG and return */ | |||
return ((int32_t)(raw << 3)); | |||
} | |||
/* Start a new conversion. If a conversion was already started | |||
but the result was not read, this does nothing. */ | |||
void adcext_start_conversion(void) | |||
{ | |||
/* If we had a previous conversion ready to read, | |||
read it out so we can start a new conversion instead */ | |||
if (adcext_is_conversion_ready()) | |||
(void) adcext_read(); | |||
/* Start conversion by completing previous read */ | |||
LAT_SCK = 0; | |||
/* Wait tKQMAX for SCK down to SDO valid in case we | |||
call adcext_is_conversion_ready right away. */ | |||
wait_200ns(); | |||
} | |||
/* Return 1 if a conversion is finished and ready to be read, 0 otherwise */ | |||
int adcext_is_conversion_ready(void) | |||
{ | |||
if (LAT_SCK == 0 && R_SDO == 0) | |||
return 1; | |||
return 0; | |||
} | |||
#include "config.h" | |||
#include "adcext.h" | |||
/* The ADC (AD7846) is tricky, as new conversions start | |||
automatically when the previous data read finishes. | |||
To allow more control over start/read, we stretch the | |||
read indefinitely by delaying the final SCK edge. | |||
This means we can't have the hardware do SPI for us. */ | |||
/* External serial clock, 2-wire I/O -- tie /EXT low, | |||
tie ADC SDI for rate selection, tie /CS low, use BUSY for | |||
interrupt notification if desired */ | |||
#define TRIS_SDO TRISBbits.TRISB5 | |||
#define R_SDO PORTBbits.RB5 | |||
#define TRIS_SCK TRISBbits.TRISB4 | |||
#define LAT_SCK LATBbits.LATB4 | |||
#define TRIS_SCS TRISBbits.TRISB3 | |||
#define LAT_SCS LATBbits.LATB3 | |||
/* Short delays */ | |||
#define wait_200ns() do { \ | |||
nop(); nop(); nop(); nop(); nop(); nop(); nop(); nop(); \ | |||
} while(0) | |||
#define wait_25ns() nop() | |||
/* Initialize ADC */ | |||
void adcext_init(void) | |||
{ | |||
int32_t i; | |||
TRIS_SDO = 1; | |||
LAT_SCK = IO_HIGH; | |||
TRIS_SCK = 0; | |||
LAT_SCS = IO_LOW; | |||
TRIS_SCS = 0; | |||
/* Startup delay CS down to SCK high t4 = 5000ns */ | |||
for (i = 0; i < 5000 / 25; i++) | |||
wait_25ns(); | |||
/* We need to monitor BUSY, I think. With the 2-wire | |||
interface, we never know if we start in the middle of | |||
a data output phase. For now, consider reading broken.. | |||
just return early. XXXX */ | |||
return; | |||
/* Trigger a dummy read so we're prepared for the | |||
next conversion */ | |||
(void) adcext_read(); | |||
} | |||
/* Start a conversion if it hasn't already been started. | |||
Wait for conversion to finish. | |||
Read the result and return the raw 32-bit value. */ | |||
uint32_t adcext_read(void) | |||
{ | |||
uint32_t val; | |||
int i; | |||
/* Start conversion by completing previous read */ | |||
LAT_SCK = IO_LOW; | |||
/* Wait tKQMAX for SCK down to SDO valid */ | |||
wait_200ns(); | |||
/* Wait for conversion to finish */ | |||
while (R_SDO == IO_HIGH) | |||
continue; | |||
/* Read it out */ | |||
val = 0; | |||
for (i = 0; i < 32; i++) { | |||
/* SCK low tLESCK = 25ns */ | |||
wait_25ns(); | |||
LAT_SCK = IO_HIGH; | |||
/* SCK high tHESCK = 25ns, but | |||
we also have SCK down to SDO valid tKQMAX = 200ns? | |||
Probably misspecified but wait tKQMAX anyway. */ | |||
wait_200ns(); | |||
val <<= 1; | |||
if (R_SDO == IO_HIGH) | |||
val |= 1; | |||
/* Leave SCK high on final bit to delay new conversion */ | |||
if (i < 31) | |||
LAT_SCK = IO_LOW; | |||
} | |||
/* Done */ | |||
return val; | |||
} | |||
/* Convert a raw 32-bit value into a signed 32-bit result. | |||
The return value is full int32 range but only the high | |||
24 should be significant: low 3 will always be 0, | |||
and the next 5 will be sub-resolution (see datasheet). */ | |||
int32_t adcext_convert(uint32_t raw) | |||
{ | |||
int sigmsb = (raw >> 28) & 3; | |||
/* If SIG & MSB, it is a positive overflow */ | |||
if (sigmsb == 3) | |||
return (int32_t)0x7FFFFFFFL; | |||
/* If !SIG & !MSB, it is a negative overflow */ | |||
if (sigmsb == 0) | |||
return (int32_t)0x80000000L; | |||
/* Shift over EOC,DMY,SIG and return */ | |||
return ((int32_t)(raw << 3)); | |||
} | |||
/* Start a new conversion. If a conversion was already started | |||
but the result was not read, this does nothing. */ | |||
void adcext_start_conversion(void) | |||
{ | |||
/* If we had a previous conversion ready to read, | |||
read it out so we can start a new conversion instead */ | |||
if (adcext_is_conversion_ready()) | |||
(void) adcext_read(); | |||
/* Start conversion by completing previous read */ | |||
LAT_SCK = 0; | |||
/* Wait tKQMAX for SCK down to SDO valid in case we | |||
call adcext_is_conversion_ready right away. */ | |||
wait_200ns(); | |||
} | |||
/* Return 1 if a conversion is finished and ready to be read, 0 otherwise */ | |||
int adcext_is_conversion_ready(void) | |||
{ | |||
if (LAT_SCK == 0 && R_SDO == 0) | |||
return 1; | |||
return 0; | |||
} |
@@ -1,23 +1,23 @@ | |||
#ifndef ADCEXT_H | |||
#define ADCEXT_H | |||
/* Initialize ADC */ | |||
void adcext_init(void); | |||
/* Start a conversion if it hasn't already been started. | |||
Wait for conversion to finish. | |||
Read the result and return the raw 32-bit value. */ | |||
uint32_t adcext_read(void); | |||
/* Convert a raw 32-bit value into a signed result. | |||
The return value range is -(2^31) to (2^31)-1 */ | |||
int32_t adcext_convert(uint32_t raw); | |||
/* Start a new conversion. If a conversion was already started | |||
but the result was not read, this does nothing. */ | |||
void adcext_start_conversion(void); | |||
/* Return 1 if a conversion is in progress, 0 otherwise */ | |||
int adcext_is_conversion_ready(void); | |||
#endif | |||
#ifndef ADCEXT_H | |||
#define ADCEXT_H | |||
/* Initialize ADC */ | |||
void adcext_init(void); | |||
/* Start a conversion if it hasn't already been started. | |||
Wait for conversion to finish. | |||
Read the result and return the raw 32-bit value. */ | |||
uint32_t adcext_read(void); | |||
/* Convert a raw 32-bit value into a signed result. | |||
The return value range is -(2^31) to (2^31)-1 */ | |||
int32_t adcext_convert(uint32_t raw); | |||
/* Start a new conversion. If a conversion was already started | |||
but the result was not read, this does nothing. */ | |||
void adcext_start_conversion(void); | |||
/* Return 1 if a conversion is in progress, 0 otherwise */ | |||
int adcext_is_conversion_ready(void); | |||
#endif |
@@ -1,180 +1,180 @@ | |||
#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; | |||
} | |||
#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; | |||
} |
@@ -1,42 +1,42 @@ | |||
#ifndef CALIBRATE_H | |||
#define CALIBRATE_H | |||
#define ADC_CLAMP_MIN 256 | |||
#define ADC_CLAMP_MAX 1792 | |||
#define OVERSAMPLE_COUNT 256 | |||
#define DAC_MIN DAC_LOW | |||
#define DAC_MAX DAC_HIGH | |||
#define SEEK_MAX_STEPS 1000 | |||
#define SEEK_FUZZ_ADC 100 | |||
#define SEEK_FUZZ_DAC (1+((DAC_RANGE * 5) / 65536)) | |||
#define CALIBRATE_ADC_ZERO 1024 | |||
#define CALIBRATE_ADC_LOW 512 | |||
#define CALIBRATE_ADC_HIGH 1536 | |||
/* Initialize. Assume some relatively-safe scaling if no calibration is run */ | |||
void calibrate_init(void); | |||
/* 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); | |||
/* Calculate a new scale factor given two DAC and ADC points */ | |||
float calculate_scale(uint16_t d1, float a1, uint16_t d2, float a2); | |||
/* Seek with the DAC to reach a specific ADC value, | |||
using current scaling as a starting point. */ | |||
uint16_t seek(uint16_t starting_dac, int16_t desired_adc); | |||
/* Perform calibration */ | |||
uint16_t do_calibrate(void); | |||
/* Oversample to get a nice ADC value */ | |||
float oversample(uint16_t dac); | |||
extern float g_scale; /* delta(DAC) / delta(ADC) */ | |||
#endif | |||
#ifndef CALIBRATE_H | |||
#define CALIBRATE_H | |||
#define ADC_CLAMP_MIN 256 | |||
#define ADC_CLAMP_MAX 1792 | |||
#define OVERSAMPLE_COUNT 256 | |||
#define DAC_MIN DAC_LOW | |||
#define DAC_MAX DAC_HIGH | |||
#define SEEK_MAX_STEPS 1000 | |||
#define SEEK_FUZZ_ADC 100 | |||
#define SEEK_FUZZ_DAC (1+((DAC_RANGE * 5) / 65536)) | |||
#define CALIBRATE_ADC_ZERO 1024 | |||
#define CALIBRATE_ADC_LOW 512 | |||
#define CALIBRATE_ADC_HIGH 1536 | |||
/* Initialize. Assume some relatively-safe scaling if no calibration is run */ | |||
void calibrate_init(void); | |||
/* 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); | |||
/* Calculate a new scale factor given two DAC and ADC points */ | |||
float calculate_scale(uint16_t d1, float a1, uint16_t d2, float a2); | |||
/* Seek with the DAC to reach a specific ADC value, | |||
using current scaling as a starting point. */ | |||
uint16_t seek(uint16_t starting_dac, int16_t desired_adc); | |||
/* Perform calibration */ | |||
uint16_t do_calibrate(void); | |||
/* Oversample to get a nice ADC value */ | |||
float oversample(uint16_t dac); | |||
extern float g_scale; /* delta(DAC) / delta(ADC) */ | |||
#endif |
@@ -1,77 +1,77 @@ | |||
#include "config.h" | |||
#include "dac.h" | |||
/* Initialize DAC (AD5542) */ | |||
void dac_init(void) | |||
{ | |||
/* SPI2 */ | |||
IEC2bits.SPI2IE = 0; | |||
SPI2CON1bits.DISSCK = 0; | |||
SPI2CON1bits.DISSDO = 0; | |||
SPI2CON1bits.MODE16 = 1; | |||
SPI2CON1bits.SMP = 0; | |||
SPI2CON1bits.CKE = 0; | |||
SPI2CON1bits.SSEN = 0; | |||
SPI2CON1bits.CKP = IO_HIGH; | |||
SPI2CON1bits.MSTEN = 1; | |||
SPI2CON1bits.SPRE = 4; | |||
SPI2CON1bits.PPRE = 3; | |||
/* There's no framed mode that does the normal | |||
"chip select" behavior, so control /SS2 manually */ | |||
SPI2CON2bits.FRMEN = 0; | |||
LATGbits.LATG9 = IO_HIGH; | |||
TRISGbits.TRISG9 = 0; | |||
SPI2STATbits.SPISIDL = 0; | |||
SPI2STATbits.SPIEN = 1; | |||
dac_write(0x0000); | |||
} | |||
static const uint16_t dac_lookup[1024] = { | |||
#include "lookup.inc" | |||
}; | |||
/* Write raw 16-bit desired value to DAC: | |||
DAC_HIGH 4.9998v | |||
DAC_MID 0v | |||
DAC_LOW -5v | |||
*/ | |||
void dac_write(uint16_t val) | |||
{ | |||
LATGbits.LATG9 = IO_LOW; | |||
if (IO_HIGH == 1) | |||
SPI2BUF = __dac_to_spi_cmd(val); | |||
else | |||
SPI2BUF = ~__dac_to_spi_cmd(val); | |||
while (!SPI2STATbits.SPIRBF) | |||
continue; | |||
(void) SPI2BUF; | |||
LATGbits.LATG9 = IO_HIGH; | |||
} | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a 16-bit value, where: | |||
0xffff = 4.9998v | |||
0x8000 = 0v | |||
0x0000 = -5v | |||
*/ | |||
uint16_t dac_get_actual_16bit(uint16_t val) | |||
{ | |||
return __dac_to_16bit_equiv(val); | |||
} | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a FLOAT, where: | |||
DAC_HIGH = 4.9998v | |||
DAC_MID = 0v | |||
DAC_LOW = -5v | |||
Example: for 10-bit DAC, convert integer command 123 into | |||
the more accurate 123.45 using known lower bits. | |||
*/ | |||
float dac_get_actual_float(uint16_t val) | |||
{ | |||
return __dac_to_16bit_equiv(val) * (DAC_RANGE / 65536.0); | |||
} | |||
#include "config.h" | |||
#include "dac.h" | |||
/* Initialize DAC (AD5542) */ | |||
void dac_init(void) | |||
{ | |||
/* SPI2 */ | |||
IEC2bits.SPI2IE = 0; | |||
SPI2CON1bits.DISSCK = 0; | |||
SPI2CON1bits.DISSDO = 0; | |||
SPI2CON1bits.MODE16 = 1; | |||
SPI2CON1bits.SMP = 0; | |||
SPI2CON1bits.CKE = 0; | |||
SPI2CON1bits.SSEN = 0; | |||
SPI2CON1bits.CKP = IO_HIGH; | |||
SPI2CON1bits.MSTEN = 1; | |||
SPI2CON1bits.SPRE = 4; | |||
SPI2CON1bits.PPRE = 3; | |||
/* There's no framed mode that does the normal | |||
"chip select" behavior, so control /SS2 manually */ | |||
SPI2CON2bits.FRMEN = 0; | |||
LATGbits.LATG9 = IO_HIGH; | |||
TRISGbits.TRISG9 = 0; | |||
SPI2STATbits.SPISIDL = 0; | |||
SPI2STATbits.SPIEN = 1; | |||
dac_write(0x0000); | |||
} | |||
static const uint16_t dac_lookup[1024] = { | |||
#include "lookup.inc" | |||
}; | |||
/* Write raw 16-bit desired value to DAC: | |||
DAC_HIGH 4.9998v | |||
DAC_MID 0v | |||
DAC_LOW -5v | |||
*/ | |||
void dac_write(uint16_t val) | |||
{ | |||
LATGbits.LATG9 = IO_LOW; | |||
if (IO_HIGH == 1) | |||
SPI2BUF = __dac_to_spi_cmd(val); | |||
else | |||
SPI2BUF = ~__dac_to_spi_cmd(val); | |||
while (!SPI2STATbits.SPIRBF) | |||
continue; | |||
(void) SPI2BUF; | |||
LATGbits.LATG9 = IO_HIGH; | |||
} | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a 16-bit value, where: | |||
0xffff = 4.9998v | |||
0x8000 = 0v | |||
0x0000 = -5v | |||
*/ | |||
uint16_t dac_get_actual_16bit(uint16_t val) | |||
{ | |||
return __dac_to_16bit_equiv(val); | |||
} | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a FLOAT, where: | |||
DAC_HIGH = 4.9998v | |||
DAC_MID = 0v | |||
DAC_LOW = -5v | |||
Example: for 10-bit DAC, convert integer command 123 into | |||
the more accurate 123.45 using known lower bits. | |||
*/ | |||
float dac_get_actual_float(uint16_t val) | |||
{ | |||
return __dac_to_16bit_equiv(val) * (DAC_RANGE / 65536.0); | |||
} |
@@ -1,60 +1,60 @@ | |||
#ifndef DAC_H | |||
#define DAC_H | |||
#include "config.h" | |||
#define DAC_TYPE 2 | |||
#if DAC_TYPE == 0 | |||
/* AD5542, normal 16-bit range */ | |||
#define DAC_BITS 16 | |||
#define __dac_to_spi_cmd(x) (x) | |||
#define __dac_to_16bit_equiv(x) (x) | |||
#elif DAC_TYPE == 1 | |||
/* AD5542, fake 10-bit range using random lower bits */ | |||
#define DAC_BITS 10 | |||
#define __dac_to_spi_cmd(x) (dac_lookup[(x)&1023]) | |||
#define __dac_to_16bit_equiv(x) (dac_lookup[(x)&1023]) | |||
#elif DAC_TYPE == 2 | |||
/* MAX504, true 10-bit DAC */ | |||
#define DAC_BITS 10 | |||
#define __dac_to_spi_cmd(x) ((x) << 2) | |||
#define __dac_to_16bit_equiv(x) ((x) << 6) | |||
#else | |||
#error Unknown DAC type | |||
#endif | |||
#define DAC_LOW 0 | |||
#define DAC_HIGH ((uint16_t)(((uint32_t)1 << DAC_BITS) - 1)) | |||
#define DAC_MID ((uint16_t)((DAC_LOW + DAC_HIGH + (uint32_t)1) / 2)) | |||
#define DAC_RANGE (DAC_HIGH - DAC_LOW + 1) | |||
/* Initialize DAC (AD5542) */ | |||
void dac_init(void); | |||
/* Write raw value to DAC: | |||
DAC_HIGH 4.9998v | |||
DAC_MID 0v | |||
DAC_LOW -5v | |||
*/ | |||
void dac_write(uint16_t val); | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a 16-bit value, where: | |||
0xffff = 4.9998v | |||
0x8000 = 0v | |||
0x0000 = -5v | |||
*/ | |||
uint16_t dac_get_actual_16bit(uint16_t val); | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a FLOAT, where: | |||
DAC_HIGH = 4.9998v | |||
DAC_MID = 0v | |||
DAC_LOW = -5v | |||
Example: for 10-bit DAC, convert integer command 123 into | |||
the more accurate 123.45 using known lower bits. | |||
*/ | |||
float dac_get_actual_float(uint16_t val); | |||
#endif | |||
#ifndef DAC_H | |||
#define DAC_H | |||
#include "config.h" | |||
#define DAC_TYPE 2 | |||
#if DAC_TYPE == 0 | |||
/* AD5542, normal 16-bit range */ | |||
#define DAC_BITS 16 | |||
#define __dac_to_spi_cmd(x) (x) | |||
#define __dac_to_16bit_equiv(x) (x) | |||
#elif DAC_TYPE == 1 | |||
/* AD5542, fake 10-bit range using random lower bits */ | |||
#define DAC_BITS 10 | |||
#define __dac_to_spi_cmd(x) (dac_lookup[(x)&1023]) | |||
#define __dac_to_16bit_equiv(x) (dac_lookup[(x)&1023]) | |||
#elif DAC_TYPE == 2 | |||
/* MAX504, true 10-bit DAC */ | |||
#define DAC_BITS 10 | |||
#define __dac_to_spi_cmd(x) ((x) << 2) | |||
#define __dac_to_16bit_equiv(x) ((x) << 6) | |||
#else | |||
#error Unknown DAC type | |||
#endif | |||
#define DAC_LOW 0 | |||
#define DAC_HIGH ((uint16_t)(((uint32_t)1 << DAC_BITS) - 1)) | |||
#define DAC_MID ((uint16_t)((DAC_LOW + DAC_HIGH + (uint32_t)1) / 2)) | |||
#define DAC_RANGE (DAC_HIGH - DAC_LOW + 1) | |||
/* Initialize DAC (AD5542) */ | |||
void dac_init(void); | |||
/* Write raw value to DAC: | |||
DAC_HIGH 4.9998v | |||
DAC_MID 0v | |||
DAC_LOW -5v | |||
*/ | |||
void dac_write(uint16_t val); | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a 16-bit value, where: | |||
0xffff = 4.9998v | |||
0x8000 = 0v | |||
0x0000 = -5v | |||
*/ | |||
uint16_t dac_get_actual_16bit(uint16_t val); | |||
/* Given a DAC command between DAC_LOW and DAC_HIGH, | |||
get the actual expected output voltage as a FLOAT, where: | |||
DAC_HIGH = 4.9998v | |||
DAC_MID = 0v | |||
DAC_LOW = -5v | |||
Example: for 10-bit DAC, convert integer command 123 into | |||
the more accurate 123.45 using known lower bits. | |||
*/ | |||
float dac_get_actual_float(uint16_t val); | |||
#endif |
@@ -1,40 +1,40 @@ | |||
#include "config.h" | |||
#include "led.h" | |||
#include "timer.h" | |||
int16_t __led_pattern; | |||
/* Debug LED */ | |||
void TISR_HANDLER(6) | |||
{ | |||
timer_clear_txif(6); | |||
if (__led_pattern == -1) | |||
return; | |||
__led_pattern <<= 1; | |||
if (__led_pattern & 0x100) { | |||
PORTBbits.RB13 = 0; /* on */ | |||
__led_pattern |= 1; | |||
} else { | |||
PORTBbits.RB13 = 1; /* off */ | |||
} | |||
__led_pattern &= 0xff; | |||
} | |||
void led_init(void) | |||
{ | |||
TRISBbits.TRISB13 = 0; | |||
PORTBbits.RB13 = 1; | |||
__led_pattern = -1; | |||
timer_setup_16bit(6, LED_BLINK_RATE, 1); | |||
timer_set_priority(6, 1); /* low priority */ | |||
} | |||
/* Set a pattern (8-bit binary pattern). */ | |||
void led_pattern(uint8_t pattern) | |||
{ | |||
__led_pattern = pattern; | |||
} | |||
#include "config.h" | |||
#include "led.h" | |||
#include "timer.h" | |||
int16_t __led_pattern; | |||
/* Debug LED */ | |||
void TISR_HANDLER(6) | |||
{ | |||
timer_clear_txif(6); | |||
if (__led_pattern == -1) | |||
return; | |||
__led_pattern <<= 1; | |||
if (__led_pattern & 0x100) { | |||
PORTBbits.RB13 = 0; /* on */ | |||
__led_pattern |= 1; | |||
} else { | |||
PORTBbits.RB13 = 1; /* off */ | |||
} | |||
__led_pattern &= 0xff; | |||
} | |||
void led_init(void) | |||
{ | |||
TRISBbits.TRISB13 = 0; | |||
PORTBbits.RB13 = 1; | |||
__led_pattern = -1; | |||
timer_setup_16bit(6, LED_BLINK_RATE, 1); | |||
timer_set_priority(6, 1); /* low priority */ | |||
} | |||
/* Set a pattern (8-bit binary pattern). */ | |||
void led_pattern(uint8_t pattern) | |||
{ | |||
__led_pattern = pattern; | |||
} |
@@ -1,27 +1,27 @@ | |||
#ifndef LED_H | |||
#define LED_H | |||
#include "config.h" | |||
#define LED_BLINK_RATE 8 | |||
extern int16_t __led_pattern; | |||
/* Initialize LED */ | |||
void led_init(void); | |||
/* Set a pattern (8-bit binary pattern). */ | |||
void led_pattern(uint8_t pattern); | |||
static inline void led_on(void) | |||
{ | |||
__led_pattern = -1; | |||
PORTBbits.RB13 = 0; | |||
} | |||
static inline void led_off(void) | |||
{ | |||
__led_pattern = -1; | |||
PORTBbits.RB13 = 1; | |||
} | |||
#endif | |||
#ifndef LED_H | |||
#define LED_H | |||
#include "config.h" | |||
#define LED_BLINK_RATE 8 | |||
extern int16_t __led_pattern; | |||
/* Initialize LED */ | |||
void led_init(void); | |||
/* Set a pattern (8-bit binary pattern). */ | |||
void led_pattern(uint8_t pattern); | |||
static inline void led_on(void) | |||
{ | |||
__led_pattern = -1; | |||
PORTBbits.RB13 = 0; | |||
} | |||
static inline void led_off(void) | |||
{ | |||
__led_pattern = -1; | |||
PORTBbits.RB13 = 1; | |||
} | |||
#endif |
@@ -1,9 +1,9 @@ | |||
#ifndef MODE_H | |||
#define MODE_H | |||
#include "config.h" | |||
void run_normal(void); | |||
void run_debug(void); | |||
#endif | |||
#ifndef MODE_H | |||
#define MODE_H | |||
#include "config.h" | |||
void run_normal(void); | |||
void run_debug(void); | |||
#endif |
@@ -1,167 +1,167 @@ | |||
#include "config.h" | |||
#include "adc.h" | |||
#include "dac.h" | |||
#include "uart.h" | |||
#include "timer.h" | |||
#include <stdio.h> | |||
#include <math.h> | |||
#include "calibrate.h" | |||
#include "util.h" | |||
#include "led.h" | |||
#include "mode.h" | |||
#include "zoom.h" | |||
static uint16_t dac = DAC_MID; | |||
void sweep(void) | |||
{ | |||
int32_t d; | |||
int16_t a; | |||
#define SWEEP ((DAC_HIGH - DAC_LOW) * (int32_t)2000 / 65535) | |||
/* sweep range */ | |||
for (d = (int32_t)dac - SWEEP; d < (int32_t)dac + SWEEP; d++) { | |||
if (d < DAC_LOW) { | |||
uart1_put_dec(DAC_LOW); | |||
uart1_put_string(" 0\r\n"); | |||
continue; | |||
} | |||
if (d > DAC_HIGH) { | |||
uart1_put_dec(DAC_HIGH); | |||
uart1_put_string(" 0\r\n"); | |||
continue; | |||
} | |||
dac_write(d); | |||
msleep(1); | |||
a = adc_get(); | |||
uart1_put_dec(d); | |||
uart1_put(' '); | |||
uart1_put_dec(a); | |||
uart1_crlf(); | |||
} | |||
} | |||
void run_debug(void) | |||
{ | |||
int16_t adc; | |||
int32_t v; | |||
char buf[4]; | |||
uart1_init(115200); | |||
led_pattern(0b10101010); | |||
uart1_put_string("Zoom NILM Debug\r\n"); | |||
while (1) { | |||
dac = dac & DAC_HIGH; // mask off invalid bits | |||
dac_write(dac); | |||
uart1_put_hex16(dac); | |||
uart1_put(' '); | |||
uart1_put_dec(dac_get_actual_16bit(dac)); | |||
uart1_put(' '); | |||
uart1_put(' '); | |||
adc = adc_get(); | |||
uart1_put_hex16(adc); | |||
uart1_put(' '); | |||
uart1_put_dec(adc); | |||
uart1_crlf(); | |||
switch (uart1_get()) { | |||
// small step | |||
case '[': | |||
dac--; | |||
break; | |||
case ']': | |||
dac++; | |||
break; | |||
// medium step | |||
case '-': | |||
dac -= 16; | |||
break; | |||
case '+': | |||
case '=': | |||
dac += 16; | |||
break; | |||
// big step | |||
case ',': | |||
case '<': | |||
dac -= 1024; | |||
break; | |||
case '.': | |||
case '>': | |||
dac += 1024; | |||
break; | |||
// set DAC to midpoint | |||
case '0': | |||
dac = DAC_MID; | |||
break; | |||
// set DAC to specified hex value | |||
case 'v': | |||
case 'V': | |||
buf[0] = uart1_get(); | |||
buf[1] = uart1_get(); | |||
buf[2] = uart1_get(); | |||
buf[3] = uart1_get(); | |||
v = hex_to_u16(buf); | |||
if (v < 0) | |||
uart1_put_string("bad value\r\n"); | |||
else | |||
dac = v; | |||
break; | |||
// maintain ADC input at zero | |||
case 'z': | |||
case 'Z': | |||
uart1_put_string("zeroing input...\r\n"); | |||
while (!uart1_can_get()) | |||
dac = seek(dac, 1024); | |||
uart1_get(); | |||
break; | |||
// test seeking | |||
case '1': | |||
uart1_put_string("seek 512\r\n"); | |||
dac = seek(dac, 512); | |||
break; | |||
case '2': | |||
uart1_put_string("seek 1536\r\n"); | |||
dac = seek(dac, 1536); | |||
break; | |||
// run calibration | |||
case 'c': | |||
case 'C': | |||
uart1_put_string("calibrating...\r\n"); | |||
dac = do_calibrate(); | |||
uart1_put_string("new g_scale "); | |||
uart1_put_float(g_scale); | |||
uart1_crlf(); | |||
break; | |||
// sweep DAC | |||
case 's': | |||
case 'S': | |||
uart1_put_string("sweep around "); | |||
uart1_put_dec(dac); | |||
uart1_crlf(); | |||
sweep(); | |||
break; | |||
// dump raw ADC value | |||
case 'r': | |||
case 'R': | |||
while(!uart1_can_get()) { | |||
uart1_put_hex16(adc_get_raw()); | |||
uart1_crlf(); | |||
} | |||
uart1_get(); | |||
break; | |||
} | |||
} | |||
} | |||
#include "config.h" | |||
#include "adc.h" | |||
#include "dac.h" | |||
#include "uart.h" | |||
#include "timer.h" | |||
#include <stdio.h> | |||
#include <math.h> | |||
#include "calibrate.h" | |||
#include "util.h" | |||
#include "led.h" | |||
#include "mode.h" | |||
#include "zoom.h" | |||
static uint16_t dac = DAC_MID; | |||
void sweep(void) | |||
{ | |||
int32_t d; | |||
int16_t a; | |||
#define SWEEP ((DAC_HIGH - DAC_LOW) * (int32_t)2000 / 65535) | |||
/* sweep range */ | |||
for (d = (int32_t)dac - SWEEP; d < (int32_t)dac + SWEEP; d++) { | |||
if (d < DAC_LOW) { | |||
uart1_put_dec(DAC_LOW); | |||
uart1_put_string(" 0\r\n"); | |||
continue; | |||
} | |||
if (d > DAC_HIGH) { | |||
uart1_put_dec(DAC_HIGH); | |||
uart1_put_string(" 0\r\n"); | |||
continue; | |||
} | |||
dac_write(d); | |||
msleep(1); | |||
a = adc_get(); | |||
uart1_put_dec(d); | |||
uart1_put(' '); | |||
uart1_put_dec(a); | |||
uart1_crlf(); | |||
} | |||
} | |||
void run_debug(void) | |||
{ | |||
int16_t adc; | |||
int32_t v; | |||
char buf[4]; | |||
uart1_init(115200); | |||
led_pattern(0b10101010); | |||
uart1_put_string("Zoom NILM Debug\r\n"); | |||
while (1) { | |||
dac = dac & DAC_HIGH; // mask off invalid bits | |||
dac_write(dac); | |||
uart1_put_hex16(dac); | |||
uart1_put(' '); | |||
uart1_put_dec(dac_get_actual_16bit(dac)); | |||
uart1_put(' '); | |||
uart1_put(' '); | |||
adc = adc_get(); | |||
uart1_put_hex16(adc); | |||
uart1_put(' '); | |||
uart1_put_dec(adc); | |||
uart1_crlf(); | |||
switch (uart1_get()) { | |||
// small step | |||
case '[': | |||
dac--; | |||
break; | |||
case ']': | |||
dac++; | |||
break; | |||
// medium step | |||
case '-': | |||
dac -= 16; | |||
break; | |||
case '+': | |||
case '=': | |||
dac += 16; | |||
break; | |||
// big step | |||
case ',': | |||
case '<': | |||
dac -= 1024; | |||
break; | |||
case '.': | |||
case '>': | |||
dac += 1024; | |||
break; | |||
// set DAC to midpoint | |||
case '0': | |||
dac = DAC_MID; | |||
break; | |||
// set DAC to specified hex value | |||
case 'v': | |||
case 'V': | |||
buf[0] = uart1_get(); | |||
buf[1] = uart1_get(); | |||
buf[2] = uart1_get(); | |||
buf[3] = uart1_get(); | |||
v = hex_to_u16(buf); | |||
if (v < 0) | |||
uart1_put_string("bad value\r\n"); | |||
else | |||
dac = v; | |||
break; | |||
// maintain ADC input at zero | |||
case 'z': | |||
case 'Z': | |||
uart1_put_string("zeroing input...\r\n"); | |||
while (!uart1_can_get()) | |||
dac = seek(dac, 1024); | |||
uart1_get(); | |||
break; | |||
// test seeking | |||
case '1': | |||
uart1_put_string("seek 512\r\n"); | |||
dac = seek(dac, 512); | |||
break; | |||
case '2': | |||
uart1_put_string("seek 1536\r\n"); | |||
dac = seek(dac, 1536); | |||
break; | |||
// run calibration | |||
case 'c': | |||
case 'C': | |||
uart1_put_string("calibrating...\r\n"); | |||
dac = do_calibrate(); | |||
uart1_put_string("new g_scale "); | |||
uart1_put_float(g_scale); | |||
uart1_crlf(); | |||
break; | |||
// sweep DAC | |||
case 's': | |||
case 'S': | |||
uart1_put_string("sweep around "); | |||
uart1_put_dec(dac); | |||
uart1_crlf(); | |||
sweep(); | |||
break; | |||
// dump raw ADC value | |||
case 'r': | |||
case 'R': | |||
while(!uart1_can_get()) { | |||
uart1_put_hex16(adc_get_raw()); | |||
uart1_crlf(); | |||
} | |||
uart1_get(); | |||
break; | |||
} | |||
} | |||
} | |||
@@ -1,139 +1,139 @@ | |||
#include "config.h" | |||
#include "adc.h" | |||
#include "adcext.h" | |||
#include "dac.h" | |||
#include "uart.h" | |||
#include "timer.h" | |||
#include <stdio.h> | |||
#include <math.h> | |||
#include "calibrate.h" | |||
#include "util.h" | |||
#include "mode.h" | |||
#include "led.h" | |||
#include "zoom.h" | |||
#include "packet.h" | |||
int send_data = 0; | |||
uint16_t send_adc; | |||
uint16_t send_dac; | |||
int possibly_clamped = 0; | |||
uint16_t dac_cmd; | |||
#define TIMER_RATE 8000 /* how often to read the ADC and update DAC */ | |||
#define PC_RATE 8000 /* how often to send data to the PC */ | |||
#define DEBUG_ISR_TIME | |||
#define ADC_WINDOW_MIN 512 | |||
#define ADC_WINDOW_MIN_STEPTO 1280 | |||
#define ADC_WINDOW_MAX 1536 | |||
#define ADC_WINDOW_MAX_STEPTO 768 | |||
/* Run mode */ | |||
void TISR_HANDLER(5) | |||
{ | |||
static int count = 0; | |||
int16_t v; | |||
#ifdef DEBUG_ISR_TIME | |||
LATAbits.LATA9 = 1; | |||
#endif | |||
timer_clear_txif(5); | |||
/* Get most recent sample from 12-bit ADC. */ | |||
v = adc_get(); | |||
if (v < ADC_CLAMP_MIN || v >= ADC_CLAMP_MAX) | |||
possibly_clamped = 1; | |||
/* Send data to PC */ | |||
if (++count >= (TIMER_RATE / PC_RATE)) { | |||
count = 0; | |||
/* Send most recent sample and old DAC value */ | |||
send_adc = v; | |||
send_dac = dac_cmd; | |||
send_data = 1; | |||
} | |||
#define WINDOW | |||
#ifdef WINDOW | |||
/* If ADC value is outside the window, step DAC */ | |||
if (v < ADC_WINDOW_MIN) | |||
dac_cmd = adc_to_dac(dac_cmd, v, ADC_WINDOW_MIN_STEPTO, g_scale); | |||
else if (v > ADC_WINDOW_MAX) | |||
dac_cmd = adc_to_dac(dac_cmd, v, ADC_WINDOW_MAX_STEPTO, g_scale); | |||
#else | |||
dac_cmd = adc_to_dac(dac_cmd, v, 1024, g_scale); | |||
#endif | |||
/* Send it out */ | |||
dac_write(dac_cmd); | |||
#ifdef DEBUG_ISR_TIME | |||
LATAbits.LATA9 = 0; | |||
#endif | |||
} | |||
void run_normal(void) | |||
{ | |||
int i; | |||
uart1_init(500000); | |||
led_pattern(0b00110011); | |||
/* Keep writing zero to the DAC for about 30 seconds after startup, | |||
or until we receive a character on the UART */ | |||
while(uart1_can_get()) | |||
uart1_get(); | |||
for (i = 0; i < 1500; i++) { | |||
dac_write(DAC_MID); | |||
if (uart1_can_get()) { | |||
uart1_get(); | |||
break; | |||
} | |||
msleep(10); | |||
} | |||
led_on(); | |||
/* Assume startup current is 0 */ | |||
msleep(100); | |||
dac_cmd = do_calibrate(); | |||
timer_setup_16bit(5, TIMER_RATE, 1); | |||
timer_set_priority(5, 6); | |||
while(1) { | |||
if (send_data) { | |||
/* There's data to send. Disable the ISR briefly | |||
while we grab it */ | |||
uint16_t a, d, o; | |||
disable_int({ | |||
if (possibly_clamped) { | |||
/* Mark a possible overflow in the output */ | |||
o = 1; | |||
possibly_clamped = 0; | |||
} else o = 0; | |||
a = send_adc; | |||
d = send_dac; | |||
send_data = 0; | |||
}); | |||
packet_send_adc_dac(a, d, o); | |||
} | |||
if (uart1_can_get()) { | |||
switch (uart1_get()) { | |||
case 'c': | |||
disable_int({ | |||
send_data = 0; | |||
dac_cmd = do_calibrate(); | |||
}); | |||
packet_send_calibration(g_scale); | |||
default: | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
#include "config.h" | |||
#include "adc.h" | |||
#include "adcext.h" | |||
#include "dac.h" | |||
#include "uart.h" | |||
#include "timer.h" | |||
#include <stdio.h> | |||
#include <math.h> | |||
#include "calibrate.h" | |||
#include "util.h" | |||
#include "mode.h" | |||
#include "led.h" | |||
#include "zoom.h" | |||
#include "packet.h" | |||
int send_data = 0; | |||
uint16_t send_adc; | |||
uint16_t send_dac; | |||
int possibly_clamped = 0; | |||
uint16_t dac_cmd; | |||
#define TIMER_RATE 8000 /* how often to read the ADC and update DAC */ | |||
#define PC_RATE 8000 /* how often to send data to the PC */ | |||
#define DEBUG_ISR_TIME | |||
#define ADC_WINDOW_MIN 512 | |||
#define ADC_WINDOW_MIN_STEPTO 1280 | |||
#define ADC_WINDOW_MAX 1536 | |||
#define ADC_WINDOW_MAX_STEPTO 768 | |||
/* Run mode */ | |||
void TISR_HANDLER(5) | |||
{ | |||
static int count = 0; | |||
int16_t v; | |||
#ifdef DEBUG_ISR_TIME | |||
LATAbits.LATA9 = 1; | |||
#endif | |||
timer_clear_txif(5); | |||
/* Get most recent sample from 12-bit ADC. */ | |||
v = adc_get(); | |||
if (v < ADC_CLAMP_MIN || v >= ADC_CLAMP_MAX) | |||
possibly_clamped = 1; | |||
/* Send data to PC */ | |||
if (++count >= (TIMER_RATE / PC_RATE)) { | |||
count = 0; | |||
/* Send most recent sample and old DAC value */ | |||
send_adc = v; | |||
send_dac = dac_cmd; | |||
send_data = 1; | |||
} | |||
#define WINDOW | |||
#ifdef WINDOW | |||
/* If ADC value is outside the window, step DAC */ | |||
if (v < ADC_WINDOW_MIN) | |||
dac_cmd = adc_to_dac(dac_cmd, v, ADC_WINDOW_MIN_STEPTO, g_scale); | |||
else if (v > ADC_WINDOW_MAX) | |||
dac_cmd = adc_to_dac(dac_cmd, v, ADC_WINDOW_MAX_STEPTO, g_scale); | |||
#else | |||
dac_cmd = adc_to_dac(dac_cmd, v, 1024, g_scale); | |||
#endif | |||
/* Send it out */ | |||
dac_write(dac_cmd); | |||
#ifdef DEBUG_ISR_TIME | |||
LATAbits.LATA9 = 0; | |||
#endif | |||
} | |||
void run_normal(void) | |||
{ | |||
int i; | |||
uart1_init(500000); | |||
led_pattern(0b00110011); | |||
/* Keep writing zero to the DAC for about 30 seconds after startup, | |||
or until we receive a character on the UART */ | |||
while(uart1_can_get()) | |||
uart1_get(); | |||
for (i = 0; i < 1500; i++) { | |||
dac_write(DAC_MID); | |||
if (uart1_can_get()) { | |||
uart1_get(); | |||
break; | |||
} | |||
msleep(10); | |||
} | |||
led_on(); | |||
/* Assume startup current is 0 */ | |||
msleep(100); | |||
dac_cmd = do_calibrate(); | |||
timer_setup_16bit(5, TIMER_RATE, 1); | |||
timer_set_priority(5, 6); | |||
while(1) { | |||
if (send_data) { | |||
/* There's data to send. Disable the ISR briefly | |||
while we grab it */ | |||
uint16_t a, d, o; | |||
disable_int({ | |||
if (possibly_clamped) { | |||
/* Mark a possible overflow in the output */ | |||
o = 1; | |||
possibly_clamped = 0; | |||
} else o = 0; | |||
a = send_adc; | |||
d = send_dac; | |||
send_data = 0; | |||
}); | |||
packet_send_adc_dac(a, d, o); | |||
} | |||
if (uart1_can_get()) { | |||
switch (uart1_get()) { | |||
case 'c': | |||
disable_int({ | |||
send_data = 0; | |||
dac_cmd = do_calibrate(); | |||
}); | |||
packet_send_calibration(g_scale); | |||
default: | |||
break; | |||
} | |||
} | |||
} | |||
} |
@@ -1,100 +1,100 @@ | |||
#include "config.h" | |||
#include "timer.h" | |||
/* Setup a 16-bit timer to overflow at the specified frequency | |||
timer = 1-9 | |||
freq = Hz, or 0 to disable the timer. | |||
interrupt = 1 to enable interrupt, 0 to disable | |||
*/ | |||
int timer_setup_16bit(int timer, uint32_t freq, int ie) | |||
{ | |||
uint32_t period; | |||
uint16_t prescale; | |||
if (timer < 1 || timer > 9) | |||
return -1; | |||
if (freq == 0) { | |||
switch(timer) { | |||
case 1: T1CONbits.TON = 0; return 0; | |||
case 2: T2CONbits.TON = 0; return 0; | |||
case 3: T3CONbits.TON = 0; return 0; | |||
case 4: T4CONbits.TON = 0; return 0; | |||
case 5: T5CONbits.TON = 0; return 0; | |||
case 6: T6CONbits.TON = 0; return 0; | |||
case 7: T7CONbits.TON = 0; return 0; | |||
case 8: T8CONbits.TON = 0; return 0; | |||
case 9: T9CONbits.TON = 0; return 0; | |||
} | |||
} | |||
/* Figure out timer prescaler and period values. Max period | |||
is 65535 (PRx = 65534) so we can still attain 100% duty | |||
cycle when using a timer for PWM. */ | |||
if ((period = FCY / (freq * 1L)) <= 65535) | |||
prescale = 0; | |||
else if ((period = FCY / (freq * 8L)) <= 65535) | |||
prescale = 1; | |||
else if ((period = FCY / (freq * 64L)) <= 65535) | |||
prescale = 2; | |||
else if ((period = FCY / (freq * 256L)) <= 65535) | |||
prescale = 3; | |||
else | |||
prescale = 3, period = 65535; | |||
if (period > 0) | |||
period -= 1; | |||
switch (timer) { | |||
#define __timer_setup_case(x) \ | |||
case x: \ | |||
T##x##CONbits.TON = 0; \ | |||
T##x##CONbits.TCKPS = prescale; \ | |||
PR##x = period; \ | |||
T##x##CONbits.TON = 1; \ | |||
break | |||
__timer_setup_case(1); | |||
__timer_setup_case(2); | |||
__timer_setup_case(3); | |||
__timer_setup_case(4); | |||
__timer_setup_case(5); | |||
__timer_setup_case(6); | |||
__timer_setup_case(7); | |||
__timer_setup_case(8); | |||
__timer_setup_case(9); | |||
#undef __timer_setup_case | |||
} | |||
/* Enable interrupt if requested */ | |||
timer_clear_txif(timer); | |||
switch (timer) { | |||
case 1: IEC0bits.T1IE = ie; break; | |||
case 2: IEC0bits.T2IE = ie; break; | |||
case 3: IEC0bits.T3IE = ie; break; | |||
case 4: IEC1bits.T4IE = ie; break; | |||
case 5: IEC1bits.T5IE = ie; break; | |||
case 6: IEC2bits.T6IE = ie; break; | |||
case 7: IEC3bits.T7IE = ie; break; | |||
case 8: IEC3bits.T8IE = ie; break; | |||
case 9: IEC3bits.T9IE = ie; break; | |||
} | |||
return 0; | |||
} | |||
/* sleep for between "ms" and "ms+1" milliseconds */ | |||
void msleep(int ms) | |||
{ | |||
static int initialized = 0; | |||
if (!initialized) { | |||
timer_setup_16bit(1, 1000, 0); | |||
initialized = 1; | |||
} | |||
/* Very basic, assumes timer1 is set up at 1 khz */ | |||
while (ms-- >= 0) { | |||
IFS0bits.T1IF = 0; | |||
while(IFS0bits.T1IF == 0) | |||
continue; | |||
} | |||
} | |||
#include "config.h" | |||
#include "timer.h" | |||
/* Setup a 16-bit timer to overflow at the specified frequency | |||
timer = 1-9 | |||
freq = Hz, or 0 to disable the timer. | |||
interrupt = 1 to enable interrupt, 0 to disable | |||
*/ | |||
int timer_setup_16bit(int timer, uint32_t freq, int ie) | |||
{ | |||
uint32_t period; | |||
uint16_t prescale; | |||
if (timer < 1 || timer > 9) | |||
return -1; | |||
if (freq == 0) { | |||
switch(timer) { | |||
case 1: T1CONbits.TON = 0; return 0; | |||
case 2: T2CONbits.TON = 0; return 0; | |||
case 3: T3CONbits.TON = 0; return 0; | |||
case 4: T4CONbits.TON = 0; return 0; | |||
case 5: T5CONbits.TON = 0; return 0; | |||
case 6: T6CONbits.TON = 0; return 0; | |||
case 7: T7CONbits.TON = 0; return 0; | |||
case 8: T8CONbits.TON = 0; return 0; | |||
case 9: T9CONbits.TON = 0; return 0; | |||
} | |||
} | |||
/* Figure out timer prescaler and period values. Max period | |||