Help communicating with SPI sensor

Hi guys,

I'm trying to communicate with a relatively simple sensor but after many attempts I don't even know how to get an "ack" out of it. I've tried going through tutorials and sketches for a while now to no avail.

I'll post my code then post the datasheet of the communications portion.

How do I build and send the start command? Am I reading the data back correctly?

[update] Using an Uno.

Any help is MUCH appreciated. Thanks.

My Code:

/*
 Circuit:
 
 CSB: pin 10
 MOSI: pin 11
 MISO: pin 12
 SCK: pin 13
*/

// inslude the SPI library:
#include <SPI.h>

const int chipSelectPin = 10;

void setup() {
  Serial.begin(115200);

  // start the SPI library:
  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(SPI_MODE2);
  SPI.setClockDivider(SPI_CLOCK_DIV4);
  
  // initalize the  data ready and chip select pins:
  pinMode(chipSelectPin, OUTPUT);
  digitalWrite(chipSelectPin, LOW);
  
  // give the sensor time to set up:
  delay(1000);
}

void loop(){
  startMeasuring();
  delay(1000);
}

void startMeasuring(){
  byte cmd = 0x80;
  byte len = 0x0001;
  byte fcs = 0x81;
  
  //Trying to get either to work
  Serial.println(SPI.transfer(cmd));
  Serial.println(SPI.transfer(len));
  Serial.println(SPI.transfer(fcs));
  
  Serial.println(SPI.transfer(cmd & len & fcs));
}

Datasheet:



I would double-check that SPI_MODE2 sets the data phase and clock phase as required in the device's datasheet (SCLK should be normally at a HIGH level and so should CSB). You didn't specify which Arduino board you're using , but check that SPI_CLOCK_DIV4 sets the spi clock to 1 mhz. Also, try running the SPI.transfer commands on their own and not within a print command.

EDIT:
In your setup, you should have digitalWrite(chipSelectPin, HIGH);
In your main loop, you should wrap the digital writes to CSB around the SPI.transfer commands as follows:

digitalWrite(chipSelectPin, LOW);
SPI.transfer(cmd);
SPI.transfer(len);
SPI.transfer(fcs);
digitalWrite(chipSelectPin, HIGH);

The variables cmd, len and fcs should be initialized as byte.

dlloyd:
I would double-check that SPI_MODE2 sets the data phase and clock phase as required in the device's datasheet (SCLK should be normally at a HIGH level and so should CSB). You didn't specify which Arduino board you're using , but check that SPI_CLOCK_DIV4 sets the spi clock to 1 mhz. Also, try running the SPI.transfer commands on their own and not within a print command.

EDIT:
In your setup, you should have digitalWrite(chipSelectPin, HIGH);
In your main loop, you should wrap the digital writes to CSB around the SPI.transfer commands as follows:

digitalWrite(chipSelectPin, LOW);

SPI.transfer(cmd);
SPI.transfer(len);
SPI.transfer(fcs);
digitalWrite(chipSelectPin, HIGH);



The variables cmd, len and fcs should be initialized as byte.

Thanks dlloyd for the help. When I look at Diagram (6) it looks like data is sent on the falling edge and when the clock is high, the data is idle which is how I was lead to think it's SPI_MODE2, but just in case I tried all the SPI_MODE{0,1,2,3}s with the new code below.

Changes:

  • Your suggestion of wrapping the low with the transfer itself instead of keeping it low constantly.
  • Slowed clock as low as possible with divide by 128
  • Tried with and without Serial.println()s

What do you recommend to use to read the data base from the sent command (if i can ever get a response).

/*
 Circuit:
 
 CSB: pin 10
 MOSI: pin 11
 MISO: pin 12
 SCK: pin 13
*/

// inslude the SPI library:
#include <SPI.h>

const int chipSelectPin = 10;

void setup() {
  Serial.begin(115200);

  // start the SPI library:
  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV128);
  
  // initalize the  data ready and chip select pins:
  pinMode(chipSelectPin, OUTPUT);
  digitalWrite(chipSelectPin, HIGH);
  
  // give the sensor time to set up:
  delay(1000);
  
  startMeasuring();
}

void loop(){

  
  delay(1000);
  reqMeasurements();
}

void startMeasuring(){
  byte cmd = 0x80;
  byte len = 0x0001;
  byte fcs = 0x81;
  
digitalWrite(chipSelectPin, LOW);
SPI.transfer(cmd);
SPI.transfer(len);
SPI.transfer(fcs);
digitalWrite(chipSelectPin, HIGH);  
  
//  Serial.println(SPI.transfer(cmd));
//  Serial.println(SPI.transfer(len));
//  Serial.println(SPI.transfer(fcs));
//  
//  Serial.println(SPI.transfer(cmd & len & fcs));
}

void reqMeasurements(){
  byte cmd = 0x82;
  byte len = 0x0001;
  byte fcs = 0x83;
  
  digitalWrite(chipSelectPin, LOW);
  
  SPI.transfer(cmd);
  SPI.transfer(len);
  SPI.transfer(fcs);
  digitalWrite(chipSelectPin, HIGH);  
  
  delay(1000);
  
  digitalWrite(chipSelectPin, LOW);
  
  Serial.println(SPI.transfer(cmd));
  Serial.println(SPI.transfer(len));
  Serial.println(SPI.transfer(fcs));
  
  Serial.println(SPI.transfer(cmd & len & fcs));
  
  digitalWrite(chipSelectPin, HIGH);  
    
}

From the datasheet excerpt you posted it says send on falling edge, read on rising edge so should this be SPI_MODE0 ?
The max transfer speed is 1MHz so clock divider needs to be >= SPI_CLOCK_DIV16 assuming at 16MHz clock speed.

A big problem you have is the LEN value is 16 bit but SPI can only transfer 8 bits at a time so you need to split it up into 2 sends like...

digitalWrite(chipSelectPin, LOW);
SPI.transfer(cmd);
SPI.transfer((byte)len);
SPI.transfer((byte)len >> 8);
SPI.transfer(fcs);
digitalWrite(chipSelectPin, HIGH);

Riva:
From the datasheet excerpt you posted it says send on falling edge, read on rising edge so should this be SPI_MODE0 ?
The max transfer speed is 1MHz so clock divider needs to be >= SPI_CLOCK_DIV16 assuming at 16MHz clock speed.

A big problem you have is the LEN value is 16 bit but SPI can only transfer 8 bits at a time so you need to split it up into 2 sends like...

digitalWrite(chipSelectPin, LOW);

SPI.transfer(cmd);
SPI.transfer((byte)len);
SPI.transfer((byte)len >> 8);
SPI.transfer(fcs);
digitalWrite(chipSelectPin, HIGH);

Thanks Riva for the help. My big question is:

How do I read the response when it's 4-6 bytes long with the SPI library?

Riva, to reply directly. When I look at Diagram (6) and compare it against the SPI wiki article it looks like SPI_MODE3 in fact, but I've been trying them all anyway.

From Diagram(6) I added some delays to the send, but... it still isn't working then.

Fig. 10 communication timing
(A) CS signal setup time
Time of between CS output and SCLK is more than 60 (us) us: micro second
(B) Send data setup time
Time from end of receiving to start of sending is more than 100 (us)
(C) CS signal hold time is minimum 10(um)
High level holding time of the CS is minimum 10 (um)
Note: CS level has to return to high level, even if continuous sending

void startMeasuring(){
  byte cmd = 0x80;
  byte len_1 = 0x01;
  byte len_2 = 0x00;
  byte fcs = 0x81;
  byte response = 0x0;

  digitalWrite(chipSelectPin, LOW);
  delay(100);
  SPI.transfer(cmd);
  SPI.transfer(len_1);
  SPI.transfer(len_2);
  response = SPI.transfer(fcs);
  delay(200);
  digitalWrite(chipSelectPin, HIGH);  

  Serial.println(response);

}

void reqMeasurements(){
  byte cmd = 0x82;
  byte len = 0x0001;
  byte fcs = 0x83;

  byte response = 0x0;

  digitalWrite(chipSelectPin, LOW);
  delay(100);
  SPI.transfer(cmd);
  SPI.transfer((byte)len);
  SPI.transfer((byte)len >> 8);
  delay(200);
  response = SPI.transfer(fcs);

  Serial.print("Response: ");
  Serial.println(response);
  digitalWrite(chipSelectPin, HIGH);
}

nitsujri:
How do I read the response when it's 4-6 bytes long with the SPI library?

Use something like...

byte result = SPI.transfer(0x00);

You send 0x00 (or any other value) to generate the SPI clock and result will hold the value sent back from the device.

nitsujri:
Riva, to reply directly. When I look at Diagram (6) and compare it against the SPI wiki article it looks like SPI_MODE3 in fact, but I've been trying them all anyway.

I missed that one, I concur with you it should be mode3.

Note: CS level has to return to high level, even if continuous sending

Not sure if this mean CS needs pulsing between each byte or each command?

Your delays are a bit long and some are in the wrong place, try using delayMicroseconds instead. Also your not reading back enough bytes after doing the 0x80 command. The datasheet show you will get back 6 bytes in response to the START (0x80) command and your only trying to read back one (in the wrong place) before you have finished sending the command sequence.

/*
  Circuit:
  
  CS: pin 10
  MOSI: pin 11
  MISO: pin 12
  SCK: pin 13
*/

// include the SPI library:
#include <SPI.h>

void setup() {
  Serial.begin(115200);
  
  // start the SPI library:
  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  
  // give the sensor time to set up:
  delay(1000);
}

void loop(){
  startMeasuring();
  delay(1000);
}


void startMeasuring(){
  byte cmd = 0x80;
  byte len_LSB = 0x01;
  byte len_MSB = 0x00;
  byte fcs = 0x81;
  byte resp = 0x00;
  
  digitalWrite(SS, LOW);
  delayMicroseconds(5);
  SPI.transfer(cmd);
  SPI.transfer(len_LSB);
  SPI.transfer(len_MSB);
  SPI.transfer(fcs);
  delayMicroseconds(100);
  cmd = SPI.transfer(0x00);
  resp = SPI.transfer(0x00);
  len_LSB = SPI.transfer(0x00);
  len_MSB = SPI.transfer(0x00);
  fcs = SPI.transfer(0x00);
  
  Serial.print(cmd,HEX);
  Serial.print(",");
  Serial.print(resp,HEX);
  Serial.print(",");
  Serial.print(cmd,HEX);
  Serial.print(",");
  Serial.print(len_LSB,HEX);
  Serial.print(",");
  Serial.print(len_MSB,HEX);
  Serial.print(",");
  Serial.println(fcs,HEX);
  
  digitalWrite(SS, HIGH);  
  
}

We need to know the type of sensor and have a more detailed datasheet ... the wording in the data provided (translated from chineese?) is sparse. It seems to be very similar to the AD7298 which has more detailed communication information in its datasheet. (i.e. this sensor pulses CS high between each transfer).

EDIT: Interesting that the Intel Galileo board uses the AD7298.

Small win! I was able read a small response out of the device, so thanks so much for your help.

First off, It appears to work best, but i can't get it to respond with anything that really makes sense. The response I get "appears" to be an FCS miss match, but I'm not 100% on that.

When I change the FCS that number gets placed in the "code" portion of the response as you can see below:

[Starting]
Sending: { 0x80, 0x01, 0x00, 0x81 }
Received: 0,0,0,81,4,1,0,84,84,84
[Starting]
Sending: { 0x80, 0x01, 0x00, 0x82 }
Received: 0,0,0,82,4,1,0,84,84,84
[Starting]
Sending: { 0x80, 0x01, 0x00, 0x80 }
Received: 0,0,0,80,4,1,0,84,84,84

Do you guys have any thoughts?

Below, I've posted the new code which is giving me the responses we've gotten together. The real response I'm looking for is { 80,0,1,0,X }.

@dlloyd, yeah I would love to give up the entire data sheet, but it's an experimental product that is still under wraps, which is why I'm only allowed to post the communications portion of the data sheet. Even still, we're not connected to the developers so that's why I've gone to the internet for help =\

Code:

/*
 Circuit:
 
 CSB: pin 10
 MOSI: pin 11
 MISO: pin 12
 SCK: pin 13
 */

// inslude the SPI library:
#include <SPI.h>

void setup() {
  Serial.begin(115200);

  // start the SPI library:
  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV16);

  // initalize the  data ready and chip select pins:
  digitalWrite(SS, HIGH);
}

void loop(){
  
  if(Serial.read() == 's'){
    Serial.println("[Starting]");
    startMeasuring();
  }
  
  if(Serial.read() == 'm'){ 
    for(int i=0; i<100;i++){
      reqMeasurements(); 
      delay(250);
    }
  }
  
  

}


void startMeasuring(){
  
  const int respLen = 10;
  
  byte cmd[4] = { 0x80, 0x01, 0x00, 0x81 };
  
  processCmd(cmd, respLen); 
  
}

void reqMeasurements(){
  
  const int respLen = 300;
  
  byte cmd[4] = { 0x82, 0x01, 0x00, 0x83 };
  
  processCmd(cmd, respLen); 
  
}

void processCmd(byte *cmd, int respLen){
  
  int cmdLen = 4;
  byte *resp = (byte*)malloc(sizeof(byte) * respLen);
  
  digitalWrite(SS, LOW);
  
  delayMicroseconds(5);
  
  for(int i=0; i<cmdLen;i++){
    SPI.transfer(cmd[i]);
  }
  
  delayMicroseconds(100);
  
  for(int i = 0; i<10; i++){
    resp[i] = SPI.transfer(0x00);
  }
  
  for(int i = 0; i<respLen; i++){
    if(i != 0){
      Serial.print(",");
    }
    Serial.print(resp[i], HEX);
  }
  Serial.println("");  
  
  digitalWrite(SS, HIGH);  
  
}

Hi nitsujri

If it is an experimental product, maybe it has the problems, not your code :slight_smile:

But thinking positively, do the product developers have test code they know works with the product? Even if it was not written for Arduino, you might be able to "reverse engineer" valid command and response sequences?

Regards

Ray

I wonder if the datasheet is wrong?
You could try a slower clock speed of SPI_CLOCK_DIV32 but I assume the current speed is good because data does not seem corrupt.
Strange the reply starts with the CRC value you sent and not the command you sent.
Try increasing/reducing the 100us delay and see if the results change.
Try just sending the length as 1 byte instead of 2 as the datasheet says. The return data seems to be two byte length but you have nothing to loose.

Sightly larger win!

Thanks for all the help so far. I revisited the datasheet and the first timing after the CS is trigger is 60us, so the code changed in one line resulting in:

[Starting]
Sending: { 0x80, 0x01, 0x00, 0x81 }
Received: 80,0,1,0,81
[Starting]
Sending: { 0x80, 0x01, 0x00, 0x81 }
Received: 80,0,1,0,81

It starts no problem! It also stops properly, the command starting 0x82. The problem running the measure data command is seemingly corrupted:

Sending: { 0x82, 0x01, 0x00, 0x83 }
82,0,8,2,99,0,53,3,0,0,0,81,3F,0,XXXXX
82,0,8,2,99,0,53,3,0,0,AF,5E,AD,7D,XXXXX
82,0,8,2,99,0,53,3,0,0,9,2A,F0,FF,XXXXX
80,0,8,2,99,0,53,3,0,0,5,3,0,0,C,XXXXX
80,0,8,2,99,0,53,3,0,0,5,3,0,0,C,XXXXX

It starts out looking fine, but the first 3 lines which "look" fine have a 53,3,0,0,0 when it should be 53,3,0,0,1 according to the datasheet. Then of course, after line 3, the CMD portion of the frame changes to 0x80 which is not right, but the rest "looks good"

Perhaps a fresh set of eyes on the datasheets could help. I've added more sheets below which belong to the "measuring" portion which I'm now tackling.

Nonetheless, I thank Riva & dlloyd for really trying to help me out.

Sadly, I know this works because the company provided a program which works on a Windows machine and reads the sensor data no problem.

I'm so close, yet so far.



SUCCESS!!!

So the problem was entirely my own stupidity. The "read" loop still had a hardcoded value of i<10 and it was screwing up all the reads. (You can see it in the latest iteration of the code)

Thanks so much guys for all your help. 100x appreciated.

A few thoughts...

The "Send data setup time" from the timing diagram shows (B) > 100 µs is needed. This delay might be needed between each of the 2 bytes returned from the sensor.

Note that SCLK needs to pulse in order to receive each data byte, so sending dummy byte(s) on MOSI ===> SDI would be required for byte2 or possibly required for both bytes.

The timing diagram also shows CS hold time >= 10 µs. It mentions "CS level has to return to high level, even if continuous sending. Its unclear to me if this means pulsing CS high between each pixel byte returned or each 2-byte pixel data.

EDIT: Your post just came in (wish it was a little earlier) ... congrats! :wink:

dlloyd:
EDIT: Your post just came in (wish it was a little earlier) ... congrats! :wink:

Glad you got it working. You should make the SPI routine dynamic so it reads back the number of bytes specified in response LEN bytes.

dlloyd:
EDIT: Your post just came in (wish it was a little earlier) ... congrats! :wink:

Same! I spent 4 hours looking everywhere but my own code for the problem.

Riva:
Glad you got it working. You should make the SPI routine dynamic so it reads back the number of bytes specified in response LEN bytes.

Good call, I'll do that. To be honest, I didn't know what was LEN was referring to, makes sense now.

Thanks again everyone.