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 原始坐标的校验与过滤
在把数据交给校准算法之前,我通常会做三步处理:
- 范围检查:确认 raw_x 和 raw_y 在芯片标称范围内(比如 0~4095)。超出范围直接丢弃。
- 去毛刺:如果当前值与上一次值的差值超过某个阈值(比如 500),认为是毛刺,丢弃本次数据。
- 简单均值滤波:连续采 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 知识体系:原始触摸数据流
下面这张图,把从触摸芯片到原始坐标的完整路径画出来了。你看一眼就能明白整个链路。
4.7 实战建议:先打印,再信任
最后,给你一个非常实在的建议。
拿到一块新的触摸屏,第一件事不是写校准算法,而是把原始坐标打印出来。用手指在屏幕上画圈,看看 X 和 Y 的变化趋势对不对。如果 X 增大时,手指往右滑,但打印出来的值却在减小——那说明坐标方向反了,需要在驱动层做一次翻转。
我习惯在调试串口上每秒输出 10 次原始坐标,观察几分钟。确认没有跳变、没有死值、范围正确,再往下走。这一步虽然枯燥,但能省掉后面 90% 的排查时间。
核心要点回顾:
- 原始坐标是 ADC 计数值,不是像素坐标
- 不同芯片的寄存器地址和字节序不同,务必查数据手册
- 读取后必须做范围检查和去毛刺
- 滤波要适度,别为了“干净”牺牲响应速度
- 调试阶段多打印,确认数据趋势正确再往下走