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).

ParameterSPI Specification (AVR/Arduino)Practical Limit
Wires Required4 shared (MOSI, MISO, SCK) + 1 SS per slavePin 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
AddressingNone (Hardware Slave Select / Chip Select)Requires 1 extra master pin per slave
DistanceUnder 1 meter (approx. 3 feet)Signal integrity degrades rapidly past 50cm
DuplexFull-Duplex (Simultaneous TX/RX)Shift registers swap data every clock edge
Physical Layer Pull-Up Requirement: SPI data lines (MOSI, MISO, SCK) are push-pull and do not require pull-up resistors. However, the Slave Select (SS) line on the slave board must have a 10kΩ pull-up resistor to VCC. If the master reboots and its SS pin floats, the slave will interpret the noise as a chip-select signal, triggering phantom interrupts and corrupting the shift register.

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.

SignalMaster (Uno/Nano) Digital PinMaster ICSP HeaderSlave (Uno/Nano) Digital Pin
MOSI (Master Out, Slave In)Pin 11Pin 4Pin 11
MISO (Master In, Slave Out)Pin 12Pin 1Pin 12
SCK (Serial Clock)Pin 13Pin 3Pin 13
SS (Slave Select)Pin 10 (or any GPIO)N/APin 10 (Hardware SS)
GNDGNDPin 6GND

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:

  1. Floating SS Lines (The SPI 'Pull-Up' Equivalent): As noted above, a floating SS line causes phantom interrupts. Always use INPUT_PULLUP on the slave's SS pin or add an external 10kΩ resistor.
  2. Clock Polarity/Phase Mismatch: SPI defines four modes based on CPOL (Clock Polarity) and CPHA (Clock Phase). If the master uses SPI_MODE0 and the slave expects SPI_MODE3, the shift register will clock data on the wrong edge, resulting in shifted or inverted garbage bytes.
  3. Baud Mismatch / ISR Overrun: If the master clocks data at 8MHz, the slave's MCU has exactly 2 microseconds to execute the SPI_STC_vect ISR, read the SPDR register, and write the next byte. If your ISR contains heavy logic (like Serial.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.

CriteriaSPII2CUART (Serial)
Best ForHigh-speed, short-distance board-to-boardMultiple sensors on a shared 2-wire busLong-distance, point-to-point, PC comms
Max Speed (Arduino)8 MHz400 kHz (Fast Mode)2 Mbps (Hardware Serial)
Wiring ComplexityHigh (4 wires + 1 per slave)Low (2 shared wires)Low (2 wires, TX/RX)
DuplexFullHalfFull (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.