Pages: [1]   Go Down
Author Topic: 16-bit spi ADC acts like a 15 bits...  (Read 2737 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 3
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi to you all,

First of all let me tell you im new here! So i hope you guys can help me.

My project:
I want to read  analog inputs from four different sensors and send them to a pc using a serial communication.

Hardware:
I recently bought a Arduino Duemilanove(Atmega328) for reading my 16 bits adc(ads8341) and sending data to a pc.  

Here's a link to the adc from texas instruments:
http://focus.ti.com/docs/prod/folders/print/ads8341.html

The problem
It seems like the adc is acting like a 15 bits adc. Let's go a little bit deeper:
Analog inputs range are 0-5v(vref is 5v). So with a 16-bits adc the 5 v is divided in 2^16( 65536) levels from 0-65535 right?
So if i put 5v on my analog inputs you should read 65535 out of my adc right?  However i get 32767 (like its is a 15-bits adc). So i tried some different input voltages (like 2v and 3.3) and all the conversions are correct only if i use 15-bits in my calculations.

the analoge input voltage  = lvl/(2^15-1)*5v

For my project i dont really need 16-bits , 14-bits is enough.  However i want to understand why it is working like a 15 bits adc and learn from the experiance.

I figure it is a software problem because the adc is sort of working.

Software
I based my mC software on this example from the arduino site:
http://www.arduino.cc/playground/Code/MCP3208

My code:
Code:
#define SELPIN 10 //Selection Pin
#define DATAOUT 11//MOSI
#define DATAIN  12//MISO
#define SPICLOCK  13//Clock
int readvalue;

void setup()
{
 //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(9600);
}

int read_adc(int channel)
{
  int adcvalue = 0;
  int cnumber = 0;
  byte commandbits =  B00000000;// read nothing....
  
  switch(channel)//switch case to select channel
  {
    case 1:  {
             commandbits = B10010100;// channel 0 selected  start bit(bit 7)-Channel select(bits 6,5,4)-dont care(bit 3)-sgl/diff(bit 2)-powerdown mode(bit 1,0)
             }
             break;
    case 2:  {
             commandbits = B11010100;// channel 1
             }
             break;
    case 3:  {
             commandbits = B10100100;// channel 2
             }
             break;
    case 4:  {
             commandbits = B11100100;// channel 3
             }
             break;
  }

  digitalWrite(SELPIN,LOW); //Select adc
  //we need 24 cycles for a full conversion
  // 8 cycles for control byte and 16 for
  // setup bits to be written
  for (int i=7; i>=0; i--){
     digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH); //8 times
    digitalWrite(SPICLOCK,LOW);    
  }  
   //read bits from adc
  for (int i=15; i>=0; i--){ //
    adcvalue+=digitalRead(DATAIN)<<i;
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);//16 times
    digitalWrite(SPICLOCK,LOW);
  }
  //24 cycles total
  digitalWrite(SELPIN, HIGH); //turn off device
  return adcvalue;
}

void loop()
{
  // send the value of analog input 0:
  char inByte;
  int ichannel;
  inByte = Serial.read();
  if(inByte == 'r')//if ibyte is r
  {
   for(ichannel=1;ichannel <=4;ichannel++)
   { //loop to read all four channels and send it to serial
     readvalue = read_adc(ichannel);
       Serial.println(readvalue,DEC);
   }
  }
  // wait a bit for the analog-to-digital converter
  // to stabilize after the last reading:
  delay(10);
}

I  send the char "r" to de atmega so that it returns the four analog values.

Can comeone please explain to me why it is acting like a 15-bits adc?

Thanks you,

Alex




Logged

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

Quote
Can comeone please explain to me why it is acting like a 15-bits adc?

Just a shot in the dark but why don't you try changing:

int readvalue;

to

Unsigned int readvalue;

The sign bit of a standard int might be causing what you are seeing?

Lefty
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 3
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Just a shot in the dark but why don't you try changing:

int readvalue;

to

Unsigned int readvalue;

The sign bit of a standard int might be causing what you are seeing?

Lefty

I tried changing it to unsigned int but it still gives me 15 bits....

thx for ur reply anyhow!!!

Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 1
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Alex,

I am also new here but allow me to take a crack at it.

The problem I see is that your code doesn't allow for a "sleep" cycle after the command byte is written to clear the busy period for the adc.  I recommend setting the SPICLOCK high then low once between writing the command byte and reading the data.  Alternatively you could write the code for reading the data in the following manner:

Code:
 
//read bits from adc
for (int i=15; i>=0; i--){
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);//16 times
    digitalWrite(SPICLOCK,LOW);

    adcvalue+=digitalRead(DATAIN)<<i;
}
  

This will also allow you to take advantage of the fact that you should be able to write the first bit of the command byte during the same clock cycle as you read the last bit of your data.

Good luck,

Brian
Logged

Manchester (England England)
Offline Offline
Brattain Member
*****
Karma: 505
Posts: 31332
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I tried changing it to unsigned int but it still gives me 15 bits....

So now change it to a long int and see what you get.
You are never going to get over 32K with an int.
Logged

0
Offline Offline
Faraday Member
**
Karma: 7
Posts: 2526
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

er, yeah, Mike, an unsigned int is 0 - 65535. AFAICT, it should fit no problem.

signed vs. unsigned can screw you up, though.

-j
Logged

Manchester (England England)
Offline Offline
Brattain Member
*****
Karma: 505
Posts: 31332
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yes the point is that if you are doing any sums with an unsigned int it screws you up and unless you deal with just bit patterns you are often better going to a long for all the variables you are using. I suspect the OP knows nothing of this.
Logged

0
Offline Offline
Faraday Member
**
Karma: 7
Posts: 2526
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Yes the point is that if you are doing any sums with an unsigned int it screws you up and unless you deal with just bit patterns you are often better going to a long for all the variables you are using
Ah, I see your point now.

-j
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Hello, World
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello all,

I'm having similar problems with an ADS8344 (the 8-channel version of the ADS8341).  I'm using unsigned long variables per Grumpy_Mike's suggestion, and have incorporated bmmcabe's posted code correction, as posted below:

Code:
// **********************************************
// Define the Arduino pins used for SPI interface
// **********************************************
#define SELPIN 10    // Selection pin
#define DATAOUT 11   // MOSI
#define DATAIN 12    // MISO
#define SPICLOCK 13  // Clock


// ************************************************************************************
// Declare (and initialize where necessary) global variables available to all functions
// ************************************************************************************
unsigned long readValue;


// *********************************************************
// Setup function - initializes the board and sets pin modes
// *********************************************************
void setup() {
  // set pin modes
  pinMode(SELPIN, OUTPUT);
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK, OUTPUT);
  
  // disable device at the outset
  digitalWrite(SELPIN, HIGH);
  digitalWrite(DATAOUT, LOW);
  digitalWrite(SPICLOCK, LOW);
  
  // Setup communications rate
  Serial.begin(9600);
}

// ****************************
// Loop function - main program
// ****************************
void loop() {
  int iChannel = 1;
    for(iChannel=1; iChannel <= 5; iChannel++) {
      Loop to read all five channels and send it to serial
      readValue = read_adc(iChannel);
      Serial.print(readValue);
      if (iChannel < 5) {
        Serial.print(",");
      }
    }
  Serial.println();
  // Wait a bit for the analog-to-digital converter
  // to stabilize after the last reading:
  delay(10);
}


// *********************************************
// Function that reads the requested ADC channel
// *********************************************
unsigned long read_adc(int channel) {
  unsigned long adcvalue = 0;
  byte commandbits = B00000000;  // Read nothing....

  switch(channel)  // Switch case to select channel
  {
    case 1:  {
      commandbits = B10000100;  // Select channel 0
      }
      break;
    case 2:  {
      commandbits = B11000100;  // Select channel 1
      }
      break;
    case 3:  {
      commandbits = B10010100;  // Select channel 2
      }
      break;
    case 4:  {
      commandbits = B11010100;  // Select channel 3
      }
      break;
    case 5:  {
      commandbits = B10100100;  // Select channel 4
      }
      break;
  }

  digitalWrite(SELPIN, LOW); // Select adc
  // We need 24 cycles for a full conversion
  // 8 cycles for control byte and 16 for
  // setup bits to be written
  for (int i=7; i>=0; i--){
     digitalWrite(DATAOUT, commandbits&1<<i);
    // Cycle the clock 8 times
    digitalWrite(SPICLOCK, HIGH);
    digitalWrite(SPICLOCK, LOW);
  }
   // Read bits from adc
  for (int i=15; i>=0; i--){ //
//    adcvalue+=digitalRead(DATAIN)<<i;
    // Cycle the clock 16 times
    digitalWrite(SPICLOCK, HIGH);
    digitalWrite(SPICLOCK, LOW);
    
    adcvalue+=digitalRead(DATAIN)<<i;
  }
  // 24 cycles total
  digitalWrite(SELPIN, HIGH); // Turn off the device
  return adcvalue;
}

Before I implemented bmccabe's code correction, sweeping 0-5V gave me a 15-bit (0-32k) reading.  Afterwards, something strange happened.  From 0V to 2.5V, I get the ADC to read from 0 to 32k; once I pass this half-full-scale point, the ADC 'rolls over' and starts readin 0 to 32k from 2.5V to 5V.

I think this could be due to the fact that I'm not reading enough bits per read.  The datasheet (which I can't link to yet as this is my first post on the forum) states that when using an external clock, we need to add one additional transfer to capture the LSB -- that is, 32 cycles instead of 24.  I'm going to investigate this a little further and will post back with my results, but if anyone has any suggestions I would really appreciate them!

Also, as a first-time poster: thanks to everyone in this thread so far for providing enough knowledge and code to get me started!
Logged

Manchester (England England)
Offline Offline
Brattain Member
*****
Karma: 505
Posts: 31332
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

You are reading it in as an unsigned long but printing it out as if it were a signed value.
It looks like it is working as your results are what I would expect.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Hello, World
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
You are reading it in as an unsigned long but printing it out as if it were a signed value.
It looks like it is working as your results are what I would expect.

Okay, I think I understand why -- Serial.print() is casting my unsigned long value to ASCII, am I on the right path here?

Not quite sure how to fix this.  If I change my code to

Code:
Serial.print(readValue, DEC);

then I still have the same problem -- should I split up the value by byte and then send those bytes sequentially?
Logged

Manchester (England England)
Offline Offline
Brattain Member
*****
Karma: 505
Posts: 31332
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Try defining the variables as simply long and not unsigned long. Long is supposed to give you 32 bit storage so that should be enough.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Hello, World
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm getting kind of frustrated with this but I suspect my troubles have less to do with my code and more to do with my circuit.

Basically, I'm just trying to measure the voltage level at one of the ADS8344's inputs.  I'm using the Arduino's +5V and GND pins to power the ADC, as well as provide VRef; I've got a 10K pot as the variable voltage source to the ADC's input.

Here's a graph of expected vs. actual results:


I can't see any reason why my code (same as above, only using long instead of unsigned long variables) would produce these results.  I'm going to try using a better (i.e., more stable) power supply option like a MAX6350 and see if this changes anything.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 8
Hello, World
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

GOT IT!

The problem was in the OP's choice of clock.  Setting PD1 = PD0 = 1 in the control bits selects the external clock (i.e., the Arduino's); I also added a fourth read byte per the "External Clock Mode, 32 Clock Cycles per Conversion" information in the datasheet (mostly zeroes).  Otherwise, the LSB of the read bytes comes in at the same time as the MSB of the control byte and I think the code just wasn't handling this properly.

However, signed/unsigned is still just not working as expected.  So, I'm leaving the variable as signed, and using logic in the code to correct negative to positive.

Code:
// **********************************************
// Define the Arduino pins used for SPI interface
// **********************************************
#define SELPIN 10    // Selection pin
#define DATAOUT 11   // MOSI
#define DATAIN 12    // MISO
#define SPICLOCK 13  // Clock


// ************************************************************************************
// Declare (and initialize where necessary) global variables available to all functions
// ************************************************************************************
long readValue;
double readVoltage;


// *********************************************************
// Setup function - initializes the board and sets pin modes
// *********************************************************
void setup() {
  // set pin modes
  pinMode(SELPIN, OUTPUT);
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK, OUTPUT);
  
  // disable device at the outset
  digitalWrite(SELPIN, HIGH);
  digitalWrite(DATAOUT, LOW);
  digitalWrite(SPICLOCK, LOW);
  
  // Setup communications rate
  Serial.begin(9600);
  analogReference(DEFAULT);
}

// ****************************
// Loop function - main program
// ****************************
void loop() {
  int iChannel = 1;

  for(iChannel=1; iChannel <= 5; iChannel++) {
    // Loop to read all five channels and send it to serial
    readValue = read_adc(iChannel);
    if (readValue < 0) { readValue += 65535; }
    readVoltage = (double(readValue)*5000)/65535;
    Serial.print(readVoltage);
    if (iChannel < 5) {
      Serial.print(",");
    }
  }
  Serial.println();
  // Wait a bit for the analog-to-digital converter
  // to stabilize after the last reading:
  delay(10);
}


// *********************************************
// Function that reads the requested ADC channel
// *********************************************
int read_adc(int channel) {
  long adcvalue = 0;
  byte commandbits = B00000000;  // Read nothing....

  switch(channel)  // Switch case to select channel
  {
    case 1:  {
      commandbits = B10000111;  // Select channel 0 (LPS-1 Uout)
      }
      break;
    case 2:  {
      commandbits = B11000111;  // Select channel 1 (LPS-2 Uout)
      }
      break;
    case 3:  {
      commandbits = B10010111;  // Select channel 2 (LPS-2 Kout)
      }
      break;
    case 4:  {
      commandbits = B11010111;  // Select channel 3 (External)
      }
      break;
    case 5:  {
      commandbits = B10100111;  // Select channel 4 (2.500V Reference)
      }
      break;
  }

  digitalWrite(SELPIN,LOW); // Select adc
  // We need 24 cycles for a full conversion
  // 8 cycles for control byte and 16 for
  // setup bits to be written
  for (int i=7; i>=0; i--) {
     digitalWrite(DATAOUT,commandbits&1<<i);
    // Cycle the clock 8 times
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  // Read bits from adc
  for (int i=16; i>=0; i--) {
    adcvalue+=digitalRead(DATAIN)<<i;

    // Cycle the clock 17 times
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  
  // Shift in 7 zeros to complete conversion cycle
  for (int i=6; i>=0; i--) {
    digitalRead(DATAIN)<<0;
    
    // Cycle the clock 7 times
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  
  // 32 cycles total
  digitalWrite(SELPIN, HIGH); //turn off device
  return adcvalue;
}

With long variables, I get 0 to 32768 and then -32768 to 0 across full scale.  With unsigned long variables, I get 0 to 32768 to half scale (2.5V) and then up around 4 billion from 2.5V to full scale.

The display on the oscilloscope is fine, but for some reason the conversion from unsigned long to something printable (I was using DEC) was messing things up.  So the heck with it, I'll go with long and an if-then statement to correct for negative values.

Hope this helps anyone else working with these ADCs!
Logged

Pages: [1]   Go Up
Jump to: