Built-in project · Arduino Uno R3

Two Arduino Unos as an SPI master and slave

One Uno clocks bytes out over SS, MOSI, MISO and SCK and the other answers in the same eight clocks, with both sides of SPI mode 0 written out in the sketches. The whole build, an Arduino Uno, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Advanced Runs in your browser. Free, and no account needed.

Two Arduino Unos as an SPI master and slave · Arduino Uno R3live0.000 s 0.00x
Click to open it in the editor
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Uno A, the SPI master. It clocks one byte at a time out to Uno B and
// reads one byte back in the same eight clocks: SPI mode 0, MSB first.
//
//   A pin 10 (SS)    ->  B pin 10   low while a byte is moving
//   A pin 11 (MOSI)  ->  B pin 11   master out, slave in
//   A pin 12 (MISO)  <-  B pin 12   master in, slave out
//   A pin 13 (SCK)   ->  B pin 13   the clock (the L LED flickers with it)
//   A GND            --  B GND
//
// Both sides are written out by hand rather than handed to the SPI library,
// so every edge of the protocol is in the code you are reading. The clock is
// slow (5 kHz) so the slave, which is a plain loop, never misses an edge.

const byte SS_PIN = 10;
const byte MOSI_PIN = 11;
const byte MISO_PIN = 12;
const byte SCK_PIN = 13;
const unsigned int HALF_US = 100;  // half a clock period

byte transfer(byte out) {
  byte in = 0;
  for (int bit = 7; bit >= 0; bit--) {
    digitalWrite(MOSI_PIN, (out >> bit) & 1);  // set the data while SCK is low
    delayMicroseconds(HALF_US);
    digitalWrite(SCK_PIN, HIGH);               // both sides sample on the rise
    in = (in << 1) | digitalRead(MISO_PIN);
    delayMicroseconds(HALF_US);
    digitalWrite(SCK_PIN, LOW);
  }
  return in;
}

byte count = 0;

void setup() {
  Serial.begin(9600);
  pinMode(SS_PIN, OUTPUT);
  pinMode(MOSI_PIN, OUTPUT);
  pinMode(SCK_PIN, OUTPUT);
  pinMode(MISO_PIN, INPUT);
  digitalWrite(SS_PIN, HIGH);
  digitalWrite(SCK_PIN, LOW);
  Serial.println("SPI master: one byte every 400 ms");
}

void loop() {
  count++;
  digitalWrite(SS_PIN, LOW);
  delayMicroseconds(300);  // give the slave time to see SS and load its byte
  byte answer = transfer(count);
  digitalWrite(SS_PIN, HIGH);

  Serial.print("master sent ");
  Serial.print(count);
  Serial.print(", slave answered ");
  Serial.println(answer);
  delay(400);
}
sketch.ino · Arduino Uno R3
// Uno B, the SPI slave. While SS is low it reads MOSI on every rising
// clock edge and puts its own answer on MISO, one bit before each rise.
// Its answer is twice the byte it received last time, so the master can
// see that the bytes really went both ways.

const byte SS_PIN = 10;
const byte MOSI_PIN = 11;
const byte MISO_PIN = 12;
const byte SCK_PIN = 13;

byte answer = 0;

void setup() {
  Serial.begin(9600);
  pinMode(SS_PIN, INPUT);
  pinMode(MOSI_PIN, INPUT);
  pinMode(SCK_PIN, INPUT);
  pinMode(MISO_PIN, INPUT);  // let go of MISO until we are selected
  Serial.println("SPI slave: waiting for SS");
}

void loop() {
  if (digitalRead(SS_PIN) == HIGH) return;

  pinMode(MISO_PIN, OUTPUT);
  byte in = 0;
  for (int bit = 7; bit >= 0; bit--) {
    digitalWrite(MISO_PIN, (answer >> bit) & 1);  // ready before the rise
    while (digitalRead(SCK_PIN) == LOW) {
      if (digitalRead(SS_PIN) == HIGH) return;    // the master gave up
    }
    in = (in << 1) | digitalRead(MOSI_PIN);
    while (digitalRead(SCK_PIN) == HIGH) {
    }
  }
  while (digitalRead(SS_PIN) == LOW) {
  }
  pinMode(MISO_PIN, INPUT);

  Serial.print("slave got ");
  Serial.println(in);
  answer = in * 2;
}

Parts list

2 parts, plus the jumper wires. Every one is in the editor's parts bin.

How it is wired

5 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Ground: Arduino Uno R3 (1) pin GND; Arduino Uno R3 (2) pin GND
  • Arduino Uno R3 (1) pin 13; Arduino Uno R3 (2) pin 13
  • Arduino Uno R3 (1) pin 12; Arduino Uno R3 (2) pin 12
  • Arduino Uno R3 (1) pin 11; Arduino Uno R3 (2) pin 11
  • Arduino Uno R3 (1) pin 10; Arduino Uno R3 (2) pin 10

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.