Go Down

Topic: 16-bit spi ADC acts like a 15 bits... (Read 4020 times) previous topic - next topic

Alexander34

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: [Select]
#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





retrolefty

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

Alexander34

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!!!


bmccabe

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

Grumpy_Mike

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.

kg4wsv

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

signed vs. unsigned can screw you up, though.

-j

Grumpy_Mike

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.

kg4wsv

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

Angelo Stavrow

Hello all,

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

Code: [Select]

// **********************************************
// 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!

Grumpy_Mike

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.

Angelo Stavrow

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: [Select]
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?

Grumpy_Mike

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.

Angelo Stavrow

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 [font=Courier New]long[/font] instead of [font=Courier New]unsigned long[/font] 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.

Angelo Stavrow

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: [Select]
// **********************************************
// 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 [font=Courier New]long[/font] variables, I get 0 to 32768 and then -32768 to 0 across full scale.  With [font=Courier New]unsigned long[/font] 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 [font=Courier New]unsigned long[/font] to something printable (I was using DEC) was messing things up.  So the heck with it, I'll go with [font=Courier New]long[/font] and an [font=Courier New]if-then[/font] statement to correct for negative values.

Hope this helps anyone else working with these ADCs!

Go Up