Serial Peripheral Interface (SPI) is the definitive choice for high-speed, short-distance, board-to-board communication. When connecting an Arduino master to an Arduino slave, SPI delivers full-duplex data transfer at speeds up to 8MHz on classic 16MHz AVR boards—vastly outperforming I2C and UART. However, because SPI lacks the built-in error checking and software addressing of other protocols, success relies entirely on correct physical wiring and precise clock configuration.
SPI Bus Mechanics and Physical Layer Requirements
Unlike I2C, which uses a shared bus with software addressing, SPI relies on dedicated hardware lines. The master controls the clock and initiates all transfers, while data shifts in and out simultaneously (full-duplex).
| Parameter | SPI Specification (AVR/Arduino) | Practical Limit |
|---|---|---|
| Wires Required | 4 shared (MOSI, MISO, SCK) + 1 SS per slave | Pin count scales linearly with slaves |
| Speed (Baud) | Up to F_CPU / 2 (8MHz on 16MHz Uno) | 4MHz (F_CPU / 4) is safest for long wires |
| Addressing | None (Hardware Slave Select / Chip Select) | Requires 1 extra master pin per slave |
| Distance | Under 1 meter (approx. 3 feet) | Signal integrity degrades rapidly past 50cm |
| Duplex | Full-Duplex (Simultaneous TX/RX) | Shift registers swap data every clock edge |
Physical Wiring Map (Uno/Nano to Uno/Nano)
You can wire SPI using the standard digital pins or the ICSP (In-Circuit Serial Programming) header. The ICSP header is generally preferred because the pinout is identical across the Uno, Nano, and Mega, whereas digital pins shift on larger boards.
| Signal | Master (Uno/Nano) Digital Pin | Master ICSP Header | Slave (Uno/Nano) Digital Pin |
|---|---|---|---|
| MOSI (Master Out, Slave In) | Pin 11 | Pin 4 | Pin 11 |
| MISO (Master In, Slave Out) | Pin 12 | Pin 1 | Pin 12 |
| SCK (Serial Clock) | Pin 13 | Pin 3 | Pin 13 |
| SS (Slave Select) | Pin 10 (or any GPIO) | N/A | Pin 10 (Hardware SS) |
| GND | GND | Pin 6 | GND |
Note: The slave's hardware SS pin (Pin 10 on Uno/Nano) must be configured as an OUTPUT or kept HIGH to prevent the ATmega328P from automatically switching itself into SPI Master mode if the line drops low.
Minimal Working Master-Slave Exchange
The standard Arduino SPI.h library is designed exclusively for Master mode. To configure an AVR-based Arduino (Uno/Nano) as an SPI slave, you must manipulate the SPI Control Register (SPCR) directly and use an Interrupt Service Routine (ISR).
Master Code
#include <SPI.h>
const int ssPin = 10;
void setup() {
Serial.begin(115200);
pinMode(ssPin, OUTPUT);
digitalWrite(ssPin, HIGH); // Deselect slave initially
SPI.begin();
// Set clock to 2MHz (16MHz / 8) for reliable breadboard wiring
SPI.setClockDivider(SPI_CLOCK_DIV8);
SPI.setDataMode(SPI_MODE0); // CPOL=0, CPHA=0
}
void loop() {
digitalWrite(ssPin, LOW); // Assert slave
// SPI is full duplex: sending 0x42 simultaneously receives a byte
byte received = SPI.transfer(0x42);
digitalWrite(ssPin, HIGH); // Deassert slave
Serial.print("Received from slave: 0x");
Serial.println(received, HEX);
delay(500);
}Slave Code (AVR Register Level)
volatile byte rxData = 0;
volatile boolean rxFlag = false;
void setup() {
Serial.begin(115200);
// MISO must be OUTPUT for SPI slave
pinMode(MISO, OUTPUT);
pinMode(10, INPUT_PULLUP); // Hardware SS with internal pull-up
// Enable SPI (SPE) and Enable SPI Interrupt (SPIE)
SPCR |= (1<<SPE) | (1<<SPIE);
}
// SPI Serial Transfer Complete Interrupt
ISR(SPI_STC_vect) {
rxData = SPDR; // Read received byte
rxFlag = true;
SPDR = 0x99; // Pre-load response byte for the NEXT master transfer
}
void loop() {
if (rxFlag) {
rxFlag = false;
Serial.print("Master sent: 0x");
Serial.println(rxData, HEX);
}
}Classic Failures, Protocol Comparisons, and Bus Sniffing
Beginners migrating from I2C often look for the classic I2C failures: address clashes and missing pull-ups on SDA/SCL. SPI does not use software addressing, and its push-pull data lines do not require pull-ups. However, SPI has its own trifecta of classic failures:
- Floating SS Lines (The SPI 'Pull-Up' Equivalent): As noted above, a floating SS line causes phantom interrupts. Always use
INPUT_PULLUPon the slave's SS pin or add an external 10kΩ resistor. - Clock Polarity/Phase Mismatch: SPI defines four modes based on CPOL (Clock Polarity) and CPHA (Clock Phase). If the master uses
SPI_MODE0and the slave expectsSPI_MODE3, the shift register will clock data on the wrong edge, resulting in shifted or inverted garbage bytes. - Baud Mismatch / ISR Overrun: If the master clocks data at 8MHz, the slave's MCU has exactly 2 microseconds to execute the
SPI_STC_vectISR, read theSPDRregister, and write the next byte. If your ISR contains heavy logic (likeSerial.print), the next byte will overwrite the unread register, triggering a Write Collision (WCOL) flag and dropping data.
How to Sniff and Debug the SPI Bus
When SPI.transfer() returns 0xFF or 0x00 consistently, your physical layer is failing. Do not guess; sniff the bus. A $12 24MHz USB logic analyzer (Saleae clone) running PulseView/sigrok is mandatory for SPI debugging.
- Hook the analyzer to SCK, MOSI, MISO, and SS.
- Set the trigger to the falling edge of the SS line.
- Verify the clock frequency matches your
SPI.setClockDivider()setting. - Check the MOSI and MISO traces: MOSI should change state on the master's clock edge, and MISO should change on the opposite edge. If MISO stays flat, your slave's MISO pin is not configured as an OUTPUT, or the slave is held in reset.
Protocol Selection: When to Use SPI vs I2C vs UART
Choosing the right protocol depends on your distance, speed, and topology constraints.
| Criteria | SPI | I2C | UART (Serial) |
|---|---|---|---|
| Best For | High-speed, short-distance board-to-board | Multiple sensors on a shared 2-wire bus | Long-distance, point-to-point, PC comms |
| Max Speed (Arduino) | 8 MHz | 400 kHz (Fast Mode) | 2 Mbps (Hardware Serial) |
| Wiring Complexity | High (4 wires + 1 per slave) | Low (2 shared wires) | Low (2 wires, TX/RX) |
| Duplex | Full | Half | Full (with 4 wires) |
| Max Distance | < 1 meter | ~1 meter (without buffers) | 15+ meters (RS-485) |
For deeper electrical characteristics and timing diagrams of the SPI bus, refer to the SparkFun SPI Tutorial and the official Arduino SPI Language Reference.
Arduino to Arduino SPI FAQ
Can I connect multiple Arduino slaves to one master over SPI?
Yes, but it requires a star topology. The MOSI, MISO, and SCK lines are shared across all slaves in parallel. However, every slave must have its own dedicated Slave Select (SS) wire routed back to a unique GPIO pin on the master. To talk to Slave B, the master pulls Slave B's SS LOW while keeping Slave A's SS HIGH. Because MISO is tri-stated (high-impedance) when a slave's SS is HIGH, only the selected slave will drive the MISO line, preventing bus contention.
How do I debug Arduino SPI communication with a logic analyzer?
Connect your logic analyzer's ground to the Arduino ground, and clip the four channels to SCK, MOSI, MISO, and the active SS line. In your analyzer software (like PulseView), add the SPI decoder. Set the decoder's CPOL and CPHA settings to match your Arduino code (usually 0 and 0 for Mode 0). Set the SS channel as the 'Chip Select' trigger. This will decode the raw hex bytes and immediately reveal if the master is clocking too fast or if the slave is failing to respond on MISO.
What is the maximum reliable distance for Arduino to Arduino SPI?
Standard SPI is designed for on-board communication, typically under 30cm. On a breadboard with short jumper wires, you can reliably push 8MHz. If you need to run SPI over 50cm to 1 meter, you must drop the clock speed to 1MHz or lower, use twisted-pair wiring (pairing SCK with GND, and MOSI/MISO with GND), and ensure the slave's input capacitance isn't rounding off the clock edges. For distances beyond 1 meter, abandon SPI and use RS-485 differential UART.






