乐于分享
好东西不私藏

触摸按键检测与软件滤波

触摸按键检测与软件滤波

机械按键靠物理通断,触摸按键靠的是手指靠近改变的那点电容。触摸按键的可靠性,软件滤波算法很关键。

从原始读数开始

触摸传感芯片(或 MCU 内置 TSC)定期给出一个表征电容大小的数值。手指按下时,数值相对"未按"基准上升(或下降,取决于电极设计)。

// 周期性读取每个通道的原始电容计数#define CH_NUM 4uint16_t raw[CH_NUM];          // 原始读数uint16_t baseline[CH_NUM];     // 当前基线(无触摸时的估计值)voidscan_raw(void){for (int i = 0; i < CH_NUM; i++) {        raw[i] = tsc_read_channel(i);   // 读取电容转后的计数    }}

裸读数抖动很大,不能拿来直接判键。第一步是建立"无触摸时它大概是多少"的估计——也就是基线。

基线跟踪:跟着环境慢变

基线必须动态更新,否则温度一变整体读数平移,固定阈值就失效了。常规做法是:在"判定为未触摸"的窗口内,用指数滑动平均缓慢追踪。

// 仅当认为未触摸时更新基线,避免把手指值吸进去voidupdate_baseline(int ch){if (!is_touched(ch)) {// 系数越小,基线跟得越慢,抗抖动越强        baseline[ch] = baseline[ch] - (baseline[ch] >> 4) + (raw[ch] >> 4);    }}

>> 4 即 1/16 的权重,基线每秒只挪一小步,既能跟上温漂,又不会被一次触摸拽偏。若权重太大(如 1/2),基线会"追"上手指值,导致松手后误判一直按下。

差分阈值:用变化量判键

判键看的是"当前值与基线的差",而不是绝对值。手指带来的差值是稳定且显著的。

#define TOUCH_THRESHOLD 30     // 差值超过此值判为触摸#define RELEASE_THRESHOLD 15   // 回落到此值以下判为松开(带回差)intis_touched(int ch){uint16_t delta = raw[ch] - baseline[ch];return delta > TOUCH_THRESHOLD;}

引入回差(hysteresis):按下阈值(30)高于松开阈值(15),两者之间的区域保持稳定,避免临界抖动时按键反复跳变。这点和机械按键的软件消抖是同一思路,只是对象从电平变为了电容差值。

温漂与环境补偿

温度变化时,整条曲线平移,单靠基线慢追踪还不够快。可以在基线更新里加约束:限制单步最大变化量,过滤掉突变干扰;同时用"长期最大值"和"长期最小值"动态收紧判定窗口。

// 限制基线单步跳变,滤除异常读数int16_t diff = (int16_t)raw[ch] - (int16_t)baseline[ch];if (diff > -MAX_STEP && diff < MAX_STEP) {    baseline[ch] += diff >> 4;          // 正常慢追踪}// 否则本次 raw 异常(如上电瞬态),忽略不更新

MAX_STEP 拦住上电、强干扰等造成的离群值,防止基线被一次性污染。配合周期性的"全通道环境重校准"(设备空闲时强制重算基线),可应对季节级温漂。

消抖状态机

和机械按键类似,触摸也有"假触发"和"抖动"。但触摸的消抖不是延时等待,而是确认状态稳定的持续时间。用一个小状态机把"疑似→确认"分开:

差值 < 松开阈值

差值 > 按下阈值

未持续够时间

持续 > 去抖时长

差值 < 松开阈值

未持续够时间

持续 > 去抖时长

IDLE
PRESSING
PRESSED
RELEASING
// 每个通道维护一个计时器,确认稳定才切换有效状态voiddebounce_ch(int ch){if (raw[ch] - baseline[ch] > TOUCH_THRESHOLD) {if (++touch_timer[ch] >= DEBOUNCE_TICKS)            key_state[ch] = 1;          // 确认按下    } elseif (raw[ch] - baseline[ch] < RELEASE_THRESHOLD) {if (++release_timer[ch] >= DEBOUNCE_TICKS) {            key_state[ch] = 0;          // 确认松开            touch_timer[ch] = 0;        }    } else {// 回差区内,两个计时器清零,保持当前状态        touch_timer[ch] = 0;        release_timer[ch] = 0;    }}

区分"疑似"和"确认"后,单次尖峰干扰不会触发按键,只有稳定超过 DEBOUNCE_TICKS 的触摸才算数。

多键扫描与防水

多键共用一个激励源时,要在扫描周期里轮流采集各通道,避免相互耦合。防水(水膜误触)靠"相邻键差值比对":水膜会让一片电极整体抬升,而真实手指是单点突出,据此可剔除水误触。

定时触发扫描
逐通道读 raw
更新基线 慢追踪+限幅
算 delta = raw - baseline
回差阈值判疑似
状态机确认 去抖
输出稳定键状态
水膜检测: 邻键整体抬升?
抑制误触
上报按键事件

完整链路

触摸按键的软件链路是:原始读数 → 基线慢追踪(限幅抗污染)→ 差分+回差判疑似 → 状态机去抖确认 → 多键/防水后处理。把基线和回差调好,比追求硬件灵敏度更重要。

// 主循环里周期调用,典型扫描周期 10~20msvoidtouch_task(void){    scan_raw();for (int i = 0; i < CH_NUM; i++) {        update_baseline(i);     // 先更新基线        debounce_ch(i);         // 再判定与去抖    }// key_state[] 即为稳定的按键有效状态,供上层读取}