How do I convert a struct to a byte array and back to a struct again?

Hey guys.

I've got lots of data of different variable types,
I'd like to send that data from one Arduino to another using the SPI protocol.
(for fellow noobs: Nick Gammon has a great write-up on different protocols: Gammon Forum : Electronics : Microprocessors : Comparison of transfer protocols )

I figured the best way to keep track of my data would be to create a struct.
And I figured since the SPI protocol sends one byte at a time, the best way to send my data would be to first convert the struct into a byte array (of the same size as my struct).

Here is what I've got thus far:

// Struct -to - Byte Array TEST

// define global data variables:
byte exampleByte = 255;
int exampleInt = 32767;
long exampleLong = 2147483647;

// create a structure to store the different data values:
struct myDataStruct
{
  byte exampleByteVessel;
  int exampleIntVessel;
  long exampleLongVessel;
  // since I plan on using these variables to later update the
  // global variables on the slave arduino, I am calling these
  // variables "vessels". I'll have to update these "vessels" w/
  // the coresponding global data values.
};

// name the structure:
typedef struct myDataStruct dataVessels;
// I'm not sure why I have to name it, because it sure seems like
// a name was already assigned to it when I created the struct (?)

// declare a byte array:
byte byteArray[sizeof(dataVessels)];
// I plan to use this byte array to send over SPI to the slave
// ardino. Now if only I could figure out how to populate this
// array with all the bytes within the struct (?)



void setup() 
{
  // populate the variables within the struct:
  myDataVessels.exampleByteVessel = exampleByte;
  myDataVessels.exampleIntVessel = exampleInt;
  myDataVessels.exampleLongVessel = exampleLong;
  // now the vessels are loaded.
  
  // populate the byte array by...????
  //...
  //...
  //...
  
  // send the byte array over SPI:
  // haven't gotten this far yet, because I'm stuck on trying
  // to figure out how to convery my struct to a byte array.
}

void loop() 
{
  
}

I've noticed two different approaches to solving this problem:
one involved a "union" : union and struct to make byte array - Programming Questions - Arduino Forum
and the other involved casting a char* : c++ - Convert struct into bytes - Stack Overflow

I'm not familiar with either of these techniques, nor do I know what the "best practice" is, and I don't fully understand how to implement them.

If someone could please baby-step me through the struct-to-byte array conversion process I would be very grateful.

Thanks.

-Josh!

Hello,

joshpit2003:
I figured the best way to keep track of my data would be to create a struct.

Excellent choice. I've always been a big fan of the struct and its ilk.

one involved a "union" ... and the other involved casting a char* ... nor do I know what the "best practice" is

The best practice involves the space between your ears. Whichever one you understand is the one to use. Any other answer involves zealots with too much time on their hands arguing about things that are irrelevant.

If someone could please baby-step me through the struct-to-byte array conversion process I would be very grateful.

At this point, do any of the techniques make any sense?

Out of curiosity, what's on either side of the SPI connection?

You could do something similar to what I did for I2C to send a struct (or anything):

(for fellow noobs: Nick Gammon has a great write-up on different protocols: Gammon Forum : Electronics : Microprocessors : Comparison of transfer protocols )

Thanks!

I would use a union because then the data exists in two different formats at the same time without any need for conversion.

...R

iterate " sizeof a type with byte pointer " is the generic way to do stuff in "c",
it is ok has long you stay within the same compiler space and environment.

different compiler and/or compiler options can pack struct differently ...
environment can modifi stuff, take a peek at the endianness of processor and IC.
protocols also need sometime adjustments.

so for a more generic approach (when you communicate within different things) the union approach work very well.
In that case we can implement a hierarchy of function to encode in a standard format every types or classes, and stream-in or stream-out those objects.

// name the structure:
typedef struct myDataStruct dataVessels;
// I'm not sure why I have to name it, because it sure seems like
// a name was already assigned to it when I created the struct (?)

A name WAS defined. The name was "struct myDataStruct". If you are comfortable using the name "struct myDataStruct" everywhere you need to refer to the type, the typedef statement is not necessary.

Most people find that typing "struct myDataStruct" as a type does not feel natural, so they use a typedef statement to create a one-word type name.

In the code,

struct myDataStruct myInstance;

and

dataVessels myInstance;

produce exactly the same results.

Thanks guys for the responses.

since Nick Gammon gave an example w/ code:

// Written by Nick Gammon
// May 2012

#include <Arduino.h>
#include <Wire.h>

template <typename T> 
unsigned int I2C_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          Wire.write(*p++);
    return i;
  }  // end of I2C_writeAnything

template <typename T> 
unsigned int I2C_readAnything(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          *p++ = Wire.read();
    return i;
  }  // end of I2C_readAnything

I've been trying to break his code down to figure out what is happening here.
Unfortunately I didn't get very far.

I figured I could just pull the function:

unsigned int I2C_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          Wire.write(*p++);
    return i;
  }  // end of I2C_writeAnything

from his library code, and place it into my existing code.

since I don't need the function to return anything I could break it down to this:

I2C_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    for (int i = 0; i < sizeof value; i++)
          Wire.write(*p++);
  }  // end of I2C_writeAnything

I'm unclear as to what it means when said function takes in "const T& value"
(and when I try to compile this function I get " error: 'value' was not declared in this scope")
(?)

moving on to the next line: const byte * p = (const byte*) &value;
I get that we are declaring a pointer "p" that points at a variable type "const byte" (constant because we don't want to be changing the byte it is pointing at) and we are setting this pointer = to ...?
(and this is where I get lost): (const byte*) &value
I'm guessing this part of the code is getting us to the address of the first byte of the variable that is being passed to the function, but I don't understand how.
(?)

moving on:

for (int i = 0; i < sizeof value; i++)
Wire.write(*p++);

we are then writing the address value (the passed variable's first byte) and all proceeding address values (all proceeding bytes) up until the point where we have incremented the entire size (number of bytes) of the passed variable.

So even though I don't understand everything that is going on with this function, I figured I could just replace the "Wire.write()" part of the code with some other code that would just place down each byte into a global array. Unfortunately I still have the compiler issue of "error: 'value' was not declared in this scope".

Thoughts?

Oh, and the goal of learning How do I convert a struct to a byte array and back to a struct again is because my application is:
Arduino Mega is collecting many temp readings from around my house (w/ the use of Xbees and temp sensors), I then want to send all of this data to my Arduino Yun, which I will then have write to the on-board SD Card and send off to web. I need the Mega's SRAM for the sketch I am currently running, otherwise I could avoid all of this Arduino-to-Arduino data transfer if I could run the whole sketch just on the Yun. I decided that SPI was the best method to perform this arduino-to-arduino data-transfer.

I2C_writeAnything (const T& value)

You can't do that. A function has to have a return type, even if it is void, so:

void I2C_writeAnything (const T& value)

Anyway, for your goal of sending structure via SPI I came up with this variation:

SPI_anything.h :

#include <Arduino.h>

template <typename T> unsigned int SPI_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          SPI.transfer(*p++);
    return i;
  }  // end of SPI_writeAnything

template <typename T> unsigned int SPI_readAnything(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          *p++ = SPI.transfer (0);
    return i;
  }  // end of SPI_readAnything
  
  
template <typename T> unsigned int SPI_readAnything_ISR(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    *p++ = SPDR;  // get first byte
    for (i = 1; i < sizeof value; i++)
          *p++ = SPI.transfer (0);
    return i;
  }  // end of SPI_readAnything_ISR

Now for a master (sender):

// master

#include <SPI.h>
#include "SPI_anything.h"

// create a structure to store the different data values:
typedef struct myDataStruct
{
  byte exampleByteVessel;
  int exampleIntVessel;
  long exampleLongVessel;
};

myDataStruct foo;

void setup ()
  {
  SPI.begin ();
  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

  foo.exampleByteVessel = 42;
  foo.exampleIntVessel = 32000;
  foo.exampleLongVessel = 100000;
  }  // end of setup

void loop () 
  { 
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI_writeAnything (foo);
  digitalWrite(SS, HIGH);
  delay (1000);  // for testing  
  
  foo.exampleLongVessel++;
  }  // end of loop

That uses SPI_writeAnything to send your structure every second. (It doesn't matter if there is a return value, you can ignore it).


Now for the receiver (slave).

First version just waits for data:

// slave

#include <SPI.h>
#include "SPI_anything.h"

// create a structure to store the different data values:
typedef struct myDataStruct
{
  byte exampleByteVessel;
  int exampleIntVessel;
  long exampleLongVessel;
};

myDataStruct foo;

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

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  }  // end of setup

void loop () 
  { 
  SPI_readAnything (foo);
  Serial.println ((int) foo.exampleByteVessel);
  Serial.println (foo.exampleIntVessel);
  Serial.println (foo.exampleLongVessel);
  Serial.println ();
  }  // end of loop

That works, but you just sit in loop waiting for the SPI data to arrive.

Output:

42
32000
100961

42
32000
100962

42
32000
100963

42
32000
100964

42
32000
100967

Now one with interrupts, which is slightly trickier, because when the interrupt fires we already have the first byte. So we need to use a different version designed to go into an ISR:

// slave

#include <SPI.h>
#include "SPI_anything.h"

// create a structure to store the different data values:
typedef struct myDataStruct
{
  byte exampleByteVessel;
  int exampleIntVessel;
  long exampleLongVessel;
};

volatile myDataStruct foo;
volatile bool haveData = false;

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

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // now turn on interrupts
  SPI.attachInterrupt();
  
  }  // end of setup

void loop () 
  { 
  if (haveData)
     {
     Serial.println ((int) foo.exampleByteVessel);
     Serial.println (foo.exampleIntVessel);
     Serial.println (foo.exampleLongVessel);
     Serial.println ();
     haveData = false;
     }
  }  // end of loop

// SPI interrupt routine
ISR (SPI_STC_vect)
  {
  SPI_readAnything_ISR (foo);
  haveData = true;
  }  // end of interrupt routine SPI_STC_vect

Output:

42
32000
101060

42
32000
101063

42
32000
101064

42
32000
101065

42
32000
101066

42
32000
101067
1 Like

Thanks Nick!

I saved your code as a library and got your examples working in no-time.
So now I have something that works, but I'm still not sure how it works.

I'm still confused about what is going on inside the main functions of your library code:

template <typename T> unsigned int SPI_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          SPI.transfer(*p++);
    return i;
  }  // end of SPI_writeAnything

Any chance you could break down the first 4 lines of code for noobs like me?

Thanks again.

The templated function SPI_writeAnything matches a call to it with any type. That type becomes T.

So, for example:

float foo = 42;
SPI_writeAnything (foo);

In that example T would be "float".

So continuing that example, the function now becomes, in effect:

unsigned int SPI_writeAnything (const float & value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          SPI.transfer(*p++);
    return i;
  }  // end of SPI_writeAnything

Now line by line:


    const byte * p = (const byte*) &value;

Convert the argument (value) to a pointer, and cast it to const byte *.


    unsigned int i;
    for (i = 0; i < sizeof value; i++)

For each byte in the size of the passed value (ie. in this case the size of a float which would be 4 bytes) do something.


          SPI.transfer(*p++);

Transfer (send) the byte we are pointing to and increment that pointer by one.


    return i;

Return the number of bytes we sent in case that is useful.

1 Like

hi,
Can you tell my why it is better to instantiate multiple functions through a template definition
instead of using a generic method ?

// something like that 
void write( byte *p, int size) {
   for ( ; size--;) {
      SPI.transfer(*p++);
   }          
}

//...

  int x;
  float f;
 
  write( (byte *)&x, sizeof(int));
  write( (byte *)&f, sizeof(float));

Saves you having to cast the pointer, and then find the size of whatever-it-is. It's for convenience.