5
\$\begingroup\$

I'm new to microcontrollers - I'm trying to read external ADC values from an AD7798 ADC using SPI communication.

Initially I have to setup some ADC registers - some registers are not configured. To configure the registers, I have to use the communication register to select which register I want to configure.

For example, I want to set the AD7798 configuration register (16bit). I have code like this: #include #define ADC_CS PORTB.3 #define WG_CS PORTB.4 #define MOSI PORTB.5 #define MISO_PU PORTB.6 #define MISO_PIN PINB.6 #define SCK PORTB.7

//global functions.
unsigned int adcConfig;
unsigned int adcMode;
unsigned int adcId;

void init_io(void) 
{ 
DDRB = 0xBF;        // make SCK, MOSI, CS1, CS2 outputs 
ADC_CS = 1;              //disable ADC 
WG_CS = 1;               //disable WaveGenerator 
MISO_PU = 1;             //enable pull-up on MISO so we can test !RDY 
} 

unsigned char spi(unsigned char data) 
{ 
//Start transmision 
SPDR = data; 
//Wait for transmision complete 
while (!(SPSR & (1<<SPIF))); 
return SPDR; 
} 

//Sets the waveform generator output to given phase 
void SetWGPhase(unsigned int phase) 
{ 
SPCR = 0x5A; // mode #2 F_CPU/64 
WG_CS = 0;                      // enable 
spi(0x20); 
spi(0x00); 
spi((phase >> 8) | 0xC0);       //Load into phase register 0 
spi(phase & 0x00FF); 
WG_CS = 1; 
} 

void setupAd(){ 
    SPCR = 0x5D; 
    ADC_CS = 0; 
   // while(spi(0x10) != 0x10); 
    spi(0x10);                  //set up communication register for configuration reg. 
    spi(0x07);        
    spi(0x10); 

    spi(0x08);                  //set up communication register for mode reg. 
    spi(0x00);        
    spi(0x0A); 
    ADC_CS = 1; 
    } 


 unsigned int ReadAd(void) 
 { 
unsigned int data; 
SPCR = 0x5D; // mode #3 F_CPU/16 
CheckStatus();
ADC_CS = 0;                     // enable 
while (MISO_PIN != 0) ;         // wait for  DOUT/!RDY line to go low 
//Read data 
spi(0x58);                      //Place readinstruction in communication register 
data = spi(0xFF);               // read hi-byte 
data = (data << 8) | spi(0xFF); // and lo-byte. 
ADC_CS = 1;                     // disable 
return data; 
} 

 unsigned char CheckStatus(void)
{
char adcStatus; 
            SPCR = 0x5D;
            ADC_CS = 0;                     // enable  
            while(ADC_CS_PIN);
            adcStatus = 0xFF; 
while(!(adcStatus & 0x80)){                                     

             spi(0x40);
             adcStatus = spi(0xFF); 
          }          
ADC_CS = 1;                      

return adcStatus;
}

unsigned int ReadAdConfReg(void) 
{              
  unsigned int retvalconfig;
SPCR = 0x5D;  
ADC_CS = 0;      
while (MISO_PIN != 0) ; 
spi(0x50); 
adcConfig = spi(0xFF);    
adcConfig = (adcConfig << 8) | spi(0xFF); 
retvalconfig= adcConfig;
ADC_CS = 1; 
return retvalconfig;
} 

unsigned int ReadAdModeReg(void) 
{              
  unsigned retvalmode;
SPCR = 0x5D;  
ADC_CS = 0;        
while (MISO_PIN != 0) ; 
spi(0x48); 
adcMode = spi(0xFF);  
adcMode = (adcMode << 8) | spi(0xFF); 
retvalmode =adcMode;   
ADC_CS = 1;
return retvalmode;
} 
unsigned int ReadAdIdReg(void) 
{              

SPCR = 0x5D;  
ADC_CS = 0;          
while (MISO_PIN != 0) ; 
spi(0x60); 
adcId = spi(0xFF);    
ADC_CS = 1;
 return adcId; 
} 

when I print configuration register it is giving value"16383". but when I power off/on the target i am getting "1808(which is equivalent to 0x0710)" after that it is giving same value as"16383". I have tested with different configurations also but it is not changing, always printing "16383" except power off/on.I think default value.

Even with mode register it is always printing"10(which is equivalent to 0x000A)" but that is the value i am getting always, even if I change the configuration to "0x0022".

Even I have tried to read Id register, but it is giving "0x48". but in data sheet it mentioned "0xX8" for AD7798. Thanks in advance.

Someone help me please, I am not getting any idea about what mistake I am doing here.

\$\endgroup\$
  • \$\begingroup\$ You'll find that users are more willing to answer if they get all possible relevant information, like a link to the ADC's datasheet. \$\endgroup\$ – stevenvh Oct 10 '12 at 16:15
  • \$\begingroup\$ What is spi(0x07)<<8 supposed to do? The <<8 part doesn't have any effect here. \$\endgroup\$ – Rocketmagnet Oct 10 '12 at 16:17
  • 1
    \$\begingroup\$ You said in a comment on a previous question that you've checked that the waveform is correct using an oscilloscope. Now I'm not so sure. Can you post a screenshot from the 'scope or logic analyser? \$\endgroup\$ – Rocketmagnet Oct 10 '12 at 16:19
  • \$\begingroup\$ @Rocketmagnet I am very sorry for previous comment. I have checked it wrongly. I checked it once again after your suggestion so I found Clock signal difference was there and I found my configuration register was not set. i have done some modifications in the previous code but I have to try this and I will edit it there. \$\endgroup\$ – verendra Oct 10 '12 at 19:17
  • \$\begingroup\$ @verendra - If you add the 'c' tag to your question the code will automatically get syntax highlighting. \$\endgroup\$ – Trygve Laugstøl Oct 22 '12 at 9:30
5
\$\begingroup\$

While this datasheet is making my eyes bleed with the complexity of doing a simple conversion/read, you need to make some simple changes.

I'm not sure why people are making this harder than it really is. You pull the chip select line low, and the target IC should simply read whatever you give it until you pull it high again. Just because your method takes an 8-bit value doesn't mean you can't call it twice to pass a 16-bit value to your IC. There is no need to bit-bang anything. That is nonsense.

Call your SPI method to tell the ADC you're looking to write to the configuration register:

spi(0x10);

As I understand from the datasheet, you can immediately write your 16-bit value to the configuration register after that, so you'd do:

spi(0x07);
spi(0x10);

I forget which way that'll end up being assembled on the target IC side, so you could simply reverse them if things don't work right. There's no need to bit shift any values at all. Worst case scenario is you have to pull the CS line back high before pulling it low again to actually send the data to write to the configuration register.

Otherwise, this is super simple. Remember, if the target SPI device expects more than one byte, i.e. a 16-bit value, chances are good that sending the bytes in the order they'd otherwise appear normally (so if you want to send 0x1234, you'd have 0x12 and 0x34) will work fine and you won't need to shift anything.

\$\endgroup\$
  • \$\begingroup\$ If the SPI controller on the microcontroller is 8-bit (very likely) then after 8 bits the SS will be released, like you say, which will cause the data to be latched. The next 8 bits will fix that: when they are latched the 16-bit word is complete, but the first latch will be processed by the ADC and probably be seen as garbage, or worse. Bit-banging may be a solution. \$\endgroup\$ – stevenvh Oct 10 '12 at 17:32
  • \$\begingroup\$ That's if you release the SS.. which you don't have to. I've done 16-bit transfers on an LPC1769 with a 16-bit SSP controller and on a regular old Arduino Uno with, presumably, an 8-bit SPI controller. Controlling the SS works as intended in both cases. Pull it low. Send 16 bits. Pull it high. Voila. This just really seems like it's being made into this crazy hard solution but there's no way everybody out there with an ATmega is bit-banging SPI just to do a freakin' 16-bit transfer. :P The key is controlling the SS line yourself. \$\endgroup\$ – Toby Lawrence Oct 10 '12 at 17:37
  • 1
    \$\begingroup\$ yes, you're absolutely right. For a moment I was thinking the SS was controlled by the SPI controller, but that's impossible because it doesn't know which of the 57 :-) SS lines should be toggled. I stand corrected: no bit-banging needed. (With the NXP LPC17xx no problem anyway, since its SPI FIFO length is user selectable.) \$\endgroup\$ – stevenvh Oct 10 '12 at 17:42
  • \$\begingroup\$ Right. I control the SS manually in my case for this very reason... I know better than the controller what I want to send and receive. That was the basis of my entire argument - control it manually, and you'll be golden. Sorry if that didn't come through clearly. :) \$\endgroup\$ – Toby Lawrence Oct 10 '12 at 17:43
  • 1
    \$\begingroup\$ It looks like he has ChipSelectAd() to toggle the SS line for the ADC specifically. He just needs to deassert and reassert before sending the actual register contents. \$\endgroup\$ – Toby Lawrence Oct 10 '12 at 17:47
3
\$\begingroup\$

The chip select line on the AD7798 is active low. It looks like you have the polarity backwards in your code.

You need to set CS to low (0) at the beginning of the transfer, and high after the transfer. So try this:

void setupADC()
{
    ChipSelectAd(0); // was 1
    spi(0x10);
    spi(0x07);
    spi(0x10);
    ChipSelectAd(1); // was 0  
}

Also check that you're using the correct SPI mode (called CPOL and CPHA on many microcontrollers, for "clock polarity" and "clock phase"). This determines which edge of the clock triggers data transitions, and what level the clock has between transactions (during idle).

The AD7798 requires CPOL=1 and CPHA=1 (SPI mode 3).

\$\endgroup\$
  • \$\begingroup\$ Still the same problem can you have a look at my edited code. \$\endgroup\$ – verendra Oct 11 '12 at 7:55
  • \$\begingroup\$ @verendra: Oh ok, looks like ChipSelectAd is performing the inversion (this is why it helps to show more code). What processor are you using? (add this to the question) \$\endgroup\$ – Ben Voigt Oct 11 '12 at 13:28
  • \$\begingroup\$ Canu you have a look at my new post but it was closed. there I have added Oscilloscope images to check clock polarities and chip select. \$\endgroup\$ – verendra Oct 13 '12 at 9:45
  • \$\begingroup\$ @verendra: On those oscilloscope tracings, the timing looks pretty good but the data is not 0x10, 0x07, 0x10. Any idea why? Is this a capture of a different command? Also, say what order the captures are (clearly the top trace in each is clock, but then are they MOSI, MISO, CS?) \$\endgroup\$ – Ben Voigt Oct 13 '12 at 13:57
  • \$\begingroup\$ Those images are not related to ADC setup, Those images are related to reading ADC register setup. I have tested Clock with CS, MOSI, MISO while reading ADCsetup. It seems right. But I am asking how to check ADC register setup? \$\endgroup\$ – verendra Oct 13 '12 at 15:04
1
\$\begingroup\$

Like RocketMagnet says

spi(0x07)<<8;   

makes no sense. Even though the function spi() is defined char, you're shifting that to the left, and then discard it.

spi(0x07 << 8);   

shifts the argument 8 bits to the left, so it will be 0x0700. But that doesn't do what you want. You don't de-assert SS (Slave Select) between calls of the spi() function, so that will be automatic. I would change it to have an optional argument hold_ss, so that you can shift in the first byte, then the second one, and only then de-assert SS.

That way you'll be able to shift 16 bits of data to the ADC, without having to shift data before the spi() call. Alternatively you can write a function spi16():

unsigned int spi16(unsigned int data16)
{ 
    assert_SS(IOpin);          
    spi8(data >> 8); /* assuming LSB is shifted first */
    spi8(data);
    deassert_SS(IOpin);
}

(I'm ignoring the incoming data for the moment.)

The datasheet indicates that the Configuration Register indeed needs 16 bit, but while it doesn't give details, I would expect it needs them as a single transfer. Check the spi() function of your microcontroller's compiler, and the microcontroller's datasheet to see if 16-bit transfers are actually possible in the first place. If the controller can do 16-bit transfers you should be able to do this:

spi(0x0710);

If only 8-bit transfers are possible, I think you'll have to resort to a bit-banged SPI (which is not that hard).

edit
Rocketmagnet points me to the definition of the spi() function:

char spi(char data) 

That says it all: the argument is of type char, so only 8-bit.

(Thanks for the pointer (no pun intended), Rocketmagnet)

\$\endgroup\$
  • \$\begingroup\$ See his previous question for the definition of char spi(char data). \$\endgroup\$ – Rocketmagnet Oct 10 '12 at 16:51
  • \$\begingroup\$ He's already controlled the chip select pin at the appropriate times, if perhaps the wrong levels. \$\endgroup\$ – Ben Voigt Oct 10 '12 at 18:27
  • \$\begingroup\$ @BenVoigt I am controlling chip select pin exactly. now have a look at my edited code. \$\endgroup\$ – verendra Oct 10 '12 at 19:21
0
\$\begingroup\$

What micro are you using? You provided no information as to what is spi() doing there. It might be sending 8bits. In which case your 8 bit shift results in nonsense. Now, you stated you want to send "0x0710". Think for a second what may be happening in your code.

You seem to be confused about how this SPI transfer takes place.

First, you're sending 0x10. Then, supposing your spi() function sends 16 bits, you're sending 0x0700. You've just transferred 0x100700.

I'm quite sure your spi() function does not in fact send your 16 bits. So you're basically sending two bytes: 0x10 and 0x00. Well done.

You have to reverse the byte order when sending multiple bytes via SPI. First, send 0x07, and only then send 0x10.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.