3. 原始触摸数据采集:从物理信号到可用坐标
触摸屏能响应你的手指,靠的是底层控制器不断上报原始数据。但控制器吐出来的东西,说白了就是一堆带噪声的电压值或电容变化量。直接拿这些值去驱动光标?那光标会像喝醉了一样满屏乱跳。
我刚开始做触摸驱动时,就吃过这个亏。当时觉得「数据读回来不就能用了吗」,结果光标抖得用户根本点不准按钮。后来才明白——原始数据必须经过采集、滤波、归一化这三关,才能交给上层用。
核心流程: 物理触摸 → 控制器寄存器读取 → 数据滤波去抖 → 坐标归一化 → 可用坐标
3.1 通过 SPI/I2C 读取触摸控制器寄存器
触摸控制器和主控芯片之间,最常用的就是 SPI 或 I2C。选哪个?看你的项目需求。
| 通信协议 | 引脚数 | 速度 | 适合场景 |
|---|---|---|---|
| I2C | 2 线 (SCL, SDA) | 100kHz~1MHz | 引脚紧张、布线简单 |
| SPI | 4 线 (SCLK, MOSI, MISO, CS) | 10MHz~50MHz | 高刷新率、多触摸点 |
我个人习惯用 I2C 做原型验证,因为接线少、调试方便。但量产项目如果触摸点数超过 5 个,我会换成 SPI——数据量一大,I2C 的速率瓶颈就暴露了。
读取流程
- 初始化通信接口:配置 GPIO 为对应功能,设置时钟频率。
- 发送读命令:向控制器写入寄存器地址,通常需要先写地址再读数据。
- 读取数据包:根据控制器数据手册,读取固定长度的字节。
- 解析数据:提取触摸状态、X/Y 坐标、压力值等。
我的经验: 很多控制器支持「批量读取」模式。一次读取多个寄存器,比单次读取效率高得多。我曾经在 GT911 上把读取时间从 2ms 降到了 0.5ms,就靠这个。
// I2C 读取触摸控制器示例 (以 FT5x06 为例)
uint8_t buf[8];
uint8_t reg_addr = 0x02; // 触摸状态寄存器地址
i2c_write(dev_addr, ®_addr, 1); // 发送寄存器地址
i2c_read(dev_addr, buf, 8); // 读取 8 字节数据
// 解析坐标
uint8_t touch_points = buf[0] & 0x0F; // 触摸点数
uint16_t x = ((buf[1] & 0x0F) << 8) | buf[2];
uint16_t y = ((buf[3] & 0x0F) << 8) | buf[4];
注意: 不同控制器的数据格式差异很大。有的用 12 位坐标,有的用 16 位。一定要仔细看数据手册的寄存器映射表。我曾经因为字节序搞反,浪费了整整一天。
3.2 数据滤波与去抖
原始数据读回来了,但你会发现——手指明明没动,坐标却在几个像素之间来回跳。这就是噪声。触摸控制器的 ADC 采样、电源纹波、甚至手指的微小颤动都会引入抖动。
怎么处理?我常用的方法有三种,各有优劣。
| 滤波方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 限幅滤波 | 当前值与上次值差值超过阈值则丢弃 | 简单、响应快 | 无法消除周期性噪声 |
| 中值滤波 | 取 N 次采样值的中间值 | 抗脉冲噪声好 | 延迟较大 |
| 滑动平均 | 取最近 N 次采样值的平均值 | 平滑效果好 | 有相位滞后 |
我推荐的组合方案
实际项目中,我很少只用一种滤波。我的惯用套路是:
- 先限幅:剔除明显异常的跳变(比如一次跳了 100 像素以上)。
- 再滑动平均:取最近 4~8 次数据做平均,平滑微小抖动。
// 限幅 + 滑动平均 组合滤波
#define MAX_DELTA 50 // 最大允许跳变
#define FILTER_N 4 // 滑动窗口大小
static int16_t filter_buf[FILTER_N];
static uint8_t filter_idx = 0;
int16_t touch_filter(int16_t raw_value, int16_t last_value) {
// 1. 限幅
if (abs(raw_value - last_value) > MAX_DELTA) {
return last_value; // 丢弃异常值
}
// 2. 滑动平均
filter_buf[filter_idx] = raw_value;
filter_idx = (filter_idx + 1) % FILTER_N;
int32_t sum = 0;
for (int i = 0; i < FILTER_N; i++) {
sum += filter_buf[i];
}
return sum / FILTER_N;
}
避坑指南: 滤波窗口不是越大越好。窗口太大,触摸响应会变慢,用户会感觉「卡顿」。我一般把窗口控制在 4~8 次,采样率 100Hz 时延迟约 40~80ms,人眼基本无感。
3.3 坐标归一化处理
滤波后的坐标还是「原始坐标」,范围取决于触摸控制器的分辨率。比如某款控制器输出 0~4095,但你的屏幕分辨率是 480x320。直接拿 4095 去驱动 480 像素?那触摸点会跑到屏幕外面去。
归一化,就是把原始坐标映射到屏幕坐标空间。
归一化公式
screen_x = (raw_x - x_min) * screen_width / (x_max - x_min)
screen_y = (raw_y - y_min) * screen_height / (y_max - y_min)
其中 x_min、x_max 是触摸屏有效区域的原始坐标范围。这个范围怎么得到?
- 方法一:读数据手册——控制器厂商通常会给出。
- 方法二:实际标定——在屏幕四角点按,记录坐标值。
我个人更推荐方法二。因为硬件装配有公差,数据手册的值往往不准。我在一个项目里就遇到过——手册写 X 范围 0~4095,实际测下来只有 50~4000,边缘差了将近 2mm。
// 坐标归一化实现
typedef struct {
int16_t x_min, x_max;
int16_t y_min, y_max;
int16_t screen_width;
int16_t screen_height;
} touch_calib_t;
void touch_normalize(int16_t raw_x, int16_t raw_y,
int16_t *screen_x, int16_t *screen_y,
touch_calib_t *calib) {
// 防止除零
if (calib->x_max == calib->x_min) return;
if (calib->y_max == calib->y_min) return;
// 限幅
if (raw_x < calib->x_min) raw_x = calib->x_min;
if (raw_x > calib->x_max) raw_x = calib->x_max;
if (raw_y < calib->y_min) raw_y = calib->y_min;
if (raw_y > calib->y_max) raw_y = calib->y_max;
*screen_x = (raw_x - calib->x_min) * calib->screen_width / (calib->x_max - calib->x_min);
*screen_y = (raw_y - calib->y_min) * calib->screen_height / (calib->y_max - calib->y_min);
}
小技巧: 归一化时用整数运算,避免浮点数。嵌入式 MCU 没有 FPU 的话,浮点运算会拖慢速度。上面的代码全部用整数除法,效率高很多。
3.4 完整数据采集流程示例
把上面三步串起来,就是一个完整的触摸数据采集循环。嗯,这里我贴一个实际项目里用过的伪代码框架。
void touch_task(void) {
static int16_t last_x = 0, last_y = 0;
int16_t raw_x, raw_y;
int16_t filtered_x, filtered_y;
int16_t screen_x, screen_y;
// 1. 读取原始数据
if (touch_read(&raw_x, &raw_y) != 0) {
return; // 读取失败
}
// 2. 滤波去抖
filtered_x = touch_filter(raw_x, last_x);
filtered_y = touch_filter(raw_y, last_y);
// 3. 坐标归一化
touch_normalize(filtered_x, filtered_y,
&screen_x, &screen_y,
&g_calib);
// 4. 更新 LVGL 输入
lv_indev_set_point(g_indev, screen_x, screen_y);
last_x = filtered_x;
last_y = filtered_y;
}
重要提醒: 这个任务要放在定时器或 RTOS 任务里周期性执行。采样率建议 50~100Hz。太快了 MCU 扛不住,太慢了用户觉得卡。我一般用 80Hz,兼顾流畅度和性能。
3.5 本章小结
原始触摸数据采集,说白了就是三件事:
- 读出来——通过 SPI/I2C 从控制器寄存器拿数据。
- 洗干净——用限幅+滑动平均去掉噪声和抖动。
- 对齐——把原始坐标映射到屏幕像素坐标。
这三步做好了,触摸数据才能交给 LVGL 用。否则后面做再多校准算法,都是白搭。我在好几个项目里都验证过——滤波和归一化做扎实了,触摸体验直接上一个台阶。