is this another Heisenberg

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:

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 put in a 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;
  }
}

To give the slave time to put data in its data register so when the master sends the next byte to slave, the slave can clock the 'requested' data out.

If the slave does not have to send data back, you do not need the delay in the master.

Disclaimer: never looked at SPI in depth before.

sterretje:
To give the slave time to put data in its data register so when the master sends the next byte to slave, the slave can clock the 'requested' data out.

If the slave does not have to send data back, you do not need the delay in the master.

Disclaimer: never looked at SPI in depth before.

even if that were true (which I doubt btw as MISO and MOSI are read simultanously) 20us is a VERY LONG time.

I did try to reduce it and the comms messed up.

From what I could see from my experiments, SOMETHING seems to need that delay on the master side after an SPI. transfer (as in the "transferAndWait" routine in my OP)

but I could be wrong here as with other SPI devices you can bang SPI.transfers one after the other without problem.

it's frustrating as SPI is fast but if you need up need that delay to have a stable comms between 2 arduino its no better that Serial or I2C...

The normal way SPI is done is this, because its fully duplex:

The slave already has a status byte ready to return, and preloaded into the SPI hardware.

When the master transfers a byte the status goes back as the command comes in.

The slave looks at the command, decides what other data needs to be returned and loads the
SPI hardware with it, waiting for it to be clocked through or the CS to de-assert. It might also
be reading in more arguments from the master and thus sending back zeroes.

On the master side you simply clock through the command and typically then clock zeroes to
read the extra results from slave (or clocks more data for the command).

What you cannot do (unless implementing SPI in hardware), is respond to the current command
byte live - the slaves response has to be pre-loaded into the SPI hardware before the SPI
request ever comes in, the command and response bytes are clocked simultaneously between
master and slave by the SPI hardware from the hardware registers.

Note that the master can look at the status byte and avoid sending anymore bytes if it
realizes the slave is not ready - so a busy bit is normally included in the status byte format.

In your case I don't see the master waiting for the SPI hardware to be free before transfering
a byte - it must wait until the previous byte is processed, and by looking at the relevant
flag, not delayMicroseconds...

MarkT:
The normal way SPI is done is this, because its fully duplex:

The slave already has a status byte ready to return, and preloaded into the SPI hardware.

When the master transfers a byte the status goes back as the command comes in.

The slave looks at the command, decides what other data needs to be returned and loads the
SPI hardware with it, waiting for it to be clocked through or the CS to de-assert. It might also
be reading in more arguments from the master and thus sending back zeroes.

On the master side you simply clock through the command and typically then clock zeroes to
read the extra results from slave (or clocks more data for the command).

What you cannot do (unless implementing SPI in hardware), is respond to the current command
byte live - the slaves response has to be pre-loaded into the SPI hardware before the SPI
request ever comes in, the command and response bytes are clocked simultaneously between
master and slave by the SPI hardware from the hardware registers.

Note that the master can look at the status byte and avoid sending anymore bytes if it
realizes the slave is not ready - so a busy bit is normally included in the status byte format.

In your case I don't see the master waiting for the SPI hardware to be free before transfering
a byte - it must wait until the previous byte is processed, and by looking at the relevant
flag, not delayMicroseconds...

did you check the SPI.transfer code?

that's what it does I believe....

  /*extract from SPI.h library*/
// 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;
  }

Actually I misremembered, dug out my master-side code for this,
you only need to wait after initiating on the master side, which is what the standard SPI library does.

I wrote my own library that's more robust, and timesout should the SPI hardware jam up:

char HardSPI::transfer (char data)
{
  SPDR = data;                    // Start the transfer
  int timeout = 0 ;
  while (!(SPSR & (1<<SPIF)))     // Wait for the end of the transfer
    {
      timeout ++ ; 
      if (timeout > 100)
	break ;
    }
  return SPDR;                    // return the received byte
}

You probably just need the delay as the slave is messing about with various calls to micros() which
lock out interrupts on the client side and thus limit throughput?

MarkT:
Actually I misremembered, dug out my master-side code for this,
you only need to wait after initiating on the master side, which is what the standard SPI library does.

I wrote my own library that's more robust, and timesout should the SPI hardware jam up:

You probably just need the delay as the slave is messing about with various calls to micros() which
lock out interrupts on the client side and thus limit throughput?

interesting... not we getting somewhere.

care to share the whole library?