5 years, 2 months ago.

SPI external 16 bit ADC

Hi all,

I have been trying to get the LTC1859 16 bit ADC to work with the NUCLEO-F303RE and am having a heck of a time with something that should be simple...I understand what is going on, but I think I've got the bits being shifted/or'd wrong. Here is a link to the datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/185789fb.pdf

There is an 8bit word sent to the device to tell it what settings to expect (single/differential input, gain, etc), which I have in my code as 0x88. I write this, then I write two dummy variables to get the receiving data. I then shift the high bits over and or the low bits.

I have triple checked all my connections, I am using SPI1 pins. I am using a digital pin as the CS. I also checked power to the board and external ADC.

So here are my questions...

  • 1) My output data is in 16 bits, I have spi.format as 16, should it be 8?
  • 2) I have no idea if SPI or this chip reads in LSB/MSB, I couldn't find it on the datasheet, could this be a problem?
  • 3) Am I OR'ing the two values together correctly?

Thanks...I have been working on this for 3 days straight, I feel like it is something simple I am missing. Attached is my code and what i am outputting. I have a sine wave going to it on channel 0 from 0-5V with 1.2kHz signal. So I should be reading values from 0 to 65535....ugh.

LTC_ADC

#include "mbed.h"
 
 // NUCLEO F303RE, using CN10 SPI1_SCLK=11=PA5, SPI1_MISO=13=PA6, 
 // SPI1_MOSI=15=PA7
SPI spi(PA_7, PA_6, PA_5); // mosi(out), miso(in), sclk(clock)

//I think ours is RD (read input); use D9/PWM=19=PC7
DigitalOut rd(PC_7); // cs (the chip select signal)
 
Serial pc(USBTX, USBRX); // tx, rx ( the usb serial communication )
 
int main() {
    // Setup the spi for 8 bit data, mode = 0 low SS clock & CPHA=0
    // if not working try mode 1 -> unclear in datasheet
    spi.format(16,0);
    
    // ours has max 20MHz for sck so I guess 1Mhz is okay
    spi.frequency(1000000);
 
    // notify the user that we are starting with the ADC communication
    pc.printf("Starting ADC interaction\n");
    
    // send first 8 bits, assume rest of 16 go as don't care
    spi.write(0x88);
    // lets do this for 200 values
    int i=1;
    while (i<=200) {
 
        // Select the device by setting read input low
        rd = 0;
        
        // in the manual this stands for the 8 bit word to tell the LTC what our 
        // setup is desired to be, we have 0x88 
        // now the device sends back the readings 16 bits, 8 bits at a time
        // MISO & MOSI work at the same time, so we send 'dummy variable' to get
        // contents of the ADC
    
        uint8_t high_receive = spi.write(0x00);
        uint8_t low_receive = spi.write(0x00);
 
        // add two 8 bit together to form 16 bit value
        // short is signed 16bit integer
        
        short int16_receive = (((high_receive & 0xFF) << 8) | (low_receive & 0xFF));
        
                
        // print it for the user; \r\n 
        // let's printf explicity hold to the left instead of staggering
        pc.printf("sensor value = %u\r\n", int16_receive);
 
        // Deselect the device
        rd = 1;
 
        // delay some time before reading again
        wait(0.01);
        i=i+1;
    }
}

/media/uploads/dizoncmbed/adc_values.png

1 Answer

5 years, 2 months ago.

What do you have the CONVST pin connected to? It looks like the easiest thing is to tie it to nRD, if not then you need to toggle it high for at least 40 ns and then wait 5 us of for busy to go away before reading the value back.

You set the SPI to do 16 bit transfers but then treat it as if it was doing single byte transfers. If it's set to 16 bits then you only need one transfer to get all 16 bits.

From the datasheet it looks like you need to send the config every time and you need to make it the first 8 bits of the 16 bit transfer. Also you weren't chip selecting the device when you wrote the config the first time.

I think you need something more like this:

// transfers are 16 bits with the config as the first 8
#define _configWord_ 0x8800 // Read channel 0, single ended, 0-5V range, don't power down.

DigitalOut rd(PC_7); // cs (the chip select signal)
 
Serial pc(USBTX, USBRX); // tx, rx ( the usb serial communication )
 
int main() {
    rd = 1;
    wait_us(100);
    spi.format(16,0);
    
    // ours has max 20MHz for sck so I guess 1Mhz is okay
    spi.frequency(1000000);
 
    // notify the user that we are starting with the ADC communication
    pc.printf("Starting ADC interaction\n");
    
    rd = 0;
    wait_us(10); // only really needs to be 20 ns which probably means we don't need the wait at all
    spi.write(_configWord_ );
    wait_us(10); // only really needs to be a few ns which probably means we don't need the wait at all
    rd = 1;

    // lets do this for 200 values
    int i=1;
    while (i<=200) {
 
        // Select the device by setting read input low
        rd = 0;
        wait_us(10); // only really needs to be 20 ns which probably means we don't need the wait at all
        uint16_t receiveValue = spi.write(_configWord_ );
        wait_us(10); // only really needs to be a few ns which probably means we don't need the wait at all
        rd = 1;
                
        // print it for the user
        // When trying to figure out what's going on with binary data printing as hex is often more useful.
        pc.printf("sensor value = %u (0x%04X) = %.4f V\r\n", receiveValue,receiveValue, (5.0f*receiveValue)/65536);
 
        // delay some time before reading again
        wait(0.01); // should be at least the conversion time. Conversion starts when read goes high (unless controlled independently)
        i=i+1;
    }
}

For what it's worth it looked like you were combining the two 8 bit values correctly for if you had set the system to 8 bit transfers although you were storing the result in a signed variable type rather than unsigned.

Oh my gosh you're right, I'm doing spi.write before it is even selected. This is what happens when you're staring at a screen all day, need a fresh pair of eyes.

Sorry, I am using Linear Technologies DC682, which has the LTC on it. On the LTC, CONVST is tied to RD, which go to DC682's CS pin as seen in the diagram here (last page): https://www.analog.com/media/en/dsp-documentation/evaluation-kit-manuals/dc682af.pdf

I understand what you are saying with doing one transfer of 16 bits, however I would like to understand it from a code perspective. I haven't worked with C/C++ since freshman year undergrad oops... D: So, because of my naivety, am I sending one bit at a time because I was using integers instead of words? I would appreciate fully understanding this.

(for reference, this is what I was trying to follow: https://os.mbed.com/cookbook/SPI-communication-with-external-ADC-MCP3 I understood their code with the MCP3's datasheet, then adapted to my situation)

I will try this code, fully get a grasp of it, run and possibly edit it, and comment on what happens. Thank you very much.

posted by Chris Dizon 13 Feb 2019

Each spi read/write sends the number of bits you have set as the format. So if you set it to 16 bit each spi write will send and read 16 bits. If you tell it to send 0x88 then it will do what it does for any 16 bit number that is set to a small value, put 0's in front of it until it's the correct length.

So your write of 0x88 was sending 0x0088.

The same for the reads. You read 16 bits but we're storing the result in an 8 bit value so the top 8 bits were getting lost and you only got the least significant 8 bits in your high variable.

Since you can do a 16 bit spi transfer by doing two 8 bit transfers in a row on the same chip select some libraries will use that method. On a 32 bit processor there is no reason to do things that way but a lot of hobbiest code is copied from Arduino libraries and since that is an 8 bit processor they tend to split the transfers up like that.

posted by Andy A 13 Feb 2019

Thanks for all the help Andy. It seems to be working great, going to try the differential side now. Will have to change the unsigned into to a signed though

posted by Chris Dizon 13 Feb 2019