4. 获取原始触摸点:如何从底层驱动读取未经处理的触摸坐标

好,咱们进入实战环节的第一个硬骨头。

校准算法写得再漂亮,如果拿到的原始数据本身就是错的,那一切都是白搭。我见过不少工程师,花了两周调滤波、搞插值,最后发现是驱动层读回来的坐标根本没对齐——那种感觉,真的很崩溃。

所以这一章,咱们就扎进底层,看看怎么从触摸芯片手里,把最原始的坐标数据“掏”出来。

4.1 原始触摸点长什么样?

先搞清楚一个概念:什么是“原始触摸点”?

说白了,就是触摸芯片直接通过 I2C 或 SPI 总线丢给你的那串数字。它还没经过任何滤波、缩放、旋转,更没做过屏幕映射。你想想看,芯片内部其实就是一个 ADC(模数转换器),它把触摸屏上的电压值转成了数字量。

举个例子,一个典型的电容触摸芯片,比如 FT5x06 系列,它上报的原始坐标范围通常是 0~4095(12位精度)。这个范围跟屏幕分辨率(比如 480x272)没有半毛钱关系。

关键认知:原始坐标 ≠ 屏幕坐标。原始坐标是触摸芯片自己“看到”的物理位置,单位是 ADC 计数,不是像素。

4.2 驱动层读取的两种姿势

我在项目中遇到过两种主流的读取方式,各有各的坑。

4.2.1 轮询模式(Polling)

最简单粗暴。主循环里每隔几毫秒去读一次触摸芯片的寄存器。好处是代码简单,坏处是浪费 CPU。TouchGFX 的底层通常不推荐纯轮询,但调试阶段用起来很方便。

// 伪代码:轮询读取 FT5x06 原始坐标
uint16_t raw_x, raw_y;

void poll_touch(void) {
    uint8_t buf[4];
    // 读取 0x03 寄存器开始的 4 个字节
    i2c_read(FT5x06_ADDR, 0x03, buf, 4);
    
    raw_x = ((uint16_t)buf[0] & 0x0F) << 8 | buf[1];
    raw_y = ((uint16_t)buf[2] & 0x0F) << 8 | buf[3];
    
    // 注意:这里读到的 raw_x 和 raw_y 就是原始值
    printf("RAW: X=%d, Y=%d\n", raw_x, raw_y);
}

4.2.2 中断模式(Interrupt)

更推荐的做法。触摸芯片的 INT 引脚拉低时,表示有触摸事件。CPU 在中断服务函数里读取坐标。这样既省电又及时。

// 伪代码:中断服务函数中读取
void EXTI_IRQHandler(void) {
    if (touch_int_pin == 0) {
        uint8_t status = i2c_read_reg(FT5x06_ADDR, 0x02);
        if (status & 0x0F) {  // 有触摸点
            raw_x = read_raw_x();
            raw_y = read_raw_y();
            // 丢给上层处理
            touch_data_ready = 1;
        }
    }
}

我的习惯:调试阶段先用轮询,把原始值打印出来看看。确认芯片通信没问题了,再切到中断模式。别一上来就搞中断,万一 I2C 时序不对,调试起来很麻烦。

4.3 寄存器地图:你得知道读哪里

不同的触摸芯片,寄存器地址完全不一样。我拿最常见的两款芯片做个对比,你心里就有数了。

芯片型号 X坐标寄存器 Y坐标寄存器 数据格式 有效位数
FT5x06 0x03 (高字节) / 0x04 (低字节) 0x05 (高字节) / 0x06 (低字节) 大端,12位 0~4095
GT911 0x03 (低字节) / 0x04 (高字节) 0x05 (低字节) / 0x06 (高字节) 小端,12位 0~4095
XPT2046 通过控制字配置通道 通过控制字配置通道 SPI 12位 0~4095

看到没?同样是 12 位数据,FT5x06 是大端,GT911 是小端。你要是搞反了字节序,读出来的坐标就是乱的。我曾经在这个坑里爬了整整一个下午——明明寄存器地址没错,但坐标就是跳来跳去,最后发现是字节序搞反了。

4.4 原始数据的“脏”与“净”

直接从芯片读回来的数据,其实挺“脏”的。我总结了几种常见问题:

  • 抖动:手指没动,但坐标在 ±5 以内跳动。这是 ADC 本身的噪声。
  • 毛刺:偶尔跳出一个离群值,比如从 2000 突然跳到 4000。通常是电源干扰或触摸芯片内部处理异常。
  • 边界溢出:手指按在边缘时,读到的值可能超出芯片标称范围(比如超过 4095 或小于 0)。
  • 多点干扰:虽然只按了一个点,但芯片可能误报第二个点(坐标全为 0 或全为 0xFFF)。

注意:千万不要把原始数据直接拿去用!一定要做有效性检查。我见过有人直接把 0xFFFF 当成有效坐标传给上层,结果触摸点直接飞到屏幕外面去了。

4.5 原始坐标的校验与过滤

在把数据交给校准算法之前,我通常会做三步处理:

  1. 范围检查:确认 raw_x 和 raw_y 在芯片标称范围内(比如 0~4095)。超出范围直接丢弃。
  2. 去毛刺:如果当前值与上一次值的差值超过某个阈值(比如 500),认为是毛刺,丢弃本次数据。
  3. 简单均值滤波:连续采 3~5 次,去掉最大最小值,剩下的取平均。这一步能有效抑制抖动。
// 一个简单的原始数据过滤函数
uint16_t filter_raw(uint16_t new_val, uint16_t last_val) {
    // 毛刺检测
    if (abs((int)new_val - (int)last_val) > 500) {
        return last_val;  // 丢弃,返回上一次的值
    }
    // 范围检查
    if (new_val > 4095) {
        return last_val;
    }
    return new_val;
}

我的经验:滤波不要太狠。均值窗口超过 5 次,触摸响应就会有明显的延迟。用户划屏时感觉“粘手”,其实就是滤波过度了。

4.6 知识体系:原始触摸数据流

下面这张图,把从触摸芯片到原始坐标的完整路径画出来了。你看一眼就能明白整个链路。

原始触摸数据流 触摸屏物理层 触摸芯片 ADC I2C / SPI 总线 驱动层读取 原始坐标缓冲区 有效性检查 去毛刺与滤波 输出原始坐标 说明:原始坐标范围通常为 0~4095(12位),与屏幕分辨率无关。后续校准算法将基于此数据计算映射关系。

4.7 实战建议:先打印,再信任

最后,给你一个非常实在的建议。

拿到一块新的触摸屏,第一件事不是写校准算法,而是把原始坐标打印出来。用手指在屏幕上画圈,看看 X 和 Y 的变化趋势对不对。如果 X 增大时,手指往右滑,但打印出来的值却在减小——那说明坐标方向反了,需要在驱动层做一次翻转。

我习惯在调试串口上每秒输出 10 次原始坐标,观察几分钟。确认没有跳变、没有死值、范围正确,再往下走。这一步虽然枯燥,但能省掉后面 90% 的排查时间。

核心要点回顾:

  • 原始坐标是 ADC 计数值,不是像素坐标
  • 不同芯片的寄存器地址和字节序不同,务必查数据手册
  • 读取后必须做范围检查和去毛刺
  • 滤波要适度,别为了“干净”牺牲响应速度
  • 调试阶段多打印,确认数据趋势正确再往下走