Arduino as SPI slave

Hi Gents, Can I just check something with you, testing tonight I noticed that when the SPI connection is plugged into the Arduino the LED marked L is flashing very faintly. Looking at the schematic this looks to be the SCK working would you agree? if so is this normal? doe sthe SCK continually send a pulse? or does thsi idicate the SPI is pumping data out were just not seeing it?

nathanBomberHarris:
... does the SCK continually send a pulse?

No it doesn't. See my logical analyzer data here:

However, it is important to note that SPI does not wait for any acknowledgement. So if you are the slave, and you are not processing the data, that's your bad luck. The master will keep sending it.

nathanBomberHarris:
... or does this indicate the SPI is pumping data out were just not seeing it?

Sounds like it is doing just that.

Sounds like either you have not configured your end as a slave, you haven't pulled SS low, or your interrupt service routine is not firing for some reason (like interrupts disabled).

Hello Nick/Rob

This is odd after studying the ARDUINO 2009 sckematic I copied the 1K resistor and LED L off the SCK PIN and did the same on my breadboard for PIN 11 boths pins flash away nicely!

I take it that this means SPI data must be traversing the connections but still no output on the seriasl monitor window????

Help! I feel its nearly there what I am I missing (PIN 10 to gnd)

:frowning:

Can we see the sketch for the receiving end?

Sure //note code added as suggested by Rob I tried with and without :slight_smile: thank you for helping

// Written by Nick Gammon
// February 2011
#include "pins_arduino.h"
char buf [100];
volatile byte pos;
volatile boolean process_it;
void setup (void)
{
Serial.begin (9600); // 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
SPCR |= _BV(SPIE);
} // 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;
for (int i = 0; i < 100; i++)// Code added on suggestion Rob
{
Serial.println (buf[0],HEX); // Code added on suggestion Rob
}
pos = 0;
process_it = false;
} // end of flag set

} // end of loop

It helps to use the [ code ] tags (hit the # button above the post).

Anyway, you won't see anything if process_it is not true, and that hinges on getting a newline. Does the device send one?

For debugging you could change it to something like this to see if the buffer is filling up:

void loop (void)
{

  // debugging  
  if (pos > 20)
  {
   for (int i = 0; i < pos; i++)
      {
      Serial.println (buf[i], HEX); 
      }
      pos = 0;
  }
  
   
}  // end of loop

Nick this is GREAT as I've just discovered my none-interrupt version can't keep up with 2 bytes of transfer - it needs a gap between bytes..

So - this is GREAT but forgive my slowness- how can I SEND data from the slave - so let's say that first byte comes in under interrupts and while receiving it - I want to return some info with the second incoming byte (as SPI is bidirectional) ????

Pete

Any ideas?

hmm, another question - you're assuming constant data coming in ending in say CR.. how would one END it when the SS line goes back up...

My slave needs to accept 2 bytes - passing a byte of info back on the second incoming one - then it is done - SS line goes back up and that's when i need to know about it..

??

Have a function called something like spiBusy() that returns the state of the SS line , also the ISR can look at a parameter that states how many bytes to accept before setting the string ready flag (Instead of the /r/n) or whatever.

Or

Have the ISR manage a count that states how many bytes have been received and all your main loop does is wait for the right number to be received.

None of the above however deals with the zero time between bytes (IE, no empty clock cycles)

I am looking into that now

So there is no hardware flow control available to the slave. The master can use clock delay tactics to help but again, this is not the slave saying "Wait... i'm busy getting your info" so in general it would be a bad idea to tie any significant processing within the messaging loop. I guess SPI is not really geared for this type of communications, I2C has the mechanics to delay responses but not SPI.

So making the slave simply send back a status of the inverse of the sent message or something may be the best you can hope for beyond a certain speed unless the messaging it broken up into separate packets.

  1. Send Command
  2. Check Status for completion

if reading several bytes is needed then a send routine that focuses on the already prepared byte stream would be the way to go.

Many SPI devices have all this implemented in hardware and are tuned to respond within the clock cycles (SAR ADCs for instance)

The example code above where a message is sent using a /r/n as a terminator is probably not the best example of how to do an interactive SPI communications, this is probably why it is Master to Slave only. the received string is sent out the serial port once the whole message is in the buffer.

nickgammon:
That's a good question. I was wondering that too. A little research and I have an example.

Hardware: Connect two Arduinos together with the following pins connected:

10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK)
Also, +5v and GND

Master (the one that sends the data) - upload this test sketch:

#include <SPI.h>

#include "pins_arduino.h"

void setup (void)
{
  // 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
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV8);
 
}  // end of setup

void loop (void)
{

char c;

// enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

// send test string
  for (const char * p = "Hello, world!\n" ; c = *p; p++)
    SPI.transfer (c);

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

delay (1000);  // 1 seconds delay
}  // end of loop






Slave (the one that receives the data) - upload this test sketch:




#include "pins_arduino.h"

char buf [100];
volatile byte pos;
volatile boolean process_it;

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

// 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);
 
  pos = 0;
  process_it = false;
}  // end of setup

// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;
 
  // 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
}

// 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




Then fire up the serial monitor on the slave and watch "Hello, world" scroll by.

Points to note:


- The master divides the clock down a bit, otherwise it pushes out data too fast for the slave to process
- The slave is interrupt-driven. Thus it can be doing other stuff until incoming data arrives.

Hello,
I am trying to implement this using the Metro M4 Express. When I try to compile the slave code, it errors out at the slave interrupt routine. If I comment that out, then it errors out due to SPCR not being defined. I'm kinda new at this, would someone be able to help me find a work around?

hy all.....
i try to reading data from Mcu,
that is note 3 wire : sck, din,stb

that is SPI mode?and how acan read data on that.

thanks,

gunaone:
hy all.....
i try to reading data from Mcu,
that is note 3 wire : sck, din,stb

that is SPI mode?and how acan read data on that.

There are few SPI examples (Section-7.3) in the pdf of this link.

thanks so much @GolamMostofa

i was try it, and i get data :

163,0,83,0,0,3,0,0,

actualy i read data 7segment from Mcu to driver 7segment GN6932 loadscale

when i put 0.030Kg i get data :

163,32,83,0,32,3,253,221,

any idea?

Hi !

Thanks a lot for the code provided here.
I was able to set up the Arduino Uno and Arduino Mega as SPI slaves. However, I am unable to
do the same with a Arduino Nano Every. Here is the code I used:

#include "pins_arduino.h"

#define  MAX_BUFFER_SIZE 1024

/* Configuration Flags */

/*
   Core functions
*/


/* Globals */
bool spiMessageToBePrinted = false;
uint8_t buf [100];
volatile byte pos = 1;
volatile boolean process_it = false;
volatile byte interruptCounter = 0;
volatile byte messageSize = 0;
bool isStringMessage = true;

void setup() {
  Serial.begin(250000); // opens and configures the USB serial port for baudrate 115200
  Serial.println("Arduino: SPI Communication setup");
  // 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);
}

void loop() {
  
  interruptCounter = 0;
  if (process_it) { 
    if(isStringMessage){
      Serial.print((char *)buf);
    }
    else {
      Serial.println("SPI: packet received");
      buf [pos] = 0;
      Serial.println("SPI: Received data:");
      for(int i=0; i < messageSize ; i++) {
        Serial.println(buf[i],DEC);
      }  
    }
    process_it = false;
    pos = 0;   
  }  // end of flag set
}

// SPI interrupt routine
ISR (SPI_STC_vect) {
  byte c = SPDR;
  // Echo Reply
  SPDR = c;
  //Serial.println((char)c);
  //interruptCounter ++;
  // add to buffer if room
  if (pos < sizeof(buf)) {
     buf [pos++] = c;
     if ((char)buf[pos-1] == '\n' and (char)buf[pos-2] == '\r' and process_it == false) {
       process_it = true;
       buf[pos] = '\0';
       messageSize = pos;
       pos = 0;
     }  
  }
}  // end of room available

I get the following error:
'SPCR' was not declared in this scope

I assume the Nano Every has some other architecture. Now I am wondering how to set it up as a SPI slave. I am also not sure which pin is slave select now for a slave. I just assumed it is 10, like on the Uno.
Thanks a lot in advance for any ideas how to fix this!

UPDATE: I am having a look at the datasheet of the used microprocessor:

However, I am not that familiar with low level programming. I tried to replace SPCR by CTRLA/SPIn.CRTLA, but that just yield the same error..

Okay, so I am trying to figure out the low level programming required myself,
but I think I am missing something.
I used these Arduino Nano Every schematics to map the arduino pins to the chip pinshttps://github.com/MicrochipTech/TB3215_Getting_Started_with_SPI/blob/master/ATmega4809_SPI_Examples/Receiving_Data_as_Slave/main.c

I then used following guide: https://github.com/MicrochipTech/TB3215_Getting_Started_with_SPI/blob/master/ATmega4809_SPI_Examples/Receiving_Data_as_Slave/main.c
which is based on some example document.

The default code did not work so I made some tweaks and came up with this:

#include "pins_arduino.h"

#define  MAX_BUFFER_SIZE 1024

/* Configuration Flags */

/*
   Core functions
*/


/* Globals */
bool spiMessageToBePrinted = false;
uint8_t buf [100];
volatile byte pos = 1;
volatile boolean process_it = false;
volatile byte interruptCounter = 0;
volatile byte messageSize = 0;
bool isStringMessage = true;

void setup() {
  Serial.begin(250000); // opens and configures the USB serial port for baudrate 115200
  Serial.println("Arduino: SPI Communication setup");
  // have to send on master in, *slave out*
  // pinMode(MISO, OUTPUT);

  PORTE.DIR &= ~PIN0_bm; /* Set MOSI pin direction to input */
  PORTE.DIR |= PIN1_bm; /* Set MISO pin direction to output */
  PORTE.DIR &= ~PIN2_bm; /* Set SCK pin direction to input */
  PORTB.DIR &= ~PIN1_bm; /* Set SS pin direction to input */
    
  // turn on SPI in slave mode
  SPI0.CTRLA = SPI_DORD_bm        /* LSB is transmitted first */
               | SPI_ENABLE_bm      /* Enable module */
               & (~SPI_MASTER_bm);     /* SPI module in Slave mode */

  // turn on interrupts
   SPI0.INTCTRL = SPI_IE_bm; /* SPI Interrupt enable */
}

void loop() {
  
  interruptCounter = 0;
  if (process_it) { 
    if(isStringMessage){
      Serial.print((char *)buf);
    }
    else {
      Serial.println("SPI: packet received");
      buf [pos] = 0;
      Serial.println("SPI: Received data:");
      for(int i=0; i < messageSize ; i++) {
        Serial.println(buf[i],DEC);
      }  
    }
    process_it = false;
    pos = 0;   
  }  // end of flag set
}

// SPI interrupt routine
// NEW CODE!
ISR(SPI0_INT_vect) {
   byte c = SPI0.DATA;
  // Echo Reply
  SPI0.DATA = c;
  //Serial.println((char)c);
  //interruptCounter ++;
  // add to buffer if room
  if (pos < sizeof(buf)) {
     buf [pos++] = c;
     if ((char)buf[pos-1] == '\n' and (char)buf[pos-2] == '\r' and process_it == false) {
       process_it = true;
       buf[pos] = '\0';
       messageSize = pos;
       pos = 0;
     }  
  }
// NEW CODE!
SPI0.INTFLAGS = SPI_IF_bm; /* Clear the Interrupt flag by writing 1 */
}  // end of room available

Unfortunately, it still does not work. I will do some more testing..

Okay, so checked again that my master device works by switching the slave select line to an UNO and the master sender should definitely work..

Alright, it is working with an Arduino Uno master after some experimentation.
I will leave the code here in case anyone else wants to use the Nano Every as a SPI slave:

Uno Master Code (2 Slave Arduinos connected, Nano Every Slave Select pin ( 8 ) was connected with
pin 9). It is important to write "\r\n" at the end of the string, this is the string terminator
used by the slaves to print out received messages:

/**
 * @file  SPI_Master.ino
 * 
 * @date  10.04.2020
 */
#include <SPI.h>
 
void setup (void) {
   Serial.begin(115200); //set baud rate to 115200 for usart
   pinMode(9, OUTPUT);
   digitalWrite(SS, HIGH); // disable Slave Select
   digitalWrite(9, HIGH); // disable Slave Select 2
   SPI.begin();
   SPI.setClockDivider(SPI_CLOCK_DIV8);  
}
 
void loop (void) {
   char c;
   digitalWrite(SS, LOW); // enable Slave Select
   // send test string
   for (const char * p = "Hello, world!\r\n" ; c = *p; p++) {
      SPI.transfer(c);
      Serial.print(c);
   }
   digitalWrite(SS, HIGH); // disable Slave Select
   digitalWrite(9, LOW);
   for (const char * p = "Hello, world!\r\n" ; c = *p; p++) {
      SPI.transfer(c);
      Serial.print(c);
   }
   digitalWrite(9, HIGH);
   delay(2000);
}

Nano Every Slave Code:

/*
  File:
  SPI_Echo_String_Slave_Nano_Every.ino
  Date:
  23.11.2019
  Author:
  R. Mueller
  Description:
  Sources:
  http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega4808-4809-Data-Sheet-DS40002173A.pdf
  https://github.com/arduino/ArduinoCore-megaavr/issues/67
*/

#include <Arduino.h>
#include <SPI.h>
#include "TemplateHeader.h"

/* Globals */
bool spiMessageToBePrinted = false;
uint8_t readBuffer [100];
bool isStringMessage = true;

volatile bool controlFlag;
volatile byte pos = 1;
volatile boolean process_it = false;
volatile byte messageSize = 0;

/* This needs to be put before setup or in separate header file */
template <typename T> T serialPrintBinary(T x, bool usePrefix = true)
{
  if(usePrefix) Serial.print("0b");
  for(int i = 0; i < 8 * sizeof(x); i++)
  {
    Serial.print(bitRead(x, sizeof(x) * 8 - i - 1));
  }
  Serial.println();
  return x;
}

void setup() {
  Serial.begin(115200); // opens and configures the USB serial port for baudrate 115200
  Serial.println("SPI Slave Communication setup");
  /* This does some magic which is necessary on the Nano Every */
  SPI.begin();
  /* Enable SPI, put it in Slave mode, set LSB first */
  SPI0.CTRLA = (~SPI_DORD_bm & (SPI_ENABLE_bm & (~SPI_MASTER_bm)));  
  /* disable "Slave Select Disable" --> to ensure it will stay slave */
  SPI0.CTRLB &= ~(SPI_SSD_bm); 
  /* Set Mode 0 (is default though) */
  SPI0.CTRLB |= (SPI_MODE_0_gc); 
  SPI0.INTCTRL = SPI_IE_bm;        /* SPI Interrupt enable */
  
  Serial.println("SPI registers on ATmega4809 were set: ");
  Serial.print("SPI0_CTRLA: ");
  serialPrintBinary<byte>(SPI0_CTRLA);
  Serial.print("SPI0_CTRLB: ");
  serialPrintBinary<byte>(SPI0_CTRLB);
  Serial.print("SPI0_INTCTRL: ");
  serialPrintBinary<byte>(SPI0_INTCTRL);
  Serial.print("SPI0_INTFLAGS: ");
  serialPrintBinary<byte>(SPI0_INTFLAGS);
  Serial.print("SPI0_DATA: ");
  serialPrintBinary<byte>(SPI0_DATA);

  pinMode(MISO, OUTPUT);
  pinMode(SCK, INPUT);
  pinMode(MOSI, INPUT);
  pinMode(SS, INPUT); 
}

void loop() {
  if (process_it) { 
    if(isStringMessage){
      Serial.print((char *)readBuffer);
    }
    else {
      Serial.println("SPI: packet received");
      readBuffer [pos] = 0;
      Serial.println("SPI: Received data:");
      for(int i=0; i < messageSize ; i++) {
        Serial.println(readBuffer[i],DEC);
      }  
    }
    process_it = false;
    pos = 0;   
  }  // end of flag set
}

// SPI interrupt routine
ISR(SPI0_INT_vect) {
  controlFlag = true;
  byte c = SPI0.DATA;
  /* Echo Reply */
  SPI0.DATA = c;
  //Serial.println((char)c);
  //interruptCounter ++;
  // add to readBufferf if room
  if (pos < sizeof(readBuffer)) {
     readBuffer [pos++] = c;
     if ((char)readBuffer[pos-1] == '\n' and (char)readBuffer[pos-2] == '\r' and process_it == false) {
       process_it = true;
       readBuffer[pos] = '\0';
       messageSize = pos;
       pos = 0;
     }  
  }
  SPI0.INTFLAGS = SPI_IF_bm; /* Clear the Interrupt flag by writing 1 */
}

I've been trying to connect an ESP32 as a slave and I keep getting these errors that I don't know how to fix. I know what the errors mean (for the most part) but I'm not familiar enough with SPI programming to troubleshoot on my own.

Slave:43:5: error: expected constructor, destructor, or type conversion before '(' token
ISR (SPI_STC_vect) //Inerrrput routine function
^
C:\Users\dfost\OneDrive\Documents\Arduino\Slave\Slave.ino: In function 'void setup()':
Slave:36:3: error: 'SPCR' was not declared in this scope
SPCR |= _BV(SPE); //Turn on SPI in Slave Mode
^
In file included from C:\Users\dfost\AppData\Local\Temp\arduino_build_633825\sketch\Slave.ino.cpp:1:0:
Slave:36:15: error: 'SPE' was not declared in this scope
SPCR |= _BV(SPE); //Turn on SPI in Slave Mode
^
C:\Users\dfost\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\cores\esp32/Arduino.h:99:25: note: in definition of macro '_BV'
#define _BV(b) (1UL << (b))
^
Slave:39:7: error: 'class SPIClass' has no member named 'usingInterrupt'
SPI.usingInterrupt(ISR); //Interuupt ON is set for SPI commnucation
^
Slave:39:22: error: 'ISR' was not declared in this scope
SPI.usingInterrupt(ISR); //Interuupt ON is set for SPI commnucation
^
C:\Users\dfost\OneDrive\Documents\Arduino\Slave\Slave.ino: At global scope:
Slave:43:5: error: expected constructor, destructor, or type conversion before '(' token
ISR (SPI_STC_vect) //Inerrrput routine function
^
C:\Users\dfost\OneDrive\Documents\Arduino\Slave\Slave.ino: In function 'void loop()':
Slave:104:3: error: 'SPDR' was not declared in this scope
SPDR = Slavesend; //Sends the x value to master via SPDR
^
expected constructor, destructor, or type conversion before '(' token

1 Like

Can you also make an example how to use the interrupt function of the SPI slave ?

For example, the Arduino is doing something on it's own (display text on a display when a button is pressed), and skips out of this button/display loop when the SPI interrupt is activated, and then show the SPI data on the display.

Almost no experience with Arduino, not sure how to do this...
Thank you