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.
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;
}
}
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.
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.
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.
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.
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.
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.
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.
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.
This should result in the LTC2400 using the internal clock with a 50hz rejection (I have no idea what this rejection is)
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.
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.
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.
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.
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.
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