Hello all,
Introduction
Here is a tutorial/recommendation for those who need a high-precision [24-bit] analog to digital converter (ADC) that is easy to use (SPI but just 2 wires, and ADC itself has only 8 pins) and is fast (40,000 samples per second). Learning to use this ADC will also be a good beginner exercise in using Serial Peripheral Interface (SPI).
I'm no expert yet, but since I've played around with this for some time, feel free to ask if you have any questions.
The ADC I use is ADS1252 from Texas Instruments. It has a maximum of 24 bit digital readout precision, which is significantly better than Arduino's inbuilt 10-bit ADC, so if you need to make some precise readings, this is useful.
I've spent the last couple of weeks working on reading the datasheet and gaining some very useful knowledge while figuring out how to code this thing, along with some help from forum members here, so now, I decided to make a small tutorial with the basic pseudocode as well as actual code.
You can see the datasheet here: http://focus.ti.com/lit/ds/symlink/ads1252.pdf
What to do
If you want to learn more details, for future use, about how exactly the interfacing (SPI) is working between the Arduino and the ADC, then read the "FULL DETAILS" section below (and consult the datasheet as well; this one's pretty simple).
By the way, a very clear tutorial on SPI by Nick Gammon is here: http://www.gammon.com.au/forum/?id=10892
But if you want to just quickly use the ADC, I suggest you simply read the ADC clock source section below, then view the Pinout diagram below and plug the wires in as shown (be sure to add capacitors as appropriate too), and finally just use the code I pasted below... And you can start reading data at 24 bit precision (kinda) from the serial terminal.
ADC Clock Source
For the clock pin of this ADC, I used 2MHz; if you use a different frequency, you have to change some timing variables in the code, as outlined in the "Full details" section. As far as how to actually supply the clock (not a typical 2-pin crystal oscillator input), you have three options (thanks BenF for suggestions):
- Use CKOUT of Arduino/Atmega chip. Not very hard but you have to set some fuse bits; You can search Google or this forum for help on this. I recommend using the option of Prescaler of 8, so the ADC gets 2MHz instead of 16MHz.
- Set up a timer for your chosen frequency on one of Arduino/Atmega's pins.
- Or easiest, buy a clock oscillator (has 4 pins, one of which goes to the CLK pin of the ADC). Buy one of low jitter, such as Crystek's C33xx or S33xx (DIP)... http://www.crystekcrystals.com/crystal/spec-sheets/clock/S33xx.pdf
Pinout diagram (from TI datasheet)
Present code
#include <SPI.h>
#define MISOPIN 12
#define SCLKPIN 13
byte byte1; byte byte2; byte byte3;
// declare 3 bytes = 24 bits
void setup()
{
Serial.begin(9600);
pinMode(SCLKPIN, OUTPUT); pinMode(MISOPIN, INPUT);
// corresponding to SCK pin and DRDY/DOUT pin on ADC
reset_adc();
// put ADC on reset at the outset
SPI.begin();
// initialize SPI (with default settings, including...
// CPOL = 0: so that SCLK is normally LOW
// CPHA = 0: data sampled on rising edge (LOW to HIGH)
// perhaps try changing CPHA ??
digitalWrite(SCLKPIN, LOW);
// release ADC from reset; now we're at a known point
// in the timing diagram, and just have to wait for
// the beginning of a conversion cycle
}
void loop()
{
if (digitalRead(MISOPIN) == HIGH) read_adc();
// "sort of" an interrupt to go to read_adc routine;
// can use hardware interrupt in future but now just poll
}
void reset_adc()
// to reset ADC, we need SCLK HIGH for min of 4 CONVCYCLES
// so here, hold SCLK HIGH for 5 CONVCYCLEs = 1440 usec
{
digitalWrite(SCLKPIN, HIGH);
delayMicroseconds(1440);
}
void read_adc()
{
drdy_wait();
// go to drdy_wait routine, where we wait for
// DRDY phase to pass, and thus for DOUT phase to begin
byte1 = SPI.transfer(0x00);
byte2 = SPI.transfer(0x00);
byte3 = SPI.transfer(0x00);
// read in adc data (sending out don't care bytes)
// and store read data into three bytes */
Serial.println(byte1, DEC);
Serial.println(byte2, DEC);
Serial.println(byte3, DEC);
Serial.println();
// print out data;
// will these instructions eat into time significantly?
// possible improvement: store all data from multiple cycles
// into array, and print out only later at end.
}
void drdy_wait()
// wait for DRDY to pass and to reach start-point of DOUT
{
delayMicroseconds(30);
// to be safe, 30 usec, instead of 27 usec, which is
// the expected period of DRDY phase
}
FULL DETAILS
My approach is partly based on TI Application Note for same ADC: http://focus.ti.com/lit/an/slaa242/slaa242.pdf
This ADC only uses two pins to interface with the Arduino/microcontroller:
-SCLK pin (for commands: Synchronization, Power-down, or RESET)
-DRDY/DOUT pin (yes, only one pin)
The ADS1252 has DRDY (converted data ready indication) and DOUT (actual data output) both on the same pin, one followed by the other in a chronologically structured way, so I can use proper timing and interrupts to check when data is ready and when to get data.
So my method is to initially have the microcontroller reset the ADC, so that we have awareness/control of the timing from then on (also see Timing diagram below)...
-At the start of a new conversion cycle, the DRDY phase lasts for 36 MCLKs (modulator cycle periods), so each cyle, we can wait/delay for the DRDY phase to simply pass.
-Then the DOUT phase starts and lasts for 348 MCLKs (typical numbers), during which we can clock out the data.
-At the end of each conversion cycle (36 + 348 = 384 MCLKs total), there is a low-to-high (RISING edge) in the transition from the end of DOUT to the beginning of DRDY, which we can detect using a RISING-edge interrupt/poll on the DRDY/DOUT line.
-And then the next conversion cycle begins, and so on.
Timing diagram (from TI datasheet)
The math
I'm having the ADC clocked using CKOUT of 16MHz Atmega chip, with prescaler of 8, so it's clocked with f_ADSCLK = 2 MHz, implying...
See datasheet for details on math below: http://focus.ti.com/lit/ds/symlink/ads1252.pdf
calculation formulas:
f_MCLK = f_ADSCLK / 6
DRATE = f_MCLK / 64
1 p_MCLK = 1 / f_MCLK
1 DRDY partition = 36 * p_MCLK
1 DOUT partition = 348 * p_MCLK
1 CONVCYCLE = 1 DRDY partition + 1 DOUT partition = 384 * p_MCLK
calculation with actual values:
f_ADSCLK = 2 MHz
f_MCLK = 0.333 MHz
DRATE = 5.21 KHz
p_MCLK = 0.75 usec
1 CONVCYCLE = 384 * p_MCLK = 288 usec
1 DRDY partition = 36 * p_MCLK = 27 usec
1 DOUT partition = 348 * p_MCLK = 261 usec
t_RESET_5 = 5 * CONVCYCLE = 1440 usec
PSEUDOCODE
MY INITIAL SEQUENCE
(1)
ACTION: reset by holding SCLK high for at least 4 conversion cycles
With 1 conversion cycle = 1 DRDY + 1 DOUT = 36 MCLK + 348 MCLK = 384 MCLK
RESULT: ADC is held into reset
(2)
ACTION: write SCLK LOW after holding it HIGH for 4 conversion cycles
RESULT: ADC is released from reset and begins operation
Then, for each of the conversion cycles, I do the following in a loop...
LOOP begins here
(1)
time_position: 0th MCLK; Beginning of conversion cycle, i.e., beginning of DRDY partition
ACTION: delay for an amount of time equivalent to 36 MCLK cycles
result: remaining time (36 MCLK cycles) of DRDY partition simply passes
(2)
time_position: 37th MCLK; End of DRDY; Beginning of DOUT partition
ACTION: read 24 bits of data
result: data read into microcontroller memory
(note: due to filter, data only starting from the 6th conversion cycle after reset is valid; see datasheet for details regarding the digital filter)
(3)
time_position: nth MCLK (post data-read-time); Somewhere in the middle of DOUT partition
ACTION: wait until interrupt triggered on DOUT/DRDY-line going from LOW to HIGH (i.e., RISING)
result: remaining time (post data-read-time) of DOUT partition simply passes
LOOP ends here