Go Down

Topic: >Working, with library!< Interfacing ADS1213 22-bit ADC (Read 16 times) previous topic - next topic

BenF

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.


Murdock

@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: [Select]
// 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!
}

Murdock

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: [Select]
// 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);
}

CrossRoads

Yes I did, haven't got a crystal yet tho.
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.

Murdock

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: [Select]
// 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);
}


Go Up