在 4-line serial I/F Ⅰ(IM[3:0]=0110) 模式下,ST7789 的 SDA 线是双向的(复用 MOSI),
因此可以用一根线完成"主机发命令 → 屏回数据"的半双工读操作。本仓库给出一套在
GD32F330 上验证可用的读/写代码,并固化了调试过程中踩到的两个关键坑:
- RDDPM(0x0A) 必须用 8bit 时序才能稳定读出
0x9C; - RDDID(0x04) 必须连读两次(首读是"预热",返回 0x00;次读才是真实
85 85 52)。
代码同时提供面板复位恢复(无需重启 MCU)。
| 信号 | 引脚 (GD32F330 HARDWARE_MK) | 说明 |
|---|---|---|
| SCK | PA5 | SPI 时钟(读时由 GPIO 接管) |
| SDA | PA7 (MOSI) | 双向数据线(I/F Ⅰ) |
| CS | PA6 | 片选 |
| DCX | PC4 (A0) | 命令/数据选择 |
| RST | PA4 | 硬件复位 |
模组必须配置为 4-line I/F Ⅰ(IM[3:0]=0110,有 SDA 无独立 SDO)。若你的板子是 I/F Ⅱ/Ⅲ(独立 SDO),读方案会不同,本代码不适用。
- 主机先发 1 字节读命令(D/CX=0),此后方向反向(SDA 由屏回驱)。
- 屏在 SCL 下降沿移位输出 SDA,主机在 SCL 上升沿采样 SDA。
- 命令发完后,SDA 必须不晚于命令字节最后一个 SCL 下降沿置为三态(Hi-Z)。
- 注意:是真三态(
GPIO_PUPD_NONE),不能用上/下拉,否则不是 Hi-Z。
- 注意:是真三态(
- 读完 CSX 必须回到高电平,才能发下一条命令。
- 读回数据的第 1 个 parameter 是 dummy(don't-care),正常读时序会用一个 dummy SCL
周期把它"吃掉",所以
buf[0]拿到的就是第一个真实参数。
实现上:读的时候用 GPIO 临时接管 PA5/PA7,读完原样恢复 SPI0 的 AF 配置,不影响后续写屏。
| 时序 | SCL 空闲 | 命令后 dummy | CS | 用于 |
|---|---|---|---|---|
EX (read_reg) |
高 (CPOL=1) | 有 1 个 dummy SCL | 全程低 | RDDID(0x04)、RDDST(0x09)、RDDSDR(0x0F) |
8bit (read_reg_8bit) |
低 (CPOL=0) | 无 dummy | 全程低(可选中间翻转一次) | RDDPM(0x0A) |
为什么 0x0A 必须用 8bit 时序? 实测:
- 8bit 时序(SCL 空闲低 + 无 dummy + CS 全程低 + 命令/数据段间 ~2us 换向空档)下,
0x0A 稳定读出
9C 00 00 ...; - 用 EX 时序(SCL 空闲高 + 有 dummy)读 0x0A,结果不稳定(常返
00或噪声)。
代码默认调用
st7789_1wire_read_reg_8bit(0x0A, ..., cs_toggle=0, gap_loops=ST7789_BB_GAP_2US_LOOPS)。cs_toggle=1会按 8bit 时序图在命令段/数据段之间翻转一次 CS,但实测cs_toggle=0(CS 全程低)才读到 0x9C。
现象:0x04 单次读返回全 00;连读两次后,第二次返回 85 85 52。
机理:ST7789 的读回移位寄存器需要**一次读命令"充能"**后才能输出有效数据。第一次读时 SDA 尚未被驱动,返回 0x00(或 stale 值);第二次读时寄存器已就绪,输出真实 ID。
闭环证据(原工程里的"时灵时不灵"):
- 旧基线里 0x04 之前恰好有 3 次 0x0A(EX 版)读,那 3 次读充当了预热 → 单次即
85 85 52; - 把 0x0A 改成 8bit 时序后,前序不再是 EX 预热 → 0x04 单次变 0x00;
- 把 0x04 排到 0x0A 之前(无前序读)也变 0x00;
- 0x04 连读两次 → 第二次
85 85 52。
结论:读 0x04 一律用 st7789_1wire_read_id(),它内部连读两次并取第二遍:
uint8_t id[3];
if (st7789_1wire_read_id(id)) {
/* id = {0x85, 0x85, 0x52} */
}| 寄存器 | 命令 | 正常读值 | 含义 |
|---|---|---|---|
| RDDPM | 0x0A | 0x9C |
BSTON + SLPOUT + NORON + DISON |
| RDDID | 0x04 | 85 85 52 |
固定厂商 ID(需连读两次) |
| RDDST | 0x09 | 80 53 26 00 |
显示状态全集 |
| RDDSDR | 0x0F | 80 00 |
D7=1 表示自检通过 |
异常/复位态:若 0x0A 读回 0x08(仅 NORON=1),说明面板处于默认/未初始化态,需执行复位恢复(见第 6 节);此时 0x04 仍可读出 85 85 52,证明通信完好,可软件复位恢复。
判定:周期性读 0x0A(RDDPM),若 != 0x9C 即判定面板异常,需执行复位。
恢复序列(无需重启 MCU):
0x28 (DISPOFF) → 0x10 (SLPIN) → RST 硬件复位脉冲 → 重新下发初始化序列(st7735s_init)
→ 重绘当前界面
代码封装为 st7789_1wire_panel_recover()。建议配合定时器每 ~2s 调用
st7789_1wire_panel_check(),异常即 recover():
static uint32_t s_ms = 0;
uint32_t now = st7789_user_tick(); /* 你的 ms 时钟 */
if (now - s_ms >= 2000u) {
s_ms = now;
if (st7789_1wire_panel_check() == 0u) {
st7789_1wire_panel_recover();
}
}注意:读窗口建议用
__disable_irq()/__enable_irq()包住 (见原工程app_lcd_read_reg_poll()),防止中断级的 SPI 访问打断 GPIO 位带读。 恢复函数内部走 SPI0 写路径,必须在读窗口之外调用。
st7789_1wire_write_cmd(cmd)—— DCX=0 发命令st7789_1wire_write_data(data)—— DCX=1 发数据st7789_1wire_write_stream(p,len)—— DCX=1 连续发像素流
为了防止打断正在进行的写(如 DMA 传输),读之前应保证写已空闲。本模块用轮询 SPI
实现写,天然无 DMA 冲突;若你的工程用 DMA 写屏,请在调用读 API 前先等待 DMA 完成
(原工程用 app_lcd_is_write_done() 做这件事)。
| 函数 | 说明 |
|---|---|
st7789_1wire_hw_init() |
RCU + GPIO(RST/CS/DCX) + SPI0 主模式初始化 |
st7789_1wire_set_dcx(is_data) |
设置 DCX(0=命令 / 非0=数据) |
st7789_1wire_write_cmd/data/stream |
写命令 / 数据 / 流 |
st7789_1wire_read_reg(cmd,buf,len) |
EX 时序读(0x04/0x09/0x0F) |
st7789_1wire_read_reg_8bit(cmd,buf,len,cs_toggle,gap) |
8bit 时序读(0x0A) |
st7789_1wire_read_id(id3) |
RDDID 预热双读,返回是否匹配 85 85 52 |
st7789_1wire_panel_check() |
读 0x0A,1=正常 / 0=异常 |
st7789_1wire_panel_recover() |
复位恢复(DISPOFF→SLPIN→RST→init) |
需用户实现的钩子(已提供 weak 空桩,务必覆盖):
st7789_user_panel_init()—— RST 后的重新初始化(你的st7735s_init())st7789_user_frame_refresh()—— 恢复后重绘界面(可选)st7789_user_delay_ms(ms)—— 必须覆盖为真实毫秒延时,否则恢复时序无效
- 改
st7789_1wire.h顶部的引脚宏(SCK/SDA/CS/DCX/RST)到你的板子。 - 若不是 GD32F3x0:把
.c里的gpio_* / spi_* / rcu_*换成你的 MCU SDK 等价函数, 位带收发逻辑(CPOL、三态、上升沿采样)保持不变。 - 调时序:若读值不稳,先调
ST7789_BB_DELAY_*和ST7789_BB_GAP_2US_LOOPS(随主频变化,需实测微调)。 - SPI0 若你的工程已初始化,可跳过
st7789_1wire_hw_init()里的_spi0_master_init(), 仅用 GPIO 部分即可。 - 实现三个 user 钩子,尤其是
st7789_user_delay_ms()。
正常屏(每 2~3s 轮询):
0Ah : 9C 00 00 00 00 00
04h : 85 85 52 00 00 00
09h : 80 53 26 00 00 00
0Fh : 80 00
异常/复位态(0x0A 读回 0x08 时):
0Ah : 08 00 00 00 00 00
04h : 85 85 52 00 00 00
09h : 00 51 00 00 00 00
0Fh : 00 00
此时
0x04的 RDDID 仍可读出85 85 52,说明通信完好,按第 6 节复位流程即可恢复。
- 0x0A 与 0x04/0x09/0x0F 的读时序不同,切勿混用(0x0A 用 8bit,其余用 EX)。
- 0x04 一定要连读两次,单读必为 0x00。
- 读操作会临时把 SCK/SDA 从 SPI0 AF 切到 GPIO,务必在读前保证写已空闲,读完恢复 AF。
- 若打开调试串口日志且串口与 SPI/DMA 通道冲突(原工程
DEBUG_LOG开启会冻屏), 产品态应关闭调试日志。