
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.
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ố |
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 |
|
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.