git-svn-id: https://bucket.mit.edu/svn/nilm/zoom@6896 ddd99763-3ecb-0310-9145-efcb8ce7c51ftags/zoom-1.0
@@ -2,93 +2,39 @@ | |||||
#include "adc.h" | #include "adc.h" | ||||
#include "timer.h" | #include "timer.h" | ||||
/* Layout of buffer: | |||||
adc_dmabuf[x] contains 16 samples of channel AN0 | |||||
*/ | |||||
uint16_t adc_dmabuf[16] __attribute__((space(dma))); | |||||
/* Currently using software SPI because it's easy. | |||||
Consider using hardware SPI with DMA on a timer, though. */ | |||||
/* Stores the offset of the most recently written DMA sample */ | |||||
int adc_offset = 0; | |||||
#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_adc_callback)(void) = 0; | |||||
void (*adc_dma_callback)(void) = 0; | |||||
/* ADC1 interrupt after each sample */ | |||||
void __attribute__((__interrupt__,auto_psv)) _ADC1Interrupt(void) | |||||
{ | |||||
static int next_adc_offset = 0; | |||||
adc_offset = next_adc_offset; | |||||
/* DMA0STA points to the next sample that will be written. | |||||
Convert that into a buffer offset that we'll for next time */ | |||||
next_adc_offset = (DMA0STA - __builtin_dmaoffset(adc_dmabuf)) >> 1; | |||||
IFS0bits.AD1IF = 0; | |||||
if (adc_adc_callback) | |||||
(*adc_adc_callback)(); | |||||
} | |||||
/* DMA0 interrupt after 16 samples */ | |||||
void __attribute__((__interrupt__,auto_psv)) _DMA0Interrupt(void) | |||||
void adc_init(void) | |||||
{ | { | ||||
IFS0bits.DMA0IF = 0; | |||||
if (adc_dma_callback) | |||||
(*adc_dma_callback)(); | |||||
TRIS_SDATA = 1; | |||||
LAT_CS = IO_HIGH; | |||||
TRIS_CS = 0; | |||||
LAT_SCLK = IO_HIGH; | |||||
TRIS_SCLK = 0; | |||||
} | } | ||||
/* Initialize ADC1 to constantly DMA AN0. */ | |||||
void adc_init(void) | |||||
uint16_t adc_get(void) | |||||
{ | { | ||||
/* AD1CON1 */ | |||||
AD1CON1bits.ADDMABM = 1; /* Scatter-gather DMA */ | |||||
AD1CON1bits.AD12B = 1; /* 12-bit, 1 channel */ | |||||
AD1CON1bits.FORM = 0; /* Integer output 0000dddddddddddd */ | |||||
AD1CON1bits.SSRC = 2; /* Convert on Timer3 */ | |||||
AD1CON1bits.ASAM = 1; /* Automatically start sampling */ | |||||
AD1CON1bits.SIMSAM = 0; /* Simul sampling, N/A when AD12B=1 */ | |||||
/* AD1CON2 */ | |||||
AD1CON2bits.VCFG = 0; /* Ref: AVDD / AVSS */ | |||||
// AD1CON2bits.VCFG = 3; /* Ref: VREF+ / VREF- */ | |||||
AD1CON2bits.CSCNA = 0; /* Do not scan inputs on Mux A */ | |||||
AD1CON2bits.CHPS = 0; /* Convert CH0 only, N/A when AD12B=1 */ | |||||
AD1CON2bits.SMPI = 0; /* Increase DMA address at each sample */ | |||||
AD1CON2bits.BUFM = 0; /* Fill buffer from start */ | |||||
AD1CON2bits.ALTS = 0; /* Always use Mux A settings */ | |||||
/* AD1CON3 */ | |||||
AD1CON3bits.ADRC = 0; /* Use system clock, not internal RC */ | |||||
AD1CON3bits.SAMC = 0; /* Sample time, N/A when ADRC = 0 */ | |||||
AD1CON3bits.ADCS = 0x7; /* Tad = 8 * Tcy */ | |||||
/* AD1CON4 */ | |||||
AD1CON4bits.DMABL = 4; /* Each input gets 16 words in DMA buf */ | |||||
/* Channel 0 setup */ | |||||
AD1CHS0bits.CH0NA = 0; /* CH0-: Vrefl */ | |||||
AD1CHS0bits.CH0SA = 0; /* CH0+: AN0 */ | |||||
/* Set Timer3 to trigger conversion at 128KHz */ | |||||
timer_setup_16bit(3, 128000, 0); | |||||
/* Turn it on */ | |||||
AD1PCFGL = 0xfffe; /* Analog = AN0 */ | |||||
AD1PCFGH = 0xffff; | |||||
AD2PCFGL = 0xffff; | |||||
AD1CON1bits.ADON = 1; | |||||
/* DMA0 setup */ | |||||
DMA0CONbits.AMODE = 0; /* Register indirect, post-increment */ | |||||
DMA0CONbits.MODE = 0; /* Continuous, no ping-pong */ | |||||
DMA0PAD = (int)&ADC1BUF0; /* Get addresses from ADC1 */ | |||||
DMA0CNT = 15; /* 16 transfers before wraparound */ | |||||
DMA0STA = __builtin_dmaoffset(adc_dmabuf); | |||||
DMA0REQbits.IRQSEL = 13; /* Triggered by ADC1 */ | |||||
IFS0bits.DMA0IF = 0; | |||||
IEC0bits.DMA0IE = 1; /* DMA interrupt (every 16 samples) */ | |||||
DMA0CONbits.CHEN = 1; /* enable this DMA channel */ | |||||
IFS0bits.AD1IF = 0; | |||||
IEC0bits.AD1IE = 1; /* ADC interrupt (every sample) */ | |||||
uint16_t v = 0; | |||||
int i; | |||||
LAT_CS = 0; | |||||
for (i = 0; i < 16; i++) { | |||||
if (R_SDATA) | |||||
v |= 1; | |||||
v <<= 1; | |||||
LAT_SCLK = 0; | |||||
nop(); | |||||
LAT_SCLK = 1; | |||||
} | |||||
LAT_CS = 1; | |||||
return v; | |||||
} | } |
@@ -1,17 +1,12 @@ | |||||
#ifndef ADC_H | #ifndef ADC_H | ||||
#define ADC_H | #define ADC_H | ||||
/* adc_dmabuf[x] contains 16 samples of channel AN0 */ | |||||
extern uint16_t adc_dmabuf[16]; | |||||
#include "config.h" | |||||
/* Stores the offset of the most recently written DMA sample */ | |||||
extern int adc_offset; | |||||
/* Initialize internal ADC */ | |||||
/* Initialize external 12-bit ADC (AD7450R) */ | |||||
void adc_init(void); | void adc_init(void); | ||||
/* Callback functions */ | |||||
extern void (*adc_adc_callback)(void); | |||||
extern void (*adc_dma_callback)(void); | |||||
/* Trigger conversion and return it */ | |||||
uint16_t adc_get(void); | |||||
#endif | #endif |
@@ -11,15 +11,12 @@ | |||||
tie ADC SDI for rate selection, tie /CS low, use BUSY for | tie ADC SDI for rate selection, tie /CS low, use BUSY for | ||||
interrupt notification if desired */ | interrupt notification if desired */ | ||||
/* Since the ADC is 5v, ADC SDO must be on a digital-only pin. | |||||
All exposed pins on B D and E are analog, so use SPI pins: | |||||
ADC SDO = PIC SDI1 = RF7 | |||||
ADC SCK = PIC SCK1 = RF6 | |||||
*/ | |||||
#define TRIS_SDO TRISFbits.TRISF7 | |||||
#define R_SDO PORTFbits.RF7 | |||||
#define TRIS_SCK TRISFbits.TRISF6 | |||||
#define LAT_SCK LATFbits.LATF6 | |||||
#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 */ | /* Short delays */ | ||||
#define wait_200ns() do { \ | #define wait_200ns() do { \ | ||||
@@ -33,8 +30,10 @@ void adcext_init(void) | |||||
int32_t i; | int32_t i; | ||||
TRIS_SDO = 1; | TRIS_SDO = 1; | ||||
LAT_SCK = 0; | |||||
LAT_SCK = IO_HIGH; | |||||
TRIS_SCK = 0; | TRIS_SCK = 0; | ||||
LAT_SCS = IO_LOW; | |||||
TRIS_SCS = 0; | |||||
/* Startup delay CS down to SCK high t4 = 5000ns */ | /* Startup delay CS down to SCK high t4 = 5000ns */ | ||||
for (i = 0; i < 5000 / 25; i++) | for (i = 0; i < 5000 / 25; i++) | ||||
@@ -60,13 +59,13 @@ uint32_t adcext_read(void) | |||||
int i; | int i; | ||||
/* Start conversion by completing previous read */ | /* Start conversion by completing previous read */ | ||||
LAT_SCK = 0; | |||||
LAT_SCK = IO_LOW; | |||||
/* Wait tKQMAX for SCK down to SDO valid */ | /* Wait tKQMAX for SCK down to SDO valid */ | ||||
wait_200ns(); | wait_200ns(); | ||||
/* Wait for conversion to finish */ | /* Wait for conversion to finish */ | ||||
while (R_SDO == 1) | |||||
while (R_SDO == IO_HIGH) | |||||
continue; | continue; | ||||
/* Read it out */ | /* Read it out */ | ||||
@@ -74,7 +73,7 @@ uint32_t adcext_read(void) | |||||
for (i = 0; i < 32; i++) { | for (i = 0; i < 32; i++) { | ||||
/* SCK low tLESCK = 25ns */ | /* SCK low tLESCK = 25ns */ | ||||
wait_25ns(); | wait_25ns(); | ||||
LAT_SCK = 1; | |||||
LAT_SCK = IO_HIGH; | |||||
/* SCK high tHESCK = 25ns, but | /* SCK high tHESCK = 25ns, but | ||||
we also have SCK down to SDO valid tKQMAX = 200ns? | we also have SCK down to SDO valid tKQMAX = 200ns? | ||||
@@ -82,11 +81,11 @@ uint32_t adcext_read(void) | |||||
wait_200ns(); | wait_200ns(); | ||||
val <<= 1; | val <<= 1; | ||||
if (R_SDO) | |||||
if (R_SDO == IO_HIGH) | |||||
val |= 1; | val |= 1; | ||||
/* Leave SCK high on final bit to delay new conversion */ | /* Leave SCK high on final bit to delay new conversion */ | ||||
if (i < 31) | if (i < 31) | ||||
LAT_SCK = 0; | |||||
LAT_SCK = IO_LOW; | |||||
} | } | ||||
/* Done */ | /* Done */ | ||||
@@ -14,6 +14,11 @@ typedef unsigned long long uint64_t; | |||||
#define FCY 40000000 | #define FCY 40000000 | ||||
/* define as 0/1 to invert I/O polarity for optocouplers | |||||
define as 1/0 for normal polarity */ | |||||
#define IO_HIGH 0 | |||||
#define IO_LOW 1 | |||||
void config_init(void); | void config_init(void); | ||||
#define nop() __asm("nop") | #define nop() __asm("nop") | ||||
@@ -13,7 +13,7 @@ void dac_init(void) | |||||
SPI2CON1bits.SMP = 0; | SPI2CON1bits.SMP = 0; | ||||
SPI2CON1bits.CKE = 0; | SPI2CON1bits.CKE = 0; | ||||
SPI2CON1bits.SSEN = 0; | SPI2CON1bits.SSEN = 0; | ||||
SPI2CON1bits.CKP = 1; | |||||
SPI2CON1bits.CKP = IO_HIGH; | |||||
SPI2CON1bits.MSTEN = 1; | SPI2CON1bits.MSTEN = 1; | ||||
SPI2CON1bits.SPRE = 4; | SPI2CON1bits.SPRE = 4; | ||||
SPI2CON1bits.PPRE = 3; | SPI2CON1bits.PPRE = 3; | ||||
@@ -21,7 +21,7 @@ void dac_init(void) | |||||
/* There's no framed mode that does the normal | /* There's no framed mode that does the normal | ||||
"chip select" behavior, so control /SS2 manually */ | "chip select" behavior, so control /SS2 manually */ | ||||
SPI2CON2bits.FRMEN = 0; | SPI2CON2bits.FRMEN = 0; | ||||
LATGbits.LATG9 = 1; | |||||
LATGbits.LATG9 = IO_HIGH; | |||||
TRISGbits.TRISG9 = 0; | TRISGbits.TRISG9 = 0; | ||||
SPI2STATbits.SPISIDL = 0; | SPI2STATbits.SPISIDL = 0; | ||||
@@ -37,10 +37,13 @@ void dac_init(void) | |||||
*/ | */ | ||||
void dac_write(uint16_t val) | void dac_write(uint16_t val) | ||||
{ | { | ||||
LATGbits.LATG9 = 0; | |||||
SPI2BUF = val; | |||||
LATGbits.LATG9 = IO_LOW; | |||||
if (IO_HIGH == 1) | |||||
SPI2BUF = val; | |||||
else | |||||
SPI2BUF = ~val; | |||||
while (!SPI2STATbits.SPIRBF) | while (!SPI2STATbits.SPIRBF) | ||||
continue; | continue; | ||||
(void) SPI2BUF; | (void) SPI2BUF; | ||||
LATGbits.LATG9 = 1; | |||||
LATGbits.LATG9 = IO_HIGH; | |||||
} | } |
@@ -22,66 +22,41 @@ uint16_t send_adc, send_dac; | |||||
uint16_t dac_cmd = ((uint32_t)DAC_MAX + DAC_MIN) / 2; | uint16_t dac_cmd = ((uint32_t)DAC_MAX + DAC_MIN) / 2; | ||||
/* 128 KHz, after each ADC sample */ | |||||
void fast_callback(void) | |||||
/* 64 KHz */ | |||||
void timer(void) | |||||
{ | { | ||||
/* Run at 64 KHz */ | |||||
static int count = 0; | static int count = 0; | ||||
if (count++ < 1) return; | |||||
count = 0; | |||||
/* Get most recent sample. */ | |||||
uint16_t v = adc_dmabuf[adc_offset]; | |||||
uint16_t v; | |||||
int32_t adjustment; | |||||
int32_t newdac; | |||||
/* Get most recent sample from 12-bit ADC. */ | |||||
v = adc_get(); | |||||
/* Send data to PC at 8 KHz */ | |||||
if (count++ >= 8) { | |||||
count = 0; | |||||
/* Send most recent sample and old DAC value */ | |||||
send_adc = v; | |||||
send_dac = dac_cmd; | |||||
send_data = 1; | |||||
} | |||||
#if 1 | |||||
/* Update DAC. Add whatever is necessary to cause the ADC | /* Update DAC. Add whatever is necessary to cause the ADC | ||||
to change by 150% towards the center of 0x0800 | to change by 150% towards the center of 0x0800 | ||||
(intentional overshoot to increase slew rate capability) */ | (intentional overshoot to increase slew rate capability) */ | ||||
{ | |||||
int32_t adjustment = ((int32_t) v - 0x0800) * 1.50 * ADC_DAC_SCALING; | |||||
int32_t newdac = dac_cmd + adjustment; | |||||
//if (adjustment) { | |||||
if (newdac < DAC_MIN) | |||||
dac_cmd = DAC_MIN; | |||||
else if (newdac > DAC_MAX) | |||||
dac_cmd = DAC_MAX; | |||||
else | |||||
dac_cmd = newdac; | |||||
dac_write(dac_cmd); | |||||
//} | |||||
adjustment = ((int32_t) v - 0x0800) * 1.50 * ADC_DAC_SCALING; | |||||
newdac = dac_cmd + adjustment; | |||||
if (1 || adjustment) { | |||||
if (newdac < DAC_MIN) | |||||
dac_cmd = DAC_MIN; | |||||
else if (newdac > DAC_MAX) | |||||
dac_cmd = DAC_MAX; | |||||
else | |||||
dac_cmd = newdac; | |||||
dac_write(dac_cmd); | |||||
} | } | ||||
#endif | |||||
#if 0 | |||||
/* Update DAC if necessary */ | |||||
if (v < ADC_BIGMIN && dac_cmd < (DAC_MAX - DAC_BIGSTEP)) | |||||
dac_cmd += DAC_BIGSTEP; | |||||
else if (v < ADC_MIN && dac_cmd < (DAC_MAX - DAC_STEP)) | |||||
dac_cmd += DAC_STEP; | |||||
else if (v > ADC_BIGMAX && dac_cmd > (DAC_MIN + DAC_BIGSTEP)) | |||||
dac_cmd -= DAC_BIGSTEP; | |||||
else if (v > ADC_MAX && dac_cmd > (DAC_MIN + DAC_STEP)) | |||||
dac_cmd -= DAC_STEP; | |||||
else | |||||
return; | |||||
dac_write(dac_cmd); | |||||
#endif | |||||
} | |||||
/* 8 KHz, after DMA buffer filled */ | |||||
void slow_callback(void) | |||||
{ | |||||
/* Get most recent sample. */ | |||||
uint16_t v = adc_dmabuf[adc_offset]; | |||||
/* Send most recent sample to PC */ | |||||
send_adc = v; | |||||
send_dac = dac_cmd; | |||||
send_data = 1; | |||||
} | |||||
void debug_callback(void) | |||||
{ | |||||
send_adc = adc_dmabuf[adc_offset]; | |||||
} | } | ||||
void send_to_pc(void) | void send_to_pc(void) | ||||
@@ -97,65 +72,74 @@ void send_to_pc(void) | |||||
uart1_put((send_dac & 0x007F)); | uart1_put((send_dac & 0x007F)); | ||||
} | } | ||||
void run_debug(void) | |||||
{ | |||||
uint16_t dac = 32768; | |||||
uart1_init(115200); | |||||
while (1) { | |||||
dac_write(dac); | |||||
uart1_put_dec(dac); | |||||
uart1_put(' '); | |||||
uart1_put_hex16(adc_get()); | |||||
uart1_crlf(); | |||||
switch (uart1_get()) { | |||||
case '[': | |||||
dac--; | |||||
break; | |||||
case ']': | |||||
dac++; | |||||
break; | |||||
case '-': | |||||
dac -= 16; | |||||
break; | |||||
case '+': | |||||
case '=': | |||||
dac += 16; | |||||
break; | |||||
case ',': | |||||
case '<': | |||||
dac -= 1024; | |||||
break; | |||||
case '.': | |||||
case '>': | |||||
dac += 1024; | |||||
break; | |||||
case '0': | |||||
dac = 32768; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
void run_normal(void) | |||||
{ | |||||
uart1_init(500000); | |||||
while(1) { | |||||
if (send_data) { | |||||
send_to_pc(); | |||||
send_data = 0; | |||||
} | |||||
} | |||||
} | |||||
int main(void) | int main(void) | ||||
{ | { | ||||
int debug = 0; | |||||
int i; | |||||
config_init(); | config_init(); | ||||
adcext_init(); | adcext_init(); | ||||
dac_init(); | dac_init(); | ||||
adc_init(); | adc_init(); | ||||
if (debug) { | |||||
uart1_init(115200); | |||||
adc_dma_callback = debug_callback; | |||||
uint16_t dac = 32768; | |||||
while (1) { | |||||
dac_write(dac); | |||||
uart1_put_dec(dac); | |||||
uart1_put(' '); | |||||
uart1_put_dec(send_adc); | |||||
uart1_crlf(); | |||||
switch (uart1_get()) { | |||||
case '[': | |||||
dac--; | |||||
break; | |||||
case ']': | |||||
dac++; | |||||
break; | |||||
case '-': | |||||
dac -= 16; | |||||
break; | |||||
case '+': | |||||
case '=': | |||||
dac += 16; | |||||
break; | |||||
case ',': | |||||
case '<': | |||||
dac -= 1024; | |||||
break; | |||||
case '.': | |||||
case '>': | |||||
dac += 1024; | |||||
break; | |||||
case '0': | |||||
dac = 32768; | |||||
break; | |||||
} | |||||
} | |||||
} else { | |||||
uart1_init(500000); | |||||
adc_adc_callback = fast_callback; | |||||
adc_dma_callback = slow_callback; | |||||
/* Pull PGD high internally. If it is externally tied | |||||
to ground, run in special debug mode. */ | |||||
TRISCbits.TRISC13 = 1; | |||||
CNPU1bits.CN1PUE = 1; | |||||
if (PORTCbits.RC13 == 0) | |||||
run_debug(); | |||||
else | |||||
run_normal(); | |||||
while(1) { | |||||
if (send_data) { | |||||
send_to_pc(); | |||||
send_data = 0; | |||||
} | |||||
} | |||||
} | |||||
for (;;) | |||||
continue; | |||||
} | } |