Pages: [1] 2 3 4   Go Down
Author Topic: >Working, with library!< Interfacing ADS1213 22-bit ADC  (Read 15574 times)
0 Members and 1 Guest are viewing this topic.
Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

EDIT: The problems were fixed (it was some bad coding, confusion by direct port manipulation and ultimately an OR operation instead of an AND operation with direct port manipulation). A library has been written and is dowloadable here. Place it in the library folder of your Arduino install. It includes a readme with explanation of all functions, a sample sketch for channel selection, one for standard deviation and the datasheet. It can be easily adjusted for other, similar ADC chips. See the post here for more info.
Update 28/3/'12: Made compatible with Arduino 1.0, is still compatible with earlier versions. Nothing really changed except for the name of the Arduino library which was changed from Wiring.h to Arduino.h.


Hi, a few weeks ago I ordered an Arduino and since I've been really getting in to it. I've already used various sensors, used a LCD and written software for a new instrument I'm developing, including various menus and settings and data logging to EEPROM (actually I had already written a large part of it before the Arduino even arrived). The instrument I'm developing is for gliding, I want to measure the temperature of the air to detect thermals. So far we have always used variometers which detect the vertical speed, but the temperature is also a very valuable source of information as the increase in temperature is measurable outside the actual lift-sink boundary of the thermal.

My instrument will combine the variometer and the temperature sensor in a single audible signal, for easy processing by humans. A temperature measuring device has been made in the past but the problem was that it was a stand-alone device with its own audible signal, and it was really hard to concentrate on both the vario and temperature sound.

With the internal 10-bit ADC I'm able to get a .1*C sensitivity. It gives erratic data if the temperature is between 2 points, and I need more accuracy anyway. With more accuracy I could also implement some filters to filter noise and adjust sensitivity. So I ordered a Texas Instruments ADS1213 (was the only decent ADC available on ebay and had to buy it on ebay, because mouser, farnell and such charge insane shipping costs), which is accessible by SPI. It also has a very high sample rate of 1k SPS at an effective resolution of 16 bits and something like 3.5SPS at an amazing 22 bits. The fastest setting would allow me to get an accuracy of 65 times the Arduino's internal ADC, which equals .0015 *C. I spent yesterday reading the datasheet and trying to figure out how to make it work.

I did the following:
- As the ADS1213 needs an external clock, I hacked together some code to configure Timer2 to output a 1MHz signal (the recommended nominal value).
- I hacked together some other code to try to bit-bang SPI. In part because setting Timer2 to 1MHz makes pin 11 unusable too, and that's one of the hardware SPI ports. In the datasheet there were some minimum times and as I was worried that the Arduino would switch signals too fast, I built in some delays. Also, the code I took was written for a MCP3208 which changes data on the falling side of the SPI clock signal, and I modified it to change data on the rising side of the SPI clock signal.

Here's my pin connections and setup:
ADC pin
4 non-inverting input channel 1     Thermistor
6 Analog GND
8 Chip Select                              Pin 8
10 Crystal in                               Pin 3
12 Digital GND
13 Digital +5V
14 Xin, Serial Clock                     Pin 13
15 Serial in/out                          Pin 10 (MOSI)
16 Serial out                               Pin 12 (MISO)
17 Data Ready (high = ready)        Pin 4
18 Mode (0 = slave) GND
19 Analog +5V
20 Refout > Refin
21 Refin < Refout



Here's my commented code, it polls every 1/2 second if data is available (data is available at >300Hz per second but I poll every 1/2 second to avoid too much data on the screen).

*** See post below for code, apparently I exceed the limit of 9500 characters ***

The problem is that I only get bogus data, sometimes it returns 14 or 15 bits of mainly 1's, sometimes it returns the complete long filled primarily with 1's. I don't really get why this is happening, since it shouldn't even be possible. I tried to vary the delay times and even delete them but it made no difference. I tried to read the command register which I wrote to in setup() but it just gave a 0.

Here's the ADS1213 datasheet: http://focus.ti.com/lit/ds/symlink/ads1213.pdf and product page: http://focus.ti.com/docs/prod/folders/print/ads1213.html

Problems I could imagine myself:
- The clock output on pin 3 is not working correctly. Though if I unplug it, I get no data at all.
- The Refout > Refin should be connected to ground with a capacitor like in the datasheet?
- The ADS1213 doesn't like the Clock input from Arduino's pin 3, because it is either a different frequency or it really does like real crystals that produce sine waves more. In the datasheet it says that it does like real crystals more because they produce sine waves, but it also says somewhere that Xin can be connected to a microcontroller to be able to vary the power consumption on the fly.
- I fucked up somewhere in the code or connections but don't see it because I have too little experience with interfacing. (the most viable option...)

I'd really appreciate it if someone would take a look at my code and connections and see where it went wrong. When (or if?) it is working it can maybe also be converted into a library to aid other people in need of a very precise ADC.
« Last Edit: March 28, 2012, 10:12:37 am by Murdock » Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here's my commented code, it polls every 1/2 second if data is available (data is available at >300Hz per second but I poll every 1/2 second to avoid too much data on the screen).

The ADS1213 has a DRDY pin which goes LOW if data is available. That's the pin I poll every 1/2 second. Just now I spotted a mistake: I read it when it went HIGH, but I just corrected that to read if it is LOW. It makes no difference though, the data is still the same.

EDIT: I tried to connect the Xin to just a random port or touch it with my finger, and when I touch it or attach it to a random port the data is the same. When I don't touch it or attach it to a port it usually displays 0. So it seems that the data that is read isn't really data but simply noise. I'm starting to think that I really do need a crystal or the 1MHz output doesn't work at all. I don't have access to an oscilloscope, which would make everything a whole lot easier...

Code:
// This code tries to read the ADS1213 chip from Texas Instruments using SPI.

// The ADS1213 needs a clock input so we set pin 3 to oscillate at a 1MHz frequency. We lose pin 11 also though.
#define PWMout 3

#define DRDYPIN 4 // Data ready pin

#define SELPIN 8 //Selection Pin
#define DATAOUT 10//MOSI
#define DATAIN  12//MISO
#define SPICLOCK  13//Clock
int readvalue;

void setup ()
{
// Original code from: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1295040231
// Sets timer 2 (port 3 & 11) to a 1MHz square wave
  TCCR2A = 0xB3 ; // fast PWM with programmed TOP val
  TCCR2B = 0x09 ; // divide by 1 prescale
  TCNT2  = 0x00 ;
  OCR2A  = 0x0F ; // TOP = 15, cycles every 16 clocks
  OCR2B  = 0x07 ; // COMP for pin3
  pinMode (PWMout, OUTPUT) ;

/* Or maybe this would work (using the Timer library from http://www.arduino.cc/playground/Code/Timer1)?
  pinMode(10, OUTPUT);
  Timer1.initialize(1);         // initialize timer1, and set a 1 microsecond period
  Timer1.pwm(9, 1); */

// Bitbanging the SPI interface, from: http://www.arduino.cc/playground/Code/MCP3208
 //set pin modes
  pinMode(SELPIN, OUTPUT);
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK, OUTPUT);
  //disable device to start with
  digitalWrite(SELPIN,HIGH);
  digitalWrite(DATAOUT,LOW);
  digitalWrite(SPICLOCK,LOW);

  Serial.begin(115200);
  
  // Set the desired mode
  byte commandWriteCMR = B00000100;
  // 0 for write: 00 for 1 byte: 0 because it has to be there: 0100 for command register byte 3
  byte CMRbyte1 = B01100010; // Only bit that has to be written (other settings can be left standard)
  //byte CMR_2 = B00000000 // Defaults
  //byte CMR_3 = B00000000 // Defaults
  //byte CMR_4 = B00010111 // Defaults

// Now for the actual write sequence:
  digitalWrite(SELPIN,LOW); //Select ADC
  delayMicroseconds(11); // t24 in datasheet
  // Write the instruction byte
  for (int i=7; i>=0; i--){
    // Clock high (transmits data on RISING EDGE of clock)
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(DATAOUT,commandWriteCMR&1<<i);
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this neccesary?
    digitalWrite(SPICLOCK,LOW);
    delayMicroseconds(5); // t11 in datasheet
  }
  
  delayMicroseconds(12); // t19 in datasheet, min time between sending INSR and writing CMR
  // Write to the command register to set the right settings
    for (int i=7; i>=0; i--){
    // Clock high (transmits data on RISING EDGE of clock)
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(DATAOUT,CMRbyte1&1<<i);
    delayMicroseconds(5); // t10 in datasheet
    digitalWrite(SPICLOCK,LOW);
    delayMicroseconds(5); // t11 in datasheet
  }

  digitalWrite(SELPIN,HIGH); // Disable device again
  delay(100); // Stabilize everything. Is this neccesary?
  Serial.println("Finished with setup");
}


int read_adc(){
  unsigned long startTime = micros();
  
  long adcvalue = 0;
  byte commandbits = B11000000;
  // 1 for read: 10 for three bytes: 0 don't care: 0000 for DOR byte 2 (and proceed from there)

  digitalWrite(SELPIN,LOW); //Select adc
  
  delayMicroseconds(11); // t24 in datasheet
  
  // Write the instruction byte
  for (int i=7; i>=0; i--){
    // Cycle clock (transmits data on RISING EDGE of clock)
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(DATAOUT,commandbits&1<<i);
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this neccesary?
    digitalWrite(SPICLOCK,LOW);
    delayMicroseconds(5); // t11 in datasheet
  }
  
  delayMicroseconds(12); // t19 in datasheet, min time between sending INSR and reading DOR
  
  // Read bits from adc
  for (int i=23; i>=0; i--){ // Assumes data is provided in Most Significant Bit first
    digitalWrite(SPICLOCK,HIGH);
    adcvalue+=digitalRead(DATAIN)<<i;
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this neccesary?
    digitalWrite(SPICLOCK,LOW);
    // ADC value can also be read here right?
    delayMicroseconds(5); // t11 in datasheet
  }
  digitalWrite(SELPIN, HIGH); //turn off device
  unsigned long TotalTime = micros() - startTime;
  Serial.print("Finished with reading ADC, operation took in micros: "); Serial.println(TotalTime,DEC);
  return adcvalue;
}


void loop() {
 if (digitalRead(DRDYPIN) == LOW) {
   //Serial.println("DRDY line high, now reading");
   readvalue = read_adc();
 Serial.print("ADC value is: "); Serial.println(readvalue,BIN);
 //readvalue = read_adc(2);
 //Serial.println(readvalue,DEC);
 Serial.println(" ");
 }
 delay(500);
}
« Last Edit: February 21, 2011, 02:14:41 pm by Murdock » Logged

Offline Offline
Edison Member
*
Karma: 3
Posts: 1001
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The ADS1213 has a DRDY pin which goes LOW if data is available. That's the pin I poll every 1/2 second. Just now I spotted a mistake: I read it when it went HIGH, but I just corrected that to read if it is LOW. It makes no difference though, the data is still the same.
You may be onto something here.

I've used ADS1211 which I think share the same architecture and DRDY needs to be low not only when reading, but also when writing. You may want to try this first, before getting exotic in terms of fault finding.

It's a big datasheet with lots of options and parameters to relate to, but I'm impressed with performance. The device has a dynamic range (24-bits for the ADS1210/11) which is just incredible. For many designs this can eliminate the need for an analog front end.

Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I modified the code using interrupts to make sure that I start reading right when DRDY goes LOW, but still only noise. I shuffled some wires and it seems that all that's coming in is pure noise, no data whatsoever. Disconnecting some random wires doesn't do anything except for disconnecting the Xin wire, which leads me to believe that the Xin creates a little bit of noise which leaks through to the interrupt pin and creates the interrupts which then reads just noise.

Ben, that sounds interesting. I think they're very similar, large parts of the datasheet are the same and the serial interface is probably exactly the same. Did you interface it with the Arduino, and if so, do you still have the code? And how did you check that DRDY was LOW when writing? Did you use a crystal for the clock?
Logged

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 543
Posts: 27348
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Murdock,
Got me stumped.
I have some of these ordered as samples from TI.
I'm going to use with a crystal and the SPI.h library tho.
(note to self - order a 1 MHz crystal somewhere ...)
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Offline Offline
Edison Member
*
Karma: 3
Posts: 1001
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It's been a while since I wrote the interface, but when reading your post it gave me flashbacks on using the DRDY pin.

I use timer2 as clock source (4MHz in my case) and bit bang SPI. First step is doing a reset in accordance with the datasheet. DRDY should then output a valid signal (you may want to check DRDY with a scope or otherwise to see if the low/high frequency reflects the default configuration). SPI will then be configured for MSB (most significant bit first) and you should be able to get some consistant readings as an initial test (write adr/cmd byte followed by read).

Also note that when in differential mode, the returned value will be signed (manual sign extension to 32-bits is needed however to conform to a long). My implementation is polled and I use direct port I/O to check DRDY (wait for high then low before proceeding). Also for the SPI part I use direct port io as it is "impossible" to get timing right with digitalRead and digitalWrite.

Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@CrossRoads: I might go that way too, I'm getting pretty desperate.

Today I rewrote the code to use direct port access and corrected some mistakes that were in the code. I also finally realized what 'DRDY needs to be low also when writing' meant so I made a while loop to hold when DRDY is still high. I also made a reset function which should reset the chip.

Unfortunately, it's still not working. I reconnected all the wires, took a look at my code multiple times but I really can't figure out what is wrong. I measured Vref and that outputs a nice and steady 2.45v, just to test that it's at least doing something. I tried it in a lot of different ways, used the code with and without command register writing etc. but it's still not working. I get only 1's as data, and it doesn't even matter if I connect power to the chip or not.

I also tried to write to the command register to set Vbias (because with default settings it's off) and then measured it, but that gave 0v. Obviously the command register never gets written properly.

I hope I haven't fried the chip with all my experiments. Is it easy to fry such a chip by applying wrong signals or voltages to wrong pins?

@Ben: Unfortunately I don't have a scope. Did I do the reset part right in my code? The datasheet is kinda vague, it only specifies SCLK. I don't know if my direct port parts of the code are right either, I use if statements and use two steps to make dataout low and serialclock high. I couldn't figure out a faster way. Also, I use digitalRead() to check DRDY, is this fast enough or should I use your method of checking it with direct port I/O? And do you still have the code you wrote and if so, would you care to donate some of it? The serial part should be exactly the same. Because I'm getting kinda desperate with this chip.

Code:
// This code tries to read the ADC1213 chip from Texas Instruments using SPI.

// The ADC1213 needs a clock input so we set pin 3 to oscillate at a 1MHz frequency. We lose pin 11 also though.
#define PWMout 3 // Clock input (Xin)

#define DRDYPIN 6 // Data ready pin

#define SELPIN 8 // Selection Pin (CS)
#define DATAOUT 10// MOSI
#define DATAIN  12// MISO
#define SPICLOCK  13// Serial clock (SCLK)
int readvalue;

void ResetADC() { // Resets the ADS1213, needed after power-up
 // PORTB for ports 8-13 is in order (13 is LSB), 2 MSB are not usable
 // PORTD for ports 0-7 is reverse (7 is MSB)
  // NOTE: Does SELPIN have to be low (selected) while resetting, and if so, what are the minimum delay times? Datasheet is quite vague here, specifying nothing else than SLKC times.
  delay(300); // Delay to stabilize power

  PORTB &= B11011111; //digitalWrite(SELPIN,LOW); //Select ADC
  delayMicroseconds(11); // t24 in datasheet
 
//  NOTE: A reset doesn't need a DRDY LOW right?

  PORTB |= B00000001; // Set port 13 (SCLK) HIGH
  delayMicroseconds(1050); // t3 on datasheet p.30, @1MHz clock
  PORTB &= B11111110; // Set port 13 LOW
  delayMicroseconds(12); // t2
  PORTB |= B00000001; // Port 13 HIGH
  delayMicroseconds(520); // t1
  PORTB &= B11111110; // Port 13 LOW
  delayMicroseconds(12); // t2
  PORTB |= B00000001; // Port 13 HIGH
  delayMicroseconds(1050); // t3
  PORTB &= B11111110; // Port 13 LOW
  delayMicroseconds(12); // t2
  PORTB |= B00000001; // Port 13 HIGH
  delayMicroseconds(2060); // t4
  PORTB &= B11111110; // Port 13 LOW

  delayMicroseconds(11); // Random quirk (necessary?)

  digitalWrite(SELPIN,HIGH); // Deselect device again
  delay(100); // Stabilize everything. Is this necessary?
}

void setup ()
{
// Original code from: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1295040231
// Sets timer 2 (port 3 & 11) to a 1MHz square wave
  TCCR2A = 0xB3 ; // fast PWM with programmed TOP val
  TCCR2B = 0x09 ; // divide by 1 prescale
  TCNT2  = 0x00 ;
  OCR2A  = 0x0F ; // TOP = 15, cycles every 16 clocks
  OCR2B  = 0x07 ; // COMP for pin3
  pinMode (PWMout, OUTPUT) ;

/* Or maybe this would work (using the Timer library from http://www.arduino.cc/playground/Code/Timer1)?
  pinMode(10, OUTPUT);
  Timer1.initialize(1);         // initialize timer1, and set a 1 microsecond period
  Timer1.pwm(9, 1); */

// Bitbanging the SPI interface, from: http://www.arduino.cc/playground/Code/MCP3208
 //set pin modes
  pinMode(SELPIN, OUTPUT);
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK, OUTPUT);
  pinMode(DRDYPIN, INPUT);
  //Deselect device to start with
  digitalWrite(SELPIN,HIGH);
  digitalWrite(DATAOUT,LOW);
  digitalWrite(SPICLOCK,LOW);

  Serial.begin(115200);
 
  ResetADC();
 
  // Set the desired mode
  byte commandWriteCMR = B00000100;
  // 0 for write: 00 for 1 byte: 0 because it has to be there: 0100 for command register byte 3
  byte CMRbyte1 = B01100010; // Only bit that has to be written (other settings can be left standard)

//********************************************************** NOTE: DRDY should be low when writing! Therefore I'll use this bullocks while loop. It's not bullocks though.
  while (digitalRead(DRDYPIN) == HIGH) { // Actually it says DRDYPIN != 0
  Serial.println("while (DRDYPIN)");
  }

// Now for the actual write sequence:

  PORTB |= B11011111; //digitalWrite(SELPIN,LOW); //Select ADC
  delayMicroseconds(11); // t24 in datasheet
 
  //*** Write the instruction byte
  for (int i=7; i>=0; i--){
    if (commandWriteCMR&1<<i) {
      PORTB |= B00001001; // Set SPICLOCK & DATAOUT HIGH
    }
    else {
      PORTB &= B11110111; // Set DATAOUT LOW
      PORTB |= B00000001; // Set SPICLOCK HIGH
    }
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this necessary?
    PORTB &= B11111110; //digitalWrite(SPICLOCK,LOW); note: Also possible using ^=
    delayMicroseconds(5); // t11 in datasheet
  }
  PORTB &= B11110111; // Set DATAOUT LOW, for when the last bit was a 1 (we don't want it to stay high though it might work?)
 
  delayMicroseconds(12); // t19 in datasheet, min time between sending INSR and writing CMR
 
  //*** Write to the command register to set the right settings 
  for (int i=7; i>=0; i--){
    if (CMRbyte1&1<<i) {
      PORTB |= B00001001; // Set SPICLOCK & DATAOUT HIGH
    }
    else {
      PORTB &= B11110111; // Set DATAOUT LOW
      PORTB |= B00000001; // Set SPICLOCK HIGH
    }
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this necessary?
    PORTB &= B11111110; //digitalWrite(SPICLOCK,LOW); note: Also possible using ^=
    delayMicroseconds(5); // t11 in datasheet
  }
  PORTB &= B11110111; // Set DATAOUT LOW, for when the last bit was a 1 (we don't want it to stay high though it might work?)

  digitalWrite(SELPIN,HIGH); // Deselect device again
  delay(100); // Stabilize everything. Is this necessary?
  Serial.println("Finished with setup");
}

// Declare adcvalue here so read_adc can be made void which hopefully saves some clock cycles.
  long adcvalue;
//******************************************** ADC reading function
int read_adc(){
  adcvalue = 0;
  //unsigned long startTime = micros();
  byte commandbits = B11000000;
  // 1 for read: 10 for three bytes: 0 gapfiller: 0000 for DOR byte 2 (and proceed from there)
 
  PORTB |= B11011111; //digitalWrite(SELPIN,LOW); //Select ADC
  delayMicroseconds(11); // t24 in datasheet
 
  // Write the instruction byte
  for (int i=7; i>=0; i--){
    if (commandbits&1<<i) {
      PORTB |= B00001001; // Set SPICLOCK & DATAOUT HIGH
    }
    else {
      PORTB &= B11110111; // Set DATAOUT LOW
      PORTB |= B00000001; // Set SPICLOCK HIGH
    }
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this necessary?
    PORTB &= B11111110; //digitalWrite(SPICLOCK,LOW); note: Also possible using ^=
    delayMicroseconds(5); // t11 in datasheet
  }
  PORTB &= B11110111; // Set DATAOUT LOW, for when the last bit was a 1 (we don't want it to stay high though it might work?)
 
  delayMicroseconds(12); // t19 in datasheet, min time between sending INSR and reading DOR
 
  // Read bits from adc
  for (int i=23; i>=0; i--){ // Assumes data is provided in Most Significant Bit first
    PORTB |= B00000001; // Set SPICLOCK HIGH
    delayMicroseconds(5); // t10 in datasheet. NOTE: Is this necessary?
    adcvalue+=digitalRead(DATAIN)<<i; // Should it be placed here or one line higher/lower?
    PORTB &= B11111110; // Set SPICLOCK LOW
    // ADC value can also be read here right?
    delayMicroseconds(5); // t11 in datasheet
  }
 
  while (digitalRead(DRDYPIN) == LOW) { // Hold when DRYDY is still low, then delay to adhere to t23 in the datasheet
  }
  // Following line probably not even neccesary due to slowness of digitalWrite().
  delayMicroseconds(2); // t23 in the datasheet (min time from rising edge of DRDY to rising edge of CS)
  digitalWrite(SELPIN, HIGH); //turn off device
}

 byte HasBeenHigh = 0;
void loop() {
 if (digitalRead(DRDYPIN) == HIGH) {
   HasBeenHigh = 1;
 }
 if (digitalRead(DRDYPIN)==LOW && HasBeenHigh>0) {
   read_adc();
   Serial.print("ADC value is: "); Serial.println(adcvalue,BIN);
   Serial.println(" ");
   HasBeenHigh = 0;
 }
 // No delay to ensure that we don't arrive at the end of a DRDYPIN LOW!
}
Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Today I bought a 2MHz crystal (1MHz was way expensive), and 3 capacitors to comply with the datasheet examples. But, however, still no luck. I tried with and without resetting, initializing etc. but it just doesn't work. I get 10000000 as an output (I only tried to read a single byte), sometimes 0, sometimes all 1's. So it's still pretty much noise that's read.

To see if writing the settings was successful, I modified the code to set Vbias on, because default it's off. Then the Vbias pin gets connected to analog pin 2. Every second it returns the number of times that data was ready (the DRDY pin went low), and the internal ADC's read from the Vbias pin. The Vbias stays 0 though, indicating that nothing is ever written to the command register.

I used the SPI library to communicate, but the problem is that the SPI.transfer() function writes a byte, and then reads the byte that's returned. So the command register can never even be written to... Is there another way to solve this than bitbanging SPI? As that obviously didn't work for me.

@CrossRoads: Did you already receive your ADS1213?

@Ben: Do you still have the code?

Here's my code (obviously with the faulty command register write at init_ADS1213()
Code:
// Reads ADS1213.

#include <SPI.h>

const byte SCKpin = 13;
/*
pin 13 SCK SPI clock
pin 12 MISO SPI master in, slave out
pin 11 MOSI SPI master out, slave in
pin 10 SS SPI slave select
*/

const byte DRDYPIN = 9;
const byte chipSelectPin = 10;
byte ADCvalue = 0;

void init_ADS1213() {
  while (digitalRead(DRDYPIN)) {// don't do anything while DRDYPIN is still high
  Serial.println("DRDY HIGH");
  }
  digitalWrite(chipSelectPin, LOW);
  SPI.transfer(B00000100);  // Set the desired instruction
  // 0 for write: 00 for 1 byte: 0 because it has to be there: 0100 for command register byte 3
  SPI.transfer(B11100010); // Only bit that has to be written (other settings can be left standard)
  digitalWrite(chipSelectPin, HIGH);
}

void ResetADC() { // Resets the ADS1213, needed after power-up?
 // PORTB for ports 8-13 is in order (13 is LSB), 2 MSB are not usable
 // PORTD for ports 0-7 is reverse (7 is MSB)
  // NOTE: Does SELPIN have to be low (selected) while resetting, and if so, what are the minimum delay times? Datasheet is quite vague here, specifying nothing else than SLKC times.

  digitalWrite(chipSelectPin,LOW); //Select ADC
  delayMicroseconds(11); // t24 in datasheet
 
//  NOTE: A reset doesn't need a DRDY LOW right?

  digitalWrite(SCKpin,HIGH); // Set port 13 (SCLK) HIGH
  delayMicroseconds(1050); // t3 on datasheet p.30, @1MHz clock
  digitalWrite(SCKpin,LOW); // Set port 13 LOW
  delayMicroseconds(12); // t2
  digitalWrite(SCKpin,HIGH); // Port 13 HIGH
  delayMicroseconds(520); // t1
  digitalWrite(SCKpin,LOW); // Port 13 LOW
  delayMicroseconds(12); // t2
  digitalWrite(SCKpin,HIGH); // Port 13 HIGH
  delayMicroseconds(1050); // t3
  digitalWrite(SCKpin,LOW); // Port 13 LOW
  delayMicroseconds(12); // t2
  digitalWrite(SCKpin,HIGH); // Port 13 HIGH
  delayMicroseconds(2060); // t4
  digitalWrite(SCKpin,LOW); // Port 13 LOW

  delayMicroseconds(11); // Random quirk (necessary?)

  digitalWrite(chipSelectPin,HIGH); // Deselect device again
  delay(50); // Stabilize everything. Is this necessary?
}

void setup() {
  Serial.begin(115200);
  pinMode (chipSelectPin, OUTPUT);  // set the chipSelectPin as an output
  pinMode (DRDYPIN, INPUT);
  SPI.begin();  // initialize SPI:
  SPI.setDataMode(1); // CPOL 0, CPHA 1
  SPI.setClockDivider(128);
  ResetADC();
  init_ADS1213();
}

unsigned long LowCount = 0;
unsigned long StartTime = 0;
void loop() {
  static byte HasBeenHigh = 0;
  if (digitalRead(DRDYPIN) == HIGH) {
    HasBeenHigh = 1;
  }
  if (digitalRead(DRDYPIN)==LOW && HasBeenHigh>0) {
    //ReadADC();
    //Serial.print("ADC value is: "); Serial.println(ADCvalue,BIN);
    HasBeenHigh = 0;
    LowCount = LowCount + 1;
    if (millis() - 1000 > StartTime) {
      Serial.println(LowCount, DEC);
      Serial.println(analogRead(2));
      StartTime = millis();
      Serial.println(" ");
      LowCount = 0;
    }
  }
}

void ReadADC() {
  digitalWrite(chipSelectPin, LOW);
  ADCvalue = SPI.transfer(B10000010);
  // 1 for read: 00 for one byte: 0 gapfiller: 0010 for DOR byte 0 (and proceed from there)
  digitalWrite(chipSelectPin, HIGH);
}
Logged

Global Moderator
Boston area, metrowest
Offline Offline
Brattain Member
*****
Karma: 543
Posts: 27348
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes I did, haven't got a crystal yet tho.
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Today I glanced over my code again, and I discovered a major fault: I wrote the PORTB bit the wrong way round! So now port 7 was being set instead of 13... But I fixed it, as well as adjusting the timing for the reset for a 2 MHz crystal, as the reset function could not have worked before because the reset function has maximum timings which were not met.

I looked over my code a lot of times, tested a lot of times but it just doesn't work. The main loop simply reads the Vbias, the easiest way to check if the write to the CMR has been successful.

So I really don't know what to do any more because I tried everything but it still doesn't seem to work.

@CrossRoads: Have you done any testing with the ADS1213 yet, and if not, would you like to check and try my code? (it might work in the very unlikely event that both my ADS1213 chips are smoked)

Here's the latest code:
Code:
// This code tries to read the ADC1213 chip from Texas Instruments using SPI.

// The ADC1213 is assumed to have a 2MHz external crystal connected here.

#define DRDYPIN 6 // Data ready pin

#define SELPIN 8 // Selection Pin (CS)
#define DATAOUT 10// MOSI
#define DATAIN  12// MISO
#define SPICLOCK  13// Serial clock (SCLK)

void ResetADC() { //********************************************************* Resets the ADS1213, needed after power-up
  // NOTE: Does SELPIN have to be low (selected) while resetting, and if so, what are the minimum delay times? Datasheet is quite vague here, specifying nothing else than SLKC times.
  delay(300); // Delay to stabilize power

  PORTB &= B11111110; //digitalWrite(SELPIN,LOW); //Select ADC
  delayMicroseconds(6); // t24 in datasheet

//  NOTE: A reset doesn't need a DRDY LOW right?

  PORTB |= B00100000; // Set port 13 (SCLK) HIGH
  delayMicroseconds(550); // t3 on datasheet p.30, @2MHz clock
  PORTB &= B11011111; // Set port 13 LOW
  delayMicroseconds(6); // t2
  PORTB |= B00100000; // Port 13 HIGH
  delayMicroseconds(300); // t1
  PORTB &= B11011111; // Port 13 LOW
  delayMicroseconds(6); // t2
  PORTB |= B00100000; // Port 13 HIGH
  delayMicroseconds(550); // t3
  PORTB &= B11011111; // Port 13 LOW
  delayMicroseconds(6); // t2
  PORTB |= B00100000; // Port 13 HIGH
  delayMicroseconds(1030); // t4
  PORTB &= B11011111; // Port 13 LOW

  delayMicroseconds(5); // Random quirk (necessary?)
 
  PORTB |= B00000001;// Deselect device again (= //digitalWrite(SELPIN,HIGH);)
  delay(100); // Stabilize everything. Is this necessary?
}



void setup ()//********************************************************* Setup
{
 // Bitbanging the SPI interface, from: http://www.arduino.cc/playground/Code/MCP3208
 //set pin modes
  pinMode(SELPIN, OUTPUT);
    digitalWrite(SELPIN,HIGH);   //Deselect device to start with
  pinMode(DATAOUT, OUTPUT);
    digitalWrite(DATAOUT,LOW);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK, OUTPUT);
    digitalWrite(SPICLOCK,LOW);
  pinMode(DRDYPIN, INPUT);

  Serial.begin(115200);
  ResetADC();
 
//*** NOTE: DRDY should be low when writing! Therefore I'll first check if it's low and if it has been high (to avoid arriving at the end of a LOW).
  byte GoOn = 0;
  byte HasBeenHigh = 0;
  while (GoOn == 0) {
    if (PIND & B01000000) HasBeenHigh = 1;
    else if (HasBeenHigh) GoOn = 1;
    //Serial.println('W');
  }
 
  // Set the desired mode
  byte commandWriteCMR = B00000100;
  // 0 for write: 00 for 1 byte: 0 because it has to be there: 0100 for command register byte 3
  byte CMRbyte1 = B11100010; // Sets Vbias ON
 
  //delayMicroseconds(2); // t21 in datasheet (probably not neccesary...)
 
//*** Now for the actual write sequence:
  PORTB |= B11111110; //digitalWrite(SELPIN,LOW); //Select ADC
  delayMicroseconds(6); // t24 in datasheet
 
  //*** Write the instruction byte
  for (int i=7; i>=0; i--){
    if (commandWriteCMR&1<<i) {
      PORTB |= B00100100; // Set SPICLOCK & DATAOUT HIGH
    }
    else {
      PORTB &= B11111011; // Set DATAOUT LOW
      PORTB |= B00100000; // Set SPICLOCK HIGH
    }
    delayMicroseconds(3); // t10 in datasheet. NOTE: Is this necessary?
    PORTB &= B11011111; //digitalWrite(SPICLOCK,LOW); note: Also possible using ^=
    delayMicroseconds(3); // t11 in datasheet
  }
  PORTB &= B11111011; // Set DATAOUT LOW, for when the last bit was a 1 (we don't want it to stay high though it might work?)
 
  delayMicroseconds(7); // t19 in datasheet, min time between sending INSR and writing CMR
 
  //*** Write to the command register to set the right settings 
  for (int i=7; i>=0; i--){
    if (CMRbyte1&1<<i) {
      PORTB |= B00100100; // Set SPICLOCK & DATAOUT HIGH
    }
    else {
      PORTB &= B11111011; // Set DATAOUT LOW
      PORTB |= B00100000; // Set SPICLOCK HIGH
    }
    delayMicroseconds(3); // t10 in datasheet. NOTE: Is this necessary?
    PORTB &= B11011111; //digitalWrite(SPICLOCK,LOW); note: Also possible using ^=
    delayMicroseconds(3); // t11 in datasheet
  }
  PORTB &= B11111011; // Set DATAOUT LOW, for when the last bit was a 1 (we don't want it to stay high though it might work?)

  while (digitalRead(DRDYPIN) == LOW) { // Hold when DRYDY is still low, then delay to adhere to t23 in the datasheet
    //Serial.println('D');
  }
  delayMicroseconds(2); // t23 in the datasheet (min time from rising edge of DRDY to rising edge of CS)
  digitalWrite(SELPIN,HIGH); // Deselect device again
  delay(100); // Stabilize everything. Is this necessary?
  Serial.println("Finished with setup");
}


void loop() {//********************************* Main loop
  int VbiasPin = 1;
  Serial.println(analogRead(VbiasPin));
  delay(1000);
}
Logged

Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 361
Posts: 17301
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Maybe get some ideas from this recent thread?

http://arduino.cc/forum/index.php/topic,57873.0.html

Lefty
Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Lefty, thanks for that link. That seems to be a great ADC, but it is only single channel. There's a couple fundamental differences between that ADS1252 and this ADS1213: he doesn't have to write instructions to the command register as it doesn't have one, and therefore he can use the SPI library to read stuff.

Such an approach would also be possible with the ADS1213 (using continuous read mode), but for my application I really need to be able to switch between channels. Maybe I'll write some code to be able to get data from it while only receiving though, just for testing.
Logged

Portland, OR
Offline Offline
Sr. Member
****
Karma: 7
Posts: 260
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'll try to take a look at your code, after going through the datasheet.

In the meantime, if you absolutely need multiple channels and are not having much luck with getting the code to work, how about going with this 18-bit ADC (simple, well-documented, tested and tried):
http://forums.adafruit.com/viewtopic.php?f=31&t=12269&p=58824

But then again, programming your new ADC and going through all the perfect-timing business might be one of the hardest and most rewarding things hehe
Logged

Netherlands
Offline Offline
Newbie
*
Karma: 0
Posts: 21
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Giantsfan, I'd really appreciate that.
I've seen that one, but I need at least 18 bits of resolution and I want to measure temperature, an analog pressure sensor (height) and another one for airspeed. So I need at least 3 channels with 18 bits, and then I also need to be able to read them really fast, in the order of at least 100SPS per channel as I want to do some data filtering to get extremely accurate results. The microchip one only has 3.75SPS for 18 bits which is way too slow.
Logged

Offline Offline
Edison Member
*
Karma: 3
Posts: 1001
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Murdock:
You seemed to be on a good roll to get his up and running, but I understand now it hasn’t progressed much. When having issues it is always a good idea to take a step back, clean up the code and verify basic functionality. In terms of code there really isn’t much required as the main challenge is with understanding the datasheet and getting timing right. Once the basic functions are in place you can explore options further.

Supplying a clock:
When using the ADC with a microcontroller, the obvious option is to supply the clock from a timer. Low level code to output a clock on PD3 is as follows (set OCR2A to 1 for a 4MHz clock or 2 for a 2MHz clock):

Code:
  // configure timer2 for 8MHz output on PD3
  TCCR2B = _BV(CS20);  // prescaler = F_CPU/1
  OCR2A = 0;  // clear timer at 0 count, f = F_CPU/2
  TCCR2A = _BV(COM2B0) | _BV(WGM21);  // toggle PD3 on compare match
  DDRD |= _BV(PORTD3);  // start clock output on XIN/PD3

I would create a separate init function with the above code and call this from setup. Next step is reset.

Resetting the ADC:
The ADC reset procedure is well explained in the datasheet, but you need to observe that timing is relative to clock. Basic steps (appropriate for 8MHz) are as follows:

Code:
  PORTB |= _BV(PORTB5);
  delayMicroseconds(32+1);  // 256 x Xin
  PORTB &= ~_BV(PORTB5);
  delayMicroseconds(1);
  PORTB |= _BV(PORTB5);
  delayMicroseconds(64+1);  // 512 x Xin
  PORTB &= ~_BV(PORTB5);
  delayMicroseconds(1);
  PORTB |= _BV(PORTB5);
  delayMicroseconds(128+1); // 1024 x Xin
  PORTB &= ~_BV(PORTB5);

Again I would put this in a separate function to be called from within setup.

Then we need two more functions, one for writing to the chip and another for reading. For this we can use bit bang SPI.

Writing to the ADC:
The function is called with the ADC command register address (adr), number of bytes to write (count) and the actual data to send (val). Choice of IO pins will have to match how you wired the ADC to your Arduino. Using direct port io is extremely fast so depending on the clock you supply to the ADC you may have to verify/adjust delays in accordance with the datasheet.

Code:
#define WRITE 0
#define READ 1

typedef struct _insr_type {
  uint8_t adr :5;        // high-bit unused
  uint8_t count :2;
  uint8_t rw :1;         // 0=write, 1=read
} insr_type;

typedef union {
    insr_type t;
    byte b;
} insr_union;

byte spi_write(byte adr, byte count, byte val[])
{
  insr_union insr;
 
  insr.t.adr = adr;
  insr.t.count = count - 1;
  insr.t.rw = WRITE;

  while (!(PIND & _BV(PIND2)));  // wait for ready to go high
  while (PIND & _BV(PIND2));     // wait for ready to go low
 
  // clock out instruction byte
  DDRB |= _BV(PORTB3);
  delayMicroseconds(1);
  for (byte mask = 0x80; mask; mask >>= 1) {
    if (mask & insr.b) PORTB |= _BV(PORTB3); else PORTB &= ~_BV(PORTB3);
    PORTB |= _BV(PORTB5);
    delayMicroseconds(1);
    PORTB &= ~_BV(PORTB5);
    delayMicroseconds(1);
  }
 
  // clock out bits, MSB first
  for (byte i = 0; i < count; i++) {
    for (byte mask = 0x80; mask; mask >>= 1) {
      if (mask & val[count - i - 1]) PORTB |= _BV(PORTB3); else PORTB &= ~_BV(PORTB3);
      PORTB |= _BV(PORTB5);
      delayMicroseconds(1);
      PORTB &= ~_BV(PORTB5);
      delayMicroseconds(1);
    }
  }
  DDRB &= ~_BV(PORTB3);
}

Reading from the ADC:
The final piece of the puzzle is getting data back from the ADC. Again we can use bit bang SPI as follows:

Code:
void spi_read(byte adr, byte count, byte val[], boolean sync)
{
  insr_union insr;
 
  insr.t.adr = adr;
  insr.t.count = count - 1;
  insr.t.rw = READ;
 
  if (sync) {
    while (!(PIND & _BV(PIND2)));  // wait for ready to go high
    while (PIND & _BV(PIND2));     // wait for ready to go low
  }
 
  // clock out instruction byte
  DDRB |= _BV(PORTB3);
  delayMicroseconds(1);
  for (byte mask = 0x80; mask; mask >>= 1) {
    if (mask & insr.b) PORTB |= _BV(PORTB3); else PORTB &= ~_BV(PORTB3);
    PORTB |= _BV(PORTB5);
    delayMicroseconds(1);
    PORTB &= ~_BV(PORTB5);
    delayMicroseconds(1);
  }
 
  DDRB &= ~_BV(PORTB3);
  PORTB &= ~_BV(PORTB3);
 
  // clock in bits, MSB first
  for (byte i = 0; i < count ; i++) {
    val[count-i-1] = 0;
    for (byte j = 0; j < 8; j++) {
      PORTB |= _BV(PORTB5);
      delayMicroseconds(1);
      val[count-i-1] <<= 1;
      if (PINB & _BV(PINB3)) val[count-i-1] |= 1;
      PORTB &= ~_BV(PORTB5);
      delayMicroseconds(1);
    }
  }
}

This function uses the same command byte structure as for the write function. Parameters include the ADC command register starting address (adr), number of bytes to read (count), a reference to an array where data is returned (val) and a boolean sync parameter. The sync parameter can be used to avoid waiting for a DRDY sync if you already account for this outside of the read function (such as in a polling routine).

Next step would be to test the core functions as well as your hardware wiring (and get a much needed confidence boost) it is always a good idea to first read a command register and check if what you get back is in accordance with the manual. Next step is to write to a command register (change some non critical value) and then read back the same command register to verify it is matching the value you wrote. Once this is complete you will have the basic tools required to use the ADC in your project.

I’m using the ADC1211 (4 channel multiplexer) running at 8Mhz. Getting the full 24-bit differential resolution with acceptable absolute accuracy requires a careful board layout with appropriate filters as per datasheet recommendations. The end result was very rewarding however and provides a tremendous dynamic range that would be very hard to match with an op-amp ADC pre stage design.

Logged

Pages: [1] 2 3 4   Go Up
Jump to: