19、TSP抖动处理:抖动产生原因、去抖算法、平滑滤波
触摸屏的抖动问题,说白了就是「该动的时候不动,不该动的时候乱动」。我刚开始做TSP驱动时,被这个抖动坑过好几次。明明手指已经稳稳按在屏幕上,坐标却像喝醉了酒一样飘来飘去。嗯,今天我们就来彻底搞定它。
19.1 抖动是怎么来的?
抖动不是玄学,它有实实在在的物理原因。我个人习惯把抖动来源分成三类:
- 硬件噪声:触摸屏的ADC采样本身就有噪声。电源纹波、LCD干扰、充电器噪声,都会叠加到触摸信号上。我在项目中遇到过,手机插着充电器时,触摸坐标明显比不充电时跳得更厉害。
- 人体生理抖动:你的手不可能绝对静止。哪怕你觉得没动,微小的肌肉颤动也会让接触点变化。这个抖动频率通常在8-12Hz左右。
- 触摸屏自身特性:电容屏检测的是电容变化,当手指边缘的电容值接近阈值时,检测到的中心点会来回跳。尤其是边缘触摸,抖动更明显。
核心结论:抖动是必然存在的,我们的目标不是消除抖动(那不可能),而是让用户感觉不到抖动。
19.2 去抖算法:从源头掐掉毛刺
去抖算法,说白了就是「别信单次采样,多看几次再说」。我常用的去抖方法有下面几种:
19.2.1 阈值去抖
最简单粗暴的方法。如果当前坐标和上次坐标的差值小于某个阈值,就认为没动。
// 阈值去抖示例
#define JITTER_THRESHOLD 10 // 单位:像素
int last_x = 0, last_y = 0;
int debounce_x(int new_x) {
if (abs(new_x - last_x) < JITTER_THRESHOLD) {
return last_x; // 抖动太小,忽略
}
last_x = new_x;
return new_x;
}
这个方法简单,但有个问题:阈值设小了不管用,设大了又感觉触摸「粘滞」。我曾经在一个项目里把阈值从5调到15,结果用户反馈滑动不跟手。嗯,阈值去抖只适合对精度要求不高的场景。
19.2.2 状态机去抖
这个更聪明一些。它把触摸状态分成「稳定」「抖动」「变化中」几个阶段,只有状态稳定后才输出坐标。
// 状态机去抖
enum { STABLE, JITTERING, CHANGING } state = STABLE;
int stable_x = 0;
int jitter_count = 0;
int debounce_state_machine(int new_x) {
switch (state) {
case STABLE:
if (abs(new_x - stable_x) > JITTER_THRESHOLD) {
state = JITTERING;
jitter_count = 0;
}
return stable_x;
case JITTERING:
jitter_count++;
if (jitter_count > 3) { // 连续3次都偏离,才认为真的动了
stable_x = new_x;
state = STABLE;
} else if (abs(new_x - stable_x) < JITTER_THRESHOLD) {
state = STABLE; // 又回来了,算抖动
}
return stable_x;
}
}
这个算法我在一个平板项目里用过,效果不错。它不会让触摸「粘滞」,又能有效过滤掉单次毛刺。
19.3 平滑滤波:让轨迹更丝滑
去抖解决的是「跳变」问题,平滑解决的是「轨迹不自然」问题。你想想看,哪怕没有抖动,原始坐标的轨迹也是锯齿状的,因为手指移动时采样点不可能完全在一条直线上。
19.3.1 滑动平均滤波
取最近N个点的平均值作为输出。N越大越平滑,但延迟也越大。
#define FILTER_WINDOW 5
int buffer_x[FILTER_WINDOW] = {0};
int buffer_index = 0;
int moving_average(int new_x) {
int sum = 0;
buffer_x[buffer_index] = new_x;
buffer_index = (buffer_index + 1) % FILTER_WINDOW;
for (int i = 0; i < FILTER_WINDOW; i++) {
sum += buffer_x[i];
}
return sum / FILTER_WINDOW;
}
滑动平均的缺点是:窗口大了延迟高,窗口小了效果差。我一般取3-5个点,兼顾平滑和响应速度。
19.3.2 一阶低通滤波(指数加权)
这个算法更常用,因为它计算量小,而且可以调节平滑程度。
// 一阶低通滤波
#define ALPHA 0.3f // 滤波系数,0~1之间
int filtered_x = 0;
int lowpass_filter(int new_x) {
filtered_x = (int)(ALPHA * new_x + (1 - ALPHA) * filtered_x);
return filtered_x;
}
ALPHA值越大,响应越快但平滑度差;ALPHA越小,越平滑但延迟大。我个人习惯ALPHA取0.2-0.4之间,具体要看触摸屏的采样率和实际手感。
我的经验:一阶低通滤波配合状态机去抖,是我最常用的组合。去抖先干掉毛刺,平滑再让轨迹变顺,两者分工明确。
19.4 抖动处理的完整流程
实际项目中,我不会只用一种方法。通常的流程是:
- 原始坐标:从触摸屏IC读到的原始数据
- 阈值去抖:先过滤掉微小抖动
- 状态机去抖:处理较大幅度的跳变
- 低通滤波:平滑轨迹
- 输出坐标:给上层使用
下面这张图展示了整个处理流程:
19.5 避坑指南
做抖动处理这些年,我踩过不少坑。分享几个典型的:
我曾经犯过的错:
- 滤波参数一刀切:所有触摸点用同样的滤波系数。后来发现,手指快速滑动时需要响应快(ALPHA大),慢速移动时需要平滑(ALPHA小)。现在我会根据手指速度动态调整滤波系数。
- 忽略边缘区域:屏幕边缘的触摸抖动比中间大很多。我建议边缘区域用更严格的去抖阈值。
- 滤波导致延迟过大:有一次把滑动平均窗口设到10个点,结果用户画线时笔迹明显滞后。后来改成5个点,延迟降到可接受范围。
调试小技巧:在驱动里加一个调试模式,把原始坐标和滤波后的坐标同时打印出来。用手指在屏幕上慢慢画圈,观察两条轨迹的差异。如果滤波后的轨迹明显滞后,说明延迟太大;如果还是锯齿状,说明平滑不够。
19.6 参数调优建议
不同场景下,抖动处理的参数应该不同。我整理了一个参考表:
| 场景 | 阈值去抖 | 状态机去抖 | 低通滤波ALPHA | 说明 |
|---|---|---|---|---|
| 普通触摸 | 5-10像素 | 3次确认 | 0.3 | 日常使用,平衡响应和平滑 |
| 手写输入 | 3-5像素 | 2次确认 | 0.4 | 需要快速响应,减少延迟 |
| 游戏场景 | 2-3像素 | 1次确认 | 0.5 | 极致响应,允许少量抖动 |
| 充电时 | 10-15像素 | 4次确认 | 0.2 | 充电噪声大,需要更强滤波 |
嗯,参数没有标准答案。每个触摸屏模组、每个项目都不一样。我建议你拿到新屏后,先花半天时间调参,找到最适合当前硬件的组合。
抖动处理说难不难,说简单也不简单。核心就一句话:在响应速度和稳定性之间找到平衡点。多试几次,手感会告诉你答案。
公众号:蓝海资料掘金营,微信deep3321