LCD I2C không hiện chữ, chỉ hiện ô vuông, sai địa chỉ 0x27/0x3F: tìm lỗi từng bước
LCD 1602, LCD 2004 I2C không hiện chữ, chỉ hiện hàng ô vuông đen hoặc sai địa chỉ 0x27/0x3F: tìm lỗi nguồn, tương phản, dây I2C, địa chỉ và chương trình kiểm tra tự động.
Nối LCD 1602 hay LCD 2004 I2C vào ESP32, Arduino, nạp chương trình xong mà màn hình chỉ sáng đèn, chỉ hiện một hàng ô vuông đen hoặc không thấy gì là lỗi gặp rất thường xuyên. Nguyên nhân hầu như luôn nằm ở một trong bốn chỗ: nguồn, biến trở tương phản, dây I2C và địa chỉ I2C. Bài này đi qua từng chỗ theo thứ tự, kèm một chương trình kiểm tra tự quét địa chỉ, tự chọn địa chỉ của LCD và hướng dẫn chỉnh tương phản qua Serial Monitor.
1. Nhìn màn hình trước khi sửa

- Đèn nền tắt: LCD chưa có nguồn, đứt dây GND, hoặc jumper LED phía sau đã bị tháo.
- Đèn sáng nhưng màn hình trống: biến trở tương phản đang vặn quá nhạt, hoặc LCD đang được cấp 3,3 V.
- Hàng trên toàn ô vuông đen, hàng dưới trống: đây là trạng thái của LCD khi có điện mà chưa nhận được lệnh khởi tạo nào. Tức là nguồn và tương phản đã ổn, lỗi nằm ở đường I2C: sai địa chỉ, sai dây SDA, SCL, hoặc chương trình chưa chạy tới lệnh khởi tạo.
- Chữ hiện đúng: mọi thứ đã ổn.
2. Tìm lỗi từng bước

- Nguồn và đèn nền. LCD I2C cần 5 V. Với Arduino Nano, UNO lấy chân 5V; với ESP32 lấy chân VIN (là 5 V khi cắm USB). Đèn nền vẫn tắt thì kiểm tra dây GND và jumper LED ở mạch I2C phía sau.
- Tương phản. Dùng tua vít nhỏ xoay biến trở xanh phía sau, có khi phải xoay vài vòng, tới khi thấy ô vuông hoặc chữ rõ trên nền sáng. Cấp 3,3 V thì hầu như vặn thế nào cũng không thấy chữ.
- Dây I2C. Arduino Nano, UNO: SDA vào A4, SCL vào A5. ESP32: SDA vào GPIO21, SCL vào GPIO22, đi qua mạch chuyển mức 3,3 V – 5 V vì mạch I2C của LCD kéo SDA, SCL lên 5 V. GND của LCD phải nối chung với GND của mạch điều khiển. Dây nên ngắn dưới khoảng 30 cm.
- Địa chỉ I2C. Hai địa chỉ hay gặp là 0x27 và 0x3F, tùy chip trên mạch I2C. Chạy chương trình kiểm tra ở mục 4 để biết chắc, rồi sửa địa chỉ trong chương trình của bạn.
- Chương trình. Khai báo đúng số cột, số hàng (16, 2 hay 20, 4), gọi
lcd.init()vàlcd.backlight()trongsetup(). Bài dùng thư viện LiquidCrystal I2C bản 1.1.2 của Frank de Brabander; các bản cũ hơn có lỗi chỉ in ra ký tự đầu tiên của chuỗi.
3. Mặt sau LCD: mạch I2C và địa chỉ

Mạch I2C phía sau dùng chip PCF8574 để điều khiển 16 chân của LCD qua 2 dây. Có hai loại chip: PCF8574T có địa chỉ từ 0x20 đến 0x27, PCF8574AT có địa chỉ từ 0x38 đến 0x3F. Ba cặp pad A0, A1, A2 để hở thì địa chỉ cao nhất (0x27 hoặc 0x3F); hàn nối cặp nào thì bit địa chỉ tương ứng về 0. Nhờ vậy có thể nối tới 8 LCD trên cùng hai dây I2C nếu mỗi màn hình được hàn một địa chỉ riêng.
4. Chương trình kiểm tra LCD I2C
Chương trình quét toàn bộ bus I2C, in ra từng địa chỉ tìm thấy kèm loại chip, tự chọn địa chỉ giống mạch I2C của LCD rồi kiểm tra lần lượt: nháy đèn nền, lấp đầy ô đen để chỉnh tương phản, hiện chữ và bộ đếm giây. Chưa thấy LCD thì chương trình tự quét lại mỗi 3 giây, sửa dây xong không cần nạp lại.
Chuẩn bị: LCD 1602 hoặc LCD 2004 I2C, ESP32 DevKit V1 hoặc Arduino Nano, dây cắm. Dùng ESP32 thì cần thêm mạch chuyển mức logic hai chiều 3,3 V – 5 V.
| Chân LCD | Arduino Nano, UNO | ESP32 DevKit V1 |
|---|---|---|
| GND | GND | GND |
| VCC | 5V | VIN (5 V khi cắm USB) |
| SDA | A4 | GPIO21, qua mạch chuyển mức |
| SCL | A5 | GPIO22, qua mạch chuyển mức |
Cài thư viện: trong Arduino IDE mở Tools > Manage Libraries, tìm "LiquidCrystal I2C", cài bản của Frank de Brabander. Dùng LCD 2004 thì đổi LCD_COT thành 20 và LCD_HANG thành 4.
// PYWORLD.VN - Kiểm tra LCD I2C từng bước: quét bus I2C, đoán loại chip theo địa chỉ, tự chọn địa chỉ của LCD
// PYWORLD.VN - Nháy đèn nền, lấp đầy ô đen để chỉnh biến trở tương phản, rồi hiện chữ và bộ đếm thời gian
// PYWORLD.VN - Chạy trên ESP32 (SDA GPIO21, SCL GPIO22) và Arduino Nano, UNO (SDA A4, SCL A5); thư viện LiquidCrystal I2C 1.1.2
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const uint8_t LCD_COT = 16; // LCD 2004: đổi thành 20
const uint8_t LCD_HANG = 2; // LCD 2004: đổi thành 4
const unsigned long CHU_KY_QUET_MS = 3000; // chưa thấy LCD thì quét lại sau 3 giây
LiquidCrystal_I2C *lcd = nullptr; // tạo sau khi biết địa chỉ
uint8_t diaChiLcd = 0;
void inHex(uint8_t so) {
Serial.print(F("0x"));
if (so < 0x10) {
Serial.print('0');
}
Serial.print(so, HEX);
}
// Mạch I2C của LCD dùng PCF8574 (0x20–0x27) hoặc PCF8574A (0x38–0x3F); 0x3C, 0x3D thường là màn hình OLED
const __FlashStringHelper *loaiChip(uint8_t dc) {
if (dc == 0x3C || dc == 0x3D) {
return F("thuong la OLED (cung co the la PCF8574A)");
}
if (dc >= 0x20 && dc <= 0x27) {
return F("PCF8574: mach I2C cua LCD");
}
if (dc >= 0x38 && dc <= 0x3F) {
return F("PCF8574A: mach I2C cua LCD");
}
return F("thiet bi I2C khac (cam bien, RTC...)");
}
bool laDiaChiLcd(uint8_t dc) {
return (dc >= 0x20 && dc <= 0x27) || (dc >= 0x38 && dc <= 0x3F && dc != 0x3C && dc != 0x3D);
}
// Quét địa chỉ 1–126, in từng thiết bị tìm thấy; trả về địa chỉ đầu tiên giống mạch I2C của LCD (0 nếu không có)
uint8_t quetI2C() {
uint8_t soThietBi = 0, diaChi = 0;
Serial.println(F("Quet bus I2C..."));
for (uint8_t dc = 1; dc < 127; dc++) {
Wire.beginTransmission(dc);
if (Wire.endTransmission() == 0) {
soThietBi++;
Serial.print(F(" Tim thay "));
inHex(dc);
Serial.print(F(" -> "));
Serial.println(loaiChip(dc));
if (diaChi == 0 && laDiaChiLcd(dc)) {
diaChi = dc;
}
}
}
if (soThietBi == 0) {
Serial.println(F("Khong thay thiet bi I2C nao. Kiem tra GND chung, SDA/SCL co bi dao, LCD da co nguon 5V chua."));
} else if (diaChi == 0) {
Serial.println(F("Co thiet bi I2C nhung khong giong mach I2C cua LCD."));
}
return diaChi;
}
void lapDayOden() {
for (uint8_t hang = 0; hang < LCD_HANG; hang++) {
lcd->setCursor(0, hang);
for (uint8_t cot = 0; cot < LCD_COT; cot++) {
lcd->write(255); // ký tự 255 của LCD là một ô đen đầy
}
}
}
void kiemTraLcd() {
lcd = new LiquidCrystal_I2C(diaChiLcd, LCD_COT, LCD_HANG);
lcd->init();
lcd->backlight();
Serial.print(F("Dung LCD o dia chi "));
inHex(diaChiLcd);
Serial.println();
Serial.println(F("Buoc 1: den nen tat 1 giay roi bat lai. Den khong doi: kiem tra jumper LED sau mach I2C."));
delay(1000);
lcd->noBacklight();
delay(1000);
lcd->backlight();
Serial.println(F("Buoc 2: lap day o den 5 giay. Xoay bien tro xanh den khi o den ro, nen sang."));
lapDayOden();
delay(5000);
Serial.println(F("Buoc 3: hien chu. Chi thay ky tu dau tien: cap nhat thu vien LiquidCrystal I2C."));
char dong[21];
lcd->clear();
snprintf(dong, sizeof(dong), "LCD OK 0x%02X", diaChiLcd);
lcd->setCursor(0, 0);
lcd->print(dong);
if (LCD_HANG >= 4) {
snprintf(dong, sizeof(dong), "Kich thuoc %u x %u", (unsigned)LCD_COT, (unsigned)LCD_HANG);
lcd->setCursor(0, 1);
lcd->print(dong);
lcd->setCursor(0, 2);
lcd->print(F("pyworld.vn"));
}
}
void setup() {
Serial.begin(115200);
delay(500);
Wire.begin(); // ESP32: SDA 21, SCL 22; Nano, UNO: SDA A4, SCL A5
Wire.setClock(100000); // 100 kHz cho dây dài vẫn ổn
Serial.println();
Serial.println(F("PYWORLD - Kiem tra LCD I2C"));
diaChiLcd = quetI2C();
if (diaChiLcd != 0) {
kiemTraLcd();
}
}
void loop() {
static unsigned long lanTruoc = 0;
if (lcd == nullptr) {
// Chưa thấy LCD: sửa dây xong không cần nạp lại, chương trình tự quét lại
if (millis() - lanTruoc >= CHU_KY_QUET_MS) {
lanTruoc = millis();
diaChiLcd = quetI2C();
if (diaChiLcd != 0) {
kiemTraLcd();
}
}
return;
}
if (millis() - lanTruoc >= 1000) {
lanTruoc = millis();
char dong[21];
snprintf(dong, sizeof(dong), "Dem: %lu s", (unsigned long)(millis() / 1000));
lcd->setCursor(0, LCD_HANG - 1);
lcd->print(dong);
}
}
Các điểm chính của chương trình:
Wire.begin()không truyền chân nên dùng chân I2C mặc định: GPIO21, GPIO22 trên ESP32; A4, A5 trên Nano, UNO. Cùng một chương trình nạp được cho cả hai.- Địa chỉ 0x3C, 0x3D thường là màn hình OLED nên chương trình không chọn làm LCD, trừ khi bạn tự sửa.
- Ký tự số 255 trong bảng ký tự của LCD là một ô đen đầy; lấp kín màn hình bằng ký tự này giúp chỉnh biến trở tương phản dễ hơn.
- Đối tượng LCD được tạo sau khi đã biết địa chỉ (
new LiquidCrystal_I2C(...)), nên không phải sửa địa chỉ bằng tay.

5. Bảng lỗi thường gặp
| Hiện tượng | Nguyên nhân hay gặp | Cách sửa |
|---|---|---|
| Hàng trên toàn ô đen | LCD chưa được khởi tạo: sai địa chỉ, sai SDA, SCL | Chạy chương trình kiểm tra, sửa địa chỉ, kiểm tra dây |
| Đèn sáng, không thấy gì | Tương phản quá nhạt, nguồn 3,3 V | Xoay biến trở, cấp 5 V |
| Quét I2C không thấy thiết bị nào | Đảo SDA, SCL; thiếu GND chung; LCD chưa có nguồn | Kiểm tra lại 4 dây, đo 5 V ở chân VCC của LCD |
| Chỉ hiện ký tự đầu tiên của mỗi chuỗi | Thư viện LiquidCrystal I2C bản cũ | Cài bản 1.1.2 |
| LCD 2004 chữ nhảy sang dòng 3, dòng 4 trống | Khai báo 16 cột, 2 hàng | Khai báo 20 cột, 4 hàng |
| Chữ tiếng Việt thành ký tự lạ | Bảng ký tự của LCD không có chữ có dấu | Viết không dấu, hoặc tự tạo tối đa 8 ký tự bằng createChar() |
| Chạy một lúc thì chữ lộn xộn | Dây dài, nhiễu từ relay, động cơ, nguồn yếu | Dây ngắn, đi xa dây tải; gọi lại lcd.init() khi cần |
6. Xem thêm
- Dùng màn hình LCD 2004 I2C với ESP32: nối dây, hiển thị 4 dòng, tạo ký tự riêng, bộ đếm thời gian.
- Dùng màn hình LCD 1602 I2C với Arduino Uno.
- Giao tiếp I2C trên ESP32: quét địa chỉ, đổi chân SDA/SCL, nối nhiều thiết bị.
- Bài chọn màn hình cho ESP32, Arduino (OLED hay LCD) trong cùng đợt bài này.
Cần hỗ trợ khi LCD vẫn không hiện chữ, liên hệ PYWORLD qua số 0915703319.
Xem thêm