137 lines
3.4 KiB
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;
|
|
}
|