zoom/firmware/adcext.c

137 lines
3.4 KiB
C

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