SPI (Serial Peripheral Interface) is the fastest serial communication protocol available on Arduino. When you need to drive a TFT display at 60 fps, stream data from an SD card, or interface with a high-speed ADC, SPI is the protocol of choice. In this tutorial you will learn how SPI works, how to use the Arduino SPI library, how to set up custom clock speeds and modes, and how to control registers directly for maximum performance.
Table of Contents
- What Is SPI and How Does It Work?
- SPI Pins on Arduino Boards
- Using the Arduino SPI Library
- SPI Clock Polarity and Phase (CPOL/CPHA)
- Multiple SPI Slave Devices
- SPI TFT Display Example
- Direct SPI Register Control
- Frequently Asked Questions
What Is SPI and How Does It Work?
SPI is a synchronous serial communication protocol developed by Motorola. Unlike I2C which uses addresses, SPI uses dedicated Chip Select (CS) pins to choose which device to communicate with. It is a master/slave protocol — the master (Arduino) controls the clock and initiates all communication.
SPI uses four signal lines:
- MOSI (Master Out Slave In): Data from master to slave
- MISO (Master In Slave Out): Data from slave to master
- SCK (Serial Clock): Clock signal generated by the master
- CS/SS (Chip Select / Slave Select): Active LOW signal that selects a specific slave
Data transfer is full-duplex — master and slave exchange data simultaneously on MOSI and MISO. For every byte the master sends on MOSI, it simultaneously receives a byte on MISO. This makes SPI inherently faster than I2C for high-throughput applications.
A typical SPI transaction:
- Master pulls the slave’s CS pin LOW (selects it)
- Master generates clock pulses on SCK
- Data bits shift out on MOSI and in on MISO simultaneously, one bit per clock edge
- After all bytes are transferred, master pulls CS HIGH (deselects the slave)
Typical SPI speeds on Arduino Uno: up to 8 MHz (half the 16 MHz clock). The Arduino Due can run SPI at 42 MHz for truly high-speed applications. Most sensors operate at 1–10 MHz, while TFT displays can often accept 40–80 MHz.
SPI Pins on Arduino Boards
Each Arduino board has dedicated hardware SPI pins. Using these pins (rather than any other pins) is required for hardware SPI — it enables the hardware SPI peripheral for maximum speed. Software SPI (bit-banging) can work on any pins but is much slower.
| Board | MOSI | MISO | SCK | Default SS |
|---|---|---|---|---|
| Uno / Nano | 11 | 12 | 13 | 10 |
| Mega 2560 | 51 | 50 | 52 | 53 |
| Leonardo | ICSP-4 | ICSP-1 | ICSP-3 | 10 |
| Nano 33 IoT | 11 | 12 | 13 | 10 |
| Nano RP2040 | 11 | 12 | 13 | 10 |
Note for Mega 2560 users: SPI pins are on 50–53 AND also exposed on the ICSP header. If a SPI shield was designed for Uno, it connects via the ICSP header and will also work on the Mega.
The built-in LED is on pin 13 on Uno/Nano, the same pin as SCK. When SPI is active, the LED may flicker. This is normal and does not affect SPI communication.
Using the Arduino SPI Library
The Arduino IDE includes the SPI library which provides a clean interface to the hardware SPI peripheral. Here is a complete example that transfers data to a SPI device:
Basic SPI Transfer Example
#include <SPI.h>
const int CS_PIN = 10; // Chip Select pin
void setup() {
Serial.begin(9600);
// Initialize CS pin as output and deselect device
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
// Initialize SPI
SPI.begin();
}
void writeToDevice(uint8_t reg, uint8_t value) {
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW); // Select device
SPI.transfer(reg); // Send register address
SPI.transfer(value); // Send value
digitalWrite(CS_PIN, HIGH); // Deselect device
SPI.endTransaction();
}
uint8_t readFromDevice(uint8_t reg) {
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW);
SPI.transfer(reg | 0x80); // Set MSB to indicate read (device-specific)
uint8_t result = SPI.transfer(0x00); // Send dummy byte, receive response
digitalWrite(CS_PIN, HIGH);
SPI.endTransaction();
return result;
}
SPISettings Parameters Explained
SPISettings(speed, bitOrder, dataMode) takes three arguments:
- speed: Maximum clock frequency in Hz (e.g.,
1000000for 1 MHz,8000000for 8 MHz). The SPI library will round down to the nearest achievable speed. - bitOrder:
MSBFIRST(most significant bit first — standard) orLSBFIRST - dataMode:
SPI_MODE0,SPI_MODE1,SPI_MODE2, orSPI_MODE3(see next section)
Always use SPI.beginTransaction() and SPI.endTransaction() to bracket your SPI transfers. This ensures that if multiple libraries or interrupt handlers try to use SPI, they wait their turn without corrupting each other’s data.
SPI Clock Polarity and Phase (CPOL/CPHA)
SPI has four modes defined by two bits: CPOL (Clock Polarity) and CPHA (Clock Phase). These determine the idle state of the clock and which edge data is sampled on.
| Mode | CPOL | CPHA | Clock idle | Data sampled on |
|---|---|---|---|---|
| SPI_MODE0 | 0 | 0 | LOW | Rising edge |
| SPI_MODE1 | 0 | 1 | LOW | Falling edge |
| SPI_MODE2 | 1 | 0 | HIGH | Falling edge |
| SPI_MODE3 | 1 | 1 | HIGH | Rising edge |
Most common sensors and displays (including the ST7735 TFT) use SPI_MODE0. Always check your component’s datasheet for the correct mode. Using the wrong mode produces garbage data without any error.
Multiple SPI Slave Devices
Unlike I2C, SPI does not use addresses. Instead, each slave device needs its own Chip Select (CS) pin connected to the Arduino. MOSI, MISO, and SCK are shared by all devices.
#include <SPI.h>
const int CS_DISPLAY = 10;
const int CS_SD_CARD = 9;
const int CS_SENSOR = 8;
void setup() {
// Initialize all CS pins HIGH (deselected)
pinMode(CS_DISPLAY, OUTPUT); digitalWrite(CS_DISPLAY, HIGH);
pinMode(CS_SD_CARD, OUTPUT); digitalWrite(CS_SD_CARD, HIGH);
pinMode(CS_SENSOR, OUTPUT); digitalWrite(CS_SENSOR, HIGH);
SPI.begin();
}
void talkToDisplay() {
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_DISPLAY, LOW); // Select only the display
SPI.transfer(0x01);
digitalWrite(CS_DISPLAY, HIGH);
SPI.endTransaction();
}
void talkToSD() {
SPI.beginTransaction(SPISettings(250000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_SD_CARD, LOW); // Select only the SD card
SPI.transfer(0x40);
digitalWrite(CS_SD_CARD, HIGH);
SPI.endTransaction();
}
The critical rule: only one CS pin should be LOW at any time. If two devices are selected simultaneously, their MISO outputs collide and you get corrupted data. Always pull CS HIGH before selecting a different device.
SPI TFT Display Example
The 1.8-inch ST7735 TFT is one of the most popular SPI devices for Arduino. Here is how to set it up:
Wiring (Uno):
- VCC → 5V
- GND → GND
- SCL → Pin 13 (SCK)
- SDA → Pin 11 (MOSI)
- RES → Pin 9 (Reset)
- DC → Pin 8 (Data/Command)
- CS → Pin 10 (Chip Select)
- BL → 3.3V or 5V (backlight)
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 9
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
void setup() {
tft.initR(INITR_BLACKTAB); // Init ST7735S chip, black tab
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(2);
tft.setCursor(10, 30);
tft.println("Hello SPI!");
// Draw a colored rectangle
tft.fillRect(10, 60, 100, 40, ST77XX_BLUE);
}
void loop() {
// Update display with sensor data
tft.fillRect(10, 60, 100, 40, ST77XX_RED);
delay(500);
tft.fillRect(10, 60, 100, 40, ST77XX_GREEN);
delay(500);
}
Direct SPI Register Control
For the absolute maximum SPI speed on ATmega Arduinos, you can bypass the SPI library and write directly to the hardware registers. This eliminates function call overhead and is useful in time-critical applications:
void spiInit() {
// Set MOSI, SCK, SS as outputs
DDRB |= (1 << PB3) | (1 << PB5) | (1 << PB2);
// Set MISO as input
DDRB &= ~(1 << PB4);
// Enable SPI, Master mode, clock = fosc/2 (8 MHz)
// SPIE=0 (no interrupt), SPE=1 (enable), DORD=0 (MSB first)
// MSTR=1 (master), CPOL=0, CPHA=0 (Mode 0)
// SPR1=0, SPR0=0 + SPI2X=1 → clock / 2 = 8 MHz
SPCR = (1 << SPE) | (1 << MSTR);
SPSR = (1 << SPI2X); // Double speed
}
uint8_t spiTransfer(uint8_t data) {
SPDR = data; // Start transfer
while (!(SPSR & (1 << SPIF))); // Wait for completion
return SPDR; // Return received byte
}
This gives you the maximum 8 MHz SPI clock on a 16 MHz Uno. The SPI library with SPISettings(8000000, ...) achieves the same clock speed but with slightly more overhead due to function calls and transaction management.
The available clock speeds on a 16 MHz Uno using the prescaler bits (SPR1, SPR0, SPI2X):
- fosc/2 = 8 MHz (SPI2X=1, SPR=00)
- fosc/4 = 4 MHz (SPI2X=0, SPR=00)
- fosc/8 = 2 MHz (SPI2X=1, SPR=01)
- fosc/16 = 1 MHz (SPI2X=0, SPR=01)
- fosc/64 = 250 kHz (SPI2X=0, SPR=10)
- fosc/128 = 125 kHz (SPI2X=0, SPR=11)
Frequently Asked Questions
What is the maximum SPI speed on Arduino Uno?
The Arduino Uno’s ATmega328P hardware SPI peripheral supports a maximum of fosc/2 = 8 MHz (with the SPI2X double-speed bit set). Requesting any higher speed with SPISettings() will automatically be capped at 8 MHz. For higher SPI speeds, use the Arduino Due (42 MHz) or Nano RP2040 Connect (up to 62.5 MHz).
Can I use SPI without the CS pin?
Only if you have exactly one SPI device. In that case, you can tie the device’s CS pin permanently LOW. However, if there is any chance you will add more devices, use a CS pin. Also note that the Arduino’s hardware SPI requires the SS pin (pin 10 on Uno) to be an output, even if you are using a different pin as CS. If SS is configured as an input and pulled LOW by external hardware, the SPI peripheral switches to slave mode.
Why does my SPI device return 0xFF or garbage data?
Common causes: wrong SPI mode (try all four modes), wrong clock speed (try 1 MHz as a safe starting point), CS pin not pulled LOW during transfer, MOSI/MISO swapped, or the device needs initialization commands before responding. Check the datasheet carefully for the correct command format — many devices expect a specific framing byte or command byte before accepting read requests.
What is the difference between hardware SPI and software SPI?
Hardware SPI uses the Arduino’s built-in SPI peripheral and is limited to fixed pins (MOSI, MISO, SCK). It runs at maximum speed (up to 8 MHz on Uno) with no CPU overhead per bit. Software SPI (bit-banging) manually toggles any GPIO pins using code, which is much slower (typically 100–500 kHz) but allows using any pins. Use hardware SPI whenever possible. Use software SPI only when the hardware SPI pins are occupied by another device or the device requires an unusual configuration.
Can Arduino be both a SPI master and a SPI slave at the same time?
No, not on the same SPI bus. The ATmega328P has one SPI peripheral that can only be in master or slave mode at a time. If you need to act as both (e.g., read data from a sensor as master and send it to a Raspberry Pi as slave), you would need to use software SPI for one role while using hardware SPI for the other, or use a processor with multiple independent SPI controllers like the Raspberry Pi Pico / RP2040.
Start Building High-Speed SPI Projects
SPI is the protocol to reach for when you need speed. Its full-duplex operation and high clock rates make it the best choice for TFT displays, SD card modules, ADCs, DACs, and any peripheral where data rate matters. The SPI library makes it straightforward, while direct register access lets you push every last MHz of performance from your Arduino.
Browse the Arduino boards, SPI displays, and accessories at Zbotic.in — India’s go-to electronics store for makers and engineers. From beginner kits to the powerful Nano RP2040 Connect, we have the right hardware for your SPI project.
Add comment