SPI between 2 Arduinos one as master and one as slave

Hi,

I followed the topic (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1264732746) - to create a basic case in which one Arduino
is acting as a master over SPI and sending a byte to a second Arduino acting as an SPI slave.

I would like to be able to send 128 bytes between the two - can I accomplish this in one read/write operation with something like the below ?
Are there any issues I’m overlooking below ?

i.e.
if I have an array of 128 bytes on the master:

digitalWrite(SS, LOW);
char buffer[128];
for (int i=0; i<128;i++)
{ 
  SPI.transfer(buffer[i]);
}
digitalWrite(SS, HIGH);

and on the SPI slave:

char buffer[128];

void loop()
{
  for (int i=0; i<128; i++)
  {
    buffer[i] = SPI.SPI_SlaveReceive();
  }
}

Read this before posting a programming question

Code tags, please.

out of curiosity would softserial be so much easier? over uart i find spi and bitshifting awkward ...

It's easy enough to send data between two Arduinos using SPI. No bit shifting necessary.

http://www.gammon.com.au/spi

Thanks for the awesome link, Nick !
That answers all my questions.

Best Regards,
Rick

For my new WavePro shield that I am working on I came up with a simple, but flexible, multi-byte bi-directional SPI protocol:

Basically, the SS line is used to “frame” the whole transaction - when the SS line goes low, the slave starts watching the SPI bus. When it goes high, the transaction is over.

The first byte of the transaction is the “command” byte, telling the slave what you want to do.
Next comes a payload length byte - this is the number of extra “parameter” bytes that accompany the command. The slave prepares to read this number of bytes.
The payload bytes are then sent, followed by a simple checksum byte.

The data transfer at this point gets reversed.

The master starts sending regular NULL bytes to the slave, and recording the results. It throws away NULL results until it gets the first non-null result. This is a “processing” period to allow the slave to process whatever needs processing and return the results. (I have at per-command configurable timeout on this bit.)

The first byte to be returned by the slave is a simple “status” byte - OK or ERROR.
Then, just like the command packet, a payload length follows, followed by that number of payload bytes and a checksum.

Once that has all finished, the transaction is over, and the SS line is raised.

Simple, flexible, and easy enough to implement.

I modified one of the examples (Gammon Forum : Electronics : Microprocessors : SPI - Serial Peripheral Interface - for Arduino) such that the Master sends the string “Hello, World!\n” to the Slave.
My intention was to have the Slave send back the same string to the master as it is received, although that is not what is occuring.

Here is the Master Code:

/* SPI Master - Hello World (derived from Nick Gammon's samples)

Arduino Mega is acting as an SPI Master for 
a corresponding Arduino Mega board

Circuit:
 SS_PIN: pin 53
 MISO: pin 50
 MOSI: pin 51
 SCK: pin 52

*/

#include <stdlib.h>
#include <SPI.h>

long int count = 1;
byte config = B01010000;
#define SCK_PIN  52
#define MISO_PIN 50
#define MOSI_PIN 51
#define SS_PIN   53
uint8_t buffer[128];
char respBuffer[100];
volatile byte pos;
volatile boolean process_it;

void setup()
{
  byte tmp;
  
  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  pinMode(MOSI_PIN, OUTPUT);
  pinMode(MISO_PIN, INPUT);
  pinMode(SCK_PIN, OUTPUT);
  pinMode(SS_PIN, OUTPUT);
  
  digitalWrite(SS_PIN, HIGH);  // Ensure SS stays high for now
  
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
  byte clr;
  clr=SPSR;
  clr=SPDR;
  
  Serial.begin(115200);
}


byte transferAndWait(const byte what)
{
  byte a = SPI.transfer(what);
  delayMicroseconds(20);
  return a;
} // end of transferAndWait

void loop()
{
 char c;
 byte r;

 // enable Slave Select  
 digitalWrite(SS_PIN, LOW);
   
 pos=0;
 // Send test string
 for (const char *p="Hello, world!\n";c=*p;p++)
 {
   //SPI.transfer(c);
   r=transferAndWait(c);
   respBuffer[pos++]=r;
   Serial.print(c);
   Serial.print(r);
 }
 
 //respBuffer[pos]=0;
 Serial.println("RX:");
 Serial.println(pos,DEC);
 Serial.println(respBuffer);
 pos=0;
   

 delay(100);
 
 // Disable slave select
 digitalWrite(SS_PIN, HIGH);
 
 // 1 second delay
 delay(100);
   
}

Here is the Slave code:

/* SPI Slave - Hello World (from Nick Gammon's sample)

Arduino Mega ADK is acting as an SPI Slave while 
a corresponding Arduino Duemilanove is acting as the
SPI Master

Circuit:
 SS_PIN: pin 53
 MISO: pin 50
 MOSI: pin 51
 SCK: pin 52

*/

#include <stdlib.h>
#include <SPI.h>

long int count = 1;
uint8_t buffer[3];
char buf [100];
volatile byte pos;
volatile boolean process_it;
#define SCK_PIN  52
#define MISO_PIN 50
#define MOSI_PIN 51
#define SS_PIN   53  

void setup (void)
{
  Serial.begin (115200);   // debugging

  // have to send on master in, *slave out*
  pinMode(MOSI_PIN, INPUT);
  pinMode(MISO_PIN, OUTPUT);
  pinMode(SCK_PIN, INPUT);
  pinMode(SS_PIN, INPUT);

  //SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  
  DDRB=(1<<DDB4);  //prev
  SPCR=(1<<SPE);   //prev
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
    
  // turn on interrupts
  SPCR != _BV(SPIE);
  
  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();
  
  // interrupt for SS falling edge
  //attachInterrupt (0, ss_falling, FALLING);

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register
  
  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    SPDR = c; // Added response - return same string
    
    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;
      
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  char c;
  
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    
    // Send response test string to Master
    //for (const char * p = "Goodbye, earth!\n" ; c = *p; p++)
    //  SPDR=c; //SPI.transfer(c);
    
    }  // end of flag set
    
}  // end of loop

The slave outputs the expected message:

Hello, world!

However, the master outputs the following (what is expected to be Hello World! from the slave are non visible chars - left blank here):

H e l l o W o r l d !
RX:
14

Do you know what I am doing wrong ?

You don’t need all this stuff:

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  pinMode(MOSI_PIN, OUTPUT);
  pinMode(MISO_PIN, INPUT);
  pinMode(SCK_PIN, OUTPUT);
  pinMode(SS_PIN, OUTPUT);
 
...

  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
  byte clr;
  clr=SPSR;
  clr=SPDR;

SPI.begin() does all that.

The fundamental thing you are doing wrong is that SPI.transfer (or if you must, if you do it yourself using the ports) transfers and receives at the same time. Hence it is just plain impossible to send and receive the same byte, delay or no delay.

You always have to send something, and then receive the response on the next transfer, eg.

SPI.transfer ('a');    // send something
byte x = SPI.transfer (0);  // get a response

Now you can send and receive lots of bytes, but you always have to be “out by one”.

On my page: http://www.gammon.com.au/spi

Scroll down to: "How to get a response from a slave"

That is illustrated there, plus there is a mention of needing a delay to allow the slave time to assemble a response.

I made the modification to use two separate SPI.transfer operations with a delay of various values (20 microseconds to 200 microseconds) on the master (see below), however, when I’m printing the byte received from the Slave it’s consistently a null. The data sent from the slave to master doesn’t seem to be getting transferred:

/* SPI Master - Hello World (derived from Nick Gammon's samples)

Arduino Mega is acting as an SPI Master for 
a corresponding Arduino Mega board

Circuit:
 SS_PIN: pin 53
 MISO: pin 50
 MOSI: pin 51
 SCK: pin 52

*/

#include <stdlib.h>
#include <SPI.h>

long int count = 1;
byte config = B01010000;
#define SCK_PIN  52
#define MISO_PIN 50
#define MOSI_PIN 51
#define SS_PIN   53
uint8_t buffer[128];
char respBuffer[100];
volatile byte pos;
volatile boolean process_it;

void setup()
{
  byte tmp;
  
  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  pinMode(MOSI_PIN, OUTPUT);
  pinMode(MISO_PIN, INPUT);
  pinMode(SCK_PIN, OUTPUT);
  pinMode(SS_PIN, OUTPUT);
  
  digitalWrite(SS_PIN, HIGH);  // Ensure SS stays high for now
  
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  //SPI.setBitOrder(MSBFIRST);
  //SPI.setDataMode(SPI_MODE0);
  
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
  byte clr;
  clr=SPSR;
  clr=SPDR;
  
  Serial.begin(115200);
}

byte transferAndWait(const byte what)
{
  byte a;

  SPI.transfer(what);
  delayMicroseconds(20);
  a=SPI.transfer(0);
  return a;
} // end of transferAndWait

void loop()
{
 char c;
 byte r;

 // enable Slave Select  
 digitalWrite(SS_PIN, LOW);
   
 pos=0;
 // Send test string
 for (const char *p="Hello, world!\n";c=*p;p++)
 {
   //SPI.transfer(c);
   r=transferAndWait(c);
   respBuffer[pos++]=r;
   Serial.print(c);
   Serial.print(r);
 }
 
 //respBuffer[pos]=0;
 Serial.println("RX:");
 Serial.println(pos,DEC);
 Serial.println(respBuffer);
 pos=0;
   
 delay(100);
 
 // Disable slave select
 digitalWrite(SS_PIN, HIGH);
 
 // 1 second delay
 delay(100);
   
}

It looks like it's working okay now - was following mix of examples/had remnants of old code

When I changed the slave side with the following (and removed the previous lines which are covered by SPI.begin()) things seem to be working now:

#include "pins_arduino.h"

...

pinMode(MISO, OUTPUT);

Nick, that is an outstanding link, thanks very much!

I am confused about something though. On the slave Arduino, could I use any pin I liked as slave select, or must it be the SS pin identified in the data sheet (and called pin 10 on an Uno)? Reading your instruction about using an interrupt pin to tell the slave to start receiving, you seems to imply that this is possible. But, I didn't see any code that told the uC that a different SS pin was being used. Does that mean that the uC does not care, and its just up to the slave code in the sketch to start the receiving process when whatever SS pin is asserted?

Also, you said the interrupt version could be used with any interrupt pin. Is there any reason that a pin change interrupt could not be used?

Thank you!

skyjumper: Nick, that is an outstanding link, thanks very much!

Thanks!

I am confused about something though. On the slave Arduino, could I use any pin I liked as slave select, or must it be the SS pin identified in the data sheet (and called pin 10 on an Uno)?

This is a hardware function, so you must use the hardware SS pin, as documented in the datasheet page 167:

When configured as a Slave, the SPI interface will remain sleeping with MISO tri-stated as long as the /SS pin is driven high.

Reading your instruction about using an interrupt pin to tell the slave to start receiving, you seems to imply that this is possible. But, I didn't see any code that told the uC that a different SS pin was being used. Does that mean that the uC does not care, and its just up to the slave code in the sketch to start the receiving process when whatever SS pin is asserted?

I used the words "you would physically connect SS to one of the interrupt inputs", suggesting you would connect pin 10 to pin 2, for the interrupt detection.

Also, you said the interrupt version could be used with any interrupt pin. Is there any reason that a pin change interrupt could not be used?

Quite possible a pin change interrupt on pin 10 would have the same effect, then you don't need two pins, nor the wire.

Right, but where? Pin 10 on the slave to pin 2 on the master? I don’t understand how the interrupt pin works with the SS pin.

On the slave, jumper pins 10 and 2. Pin 10 is SS, and pin 2 which you can use in attachInterrupt will let you know when SS line goes low.

However as I said above, you could probably (I haven't tested it) do the same thing with a pin change interrupt on pin 10.

[quote author=Nick Gammon link=topic=120049.msg911666#msg911666 date=1346570793] On the slave, jumper pins 10 and 2. Pin 10 is SS, and pin 2 which you can use in attachInterrupt will let you know when SS line goes low. [/quote]

Oh, now I get it. Thanks!

However as I said above, you could probably (I haven't tested it) do the same thing with a pin change interrupt on pin 10.

I'll give it a try at some point and post back.

Thanks Nick!

I used sketches attributed mainly to Nick Gammon (with a special THANKS to him for his contributions here!) to work on two Arduino Mega’s using IDE 1.5.5. It worked, with problems. I tried the correction listed in the last part of Reply #7 by Nick Gammon above, but because I do not understand where the correction should go it is not included. I have added the sample output where it is obvious that the “Hello world“ lines print more than once on a single line on the monitor and some letters come out wrong.

The master program I used is:

/* SPI Master Send - Hello World (derived from Nick Gammon's samples)

Copied and modified from Reply #6 by Rofarley:  http://forum.arduino.cc/index.php?topic=120049.0
Arduino Mega is acting as an SPI Master for 
a corresponding Arduino Mega board

Circuit:
 SS_PIN: pin 53
 MISO: pin 50
 MOSI: pin 51
 SCK: pin 52

*/

#include <SPI.h>

long int count = 1;
byte config = B01010000;
#define SCK_PIN  52
#define MISO_PIN 50
#define MOSI_PIN 51
#define SS_PIN   53
uint8_t buffer[128];


void setup()
{
  digitalWrite(SS_PIN, HIGH);  // Ensure SS stays high for now
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  Serial.begin(115200);
}

void loop()
{
 char c;

 // enable Slave Select  
 digitalWrite(SS_PIN, LOW);
   
 // Send test string
 for (const char *p="Hello, world!\n";c=*p;p++)
 {
   SPI.transfer(c);
   Serial.print(c);
 }


 //delay(100);
 
 // Disable slave select
 digitalWrite(SS_PIN, HIGH);
 
 // 0.1 second delay
 delay(100);
   
}

The Slave program I used is:

// Written by Nick Gammon
// February 2011
/**
 * Send arbitrary number of bits at whatever clock rate (tested at 500 KHZ and 500 HZ).
 * This script will capture the SPI bytes, when a '\n' is recieved it will then output
 * the captured byte stream via the serial.
 */
 
#include <SPI.h>
 
char buf [100];
volatile byte pos;
volatile boolean process_it;
 
void setup (void)
{
  Serial.begin (115200);   // debugging
 
  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  
  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;
 
  // now turn on interrupts
  SPI.attachInterrupt();
 
}  // end of setup
 
 
// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register
  
  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    
    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;
      
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect
 
// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set
    
}  // end of loop

The output irregularities occur irregularly and fairly frequently. A sample of these is depicted below:

Hello, world!

Hello, world!

Hello, world!Hello, world!

Hello, world!

Hello, world!

Hello, world!

Hello, wo&ÆBHello, world!

Hello, world!

Hello, world!Hello, world!

Hello, worBHello, world!

Hello, world!

Hello, world!

Question 1: Could anyone please sprinkle some wisdom on these sketches to make them print correctly?

The Master program compiles properly on the Arduino Due, but the slave program simply does not compile.

Question 2: What changes to the Slave program should I make to make the Slave sketch work with the DUE?

Thanks for giving this attention!

The master may be sending too fast. SPI is much faster than serial comms.

I don’t know about compiling for the Due, it is a completely different chip and I haven’t attempted to compile the SPI stuff for it.

Nick, Thanks for trying to help!

I tried lower transmission speeds between the two Mega's down to SPI.setClockDivider(SPI_CLOCK_DIV128); the result was the same. Then I tried faster transmission speeds, the fasted that worked (with the same problems) was SPI.setClockDivider(SPI_CLOCK_DIV4).

Then I tried to transmit 75 characters. That worked a few times with similar problems and then the receiver simply stopped working.

Have you heard if anyone has similar SPI problems for Arduino to Arduino communications (I could not find other examples)? Given the pedigree of the original programs, could it perhaps be possible that there are problems with the Mega SPI library?

Hi,

I am trying to establish SPI communication between 2 Arduino Uno's and also between the Master Uno and a Micro Sd card module (which again uses SPI). When connecting the Uno's via SPI pins, I'm able to print commands from Master to the Slave on the Slave's Hyperterminal. I'm also able to store pictures onto the SD card module from the Master Uno independently.

The problem arises when I try to combine the SD card and Slave Uno with the Master Uno. Nothing seems to get stored in the SD card. At the same time, if I try to glow some LEDs on the Slave Uno on commands from the Master, the LEDs don't glow. The commands show on the Hyperterminal alright though..

I've used two different slave select lines (SS for slave uno and CS=4 for SD card).

Any sort of help would be very much appreciated! Thanks in advance!