第三十六章 RGB LCD触摸屏实验

本章在讲什么

前一章我们用 I2C 读 EEPROM、读 RTC,本章让 I2C 迎来第三位"对话者"——电容触摸屏控制器。手指按在屏幕上,触摸芯片把触摸点坐标写进自己的寄存器,FPGA 通过 I2C 把坐标读出来,再显示到 LCD 屏幕上。这是整本实战篇的收官实验,也是第一次让"输入"(触摸)和"输出"(显示)在同一系统里联动。

本章有四个看点:

  • 一个驱动应付两家芯片:开发板配套的触摸屏可能用 GT911(汇顶 GT 系列)也可能用 FT5206(敦泰 FT 系列),两者 I2C 地址、寄存器位宽、坐标格式全都不一样,代码要能"自动识别、按需解析";
  • i2c_dri 驱动的最终形态:在前几章版本基础上升级出连续读写能力,从此这个模块可以通吃几乎所有 I2C 器件;
  • 又一颗状态机驱动的芯片:touch_dri 七状态状态机,上电配置→检测触摸→读坐标→清除标志,循环往复;
  • 二进制转 BCD:坐标是二进制数,显示要用十进制每一位,这一章把这个转换模块补齐。

核心概念拆解

36.1 电容触摸屏与两系触摸控制器

电容触摸屏的四角有电极,手指靠近时会改变触点处的电容,触摸控制器(就焊在屏幕排线上)扫描出触点坐标后存进自己的寄存器,同时拉低 INT 引脚通知主机"有新数据了"。FPGA 要做的就是:看到 INT 有效→通过 I2C 读坐标寄存器→读完后清掉芯片的状态标志。

两系控制器的差异一览:

对比项 GT 系列(GT911) FT 系列(FT5206)
I2C 器件地址 7'h14(INT 高电平期间复位)或 0x5D 7'h38
寄存器地址位宽 16 位 8 位
触摸状态寄存器 0x814E(bit7 是 buffer 状态,读后必须写 0 清除 0x02
坐标寄存器 0x8150~0x8153:X 低 8 位、X 高 8 位、Y 低 8 位、Y 高 8 位 0x03~0x06:X 高 4 位、X 低 8 位、Y 高 4 位、Y 低 8 位
坐标方向定义 横屏方向 竖屏方向

最后一个差异最隐蔽:两颗芯片对 X/Y 轴的定义方向不同,所以 FT 的坐标数据不光要按高低位拼起来,还要交换 X 和 Y才能与屏幕位置对应。

36.2 系统架构:触摸通道 + 显示通道

top_lcd_touch
 ├── touch_top                 (触摸通道)
 │    ├── touch_dri            (七状态状态机:识别芯片/配置/读坐标)
 │    └── i2c_dri              (通用 I2C 驱动,最终形态)
 ├── lcd_rgb_char              (显示通道)
 │    ├── rd_id / clk_div / lcd_driver
 │    ├── binary2bcd ×2        (X、Y 坐标各自转 BCD)
 │    └── lcd_display          (屏幕上显示坐标数值)
 └── (ILA 在线调试核,抓取 touch_valid 与坐标)

触摸通道输出 32 位的 data(高 16 位 X 坐标、低 16 位 Y 坐标),显示通道把它转成十进制画到屏幕上。屏上同时显示一个提示字符,触摸时坐标数字实时变化。

36.3 i2c_dri 升级:从"专用"到"通用"

本章的 i2c_dri 是第三十三章版本的升级,改动有三处:

  1. 器件地址和字地址位宽改成端口slave_addr[6:0]bit_ctrl(0 表示 8 位寄存器地址、1 表示 16 位)不再写死为参数,而由上层业务模块实时给出——同一个驱动,接 EEPROM 时给 7'h50/1,接 RTC 时给 7'h51/0,接触摸时按芯片给 7'h14 或 7'h38;
  2. 新增 reg_num 端口:一次操作连续读/写几个寄存器。读数据状态里以 reg_cnt == reg_num-1 判断是否到了最后一字节,是则回非应答并停止,否则继续应答接收;
  3. 新增 once_byte_done:每收/发完一字节拉高一拍,让上层不必等整个连续操作结束就能逐字节取数。

读时序的"虚写+重复起始"骨架与第三十三章完全一致。这次升级之后,i2c_dri 基本成为通用 I2C 主机控制器,后面遇到任何 I2C 器件都能直接复用——这是前几章反复打磨这个模块的回报。

36.4 touch_dri:七状态状态机与双向 INT 引脚

touch_dri 的状态流转:

st_idle → st_init → st_get_id → st_cfg_reg → st_check_touch
                                                ↓ (touch_valid=1)
        st_coord_handle ← st_get_coord ←────────┘
              └────── 回到 st_check_touch 循环

上电时序(st_init,决定 GT911 的 I2C 地址):GT 系列的器件地址由复位期间 INT 引脚的电平决定——INT 为高是 0x14(本实验采用),为低是 0x5D。流程为:先拉低复位引脚 10ms,随后拉高复位;在 INT 上输出高电平并保持约 50ms;最后把 INT 切换为浮空输入,交给芯片接管。INT 是双向引脚,与 SDA 一样用三态门控制:

assign touch_int = touch_int_dir ? touch_int_out : 1'bz;

芯片识别(st_get_id):先看 LCD ID——ID 为 16'h4384/4342/1018 的屏固定用 GT 系列;否则读 FT 系列的版本号寄存器(0xA1~0xA2),读到 0x3003/0001/0002/0000 之一判为 FT,I2C 无应答或版本号对不上就判为 GT。识别结果存入 ft_flag,后续所有分支都由它决定。

配置寄存器(st_cfg_reg):FT 系列需写入工作模式寄存器(0x00,正常扫描模式)、有效触摸门限寄存器(0x80,写 22,值越小越灵敏)、激活周期寄存器(0x88,写 12);GT 系列使用内部默认配置,这一步可跳过。每个寄存器写入后检查 i2c_ack,未应答就退回重写,直到全部配置成功。

检测触摸(st_check_touch):先延时约 20ms,再读触摸状态寄存器并判断:

if(ft_flag) begin
    if(i2c_data_r[3:0] > 4'd0 && i2c_data_r[3:0] <= 4'd5)
        touch_valid <= 1'b1;               // FT:低4位为触摸点数(1~5)
end
else begin
    if(i2c_data_r[7] == 1'b1 && i2c_data_r[3:0] > 4'd0
       && i2c_data_r[3:0] <= 4'd5)
        touch_valid <= 1'b1;               // GT:还要 bit7=1 才是有效数据
end

无论有没有触摸,随后都要向状态寄存器写 0 清除标志(GT 的 bit7 不清除,芯片不会报告新数据),然后 st_done <= touch_valid:有触摸才去读坐标,没有则回到检测态继续轮询。

读坐标(st_get_coord):从坐标寄存器首地址起连续读 4 个字节(正是 i2c_dri 新增的 reg_num 能力),按 X 低 8、X 高 8、Y 低 8、Y 高 8 的顺序拼出 16 位 X 和 16 位 Y。

坐标处理(st_coord_handle):GT 系列直接使用;FT 系列要交换 X/Y 并且高低位调换:

if(ft_flag) begin
    tp_x_coord <= {4'd0, tp_y_coord_t[3:0], tp_y_coord_t[15:8]};
    tp_y_coord <= {4'd0, tp_x_coord_t[3:0], tp_x_coord_t[15:8]};
end
else begin
    tp_x_coord <= tp_x_coord_t;
    tp_y_coord <= tp_y_coord_t;
end

FT 坐标寄存器里 X 高 4 位在前,所以 tp_y_coord_t[3:0](实为 X 的高 4 位)要放到结果的中间段,再把低 8 位跟在后面——一句拼接同时完成"去高4位补零 + 高低位重组 + X/Y 互换"。

36.5 触摸顶层与显示端:binary2bcd 登场

touch_top 只做两件事:例化 i2c_dri(CLK_FREQ=50_000_000I2C_FREQ=250_000,SCL 不超过 400KHz)和 touch_dri,把两者信号对接,对外输出 32 位坐标。

lcd_rgb_char 在"字符显示"实验代码基础上,插入两个 binary2bcd 模块,分别把 data[31:16](X)和 data[15:0](Y)转成 BCD 码,再交给 lcd_display 按字模方式在屏幕上画出 "X:"、"Y:" 和十进制数字。二进制转 BCD 的思路是不断"除 10 取余",得到个位、十位、百位……每个 nibble 一个十进制位,之后就能像 RTC 章那样直接拿 nibble 索引字模。

36.6 在线调试验证

工程中嵌入了 ILA 调试核,抓取 touch_valid 与坐标 data。先让屏幕显示一组已知坐标(如 0109X145Y,即十六进制 6D、91),把 ILA 触发条件设为 touch_valid 上升沿,运行后用手指点击同一位置,ILA 抓到 data = (006d, 0091),与屏幕显示完全一致——触摸读数链路验证通过。

图片对照

电容触摸屏原理

图:电容式触摸屏原理——手指改变触点处电容,触摸控制器扫描出坐标

GT911 相关说明

图:触摸控制器寄存器说明——GT 系列状态寄存器 0x814E 与坐标寄存器 0x8150 起的排布

touch_dri 状态机

图:触摸驱动状态机跳转图——从上电初始化到坐标处理的主循环

触摸屏显示坐标

图:屏幕显示的参考坐标 0109X145Y(十六进制 6DX、91Y),作为调试对照基准

ILA 抓取的触摸坐标

图:ILA 以 touch_valid 上升沿触发,抓到 data=(006d, 0091),与屏幕坐标一致

实验最终现象

图:实验最终现象——手指在屏幕上滑动,LCD 实时刷新触摸点坐标

初学者容易踩的坑

  1. GT911 上电时序不对,I2C 直接无应答:复位拉低时长不足、INT 没在正确窗口保持高电平,芯片地址就不是 0x14。严格照"10ms 复位 → INT 拉高 50ms → INT 转浮空输入"的顺序来,且 INT 必须用三态门控制,不能一直是推挽输出。
  2. GT 的状态标志忘记写 0 清除:0x814E 的 bit7 是"数据就绪"标志,读走坐标后必须回写 0,否则芯片不再报告新触摸,现象是"只响应第一次触摸"。
  3. FT 系列坐标没有交换 X/Y 或拼错高低位:FT 与 GT 的轴向定义相反、字节顺序相反,直接把 FT 原始数据当 GT 用,会出现"点左上角、光标跑右下角"或坐标乱跳。对照 36.4 的拼接代码逐位核对。
  4. 寄存器地址位宽搞混:GT 用 16 位字地址(bit_ctrl=1),FT 用 8 位(bit_ctrl=0)。位宽给错时写操作会破坏芯片配置、读操作拿到错位数据。
  5. 连续读少发了非应答reg_num=4 时只有最后一字节回非应答,中间字节都要应答;如果驱动沿用单字节版本的"读完即非应答",连续读会在第一字节就中断。
  6. SCL 频率超限:触摸芯片 I2C 一般不超过 400KHz,本实验 250KHz。把 I2C_FREQ 参数改太大可能偶发通信失败。
  7. 坐标是二进制,直接索引字模会花屏:显示端必须先过 binary2bcd 转成十进制逐位 BCD,再按 nibble 查字模——这也是 RTC 章"天生 BCD 直接显示"与本章"二进制需转换"的区别。

小结 & 下一步

作为实战篇的收官章,本章的收获有三层:

  • 驱动复用的红利:i2c_dri 升级为通用版(器件地址/位宽端口化 + 连续读写)后,EEPROM、RTC、触摸控制器三套业务共用同一个底层驱动——分层设计让每新增一种 I2C 器件只需新写业务层状态机;
  • 兼容性设计思维:一套代码识别并适配 GT/FT 两系芯片,靠"已知信息优先(LCD ID)、实测验证兜底(版本号 + ACK)"的识别策略,这在真实产品开发中极其常用;
  • 输入输出联动:触摸采集(I2C 输入)与 LCD 显示(RGB 输出)首次组成闭环,加上二进制转 BCD 模块,你已经掌握了"传感器数据采集→处理→人性化显示"的完整链路。

至此,第五篇全部实验完成。回头看这段旅程:串口、HDMI、I2C 三兄弟(EEPROM/RTC/触摸)、频率计、触摸屏——从通信协议到显示驱动再到测量仪器,FPGA 的"实战武器库"已经初步成型。下一步:合上实验篇,建议用这些模块自由组合做一个小综合项目(比如"可触摸调参的频率计"),检验一下这些知识是否真正长在了自己手上。