Skip to content

About

使用GPIO模拟SPI来读取ST7789的寄存器状态

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

ST7789 单线半双工读/写驱动(st7789_1wire)

在 4-line serial I/F Ⅰ(IM[3:0]=0110) 模式下,ST7789 的 SDA 线是双向的(复用 MOSI), 因此可以用一根线完成"主机发命令 → 屏回数据"的半双工读操作。本仓库给出一套在 GD32F330 上验证可用的读/写代码,并固化了调试过程中踩到的两个关键坑:

  1. RDDPM(0x0A) 必须用 8bit 时序才能稳定读出 0x9C;
  2. RDDID(0x04) 必须连读两次(首读是"预热",返回 0x00;次读才是真实 85 85 52)。

代码同时提供面板复位恢复(无需重启 MCU)。


1. 适用硬件与接线

信号 引脚 (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),读方案会不同,本代码不适用。


2. 单线读原理(datasheet §8.4.3 / §8.4.5)

  1. 主机先发 1 字节读命令(D/CX=0),此后方向反向(SDA 由屏回驱)。
  2. 屏在 SCL 下降沿移位输出 SDA,主机在 SCL 上升沿采样 SDA。
  3. 命令发完后,SDA 必须不晚于命令字节最后一个 SCL 下降沿置为三态(Hi-Z)。
    • 注意:是真三态(GPIO_PUPD_NONE),不能用上/下拉,否则不是 Hi-Z。
  4. 读完 CSX 必须回到高电平,才能发下一条命令。
  5. 读回数据的第 1 个 parameter 是 dummy(don't-care),正常读时序会用一个 dummy SCL 周期把它"吃掉",所以 buf[0] 拿到的就是第一个真实参数。

实现上:读的时候用 GPIO 临时接管 PA5/PA7,读完原样恢复 SPI0 的 AF 配置,不影响后续写屏。


3. 两套读时序(关键)

时序 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。


4. 重点坑:RDDID(0x04) 必须连读两次(priming read)

现象: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} */
}

5. 四个寄存器预期值(实测)

寄存器 命令 正常读值 含义
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,证明通信完好,可软件复位恢复。


6. 面板复位恢复

判定:周期性读 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 写路径,必须在读窗口之外调用。


7. 写方案(SPI0 硬件,CPOL=0/CPHA=0,8-bit,MSB first)

  • 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() 做这件事)。


8. API 速查

函数 说明
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) —— 必须覆盖为真实毫秒延时,否则恢复时序无效

9. 移植指南

  1. 改 st7789_1wire.h 顶部的引脚宏(SCK/SDA/CS/DCX/RST)到你的板子。
  2. 若不是 GD32F3x0:把 .c 里的 gpio_* / spi_* / rcu_* 换成你的 MCU SDK 等价函数, 位带收发逻辑(CPOL、三态、上升沿采样)保持不变。
  3. 调时序:若读值不稳,先调 ST7789_BB_DELAY_* 和 ST7789_BB_GAP_2US_LOOPS (随主频变化,需实测微调)。
  4. SPI0 若你的工程已初始化,可跳过 st7789_1wire_hw_init() 里的 _spi0_master_init(), 仅用 GPIO 部分即可。
  5. 实现三个 user 钩子,尤其是 st7789_user_delay_ms()。

10. 实测数据记录

正常屏(每 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 节复位流程即可恢复。


11. 已知注意点

  • 0x0A 与 0x04/0x09/0x0F 的读时序不同,切勿混用(0x0A 用 8bit,其余用 EX)。
  • 0x04 一定要连读两次,单读必为 0x00。
  • 读操作会临时把 SCK/SDA 从 SPI0 AF 切到 GPIO,务必在读前保证写已空闲,读完恢复 AF。
  • 若打开调试串口日志且串口与 SPI/DMA 通道冲突(原工程 DEBUG_LOG 开启会冻屏), 产品态应关闭调试日志。

About

使用GPIO模拟SPI来读取ST7789的寄存器状态

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages