第三十六章 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 是第三十三章版本的升级,改动有三处:
- 器件地址和字地址位宽改成端口。
slave_addr[6:0]和bit_ctrl(0 表示 8 位寄存器地址、1 表示 16 位)不再写死为参数,而由上层业务模块实时给出——同一个驱动,接 EEPROM 时给 7'h50/1,接 RTC 时给 7'h51/0,接触摸时按芯片给 7'h14 或 7'h38; - 新增
reg_num端口:一次操作连续读/写几个寄存器。读数据状态里以reg_cnt == reg_num-1判断是否到了最后一字节,是则回非应答并停止,否则继续应答接收; - 新增
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_000,I2C_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),与屏幕显示完全一致——触摸读数链路验证通过。
图片对照

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

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

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

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

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

图:实验最终现象——手指在屏幕上滑动,LCD 实时刷新触摸点坐标
初学者容易踩的坑
- GT911 上电时序不对,I2C 直接无应答:复位拉低时长不足、INT 没在正确窗口保持高电平,芯片地址就不是 0x14。严格照"10ms 复位 → INT 拉高 50ms → INT 转浮空输入"的顺序来,且 INT 必须用三态门控制,不能一直是推挽输出。
- GT 的状态标志忘记写 0 清除:0x814E 的 bit7 是"数据就绪"标志,读走坐标后必须回写 0,否则芯片不再报告新触摸,现象是"只响应第一次触摸"。
- FT 系列坐标没有交换 X/Y 或拼错高低位:FT 与 GT 的轴向定义相反、字节顺序相反,直接把 FT 原始数据当 GT 用,会出现"点左上角、光标跑右下角"或坐标乱跳。对照 36.4 的拼接代码逐位核对。
- 寄存器地址位宽搞混:GT 用 16 位字地址(bit_ctrl=1),FT 用 8 位(bit_ctrl=0)。位宽给错时写操作会破坏芯片配置、读操作拿到错位数据。
- 连续读少发了非应答:
reg_num=4时只有最后一字节回非应答,中间字节都要应答;如果驱动沿用单字节版本的"读完即非应答",连续读会在第一字节就中断。 - SCL 频率超限:触摸芯片 I2C 一般不超过 400KHz,本实验 250KHz。把 I2C_FREQ 参数改太大可能偶发通信失败。
- 坐标是二进制,直接索引字模会花屏:显示端必须先过 binary2bcd 转成十进制逐位 BCD,再按 nibble 查字模——这也是 RTC 章"天生 BCD 直接显示"与本章"二进制需转换"的区别。
小结 & 下一步
作为实战篇的收官章,本章的收获有三层:
- 驱动复用的红利:i2c_dri 升级为通用版(器件地址/位宽端口化 + 连续读写)后,EEPROM、RTC、触摸控制器三套业务共用同一个底层驱动——分层设计让每新增一种 I2C 器件只需新写业务层状态机;
- 兼容性设计思维:一套代码识别并适配 GT/FT 两系芯片,靠"已知信息优先(LCD ID)、实测验证兜底(版本号 + ACK)"的识别策略,这在真实产品开发中极其常用;
- 输入输出联动:触摸采集(I2C 输入)与 LCD 显示(RGB 输出)首次组成闭环,加上二进制转 BCD 模块,你已经掌握了"传感器数据采集→处理→人性化显示"的完整链路。
至此,第五篇全部实验完成。回头看这段旅程:串口、HDMI、I2C 三兄弟(EEPROM/RTC/触摸)、频率计、触摸屏——从通信协议到显示驱动再到测量仪器,FPGA 的"实战武器库"已经初步成型。下一步:合上实验篇,建议用这些模块自由组合做一个小综合项目(比如"可触摸调参的频率计"),检验一下这些知识是否真正长在了自己手上。