SPI communication between 2 UNOs

Hi,

I have written the codes below. to use SPI between 2 arduino UNOs as a first trial based on Nick Gammon’s tutorial.

I send commands ‘i’ and ‘o’ to the master over serial. Slave receives commands over SPI. ‘i’ turns ON and LED. ‘o’ turns the LED OFF.

I’ve set up this protocol for the SPI communitcation:
SPI MASTER_SLAVE Communication Setup.PNG

The code does what just that but it really bothers me that it seems that you need to have that “delayMicroseconds (20)” inbetween transfers else things just don’t work properly and stop responding!

the SPDR register should be already updated by the time SPI.transfer is complete. so why do we need to wait 20us before actually reading the data.

And to make things more fun, if I use Serial.print to try and debug, it work fine! :o

Any ideas why, anyone?

MASTER:

#include <SPI.h>

volatile char req[9];
volatile uint8_t a, i = 0;

byte transferAndWait (byte what)
{
  byte x = SPI.transfer (what);
  delayMicroseconds (20); //wait for slave to process and reply
  return x;
}

void setup (void)
{
  Serial.begin (9600);

  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  SPI.begin ();

  SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));

  Serial.println ("READY");
}

void loop (void)
{
  uint8_t j;

  if (Serial.available()) {
    req[i] = Serial.read();
    ++i;
  }


  if (req[i - 1] == ';') {
    if (req[0] == 'o') { //turn LED OFF
      // enable Slave Select
      digitalWrite(SS, LOW);

      a = transferAndWait(0); //start SPI COMMS
      //Serial.println (String(a,HEX));
      a = transferAndWait('o'); //send command
      //Serial.println (String(a,HEX));
      a = transferAndWait(0); //end SPI COMMS

      if (a == 'O') {
        Serial.print("LED OFF!");
        Serial.println (char(a));
      }
      else {
        Serial.print("Failed! ");
        Serial.println (String(a, HEX));
      }

      // disable Slave Select
      digitalWrite(SS, HIGH);
    }
    else if (req[0] == 'i') { //turn LED ON
      // enable Slave Select
      digitalWrite(SS, LOW);

      a = transferAndWait(0); //start SPI COMMS
      //Serial.println (String(a,HEX));
      a = transferAndWait('i'); //send command
      //Serial.println (String(a,HEX));
      a = transferAndWait(0); //end SPI COMMS

      if (a == 'I') {
        Serial.print("LED ON!");
        Serial.println (char(a));
      }
      else {
        Serial.print("Failed! ");
        Serial.println (String(a, HEX));
      }

      // disable Slave Select
      digitalWrite(SS, HIGH);
    }

    i = 0;
  }

}

SLAVE:

#define SPI_TIMEOUT 25

#include <SPI.h>

volatile uint8_t start = 0, command = 0;

// SPI interrupt routine
ISR (SPI_STC_vect)
{
  byte spi = SPDR;

  if (spi == 0 && start == 0) { //start of SPI comms
    if (command != 0xA3) {
      start = 1;
      SPDR = 0xA4; //acknowledge start of comms
    }
    else {
      command = 0;
      SPDR = 0;
    }
  }
  else if (start > 0) {

    if (start == 1) {
      start = 2;
      command = spi;
    }

    if (command == 'o') {
      digitalWrite(2, LOW);
      command = 0xA3; //end of command
      start = 0; //end of comms
      SPDR = 'O';
    }
    else if (command == 'i') {
      digitalWrite(2, HIGH);
      command = 0xA3; //end of command
      start = 0; //end of comms
      SPDR = 'I';
    }
    else { //reset spi comms
      start = 0;
      command = 0;
    }
  }

  timeout = micros();
}

void setup (void)
{

  pinMode(2, OUTPUT); //LED output
  digitalWrite(2, LOW);

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  SPDR = 0; //initialise SPI register

}  // end of setup

void loop (void)
{
  //re-initialise SPI variables on timeout
  if (SPDR != 0 && (micros() - timeout) > SPI_TIMEOUT) {
    start = 0;
    command = 0;
    SPDR = 0;
  }
}

Edit - Gammon says,

Otherwise the slave doesn't have a chance to react to the incoming data and do something with it.

How does that relate to your problem?

aarg:
Edit - Gammon says,
How does that relate to your problem?

That's exactly what does not make sense to me! If you look at the SPI.transfer() code found in SPI.h library, you may be able to understand what I mean. I've posted a snippet below

//****extracted from SPI.h******
// Write to the SPI bus (MOSI pin) and also receive (MISO pin)

inline static uint8_t transfer(uint8_t data) {
  SPDR = data;
  
  asm volatile("nop");
  
  while (!(SPSR & _BV(SPIF))) ; // I would have expected this to provide the waiting time necessary for
                                // the slave to react to the incoming data, do something with it and reply back
  
  return SPDR;
}

What am I missing in my understanding that would justify the need to include a delay? :confused:

I think that check of the flags only prevents you from sending another frame until the current one is shifted out. The delay that Gammon uses is related to the handshaking arrangement. The slave doesn't know what the master is doing, except by receiving frames. Sure, it receives a frame instantaneously, but once received, it takes some time to process. Off the top of my head, an ISR is called at the very least. Until the ISR responds, the frame just sits there in the RX register.

Disclaimer: I haven't actually played with this much. But this is my take for what it's worth.

If there was no handshaking, and the master was just sending out frames, and the slave was just blindly receiving them, I suppose the master would not have to use a delay.