Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Netduino with 24Bit ADC (LTC2400) Help.


  • Please log in to reply
19 replies to this topic

#1 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 12 May 2011 - 08:19 AM

I'm fairly new with netduino, had mine (plus) for couple of days now and I'm very pleased with it so far.
My electronics skills are pretty basic so please bare with me and my pretty long rant :)

Any help/hints are greatly appreciated.

I've build a 24bit ADC using the LTC2400 based on the this.
This was initially made for arduino on which works like a charm but now I need to move this to my netduino plus.

Looking around in the LTC2400 datasheet for the SPI configuration values helped a bit but some things like "SS Active", "Clock idle" and "Clock rate" are still missing as I am not sure what I should be looking for.

I've created a test project based on the MCP320X Test by GDSever but ended up changing most of the ReadADC method to match the code from my arduino project (provided in the end of the post).

The end results is pretty random, some times it seems to work (usually when voltage is around 1.6v for some weird and most likely random reason).
Let me try to explain what happens.

byte[] writeArray = new byte[];
byte[] readArray = new byte[8];
spi.WriteRead(writeArray, readArray);

This populated the readArray with data on the first 4 bytes and the rest are full of 1111s;
To me this seems wrong, could it be due to SPI configuration values?

Due to the fact that the ADC is 24 bit, the data is split into 4 bytes.
The first byte seems to contain the integer's sign (-/+) and some leading junk data.
The last byte seems have 4 bits junk data in the end.

Does SPI.WriteRead do something with the binary data? Reverse their order or something?

What else might I be missing?

C# ReadADC method.
public Int32 ReadADC(uint numSamples = 1)
{
    Int32 ltw = new byte();

    byte[] writeArray = new byte[4];
    byte[] readArray = new byte[8];

    // Send the command and read the response.
    SPI.Configuration config = CurrentConfig();
    using (SPI spi = new SPI(config))
    {
        Thread.Sleep(100);
        spi.WriteRead(writeArray, readArray);

        ltw = 0;

        bool sig = false;
        if ((readArray[0] & 0x20) == 1)
        {
            sig = true;
        }

        readArray[0] &= 0x1F;
        ltw |= readArray[0];
        if (sig)
        {
            ltw |= 0xF0;
        }
        ltw <<= 8;

        ltw |= readArray[1];
        ltw <<= 8;

        ltw |= readArray[2];
        ltw <<= 8;

        ltw |= readArray[3];

        ltw = ltw / 16;

        spi.Dispose();
    }

    return ltw;

}

Arduino code.
/* LTC2400 24 Bit ADC Test
* Connect an LTC2400 24 Bit ADC to the Arduino Board in SPI Mode
*
*
*
* KHM 2009 /  Martin Nawrath
* Kunsthochschule fuer Medien Koeln
* Academy of Media Arts Cologne

*/
#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 2         // LTC2400 Chip Select Pin  on Portb 2
#define LTC_MISO  4      // LTC2400 SDO Select Pin  on Portb 4
#define LTC_SCK  5       // LTC2400 SCK Select Pin  on Portb 5

void setup() {

 cbi(PORTB,LTC_SCK);      // LTC2400 SCK low
 sbi (DDRB,LTC_CS);       // LTC2400 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("LTC2400 ADC Test");

}
float volt;
float v_ref=3.0;          // Reference Voltage, 5.0 Volt for LT1021 or 3.0 for LP2950-3

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

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

 cbi(PORTB,LTC_CS);             // LTC2400 CS Low
 delayMicroseconds(1);
 if (!(PINB & (1 << PB4))) {    // ADC Converter ready ?
   //    cli();
   ltw=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
   ltw |= b0;
   ltw <<= 8;
   b0 = SPI_read();
   ltw |= b0;
   ltw <<= 8;
   b0 = SPI_read();
   ltw |= b0;
   ltw <<= 8;
   b0 = SPI_read();
   ltw |= b0;

   delayMicroseconds(1);

   sbi(PORTB,LTC_CS);           // LTC2400 CS Low
   delay(200);

   if (sig) ltw |= 0xf0000000;    // if input negative insert sign bit
   ltw=ltw/16;                    // scale result down , last 4 bits have no information
   volt = ltw * v_ref / 16777216; // max scale

   Serial.print(cnt++);
   Serial.print(";  ");
   printFloat(volt,6);           // print voltage as floating number
   Serial.println("  ");

 }
 sbi(PORTB,LTC_CS); // LTC2400 CS hi
 delay(20);

}
/********************************************************************/
byte SPI_read()
{
 SPDR = 0;
 while (!(SPSR & (1 << SPIF))) ; /* Wait for SPI shift out done */
 return SPDR;
}
/********************************************************************/
//  printFloat from  tim / Arduino: Playground
// printFloat prints out the float 'value' rounded to 'places' places
//after the decimal point
void printFloat(float value, int places) {
 // this is used to cast digits
 int digit;
 float tens = 0.1;
 int tenscount = 0;
 int i;
 float tempfloat = value;

 // if value is negative, set tempfloat to the abs value

   // make sure we round properly. this could use pow from
 //<math.h>, but doesn't seem worth the import
 // if this rounding step isn't here, the value  54.321 prints as

 // calculate rounding term d:   0.5/pow(10,places)
 float d = 0.5;
 if (value < 0)
   d *= -1.0;
 // divide by ten for each decimal place
 for (i = 0; i < places; i++)
   d/= 10.0;
 // this small addition, combined with truncation will round our

 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;
 }
}

Attached Files



#2 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 12 May 2011 - 09:55 AM

Welcome George! I am not sure, but I think you have to declare a double buffer to exchange data to and from your SPI device. I mean that the WriteRead method acts a byte transfer both to and from the device (i.e. the ADC). In this case you should provide two equally-sized buffers (an input and an output). Try this. Cheers
Biggest fault of Netduino? It runs by electricity.

#3 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 12 May 2011 - 11:27 AM

Thanks for a fast responce mario. At first I had both the write and read arrays set to 4 bytes and there was no difference. I changed it again now to make sure and still gives out the same results.

#4 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 12 May 2011 - 11:52 AM

George, after a good lunch I have a bit more of time to inspect the code. Assuming you haven't a scope to check the circuit, let's analyze pin by pin of the ADC... SDO (pin 6), is going to Hi-z when the ADC is not accessed, so /CS=high. Since isn't a good choice leaving the MISO input without any drive, I'd add a weak pullup to this pin. Then, as the ADC specs, you should configure the SPI for sampling on the RISING edge of the clock. It seems that on the FALLING edge the ADC shifts its data out. Cheers
Biggest fault of Netduino? It runs by electricity.

#5 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 12 May 2011 - 01:40 PM

Mario thank you very much for your help, it's greatly appreciated, I'll try not to bore you much with this :P

I tried to pull up the pin6 with a 1K ohm resistor on the 3V3 without any luck.

This is my current SPI configuration.
return new SPI.Configuration(
  ChipSelectPin,
  false,
  0,
  0,
  false,
  false,
  defaultClockRateKHz,
  SPI.SPI_module.SPI1
);

In order to configure the SPI for sampling on the RISING edge should be altered like this:
return new SPI.Configuration(
  ChipSelectPin,
  false,
  0,
  0,
  false,
  true,
  defaultClockRateKHz,
  SPI.SPI_module.SPI1
);
But any ideas on the correct clock rate and Clock_IdleState values?

This is sample from the readArray[] values:
[0]: 01010001
[1]: 00001001
[2]: 10010011
[3]: 10001101
ps. The data are with the pull up on the board

I then "& 0x1F" the first [0] byte to clear up the first bits as they should be junk (according to the arduino/working code).
[0]: 00010001

Then the bytes get shifted to the left and append the next one as 0-1-2-3 which results to this.
[ltw]: 00010001000010011001001110001101

Finally the 4 last bytes are stripped off as they should contain junk data.
[ltw]: 00000101000100001001100100111000

Is my logic sane?

#6 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 12 May 2011 - 01:43 PM

ps. I'll try to find a scope, till then would a polymeter help?

#7 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 12 May 2011 - 03:08 PM

George, you are the welcome. About the multimeter, could be useful only to check the ADC analog input, but I assume is stable enough. A 24-bit conversion over VRef=5V surely have several LSBs of junks. Supposing a 1mV of noise over the stable input, would means only the upper 12 MSBs are stable, while the lower are floating (junk). A question: is your ADC powered at +5V or +3.3V? Perhaps don't mind, but just to bear in mind any possible parameter. Finally, according to the specs, your ADC is getting out 32 bits out of only 24 of conversion. At page 12, the table shows that the first 4 LSBs are junk (sub-conversion result). I don't mean the noise of the input signal. Then, the higher bits (28-31) are for status. For making a test, I would take only the upper word, then and-ize with 0x0FFF: if the result looks like the input, you won! Cheers
Biggest fault of Netduino? It runs by electricity.

#8 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 12 May 2011 - 05:10 PM

In order to configure the SPI for sampling on the RISING edge should be altered like this:

return new SPI.Configuration(
  ChipSelectPin,
  false,
  0,
  0,
  false,
  true,
  defaultClockRateKHz,
  SPI.SPI_module.SPI1
);

Unfortunately, I don't have the device to verify this, but I would try passing 'true' for Clock_IdleState, if I wanted to configure sampling for the rising edge (Clock_Edge = true). The following (a little bit unusual) sentence in the datasheet got my attention: The Serial Clock mode is determined by the level applied to SCK at power up and the falling edge of CS.

#9 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 13 May 2011 - 04:04 AM

Unfortunately, I don't have the device to verify this, but I would try passing 'true' for Clock_IdleState, if I wanted to configure sampling for the rising edge (Clock_Edge = true). The following (a little bit unusual) sentence in the datasheet got my attention: The Serial Clock mode is determined by the level applied to SCK at power up and the falling edge of CS.

I don't think it's a good choice.
If you read carefully, the power-up level on the clock input determines whether to use an external clock source (the MCU) or an internal one (the ADC itself).
A low level configures the ADC to use the ext source.

I'd be scared about the "initial" condition of a so-messy firmware (not because Chris!).
How an user could be determine the initial status of certain I/Os via managed code?
Another weakness...

Anyway, the easiest way to check the circuit is using a scope.
Cheers
Biggest fault of Netduino? It runs by electricity.

#10 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 13 May 2011 - 10:22 AM

I'm currently trying to get my hands on an oscilloscope. Could you please let me know what I need to check with it? Thanks in advance.

#11 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 13 May 2011 - 12:41 PM

Hi George. I'd check the SPI bus to see exactly what are the data exchanged. Overall, I'd check primarily whether the clock source is correctly selected at power-on: it must be "external", that is provided by the MCU. After that I'd check whether the config data are transferred correctly, and finally what the ADC outputs. To do all that you must have a two-traces scope: best if is a digital-scope. Cheers
Biggest fault of Netduino? It runs by electricity.

#12 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 17 May 2011 - 01:24 PM

Ok some things are more clear to me now but others are still foggy.

1) Mario, you said that my clock source should be "external".
According to the schematic I followed for the ADC design the f0 pin of the LTC2400 goes to the VCC.
Posted Image
This should result in the LTC2400 using the internal clock with a 50hz rejection (I have no idea what this rejection is)
Posted Image

What should I change the f0 pin to use an external clock?

2) I got my hands on a scope and I tried to figure out some stuff but without much help.
The top line is the CS and the bottom is the output.
Posted Image

This is a single transaction and the result that I got from my code was this:
[0]: 00101000
[1]: 10010000
[2]: 01101101
[3]: 00010011

3) As for the configuration I changed the clock_edge to true as according to the following graph from the datasheet it seems that when the internal clock is used, the data is sampled on the rising of the clock.
Posted Image

#13 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 19 May 2011 - 03:47 AM

George, I apologize for being in delay for the answer. 1) I mean the SPI clock must be external, not the conversion clock. Your schematic looks OK. By using Netduino the SPI is always master (as far I know), so it must be *always* the MCU that feed the SPI clock to any device. This particular ADC offers the ability to be a master also: that should be avoided. 2) You should observe the SPI clock and data traces together. In particular the SPI clock because it may involved in the above clock generation. 3) OK. Anyway, you read four bytes. Which is the voltage input? How much is the expected value? According with the specs, if you get the following bytes: [0]: 00101000 [1]: 10010000 [2]: 01101101 [3]: 00010011 we may discard the last two bytes (at the moment), then the very first 4 LSB must also cut off. The resulting data is: 1000 1001 0000 This value is close to expected one? Those 12 bits are pretty stable (supposing that your input is stable also)? Cheers
Biggest fault of Netduino? It runs by electricity.

#14 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 19 May 2011 - 08:08 AM

Mario thanks for still being around :)
I seem to have solved the whole thing.

1) Configuration is like this for the schematic I have used:
            return new SPI.Configuration(
               ChipSelectPin,
               false,
               200,
               200,
               false,
               true ,
               1000,
               SPI.SPI_module.SPI1
               );

2) The first line is byte stream from the ADC and the second one is the usable bits
00101000 10010000 01101101 00010011
xxx01000 10010000 01101101 0001xxxx

3 and final) This last thing has freaked me out big time.
The calculation I did to see the Volt value was this:
3000 * adcValue / System.math.pow(2, 24)

This resulted in something obscene.
When a colleague was messing around with my code he changed it to this:
3000.0 * adcValue / System.math.pow(2, 24)

Which actually gave the correct volt value!!!
I don't really get why this happens but I assume it has something to do with my declaration of ltw
Int32 ltw = new byte();
ltw = 0;
And the fact that i'm doing binary calculations on an integer.
So I guess that .net wasn't able to automatically cast it to the correct type.

In any case it seems to be working as expected now.
I'll pack it up a bit better and post it here for future reference.

Mario I'd like to thank you for all your help and patience. :)

G.

#15 randomrichard

randomrichard

    New Member

  • Members
  • Pip
  • 1 posts

Posted 02 August 2011 - 02:58 PM

Thanks for this discussion, which I hope to be able to learn from when trying to control another 24bit ADC with my new Netduino. RR

#16 George Antoniadis

George Antoniadis

    Member

  • Members
  • PipPip
  • 25 posts

Posted 02 August 2011 - 04:18 PM

Hey Richard, if you need any help please do ask :)

#17 Ryan Mick

Ryan Mick

    Advanced Member

  • Members
  • PipPipPip
  • 36 posts
  • LocationSacramento, CA

Posted 11 October 2011 - 08:15 PM

George, have you posted this in the wiki yet?

#18 Mike P

Mike P

    Advanced Member

  • Members
  • PipPipPip
  • 41 posts
  • LocationAuckland, New Zealand

Posted 13 October 2011 - 03:41 AM

3 and final) This last thing has freaked me out big time.
The calculation I did to see the Volt value was this:

3000 * adcValue / System.math.pow(2, 24)

This resulted in something obscene.
When a colleague was messing around with my code he changed it to this:
3000.0 * adcValue / System.math.pow(2, 24)

Which actually gave the correct volt value!!!
I don't really get why this happens but I assume it has something to do with my declaration of ltw


Hi George,
I realise this post topic is quite old but someone else has brought it to the top again and it was left with an un answered issue.
There's two things at play here.

Firstly the reason your code didn't work as intended is because intermediate stages in your calculation overflowed the capacity of the int data type.
Int is a 32 bit integer and your calculation takes 3000 (12 bits) and multiplys it by adcValue (up to 24 bits) potentially the result could require 36 bits.
You need to use type long which is 64 bits long and your resulting answer would be correct, although it would be truncated to an integer millivolt value.
More likely you wanted to do floating point math, and this is what your freinds change does.

3000 * adcValue / System.math.pow(2, 24)
3000.0 * adcValue / System.math.pow(2, 24)

The two lines of code above have one important difference. In one 3000.0 is a floating point value and in the second 3000 is an integer.
so the first line of code is int * int / int and .net will solve this using integer maths.
by declaring the constant as a float the second version is float * int / int. In this case .net won't try to solve this using integer math. It will instead convert the integers to floats and use floating point math.

Something else you might find useful are the following alternatives to using System.math.pow(2, 24).
You could use the resulting value directly eg 16777216 or 0x1000000
or try (1<<24). This is "1" shifted left 24 places.

Regards,
Mike Paauwe

#19 Mike P

Mike P

    Advanced Member

  • Members
  • PipPipPip
  • 41 posts
  • LocationAuckland, New Zealand

Posted 13 October 2011 - 03:49 AM

SDO (pin 6), is going to Hi-z when the ADC is not accessed, so /CS=high. Since isn't a good choice leaving the MISO input without any drive, I'd add a weak pullup to this pin.


Hi Mario,
The firmware sets the MISO pin to its GPIO function and enables the internal pullup between transactions so an external pullup is not necessary.
Regards,
Mike Paauwe

#20 Mario Vernari

Mario Vernari

    Advanced Member

  • Members
  • PipPipPip
  • 1768 posts
  • LocationVenezia, Italia

Posted 13 October 2011 - 05:28 AM

That's right, Mike. That time I still didn't know every single "wonder" of this mighty SPI! Cheers
Biggest fault of Netduino? It runs by electricity.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.