Arduino 101/Genuino 101 SPI Slave mode

Hi folks,

I had need to get SPI slave mode working on the Arduino 101 with the Intel Curie SOC. Results from forum searches and general googling around kicked up a few aborted attempts, and a couple of posts saying that it was not possible, and if SPI slave was a must-have, then you needed another Arduino type. Let me get this out of the way - this "guidance" was wrong. And it kinda annoyed me. If you're not 100% intimate with the hardware, please don't go around making blanket comments like this. These comments live forever online, and cause a great disservice to anyone attempting to get something working.

I had another Arduino - the Yun - which can not support external SPI slave operation (not strictly correct, the AR9331 puts the ATmega into SPI slave to reprogram the micro controllers prom, but since the SS is not brought out to a header pin, you can't do this externally. Can this be changed in rev. 3 of the Yun BTW?), so I was motivated to fully understand the 101's SPI subsystem.

I'd like to report that you can indeed put the 101 into SPI slave. I am attaching a proof of concept (POC) sketch that does so. The trick was to just to get the correct register configuration:

a) reading the schematics showed that the SPI slave pins and one of the SPI master's are both brought out to the standard SPI digital pins (10-13).
b) Correctly reprogram the pin multiplexers with the right functions for the slave and master controllers.
c) Configure the master pins as all inputs and configure the slave pins appropriately (i.e MISO as output, MOSI, SS and SCK as inputs).

void setup() {

  noInterrupts();
  
  // SPI1 and SPI_S share pins, so SPI1 (master) must be disabled via enabling as GPIO
  SET_PIN_MODE(42, GPIO_MUX_MODE);      // SPI1_MISO
  SET_PIN_MODE(43, GPIO_MUX_MODE);      // SPI1_MOSI
  SET_PIN_MODE(44, GPIO_MUX_MODE);      // SPI1_SCK
  SET_PIN_MODE(45, GPIO_MUX_MODE);      // SPI0_CS_B[0]
  SET_PIN_MODE(46, GPIO_MUX_MODE);      // SPI0_CS_B[1]
  SET_PIN_MODE(47, GPIO_MUX_MODE);      // SPI0_CS_B[2]
  SET_PIN_MODE(48, GPIO_MUX_MODE);      // SPI0_CS_B[3]
  
  SET_MUX_IN_EN(42, true);
  SET_MUX_IN_EN(43, true);
  SET_MUX_IN_EN(44, true);
  SET_MUX_IN_EN(45, true);
  
  // set  GPIO 0,1,2,3 for FUNCTION_2 (SPI slave)
  SET_PIN_MODE(2, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S SCK  
  SET_PIN_MODE(1, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S MISO
  SET_PIN_MODE(3, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S MOSI 
  SET_PIN_MODE(0, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S CS 
  SET_MUX_IN_EN(2, true);
  SET_MUX_IN_EN(1, false);
  SET_MUX_IN_EN(3, true);
  SET_MUX_IN_EN(0, true);

  // disable clocks for SPI1
  CLEAR_MMIO_BIT(PERIPH_CLK_GATE_CTRL, 15); 
  // CLEAR_MMIO_BIT(CREG, CREG_CLK_CTRL_SPI1);
  // CLEAR_MMIO_BIT(SPI1.SSIENR, 0); // FIX THIS

  // configure SPI slave
  SPI_S_REG_VAL(SPIEN) &= ~SPI_ENABLE; // disable
  SET_MMIO_BIT(PERIPH_CLK_GATE_CTRL, 16); // enable clock for SPI slave (bit 16)
  SPI_S_REG_VAL(CTRL0) = ((SPI_8_BIT << 16) | (0x00 << 6)); // 8 bit frame size; SPI MODE 0
  SPI_S_REG_VAL(IMR) = SPI_DISABLE_INT; // no interrupts
  SPI_S_REG_VAL(SPIEN) |= SPI_ENABLE; // enable

  interrupts();

  Serial.begin(115200);
  while(!Serial);
  Serial.write("Configured!\n");

  SPI_S_REG_VAL(DR) = 0xD8; // write some sample data to the tx buffer
}

The standard SPI library provides functions for SPI master operation, not SPI slave, however it provides aa huge number of very useful #defines, which I've liberally used. After configuration, the POC simply uses a polling mechanism to read the RxFIFO and rewrite the same data back to the Master. I'll work on integrating an interrupt driven Rx and Tx as the next phase, but having the configuration documented here should help other poor lost souls. Note, the attached sketch is based on some other user submitted sample code - a particular user who couldn't get it running, and I've just added the correct configuration.

One more thing to note - the Arduino 101 maximum SCK frequency when operating as a slave is 3.2MHz. Be aware of this when you set your clock divisor on your master.

I know it's not going to cure cancer or solve world hunger, but hopefully anyone in need of SPI slave operation with the 101 will stumble across this post, and at least know it is possible, and inch closer to their design goals.

A101-slave-v1.ino (3.01 KB)

Here is the code for SPI slave interrupt driven receive. For this test sketch I have a 16 byte array(samples[16]) which I simply send back to the Master. Note that the RxFIFO threshold defaults to 0, so the RxFIFO FULL interrupt is asserted when the FIFO is not empty.

void ISR () {
  while (SPI_S_REG_VAL(0x24) > 0) {
   rxByte = SPI_S_REG_VAL(DR);
   SPI_S_REG_VAL(DR) = samples[sampleIndex];
   sampleIndex = ++sampleIndex % 16;
 }
}

void setup() {

 noInterrupts();
 
 // SPI1 and SPI_S share pins, so SPI1 (master) must be disabled via enabling as GPIO
 SET_PIN_MODE(42, GPIO_MUX_MODE);      // SPI1_MISO
 SET_PIN_MODE(43, GPIO_MUX_MODE);      // SPI1_MOSI
 SET_PIN_MODE(44, GPIO_MUX_MODE);      // SPI1_SCK
 SET_PIN_MODE(45, GPIO_MUX_MODE);      // SPI0_CS_B[0]
 SET_PIN_MODE(46, GPIO_MUX_MODE);      // SPI0_CS_B[1]
 SET_PIN_MODE(47, GPIO_MUX_MODE);      // SPI0_CS_B[2]
 SET_PIN_MODE(48, GPIO_MUX_MODE);      // SPI0_CS_B[3]
 
 SET_MUX_IN_EN(42, true);
 SET_MUX_IN_EN(43, true);
 SET_MUX_IN_EN(44, true);
 SET_MUX_IN_EN(45, true);
 
 // set  GPIO 0,1,2,3 for FUNCTION_2 (SPI slave)
 SET_PIN_MODE(2, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S SCK  
 SET_PIN_MODE(1, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S MISO
 SET_PIN_MODE(3, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S MOSI 
 SET_PIN_MODE(0, QRK_PMUX_SEL_MODEC);  // SPI MODE (Fn 2) for SPI0_S CS 
 SET_MUX_IN_EN(2, true);
 SET_MUX_IN_EN(1, false);
 SET_MUX_IN_EN(3, true);
 SET_MUX_IN_EN(0, true);

 // disable clocks for SPI1
 CLEAR_MMIO_BIT(PERIPH_CLK_GATE_CTRL, 15); 
 // CLEAR_MMIO_BIT(CREG, CREG_CLK_CTRL_SPI1);
 // CLEAR_MMIO_BIT(SPI1.SSIENR, 0); // FIX THIS

 // configure SPI slave
 SPI_S_REG_VAL(SPIEN) &= ~SPI_ENABLE; // disable
 SET_MMIO_BIT(PERIPH_CLK_GATE_CTRL, 16); // enable clock for SPI slave (bit 16)
 SPI_S_REG_VAL(CTRL0) = ((SPI_8_BIT << 16) | (0x00 << 6)); // 8 bit frame size; SPI MODE 0
 SPI_S_REG_VAL(IMR) = 0x10; // Enable RxFIFO FULL interrupt
 SPI_S_REG_VAL(SPIEN) |= SPI_ENABLE; // enable

 attachInterrupt(31, ISR, RISING);

 interrupts();

 Serial.begin(115200);
 while(!Serial);
 Serial.write("Configured!\n");
 for (int i=0; i< 16; i++) {
   Serial.write("S");
   Serial.write(i);
   Serial.write(" = ");
   Serial.println(samples[i], HEX);
 }

I hope this helps get people started.

FYI -- The A101-slave-v1.ino sketch is complete.
However the other 2 code snippets aren't.

They're missing headers and the top chunk of code from the A101-slave-v1.ino code

Additionally, the 2nd snippet doesn't declare the samples array.
And some other chunks, i.e. int SET_MUX_IN_EN(const uint32_t pin, const bool enable) function missing.

Just thought A) you might want to update these...

and B) by posting this comment might help others to get things sorted out.
(using the A101-slave-v1.ino code you can sort out most of the issues fairly easy)