
Debug giao tiếp I2C/SPI cơ bản
Kỹ thuật debug I2C/SPI không cần thiết bị đo chuyên dụng: I2C bus scanner, health-check định kỳ, và checklist chẩn đoán lỗi thường gặp với ESP32 DevKit.
I2C và SPI là 2 giao thức "im lặng khi lỗi": khi dây sai, thiếu pull-up, hoặc nhầm địa chỉ, thiết bị thường không báo lỗi rõ ràng — chỉ đơn giản là không trả lời, khiến người mới dễ nhầm lẫn giữa lỗi phần cứng, lỗi thư viện, hay lỗi code.
Bài này tập trung vào kỹ thuật debug hoàn toàn bằng phần mềm, không cần logic analyzer hay oscilloscope: dùng chính ESP32 làm công cụ chẩn đoán bằng I2C bus scanner (quét toàn bộ dải địa chỉ để biết thiết bị nào đang thực sự phản hồi) và health-check định kỳ (phát hiện sớm khi thiết bị "rớt" bus giữa chừng).
Đây là kỹ năng nền tảng nên có trước khi đi sâu debug bất kỳ dự án I2C/SPI nào.
Hướng dẫn chi tiết
Kỹ thuật debug I2C/SPI bằng phần mềm, không cần thiết bị đo: I2C bus scanner, health-check định kỳ, checklist chẩn đoán lỗi thường gặp.
1. Giới thiệu
I2C và SPI đều là giao thức "im lặng khi lỗi": nếu dây sai, thiếu pull-up, hoặc nhầm địa chỉ, thiết bị không tự báo lỗi mà chỉ đơn giản không phản hồi — dẫn tới các thông báo mơ hồ kiểu "sensor not found" khiến người mới không biết bắt đầu tìm lỗi từ đâu. Kỹ thuật debug ở bài này không cần logic analyzer hay oscilloscope, chỉ dùng chính ESP32 và code để chẩn đoán: quan trọng nhất là I2C bus scanner — công cụ đầu tiên nên chạy bất cứ khi nào nghi ngờ có vấn đề với I2C.
Nguyên lý bus scanner rất đơn giản: I2C có cơ chế ACK/NACK ở tầng phần cứng — gửi địa chỉ bất kỳ, nếu có thiết bị lắng nghe đúng địa chỉ đó, nó sẽ tự động kéo SDA xuống thấp để ACK. Bus scanner chỉ cần thử tuần tự tất cả 112 địa chỉ hợp lệ (0x08-0x77) và ghi lại địa chỉ nào có ACK — không cần biết trước thiết bị là gì hay dùng thư viện gì.
2. Linh kiện cần có
Linh kiện | Số lượng | Ghi chú |
|---|---|---|
ESP32 DevKit V4 | 1 | Board chính |
BMP280 (I2C) | 1 | Thiết bị mẫu để scan, địa chỉ 0x76 |
OLED 0.96" I2C (SSD1306) | 1 | Thiết bị mẫu thứ hai, địa chỉ 0x3C |
3. Sơ đồ kết nối
BMP280 & OLED | ESP32 |
|---|---|
VCC | 3V3 |
GND | GND |
SDA | GPIO21 (chung dây) |
SCL | GPIO22 (chung dây) |
Sơ đồ giống bài "I2C là gì" — cố ý dùng lại để tập trung vào kỹ thuật debug thay vì phần cứng mới. Bus scanner hoạt động với bất kỳ thiết bị I2C nào, không riêng gì 2 thiết bị này.
4. Code ví dụ #1 — I2C Bus Scanner đầy đủ
Sketch quét toàn bộ 112 địa chỉ hợp lệ mỗi 5 giây, in ra địa chỉ nào phản hồi ACK (err == 0), cảnh báo lỗi truyền bất thường (err == 4, thường do nhiễu/dây lỏng), và bỏ qua NACK bình thường (err == 2, đơn giản là không có thiết bị) để log gọn gàng. Nếu không tìm thấy thiết bị nào, in luôn checklist 3 bước kiểm tra.
/*
Debug giao tiếp I2C/SPI cơ bản — Ví dụ #1: I2C Bus Scanner đầy đủ
Board: ESP32 DevKit V4 + BMP280 (0x76) + OLED SSD1306 (0x3C) trên cùng bus I2C
SDA=GPIO21 SCL=GPIO22
Đây là công cụ debug đầu tiên nên chạy khi I2C "không thấy thiết bị":
quét toàn bộ 112 địa chỉ hợp lệ (0x08-0x77) và báo địa chỉ nào có ACK.
Không cần biết trước loại thiết bị / thư viện gì - chỉ cần dây đúng.
*/
#include <Wire.h>
#define SDA_PIN 21
#define SCL_PIN 22
void setup() {
Serial.begin(115200);
delay(300);
Serial.println(F("=== I2C Bus Scanner (debug tool) boot ==="));
Serial.printf("SDA=GPIO%d SCL=GPIO%d, 100kHz Standard-mode\n", SDA_PIN, SCL_PIN);
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(100000);
}
void scanOnce() {
Serial.println(F("--- Bat dau quet ---"));
uint8_t found = 0;
for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
Wire.beginTransmission(addr);
uint8_t err = Wire.endTransmission();
if (err == 0) {
Serial.printf(" [OK] 0x%02X - thiet bi phan hoi ACK\n", addr);
found++;
} else if (err == 4) {
// err=4: loi khong xac dinh khi truyen - thuong la nhieu/day loi tai dia chi nay
Serial.printf(" [ERR] 0x%02X - loi khong xac dinh (kiem tra day/nhieu)\n", addr);
}
// err == 2 (NACK dia chi) la binh thuong: khong co thiet bi tai dia chi do, khong in de do log
}
if (found == 0) {
Serial.println(F("[WARN] Khong tim thay thiet bi nao. Kiem tra:"));
Serial.println(F(" 1) Da co dien tro pull-up SDA/SCL chua (thuong co san tren module)"));
Serial.println(F(" 2) SDA/SCL co bi noi nguoc nhau khong"));
Serial.println(F(" 3) VCC module co dung 3.3V khong (mot so module can 5V)"));
} else {
Serial.printf("--- Ket thuc: tim thay %u thiet bi ---\n", found);
}
}
void loop() {
scanOnce();
delay(5000); // quet lai moi 5s, huu ich khi dang ro day/hot-plug de debug
}
5. Code ví dụ #2 — Health-check định kỳ 2 thiết bị đã biết
Khi đã biết chắc địa chỉ thiết bị (khác với tình huống ở ví dụ #1 khi chưa biết gì), dùng "ping" định kỳ 3 giây/lần để phát hiện sớm việc thiết bị bị rớt bus giữa chừng — dấu hiệu điển hình của dây lỏng hoặc nhiễu, khác hẳn với lỗi sai địa chỉ (sẽ mất kết nối ngay từ đầu chứ không phải "vừa OK rồi mất").
/*
Debug giao tiếp I2C/SPI cơ bản — Ví dụ #2: Health-check định kỳ 2 thiết bị đã biết
Board: ESP32 DevKit V4 + BMP280 (0x76) + OLED SSD1306 (0x3C)
Khi đã biết địa chỉ thiết bị, dùng bài kiểm tra "ping" định kỳ để phát hiện
sớm khi 1 thiết bị bị rớt bus (dây lỏng, nhiễu) thay vì đợi code chính báo lỗi khó hiểu.
*/
#include <Wire.h>
#define SDA_PIN 21
#define SCL_PIN 22
#define BMP280_ADDR 0x76
#define OLED_ADDR 0x3C
bool pingAddr(uint8_t addr) {
Wire.beginTransmission(addr);
return Wire.endTransmission() == 0;
}
void setup() {
Serial.begin(115200);
delay(300);
Serial.println(F("=== I2C Health-check (debug dinh ky) boot ==="));
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(100000);
}
void loop() {
bool bmpOk = pingAddr(BMP280_ADDR);
bool oledOk = pingAddr(OLED_ADDR);
Serial.printf("[HEALTH] BMP280(0x76)=%s OLED(0x3C)=%s\n",
bmpOk ? "OK" : "MAT KET NOI",
oledOk ? "OK" : "MAT KET NOI");
if (!bmpOk || !oledOk) {
Serial.println(F("[HINT] Neu vua OK roi mat dot ngot -> nghi ngo day long/nhieu,"));
Serial.println(F(" khong phai loi dia chi (dia chi sai se mat ngay tu dau)."));
}
delay(3000);
}
6. Debug SPI — khác biệt so với I2C
SPI không có cơ chế ACK/NACK ở tầng phần cứng như I2C, nên không thể viết "SPI scanner" tương tự. Kỹ thuật debug SPI phần mềm phổ biến: (1) đọc thanh ghi ID nếu chip hỗ trợ (nhiều IC như màn hình có lệnh đọc "device ID" qua MISO để xác nhận đang nói chuyện đúng chip); (2) kiểm tra từng chân CS/DC/RST riêng lẻ bằng digitalWrite + đo bằng LED tạm nếu nghi ngờ chân sai; (3) thử giảm tốc độ SPI (setSPISpeed) — nếu giảm tốc mà hết lỗi, nguyên nhân thường là dây quá dài hoặc nhiễu ở tốc độ cao chứ không phải sai chân.
7. Lỗi thường gặp
Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
Scanner không tìm thấy thiết bị nào | Thiếu pull-up, SDA/SCL nối ngược, hoặc sai điện áp VCC | Kiểm tra theo checklist 3 bước scanner in ra khi |
Scanner báo | Nhiễu điện hoặc dây tiếp xúc kém (err code 4) | Kiểm tra lại đầu nối, rút ngắn dây, tránh đặt gần nguồn nhiễu (động cơ, relay) |
Thiết bị lúc thấy lúc không trong health-check | Dây lỏng hoặc breadboard tiếp xúc kém, không phải lỗi địa chỉ | Kiểm tra cơ học: ấn chặt lại dây/module, thử breadboard/dây khác |
Địa chỉ scan được khác với datasheet | Module có chân cấu hình địa chỉ (ví dụ SDO của BMP280) đang ở trạng thái khác mặc định | Đối chiếu lại chân cấu hình địa chỉ trên module thực tế, không chỉ tin datasheet |
8. Tổng kết
Debug I2C/SPI hiệu quả bắt đầu từ việc xác nhận tầng thấp nhất trước khi nghi ngờ code hay thư viện: với I2C, chạy bus scanner trước tiên luôn là bước đúng — nó tách bạch rõ ràng "vấn đề phần cứng/dây nối" khỏi "vấn đề logic code".
Với SPI, vì không có cơ chế ACK, phải dựa vào đọc ID chip (nếu có) hoặc quan sát hành vi khi thay đổi tốc độ/chân. Kỹ năng này áp dụng được cho mọi dự án I2C/SPI, không riêng gì BMP280/OLED trong bài.