Getting SPI Slave Mode to Work

Getting a Photon to transmit data in SPI SLAVE mode has its obstacles. Seems like the Photon likes master mode much more…

This is how to do it.

In setup(), initialize the SPI with your data (CS line, SPI mode):

    // initialize SPI:
    SPI.begin(SPI_MODE_SLAVE, A2); 
    SPI.setDataMode(SPI_MODE3);

Now, the problem with slave mode is that you do not know when the master retrieves the data from your controller. First, you have to start a transmission. But it will not transmit right away. The data will be sitting in the output buffer and is only transmitted when the master starts the transmission. Thus, you have to implement a software-internal handshake.

I did this with variable ‘has_transmitted’ that is cleared when I have written something into the output and is set when a callback function was called. The callback function is activated, when SPI transfer has finished.

In loop(), transfer your data, when the previous transmission has finished:

if (has_transmitted)  {

        has_transmitted = false;        // indicate that this transmission has not yet started
        // transfer ONE byte, when the master sends us one (remember, we are slave):
        SPI.transfer(tx_buffer, rx_buffer, 1, spi_transfer_finished);

The callback function is set in the call to SPI.transfer() and is called spi_transfer_finished():

// is called when the master has read the data that is stored
// in the SPI
void spi_transfer_finished(void) {
    has_transmitted = true;
    digitalWrite(A0, LOW);
}

Now here are the caveats:

  1. The photon needs at least 2µs time to transmit data after the CS has gone low! This is a lot of time. So your master needs to pull CS low, wait for three or four microseconds and only then start the transmission.
  2. The photon needs a lot of time to call the callback function after the transmission. I measured more then 7µs.

Hope this helps some people with this mode. It toke me very long to figure this out.

Here’s the full program:

volatile bool has_transmitted = true;

void setup() {

    Serial.begin(9600);

    // initialize SPI:
    SPI.begin(SPI_MODE_SLAVE, A2); 
    SPI.setDataMode(SPI_MODE3);

    // init debug port:
    pinMode(A0, OUTPUT);
    digitalWrite(A0, LOW);   
}

// is called when the master has read the data that is stored
// in the SPI
void spi_transfer_finished(void) {

    has_transmitted = true;
    digitalWrite(A0, LOW);             // debug port pin
}

uint8_t rx_buffer[] = "pipapo pipapo pipapo ";
uint8_t tx_buffer[] = "abc";

// main loop
// is called only once every millisecond!
void loop() {

    char buffer[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

    if (has_transmitted) {
        has_transmitted = false;        // indicate that this transmission has not yet started
        counter = 0;
        digitalWrite(A0, HIGH);           // debug port pin
        
        // transfer ONE byte, when the master sends us one (remember, we are slave):
        SPI.transfer(tx_buffer, rx_buffer, 1, spi_transfer_finished);
    }
    
}
2 Likes

Good job, Interesting. Do you have a specific use case for using the Photon as a Slave SPI device? Normally the MCU is driving peripherals such as SD controller and displays, etc. rather than the other way around.

It’s two controllers talking to each other. The other one is a motor controller and needs to be hard real-time. So it is the master, determining the timing. And the Photon is the slave.

Does this work on the Xenon? I’m getting a SOS-10 running device OS 1.1.0 running similar code.

Slave mode is only supported on SPI1 on mesh devices.