If you’ve been working with Arduino for a while, you’ve likely hit a point where a single sensor or display isn’t enough. When it’s time to connect multiple peripherals — displays, SD cards, sensor modules, shift registers — the Arduino SPI tutorial becomes essential knowledge. SPI (Serial Peripheral Interface) is one of the fastest and most versatile communication protocols available on Arduino, capable of moving data at several MHz while supporting multiple devices on a shared bus. This guide walks you through everything from the fundamentals to practical multi-device wiring, with real code examples you can deploy today.
- What Is SPI and How Does It Work?
- SPI Pins on Arduino Boards
- SPI Modes: Clock Polarity and Phase
- Wiring Multiple SPI Devices
- Daisy-Chaining vs Independent CS Lines
- Using the Arduino SPI Library
- Practical Example: TFT Display + SD Card
- Troubleshooting Common SPI Issues
- Frequently Asked Questions
What Is SPI and How Does It Work?
SPI stands for Serial Peripheral Interface. It was introduced by Motorola in the mid-1980s and has since become a staple in embedded systems. Unlike I2C which uses a two-wire open-drain bus, SPI uses a four-wire full-duplex architecture that allows simultaneous send and receive at high clock speeds.
The four core signals are:
- MOSI (Master Out Slave In): Data line from Arduino (master) to the peripheral (slave).
- MISO (Master In Slave Out): Data line from the peripheral back to Arduino.
- SCK (Serial Clock): Clock signal generated by the master. All data is synchronised to this clock.
- CS/SS (Chip Select / Slave Select): Active-low signal that tells a specific peripheral it’s being addressed. Each device needs its own CS line.
The master drives the clock and controls CS lines. It pulls a device’s CS low to start a transaction, clocks bits in and out simultaneously on MOSI/MISO, then releases CS high to end it. No addressing bytes are needed — the CS line handles device selection at the hardware level, which is why SPI is typically faster than I2C for burst transfers.
Typical SPI clock speeds with Arduino range from 100 kHz (for beginners testing) up to 8 MHz or more, depending on the peripheral’s datasheet limits and your wiring quality.
SPI Pins on Arduino Boards
The physical SPI pins differ across Arduino boards. Here’s a quick reference:
| Board | MOSI | MISO | SCK | SS (default) |
|---|---|---|---|---|
| Uno / Nano | D11 | D12 | D13 | D10 |
| Mega 2560 | D51 | D50 | D52 | D53 |
| Leonardo | ICSP-4 | ICSP-1 | ICSP-3 | D10 |
| Nano Every | D11 | D12 | D13 | D10 |
On the Uno and most AVR-based boards, the hardware SPI pins are shared with the ICSP header. The Leonardo and Mega expose SPI only through the ICSP header, so shield compatibility differs. Always check your board’s pinout diagram before wiring.
SPI Modes: Clock Polarity and Phase
Every SPI peripheral expects a specific relationship between the clock signal and when data is sampled. This is defined by two parameters:
- CPOL (Clock Polarity): The idle state of the clock. CPOL=0 means the clock idles low; CPOL=1 means it idles high.
- CPHA (Clock Phase): When data is sampled. CPHA=0 samples on the leading edge; CPHA=1 samples on the trailing edge.
Combined, these give four modes:
| Mode | CPOL | CPHA | Common Use |
|---|---|---|---|
| 0 | 0 | 0 | Most TFT displays, SD cards |
| 1 | 0 | 1 | Some ADCs |
| 2 | 1 | 0 | Rare |
| 3 | 1 | 1 | Some shift registers, sensors |
Always check the peripheral’s datasheet to identify its required mode. Mismatched modes are one of the most common causes of corrupted SPI data. In the Arduino SPI library, you set the mode via SPISettings:
SPISettings mySettings(4000000, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(mySettings);
// ... transfer bytes
SPI.endTransaction();
Wiring Multiple SPI Devices
The beauty of SPI is that MOSI, MISO, and SCK are shared by all devices — only the CS lines differ per device. Here’s a standard multi-device wiring plan:
Arduino Pin 11 (MOSI) ──┬── Device A MOSI
└── Device B MOSI
Arduino Pin 12 (MISO) ──┬── Device A MISO
└── Device B MISO
Arduino Pin 13 (SCK) ──┬── Device A SCK
└── Device B SCK
Arduino Pin 10 ──── Device A CS
Arduino Pin 9 ──── Device B CS
Key wiring rules:
- MISO needs special care: All slaves share the MISO line. Only the selected slave (CS low) should drive MISO. Most SPI devices automatically tri-state their MISO when CS is high. Verify this in the datasheet — a few older devices don’t, and they’ll fight each other on the shared line.
- CS lines are independent: Use any available digital output pin. Keep only one CS low at a time.
- Pull-up resistors on CS: Add 10kΩ pull-ups from each CS line to VCC. This ensures slaves don’t activate during power-up before the Arduino initialises.
- Decoupling capacitors: Place 100nF ceramic capacitors between VCC and GND close to each device’s power pins. High-speed SPI switching creates transients that can corrupt data.
Daisy-Chaining vs Independent CS Lines
There are two topologies for multi-device SPI.
Independent CS (Star Topology)
Each device has its own CS pin connected directly to the master. This is the most common and flexible approach. You can communicate with any device at any time, and each device’s SPI settings (speed, mode) can differ. The only constraint is the number of available digital pins for CS lines.
Daisy-Chain Topology
In a daisy-chain, all devices share a single CS line, and the MISO of one device connects to the MOSI of the next. Data flows through all devices in a shift-register fashion. The master sends N×8 bits for N devices, and each device consumes its byte as the data chain propagates.
Daisy-chaining is used for specific device families (74HC595 shift registers, some ADCs). Most modern breakout modules do not support daisy-chain natively. Stick with the independent CS approach unless a device’s datasheet explicitly describes a daisy-chain mode.
For Arduino Uno users with limited pins, consider using a 74HC138 1-of-8 decoder to drive up to 8 CS lines with just 3 Arduino pins.
Using the Arduino SPI Library
Arduino’s built-in SPI.h library abstracts the hardware SPI peripheral. Here’s the standard transaction pattern for multiple devices:
#include <SPI.h>
#define CS_DISPLAY 10
#define CS_SDCARD 9
void setup() {
SPI.begin();
pinMode(CS_DISPLAY, OUTPUT);
pinMode(CS_SDCARD, OUTPUT);
// Both CS high = both deselected
digitalWrite(CS_DISPLAY, HIGH);
digitalWrite(CS_SDCARD, HIGH);
}
void sendToDisplay(uint8_t cmd, uint8_t data) {
SPISettings dispSettings(4000000, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(dispSettings);
digitalWrite(CS_DISPLAY, LOW);
SPI.transfer(cmd);
SPI.transfer(data);
digitalWrite(CS_DISPLAY, HIGH);
SPI.endTransaction();
}
uint8_t readFromSD(uint8_t reg) {
SPISettings sdSettings(250000, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(sdSettings);
digitalWrite(CS_SDCARD, LOW);
SPI.transfer(reg);
uint8_t result = SPI.transfer(0x00); // dummy byte to clock in data
digitalWrite(CS_SDCARD, HIGH);
SPI.endTransaction();
return result;
}
Important points about SPISettings: always call SPI.beginTransaction() before asserting CS, and SPI.endTransaction() after releasing CS. This pattern is interrupt-safe and allows different devices to run at different speeds and modes on the same bus without reconfiguring SPI manually between calls.
Avoid using SPI.setClockDivider(), SPI.setDataMode(), and SPI.setBitOrder() — these are deprecated and not thread-safe with interrupts.
Practical Example: TFT Display + SD Card
One of the most common real-world SPI multi-device scenarios is a TFT display with an SD card reader. Both use SPI Mode 0, but the display typically runs at 4–8 MHz while SD cards initialise at 250 kHz and then switch to up to 25 MHz.
Here’s a complete sketch framework:
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SD.h>
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 9
#define SD_CS 4
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
void setup() {
Serial.begin(9600);
// Ensure SD CS is high before init
pinMode(SD_CS, OUTPUT);
digitalWrite(SD_CS, HIGH);
// Init TFT first
tft.initR(INITR_BLACKTAB);
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(1);
// Init SD
if (!SD.begin(SD_CS)) {
tft.println("SD init failed!");
return;
}
// Read a file from SD and display contents
File f = SD.open("data.txt");
tft.setCursor(0, 0);
while (f.available()) {
tft.print((char)f.read());
}
f.close();
}
void loop() {}
Notice that SD_CS is explicitly set HIGH before TFT initialisation. This prevents the uninitialised SD card from interfering with the display’s SPI setup bytes. This sequencing matters — it’s a common source of mysterious initialisation failures when combining SPI devices.
Troubleshooting Common SPI Issues
SPI problems usually fall into a handful of categories:
No response from device
- Wrong SPI mode — check CPOL/CPHA in the datasheet
- CS not pulled low before transfer, or released too early
- MISO/MOSI swapped in wiring
- Device needs a power cycle after initialisation sequence
Corrupted or partial data
- SPI speed too high — reduce to 1 MHz and test, increase gradually
- Wires too long (>10cm) without proper shielding or series resistors
- Missing decoupling capacitors causing supply noise
- Another CS line left low accidentally (another device driving MISO)
Works alone but fails with other devices
- A device that doesn’t tri-state MISO when CS is high — check datasheet, may need a buffer or separate bus
- Conflicting SPI modes between devices (one device’s MOSI receiving another’s transaction)
- Insufficient current on 3.3V rail when multiple peripherals active — add local decoupling or a dedicated regulator
Intermittent failures under load
- Not using
SPI.beginTransaction()/endTransaction()— interrupts can corrupt mid-transaction - Mixing deprecated SPI configuration calls with the transaction API
- Level shifting issues — Arduino Uno is 5V; many sensors are 3.3V logic. Use a level shifter.
Frequently Asked Questions
How many SPI devices can an Arduino handle?
Theoretically, as many as you have digital pins for CS lines. An Arduino Uno has 14 digital pins; reserving D11, D12, D13 for SPI and D10 for default SS leaves 10 pins for CS lines — that’s 10 devices. In practice, MISO bus conflicts and power supply limits usually become the constraint before you run out of pins.
Can SPI and I2C be used together on the same Arduino?
Yes. SPI uses the hardware SPI peripheral (pins 11/12/13 on Uno) and I2C uses the TWI peripheral (pins A4/A5 on Uno). These are completely independent peripherals and can operate simultaneously in an interrupt-driven design. Many real-world projects use both — for example, a fast SPI display plus I2C sensors.
Is software SPI slower than hardware SPI?
Significantly. Hardware SPI on a 16 MHz Arduino Uno can reach ~8 MHz. Software (bit-bang) SPI typically peaks at ~500 kHz due to loop overhead. However, software SPI lets you use any pins — useful when hardware SPI pins are occupied. Libraries like Adafruit_SPIDevice support software SPI transparently.
What is the maximum SPI cable length?
For reliable operation, keep SPI connections under 20–30 cm on a breadboard or PCB. For longer runs, reduce the clock speed significantly (to 100–500 kHz) and use twisted-pair wiring with a ground wire alongside each signal. For runs over 1 metre, consider a differential signalling solution instead.
Do all Arduino-compatible boards support SPI?
All ATmega-based boards (Uno, Nano, Mega, Leonardo) have hardware SPI. The Arduino Nano 33 IoT (SAMD21) and Nano RP2040 Connect also have hardware SPI, though pin numbers differ. Always verify the specific board’s pinout before wiring.
Ready to dive into SPI projects? Explore our full range of Arduino boards and compatible shields at Zbotic — from the classic Uno to the feature-packed Mega, we stock everything you need to build multi-device SPI systems with confidence.
Add comment