Go Down

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

#### Alexander34

##### Jun 04, 2010, 08:29 pm
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.

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

#1
##### Jun 04, 2010, 10:15 pm
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:

to

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

Lefty

#### Alexander34

#2
##### Jun 05, 2010, 11:47 am
Quote
Just a shot in the dark but why don't you try changing:

to

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

#### bmccabe

#3
##### Jun 17, 2010, 08:08 pm
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 adcfor (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

#4
##### Jun 18, 2010, 03:31 pm
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

#5
##### Jun 18, 2010, 03:40 pm
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

#6
##### Jun 18, 2010, 03:44 pm
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

#7
##### Jun 18, 2010, 06:13 pm
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

#8
##### Jul 29, 2010, 04:30 pm
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

#9
##### Jul 29, 2010, 04:56 pm
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

#10
##### Jul 29, 2010, 06:00 pm
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

#11
##### Jul 29, 2010, 08:08 pm
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

#12
##### Jul 30, 2010, 03:59 pm
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

#13
##### Jul 30, 2010, 06:46 pm
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

Please enter a valid email to subscribe