2. TouchGFX 触摸架构:事件处理、驱动层、HAL 层与回调函数
上一章我们聊了触摸偏移的“症状”和“病因”。这一章,咱们得把引擎盖打开,看看 TouchGFX 到底是怎么处理触摸事件的。
说白了,你点一下屏幕,到程序响应,中间经过了好几个环节。任何一个环节出问题,偏移就来了。我刚开始接触 TouchGFX 时,也以为触摸就是个“点一下 -> 响应”这么简单。直到被一个偏移 bug 折磨了两天,才老老实实把整个链路啃了一遍。
2.1 事件处理机制:从物理点击到逻辑响应
TouchGFX 的事件处理,可以理解成一条流水线。物理触摸是原材料,最终的事件响应是成品。中间经过采样、上报、分发、回调这几个工位。
核心链路:物理触摸 → 驱动层采样 → HAL 层上报 → 事件队列 → 控件分发 → 回调函数执行
这条链路里,我最关注的是“坐标变换”这一步。因为偏移,本质上就是坐标在传递过程中被“篡改”了。你想想看,驱动层读到的原始坐标是 A,HAL 层处理完变成 B,到了控件手里成了 C。C 和 A 不一致,那就是偏移。
2.2 触摸控制器驱动层:最底层的“侦察兵”
驱动层,就是直接和触摸芯片打交道的代码。它负责通过 I2C 或 SPI 总线,从触摸芯片的寄存器里读出原始数据。
我习惯把驱动层比作“侦察兵”。它只负责把情报(原始坐标)带回来,不负责分析情报准不准。
一个典型的触摸驱动,核心任务就两个:
- 初始化触摸芯片:配置工作模式、采样率、中断触发方式等。
- 读取触摸数据:轮询或通过中断,读取坐标、触摸状态(按下/抬起)。
这里有个坑,我踩过。有些触摸芯片的原始坐标,默认是 12 位精度(0-4095),但你的屏幕分辨率是 480x272。如果不做映射,直接把 4095 传给上层,那坐标就飞到天边去了。
注意:驱动层返回的坐标,通常是触摸芯片的“原始物理坐标”,不是屏幕的逻辑像素坐标。这个映射关系,要在 HAL 层处理。
下面是一个简化的驱动读取函数示例:
// 伪代码:从 FT5x06 触摸芯片读取坐标
bool CstmTouchController::sampleTouch(int32_t& x, int32_t& y) {
uint8_t buf[4];
// 通过 I2C 读取触摸数据寄存器
I2C_Read(FT5x06_ADDR, FT5x06_TOUCH_DATA_REG, buf, 4);
// 解析原始坐标(12位精度)
x = ((buf[0] & 0x0F) << 8) | buf[1];
y = ((buf[2] & 0x0F) << 8) | buf[3];
// 注意:此时 x, y 是 0-4095 的原始值
return true;
}
你看,驱动层只管读,不管算。它返回的 x 和 y,是触摸芯片的物理坐标。这个坐标,和屏幕像素之间,还差着一层“映射”。
2.3 HAL 层:承上启下的“翻译官”
HAL 层,全称是 Hardware Abstraction Layer。在 TouchGFX 里,它负责把驱动层的原始坐标,翻译成上层能用的逻辑坐标。
我个人觉得,HAL 层是整个触摸架构里最关键的环节。因为偏移校准,主要就是在这里做文章。
HAL 层里有一个核心函数:TouchController::sampleTouch(int32_t& x, int32_t& y)。TouchGFX 的框架会周期性调用这个函数,获取触摸数据。
但 TouchGFX 的 HAL 层,不止是调用驱动。它还会做一件事:坐标变换。
默认情况下,TouchGFX 的 HAL 层会假设触摸芯片的坐标范围和屏幕分辨率是“一对一”的。比如屏幕是 800x480,它就认为触摸芯片返回的 x 范围是 0-799,y 范围是 0-479。
但现实很骨感。触摸芯片的坐标范围,往往比屏幕分辨率大得多。而且,触摸屏和 LCD 之间,可能存在物理安装偏差、旋转偏差。
这时候,就需要在 HAL 层里,手动做一次“映射校准”。
我的习惯:在 HAL 层的 sampleTouch 函数里,加入一个“校准矩阵”。把驱动层返回的原始坐标,通过矩阵运算,映射到屏幕的逻辑坐标。这样,上层控件拿到的坐标,就是“准”的。
下面是一个带线性校准的 HAL 层示例:
// 在 HAL 层做坐标映射
bool TouchController::sampleTouch(int32_t& x, int32_t& y) {
int32_t raw_x, raw_y;
// 1. 调用驱动层,获取原始坐标
if (!m_driver.sampleTouch(raw_x, raw_y)) {
return false;
}
// 2. 应用校准参数(线性映射)
// 假设:触摸范围 0-4095,屏幕范围 0-799
// 校准系数:scale_x = 799.0 / 4095.0
x = static_cast<int32_t>(raw_x * m_calib.scale_x + m_calib.offset_x);
y = static_cast<int32_t>(raw_y * m_calib.scale_y + m_calib.offset_y);
// 3. 边界裁剪
x = (x < 0) ? 0 : (x >= 800) ? 799 : x;
y = (y < 0) ? 0 : (y >= 480) ? 479 : y;
return true;
}
嗯,这里要注意。校准系数 scale_x 和 offset_x,不是拍脑袋定的。它们需要通过“三点校准法”或“五点校准法”计算出来。这个我们后面章节会详细讲。
2.4 回调函数:事件分发的“最后一公里”
坐标校准好了,怎么通知到具体的按钮、滑块呢?这就靠回调函数了。
TouchGFX 的事件分发机制,是基于“观察者模式”的。每个可交互的控件,都可以注册一个回调函数。当触摸事件发生时,框架会根据坐标,找到对应的控件,然后调用它的回调函数。
回调函数的签名,通常是这样的:
// 按钮点击回调
void buttonClickHandler(const AbstractButton& button) {
// 处理按钮点击事件
// 注意:这里的坐标已经是校准后的逻辑坐标了
}
我遇到过一个问题:回调函数里拿到的坐标,和实际点击位置差了几个像素。排查了半天,发现是 HAL 层的坐标映射写错了,把 x 和 y 搞反了。你看,问题又回到了 HAL 层。
所以,回调函数本身不负责校准。它只负责“消费”已经校准好的坐标。如果回调里发现坐标不准,那 99% 的概率,是 HAL 层或驱动层的问题。
2.5 整体架构图:一张图看懂触摸链路
说了这么多,不如一张图来得直观。下面是我画的 TouchGFX 触摸架构流程图,把整个链路串起来了。
从这张图可以清楚看到,触摸事件从物理层到回调函数,一共经过 6 个环节。其中,HAL 层的“坐标映射校准”是唯一一个能修正偏移的地方。驱动层只管读,回调函数只管用,都不负责“纠偏”。
2.6 避坑指南:我曾经踩过的三个坑
讲完理论,分享几个实战中容易踩的坑。这些都是我亲身经历过的。
| 坑位 | 现象 | 根因 | 解决 |
|---|---|---|---|
| 坐标方向反了 | 点左上角,响应在右下角 | 触摸芯片的 X/Y 轴与 LCD 的 X/Y 轴方向相反 | 在 HAL 层做 X/Y 交换或取反 |
| 点击偏移固定像素 | 总是偏右 10 像素 | 触摸屏与 LCD 物理安装有偏移 | 在 HAL 层加固定偏移量 offset_x/offset_y |
| 边缘点击不准 | 靠近屏幕边缘时,偏差变大 | 触摸芯片的非线性失真,线性映射不够 | 改用多点校准(三点或五点校准) |
我记得有一次,客户反馈说屏幕右上角的按钮死活点不到。我远程调试了一下午,发现是触摸芯片的坐标范围是 0-4095,但屏幕分辨率是 480x272。驱动层直接返回了原始值,HAL 层也没做映射。结果就是,触摸坐标的范围远大于屏幕范围,边缘区域的坐标被“截断”了,导致按钮无法命中。
这个问题的教训就是:永远不要假设触摸芯片的坐标范围和屏幕分辨率一致。一定要在 HAL 层做显式的映射校准。
2.7 小结
这一章,我们把 TouchGFX 的触摸架构拆开看了个遍。从驱动层到 HAL 层,再到回调函数,每个环节的职责都很清晰。
驱动层是“侦察兵”,只负责读取原始坐标。HAL 层是“翻译官”,负责把原始坐标映射成逻辑坐标。回调函数是“执行者”,只负责响应已经校准好的事件。
偏移校准的核心战场,就在 HAL 层。后面的章节,我们会深入讲解如何计算校准参数,以及如何把校准代码集成到 TouchGFX 工程里。