Fix LCD1602 blank display: HD44780 timing, pinmap switch, contrast hint.

Match LiquidCrystal_I2C init delays; add Map button for alternate PCF8574 wiring; warn about blue contrast pot.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-23 14:30:21 +08:00
parent e3f4ea89ff
commit 8ad00d8718
6 changed files with 157 additions and 70 deletions

View File

@@ -21,14 +21,20 @@ LCD1602A(HD44780)经 **PCF8574 I2C 转接板** 驱动:改两行文字、
| SDA | GPIO22 |
| SCL | GPIO27 |
上电会扫描 `0x27 / 0x3F / 0x26 / 0x3E`。串口与 TFT 状态栏会显示是否找到地址。
上电会扫描 `0x27 / 0x3F / 0x26 / 0x3E` 等。串口与 TFT 状态栏会显示是否找到地址。
### 背光有、字没有?
1. **先拧 I2C 转接板上的蓝色电位器(对比度)**——这是最常见原因;背光晶体管和字符是两回事。
2. 供电优先接 **5V**(不是 3.3V)。
3. 仍无字:点 TFT 上 **Map** 切换另一种 PCF8574 脚位映射后再试 **Hello**。
## 操作
- 中间两行:当前 LCD 内容预览(16 字符/行)
- **Demo**:循环示例文案
- **Hello**:`Hello World!` / `LCD1602A`
- **Clear**:清屏
- **Map**:切换 I2C 背包脚位映射并重新初始化
- **BL ON/OFF**:背光开关
## 构建 / 烧录

View File

@@ -1,5 +1,5 @@
name: CYD LCD1602A (cyd)
git: a4c7d54
git: e3f4ea8
date: 2026-10-23
board: ESP32-2432S028R (ILI9341)
features: SoftSPI touch, LVGL, LCD1602A I2C SDA=GPIO22 SCL=GPIO27 PCF8574
@@ -12,5 +12,5 @@ offsets:
sha256:
bootloader.bin 3D234A7471F67B013686DABD4DEE7C1FA915C9928463616A94BC9297ACF1ABF8
boot_app0.bin F94C5D786A7A8FAB06AC5D10E33BF37711A6697636DC037559EA19CC410A17F0
firmware.bin 6A01742209690884333761FB7C4437965DF816F51771E93539A39CE32FD0E8C4
firmware.bin 82F140A15F1E6312DDB8A88782FC1A9D912ED521148A94913701646886AC4B77
partitions.bin 148B959CBFF1C38AA8E1D5C0BA9D612C54997B945E56A63F41223EEF650653A1

Binary file not shown.

View File

@@ -8,6 +8,11 @@ void lcd_init();
bool lcd_ok();
uint8_t lcd_addr();
/** 0=标准 YwRobot(P0=RS..),1=部分国产板(EN/RS 换脚) */
uint8_t lcd_pinmap();
void lcd_set_pinmap(uint8_t map);
void lcd_reinit();
void lcd_set_backlight(bool on);
bool lcd_backlight();
@@ -15,6 +20,5 @@ void lcd_clear();
void lcd_set_line(uint8_t row, const char *text);
void lcd_get_line(uint8_t row, char *out, size_t out_len);
/** 写入两行示例文案并点亮背光 */
void lcd_demo(uint8_t index);
uint8_t lcd_demo_count();

View File

@@ -5,13 +5,25 @@
#include <Wire.h>
#include <string.h>
// PCF8574 背包常见接线:P0=RS P1=RW P2=E P3=BL P4..P7=D4..D7
static constexpr uint8_t BIT_RS = 0x01;
static constexpr uint8_t BIT_RW = 0x02;
static constexpr uint8_t BIT_E = 0x04;
static constexpr uint8_t BIT_BL = 0x08;
// 两套常见 PCF8574→HD44780 接线(背光位都在 bit3 / bit7 两种)
struct PinMap {
uint8_t rs;
uint8_t rw;
uint8_t en;
uint8_t bl;
uint8_t d4; // bit index 0..7 for D4; D5=d4+1 ... assumed contiguous high nibble OR scattered
bool data_high_nibble; // true: D4..D7 on P4..P7
const char *name;
};
static constexpr uint8_t CANDIDATE_ADDRS[] = {0x27, 0x3F, 0x26, 0x3E};
// Map0: YwRobot / 绝大多数 LiquidCrystal_I2C — P0=RS P1=RW P2=E P3=BL P4..7=D4..7
// Map1: 部分模块把控制脚挪到高位 — P0..3=D4..7 P4=E P5=RW P6=RS P7=BL
static const PinMap PINMAPS[] = {
{0, 1, 2, 3, 4, true, "std P0=RS"},
{6, 5, 4, 7, 0, false, "alt P6=RS"},
};
static constexpr uint8_t CANDIDATE_ADDRS[] = {0x27, 0x3F, 0x26, 0x3E, 0x20, 0x38};
struct DemoMsg {
const char *line0;
@@ -21,16 +33,21 @@ struct DemoMsg {
static const DemoMsg DEMOS[] = {
{"Hello CYD!", "LCD1602A I2C"},
{"SDA=GPIO22", "SCL=GPIO27"},
{"Line1 16 chars!", "Line2 ready...."},
{"Turn blue pot", "for contrast!"},
{"Backlight ON", "Touch UI demo"},
};
static bool g_ok = false;
static bool g_bl = true;
static uint8_t g_addr = 0;
static uint8_t g_map = 0;
static char g_lines[LCD_ROWS][LCD_COLS + 1];
static uint8_t bl_mask() { return g_bl ? BIT_BL : 0; }
static const PinMap &pm() { return PINMAPS[g_map % (sizeof(PINMAPS) / sizeof(PINMAPS[0]))]; }
static uint8_t bit_of(uint8_t idx) { return (uint8_t)(1u << idx); }
static uint8_t bl_mask() { return g_bl ? bit_of(pm().bl) : 0; }
static bool i2c_write(uint8_t data) {
Wire.beginTransmission(g_addr);
@@ -38,32 +55,57 @@ static bool i2c_write(uint8_t data) {
return Wire.endTransmission() == 0;
}
static uint8_t pack_nibble(uint8_t nibble_hi_or_val, uint8_t mode_rs) {
const PinMap &m = pm();
uint8_t out = bl_mask();
if (mode_rs) {
out = (uint8_t)(out | bit_of(m.rs));
}
// RW always 0 (write)
if (m.data_high_nibble) {
// nibble in bits 7..4
out = (uint8_t)(out | (nibble_hi_or_val & 0xF0));
} else {
// D4..D7 on P0..P3: put high nibble into low nibble
out = (uint8_t)(out | ((nibble_hi_or_val >> 4) & 0x0F));
}
return out;
}
static void pulse_enable(uint8_t data) {
i2c_write((uint8_t)(data | BIT_E));
const uint8_t en = bit_of(pm().en);
i2c_write((uint8_t)(data | en));
delayMicroseconds(1);
i2c_write((uint8_t)(data & (uint8_t)~BIT_E));
i2c_write((uint8_t)(data & (uint8_t)~en));
delayMicroseconds(50);
}
static void write4(uint8_t nibble, uint8_t mode) {
const uint8_t data = (uint8_t)((nibble & 0xF0) | mode | bl_mask());
static void write4(uint8_t value_hi_nibble, bool rs) {
// value_hi_nibble: data in upper 4 bits (same as LiquidCrystal_I2C write4bits)
const uint8_t data = pack_nibble(value_hi_nibble, rs ? 1 : 0);
i2c_write(data);
pulse_enable(data);
}
static void send(uint8_t value, uint8_t mode) {
write4((uint8_t)(value & 0xF0), mode);
write4((uint8_t)((value << 4) & 0xF0), mode);
static void send(uint8_t value, bool rs) {
write4((uint8_t)(value & 0xF0), rs);
write4((uint8_t)((value << 4) & 0xF0), rs);
}
static void cmd(uint8_t c) {
send(c, 0);
if (c <= 0x03) {
delay(2);
send(c, false);
// clear/home need >1.52ms
if (c == 0x01 || c == 0x02) {
delay(3);
} else {
delayMicroseconds(40);
}
}
static void data(uint8_t d) { send(d, BIT_RS); }
static void data(uint8_t d) {
send(d, true);
delayMicroseconds(40);
}
static void pad_copy(char *dst, const char *src) {
size_t i = 0;
@@ -93,37 +135,51 @@ static bool probe_addr(uint8_t addr) {
return Wire.endTransmission() == 0;
}
static bool hd44780_init() {
// 严格按 LiquidCrystal_I2C::begin 时序
static void hd44780_init() {
delay(50);
// 强制 8-bit 再切 4-bit(手册上电时序)
write4(0x30, 0);
delay(5);
write4(0x30, 0);
i2c_write(bl_mask());
delay(200);
// 复位到 8-bit,再切 4-bit
write4(0x30, false);
delayMicroseconds(4500);
write4(0x30, false);
delayMicroseconds(4500);
write4(0x30, false);
delayMicroseconds(150);
write4(0x30, 0);
delayMicroseconds(150);
write4(0x20, 0); // 4-bit
write4(0x20, false); // 4-bit
delayMicroseconds(150);
cmd(0x28); // 4-bit, 2 lines, 5x8
cmd(0x0C); // display on, cursor off
cmd(0x06); // entry mode
cmd(0x28); // 4-bit, 2-line, 5x8
cmd(0x0C); // display on, cursor off, blink off
cmd(0x06); // entry: increment, no shift
cmd(0x01); // clear
delay(2);
return true;
delay(3);
}
static void apply_lines() {
write_row(0);
write_row(1);
}
void lcd_init() {
memset(g_lines, 0, sizeof(g_lines));
pad_copy(g_lines[0], "LCD1602A");
pad_copy(g_lines[1], "init...");
pad_copy(g_lines[1], "turn blue pot");
Wire.begin(LCD_I2C_SDA_PIN, LCD_I2C_SCL_PIN, (uint32_t)LCD_I2C_FREQ_HZ);
delay(20);
Wire.end();
delay(2);
Wire.begin(LCD_I2C_SDA_PIN, LCD_I2C_SCL_PIN);
Wire.setClock(LCD_I2C_FREQ_HZ);
delay(50);
g_ok = false;
g_addr = 0;
g_bl = true;
if (g_map >= (sizeof(PINMAPS) / sizeof(PINMAPS[0]))) {
g_map = 0;
}
for (uint8_t a : CANDIDATE_ADDRS) {
if (probe_addr(a)) {
@@ -135,33 +191,40 @@ void lcd_init() {
}
if (!g_ok) {
Serial.printf("LCD1602A NOT FOUND (SDA=GPIO%d SCL=GPIO%d). Tried:",
Serial.printf("LCD1602A NOT FOUND (SDA=GPIO%d SCL=GPIO%d)\n",
LCD_I2C_SDA_PIN, LCD_I2C_SCL_PIN);
for (uint8_t a : CANDIDATE_ADDRS) {
Serial.printf(" 0x%02X", a);
}
Serial.println();
pad_copy(g_lines[0], "NO I2C LCD");
pad_copy(g_lines[1], "check wiring");
return;
}
if (!hd44780_init()) {
g_ok = false;
Serial.println("LCD1602A HD44780 init failed");
return;
}
write_row(0);
write_row(1);
Serial.printf("LCD1602A OK SDA=GPIO%d SCL=GPIO%d addr=0x%02X\n",
LCD_I2C_SDA_PIN, LCD_I2C_SCL_PIN, g_addr);
hd44780_init();
apply_lines();
Serial.printf("LCD1602A OK addr=0x%02X map=%u(%s) — if blank, turn BLUE pot\n",
g_addr, (unsigned)g_map, pm().name);
}
bool lcd_ok() { return g_ok; }
uint8_t lcd_addr() { return g_addr; }
uint8_t lcd_pinmap() { return g_map; }
void lcd_set_pinmap(uint8_t map) {
const uint8_t n = (uint8_t)(sizeof(PINMAPS) / sizeof(PINMAPS[0]));
g_map = (uint8_t)(map % n);
Serial.printf("LCD pinmap -> %u (%s)\n", (unsigned)g_map, pm().name);
if (g_ok) {
hd44780_init();
apply_lines();
}
}
void lcd_reinit() {
Serial.println("LCD reinit");
lcd_init();
}
void lcd_set_backlight(bool on) {
g_bl = on;
if (!g_ok) {
@@ -180,7 +243,6 @@ void lcd_clear() {
return;
}
cmd(0x01);
delay(2);
Serial.println("LCD clear");
}

View File

@@ -14,16 +14,19 @@ static constexpr uint32_t COL_ON = 0x1B5E4A;
static constexpr uint32_t COL_ACCENT = 0x7CDBFF;
static constexpr uint32_t COL_DIM = 0x3A4656;
static constexpr uint32_t COL_ERR = 0xC62828;
static constexpr uint32_t COL_WARN = 0xFFB74D;
static lv_obj_t *lbl_title;
static lv_obj_t *lbl_pin;
static lv_obj_t *lbl_status;
static lv_obj_t *lbl_hint;
static lv_obj_t *lbl_line0;
static lv_obj_t *lbl_line1;
static lv_obj_t *btn_demo;
static lv_obj_t *lbl_demo;
static lv_obj_t *btn_hello;
static lv_obj_t *btn_clear;
static lv_obj_t *btn_map;
static lv_obj_t *lbl_map;
static lv_obj_t *btn_bl;
static lv_obj_t *lbl_bl;
@@ -72,7 +75,8 @@ void ui_refresh_lcd() {
if (lbl_status) {
if (lcd_ok()) {
lv_label_set_text_fmt(lbl_status, "OK addr 0x%02X", lcd_addr());
lv_label_set_text_fmt(lbl_status, "OK 0x%02X map%u", lcd_addr(),
(unsigned)lcd_pinmap());
lv_obj_set_style_text_color(lbl_status, lv_color_hex(COL_ON), 0);
} else {
lv_label_set_text(lbl_status, "FAIL no PCF8574");
@@ -91,6 +95,9 @@ void ui_refresh_lcd() {
(unsigned)(g_demo_idx % lcd_demo_count() + 1),
(unsigned)lcd_demo_count());
}
if (lbl_map) {
lv_label_set_text_fmt(lbl_map, "Map %u", (unsigned)lcd_pinmap());
}
}
static void on_demo(lv_event_t *e) {
@@ -112,11 +119,14 @@ static void on_hello(lv_event_t *e) {
ui_refresh_lcd();
}
static void on_clear(lv_event_t *e) {
static void on_map(lv_event_t *e) {
if (lv_event_get_code(e) != LV_EVENT_CLICKED) {
return;
}
lcd_clear();
lcd_set_pinmap((uint8_t)(lcd_pinmap() + 1));
lcd_set_backlight(true);
lcd_set_line(0, "Hello World!");
lcd_set_line(1, "try Map / pot");
ui_refresh_lcd();
}
@@ -137,48 +147,53 @@ void ui_create() {
lv_label_set_text(lbl_title, "LCD1602A");
lv_obj_set_style_text_color(lbl_title, lv_color_hex(COL_INK), 0);
lv_obj_set_style_text_font(lbl_title, &lv_font_montserrat_28, 0);
lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 0, 4);
lv_obj_align(lbl_title, LV_ALIGN_TOP_MID, 0, 2);
lbl_pin = lv_label_create(scr);
lv_label_set_text_fmt(lbl_pin, "I2C SDA%d SCL%d PCF8574", LCD_I2C_SDA_PIN,
LCD_I2C_SCL_PIN);
lv_obj_set_style_text_color(lbl_pin, lv_color_hex(COL_MUTED), 0);
lv_obj_set_style_text_font(lbl_pin, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_pin, LV_ALIGN_TOP_MID, 0, 36);
lv_obj_align(lbl_pin, LV_ALIGN_TOP_MID, 0, 32);
lbl_status = lv_label_create(scr);
lv_label_set_text(lbl_status, "...");
lv_obj_set_style_text_font(lbl_status, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_status, LV_ALIGN_TOP_MID, 0, 54);
lv_obj_align(lbl_status, LV_ALIGN_TOP_MID, 0, 50);
lbl_hint = lv_label_create(scr);
lv_label_set_text(lbl_hint, "Blank? turn BLUE pot on backpack");
lv_obj_set_style_text_color(lbl_hint, lv_color_hex(COL_WARN), 0);
lv_obj_set_style_text_font(lbl_hint, &lv_font_montserrat_14, 0);
lv_obj_align(lbl_hint, LV_ALIGN_TOP_MID, 0, 68);
lbl_line0 = lv_label_create(scr);
lv_label_set_text(lbl_line0, "[ ]");
lv_obj_set_style_text_color(lbl_line0, lv_color_hex(COL_ACCENT), 0);
lv_obj_set_style_text_font(lbl_line0, &lv_font_montserrat_18, 0);
lv_obj_align(lbl_line0, LV_ALIGN_TOP_MID, 0, 78);
lv_obj_align(lbl_line0, LV_ALIGN_TOP_MID, 0, 90);
lbl_line1 = lv_label_create(scr);
lv_label_set_text(lbl_line1, "[ ]");
lv_obj_set_style_text_color(lbl_line1, lv_color_hex(COL_ACCENT), 0);
lv_obj_set_style_text_font(lbl_line1, &lv_font_montserrat_18, 0);
lv_obj_align(lbl_line1, LV_ALIGN_TOP_MID, 0, 104);
lv_obj_align(lbl_line1, LV_ALIGN_TOP_MID, 0, 116);
const int16_t gap = 8;
const int16_t bw = (UI_HOR_RES - 48 - gap) / 2;
const int16_t bx0 = 24;
const int16_t row0 = 140;
const int16_t row1 = 188;
const int16_t row0 = 148;
const int16_t row1 = 196;
btn_demo = make_btn(scr, "Demo", bx0, row0, bw, 40, on_demo, &lbl_demo);
btn_hello = make_btn(scr, "Hello", bx0 + bw + gap, row0, bw, 40, on_hello,
nullptr);
btn_clear =
make_btn(scr, "Clear", bx0, row1, bw, 40, on_clear, nullptr);
btn_map = make_btn(scr, "Map 0", bx0, row1, bw, 40, on_map, &lbl_map);
btn_bl = make_btn(scr, "BL OFF", bx0 + bw + gap, row1, bw, 40, on_bl, &lbl_bl);
(void)btn_demo;
(void)btn_hello;
(void)btn_clear;
(void)btn_map;
ui_refresh_lcd();
}