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 触摸架构流程图,把整个链路串起来了。

TouchGFX 触摸架构流程图 物理触摸 驱动层:读取原始坐标 HAL 层:坐标映射校准 事件队列 控件分发:找到目标控件 回调函数:执行响应逻辑 偏移校准的核心环节在 HAL 层,驱动层和回调函数只负责传递和消费

从这张图可以清楚看到,触摸事件从物理层到回调函数,一共经过 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 工程里。