Reading multiple bytes of data from SPI slave device and converting to double value

I’ve scoured the forums extensively but I cannot find a topic that discusses exactly what I’m trying to accomplish. I have a digital temperature meter connected to my Photon as the slave device via SPI. Basically, I’m attempting to read and convert the bytes being transferred from the meter into a double value. I’m no expert in C++ or EE so I apologize if I’m way off the mark, but I cobbled together my code from various posts that I would expect to be logically correct. However, the output being logged is always zero even though I know the sensor is producing real values. Can anyone help direct me as to what I’m doing wrong? Thanks in advance!

Here is my current code…

union Reading {
  double bit64;
  unsigned char bit8[8];
};

char resultstr[64];

void setup() {
    
    SPI.setClockDivider(SPI_CLOCK_DIV4);
    SPI.setBitOrder(LSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    SPI.begin();
    
}

void loop() {
    
    Reading reading;
    
    //clear the data
    reading.bit64 = 1.1; //something other than zero for testing
    
    digitalWrite(SS, LOW);
    delay(1);

    for(int i=0; i < 8 ; i++) {
        reading.bit8[i] = SPI.transfer(0xFF);
    }
    
    sprintf(resultstr, "val: %.2f", reading.bit64);
    
    Spark.publish("DEBUGKUNGFURY", resultstr);
    
    digitalWrite(SS, HIGH);
    delay(3000);

}

A starting point for the search what’s going on might be to print out your eight received bytes (e.g. via Serial.println((uint8_t)reading.bit8[i], HEX);) and then go on from there.
Next you could set a refeference reading.double = someValue; and print out its individual bytes.
By comparing these you might work out the byte order and see if there are any other issues.

It’s difficult to say more without knowing what sensor you’re using.

Are you meaning the value you are reading is 8 bytes long for 64bits?

the way i have done this is to read the first byte, do a bitwise move << 8 bits then OR the result with the next read… do that 7 more times…

here is an example of the same thing with i2c from the sparkfun battery shield library. the last 3 lines is what you are after i think…

uint16_t MAX17043::read16(uint8_t address)
{
  uint8_t msb, lsb;
  int16_t timeout = 1000;

  Wire.beginTransmission(MAX17043_ADDRESS);
  Wire.write(address);
  Wire.endTransmission(false);

  Wire.requestFrom(MAX17043_ADDRESS, 2);
  while ((Wire.available() < 2) && (timeout-- > 0))
    delay(1);
  msb = Wire.read();
  lsb = Wire.read();

  return ((uint16_t) msb << 8) | lsb;
}

A union should do just fine too - with less overhead even.

@jfile, did you consider the endianness of the bytes from the SPI versus double bit64? I believe all Photon vars are in little-endian format.

Really appreciate the response and great suggestions. In regards to Serial.println() for outputting the HEX bytes, I assume I’ll need something like PuTTY or Tera Term VT on my laptop to read directly from the Photon? Or is there there some other method I’m unaware of? Also, you mentioned setting a reference like reading.double = someValue; What would I assign someValue to and where would I put this? WIthin the for loop? Thanks!

Thanks @Hootie81 for your reply. I’ve seen the bitwise move in other examples but your code really helped me understand how to apply it. If I’m using a union, should it essentially produce the same result as shifting the bits manually?

Yes PuTTY is what I use, but if you already have Particle CLI, you can also use particle serial monitor or if you have Particle Dev, you can use the serial monitor included there.

About the ref value, that depends on what you expect from your sensor.
e.g. if you expect a value of 38.7°C you’d write something like

  ...
  reading.double = 38.7;
  Serial.println(reading.double, 3);
  for (i=0; i<8; i++)
    Serial.print(reading.bit8[i], HEX);
  Serial.println();
  ...

and see what bytes you should receive from your sensor to make up the same value.
Comparing what you actually get from the sensor, might give you a clue what to change (bit order per byte and/or byte order in double).

@peekay123, thanks for your response. Yes, I considered the order from our SPI but only on the assurance from my partner. I think it’s worth verifying and looking into deeper once I can really get visibility of the output from under the hood using some of the methods mentioned above. I’ll be doing some deep testing this weekend! Cheers.

That’s really great. I guess I was a little confused as to the capabilities of both Particle CLI and DEV in getting output directly from the Photon. I’ve been using Particle CLI but solely to interact with my devices via the cloud on a standalone web page. Do you have any links to posts or documentation that explains how the particle serial monitor works in Particle CLI? Also, would you recommend PuTTY over the others for certain compelling reasons?
Ok, your code example makes sense. Now I see what you mean about how I could compare the double value against the individual bytes. Thanks!

There isn't a lot to know about. You run particle serial monitor and see what your device spits out. I don't think there is a way to send bytes in tho'

No special reason for PuTTY, I happend to have it as telnet and SSH terminal, so just use if for serial too.

Aww, ok got it. That really helps! Thanks!!! :grin:

@ScruffR So I spent a good part of Saturday troubleshooting and implemented some of your suggestions, including using PuTTY to Serial print the output. I first started with… Serial.println((uint8_t)reading.bit8[i], HEX) and the output was 0 (zero)

To rule out any issue with the bytes not correctly converting to an Int, I directly printed out of the SPI.transfer() return value but still got 0 (zero). (test code below…)

void setup() {
    
    SPI.setClockDivider(SPI_CLOCK_DIV64);
    SPI.setBitOrder(LSBFIRST);
    SPI.setDataMode(SPI_MODE1);
    SPI.begin();
    
    Serial.begin(9600);         // Open serial over USB.
    while(!Serial.available())  // Wait here until the user presses ENTER 
        Spark.process();        // in the Serial Terminal. Call the BG Tasks
                                // while we are hanging around doing nothing.
    
}

void loop() {
    
    digitalWrite(SS, LOW);
    delay(1);

    for(int i=0; i < 8 ; i++) {

	Serial.println(SPI.transfer(0xFF));

    }
    
    digitalWrite(SS, HIGH);
    delay(3000);

}

After playing with the clock dividers, data modes, and bit order (same results), I’m trying to determine if…

A) Where is zero coming from? Is there a default in the SPI.transfer() base method that is setting this?
B) Am I incorrect in assuming I can serial print the output of the transfer and expect actual byte data?
C) Until I can get a logic analyzer to test the pins directly, is there anything I could test to help troubleshoot my issue?

THANK YOU!

What sensor are your using exactly?

To answer your questions
A) Since the return value of SPI.transfer() is byte and you could receive any value from 0 to 255 as good value there is no real value left to indicate a failure, so 0x00 is just as good a fail value as 0xFF would be. But if all bytes in your packet are 0x00 I’d assume the transfer actually fails. But without knowing the hardware you try talking to, it’s hard to say any more.

B) No, you are not. You can Serial.print() any kind of data, you just have to tell the function how you want to see it. If you print a char you’ll no see any non-visible bytes (e.g. 0x00), but if you either specify Serial.print(c, HEX) you’ll get the hexadecimal representation of that character, or if you cast it to a number like this Serial.print((int)c) you’ll also see the numeric value of the byte.

C) If you have an oszilloscope that’ll do too. And again, you can provide some info about the sensor :wink:

BTW: If you only have one SPI device, you can keep the SS pin active, no harm in that - at least while testing.

1 Like