What Is SPI? Compared to I2C, with a Real Application
Intermediate1/8/2026- Author: IoTSpark Maker

What Is SPI? Compared to I2C, with a Real Application

SPI principles (SCK/MOSI/MISO/CS), compared to I2C on speed and device addressing, with a hands-on example drawing to a 240x320 ILI9341 TFT display over SPI with an ESP32 DevKit.

SPIprotocolESP32TFTILI9341kien-thuc-nen-tang
0 steps2 components

SPI (Serial Peripheral Interface) is a synchronous serial communication protocol developed by Motorola, using a minimum of 4 wires: SCK (clock), MOSI (Master Out Slave In), MISO (Master In Slave Out), and CS/SS (Chip Select) — each slave device needs its own dedicated CS pin instead of an address like I2C.

In exchange, SPI has no low standardized speed ceiling like I2C: real-world speeds typically reach a few MHz up to tens of MHz, well suited for devices needing high bandwidth, like TFT displays.

This lesson uses a 2.4" 240x320 TFT display (ILI9341 driver) over SPI with an ESP32 DevKit, while directly comparing it with the I2C covered in the previous lesson.

Detailed guide

SPI principles (SCK/MOSI/MISO/CS) compared to I2C on speed and wire count, with a hands-on example drawing to a 240x320 ILI9341 TFT over SPI with an ESP32.

1. Introduction

SPI (Serial Peripheral Interface) is a full-duplex serial protocol (transmitting and receiving simultaneously) using 4 signal wires: SCK (Serial Clock, driven by the master), MOSI (Master Out Slave In), MISO (Master In Slave Out), and CS/SS (Chip Select, for device selection).

Unlike I2C, which uses addresses to distinguish devices on the same 2 wires, SPI uses a dedicated physical CS pin per device — to talk to a specific device, the master pulls that device's CS pin low before transmitting.

Trade-off: SPI needs more wires than I2C when there are multiple devices (each device adds a CS pin, while I2C stays at just 2 shared SDA/SCL wires), but in exchange it's much faster — SPI has no low standardized speed cap like I2C (100kHz/400kHz Standard/Fast-mode); in practice, many ICs/displays run at 10-40MHz.

This is why devices needing high bandwidth — TFT displays, SD cards, fast-sampling sensors — almost always use SPI rather than I2C.

2. Components Needed

ComponentQtyNote
ESP32 DevKit V41Main board, acts as the SPI master
TFT 2.4" 240x320 SPI (ILI9341)165K-color display, Adafruit_ILI9341 library
Jumper wires8VCC, GND, CLK, MOSI, MISO, CS, DC, RST

3. Wiring Diagram

TFT ILI9341ESP32
VCC3V3
GNDGND
CLK / SCKGPIO18 (VSPI SCK)
MOSI / SDAGPIO23 (VSPI MOSI)
MISOGPIO19 (VSPI MISO)
CSGPIO5
DCGPIO27
RSTGPIO33
BL (Backlight)3V3 (always on)

Compared to the I2C diagram in the previous lesson (only 2 shared signal wires, SDA/SCL, for every device), SPI here needs 5 dedicated signal wires (CLK, MOSI, MISO, CS, DC — with RST also counting as a control line). If you add a second SPI display, CLK/MOSI/MISO can be shared, but CS must be an entirely different GPIO pin.

4. Example #1 — Basic Drawing to the TFT over SPI

A sketch that initializes Adafruit_ILI9341 with CS=GPIO5, DC=GPIO27, RST=GPIO33 (the default VSPI bus: SCK=18, MOSI=23, MISO=19 need no explicit declaration), prints text, and draws a rectangle that changes color every 1.5 seconds to illustrate SPI's continuous writes with no address needed before each command.

/*
  What Is SPI? — Example #1: Basic Drawing to an ILI9341 TFT over SPI
  Board: ESP32 DevKit V4 + TFT 2.4" 240x320 SPI (ILI9341)

  SPI pins (ESP32 default VSPI): CLK=GPIO18, MOSI=GPIO23, MISO=GPIO19
  The display's own control pins: CS=GPIO5, DC=GPIO27, RST=GPIO33
  The backlight (BL) is wired directly to 3V3 (always on).

  Compared to I2C (2 shared SDA/SCL wires for multiple devices, typically
  100kHz-400kHz), SPI uses more wires (SCK, MOSI, MISO, plus a dedicated
  CS per device) but runs much faster (a few MHz to tens of MHz), suited
  for displays that need a fast refresh.
*/

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

#define TFT_CS 5
#define TFT_DC 27
#define TFT_RST 33

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  Serial.begin(115200);
  delay(300);
  Serial.println(F("=== SPI TFT ILI9341 demo boot ==="));

  tft.begin(); // SPI mac dinh: SCK=18, MOSI=23, MISO=19 (VSPI)
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);

  tft.setCursor(10, 10);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.println("SPI Demo");

  tft.setTextSize(1);
  tft.setCursor(10, 40);
  tft.println("CLK=18 MOSI=23 MISO=19");
  tft.setCursor(10, 55);
  tft.println("CS=5 DC=27 RST=33");

  Serial.println(F("Da khoi tao TFT qua SPI"));
}

void loop() {
  static uint16_t colors[] = {ILI9341_RED, ILI9341_GREEN, ILI9341_BLUE, ILI9341_YELLOW};
  static uint8_t idx = 0;
  static unsigned long last = 0;

  if (millis() - last >= 1500) {
    last = millis();
    tft.fillRect(10, 80, 100, 30, colors[idx]);
    Serial.printf("Ve hinh chu nhat mau #%u (SPI ghi lien tuc, khong can dia chi)\n", idx);
    idx = (idx + 1) % 4;
  }
}

5. Example #2 — The Real Application: a Seconds-Counter Clock

A second variant that takes advantage of SPI's high write speed to update a running-seconds counter on the display every second without stutter, illustrating a real-world case where SPI outperforms I2C when continuously redrawing a large area.

/*
  What Is SPI? — Example #2: The Real Application — a Seconds-Counter Clock on the TFT
  Board: ESP32 DevKit V4 + TFT 2.4" 240x320 SPI (ILI9341)
  Same pin wiring as example #1: CLK=18, MOSI=23, MISO=19, CS=5, DC=27, RST=33.
*/

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

#define TFT_CS 5
#define TFT_DC 27
#define TFT_RST 33

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

unsigned long bootMillis = 0;

void setup() {
  Serial.begin(115200);
  delay(300);
  Serial.println(F("=== TFT dong ho dem giay (SPI) ==="));

  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);
  tft.setTextSize(4);

  bootMillis = millis();
}

void loop() {
  unsigned long secs = (millis() - bootMillis) / 1000UL;

  tft.setCursor(40, 100);
  tft.printf("%6lus", secs); // ghi de tren cung vi tri, tan dung toc do SPI cao

  Serial.printf("Uptime: %lus (cap nhat qua SPI moi vong lap)\n", secs);

  delay(1000);
}

6. Direct Comparison: I2C vs SPI

CriterionI2CSPI
Signal wire count2 (SDA, SCL) shared by every device4 base wires (SCK, MOSI, MISO, CS) + 1 dedicated CS per device
How a device is selectedA 7-bit address sent in the data frameA physical CS pin pulled low
Typical speed100kHz–400kHz (a faster Fast/High-speed standard exists, but few modules support it)A few MHz to tens of MHz
DuplexHalf-duplex (shares one data wire)Full-duplex (independent MOSI/MISO)
Best suited forSmall sensors, little data, saving pinsDisplays, memory cards, devices needing high bandwidth

7. Common Issues

IssueCauseFix
The display is blank/shows nothingMixed-up DC/RST pins, or CS pointing to the wrong GPIODouble-check the parameter order in Adafruit_ILI9341(CS, DC, RST)
The image is noisy/colors bleedWires too long for the high SPI speed, or a missing shared GNDShorten the MOSI/SCK wires, make sure GND is solidly connected
Compile error, missing libraryAdafruit_ILI9341/Adafruit_GFX aren't installedarduino-cli lib install "Adafruit ILI9341" "Adafruit GFX Library"
Adding a second SPI display causes conflictsReusing the same CS pin as the first deviceEvery SPI device must have its own unique CS GPIO pin, never shared

8. Summary

SPI trades more wires than I2C (especially with multiple devices) for much higher speed — with no low standardized cap like I2C's 100/400kHz. For devices that need to continuously transfer large amounts of data (displays, memory cards), SPI is almost always the required choice; for small, low-data sensors where saving GPIO pins matters, I2C remains the more economical option.