If you are wiring a high-speed sensor, an SD card module, or a TFT display, you need the Serial Peripheral Interface (SPI). The direct answer for standard AVR boards: Arduino Uno and Nano SPI pins are 11 (MOSI), 12 (MISO), 13 (SCK), and 10 (SS/CS). On the Mega, they shift to 51, 50, 52, and 53. However, hardcoding these digital pin numbers is a trap for beginners; for board-agnostic hardware design, you should always route your traces to the 2x3 ICSP header, which maintains identical SPI pinouts across almost all 5V Arduino architectures.
Unlike I2C, which relies on software addressing and open-drain pull-ups, SPI is a push-pull, hardware-routed bus. Getting the physical layer wrong will result in silent failures or fried 3.3V peripherals. Here is the definitive bench guide to wiring, coding, and debugging the SPI bus.
The Physical Layer: Arduino SPI Pinouts and Bus Mechanics
SPI is a synchronous, full-duplex communication protocol. It uses a master-slave (or controller-peripheral) architecture where the controller generates the clock signal and dictates the data flow. Before you strip a single wire, you need to understand the bus mechanics and how they map to your specific microcontroller.
SPI Bus Mechanics and Limits
| Parameter | SPI Specification | Practical Bench Reality |
|---|---|---|
| Wires Required | 4 shared (SCK, MOSI, MISO) + 1 CS per device | Wire count scales linearly with device count due to individual Chip Select lines. |
| Bus Speed | Up to 50MHz+ (silicon dependent) | AVR Arduinos max out at 8MHz (F_CPU/2). ESP32 can reliably push 20-40MHz on short traces. |
| Addressing | None (Hardware routing via CS) | No software address clashes, but you will run out of GPIO pins for CS lines on complex boards. |
| Max Distance | Not formally specified | < 1 meter. High clock speeds suffer from capacitive loading and crosstalk on long ribbon cables. |
| Topology | Multi-drop (shared MISO/MOSI/SCK) | All peripherals share the data/clock lines; only the CS line is unique per target. |
Microcontroller SPI Pin Mapping
While you can use the SPI.h library to abstract the pin names, you must wire the physical silicon correctly. Note that the ESP32 features multiple hardware SPI buses; the table below reflects the default VSPI bus used by the Arduino IDE.
| Board Variant | MOSI (COPI) | MISO (CIPO) | SCK (SCLK) | Default SS (CS) | ICSP Header Mapping |
|---|---|---|---|---|---|
| Uno / Nano (ATmega328P) | 11 | 12 | 13 | 10 | MOSI=4, MISO=1, SCK=3 |
| Mega 2560 | 51 | 50 | 52 | 53 | MOSI=4, MISO=1, SCK=3 |
| ESP32 DevKit (VSPI) | 23 | 19 | 18 | 5 | No standard ICSP header |
| Raspberry Pi Pico (RP2040) | 19 (SPI0) / 3 (SPI1) | 16 (SPI0) / 0 (SPI1) | 18 (SPI0) / 2 (SPI1) | User defined | No standard ICSP header |
Wiring the Bus: Chip Select, Level Shifting, and Signal Integrity
The most common mistake makers make when transitioning from I2C to SPI is applying I2C wiring rules to an SPI bus. Let's clear up the physical layer requirements.
The Pull-Up Resistor Myth (and Reality)
SPI data lines (MOSI, MISO, SCK) are push-pull, not open-drain. They are actively driven high and low by the microcontroller and the peripheral. You do not need pull-up resistors on the data or clock lines. Adding them will only increase rise/fall times and limit your maximum clock speed.
While data lines don't need pull-ups, your Chip Select (CS/SS) line absolutely does. When an Arduino boots, its GPIO pins float before the
setup() function initializes them as outputs. If a floating CS line drifts low, your SPI peripheral will wake up and attempt to drive the MISO line, potentially colliding with an SD card or another sensor on the bus. Always place a 10kΩ pull-up resistor between the CS line and VCC on every SPI peripheral.
Level Shifting: 5V vs 3.3V Logic
Most modern high-speed SPI sensors (like the BME280 or ADXL345) and SD cards operate at 3.3V. Feeding 5V from an Arduino Uno into the MISO or SCK pins of a 3.3V device will degrade the silicon and eventually destroy the peripheral.
- For SD Cards and Low-Speed Sensors: Use a CD4050 non-inverting buffer IC. It is cheap, unidirectional, and handles the 5V-to-3.3V step-down cleanly.
- For High-Speed Displays and Bidirectional Lines: Use a BSS138 MOSFET-based bidirectional level shifter module. Ensure you pull up the LV (Low Voltage) side to 3.3V and the HV (High Voltage) side to 5V.
Minimal Working Exchange: Reading an ADXL345 Accelerometer
Let's put the theory into practice. We will wire an ADXL345 3-axis accelerometer and read its DEVID register. Reading the Device ID is the ultimate 'smoke test' for SPI; if you get the expected hex value back, your physical wiring, clock phase, and chip select are all functioning correctly.
Wiring Table
| ADXL345 Pin | Arduino Uno Pin | Notes |
|---|---|---|
| VCC | 3.3V | Do not use 5V on the ADXL345 VCC pin. |
| GND | GND | Ensure a common ground plane. |
| CS | 10 | Add a 10kΩ pull-up to 3.3V. |
| SCL (SCK) | 13 | Serial Clock. |
| SDA (MOSI) | 11 | Controller Out, Peripheral In. |
| SDO (MISO) | 12 | Peripheral Out, Controller In. |
The Code: Safe SPI Transactions
Never use the legacy SPI.transfer() without wrapping it in a transaction. Modern Arduino environments use SPI.beginTransaction() to lock the bus settings, preventing conflicts if multiple libraries (like an SD card and a display) try to alter the clock speed simultaneously.
#include <SPI.h>
const int CS_PIN = 10;
const byte READ_DEVID = 0x80; // Bit 7 high for read, 0x00 is DEVID address
const byte EXPECTED_ID = 0xE5;
// ADXL345 requires SPI Mode 3 (CPOL=1, CPHA=1) and max 5MHz for initial reads
SPISettings adxlSettings(5000000, MSBFIRST, SPI_MODE3);
void setup() {
Serial.begin(115200);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // Deselect peripheral immediately
SPI.begin();
delay(100); // Allow peripheral power-on reset
byte deviceId = readRegister(0x00);
if (deviceId == EXPECTED_ID) {
Serial.println('SPI Bus OK: ADXL345 detected (0xE5).');
} else {
Serial.print('SPI Failure. Read: 0x');
Serial.println(deviceId, HEX);
}
}
void loop() {
// Main application logic goes here
}
byte readRegister(byte address) {
byte result;
SPI.beginTransaction(adxlSettings);
digitalWrite(CS_PIN, LOW);
// Send read command + address
SPI.transfer(READ_DEVID | address);
// Clock in the response byte
result = SPI.transfer(0x00);
digitalWrite(CS_PIN, HIGH);
SPI.endTransaction();
return result;
}
Debugging the Bus: Sniffing, Protocol Choice, and Classic Failures
When the serial monitor prints 0x00 or 0xFF instead of your expected register data, you have a bus failure. Here is how to systematically isolate the fault.
Choosing the Right Protocol: SPI vs I2C vs UART
Not every sensor needs SPI. Use this decision matrix to pick your bus before designing the PCB or breadboard layout:
| Criteria | SPI | I2C | UART |
|---|---|---|---|
| Best For | High speed, high throughput (TFTs, SD cards) | Many low-speed sensors on the same bus | Point-to-point, long distance, GPS modules |
| Distance Limit | < 1 meter (board level) | < 1 meter (capacitance limited) | Up to 15m (RS232) or 1200m (RS485) |
| Device Count | Limited by available CS GPIO pins | Up to 127 (address limited) | 1-to-1 (without multiplexers) |
How to Sniff and Debug SPI
A multimeter is useless for debugging SPI clock phases. You need a logic analyzer (like a Saleae Logic 8 or a DSLogic Plus).
- Probe the Lines: Connect CH0 to SCK, CH1 to MOSI, CH2 to MISO, and CH3 to CS.
- Set the Sample Rate: The Nyquist theorem dictates you must sample at least twice the clock frequency. In practice, set your logic analyzer to at least 4x to 8x the SPI clock speed (e.g., if SPI is 4MHz, sample at 24MHz or higher) to capture clean edges.
- Decode: Use the analyzer's built-in SPI decoder. Set it to MSB first, and toggle between Mode 0 and Mode 3 until the decoded hex matches your datasheet.
The Classic SPI Failures (and Fixes)
Symptom: Logic analyzer shows data shifting by one bit, or returning garbage.
Fix: SPI has four 'Modes' dictated by Clock Polarity (CPOL) and Clock Phase (CPHA). Mode 0 and Mode 3 are the most common. If your code uses
SPI_MODE0 but the datasheet specifies the peripheral samples on the falling edge, switch your SPISettings to SPI_MODE3.
Symptom: Code compiles, CS toggles, SCK clocks, but MISO stays flat.
Fix: The naming convention is a historical mess. MOSI means 'Master Out, Slave In'. If your sensor board is labeled 'SDI' (Serial Data In), it expects to receive data, so it must connect to the Arduino's MOSI. If it is labeled 'SDO' (Serial Data Out), it connects to the Arduino's MISO. Always trace the signal direction, not just the acronym.
Symptom: System works fine on the bench, but fails randomly when powered via a battery or when a secondary peripheral is added.
Fix: Add the 10kΩ pull-up resistor to the CS line. Without it, voltage droop during MCU boot or electrical noise on the breadboard can falsely assert the chip select, causing the peripheral to drive MISO and short out the bus.
By treating SPI as a strict physical-layer protocol rather than just a software library, you eliminate 90% of the debugging headaches. Verify your pinout against the ICSP header, respect the 3.3V logic limits, and always validate your wiring with a Device ID register read before writing your main application logic.
References: For detailed timing diagrams and electrical characteristics, consult the Analog Devices ADXL345 Datasheet. For core library implementation details, see the official Arduino SPI Language Reference and the SparkFun SPI Tutorial.






