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.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// 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);
}
// 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.
- 2 × Arduino Uno R3
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.