SPI Communications between Arduinos - Critical Region??

I am a moderately experienced Arduino hacker.

I want to use a second Arduino (slave) to do some computation while keeping the Master Arduino dedicated to a pretty hard real time task. The slave would calculate a two byte value and make it available to the Master via an SPI query.

I am considering using the SPI bus to transfer data, once computed, from the Slave to the Master.

Please bear with me...I am sure this is covered somewhere but I am having a hard time finding the info.

Question: how do I make sure when the Master asks the Slave for the computation result, that the Slave is not in the process of writing into the transfer register(s)....basically, I am asking how to get a clean transfer that does not risk "stepping on" the data (basic synchronization). It sounds like I need to set up a critical region around the internal data handling inside the Slave? Seems to be the correct answer however I am concerned that a critical region might interfere with the SPI "engine" timing.

I am not sure I am communicating this well...

Thanks.

did you check Nick Gammon SPI tutorial? I think you may find your answer there...

Martin_Bonfiore:
I want to use a second Arduino (slave) to do some computation while keeping the Master Arduino dedicated to a pretty hard real time task. The slave would calculate a two byte value and make it available to the Master via an SPI query.

I am considering using the SPI bus to transfer data, once computed, from the Slave to the Master.

Please bear with me...I am sure this is covered somewhere but I am having a hard time finding the info.

Question: how do I make sure when the Master asks the Slave for the computation result, that the Slave is not in the process of writing into the transfer register(s)....basically, I am asking how to get a clean transfer that does not risk "stepping on" the data (basic synchronization). It sounds like I need to set up a critical region around the internal data handling inside the Slave? Seems to be the correct answer however I am concerned that a critical region might interfere with the SPI "engine" timing.

The following write-up may help you to get the answers of your queries.
1. This is a conceptual view of data exchange mechanism between Master-UNO and Slave-NANO using SPI Port.
spi328x.png
Figure-1: SPI Port connection between Master-UNO and Slave-NANO

2. In Fig-1, we clearly observe that the SPI communication is a 'circulating buffer' type communication -- simultaneous transmit and receive.

3. It is the Master which always initiates the data transfer (SPI transfer) by generating the SCK pulses. When the Master executes this instruction: SPDR = 0x23, the quantity 0x23 is immediately loaded into its SPDR Register, 8 SCK pulses are generated, and 0x23 (00100011) is shifted into SPDR Register of Slave; at the same time, the content of SPDR Register of Slave enters into SPDR Register of Master. If bit transfer rate is 125 Kbits/sec, the transfer time would be: 64 us.

Assume that the user has stored 0x67 into SPDR Register of Slave prior to SPI transfer process; after transfer, SPDR of Master will contain 0x67 and SPDR of Slave will contain 0x23. Let us verify this proposition:

//----------------------------------------------------------------------------------------------------------------
(Note: After transfer, how does Master know that 0x67 has really arrived into its SPDR Register? The Master knows it by looking for HIGH at SPIF-bit of SPSR Register. The same rule also applies for the Slave.

Neither Master nor Slave should attempt to write new data into its SPDR Register without waiting for the data to arrive in the respective SPDR Register and reading it out.

There is no way for the Master to know that the Slave has read out data from its SPDR and then has stored new data into it unless the Slave tells it to the Master through interrupt or complicated SPI handshaking. Instead, it is better for the Master to wait for a while (say, ~= 5 us) and then initiate the next SPI transfer cycle.
//------------------------------------------------------------------------------------------------------------------

(1) Let us connect UNO-NANO as per following diagram of Fig-2.
spiy.png
Figure-2: SPI connection between UNO and NANO

(2) Upload the following sketches in UNO and NANO
UNO Codes:

#include <SPI.h>

void setup (void)
{
  Serial.begin(115200);
  digitalWrite(SS, HIGH);  // ensure SS stays high for now
  SPI.begin ();
  delay(100);
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI.setClockDivider(SPI_CLOCK_DIV128);  //125 kbits/sec
  //----------------------------------------------------
  SPDR = 0x23;
  while(bitRead(SPSR, SPIF) != HIGH)//wait until SPDR has got data
    ;
  byte RXBuffer = SPDR;
  Serial.println(RXBuffer, HEX);  //shows: 67 has come from Slave
}

void loop()
{
  
}

NANO Codes:

#include <SPI.h>

void setup (void)
{
  Serial.begin(115200);
  pinMode(SS, INPUT_PULLUP);
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  bitClear(SPCR, 4);  //Slave Mode
  SPDR = 0x67;        //present contnet of SPDR
  //---------------------------------------------
  while (bitRead(SPSR, SPIF) != HIGH) //wait until SPDR has got data
    ;
  byte RXBuffer = SPDR;
  Serial.println(RXBuffer, HEX);  //shows: 0x23 has come from UNO
}

void loop()
{

}

(3) Bring in the Serial Monitor (SM2) of NANO.

(4) Bring in the Serial Monitor (SM1) of UNO.

(5) Check that 0x67 has appeared on SM1 and 0x23 has appeared on SM2.

Observations:
1. Data transaction is completer by one cycle of SPI transfer. The next cycle can happen safely without any conflict/collision. Let the Master write into its SPDR what it wants to transfer to Slave; let the Slave store into its SPDR what it wants to deliver to Master. This strategy could be applied to accomplish data exchange of any complexity level.
2. At Master side, this single line code: 'byte RXBuffer = SPI.transfer(0x23);' is functionally equivalent to the following 4 lines: ('byte RXBuffer = SPI.transfer(0x23) is a blocking code; to make it faster and non-blocking, interrupt strategy could be used for both Master and Slave.)

SPDR = 0x23;
while(bitRead(SPSR, SPIF) != HIGH)//wait until SPDR has got data
    ;
byte RXBuffer = SPDR;  //read data from SPDR (whether used or not) to clear the SPIF flag

......to be continued
Example-1: Master asks Slave of Fig-2 to deliver a 16-bit data item -- 0x1234.
Master-MEGA-SPI Codes:

#include <SPI.h>
byte x;

void setup (void)
{
  Serial.begin(115200);
  digitalWrite(SS, HIGH);  // ensure SS stays high for now
  SPI.begin ();
  delay(100);
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI.setClockDivider(SPI_CLOCK_DIV128);  //125 kbits/sec
}

void loop()
{
  
  //----------------------------------------------------------------------
  byte x1 = SPI.transfer(0x00);
  delayMicroseconds(5); //allow Slave to finish executing its own codes
  byte x2 = SPI.transfer(0x00);
  //----------------------------------------------------------------------------------
  /*the above 3 lines can be replaced by: int y = SPI.transfer16(0x0000);*/
  //----------------------------------------------------------------------------------
  int z = (int)x1<<8|(int)x2;
  Serial.println(z, HEX);
  delay(1000);  //test interval
}

Slave-UNO-SPI Codes

#include <SPI.h>
volatile bool flag1 = false;
byte myData[2] = {0x34, 0x12};
int i=0;

void setup ()
{
  Serial.begin(115200);
  pinMode(SS, INPUT_PULLUP);  // ensure SS stays high for now
  pinMode(MISO, OUTPUT);
  SPCR |= _BV(SPE);
  SPI.attachInterrupt();
}

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

ISR(SPI_STC_vect)
{
  flag1 = true;
}

Master-MEGA Screenshot:
SMspi-10.png

Example-2: Acquiring Temperature Signal of LM35 Sensor from Slave
multicomspiy.png

Facing extreme difficulties to create sketch that will give stable readings of temperature at the Master side.
....to be continued

spi328x.png

spiy.png

SMspi-10.png

multicomspiy.png