Author Topic: Reading SPI with AVR  (Read 5323 times)

0 Members and 1 Guest are viewing this topic.

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Reading SPI with AVR
« on: July 16, 2016, 02:46:01 pm »
Hi, im trying to talk with LTC2420 ADC chip. But never seen that slave(LTC2420) clocks out data by self. So don't know how to read it by ATmega32. If i try to clock out data by atmega everything crashes :) and no valid data can see.

Now what i do is just set CS pin for LTC2420 LOW and it starts conversions (can see 24 clock cycles and data on scope) but how to grab data with atmega? Do i have to set atmega SPI Slave mode? If so how i have to drise SS pin on atmega? Or i can drive it by code when want to read ADC chip?

hope its understandable :)

Regards.
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #1 on: July 16, 2016, 03:21:38 pm »
You need to be careful of the state of your SCK output to the ADC at the moment when CS goes low.  Read the last paragraph on Page 14 here:
http://cds.linear.com/docs/en/datasheet/2420f.pdf

When you have the SCK signal handled properly, the 2420 will behave just like any other SPI peripheral.  This all has to do with CPHA and CPOL:
http://www.avrbeginners.net/architecture/spi/spi.html

« Last Edit: July 17, 2016, 02:46:29 pm by rstofer »
 
The following users thanked this post: xoom

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #2 on: July 17, 2016, 01:04:40 pm »
Thank you. You was exactly right about SCK when CS goes LOW :) SCK must be LOW on CS falling edge to enter external clock mode :) now i can clock data out as usual:)

Thanks again :)
 

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #3 on: July 17, 2016, 09:19:50 pm »
Ok now that i solved problem with SPI reading, got another problem with firmware  :palm: as i read 3 bytes from ADC then put them together and try to calculate volts from data i get like almost random numbers.. Its like 3 times shows somewhat like maximum.. here is fragments from code

uint8_t Data[3]; // 3 byte from LTC2420 ADC
uint32_t adcvalue=0, ch1=0,ch2=0;

adcvalue=((Data[2]<<16)|(Data[1]<<8)|Data[0]); //put 3 bytes to 24bit long variable
adcvalue=(adcvalue & 0xFFFFF); //drop out 4 MSB bits (don't use those status bits now)
ch2=(32000*adcvalue)/0xFFFFF; // and here i think where the problem comes (maybe overflows 32bit variable)

is there any proper way to do that? :)
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #4 on: July 17, 2016, 10:37:45 pm »
Use floating point...

Your converter can send a value as large as 2^20-1, and you want to multiply by 2^15 so, naturally, it is possible to overflow 2^32.

Maybe you can use 64 bit arithmetic:
http://stackoverflow.com/questions/9606455/how-to-specify-64-bit-integers-in-c
« Last Edit: July 18, 2016, 01:57:21 am by rstofer »
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #5 on: July 17, 2016, 11:05:53 pm »
You might want to select a few possible values in 24 bits and walk through the math by hand.  When you divide by 2^20-1, you are losing a lot of bits if, for example, your input was only 0x00001.  Are you sure the conversion calculation is right?
« Last Edit: July 18, 2016, 01:56:50 am by rstofer »
 

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #6 on: July 18, 2016, 01:26:41 pm »
I tryied with uint_64t, but still something looks wrong as result varies from minimum to maximum like 3 times but at least it starts showing more then 9.999 result (32.00) not like with 32bit when max is 3.2xx.

the formula for calculation im using like allways used in AVR ADC :) but there is just 10bit ADC.

with float variables don't know how to cast them later to integer.. as im using 7seg displays and take care with decimal point by my self.

And what about that math by hand from bits? any example would be appreciated.
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #7 on: July 18, 2016, 02:15:05 pm »
TI recommends pre-computing the value of the LSB in floating point,  They then read a 32 bit ADC value, cast it to a double and multiply by the LSB value.
https://e2e.ti.com/blogs_/b/precisionhub/archive/2016/04/01/it-s-in-the-math-how-to-convert-adc-code-to-a-voltage-part-1

None of the simple examples I have found convert a large number of bits.

2^20 is a number with a 1 followed by 6 decimal digits - a 6-1/2 digit meter!  Are you displaying that many?  Instead of dividing off 20 bits after the multiply, why not lose the bits much earlier.  If there are enough bits, divide the reading by 100 before multiplying by 320.  Or, divide by 10 before multiplying by 3200.

I don't have any sample code and I didn't find a link to anything dealing with a 20 bit DAC.

An IEEE double floating point value has approximately 16 significant digits while a standard float has about 7.

I would write a little C code that would convert bits starting at 0x00000 then 0x00001, 0x00003, 0x00007, 0x0000f, 0x0001f and so on shifting and filling toward 0xfffff.  I would print the input value and the result.  It's only going to output about 21 lines but I would know everything there is to know about the conversion.

I'm guessing that 32000 is your 3.2 volt reference?  Have you actually calculated the LSB value (with a calculator)?

In any event, a 20 bit number times a 15 bit number yields as many as 35 bits in the result.  Since you only have 32 bits, the pattern will probably cycle 8 times.
 

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #8 on: July 18, 2016, 02:51:53 pm »
32000 is my maximum input  (its 0 - 32000mV) so i multiply adcvalue by maximum and then divide by full scale(20bit) that's how i should get full range :)
vref is 2.5v and input is with voltage divider (32v = 2.5v)

For example 8bit:

mV = (5000mV*ADC)/255 so i should get 0 - 5000 result.
don't know is that right way to do that but i done that with 8bit AVR ADC and worked well :)

im displaying 4 digits (x.xxx - xx.xx)
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #9 on: July 18, 2016, 04:02:19 pm »
32000 is my maximum input  (its 0 - 32000mV) so i multiply adcvalue by maximum and then divide by full scale(20bit) that's how i should get full range :)
vref is 2.5v and input is with voltage divider (32v = 2.5v)

For example 8bit:

mV = (5000mV*ADC)/255 so i should get 0 - 5000 result.
don't know is that right way to do that but i done that with 8bit AVR ADC and worked well :)

im displaying 4 digits (x.xxx - xx.xx)

The formula is correct but there is some discussion about the divisor.  Yes, 255 (or 2^20-1) is correct but dividing by 256 or 2^20 is easier and, in the case of 2^20, the loss of precision is insignificant.

You have at least 4 bits of excess resolution.  So, divide by 16 first (shift right 4 bits) then multiply by 32000 and divide by 2^16 (or 2^16-1).  OR, just multiply the reading by 2000 (32000/16 - the 4 bits above) and divide by 2^16 or 2^16-1.  The calculation just barely fits in 32 bits (unsigned).
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #10 on: July 18, 2016, 04:21:55 pm »
A C union can save a lot of shifting and adding when you fetch the reading.  Try this with your favorite C compiler...

Code: [Select]
// ConsoleApplication6.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdint.h>
#include <stdio.h>

union {
uint32_t value;
uint8_t  regs[4];
uint16_t results[2]; // actual result, in volts, is in results[1]
} reading;

int main()
{
 reading.regs[0] = 0xFF; // highest possible value
 reading.regs[1] = 0xFF;
 reading.regs[2] = 0x0F; // high 4 bits are always 0
 reading.regs[3] = 0x00; // force to 0

 // now compute voltage
 reading.value = 2000 * reading.value;
 // tell the world!
 printf("Result is %6d volts\n", reading.results[1]);
 return 0;
}

#include "stdafx.h" is a Microsoft Visual Studio thing and probably not necessary anywhere else!


ETA:: This version is modified over what was originally posted here
ETA:: And modified again

« Last Edit: July 18, 2016, 09:08:03 pm by rstofer »
 
The following users thanked this post: xoom

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #11 on: July 19, 2016, 09:54:10 am »
I think i have to redesign board layout.. ADC chip is badly grounded.. if i short Vin to GND i allways get negative value.. + there is huge spikes when reading ADC (atleast conversion is done while no reading) but still my board is badly designed.. Atmega getting bit warm when driving 8 x 7seg displays and they are common cathode.. cathodes are driven with NPN transistors.. and segments going through resistors to atmega IO pins. And a lot of current adds up :) need to find some better way to drive those also :) so there is no such load on atmega :)

but still thanks rstofer for your help.
 

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #12 on: July 19, 2016, 11:38:29 am »
Just little update before starter making new board :) just played little bit more with code and finaly got some proper readings :)

data = data>>4;
mV = data * Vref / 65536; //Vref = 2.5V  // divided by 16
ch2 =  mV*1000; // multiply by 1000 to get push decimal point away so i can display on 7seg.

allways seen examples with division by full 20/24bit .. but as im dividing by 16 im getting rid of 4 bits.
« Last Edit: July 19, 2016, 02:47:48 pm by xoom »
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #13 on: July 19, 2016, 09:41:14 pm »
With all expressions, including ADC conversions, you want to multiply before you divide.  You lose precision otherwise.

In my sample code, I don't divide the conversion value at all, I simply multiply it by 2000 which is 32,000 divided by 16, exactly.  The maximum value no longer overflows the 32 bit word, it uses nearly all of the 31 bits (32767 being the maximum value) and the value for 0x0F FFFF is 31999.

The union allows the same 4 bytes of memory to be treated in 3 different ways.  The ADC registers are put in to byte sized variables where they automatically become a 32 bit word for multiplication and the result is in the upper 16 bits ready to use.

No shifting or masking is required other than scrapping the upper 4 bits of the 3rd byte of the ADC result.  No ADC bits are dropped before multiplication and the result will never overflow.
 
The following users thanked this post: xoom

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #14 on: July 20, 2016, 07:23:00 pm »
Hi, rstofer

tryed your code on online compiler. Found it accidently: https://codepad.remoteinterview.io but its pretty cool :)

as im not very good coder and still dont have good idea how your code works.. but on that compiler it works good :)
 gona try it on AVR :)

Thanks for your time and help :)
 

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #15 on: July 20, 2016, 08:09:28 pm »
Your code works brilliantly :) looks like no need any math at all :D just by magic :)

+ Your code is sooo optimal :) before have 16% atmega memory used with float variables and so on.. with your code droped to 5.7%  :-+

Need study your code, but i think here is more about math before getting in messy math :)
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #16 on: July 20, 2016, 09:22:11 pm »
The 'code' is unimportant, it simply multiplies by 2000.  The trick is in the union.  In C, a union of two (or more) variables assigns them to the exact same memory address.

Think of it this way:  Draw a sideways rectangle 4 units wide (4 bytes, 32 bits)  This is the representation of 'value' - an unsigned 32 bit variable.  Maybe tentatively subdivide it into 4 equal blocks.
Now below that rectangle, draw 4 rectangles side to side so they wind up, in total, the same width as the first rectangle.  These are the 4 bytes of regs - still just 32 bits.  You can think of the left-most block as regs[0] and we fill them left to right.
Finally, below the 4 blocks, draw 2 medium size rectangles side to side.  Each is half of the width of the 32 bit rectangle and twice the size of the 8 bit rectangles.  In the same way that the left-most block of the 4 blocks was regs[0], the left-most block here is results[0].  Two 16 bit values is still 32 bits.

Now, stuff a value into regs[0]..regs[4] and copy them over to the other two representations and you can see how the values change representation.  Easy!  But remember, all 3 representations have exactly the same address and their contents are identical because they are all the same variable.

No matter which way I choose to address the union (as a 32-bit word, or 4 8-bit bytes or 2 16-bit ints), the address is identical and the values are divided up properly.

Magic!
« Last Edit: July 20, 2016, 10:04:28 pm by rstofer »
 
The following users thanked this post: xoom

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #17 on: July 21, 2016, 08:16:42 am »
Ok i got it how union works :) what is little bit confusing me now is which way data is filled. As for your code i had to rearrange data.. as LTC2420 puts MSB first and i cant put it in regs[0] do i first read to variables then fetch them to union

int ReadV()
{
CS_LOW_VOLT;
b0 = ReadSPI_byte();
b1 = ReadSPI_byte();
b2 = ReadSPI_byte();
CS_HIGH_VOLT;
if((b0 & 0x20) == 0) //Sign check
{
ch2=0; //to be implemented
}
else
{
reading.regs[0] = b2;
reading.regs[1] = b1;
reading.regs[2] = (0x0F & b0);
reading.regs[3] = 0x00;
reading.value = 2000 * reading.value;
ch2 = reading.results[1];
}
return 0;
}

i made that drawing but still that multiply confuses again.. if i multiply that 32bit (value) by 2000 i get way over 32bits value.. maybe picture shows better then i can explain :)



 

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #18 on: July 21, 2016, 09:24:18 am »
looks like sorted out and got it graphicaly working :)



now wondering how to make that working for current measure.. as im using same Vref. and maximum current will be 5A (1.1V drop on 0.22ohm shunt resistor)

rstofer, you have some kind of logic that you imagine things in binary :) i realy liked this idea about 32V that i picked accidently:) but lets say if i want 10V full scale.. and im lost again   :-//

EDIT: i think understood a bit.. as long as i use 16bit data its just FULLSCALE/16 and i get multipier number for full scale reading
example 10V Fullscale : 10000mV / 16bit = 625.
so i have to use 625 instead of 2000 and will get 10000 full scale.
« Last Edit: July 21, 2016, 09:55:30 am by xoom »
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Reading SPI with AVR
« Reply #19 on: July 21, 2016, 01:49:39 pm »
I really like your drawing!  It truly represents what is going on.

Your ADC returns a 20 bit result into a 32 bit variable.  The 32 bit value can be multiplied by anything less than 2^12 (4096) without overflowing.  In the first example (2000), the multiplier is 11 bits so it can't overflow, no matter what.  625 is 10 bits, no problem!

For an example, put 0x0FFFFF in your drawing - all 20 bits are one.  Now count the number of zeros to the left of the most significant '1'.  That's the maximum size of the multiplier 2^12 (4096).  The result of such a multiplication is 2^32 minus 4096 (no overflow) because the multiplicand was 2^20 - 1.

We're more used to thinking in decimal:  9 times 9 (the maximum single digit values) will never overflow a 2 digit result.  Same with 99 times 99 requiring creating a 4 digit result.

And, yes, 625 is the right answer.
 
The following users thanked this post: xoom

Offline xoomTopic starter

  • Regular Contributor
  • *
  • Posts: 110
    • E.xoom
Re: Reading SPI with AVR
« Reply #20 on: July 26, 2016, 04:18:22 pm »
As im redesigning board for ADC i thought maybe i can use one ADC for 2 meassurements (V & A) maybe i can multiplex ADC input with 74HCT4053 ?

is it any good for that solution? is it adds lots of noise? is there a better multiplexer chip?

Thank you for any recommendations :)
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf