3. 原始数据采集:读取触摸控制器寄存器、X/Y坐标原始值、压力值(Z轴)获取

好,咱们进入第三章。上一章我们把硬件接口调通了,I2C 或者 SPI 能正常通信了。但光能通信没用,你得把数据读回来。这一章,我们就来干这件事——读取触摸控制器的原始数据。

说白了,就是去翻触摸芯片的寄存器,把 X 坐标、Y 坐标,还有压力值(也就是 Z 轴)给掏出来。这些原始值,是所有后续校准、滤波、手势识别的基础。你想想看,如果原始数据都是错的,后面再怎么折腾也是白搭。

核心要点: 原始数据采集是触摸校准的“地基”。地基不稳,楼盖得再高也得塌。

3.1 触摸控制器的寄存器地图

每个触摸控制器芯片,内部都有一张“寄存器地图”。你得拿着这张地图,才知道去哪里找数据。我习惯先把数据手册里的寄存器表扒下来,整理成下面这样的表格,方便查阅。

以市面上最常见的 FT5x06 系列(比如 FT5206、FT5316)为例,它的寄存器布局大致如下:

寄存器地址 (Hex) 寄存器名称 描述 有效位
0x00 DEVIDE_MODE 设备模式(工作/休眠) 8 bit
0x01 GESTURE_ID 手势识别结果(如滑动、双击) 8 bit
0x02 TD_STATUS 触摸点状态(触摸点数) 8 bit
0x03 P1_XH 触摸点1 X坐标高字节 + 事件标志 高2位: 事件, 低6位: X[11:8]
0x04 P1_XL 触摸点1 X坐标低字节 8 bit (X[7:0])
0x05 P1_YH 触摸点1 Y坐标高字节 + 事件标志 高2位: 事件, 低6位: Y[11:8]
0x06 P1_YL 触摸点1 Y坐标低字节 8 bit (Y[7:0])
0x07 P1_WEIGHT 触摸点1 压力值(Z轴) 8 bit
... ... ... ...

嗯,这里要注意。很多新手会直接去读 0x03 和 0x04,以为拿到的就是完整的 X 坐标。其实不是。你看 P1_XH 这个寄存器,它的高两位是事件标志,低六位才是 X 坐标的高位数据。你得做位运算,把它们拼起来。

避坑指南: 我曾经在一个项目里,直接拿 P1_XH 寄存器的值当 X 坐标的高 8 位用,结果触摸点满屏乱跳。查了半天才发现,高两位是事件标志,不是数据。从那以后,我每次读寄存器前,都会把数据手册的位定义再确认一遍。

3.2 读取流程:三步走

读取原始数据的流程,其实不复杂。我总结了一个“三步走”的策略:

  1. 第一步:查询状态 —— 先读 TD_STATUS 寄存器,看看当前有几个触摸点。如果值为 0,说明没人摸屏幕,后面的数据就不用读了。
  2. 第二步:读取坐标 —— 根据触摸点数,循环读取对应点的 XH/XL/YH/YL 寄存器,并拼装成完整的 12 位或 16 位坐标值。
  3. 第三步:读取压力 —— 读取 WEIGHT 寄存器,获取 Z 轴压力值。有些芯片还提供 AREA 寄存器,表示触摸面积,也可以一并读取。

下面这段代码,是我在实际项目中用过的 FT5x06 读取函数。你可以直接参考:

// 假设 i2c_read(dev_addr, reg_addr, *buffer, len) 已经实现
#define FT5x06_ADDR 0x38
#define REG_TD_STATUS 0x02
#define REG_P1_XH 0x03

typedef struct {
    uint16_t x;
    uint16_t y;
    uint8_t pressure;
    uint8_t event; // 0: 按下, 1: 抬起, 2: 持续接触
} TouchPoint_t;

uint8_t ft5x06_read_touch(TouchPoint_t *points, uint8_t max_points) {
    uint8_t status;
    uint8_t buf[6];
    uint8_t touch_count = 0;

    // 第一步:读取触摸状态
    i2c_read(FT5x06_ADDR, REG_TD_STATUS, &status, 1);
    touch_count = status & 0x0F; // 低4位表示触摸点数

    if (touch_count == 0 || touch_count > max_points) {
        return 0;
    }

    // 第二步:循环读取每个触摸点
    for (uint8_t i = 0; i < touch_count; i++) {
        // 每个点占6个寄存器:XH, XL, YH, YL, WEIGHT, AREA
        uint8_t reg_base = REG_P1_XH + (i * 6);
        i2c_read(FT5x06_ADDR, reg_base, buf, 6);

        // 解析事件标志(在XH的高两位)
        points[i].event = (buf[0] >> 6) & 0x03;

        // 拼装X坐标:高6位 + 低8位
        points[i].x = ((uint16_t)(buf[0] & 0x3F) << 8) | buf[1];

        // 拼装Y坐标:高6位 + 低8位
        points[i].y = ((uint16_t)(buf[2] & 0x3F) << 8) | buf[3];

        // 读取压力值(Z轴)
        points[i].pressure = buf[4];
    }

    return touch_count;
}
个人经验: 我建议你在读取完所有触摸点后,再读一次 TD_STATUS 做校验。如果两次读取的触摸点数不一致,说明数据可能被中断污染了,最好丢弃这一帧数据。这在高速触摸场景下尤其重要。

3.3 压力值(Z轴)的获取与意义

压力值,也就是 Z 轴数据,很多人会忽略它。其实它很有用。比如,你可以根据压力大小来判断用户是“轻点”还是“重按”,从而实现不同级别的交互反馈。

不同芯片的压力值读取方式不太一样:

  • FT5x06 系列: 直接读 WEIGHT 寄存器,值范围 0x00 ~ 0xFF。值越大,表示按压力度越大。
  • GT911 系列: 压力值在 Px_WEIGHT 寄存器,但它的值受触摸面积影响,不完全是力度。
  • resistive 电阻屏: Z 轴压力是通过测量接触电阻计算出来的,需要读取 X+、Y+ 和 X-、Y- 的电压值,再做一次除法运算。

我个人习惯,在调试阶段会把压力值也打印出来。有一次,我发现某个批次的屏幕,压力值始终偏小,导致触摸灵敏度不够。排查下来,是触摸屏的贴合胶层太厚了。你看,一个小小的压力值,能帮你发现硬件问题。

3.4 数据采集的完整流程

把上面这些知识点串起来,整个原始数据采集的流程就清晰了。我画了一张流程图,帮你理清思路:

触摸原始数据采集流程图 触摸原始数据采集流程图 开始采集 步骤1:读取 TD_STATUS 获取当前触摸点数 触摸点数 > 0? 否 返回 0 是 步骤2:循环读取 XH/XL/YH/YL 寄存器 步骤3:读取 WEIGHT 获取 Z 轴压力值 返回触摸点数 注意事项 • 每次读取后校验 TD_STATUS • 注意事件标志位处理 • 压力值可用于力度检测

3.5 常见问题与调试技巧

在实际调试中,你可能会遇到一些“怪现象”。我把自己踩过的坑整理了一下:

  • 坐标值始终为 0: 先检查 I2C 通信是否正常。我习惯在初始化后,先读一下芯片的 DEVIDE_MODE 或 CHIP_ID 寄存器,如果能读到预期的值,说明通信没问题。
  • 坐标值跳变、不连续: 大概率是位拼装错了。检查一下 XH 寄存器的高两位是不是被当成了数据位。另外,注意字节序,有些芯片是大端,有些是小端。
  • 压力值始终为 0xFF 或 0x00: 可能是寄存器地址不对。有些芯片的压力值寄存器不在连续地址上,需要单独读取。
  • 触摸点数量不对: 检查 TD_STATUS 的位定义。有些芯片的低 4 位是触摸点数,但高 4 位可能表示其他状态,记得做掩码操作。
调试小技巧: 我建议你在读取每个寄存器后,都把原始值通过串口打印出来。比如打印 "XH=0xAB, XL=0xCD",然后手动计算一下拼装后的坐标值,跟预期对比。这样能快速定位问题。

好了,这一章的内容就到这里。原始数据采集是触摸校准的第一步,也是最重要的一步。把这一步做扎实了,后面的校准和滤波才能有的放矢。记住,数据不会骗人,但你的代码可能会。多读、多验、多打印,这是调试的不二法门。