Reading Data from 4-channel 24bit ADC (LTC2442) via SPI

Hello everyone!
I'm totally new to Arduino and SPI and my programming skills are purely basic.
I'm supposed to hook up a 24bit ADC to an Arduino Mega 2560 and started with a ltc2400 combined with a lt1021 5.0V voltage reference according to this tutorial:

http://interface.khm.de/index.php/lab/experiments/connect-a-ltc2400-high-precision-24-bit-analog-to-digital-converter/

It works perfectly fine. But now we realized, we need at least two, rather four channels in total for our experiments (heat flow measurements, need to acquire 2 temperatures and at least one voltage from the heat flow sensor, which is about a few mV). So I found the LTC2442 ADC which is similar to the LTC2400 but has four channels.

LTC2442 datasheet: http://docs-europe.electrocomponents.com/webdocs/10ed/0900766b810ed9bc.pdf

Then I tried to adapt the LTC2400-code to the LTC2442 by inserting a few commands i found on various websites and in this forum (e.g. Help needed with a 4 differential channels simultaneous ADC MAX11040k. - Networking, Protocols, and Devices - Arduino Forum ). But I'm not able to read any data from the ADC, all Output i get is "5.00000 V" for every channel.

Has anyone experience with this ADC or can tell me, what is wrong in my code? I'm pretty sure it's the SPI stuff...

If somebody has ideas on a better or easier solution for my experiment, I'm open for suggestions! :slight_smile:

Thanks!
Dave

Here my current code:

// LTC2442 24 Bit 4 Channel ADC Test

#include <Stdio.h>
#include <SPI.h>

#ifndef cbi
#define cbi(sfr, bit)     (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit)     (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define LTC_CS 0         // LTC2442 Chip Select Pin  on Mega Pin 53
#define LTC_MISO  3      // LTC2442 SDO Pin on Mega Pin 50
#define LTC_SCK  1       // LTC2442 SCK Pin on Mega Pin 52

byte commandCH0 = B10110000;       // Channel Selection
byte commandCH1 = B10110001;
byte commandCH2 = B10111000;
byte commandCH3 = B10111001;
byte commandResolution = B00010;   // Resolution Selection
byte commandKeepRes = B00000;      // keep previous Resolution Settings

void setup() {

  SPI.setBitOrder(MSBFIRST);

  cbi(PORTB,LTC_SCK);      // LTC2442 SCK low
  sbi (DDRB,LTC_CS);       // LTC2442 CS HIGH

  cbi (DDRB,LTC_MISO);
  sbi (DDRB,LTC_SCK);

  Serial.begin(57600);

  // init SPI Hardware
  sbi(SPCR,MSTR) ; // SPI master mode
  sbi(SPCR,SPR0) ; // SPI speed
  sbi(SPCR,SPR1);  // SPI speed
  sbi(SPCR,SPE);   //SPI enable

  Serial.println("LTC2442 4-channel ADC Test");

  cbi (PORTB,LTC_CS);
  SPI.transfer(commandCH0); //select first channel
  SPI.transfer(commandResolution);
  sbi(PORTB,LTC_CS);
 delay(200);

}
float volt0;
float volt1;
float volt2;
float volt3;
float v_ref=5.0;          // Reference Voltage, 5.0 Volt for LT1021 

long int ltw0 = 0;         // ADC Data long int
long int ltw1 = 0;         // ADC Data long int
long int ltw2 = 0;         // ADC Data long int
long int ltw3 = 0;         // ADC Data long int
int cnt;                  // counter
byte b0;                  //
byte sig;                 // sign bit flag
char st1[20];             // float voltage text

/********************************************************************/
void loop() {

  cbi(PORTB,LTC_CS);             // LTC2442 CS Low
  delayMicroseconds(1);


  //if (!(PINB & (1 << PORTB2))) {    // ADC Converter ready ? doesnt work, not sure if this is necessary/possible with ltc2442
  ltw0=0;
  ltw1=0;
  ltw2=0;
  ltw3=0;
  sig=0;

  b0 = SPI_read();             // read 4 bytes adc raw data with SPI
  if ((b0 & 0x20) ==0) sig=1;  // is input negative ?
  b0 &=0x1F;                   // discard bit 25..31
  ltw0 |= b0;
  ltw0 <<= 8;
  b0 = SPI_read();
  ltw0 |= b0;
  ltw0 <<= 8;
  b0 = SPI_read();
  ltw0 |= b0;
  ltw0 <<= 8;
  b0 = SPI_read();
  ltw0 |= b0;


  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  SPI.transfer(commandCH1);                        // Send command for next channel
  SPI.transfer(commandKeepRes);

  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  b0 = SPI_read();             // read 4 bytes adc raw data with SPI
  if ((b0 & 0x20) ==0) sig=1;  // is input negative ?
  b0 &=0x1F;                   // discard bit 25..31
  ltw1 |= b0;
  ltw1 <<= 8;
  b0 = SPI_read();
  ltw1 |= b0;
  ltw1 <<= 8;
  b0 = SPI_read();
  ltw1 |= b0;
  ltw1 <<= 8;
  b0 = SPI_read();
  ltw1 |= b0;

  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  SPI.transfer(commandCH2);                        // Send command for next channel
  SPI.transfer(commandKeepRes);

  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  b0 = SPI_read();             // read 4 bytes adc raw data with SPI
  if ((b0 & 0x20) ==0) sig=1;  // is input negative ?
  b0 &=0x1F;                   // discard bit 25..31
  ltw2 |= b0;
  ltw2 <<= 8;
  b0 = SPI_read();
  ltw2 |= b0;
  ltw2 <<= 8;
  b0 = SPI_read();
  ltw2 |= b0;
  ltw2 <<= 8;
  b0 = SPI_read();
  ltw2 |= b0;

  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  SPI.transfer(commandCH3);                        // Send command for next channel
  SPI.transfer(commandKeepRes);

  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  b0 = SPI_read();             // read 4 bytes adc raw data with SPI
  if ((b0 & 0x20) ==0) sig=1;  // is input negative ?
  b0 &=0x1F;                   // discard bit 25..31
  ltw3 |= b0;
  ltw3 <<= 8;
  b0 = SPI_read();
  ltw3 |= b0;
  ltw3 <<= 8;
  b0 = SPI_read();
  ltw3 |= b0;
  ltw3 <<= 8;
  b0 = SPI_read();
  ltw3 |= b0;

  sbi(PORTB,LTC_CS);             // LTC2442 CS high
  delay(200);
  cbi(PORTB,LTC_CS);             // LTC2442 CS Low

  SPI.transfer(commandCH1);                        // Send command for next channel
  SPI.transfer(commandKeepRes);

  delayMicroseconds(1);

  sbi(PORTB,LTC_CS);           // LTC2442 CS HIGH
  delay(200);

  if (sig) ltw0 |= 0xf0000000;    // if input negative insert sign bit
  if (sig) ltw1 |= 0xf0000000;    // if input negative insert sign bit
  if (sig) ltw2 |= 0xf0000000;    // if input negative insert sign bit
  if (sig) ltw3 |= 0xf0000000;    // if input negative insert sign bit
  ltw0=ltw0/16;                    // scale result down , last 4 bits have no information
  ltw1=ltw1/16;                    // scale result down , last 4 bits have no information
  ltw2=ltw2/16;                    // scale result down , last 4 bits have no information
  ltw3=ltw3/16;                    // scale result down , last 4 bits have no information
  volt0 = ltw0 * v_ref / 16777216; // max scale
  volt1 = ltw1 * v_ref / 16777216; // max scale
  volt2 = ltw2 * v_ref / 16777216; // max scale
  volt3 = ltw3 * v_ref / 16777216; // max scale

  Serial.print(cnt++);
  Serial.print(";  ");
  printFloat(volt0,6);           // print voltage as floating number
  Serial.print(";  ");
  printFloat(volt1,6);           // print voltage as floating number
  Serial.print(";  ");
  printFloat(volt2,6);           // print voltage as floating number  
  Serial.print(";  ");
  printFloat(volt3,6);           // print voltage as floating number
  Serial.println("  ");

  //}
  sbi(PORTB,LTC_CS); // LTC2442 CS high
  delay(20);

}
/********************************************************************/
byte SPI_read()
{
  SPDR = 0;
  while (!(SPSR & (1 << SPIF))) ; /* Wait for SPI shift out done */
  return SPDR;


}
/********************************************************************/
void printFloat(float value, int places) {
  int digit;
  float tens = 0.1;
  int tenscount = 0;
  int i;
  float tempfloat = value;

  float d = 0.5;
  if (value < 0)
    d *= -1.0;
    for (i = 0; i < places; i++)
    d/= 10.0;
   tempfloat +=  d;

  if (value < 0)
    tempfloat *= -1.0;
  while ((tens * 10.0) <= tempfloat) {
    tens *= 10.0;
    tenscount += 1;
  }

  // write out the negative if needed
  if (value < 0)
    Serial.print('-');

  if (tenscount == 0)
    Serial.print(0, DEC);

  for (i=0; i< tenscount; i++) {
    digit = (int) (tempfloat/tens);
    Serial.print(digit, DEC);
    tempfloat = tempfloat - ((float)digit * tens);
    tens /= 10.0;
  }

  // if no places after decimal, stop now and return
  if (places <= 0)
    return;

  // otherwise, write the point and continue on
  Serial.print(',');

  for (i = 0; i < places; i++) {
    tempfloat *= 10.0;
    digit = (int) tempfloat;
    Serial.print(digit,DEC);
    // once written, subtract off that digit
    tempfloat = tempfloat - (float) digit;
  }
}

This chip needs the correct bits passed to it with SPI - check the datasheet (figure 3).
You are only passing zeroes via SPI so it cannot do anything useful. In particular
you need to tell it which mode (there are 4 differential and 4 single-ended modes),
and which speed, and an enable bit.

First rewrite SPI_read thus

byte SPI_read (byte b)
{
  SPDR = b ;
  while (!(SPSR & (1 << SPIF))) ; /* Wait for SPI shift out done */
  return SPDR;
}

Then pass the correct bits in the first two calls...

no such sensor disclaimer(but it looks good!)

first step I would try is to create a functionfor one channel, with the channel nr as parameter.

float readChannel(uint8_t nr)
{
...
return f;
}

and get it to work for the LTC2442 for one channel.

then modify the code to support a second channel. Try to work with array's where possible.

Thanks for the quick response!

I adapted my code, but i still get 5.000000 as an output for every channel. But the ADC seems to do the conversions, i attached a LED to the BUSY pin and it blinks slightly about 4 times per second.

I also have a problem understanding the "discard bit 25..31" line, which is "b0 &= 0x1F", i got it from the ltc2400 tutorial and it works fine with it. What i don't understand:
On the LTC2400 the MSB is bit 27 and on the LTC2442 it is bit 28, so shouldn't we discard bits 28 to 31 or 29 to 31, respectively?
In my understanding of the "compound bitwise and" function &= we read the first byte, i.e. bits 31 to 24, and clear the first three bits by AND it with 0x1F, which is B00011111 in binary. Or am i thinking the wrong way?

here's my current program code (whitout the printfloat-part):

// LTC2442 24 Bit 4 Channel ADC Test

#include <Stdio.h>

#ifndef cbi
#define cbi(sfr, bit)     (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit)     (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define LTC_CS 0         // LTC2442 Chip Select Pin  on Mega Pin 53
#define LTC_MISO  3      // LTC2442 SDO Pin on Mega Pin 50
#define LTC_MOSI 2       // LTC 2442 SDI Pin on Mega Pin 51
#define LTC_SCK  1       // LTC2442 SCK Pin on Mega Pin 52

byte CHx = B00000000;
byte Res = B00000;

/************************************************************/

void setup() {

  cbi(PORTB,LTC_SCK);      // LTC2442 SCK low
  sbi (DDRB,LTC_CS);       // LTC2442 CS HIGH

  cbi (DDRB,LTC_MISO);
  sbi (DDRB,LTC_SCK);

  Serial.begin(57600);

  // init SPI Hardware
  sbi(SPCR,MSTR) ; // SPI master mode
  sbi(SPCR,SPR0) ; // SPI speed
  sbi(SPCR,SPR1);  // SPI speed
  sbi(SPCR,SPE);   //SPI enable

  Serial.println("LTC2442 4-channel ADC Test");

}

/********************************************************************/

float volt;
float volt0;
float volt1;
float volt2;
float volt3;
float v_ref=5.0;          // Reference Voltage, 5.0 Volt for LT1021 

long int ltw = 0;         // ADC Data long int
long int ltw0 = 0;         // ADC Data long int
long int ltw1 = 0;         // ADC Data long int
long int ltw2 = 0;         // ADC Data long int
long int ltw3 = 0;         // ADC Data long int
int cnt;                  // counter
byte b0;                  //
byte sig;                 // sign bit flag
char st1[20];             // float voltage text


/********************************************************************/

void loop() {

  if (!(PINB & (1 << PORTB2))) {    // ADC Converter ready ? 
  ltw0=0;
  ltw1=0;
  ltw2=0;
  ltw3=0;

  volt0 = readADC(1);
  volt1 = readADC(2);
  volt2 = readADC(3);
  volt3 = readADC(0);
  

  Serial.print(cnt++);
  Serial.print(";  ");
  printFloat(volt0,6);           // print voltage as floating number
  Serial.print(";  ");
  printFloat(volt1,6);           // print voltage as floating number
  Serial.print(";  ");
  printFloat(volt2,6);           // print voltage as floating number  
  Serial.print(";  ");
  printFloat(volt3,6);           // print voltage as floating number
  Serial.println("  ");
  
  }
  delay(200);

}

/********************************************************************/

float readADC(uint8_t nr)
{
  switch (nr) {             //switches channel and resolution commands for each channel
  case 0 :
    CHx = B10110000;
    Res = B00010;
    break;

  case 1:
    CHx = B10110001;
    Res = B00000;
    break;

  case 2:
    CHx = B10111000;
    Res = B00000;
    break;

  case 3:
    CHx = B10111001;
    Res = B00000;
    break;
  }

  sig = 0;
  ltw = 0;
  b0 = 0x00;
  volt = 0.0;

  cbi (PORTB,LTC_CS);
  delayMicroseconds(1);

  b0 = SPI_read(CHx);          // read 4 bytes adc raw data with SPI
  if ((b0 & 0x20) ==0) sig=1;  // is input negative ?
  b0 &=0x1F;                   // discard bit 25..31
  ltw |= b0;
  ltw <<= 8;
  b0 = SPI_read(Res);
  ltw |= b0;
  ltw <<= 8;
  b0 = SPI_read(0);
  ltw |= b0;
  ltw <<= 8;
  b0 = SPI_read(0);
  ltw |= b0;

  if (sig) ltw |= 0xf0000000;    // if input negative insert sign bit

ltw = ltw / 16;
volt = ltw * v_ref / 16777216; // max scale


delayMicroseconds(1);
  sbi (PORTB,LTC_CS);
  delay(200);
  
  return volt;
}



/********************************************************************/
byte SPI_read(byte b)
{
  SPDR = b;
  while (!(SPSR & (1 << SPIF))) ; /* Wait for SPI shift out done */
  return SPDR;


}

What actual value are you getting back from the ADC. It is not actually telling you "5.000V". You also need to make sure you clearly understand the difference between the single-ended and differential modes, both in the software and the actual physical hookup.

My setup is according to figure 4 in the datasheet (page 16), External Serial Clock and Single Cycle Operation, with Fo to ground for internal oscillator and COM as a common ground for the 4 single ended Inputs.

My commands are supposed to set these single ended Channels, Resolution and Sample Rate according to the manual.

I just realized, that the maximum Input Voltage (in differential mode) is 2.5V not 5.0V, and since I used the 3.3V Output of the Mega as Input-Voltage for CH0, i got only maximum readings of 16777215 or 5.000000 V. When i put it to ground, i get -0,00002+-10. It looks like reading the data works now.

So obviously the ADC still performs its default conversion for CH0 in differential mode, as i get the same value for every channel i call, even when the others are grounded.

That leaves the problem of sending the right commands to the ADC by SPI. Any suggestions, whats still wrong with my commands? Wrong order? Wrong code?

Couple of obvious issues with that code - you've got the channel codes for 1 and 2
mixed up, and you've not put the resulution bits in the right place - SPI is MSB first.

The sign bit in the result is inverted for some odd reason (these chip designers,
crazy people!), so it just needs re-inverting before sign-extending the result (easiest
to do with shifts).

float readADC(uint8_t nr)
{
  switch (nr) {             //switches channel and resolution commands for each channel
  case 0 :
    CHx = B10110000;
    Res = B00010<<3;   // fix resolution bits to be top 5 of second byte
    break;

  case 1:
    CHx = B10111000;  // fix channel order
    Res = B00000<<3;
    break;

  case 2:
    CHx = B10110001;  // fix channel order
    Res = B00000<<3;
    break;

  case 3:
    CHx = B10111001;
    Res = B00000<<3;
    break;
  }

  sig = 0;
  ltw = 0;
  b0 = 0x00;
  volt = 0.0;

  cbi (PORTB,LTC_CS);
  delayMicroseconds(1);

  ltw = SPI_read(CHx);          // read 4 bytes adc raw data with SPI
  ltw <<= 8;
  ltw |= SPI_read(Res);
  ltw <<= 8;
  ltw |= SPI_read(0);
  ltw <<= 8;
  ltw |= SPI_read(0);

  ltw = (ltw ^ 0x20000000L) << 2 >> 7 ;  // fix sign bit, shifts to sign extend

  volt = ltw * v_ref / 16777216; // max scale


delayMicroseconds(1);
  sbi (PORTB,LTC_CS);
  delay(200);
  
  return volt;
}

Thanks!
I changed the code just a minute ago according to your post, but it still seems to be in differential mode for CH0/CH1 :frowning:

Could it be the wiring? i attached my scheme below

no ideas? :frowning:

I had a look at two programs I used, which used SPI to communicate with devices, and I could not see anything which looks remotely like what you have.

Like i wrote above, i took most of this code from various forum or tutorial sites. So if somebody has a better or more elegant way to realize it, i'm open for suggestions!

The actual conversion is done by the chip between SPI transactions. This has a couple of consequences:

  1. When you call the readADC function, the value you get back will be the result for the channel number you passed in the previous call to readADC. Have you allowed for this?

  2. You need to have enough delay time between successive calls to readADC to allow the conversion to complete. Alternatively, you can pull /CS low and wait for MISO to go low.