Using SPI Protocol to send data between 2 Arduinos

Hey,

So i want to use SPI to send data from the slave to the master using the transfer() function however using this function only return zeros for me even though it should return the number 2. I have posted the code below.

Master code

#include<SPI.h>                             //Library for SPI 


int x;
void setup (void)

{
  Serial.begin(115200);                   //Starts Serial Communication at Baud Rate 115200 
  
  
  SPI.begin();                            //Begins the SPI commnuication
  SPI.setClockDivider(SPI_CLOCK_DIV8);    //Sets clock for SPI communication at 8 (16/8=2Mhz)
  digitalWrite(SS,HIGH);                  // Setting SlaveSelect as HIGH (So master doesnt connnect with slave)
}

void loop(void)
{
  byte Mastersend,Mastereceive;          

     x = 1;           //Logic for Setting x value (To be sent to slave) 

  
  digitalWrite(SS, LOW);                  //Starts communication with Slave connected to master
  
  Mastersend = x;                            
  Mastereceive=SPI.transfer(Mastersend); //Send the mastersend value to slave(sent to SPDR register) also receives value from slave(value from slave is also stored in SPDR before sent to master)
  Serial.println(Mastereceive);
  
  if(Mastereceive == 2)                   //Logic
  {
    Serial.println("we got this");
  }
  else
  {
    Serial.println("dont got this");
  }
  delay(1000);
}

Slave code

volatile boolean received;
volatile byte Slavereceived,Slavesend;
int x;
int buttonvalue;
void setup()

{
  Serial.begin(115200);
  
  SPCR |= _BV(SPE);                       //Turn on SPI in Slave Mode
  received = false;
  SPI.attachInterrupt();                  //Interuupt ON is set for SPI commnucation
  
}



ISR (SPI_STC_vect)                        //Inerrrput routine function 
{
  Slavereceived = SPDR;         // Value received from master is stored in variable slavereceived
  received = true;                        //Sets received as True 
}


void loop(){
 if(received)                            //Logic depending upon the value recerived from master
   {
      if (Slavereceived==1) 
      {
       x = 2;
      }else
      {
       x = 3;
      }
      
      
      
  Slavesend=x;                             
  SPDR = Slavesend;                           //Sends the x value to master via SPDR 
  delay(1000);
  }
}

Any help will be greatly appreciated! Thank you

You need to add/modify few more code lines with/of both Master and Slave sketches on the understanding of the following points of SPI Communication Protocol.

1. This diagram of Fig-1 describes that the current data byte (for your case, it is 1) of Master SPDR Register enters into SPDR of Slave; in response, the 'past data byte' of Slave (an unknown value which was there in the SPDR Register of Slave from the past) goes back to SPDR of Master. So, when you execute this code: Mastereceive=SPI.transfer(Mastersend);, naturally you will not find your expected 2 into Mastereceive variable. Think over this point.
spi328x.png
Figure-1:

2. Concept of Step-1 says that there are two data bytes that the Master is expected to receive from Slave over the SPI Port -- the 'past data byte (say: 0x67)' and the 'current data byte (2)'.

3. When you execute this code: Mastereceive=SPI.transfer(Mastersend);, 0x01 enters into Slave; in response, 0x67 enters into Mastereceive. Your Slave goes to ISR() and puts 0x02 into its SPDR, which will arrive into Mastereceive variable in the next port cycle -- that is: after the execution of Mastereceive=SPI.transfer(Mastersend); at the Master sketch. Therefore, the codes are:

Master SPI:

Materceive = SPI.transfer(Mastersend);     //Master gets 0x67
Serial.println(Mastereceive, HEX);             //shows: 67
Mastereceive=SPI.transfer(Mastersend);   //Master gets 0x02
Serial.println(Mastereceive, HEX);             //shows: 2 (leading zero does not appear)

Slave SPI: (untested)

#include <SPI.h>          //header file to get the meaning of SS, MISO symbolic names
volatile bool received = false;
volatile byte Slavereceived;

void setup ()
{
  Serial.begin(9600);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPCR &= ~(_BV(MSTR)); //Arduino is Slave
  SPDR = 0x67;  //test value
  SPCR |= _BV(SPIE);      //we not using SPI.attachInterrupt() why?
  sei();                           //global interrupt bit is enabled
}

void loop()
{
    if(received == true)
    {
       Serial.println(Slavereceived, HEX);
       received = false);
    }
}

ISR (SPI_STC_vect)                        //Inerrr put routine function
{
  Slavereceived = SPDR;              // Value received from master is stored in variable slavereceived
  SPDR = 0x02;                           //data for Master to be reached there at the next port cycle
  received = true;                        //Sets received as True
}

spi328x.png

Think of the SPI as as two shift registers: the SPDR in the Master and the SPDR in the slave. For each clock, one bit gets shifted out the top of the Master SPDR (via MOSI) and into the bottom of the Slave SPDR (via MOSI). At the same time bits are being shifted from the top of the Slave SPDR (vis MISO) and into the bottom of the Master SPDR (via MISO). After 8 clocks, the values in the two SPDR registers have swapped places.

The Slave gets an interrupt when the 8th bit arrives. At that point it is WAY too late to put the response into the SPDR to get sent because the 8 clocks have already happened. For a typical Call/Response interaction the Master has to send a dummy byte for each response byte from the Slave.

  SPI.transfer(Mastersend); //Send the Mastersend value to slave
  Mastereceive= SPI.transfer(0);  // Receive a byte from the Slave
  Serial.println(Mastereceive);

johnwasser:
For a typical Call/Response interaction the Master has to send a dummy byte for each response byte from the Slave.

  SPI.transfer(Mastersend); //Send the Mastersend value to slave

Mastereceive= SPI.transfer(0);  // Receive a byte from the Slave
  Serial.println(Mastereceive);

Excellent explanation except that you have not explained the need of issuing and extra command which you have termed as 'send a dummy byte'.

Moreover, many programmers like to collect value returned by SPI.transefer(arg) whether it is needed or not. The reason is that the MCU goes to check the SPIF flag which incurs some time delay that brings better timing between the Master and Slave.

GolamMostafa:
Excellent explanation except that you have not explained the need of issuing and extra command which you have termed as 'send a dummy byte'.

So you did not understand the part that says "The Slave gets an interrupt when the 8th bit arrives. At that point it is WAY too late to put the response into the SPDR to get sent because the 8 clocks have already happened."

GolamMostafa:
Moreover, many programmers like to collect value returned by SPI.transefer(arg) whether it is needed or not. The reason is that the MCU goes to check the SPIF flag which incurs some time delay that brings better timing between the Master and Slave.

Feel free to collect the returned value even in cases where the value is useless:

  Mastereceive = SPI.transfer(Mastersend); //Send the Mastersend value to slave
  Mastereceive = SPI.transfer(0);  // Receive a byte from the Slave
  Serial.println(Mastereceive);

I don't understand your reasoning: "the MCU goes to check the SPIF flag which incurs some time delay that brings better timing between the Master and Slave." The SPI.transfer() function has no way of knowing if the value it returns will be stored or not. How could it do anything different?

johnwasser:
The SPI.transfer() function has no way of knowing if the value it returns will be stored or not. How could it do anything different?

It is my understanding that there is a difference between the following two Arduino codes:

  1. byte x = SPI.transfer(0x00);
  2. SPI.transfer(0x00);

The 1st one is a blocking code; where, the MCU checks again and again if the SPIF flag has assumed HIGH state indicating that data is ready in the SPDR of Master; the data is saved in the variable x and then the control goes to the next label. The equivalent register level codes (at conceptual level) are:

SPDR = 0x00;
while(bitRead(SPSR, SPIF) != HIGH)
{
   ;    //data has not yet arrived form the Slave
}
byte x = SPDR;

The 2nd code is non-blocking. The MCU executes the instruction and then the control goes to the next label. The equivalent register level code (at conceptual level) is:

SPDR = 0x00;

The embedded time delay in the 1st code gives sometimes to the Slave to do its housekeeping/data loading job -- this is what I have termed as 'better timing'..

Both versions of SPI.transfer() (uint8_t transfer(uint8_t data) and void transfer(void *buf, size_t count)) wait for the SPIF flag after putting a byte in SPDR. If they did not, you could not safely do two transfers in a row because the second one could easily overwrite the SPDR register before the previous transfer was complete.

  // Write to the SPI bus (MOSI pin) and also receive (MISO pin)
  inline static uint8_t transfer(uint8_t data) {
    SPDR = data;
    /*
     * The following NOP introduces a small delay that can prevent the wait
     * loop form iterating when running at the maximum speed. This gives
     * about 10% more speed, even if it seems counter-intuitive. At lower
     * speeds it is unnoticed.
     */
    asm volatile("nop");
    while (!(SPSR & _BV(SPIF))) ; // wait
    return SPDR;
  }

  inline static void transfer(void *buf, size_t count) {
    if (count == 0) return;
    uint8_t *p = (uint8_t *)buf;
    SPDR = *p;
    while (--count > 0) {
      uint8_t out = *(p + 1);
      while (!(SPSR & _BV(SPIF))) ;
      uint8_t in = SPDR;
      SPDR = out;
      *p++ = in;
    }
    while (!(SPSR & _BV(SPIF))) ;
    *p = SPDR;
  }

Thank you @johnwasser with +1k for educating/convincing me by presenting codes from SPI.h Library that both versions of the referred SPI.transfer() method are blocking and they do repetitively check the status of SPIF flag of SPSR Register of ATmega328P MCU.