SPI là gì? So sánh với I2C và ứng dụng thực tế
Trung cấp1/8/2026- Tác giả: IoTlabs Maker

SPI là gì? So sánh với I2C và ứng dụng thực tế

Nguyên lý SPI (SCK/MOSI/MISO/CS), so sánh tốc độ và cách địa chỉ hoá thiết bị với I2C, thực hành vẽ lên màn hình TFT ILI9341 240x320 qua SPI với ESP32 DevKit.

SPIgiao-thucESP32TFTILI9341kien-thuc-nen-tang

SPI (Serial Peripheral Interface) là giao thức giao tiếp nối tiếp đồng bộ do Motorola phát triển, dùng tối thiểu 4 dây: SCK (xung nhịp), MOSI (Master Out Slave In), MISO (Master In Slave Out), và CS/SS (Chip Select) — mỗi thiết bị slave cần một chân CS riêng thay vì dùng địa chỉ như I2C.

Đổi lại, SPI không có giới hạn tốc độ chuẩn hoá thấp như I2C: tốc độ thực tế thường đạt vài MHz đến vài chục MHz, phù hợp cho thiết bị cần băng thông cao như màn hình TFT.

Bài này dùng màn hình TFT 2.4" 240x320 (driver ILI9341) qua SPI với ESP32 DevKit, đồng thời so sánh trực tiếp với I2C đã học ở bài trước.

Hướng dẫn chi tiết

Nguyên lý SPI (SCK/MOSI/MISO/CS), so sánh tốc độ/số dây với I2C, thực hành vẽ lên TFT ILI9341 240x320 qua SPI với ESP32.

1. Giới thiệu

SPI (Serial Peripheral Interface) là giao thức giao tiếp nối tiếp full-duplex (truyền và nhận đồng thời) dùng 4 dây tín hiệu: SCK (Serial Clock, xung nhịp do master phát), MOSI (Master Out Slave In), MISO (Master In Slave Out) và CS/SS (Chip Select, chọn thiết bị).

Khác với I2C dùng địa chỉ để phân biệt thiết bị trên cùng 2 dây, SPI dùng một chân CS vật lý riêng cho mỗi thiết bị — muốn nói chuyện với thiết bị nào, master kéo chân CS của thiết bị đó xuống mức thấp trước khi truyền.

Đánh đổi: SPI cần nhiều dây hơn I2C khi có nhiều thiết bị (mỗi thiết bị thêm 1 chân CS, trong khi I2C vẫn chỉ 2 dây SDA/SCL dùng chung), nhưng bù lại tốc độ cao hơn hẳn — SPI không có giới hạn chuẩn hoá thấp như I2C (100kHz/400kHz Standard/Fast-mode), thực tế nhiều IC/màn hình chạy được 10-40MHz.

Đây là lý do các thiết bị cần băng thông lớn như màn hình TFT, thẻ nhớ SD, hoặc cảm biến lấy mẫu nhanh gần như luôn dùng SPI thay vì I2C.

2. Linh kiện cần có

Linh kiện

Số lượng

Ghi chú

ESP32 DevKit V4

1

Board chính, đóng vai SPI master

TFT 2.4" 240x320 SPI (ILI9341)

1

Màn hình màu 65K màu, thư viện Adafruit_ILI9341

Dây jumper

8

VCC, GND, CLK, MOSI, MISO, CS, DC, RST

3. Sơ đồ kết nối

TFT ILI9341

ESP32

VCC

3V3

GND

GND

CLK / SCK

GPIO18 (VSPI SCK)

MOSI / SDA

GPIO23 (VSPI MOSI)

MISO

GPIO19 (VSPI MISO)

CS

GPIO5

DC

GPIO27

RST

GPIO33

BL (Backlight)

3V3 (luôn sáng)

So với sơ đồ I2C ở bài trước (chỉ 2 dây tín hiệu SDA/SCL dùng chung cho mọi thiết bị), SPI ở đây cần 5 dây tín hiệu riêng (CLK, MOSI, MISO, CS, DC — RST cũng tính là điều khiển). Nếu gắn thêm 1 màn hình SPI thứ hai, CLK/MOSI/MISO có thể dùng chung nhưng CS phải là chân GPIO khác hoàn toàn.

4. Code ví dụ #1 — Vẽ cơ bản lên TFT qua SPI

Sketch khởi tạo Adafruit_ILI9341 với CS=GPIO5, DC=GPIO27, RST=GPIO33 (bus SPI mặc định VSPI: SCK=18, MOSI=23, MISO=19 không cần khai báo lại), in chữ và vẽ hình chữ nhật đổi màu mỗi 1.5 giây để minh hoạ SPI ghi liên tục không cần gửi địa chỉ trước mỗi lệnh.

/*
  SPI là gì? — Ví dụ #1: Vẽ cơ bản lên TFT ILI9341 qua SPI
  Board: ESP32 DevKit V4 + TFT 2.4" 240x320 SPI (ILI9341)

  Chân SPI (VSPI mặc định ESP32): CLK=GPIO18, MOSI=GPIO23, MISO=GPIO19
  Chân điều khiển riêng của màn hình: CS=GPIO5, DC=GPIO27, RST=GPIO33
  Backlight (BL) nối thẳng 3V3 (luôn sáng).

  So với I2C (2 dây SDA/SCL dùng chung cho nhiều thiết bị, tốc độ thường
  100kHz-400kHz), SPI dùng nhiều dây hơn (SCK, MOSI, MISO, CS riêng cho
  từng thiết bị) nhưng tốc độ cao hơn nhiều (vài MHz - vài chục MHz),
  phù hợp cho màn hình cần refresh nhanh.
*/

#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. Code ví dụ #2 — Ứng dụng thực tế: đồng hồ đếm giây

Biến thể thứ hai tận dụng tốc độ ghi cao của SPI để cập nhật số đếm giây (uptime) trên màn hình mỗi giây mà không bị giật, minh hoạ ứng dụng thực tế nơi SPI vượt trội so với I2C khi cần vẽ lại vùng lớn liên tục.

/*
  SPI là gì? — Ví dụ #2: Ứng dụng thực tế — Đồng hồ đếm giây hiển thị trên TFT
  Board: ESP32 DevKit V4 + TFT 2.4" 240x320 SPI (ILI9341)
  Cùng sơ đồ chân như ví dụ #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. So sánh trực tiếp I2C vs SPI

Tiêu chí

I2C

SPI

Số dây tín hiệu

2 (SDA, SCL) dùng chung cho mọi thiết bị

4 cơ bản (SCK, MOSI, MISO, CS) + 1 CS riêng/thiết bị

Cách chọn thiết bị

Địa chỉ 7-bit gửi trong khung dữ liệu

Chân CS vật lý kéo thấp

Tốc độ điển hình

100kHz–400kHz (có chuẩn Fast/High-speed cao hơn nhưng ít module hỗ trợ)

Vài MHz đến vài chục MHz

Song công

Half-duplex (chia sẻ 1 dây dữ liệu)

Full-duplex (MOSI/MISO độc lập)

Phù hợp

Cảm biến nhỏ, ít dữ liệu, tiết kiệm chân

Màn hình, thẻ nhớ, thiết bị cần băng thông cao

7. Lỗi thường gặp

Lỗi

Nguyên nhân

Cách sửa

Màn hình trắng/không hiện gì

Nhầm chân DC/RST, hoặc CS chưa đúng GPIO

Kiểm tra lại đúng thứ tự tham số Adafruit_ILI9341(CS, DC, RST)

Hình ảnh bị nhiễu/lem màu

Dây quá dài ở tốc độ SPI cao, hoặc thiếu GND chung

Rút ngắn dây MOSI/SCK, đảm bảo GND nối chắc chắn

Compile lỗi thiếu thư viện

Chưa cài Adafruit_ILI9341/Adafruit_GFX

arduino-cli lib install "Adafruit ILI9341" "Adafruit GFX Library"

Gắn thêm màn hình SPI thứ 2 bị xung đột

Dùng trùng chân CS với thiết bị đầu

Mỗi thiết bị SPI phải có 1 chân CS GPIO riêng, không trùng

8. Tổng kết

SPI đánh đổi số dây nhiều hơn I2C (đặc biệt khi có nhiều thiết bị) để lấy tốc độ cao hơn hẳn — không giới hạn chuẩn hoá 100/400kHz như I2C. Với các thiết bị cần truyền lượng dữ liệu lớn liên tục (màn hình, thẻ nhớ), SPI gần như luôn là lựa chọn bắt buộc; còn với cảm biến nhỏ, ít dữ liệu và cần tiết kiệm chân GPIO, I2C vẫn là lựa chọn kinh tế hơn.