SPI between Arduino Mega2560 (Master) and Arduino Nano (Slave)

Hey there!

I've been struggling on this topic for a while now, hence I decided to ask here. I browsed the forum for clear answers, unfortunately this topic is very confusing and answers usually only cover a specific part of it.

I would like to have an Arduino Nano that drives some sensors, collects all the values and can provide these values to a connected Arduino Mega2560 via SPI. I wired them like this:

               Mega2560               Nano
MOSI           51                     11
MISO           50                     12
SCK            52                     13
SS (Slave)     53                     10

For the Nano (the slave) I use the following sketch:

#include <SPI.h>

volatile uint16_t sensorId;

volatile uint16_t distancePrimaryCM;

void setup() {
  Serial.begin(115200);
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= _BV(SPIE);
}

ISR(SPI_STC_vect) {
  sensorId = SPDR;
  uint16_t value = distancePrimaryCM;
  SPI.transfer(&value, 2);
}

void loop() {
  distancePrimaryCM = 301;    
  Serial.println("Primary:");
  Serial.println(distancePrimaryCM, DEC);
}

On the Mega2560 (the master) on the other hand, I have this running:

 #include <SPI.h>

void setup(void) {
  Serial.begin(115200);
  digitalWrite(SS, HIGH);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV4);
}

uint16_t transferMeasure(const uint8_t id) {
  uint16_t a = SPI.transfer(id);
  a |= SPI.transfer(id)  << 8;
  return a;
}

void loop(void) {
  uint16_t a;

  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));  
  digitalWrite(SS, LOW);

  a = transferMeasure(0);

  digitalWrite(SS, HIGH);
  SPI.endTransaction();   

  Serial.println("Results:");
  Serial.println(a);

  delay(2000);
}

The setup seems to be working.. sort of. When I take a look at the master's serial output, it reads like this:

11:15:06.649 -> 
11:15:06.649 -> Results:
11:15:06.649 -> 301
11:15:08.654 -> Results:
11:15:08.654 -> 0
11:15:10.669 -> Results:
11:15:10.669 -> 1
11:15:12.675 -> Results:
11:15:12.675 -> 301
11:15:14.648 -> Results:
11:15:14.648 -> 0
11:15:16.646 -> Results:
11:15:16.646 -> 301
11:15:18.655 -> Results:
11:15:18.655 -> 0
11:15:20.656 -> Results:
11:15:20.656 -> 1
11:15:22.658 -> Results:
11:15:22.658 -> 301
11:15:24.650 -> Results:
11:15:24.650 -> 0
11:15:26.650 -> Results:
11:15:26.650 -> 1
11:15:28.673 -> Results:
11:15:28.673 -> 301
11:15:30.662 -> Results:
11:15:30.662 -> 0
11:15:32.653 -> Results:
11:15:32.653 -> 301
11:15:34.678 -> Results:
11:15:34.678 -> 0
11:15:36.678 -> Results:
11:15:36.678 -> 301
11:15:38.674 -> Results:
11:15:38.674 -> 0
11:15:40.667 -> Results:
11:15:40.667 -> 1
11:15:42.643 -> Results:
11:15:42.643 -> 301
11:15:44.653 -> Results:
11:15:44.653 -> 0
11:15:46.654 -> Results:
11:15:46.654 -> 1
11:15:48.676 -> Results:
11:15:48.676 -> 301
11:15:50.647 -> Results:
11:15:50.647 -> 0
11:15:52.653 -> Results:
11:15:52.653 -> 1
11:15:54.642 -> Results:
11:15:54.642 -> 301
11:15:56.642 -> Results:
11:15:56.642 -> 0
11:15:58.648 -> Results:
11:15:58.648 -> 301
11:16:00.667 -> Results:
11:16:00.667 -> 0
...

Clearly there must be some timing issue while reading the values, as the sequence keeps repeating. I tried to fix this by adding delayMicroseconds(20); here and there, but that really didn't help.

Could anyone maybe tell, what's the issue and how this could be solved? I'd greatly appreciate help on this very unclear topic.

Thanks in advance!

On the slave you cannot use SPI.transfer(). You have to directly set the SPDR register with the current byte.

ISR(SPI_STC_vect) {
  sensorId = SPDR;
  uint16_t value = distancePrimaryCM;
  SPI.transfer(&value, 2);
}

At 4MHz speed the code has to be extremely fast, otherwise the value won't be ready until the next clock signal. With SPI you don't have a feature like the clock stretching for I2C, that allows to slow down the master until the return data is ready.

@OP

What do you expect to see on the Serial Monitor of MEGA?

What are these numbers/tags: 11:15:06.649 -> on the Serial Monitor of MEGA.

I am also getting ---
Results:
0
Results:
1
Results:
301

But not those numbers/tags?
SM-SPI.png

There are few things/codes in your sketches which are not logical. If I know what you expect to see on the Serial Monitor of MEGA, it will be easier for me to work on your sketches. From where the 0 and 1 are coming?

@pylon has already pointed out that the slave should not/can not execute SPI.transfer() function; it should be executed by Master as the execution of this code generates the Serial Clock on the SCK line.

SM-SPI.png

pylon:
On the slave you cannot use SPI.transfer(). You have to directly set the SPDR register with the current byte.

Alright, thank you for this hint! I've tried to adjust the code of the Slave and the Master according to it:

Master:

 #include <SPI.h>

void setup(void) {
  Serial.begin(115200);
  digitalWrite(SS, HIGH);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV4);
}

uint16_t transferMeasure(const uint8_t id) {
  uint16_t a = SPI.transfer(id);
  delayMicroseconds(20);
  a |= SPI.transfer(id + 1)  << 8;
  return a;
}

void loop(void) {
  uint16_t a;

  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));  
  digitalWrite(SS, LOW);

  a = transferMeasure(0);

  digitalWrite(SS, HIGH);
  SPI.endTransaction();   

  Serial.println("Results:");
  Serial.println(a);

  delay(2000);
}

Slave:

#include <SPI.h>

volatile uint16_t sensorId;
volatile uint16_t currentValue;

volatile uint16_t distancePrimaryCM;

void setup() {
  Serial.begin(115200);
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= _BV(SPIE);
}

ISR(SPI_STC_vect) {
  sensorId = SPDR;
  switch(sensorId) {
    case 0:
      currentValue = distancePrimaryCM;
      SPDR = (uint8_t)(currentValue  >> 8); 
      break;
    case 1: 
      SPDR = (uint8_t)(currentValue & 0xff); 
      break;
  }
}

void loop() {
  distancePrimaryCM = 301;    
  Serial.println("Primary:");
  Serial.println(distancePrimaryCM, DEC);
}

With this code I'm able to successfully receive 301 on the master - continuously. Now, when I extend it to a second value, the output derails again:

Master v2:

 #include <SPI.h>

void setup(void) {
  Serial.begin(115200);
  digitalWrite(SS, HIGH);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV4);
}

uint16_t transferMeasure(const uint8_t id) {
  uint16_t a = SPI.transfer(id);
  delayMicroseconds(20);
  a |= SPI.transfer(id + 1)  << 8;
  return a;
}

void loop(void) {
  uint16_t a;
  uint16_t b;

  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));  
  digitalWrite(SS, LOW);

  a = transferMeasure(0);
  b = transferAndWait(2);

  digitalWrite(SS, HIGH);
  SPI.endTransaction();   

  Serial.println("Results:");
  Serial.println(a);
  Serial.println (b);

  delay(2000);
}

Slave v2:

#include <SPI.h>

volatile uint16_t sensorId;
volatile uint16_t currentValue;

volatile uint16_t distancePrimaryCM;
volatile uint16_t distanceSecondaryCM;

void setup() {
  Serial.begin(115200);
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR |= _BV(SPIE);
}

ISR(SPI_STC_vect) {
  sensorId = SPDR;
  switch(sensorId) {
    case 0:
      currentValue = distancePrimaryCM;
      SPDR = (uint8_t)(currentValue  >> 8); 
      break;
    case 1: 
      SPDR = (uint8_t)(currentValue & 0xff); 
      break;
    case 2: 
      currentValue = distanceSecondaryCM;
      SPDR = (uint8_t)(currentValue  >> 8); 
      break;
    case 3: 
      SPDR = (uint8_t)(currentValue & 0xff); 
      break;
  }
}

void loop() {
  distancePrimaryCM = 301;    
  Serial.println("Primary:");
  Serial.println(distancePrimaryCM, DEC);

  distanceSecondaryCM = 201;
  Serial.println("Secondary:");
  Serial.println(distanceSecondaryCM, DEC);  
}

Output:

22:42:21.771 -> 
22:42:21.771 -> Results:
22:42:21.771 -> 457
22:42:21.771 -> 1
22:42:23.789 -> Results:
22:42:23.789 -> 457
22:42:23.789 -> 1
22:42:25.788 -> Results:
22:42:25.788 -> 457
22:42:25.788 -> 1
22:42:27.784 -> Results:
22:42:27.784 -> 457
22:42:27.784 -> 1
22:42:29.793 -> Results:
22:42:29.793 -> 457
22:42:29.793 -> 1
22:42:31.774 -> Results:
22:42:31.774 -> 457
22:42:31.774 -> 1
22:42:33.782 -> Results:
22:42:33.782 -> 457
22:42:33.782 -> 1
22:42:35.771 -> Results:
22:42:35.771 -> 457
22:42:35.771 -> 1
22:42:37.755 -> Results:
22:42:37.755 -> 457
22:42:37.755 -> 1
22:42:39.757 -> Results:
22:42:39.757 -> 457
22:42:39.757 -> 1
...

The output should be 301, 201, 301, 201, 301, 201, and so on.

Is there anything special that must be done in order to be able to also retrieve a second value?

Thanks again!

GolamMostafa:
@OP

What do you expect to see on the Serial Monitor of MEGA?

I would have expected to see the value from the Slave, continuously, without 1 and 0 in between. This works now. However, it only works for a single value - check the post above this one. :slight_smile:

GolamMostafa:
What are these numbers/tags: 11:15:06.649 -> on the Serial Monitor of MEGA.

I am also getting ---
Results:
0
Results:
1
Results:
301

But not those numbers/tags?

In my Arduino Serial Monitor Window I have an option that reads "Show timestamp". When I click it, it shows "those numbers/tags" (timestamps). I cannot see that in your (Windows) version, I'm using the latest MacOS Version of Arduino IDE.

GolamMostafa:
There are few things/codes in your sketches which are not logical. If I know what you expect to see on the Serial Monitor of MEGA, it will be easier for me to work on your sketches. From where the 0 and 1 are coming?

Well, this was my actual question... where do the 0s and 1s come from? :slight_smile: However, using the switch that I've implemented in the post above this, the single value is being transferred correctly. As soon as another value is to be transferred, it breaks again. Now my question is: How should the code look that allows the master to select which value he'd like to retrieve (primary or secondary distance for example)?

@OP

NANO Slave is sending two data items: 0x012D (301) and 0x01F5 (501) in response to MEGA's command over SPI Port. The Master is receiving those faithfully; there is no timing chaos (except that it deos not work at 4 MHz). Now, it is the task of the user to separate the data items from the composite according to the need.
sm-spi5.png

MEGA-Master-SPI Codes:

#include <SPI.h>
int i = 0;
byte myData[] = {0x00, 0x00, 0x00, 0x00};

union
{
  long x;
  byte rxData[4];
} data;

void setup()
{
  Serial.begin(115200);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV128); //125 kHz ; difficult at 4 MHz speed
  digitalWrite(SS, LOW);
  delay(100);
}

void loop()
{
  SPI.transfer(myData, sizeof(myData));
  data.rxData[0] = myData[1];  //SPI is simultaneous send and receive' 1-byte offset
  data.rxData[1] = myData[2];
  data.rxData[2] = myData[3];  //SPI is simultaneous send and receive
  data.rxData[3] = myData[0];

  Serial.print("Received data from Slave: ");
  Serial.println(data.x, HEX);

  int number1 = (int)(data.rxData[1] << 8) | (int)data.rxData[0];
  Serial.print("Number-1: ");
  Serial.print(number1);
  Serial.println("  (0x012D)");

  int number2 = (int)(data.rxData[3] << 8) | (int)data.rxData[2];
  Serial.print("Number-2: ");
  Serial.print(number2);
  Serial.println("  (0x01F5)");
  Serial.println("================================");

  delay(2000);
}

NANO-Slave-SPI Codes:

#include <SPI.h>                             //needed just only for the definitions of MISO and SS
volatile uint16_t sensorId;
uint16_t distancePrimaryCM = 301;  //0x012D
uint16_t dyCM = 501;  //0x01F5

volatile bool flag1 = false;
byte myData[] = 
{
  lowByte(distancePrimaryCM), highByte(distancePrimaryCM),
  lowByte(dyCM), highByte(dyCM)
  };
int i = 0;

void setup()
{
  Serial.begin(115200);
  pinMode(MISO, OUTPUT);
  pinMode(SS, INPUT_PULLUP);
  SPCR |= _BV(SPE);
  bitClear(SPCR, 4);  //SPI slave
  SPCR |= _BV(SPIE);
}

void loop()
{
  if (flag1 == false)
  {
    ;
  }
  else
  {
    SPDR = myData[i];//highByte(distancePrimaryCM);
    flag1 = false;
    i++;
    if ( i == 4)
    {
      i = 0;
    }
  }
}

ISR(SPI_STC_vect)
{
  flag1 = true;
}

Hope that the codes/results may be useful to you.

My take on this is the same as using a Fortran job card in the old days: when you get something that works, keep it, even if it's not the "best" way.

sm-spi5.png

First of all, thank you for getting back, I appreciate it! I've copied your codes and uploaded them to my master & slave - they work as described!

In order to be able to switch between multiple slaves, I've adjusted the master code a bit though:

GolamMostafa:
MEGA-Master-SPI Codes:

#include <SPI.h>
int i = 0;
byte myData[] = {0x00, 0x00, 0x00, 0x00};

union
{
  long x;
  byte rxData[4];
} data;

void setup()
{
  Serial.begin(115200);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV128);
  digitalWrite(SS, HIGH);
  delay(100);
}

void loop()
{
  digitalWrite(SS, LOW);
  SPI.beginTransaction(SPISettings(SPI_CLOCK_DIV128, MSBFIRST, SPI_MODE0));
  SPI.transfer(myData, sizeof(myData));
  data.rxData[0] = myData[1];
  data.rxData[1] = myData[2];
  data.rxData[2] = myData[3];
  data.rxData[3] = myData[0];

  SPI.endTransaction();
  digitalWrite(SS, HIGH);

  Serial.print("Received data from Slave: ");
  Serial.println(data.x, HEX);

  int number1 = (int)(data.rxData[1] << 8) | (int)data.rxData[0];
  Serial.print("Number-1: ");
  Serial.print(number1);
  Serial.println("  (0x012D)");

  int number2 = (int)(data.rxData[3] << 8) | (int)data.rxData[2];
  Serial.print("Number-2: ");
  Serial.print(number2);
  Serial.println("  (0x01F5)");
  Serial.println("================================");

  delay(2000);
}

On a first look, this seemed to have worked nicely. However, on a second look I noticed the following output:

10:27:36.203 -> Received data from Slave: 1F5012D
10:27:36.203 -> Number-1: 301  (0x012D)
10:27:36.203 -> Number-2: 501  (0x01F5)
10:27:36.203 -> ================================
10:27:37.149 -> Received data from Slave: 1F5012D
10:27:37.149 -> Number-1: 301  (0x012D)
10:27:37.149 -> Number-2: 501  (0x01F5)
10:27:37.149 -> ================================
10:27:39.129 -> Received data from Slave: 1F50101
10:27:39.166 -> Number-1: 257  (0x012D)
10:27:39.166 -> Number-2: 501  (0x01F5)
10:27:39.166 -> ================================
10:27:41.157 -> Received data from Slave: 1F5012D
10:27:41.157 -> Number-1: 301  (0x012D)
10:27:41.157 -> Number-2: 501  (0x01F5)
10:27:41.157 -> ================================
10:27:43.168 -> Received data from Slave: 1F5012D
10:27:43.168 -> Number-1: 301  (0x012D)
10:27:43.168 -> Number-2: 501  (0x01F5)
10:27:43.168 -> ================================
...
10:28:01.191 -> ================================
10:28:03.211 -> Received data from Slave: 1F5012D
10:28:03.211 -> Number-1: 301  (0x012D)
10:28:03.211 -> Number-2: 501  (0x01F5)
10:28:03.211 -> ================================
10:28:05.185 -> Received data from Slave: 1F5012D
10:28:05.185 -> Number-1: 301  (0x012D)
10:28:05.223 -> Number-2: 501  (0x01F5)
10:28:05.223 -> ================================
10:28:07.209 -> Received data from Slave: 1F50101
10:28:07.209 -> Number-1: 257  (0x012D)
10:28:07.209 -> Number-2: 501  (0x01F5)
10:28:07.209 -> ================================
10:28:09.198 -> Received data from Slave: 1F5012D
10:28:09.198 -> Number-1: 301  (0x012D)
10:28:09.198 -> Number-2: 501  (0x01F5)
10:28:09.198 -> ================================
...
10:28:25.258 -> ================================
10:28:27.240 -> Received data from Slave: 1F5012D
10:28:27.240 -> Number-1: 301  (0x012D)
10:28:27.240 -> Number-2: 501  (0x01F5)
10:28:27.240 -> ================================
10:28:29.234 -> Received data from Slave: 1F52D2D
10:28:29.234 -> Number-1: 11565  (0x012D)
10:28:29.269 -> Number-2: 501  (0x01F5)
10:28:29.269 -> ================================
10:28:31.267 -> Received data from Slave: 1F5012D
10:28:31.267 -> Number-1: 301  (0x012D)
10:28:31.267 -> Number-2: 501  (0x01F5)
10:28:31.267 -> ================================
...

As you might see, every once in a while "Number-1" is not correct, displaying values like 257 and 11565. This made me think that my modifications were to blame for it, so I switched back to your original code and had it running for > 1 minute - and see there:

10:37:21.910 -> Received data from Slave: 1F5012D
10:37:21.910 -> Number-1: 301  (0x012D)
10:37:21.910 -> Number-2: 501  (0x01F5)
10:37:21.910 -> ================================
10:37:23.922 -> Received data from Slave: 1F5012D
10:37:23.922 -> Number-1: 301  (0x012D)
10:37:23.922 -> Number-2: 501  (0x01F5)
10:37:23.922 -> ================================
10:37:25.931 -> Received data from Slave: 1F50101
10:37:25.931 -> Number-1: 257  (0x012D)
10:37:25.931 -> Number-2: 501  (0x01F5)
10:37:25.931 -> ================================
10:37:27.935 -> Received data from Slave: 1F5012D
10:37:27.935 -> Number-1: 301  (0x012D)
10:37:27.935 -> Number-2: 501  (0x01F5)
10:37:27.935 -> ================================
10:37:29.935 -> Received data from Slave: 1F5012D
10:37:29.935 -> Number-1: 301  (0x012D)
10:37:29.935 -> Number-2: 501  (0x01F5)
10:37:29.935 -> ================================

At some point the value for Number-1 detailed as well.

Maybe someone knows what's going on here and could explain how this can be avoided?

Thanks again!

Update:

I've adjusted the master code to only println to Serial if the numbers are NOT 501 or 301 and I've set the interval to 10ms (delay(10) at the end). The amount of times these transmission errors seem to occur is quite significant:

11:25:05.930 -> Received data from Slave: 1F52D2D
11:25:05.964 -> Number-1: 11565  (0x012D)
11:25:05.964 -> Number-2: 501  (0x01F5)
11:25:05.964 -> ================================
11:25:06.657 -> Received data from Slave: 1F52D2D
11:25:06.657 -> Number-1: 11565  (0x012D)
11:25:06.657 -> Number-2: 501  (0x01F5)
11:25:06.657 -> ================================
11:25:07.649 -> Received data from Slave: 101012D
11:25:07.649 -> Number-1: 301  (0x012D)
11:25:07.649 -> Number-2: 257  (0x01F5)
11:25:07.649 -> ================================
11:25:08.508 -> Received data from Slave: 101012D
11:25:08.508 -> Number-1: 301  (0x012D)
11:25:08.508 -> Number-2: 257  (0x01F5)
11:25:08.508 -> ================================
11:25:08.747 -> Received data from Slave: 101012D
11:25:08.747 -> Number-1: 301  (0x012D)
11:25:08.747 -> Number-2: 257  (0x01F5)
11:25:08.780 -> ================================
11:25:09.005 -> Received data from Slave: 101012D
11:25:09.005 -> Number-1: 301  (0x012D)
11:25:09.005 -> Number-2: 257  (0x01F5)
11:25:09.005 -> ================================
11:25:09.402 -> Received data from Slave: 101012D
11:25:09.402 -> Number-1: 301  (0x012D)
11:25:09.402 -> Number-2: 257  (0x01F5)
11:25:09.402 -> ================================
11:25:09.946 -> Received data from Slave: 101012D
11:25:09.946 -> Number-1: 301  (0x012D)
11:25:09.946 -> Number-2: 257  (0x01F5)
11:25:09.946 -> ================================
11:25:10.199 -> Received data from Slave: 101012D
11:25:10.199 -> Number-1: 301  (0x012D)
11:25:10.199 -> Number-2: 257  (0x01F5)
11:25:10.199 -> ================================
11:25:11.085 -> Received data from Slave: 101012D
11:25:11.085 -> Number-1: 301  (0x012D)
11:25:11.085 -> Number-2: 257  (0x01F5)
11:25:11.085 -> ================================
11:25:11.338 -> Received data from Slave: 1F50101
11:25:11.338 -> Number-1: 257  (0x012D)
11:25:11.338 -> Number-2: 501  (0x01F5)
11:25:11.338 -> ================================
11:25:12.830 -> Received data from Slave: 101012D
11:25:12.830 -> Number-1: 301  (0x012D)
11:25:12.830 -> Number-2: 257  (0x01F5)
11:25:12.830 -> ================================
11:25:13.072 -> Received data from Slave: 101012D
11:25:13.072 -> Number-1: 301  (0x012D)
11:25:13.072 -> Number-2: 257  (0x01F5)
11:25:13.072 -> ================================
11:25:13.320 -> Received data from Slave: 101012D
11:25:13.320 -> Number-1: 301  (0x012D)
11:25:13.320 -> Number-2: 257  (0x01F5)
11:25:13.320 -> ================================
11:25:15.016 -> Received data from Slave: 1F50101
11:25:15.016 -> Number-1: 257  (0x012D)
11:25:15.016 -> Number-2: 501  (0x01F5)
11:25:15.016 -> ================================
11:25:15.223 -> Received data from Slave: 101012D
11:25:15.223 -> Number-1: 301  (0x012D)
11:25:15.223 -> Number-2: 257  (0x01F5)
11:25:15.223 -> ================================
11:25:15.441 -> Received data from Slave: 101012D
11:25:15.479 -> Number-1: 301  (0x012D)
11:25:15.479 -> Number-2: 257  (0x01F5)
11:25:15.479 -> ================================
11:25:16.338 -> Received data from Slave: 101012D
11:25:16.338 -> Number-1: 301  (0x012D)
11:25:16.338 -> Number-2: 257  (0x01F5)
11:25:16.338 -> ================================
11:25:17.691 -> Received data from Slave: 101012D
11:25:17.691 -> Number-1: 301  (0x012D)
11:25:17.691 -> Number-2: 257  (0x01F5)
11:25:17.691 -> ================================
11:25:17.967 -> Received data from Slave: 1F50101
11:25:17.967 -> Number-1: 257  (0x012D)
11:25:17.967 -> Number-2: 501  (0x01F5)
11:25:17.967 -> ================================
11:25:18.180 -> Received data from Slave: 101012D
11:25:18.180 -> Number-1: 301  (0x012D)
11:25:18.180 -> Number-2: 257  (0x01F5)
11:25:18.180 -> ================================
11:25:18.431 -> Received data from Slave: 1F50101
11:25:18.431 -> Number-1: 257  (0x012D)
11:25:18.467 -> Number-2: 501  (0x01F5)
11:25:18.467 -> ================================
11:25:19.626 -> Received data from Slave: 1F52D2D
11:25:19.626 -> Number-1: 11565  (0x012D)
11:25:19.626 -> Number-2: 501  (0x01F5)
11:25:19.626 -> ================================
11:25:20.245 -> Received data from Slave: 101012D
11:25:20.245 -> Number-1: 301  (0x012D)
11:25:20.245 -> Number-2: 257  (0x01F5)
11:25:20.245 -> ================================
11:25:20.245 -> Received data from Slave: 101012D
11:25:20.245 -> Number-1: 301  (0x012D)
11:25:20.245 -> Number-2: 257  (0x01F5)
11:25:20.245 -> ================================
11:25:21.416 -> Received data from Slave: 101012D
11:25:21.449 -> Number-1: 301  (0x012D)
11:25:21.449 -> Number-2: 257  (0x01F5)
11:25:21.449 -> ================================
11:25:22.332 -> Received data from Slave: 1F50101
11:25:22.332 -> Number-1: 257  (0x012D)
11:25:22.332 -> Number-2: 501  (0x01F5)
11:25:22.332 -> ================================
...

Is this simply something that's "normal" for SPI communication or is there a timing problem somewhere which leads to those errors?

Update 2:

I've updated master and slave to be able to send 4 16bit numbers, now the transmission issues are even worse.

Master

#include <SPI.h>
int i = 0;
byte myData[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

union
{
  long x;
  byte rxData[8];
} data;

void setup()
{
  Serial.begin(115200);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV128);
  digitalWrite(SS, HIGH);
  delay(100);
}

void loop()
{
  digitalWrite(SS, LOW);
  SPI.beginTransaction(SPISettings(SPI_CLOCK_DIV128, MSBFIRST, SPI_MODE0));
  SPI.transfer(myData, sizeof(myData));
  data.rxData[0] = myData[1];
  data.rxData[1] = myData[2];
  
  data.rxData[2] = myData[3];
  data.rxData[3] = myData[4];
  
  data.rxData[4] = myData[5];
  data.rxData[5] = myData[6];
  
  data.rxData[6] = myData[7];
  data.rxData[7] = myData[0];

  SPI.endTransaction();
  digitalWrite(SS, HIGH);

  int number1 = (int)(data.rxData[1] << 8) | (int)data.rxData[0];
  int number2 = (int)(data.rxData[3] << 8) | (int)data.rxData[2];
  int number3 = (int)(data.rxData[5] << 8) | (int)data.rxData[4];
  int number4 = (int)(data.rxData[7] << 8) | (int)data.rxData[6];

//  if(number1 != 301 || number2 != 501) {
    Serial.print("Received data from Slave: ");
    Serial.println(data.x, HEX);
  
    Serial.print("Number-1: ");
    Serial.println(number1);
  
    Serial.print("Number-2: ");
    Serial.println(number2);

    Serial.print("Number-3: ");
    Serial.println(number3);

    Serial.print("Number-4: ");
    Serial.println(number4);

    Serial.println("================================");
//  }
  
  delay(2000);
}

Slave

#include <SPI.h>
volatile uint16_t sensorId;
uint16_t distancePrimaryCM = 200;
uint16_t distanceSecondaryCM = 300;
uint16_t distanceTertiaryCM = 400;
uint16_t distanceQuaternaryCM = 500;

volatile bool flag1 = false;
byte myData[8] = {0, 0, 0, 0, 0, 0, 0, 0};
int i = 0;

void setup()
{
  Serial.begin(115200);
  pinMode(MISO, OUTPUT);
  pinMode(SS, INPUT_PULLUP);
  SPCR |= _BV(SPE);
  bitClear(SPCR, 8);
  SPCR |= _BV(SPIE);
}

void loop()
{
  if(flag1 == true)
  {
    SPDR = myData[i];
    flag1 = false;
    i++;
    if (i == 8)
    {
      i = 0;
    }
  }

  myData[0] = lowByte(distancePrimaryCM);
  myData[1] = highByte(distancePrimaryCM);
  
  myData[2] = lowByte(distanceSecondaryCM);
  myData[3] = highByte(distanceSecondaryCM);
  
  myData[4] = lowByte(distanceTertiaryCM);
  myData[5] = highByte(distanceTertiaryCM);
  
  myData[6] = lowByte(distanceQuaternaryCM);
  myData[7] = highByte(distanceQuaternaryCM);
}

ISR(SPI_STC_vect)
{
  flag1 = true;
}

The output on the master looks like this:

11:59:01.765 -> Received data from Slave: 12C00C8
11:59:01.765 -> Number-1: 200
11:59:01.765 -> Number-2: 300
11:59:01.765 -> Number-3: 400
11:59:01.800 -> Number-4: 400
11:59:01.800 -> ================================
11:59:03.796 -> Received data from Slave: 2C0000C8
11:59:03.796 -> Number-1: 200
11:59:03.796 -> Number-2: 11264
11:59:03.796 -> Number-3: -32511
11:59:03.796 -> Number-4: 500
11:59:03.796 -> ================================
11:59:05.801 -> Received data from Slave: 12C00C8
11:59:05.801 -> Number-1: 200
11:59:05.801 -> Number-2: 300
11:59:05.801 -> Number-3: 300
11:59:05.801 -> Number-4: 500
11:59:05.801 -> ================================
11:59:07.802 -> Received data from Slave: 12C0064
11:59:07.802 -> Number-1: 100
11:59:07.802 -> Number-2: 300
11:59:07.802 -> Number-3: 11408
11:59:07.802 -> Number-4: 257
11:59:07.802 -> ================================
11:59:09.813 -> Received data from Slave: 12C00C8
11:59:09.813 -> Number-1: 200
11:59:09.813 -> Number-2: 300
11:59:09.813 -> Number-3: 400
11:59:09.813 -> Number-4: 300
11:59:09.813 -> ================================
11:59:11.815 -> Received data from Slave: 2C00C8C8
11:59:11.815 -> Number-1: -14136
11:59:11.815 -> Number-2: 11264
11:59:11.815 -> Number-3: 257
11:59:11.815 -> Number-4: 500
11:59:11.815 -> ================================
11:59:13.825 -> Received data from Slave: C800C8
11:59:13.825 -> Number-1: 200
11:59:13.825 -> Number-2: 200
11:59:13.825 -> Number-3: 400
11:59:13.825 -> Number-4: 500
11:59:13.825 -> ================================
11:59:15.799 -> Received data from Slave: C82C00C8
11:59:15.799 -> Number-1: 200
11:59:15.835 -> Number-2: -14292
11:59:15.835 -> Number-3: -28672
11:59:15.835 -> Number-4: 372
11:59:15.835 -> ================================
11:59:17.836 -> Received data from Slave: 12CC801
11:59:17.836 -> Number-1: -14335
11:59:17.836 -> Number-2: 300
11:59:17.836 -> Number-3: 400
11:59:17.836 -> Number-4: 500
11:59:17.836 -> ================================
11:59:19.839 -> Received data from Slave: 2C2C00C8
11:59:19.839 -> Number-1: 200
11:59:19.839 -> Number-2: 11308
11:59:19.839 -> Number-3: -32767
11:59:19.839 -> Number-4: 500
11:59:19.839 -> ================================
11:59:21.846 -> Received data from Slave: 2C2C00C8
11:59:21.846 -> Number-1: 200
11:59:21.846 -> Number-2: 11308
11:59:21.846 -> Number-3: 300
11:59:21.846 -> Number-4: 500
11:59:21.846 -> ================================
11:59:23.855 -> Received data from Slave: 2C2C00C8
11:59:23.855 -> Number-1: 200
11:59:23.855 -> Number-2: 11308
11:59:23.855 -> Number-3: 11308
11:59:23.855 -> Number-4: 500
11:59:23.855 -> ================================
11:59:25.856 -> Received data from Slave: 100C8C8
11:59:25.856 -> Number-1: -14136
11:59:25.856 -> Number-2: 256
11:59:25.856 -> Number-3: 400
11:59:25.856 -> Number-4: 500
11:59:25.856 -> ================================
11:59:27.848 -> Received data from Slave: 12C00C8
11:59:27.848 -> Number-1: 200
11:59:27.848 -> Number-2: 300
11:59:27.848 -> Number-3: -28528
11:59:27.848 -> Number-4: 257
11:59:27.848 -> ================================
11:59:29.870 -> Received data from Slave: 100C8C8
11:59:29.870 -> Number-1: -14136
11:59:29.870 -> Number-2: 256
11:59:29.870 -> Number-3: 400
11:59:29.870 -> Number-4: 500
11:59:29.870 -> ================================
11:59:31.862 -> Received data from Slave: 12C0001
11:59:31.862 -> Number-1: 1
11:59:31.862 -> Number-2: 300
11:59:31.862 -> Number-3: 400
11:59:31.862 -> Number-4: 500
11:59:31.862 -> ================================
11:59:33.876 -> Received data from Slave: 2C0000C8
11:59:33.876 -> Number-1: 200
11:59:33.876 -> Number-2: 11264
11:59:33.876 -> Number-3: 400
11:59:33.876 -> Number-4: 500
11:59:33.876 -> ================================
11:59:35.861 -> Received data from Slave: C8
11:59:35.861 -> Number-1: 200
11:59:35.861 -> Number-2: 0
11:59:35.861 -> Number-3: -32468
11:59:35.861 -> Number-4: 500
11:59:35.861 -> ================================
11:59:37.870 -> Received data from Slave: 116C801
11:59:37.870 -> Number-1: -14335
11:59:37.870 -> Number-2: 278
11:59:37.870 -> Number-3: 400
11:59:37.870 -> Number-4: 385
11:59:37.870 -> ================================
...

mrus:
As you might see, every once in a while "Number-1" is not correct, displaying values like 257 and 11565. This made me think that my modifications were to blame for it, so I switched back to your original code and had it running for > 1 minute - and see there:
10:37:23.922 -> ================================
10:37:25.931 -> Received data from Slave: 1F50101
10:37:25.931 -> Number-1: 257 (0x012D)

0x0101 ==> 257 ; This is perfectly alright!

What has happened is this: Master has received/accumulated 0101 instead of 012D -- it indicates that data bytes have been duplicated. This is wrong. I am not an expert on SPI Protocol; I am just a player; I like to play around to make things work. To make something that works reliably in a commercial product, there is a need of more serious works. I am not very sure about the robustness of the routines that I have presented in my posts; I only know that the design is logical and it works well for most of the time at 125 Kbits/sec. You could use Gammon's SPI Codes to see how well they work at higher bit rates. I never tried his codes!

You can see in the following diagram how simple the SPI data exchange mechanism is; but, it is really a difficult job to implement it when there is a simultaneous send and receive. You are sending a byte to generate SCK pulses; in response, you are getting the previous data from SPDR Register of the Slave, which is to be discarded.

spi328z.png

BTW: Situation may improve if interrupt strategy is implemented at the Master side to accumulate received data.

spi328z.png

I understand. Thank you for your help so far, I guess if there's nobody on this forum that implemented a robust SPI communication I need to head to other platforms to ask for help. It's a bummer that not even support from official Arduino engineers seem to be available through this platform.

Anyhow, thanks again for the help so far, I appreciate it nevertheless!

mrus:
I understand. Thank you for your help so far, I guess if there's nobody on this forum that implemented a robust SPI communication I need to head to other platforms to ask for help. It's a bummer that not even support from official Arduino engineers seem to be available through this platform.

Anyhow, thanks again for the help so far, I appreciate it nevertheless!

In this thread you have seen only two participants. In the Programming Section, all kinds of people are there. You may re-think to post your SPI inquiry (about the current problem of sudden byte duplication) in the Programming Section.

Thank you for your intimation. You will always be extended help to the best I can.

I suggest you read Nick Gammon's page on SPI

I believe this can help you out.

mrus:
I guess if there's nobody on this forum that implemented a robust SPI communication [...]

A robust communication system is characterized by the presence of 'handshaking' protocols in its implementation. You first check that the slave is present by sending an inquiry code, get the known response and then ask for the data. This is one of the ways of preventing the actual data bytes from being corrupted through byte duplication. Let us modify the codes of Post#5 to make it robust.

Task-1 Simple Handshaking -- MEGA-Master sends inquiry code 0x05 and the UNO-Slave returns back the ACK/response code 0x06.

#include<SPI.h>

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV128); //125 kHz ; difficult at 4 MHz speed
  digitalWrite(SS, LOW);
  delay(100);


}

void loop()
{
  (void)SPI.transfer(0x05); //inquiry code
  delayMicroseconds(100);  //to allow Slave gets its codes executed before Master issues next transfer
  byte x = SPI.transfer(0x00);
  if (x == 0x06)
  {
    Serial.print(x, HEX);
    Serial.println("OK!");
  }
  Serial.println("Hello!");
  delay(1000);
}
#include<SPI.h>  //needed to get the meanings of SS and MISO
volatile bool flag1 = false;
byte y = 0x06;

void setup()
{
  Serial.begin(9600);
  pinMode(SS, INPUT_PULLUP);
  pinMode(MISO, OUTPUT);

  SPCR |= _BV(SPE);
  bitClear(SPCR, 4);  //SPI slave
  SPCR |= _BV(SPIE);
}

void loop()
{
  if (flag1 == true)
  {
    byte x = SPDR;
    if (x == 0x05)
    {
      Serial.print(x, HEX);
      Serial.println("OK!");
      SPDR = y;  //ACK code
      flag1 = false;
    }
  }
}

ISR(SPI_STC_vect)
{
  flag1 = true;
}

CrossRoads:
I suggest you read Nick Gammon's page on SPI

Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino

I believe this can help you out.

Thanks! I already found his tutorials a while ago and had no success with those either. He seems to be sort of an Arduino-Superstar, since many people suggest his tutorials - which indeed are very well written. However, just to make sure I tried the last one on that SPI page you linked once again. The result is, well, sort of better than with the code posted here, but it still gives some wrong values once in a while:

14:45:40.601 -> 42
14:45:40.601 -> 32000
14:45:40.601 -> 100000
14:45:40.601 -> 
14:45:41.613 -> 42
14:45:41.613 -> 32000
14:45:41.613 -> 100000
14:45:41.613 -> 
14:45:42.611 -> 42
14:45:42.611 -> 32000
14:45:42.611 -> 100000
14:45:42.611 -> 
14:45:43.592 -> 42
14:45:43.592 -> 32000
14:45:43.592 -> 100000
14:45:43.592 -> 
14:45:44.588 -> 42
14:45:44.588 -> 32000
14:45:44.588 -> 100000
14:45:44.588 -> 
14:45:45.585 -> 42
14:45:45.585 -> 32000
14:45:45.585 -> 100000
14:45:45.585 -> 
14:45:46.607 -> 42
14:45:46.607 -> 32000
14:45:46.607 -> 100000
14:45:46.607 -> 
14:45:47.613 -> 42
14:45:47.613 -> 32000
14:45:47.613 -> 100000
14:45:47.613 -> 
14:45:48.604 -> 42
14:45:48.604 -> 32000
14:45:48.604 -> 100000
14:45:48.604 -> 
14:45:49.577 -> 0
14:45:49.577 -> 32000
14:45:49.577 -> 100000
14:45:49.577 -> 
14:45:50.588 -> 42
14:45:50.588 -> 32000
14:45:50.588 -> 100000
14:45:50.588 -> 
14:45:51.576 -> 42
14:45:51.576 -> 32000
14:45:51.576 -> 100000
14:45:51.576 -> 
14:45:52.596 -> 42
14:45:52.596 -> 32000
14:45:52.596 -> 100000
14:45:52.596 -> 
14:45:53.581 -> 42
14:45:53.581 -> 32000
14:45:53.581 -> 100000
14:45:53.581 ->

As you might have noticed, I intentionally removed foo.c++; (master) in order to be able to run this example endlessly without having a variable overflowing. At some point (after a few minutes) the Nano (slave) still seems to freeze as the output simply stops. As soon as I set the delay on the master to 10 milliseconds, very strange things happen on the slave:

14:52:57.465 -> 42
14:52:57.465 -> 32000
14:52:57.465 -> 100000
14:52:57.465 -> 
14:52:57.465 -> 42
14:52:57.465 -> 32000
14:52:57.465 -> 100000
14:52:57.465 -> 
14:52:57.499 -> 42
14:52:57.499 -> 32000
14:52:57.499 -> 100000
14:52:57.499 -> 
14:52:57.499 -> 42
14:52:57.499 -> 32000
14:52:57.499 -> 100000
14:52:57.499 -> 
14:52:57.499 -> 42
14:52:57.499 -> 32000
14:52:57.499 -> 100000
14:52:57.499 -> 
14:53:05.317 -> 125
14:53:11.992 -> 42
14:53:33.381 -> 25600125
14:53:35.813 -> 
14:53:39.115 -> 0
14:53:45.320 -> 42
14:54:39.797 -> 25600125
14:54:42.401 -> 
14:54:45.660 -> 0
...

(check the timestamps!)

Also his example puzzles me even more with statements like the one made by pylon in mind:

pylon:
On the slave you cannot use SPI.transfer(). You have to directly set the SPDR register with the current byte.

Nick Gammon utilises SPI.transfer in his the functions inside SPI_anything.h, which are being used on the master and the slave likewise. So what's the deal, is SPI.transfer allowed on the slave or isn't it?

I'm starting wonder: Am I holding it wrong or did nobody actually ever try to build a real implementation on top of all this? Is there a better suited alternative to SPI which I'm overlooking here? From what I understood, SPI is the de-facto standard for having a high-speed connection between two MCUs.

Feel free to suggest ideas, glad for any advice as I seem to have run into a cul de sac.

mrus:
Nick Gammon utilises SPI.transfer in his the functions inside SPI_anything.h, which are being used on the master and the slave likewise. So what's the deal, is SPI.transfer allowed on the slave or isn't it?

My experiments with the codes of my Post#13 indicates --
Execution of SPI.transfer(y) code at the Slave side produces erratic result.
Execution of SPDR = y code in place of SPI.transfer(y) at the Slave side produces constantly correct result.

Screenshot with SPI.transfer(y) code:
smspi11.png

Screenshot with SPDR = y code:
smspi12.png

BTW: Please, post the codes (both Master and Slave) that have produced the erratic results of your Post#14. Also mention what value you expect to see on Serial Monitor of Master.

smspi11.png

smspi12.png

GolamMostafa:
My experiments with the codes of my Post#13 indicates --
Execution of SPI.transfer(y) code at the Slave side produces erratic result.
Execution of SPDR = y code in place of SPI.transfer(y) at the Slave side produces constantly correct result.

Thank you, Sir! So with this conclusion in mind to me it seems like one of the documents (Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino) that is being referred to by what feels like half of the internet is probably not the best way to implement this. That's a bit frustrating, to be honest.

Here are the codes I used:

Master

#include <SPI.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  
  
// create a structure to store the different data values:
typedef struct myStruct
{
  byte a;
  int b;
  long c;
};

myStruct foo;

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

  foo.a = 42;
  foo.b = 32000;
  foo.c = 100000;
  }  // end of setup

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

Slave

#include <SPI.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  
  
// create a structure to store the different data values:
typedef struct myStruct
{
  byte a;
  int b;
  long c;
};

volatile myStruct 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.a);
     Serial.println (foo.b);
     Serial.println (foo.c);
     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

As stated before, this is the code from the document linked above, where I commented out the foo.c++ line.

As soon as I set the delay on the master to 10 milliseconds, very strange things happen on the slave:

You must give enough time to the Slave to execute its codes to enter into ISR, retrieve data from the SPDR Register once data are ready and then come back to the loop() function. You are pumping data from Master to Slave at at interval of 10 ms -- this is too fast. Practically, natural data does not change that much fast. Increase the delay, you will receive good data at the Slave side.

Can SPI.tarnsfer(0xnn) be executed at the Slave side?
It depends on the context--

Here are Gammon's codes (shown only the necessary part) without SPI.transfer(0xnn) at the Slave sketch. Things are working alright. You can study this link to see how SPI.transfer(0xnn) works. The essential equivalency of SPI.transfer(0x00) may be viewed as these codes:

while(bitRead(SPSR, SPIF) != HIGH) //be sure that data has arrived in SPDR of Slave
{
  ;
}
byte x = SPDR;

Gammon Master Code:

#include <SPI.h>
#include <Arduino.h>

typedef struct myStruct
{
  byte a;
  int b;
  long c;
};
myStruct foo;

void setup ()
{
  Serial.begin(115200);
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV8);  //2 MHz ==>
  foo.a = 42;     //2A
  foo.b = 32000;  //7D 00
  foo.c = 100000; //01 86 A0
}

void loop ()
{
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI_writeAnything (foo);
  delay (1000);  // test interval
}

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++)
  {
    byte m = *p;
    Serial.print(m, HEX);
    SPI.transfer(*p++); //both instruction works
  }
  Serial.println();
  Serial.println("===============================");
}

Gammon Slave Code:

#include <SPI.h>
#include <Arduino.h>

typedef struct myStruct
{
  byte a;
  int b;
  long c;
};

volatile myStruct foo;
volatile bool haveData = false;

void setup ()
{
  Serial.begin (115200);
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPI.attachInterrupt();
}


void loop ()
{
  if (haveData)
  {
    Serial.println ((int) foo.a);
    Serial.println (foo.b);
    Serial.println (foo.c);
    Serial.println ();
    haveData = false;
  }
}

ISR (SPI_STC_vect)
{
  SPI_readAnything_ISR (foo);
  haveData = true;
}

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++)
  {
    while (bitRead(SPSR, SPIF) != HIGH)  //check that data from Master has arrived
    {
      ;
    }
    *p++ = SPDR;
  }
}

Screenshot of Master's Send Data in Hex Format
smspiMas.png

Screenshot of Slave's Received Data
SMSPI.png

SMSPI.png

smspiMas.png