4. 快速瞬变光抑制:针对频闪灯、阳光直射的滤波策略

频闪灯和阳光直射,这两个东西是触控工程师的噩梦。我做过一个项目,客户在展厅用射灯一照,整个触摸面板直接“抽风”——乱跳、误触、死机,全来了。

为什么会这样?因为光电传感器对光的变化太敏感了。频闪灯每秒闪烁几十到几百次,阳光直射带来的是直流分量饱和。这两种干扰,用常规的均值滤波根本扛不住。

这一章,我们就专门聊怎么对付它们。

4.1 干扰的本质:光噪声的时频特征

先搞清楚敌人长什么样。

干扰类型 频率范围 幅度特征 对触控的影响
频闪灯(LED/荧光灯) 50 Hz – 2 kHz 周期性脉冲,占空比低 触发虚假触摸,信号抖动
阳光直射 DC – 0.1 Hz 缓慢变化,幅度极大 基线漂移,ADC 饱和
混合光照 全频段 叠加噪声 信噪比急剧下降

频闪灯的问题在于它的上升沿极陡。传感器在光脉冲到来的一瞬间,输出信号会跳变几十甚至上百个 LSB。如果你用滑动平均,窗口长度不够,这个跳变就会直接穿透滤波器,被误判为触摸。

阳光直射则是另一种麻烦。它不会让你误触,但会让你的基线一直往上飘。飘到 ADC 上限,传感器就“瞎”了。

4.2 频闪抑制:中值滤波 + 自适应窗口

对付频闪,我个人最推荐的是中值滤波。不是均值,是中值。

为什么?因为频闪脉冲是稀疏的。在一个窗口里,正常信号占大多数,脉冲只是少数几个点。均值会把脉冲的误差“平均”到所有点上,而中值直接扔掉异常值。

核心思路:用中值滤波剔除光脉冲尖峰,保留真实的触摸信号。

我习惯的窗口长度是 5 到 9 个采样点。太短了抑制效果差,太长了延迟大。具体选多少,要看你的扫描频率和频闪灯的基频。

// 快速中值滤波示例(窗口长度 5)
uint16_t median_filter_5(uint16_t *buf) {
    uint16_t a = buf[0], b = buf[1], c = buf[2], d = buf[3], e = buf[4];
    uint16_t tmp;
    // 排序网络,5个元素比较9次
    if (a > b) { tmp = a; a = b; b = tmp; }
    if (d > e) { tmp = d; d = e; e = tmp; }
    if (a > c) { tmp = a; a = c; c = tmp; }
    if (b > c) { tmp = b; b = c; c = tmp; }
    if (a > d) { tmp = a; a = d; d = tmp; }
    if (c > d) { tmp = c; c = d; d = tmp; }
    if (b > e) { tmp = b; b = e; e = tmp; }
    if (b > c) { tmp = b; b = c; c = tmp; }
    if (d > e) { tmp = d; d = e; e = tmp; }
    return c;  // 中值
}

嗯,这里要注意:中值滤波的延迟是窗口长度的一半。如果你的触摸响应要求 10 ms 以内,窗口对应的采样时间不能超过 20 ms。

小技巧:如果频闪频率是 100 Hz(周期 10 ms),你的采样周期是 1 ms,那么窗口长度 5 刚好覆盖半个周期,能有效抑制脉冲。

4.3 阳光直射抑制:高通滤波 + 基线跟踪

阳光直射是低频干扰。说白了,它和触摸信号在频域上是分开的。触摸是快速变化(手指按下到抬起通常 50-200 ms),阳光是缓慢漂移(秒级甚至分钟级)。

所以,一个简单的一阶高通滤波器就能把阳光干扰滤掉。

// 一阶高通滤波器,用于抑制阳光直射
// alpha 通常取 0.95 ~ 0.99
uint16_t highpass_filter(uint16_t raw, uint16_t *prev_out, float alpha) {
    float out = alpha * (*prev_out) + alpha * (raw - *prev_out);
    *prev_out = (uint16_t)out;
    return raw - (uint16_t)out;  // 高通输出 = 原始值 - 低通值
}

这个滤波器的截止频率是:

f_c = (1 - alpha) / (2 * π * T_s)

假设采样周期 T_s = 1 ms,alpha = 0.99,那么 f_c ≈ 1.6 Hz。也就是说,低于 1.6 Hz 的缓慢变化会被抑制,而手指触摸(通常 5-10 Hz)基本不受影响。

我曾经踩过一个坑:alpha 设得太接近 1(比如 0.999),滤波器响应太慢。阳光突然照过来时,基线要好几秒才能稳定。那段时间里,触摸完全没法用。后来我把 alpha 改成动态调整——检测到基线快速漂移时,临时降低 alpha 值,加快收敛。

4.4 综合策略:两级级联滤波架构

实际产品中,频闪和阳光往往是同时存在的。我建议用两级滤波:

  1. 第一级:中值滤波 — 剔除频闪脉冲尖峰
  2. 第二级:高通滤波 — 抑制阳光直射导致的基线漂移

顺序不能反。如果先高通再中值,高通滤波器会把脉冲尖峰“展宽”,变成一段持续的高电平,中值滤波反而不好处理。

经验值参考:

  • 中值窗口长度:5(采样率 1 kHz 时,延迟 2.5 ms)
  • 高通 alpha:0.97 ~ 0.99(截止频率 1.6 ~ 4.8 Hz)
  • 总延迟:约 3-5 ms,不影响触摸手感

4.5 知识结构图:快速瞬变光抑制策略

下面这张图展示了整个滤波策略的流程和决策逻辑。

快速瞬变光抑制策略流程图 快速瞬变光抑制策略 原始光信号 干扰类型判断 频闪干扰(50 Hz - 2 kHz) 阳光直射(DC - 0.1 Hz) 中值滤波(窗口 5-9) 高通滤波(alpha 0.97-0.99) 级联输出:稳定触控信号 注意:先中值后高通,顺序不可颠倒。总延迟控制在 5 ms 以内。

4.6 实战避坑指南

讲几个我实际项目中遇到的坑,你们以后碰到了能少走弯路。

坑一:中值滤波导致触摸灵敏度下降

有一次我发现,加了中值滤波后,轻触(力度很轻的那种)经常识别不到。排查了半天,发现是窗口长度太大(设了 15),把真实的触摸信号也当成噪声滤掉了。后来改成自适应窗口——检测到信号变化率大时缩小窗口,平稳时放大窗口。

坑二:高通滤波器引入相位延迟

高通滤波器会改变信号的相位。如果你同时用多个传感器做差分检测,相位不一致会导致误判。我的解决办法是:所有通道使用相同的滤波器参数,并且在软件里做同步采样。

坑三:阳光直射下的 ADC 饱和

这个最要命。阳光太强时,光电传感器的输出直接顶到 ADC 上限,滤波算法再牛也没用。硬件上必须加自动增益控制(AGC)或者动态偏置调整。软件上,我习惯在滤波前先做一次饱和检测——如果连续 N 个采样点都接近 ADC 最大值,直接触发增益切换。

4.7 小结

快速瞬变光抑制,说白了就是两招:中值滤波打脉冲,高通滤波压漂移。顺序别搞反,参数别设死,留点自适应空间。

你想想看,如果能把频闪和阳光都搞定,你的触控产品在户外、展厅、舞台这些场景下,基本就稳了。