Looking for SPI help (general, but my particular code is attached) [updated]

Hi all,

The various tutorials and example sketches for SPI are very sparse at best, and I've been banging rocks together over this problem for some days.

I need a good tutorial or information for communicating with an external IC that has various registers. The chip in particular is Analog Devices AD7714Y Analog to Digital converter.

http://www.analog.com/static/imported-files/data_sheets/AD7714.pdf

In general, I'd like to know more about using the Arduino's SPI library to communicate. How data is transmitted, received, and interpreted. I'm fairly certain I have a handle on transmitting from Arduino to the ADC, but receiving information isn't really working.

My problem is basically in reading information. The way I THINK this works is that inByte[j] stores each byte received into inByte[0]-inByte[2]. Then they're added up using a fancy line of bit-shifting, based on the resolution I choose for the ADC (can be either 16- or 24-bit). Then, for debugging purposes, I Serial.print() each value stored in inByte[j] as well as their total.

The problem is I get an infinite stream of

92 92 92 6052956

regardless of the value on the ADC input pins. I should mention I have a function generator feeding it A*sin(t) + A, where A is 250mv.

The funny thing I notice is that the command for reading from the data register is SPI.transfer(0x5C); and 0x5C is 92 in binary.

So, if there's a general resource for learning about communicating with ICs via SPI, could someone point me to it? Otherwise, maybe someone here could help me with my particular code - it would be appreciated. My project can't really move forward until I get this working.

#include <SPI.h>

const int SSP = 10;
const int dataReady = 7;
const int resetPin = 3;

unsigned long advalue;

const int bytes = 3;

unsigned long inByte[bytes];

void setup()
{
  pinMode(SSP, OUTPUT);
  pinMode(dataReady,INPUT);
  pinMode(resetPin,OUTPUT);
  SPI.begin();
  SPI.setDataMode(SPI_MODE1);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  SPI.setBitOrder(MSBFIRST);
  Serial.begin(115200);
  
  digitalWrite(resetPin,LOW);
  digitalWrite(resetPin,HIGH);
  
  digitalWrite(SSP,LOW);
  SPI.transfer(0x27); // setup and calibration
  SPI.transfer(0x4F);
  SPI.transfer(0x37);
  SPI.transfer(0xA0);
  SPI.transfer(0x14);
  SPI.transfer(0x20);
  digitalWrite(SSP,HIGH);
  
  delay(100);
}

void loop()
{
  if(digitalRead(dataReady) == LOW)
  {
    advalue = readChannel();
    Serial.println(advalue);
  }
}

unsigned long readChannel()
{
  memset(inByte,0,sizeof(inByte));
  unsigned long result = 0;
  digitalWrite(SSP,LOW);
  for(int i = 0; i<bytes; i++)
  {
    inByte[i] = SPI.transfer(0x5C);
    result += (inByte[i] << (((bytes - 1) * 8)-(8*i)));
    Serial.print(inByte[i]); Serial.print(" ");
  }
  digitalWrite(SSP,HIGH);
  return(result);
}

Your code looks a little short compared to the sample C code that AD provides:

You have

unsigned long readChannel()
{
  memset(inByte,0,sizeof(inByte));
  unsigned long result = 0;
  digitalWrite(SSP,LOW);
  for(int i = 0; i<bytes; i++)
  {
    inByte[i] = SPI.transfer(0x5C);
    result += (inByte[i] << (((bytes - 1) * 8)-(8*i)));
    Serial.print(inByte[i]); Serial.print(" ");
  }
  digitalWrite(SSP,HIGH);
  return(result);
}

They have:
Table XV. C Code for Interfacing AD7714 to 68HC11

/* This program has read and write routines for the 68HC11 to interface to the AD7714 and the sample
program sets the various registers and then reads 1000 samples from the part. */
#include <math.h>
#include <io6811.h>
#define NUM_SAMPLES 1000 /* change the number of data samples */
#define MAX_REG_LENGTH 3 /* this says that the max length of a register is 3 bytes */
Writetoreg (int);
Read (int,char);
char *datapointer = store;
char store[NUM_SAMPLES*MAX_REG_LENGTH + 30];
void main()
{
/* the only pin that is programmed here from the 68HC11 is the /CS and this is why the PC2 bit
of PORTC is made as an output */
char a;
DDRC = 0x04; /* PC2 is an output the rest of the port bits are inputs */
PORTC | = 0x04; /* make the /CS line high */
Writetoreg(0x27); /* set the channel AIN6/AIN6 and set the next operation as write to the filter high
register */
Writetoreg(0x4f); /* set Bipolar mode, 24 bits, boost off, all 4 MSBs of filterword to 1 */
Writetoreg(0x37); /* set the next operation as a write to the filter low register */
Writetoreg(0xA0); /* max filter word allowed for low part of the filterword */
Writetoreg(0x17); /* set the operation as a write to the mode register */
Writetoreg(0x20); /* set gain to 1, burnout current off, no filter sync, and do a self calibration */
while(PORTC & 0x10); /* wait for /DRDY to go low */
for(a=0;a<NUM_SAMPLES;a++);
{
Writetoreg(0x5f); /*set the next operation for 24 bit read from the data register */    <<< Looks like you're missing this??
Read(NUM_SAMPES,3);                                                                                         <<< They have a lot more going on after this
}                                                                                                                       <<< vs your 3 byte read.
}
Writetoreg(int byteword);
{
int q;
SPCR = 0x3f;
SPCR = 0X7f; /* this sets the WiredOR mode(DWOM=1), Master mode(MSTR=1), SCK idles high(CPOL=1), /SS
can be low always (CPHA=1), lowest clock speed(slowest speed which is master clock /32 */
DDRD = 0x18; /* SCK, MOSI outputs */
q = SPSR;
q = SPDR; /* the read of the staus register and of the data register is needed to clear the interrupt
which tells the user that the data transfer is complete */
PORTC &= 0xfb; /* /CS is low */
SPDR = byteword; /* put the byte into data register */
while(!(SPSR & 0x80)); /* wait for /DRDY to go low */
PORTC |= 0x4; /* /CS high */
}
Read(int amount, int reglength)
{
int q;
SPCR = 0x3f;
SPCR = 0x7f; /* clear the interupt */
DDRD = 0x10; /* MOSI output, MISO input, SCK output */
while(PORTC & 0x10); /* wait for /DRDY to go low */
PORTC & 0xfb ; /* /CS is low */
for(b=0;b<reglength;b++)
{
SPDR = 0;
while(!(SPSR & 0x80)); /* wait until port ready before reading */
*datapointer++=SPDR; /* read SPDR into store array via datapointer */
}
PORTC|=4; /* /CS is high */
}

Verdris:
The various tutorials and example sketches for SPI are very sparse at best ...

CrossRoads:
Your code looks a little short compared to the sample C code that AD provides:

Isn't all that stuff with the SPCR and SPCD part of the SPI library? I thought those were commands to read/write the uC's SPI register.

Some good docs for the arduino hardware SPI's stuff below.

http://atmel.com/dyn/resources/prod_documents/doc2585.pdf

Gammon, thanks for 'Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino' excellent.

Hooray for thread resurrection. I've been kicking rocks over this thing for days now and I'm closer - a lot closer. Based on the datasheet's example code (which had many typos and errors), I've come up with this:

#define NUM_SAMPLES 100
#define MAX_REG_LENGTH 3
long store[NUM_SAMPLES];
long average;
long refV = 3.0; //reference voltage

void setup()
{
  Serial.begin(115200);
  Serial.println("Writing registers for setup");
  DDRB = 0x2D; //SCK output, MISO input, MOSI output, SS output, DRDY input, SS2 output (for SD card reader)
  SPCR = 0x55; //Enable SPI, MSBfirst, Master mode, CPOL=0, SPHA=1, fosc/16
  SPSR = 0x00; //Double speed SPI off
  
  //Writetoreg(0x27); //com, filter high, channel 6/6
  Writetoreg(0x24); //com, filter high, channel 1/2
  
  //Writetoreg(0x4f); //write to filter high register, bipolar mode
  Writetoreg(0xCF); //write to filter high register, unipolarmode
  
  //Writetoreg(0x37); //com, filter low, channel 6/6
  Writetoreg(0x34); //com, filter low, channel 1/2
  
  Writetoreg(0xA0); //write to filter high register
  
  //Writetoreg(0x17); //com, mode, channel 6/6
  Writetoreg(0x14); //com, mode, channel 1/2
  
  Writetoreg(0x20); //write to mode register
  delay(1000);
}
//
void loop()
{
  while(PORTB & 0x10);
  //Serial.println("Reading");
  for(int a=0; a<NUM_SAMPLES; a++)
  {
    //Writetoreg(0x5f); //set up a read from channel 6/6
    Writetoreg(0x5C); //set up a read from channel 1/2
    Read(3);
  }
  Serial.println("Read complete");
  Serial.print(NUM_SAMPLES); Serial.println(" readings complete.  Average value tbd.");

  while(1); // pause after 1000 readings.
}

void Writetoreg(int byteword)
{
  int q;
  SPCR = 0x15; // turn off SPI
  SPCR = 0x55; // turn on SPI
  q = SPSR; // a read is needed for clearing interrupts
  q = SPDR;
  digitalWrite(10,LOW); //bring CS line low
  SPDR = byteword; // write our value to the Ardi's data register
  while(!(SPSR & 0x80)); // pause while no data available
  digitalWrite(10,HIGH); // bring CS high.
}

void Read(int reglength)
{
  unsigned long total = 0;
  int q;
  SPCR = 0x15; // turn off SPI
  SPCR = 0x55; // turn on SPI
  q = SPSR; //
  q = SPDR; // clearing interrupts
  while(PORTB & 0x02); //wait for DRDY to go low
  //PORTB & 0x04; //bring CS line low (I think - if not, use digitalWrite(10,LOW);
  digitalWrite(10,LOW);
  for(int b = 0; b<reglength;b++)
  {
    SPDR = 0;
    while(!(SPSR & 0x80));
    total+=((unsigned long)SPDR)<<(16-(8*b));
  }
  Serial.print(total);
  Serial.print(" = ");
  Serial.print(((float)total/16777216.0)*refV);
  Serial.println("V.");
  //PORTB |= 0x04; //bring CS high
  digitalWrite(10,HIGH);
}

Hopefully the comments are enough to guide anyone through it. So, the story so far is that I'm successfully reading values from the various channels of the A/D. However, when I use my function generator that emits V=250sin(t)+250 (in mV), it only grabs whatever voltage is on the pin when I start the program, regardless of its oscillation in time. It then prints this value for however many times the loop runs, as dictated by NUM_SAMPLES.

Can anyone see where I went wrong? I thought by declaring total in the Read() function would give me a fresh reading every time. But it's still only grabbing what's on the pins at startup.

Hi Verdis,

I am working with AD7705, does this code will work for AD7705. I am trying to convert the data from current sensor. I have one doubt, how can we get the address of the registers from the datasheet?