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