当前位置:首页 > 消费电子 > 消费电子
[导读]电容按键抗干扰不是“阈值调大点”就完事。真实环境里有温漂、湿手、水滴、LED PWM耦合、电源纹波、ESD,原始计数值一直在飘。 正确做法是:采样→滤波→基线跟踪→Delta判定→去抖状态机→滑条重心法→手势时序。算法不碰绝对电容值,只认“相对基线的变化量”。


电容按键抗干扰不是“阈值调大点”就完事。真实环境里有温漂、湿手、水滴、LED PWM耦合、电源纹波、ESD,原始计数值一直在飘。 正确做法是:采样→滤波→基线跟踪→Delta判定→去抖状态机→滑条重心法→手势时序。算法不碰绝对电容值,只认“相对基线的变化量”。

一、采样与滤波

触摸芯片(自电容/互电容)出来的是Raw Count,先过一遍IIR低通压高频毛刺:

#define IIR_A 8   // 越小越平滑

static int16_t filt[N_CH];


void cap_scan_filter(uint16_t raw[], int n){

   for(int i=0;i<n;i++){

       filt[i] = filt[i] + ((int16_t)raw[i]-filt[i])*IIR_A/16;

   }

}

滤波系数别太重,否则响应迟滞;LED调光、电机启动时噪声是周期性的,可加多次平均或跳频扫描。

二、基线跟踪:慢跟、触摸冻结

基线是“无触摸时稳态参考”。温度湿度慢变要跟,但手指按下去不能跟。

static int16_t baseline[N_CH];

static uint8_t  touch[N_CH];


void baseline_update(int ch, int16_t v){

   int16_t d = v - baseline[ch];

   if(touch[ch]) return;              // 触摸中冻结基线

   if(d > NOISE_TH){                  // 超噪声门限,疑似干扰,不跟

       return;

   }

   if(d>0) baseline[ch]++;            // 水桶式单步跟随

   else if(d<0) baseline[ch]--;

}

要点:

基线更新只在“非触摸”状态做

突变超过噪声阈值当干扰丢弃,别学进去

单步递增最稳,别一下拉到当前值

上电前几百ms先采空板基线再进业务

三、Delta判定+去抖状态机

判定用基线+触摸阈值,释放用滞回阈值,避免边界抖动:

#define TOUCH_TH   15

#define HYST      6

#define DEBOUNCE  3


static uint8_t cnt[N_CH];


uint8_t key_judge(int ch, int16_t v){

   int16_t delta = v - baseline[ch];

   if(!touch[ch]){

       if(delta > TOUCH_TH){

           if(++cnt[ch] >= DEBOUNCE){

               touch[ch]=1; cnt[ch]=0;

           }

       }else cnt[ch]=0;

   }else{

       if(delta < TOUCH_TH-HYST){

           if(++cnt[ch] >= DEBOUNCE){

               touch[ch]=0; cnt[ch]=0;

           }

       }else cnt[ch]=0;

   }

   return touch[ch];

}

连续N次采样才算按下/抬起,抖动脉冲被滤掉。 长按别在主循环计时,用按下时刻戳算。

四、多键防水滴误触

水滴覆盖会同时抬高相邻几个通道基线,看起来像“多键同按”。加一条规则:

int active_cnt=0;

for(int i=0;i<N_CH;i++) if(touch[i]) active_cnt++;

if(active_cnt>=3 && is_adjacent_group()){   // 大片相邻激活

   // 判为水膜,全部丢弃

   for(int i=0;i<N_CH;i++) touch[i]=0;

}

硬件上加Driven Shield/Guard Ring,软件再判“广域漂移”,家电面板基本压得住。

五、滑条位置:重心法

滑条不是“哪个电极最大”,是多个通道加权出连续位置:

int slider_pos(int16_t v[], int n){

   int32_t num=0, den=0;

   for(int i=0;i<n;i++){

       int16_t s = v[i]-baseline[i];

       if(s<SLIDER_TH) continue;

       num += s*i;

       den += s;

   }

   if(den==0) return -1;

   return num/den;     // 0~n-1 连续位置

}

输出再做一次滑动平均,跳点会少很多。 电极尺寸/走线长度不一致要存增益补偿系数,否则滑到中间会卡。

六、手势识别:时序状态机

滑条位置出来后,按时间窗判手势:

typedef struct {

   int last_pos;

   uint32_t last_move_ms;

   uint32_t press_ms;

   uint8_t  got_first;

} gesture_t;


void gesture_proc(gesture_t*g, int pos, uint32_t now, uint8_t pressed){

   if(pressed){

       if(g->press_ms==0) g->press_ms=now;

       if(pos>=0 && g->got_first){

           int dx = pos-g->last_pos;

           if(abs(dx)>=2 && now-g->last_move_ms<400){

               emit(dx>0?GEST_RIGHT:GEST_LEFT);

           }

           g->last_pos=pos; g->last_move_ms=now;

       }

       if(pos>=0) g->got_first=1;

   }else{

       if(g->press_ms && now-g->press_ms>1000)

           emit(GEST_LONGPRESS);

       g->press_ms=0; g->got_first=0;

   }

}

规则:

位移阈值过滤手颤

时间窗限死滑动判定(300~500ms)

抬起时算按压时长→长按

双击记两次press事件间隔<300ms

七、工程落坑

主循环轮询扫描→响应慢、耗电高,用定时中断/芯片INT唤醒

基线在触摸中继续跟→手按久了松不开

只有单阈值无滞回→边界来回抖

LED PWM走线并行传感器→周期误触

水滴多键不屏蔽→湿手狂触发

滑条取max通道→位置跳变

手势不设时间窗→轻微颤动判成滑动

电源不独立LDO→开关电源纹波进基线

传感器走线过长→天线效应捡RF

八、落地顺序

定时扫描各通道Raw

IIR滤波压毛刺

非触摸态慢更新基线

Delta+滞回+去抖出按键

邻键广域激活判水膜

滑条重心法出位置

位置时序状态机出手势

事件上报应用层,按键/滑条/手势分开回调

电容触控稳不稳,代码层就看三件事:基线敢不敢在触摸时冻结、阈值有没有滞回、滑条用不用重心法。硬件布局(屏蔽电极、走线远离PWM、独立LDO)决定上限,固件算法决定下限。 只比“raw>常数”的方案,LED一调光、手一湿就废;把基线跟踪+去抖状态机+重心法滑条搭起来,家电面板、门锁、耳机仓基本都能过常规抗扰测试。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭