SPI Slave mode (example code)

No doubt this has been done before, but I didn't immediately find example code. So, here's what I came up with when I needed to read some SPI data coming from an external Master-mode SPI device. I had to read a set of two bytes as an unsigned short, which were framed by the SS/ line (Slave_Select). I found out that you cannot reliably read the SS line (Digital_10) as a digital input line while the SPI module is active, at least with the settings I used. So I turn SPI on, read two bytes, turn SPI off again immediately, and wait for the next SS falling edge. That way I ensure correct byte synchronization (in my case).

// ====================================================================
// Arduino code example for SPI Slave Mode
// Read unsigned short (two bytes) from SPI, send word to serial port
// On 16 MHz Arduino, can work at > 500 words per second
// J.Beale July 19 2011
// ====================================================================

#define SCK_PIN   13  // D13 = pin19 = PortB.5
#define MISO_PIN  12  // D12 = pin18 = PortB.4
#define MOSI_PIN  11  // D11 = pin17 = PortB.3
#define SS_PIN    10  // D10 = pin16 = PortB.2

#define UL unsigned long
#define US unsigned short

void SlaveInit(void) {
  // Set MISO output, all others input
  pinMode(SCK_PIN, INPUT);
  pinMode(MOSI_PIN, INPUT);
  pinMode(MISO_PIN, OUTPUT);  // (only if bidirectional mode needed)
  pinMode(SS_PIN, INPUT);

  /*  Setup SPI control register SPCR
  SPIE - Enables the SPI interrupt when 1
  SPE - Enables the SPI when 1
  DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0
  MSTR - Sets the Arduino in master mode when 1, slave mode when 0
  CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0
  CPHA - Samples data on the trailing edge of the data clock when 1, leading edge when 0
  SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz)   */
  
  // enable SPI subsystem and set correct SPI mode
  // SPCR = (1<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); 
}

// SPI status register: SPSR
// SPI data register: SPDR

// ================================================================
// read in short as two bytes, with high-order byte coming in first
// ================================================================
unsigned short Read2Bytes(void) {
    union {
    unsigned short svar;
    byte c[2];
  } w;        // allow access to 2-byte word, or separate bytes
  
  while(!(SPSR & (1<<SPIF))) ; // SPIF bit set when 8 bits received
  w.c[1] = SPDR;               // store high-order byte
  while(!(SPSR & (1<<SPIF))) ; // SPIF bit set when 8 bits received
  w.c[0] = SPDR;               // store low-order byte
  return (w.svar); // send back unsigned short value
}

void setup() {
  Serial.begin(115200);
  SlaveInit();  // set up SPI slave mode
  delay(10);
  Serial.println("SPI port reader v0.1"); 
}

// ============================================================
// main loop: read in short word (2 bytes) from external SPI master
// and send value out via serial port
// On 16 MHz Arduino, works at > 500 words per second
// ============================================================
void loop() {
  unsigned short word1;
  byte flag1;

     // SS_PIN = Digital_10 = ATmega328 Pin 16 =  PORTB.2
    // Note: digitalRead() takes 4.1 microseconds
    // NOTE: SS_PIN cannot be properly read this way while SPI module is active!
    while (digitalRead(SS_PIN)==1) {} // wait until SlaveSelect goes low (active)
    
    SPCR = (1<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); // SPI on
    word1 = Read2Bytes();          // read unsigned short value
    SPCR = (0<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0);  // SPI off
  
//    float seconds = millis()/1000.0;  // time stamp takes more serial time, of course
//    Serial.print(seconds,3);   
//    Serial.print(",");
    Serial.print(word1);
    Serial.println();

}  // end loop()

Does the master insert a small delay between the bytes? I've always found this to be required to give the slave time to grab the last byte (at high speeds anyway).

I did a version once that IIRC transferred 8 bytes in 100uS.


Rob

Hi

I am working on SPI Slave in arduino, I used your code as reference.
Master device is generating SCLK=750KHz ,SS=10KHz
After every 3-5 samples ,junk data is captured and this repeats .

can you tell me what the issue may be ? or suggestion as to how i can correct this

Help is appreciated
Thank you

Hi, I'm using your code and I have a problem.
I need to read 4 bytes try to modify it but I can not make it work.
Helppp
Thanks

@OP

Does SPI Protocol require for the SPI Slave to sample its SS/-pin in order to grasp right data at the right time, which is coming from SPI Master? The following discussion and an example will provide the answer.

1. This is the wiring connection between SPI Master UNO and SPI Slave NANO.
spi328z.png
Figure-1: SPI Master-Slave connection between UNO and NANO

2. When we include the following two lines at the appropriate place of the Master sketch, the events that take place are:

#include<SPI.h>
SPI.begin();

(1) DPin-10 takes over the symbolic name SS (Slave Select) and gets configures as output line with value logic-high.

This SS line is connected with the SS/-pin (if there is any) of the Slave NANO. To select the Slave, the Master must assert logic-low (LL) on the SS/-pin. The user may drive the SS/-pin of the Slave to logic-low state by using any other digital pin of the Master.

(2) DPin-11 takes over the symbolic name MOSI (Master Out Slave In) and gets configures as output line with value logic-low.

(3) DPin-12 takes over the symbolic name MISO (Master In Slave Out) and gets configures as input line.

(4) DPin-13 takes over the symbolic name SCK (Serial Clock) configures as output line with value logic-low (Mode-1 operation: CPOL = LOW, CPHA = FALLING).

(5) The default data transfer speed is 4 MBits/sec. The speed can be changed using this code: SPI.setClockDivider();.

3. In the SPI Slave sketch, we include the SPI.h Library; but, we don't include the SPI.begin() instruction. The inclusion of the SPI.h Library helps to use use the following symbolic names and function of the Library:

(1) SS: Slave Select which does refer to DPin-10 of the Slave. To enable the 'SPI Port' of the Slave, we must configure its direction as input so that the SPI Master can assert logic-low signal on this pin to select (enable Slave's SPI Port) the Slave for data communication.

(2) MOSI: Master Out Slave In which does refer to DPin-11 of the Slave. We must set its direction as input so that data coming from Master can enter into the SPDR Register of the Slave.

(3) MISO: Master In Slave Out which does refer to DPin-12 of the Slave. We must set its direction as output so that data leaving the Slave can enter into the SPDR Register of the Master.

(4) SPI.attachInterrupt(): This function/method enables the interrupt logic of the SPI Port of the Slave. The result is this: whenever a data bytes enters into the SPDR register of the Slave, the Slave is automatically interrupted; it goes to interrupt handler (the ISR) and spends very short time there either to collect data from the SPDR Register or to set a flag to enable the MCU to collect data in the loop() function.

(5) SCK: Serial Clock which does refer to DPin-13 of the Slave. We must set its direction as input so that the serial clock signal generated by Master can simultaneously 'clock out' data from its SPDR Register and 'clock in' the same data into SPDR Register of the Slave. This is reason for saying that SPI stands for 'Synchronous Serial Peripheral Interface'.

4. Example (SPI Master sends data item 0x1234 to SPI Slave)
(1) SPI Master UNO Codes (tested)

#include<SPI.h>
unsigned short x = 0x1234;

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV128); //bit rate = 16 MHz/128 = 125 kbit/sec
  digitalWrite(SS, LOW);   //Slave is selected
}

void loop()
{
  SPI.transfer(highByte(x));
  SPI.transfer(lowByte(x));
  //-----------------------
  delay(3000);  //test interval
}

(2) SPI Slave Codes (tested)

#include<SPI.h>
volatile int i = 0;
byte myArray[2];

void setup()
{
  Serial.begin(9600);
  pinMode(SS, INPUT_PULLUP);
  pinMode(MOSI, OUTPUT);
  pinMode(SCK, INPUT);
  SPCR |= _BV(SPE);
  SPI.attachInterrupt();  //allows SPI interrupt
}

void loop(void)
{
  if (i == 2)
  {
    int x = (int)myArray[0]<<8|(int)myArray[1];
    Serial.print("Received 16-bit data item from Master: ");
    Serial.println(x, HEX);
    i=0;
    Serial.println("=============================================");
  }
}

ISR (SPI_STC_vect)   //Inerrrput routine function
{
  myArray[i] = SPDR;
  i++;
}

(3) The Screenshot
Sm117.png

spi328z.png

Sm117.png

1 Like

GolamMostafa thanks for your post. Your slave code saved me a lot of hair.....