第2章 信号采集端的噪声抑制

做运动控制这些年,我踩过最大的坑,就是信号采集端的噪声问题。你想想看,一个好好的伺服系统,电机转起来抖得像筛子,PID调了无数遍都没用——最后发现,是编码器信号线上串进了高频干扰。说白了,源头没掐住,后面再怎么优化算法都是白搭。

这一章,我们就来聊聊传感器噪声的来源,以及怎么从硬件和软件两个层面把它压下去。我个人习惯是:先硬件滤波,再软件滤波。硬件能干掉80%的噪声,剩下的交给软件兜底。

2.1 传感器噪声来源分析

噪声从哪来?我把它分成三类:

  • 电磁干扰(EMI):电机驱动器、变频器、开关电源这些大功率设备,工作时会产生强烈的电磁场。传感器信号线如果离得近,就像天线一样把干扰收进来。我在一个机器人项目里遇到过,编码器信号在电机加速时直接跳变,后来发现是动力线和信号线走在了同一个线槽里。
  • 电源纹波:传感器供电如果不干净,输出信号自然也不干净。尤其是低成本开关电源,纹波能到几十毫伏,对模拟输出的传感器影响很大。
  • 机械振动与接触不良:连接器松动、焊点虚焊、线缆磨损,这些都会产生间歇性的信号毛刺。嗯,这种噪声最难排查,因为它不是一直有,而是随机出现。

我的经验法则:先检查物理连接,再查电源质量,最后才怀疑外部电磁干扰。顺序搞反了,容易白费功夫。

2.2 硬件滤波:RC低通滤波设计

硬件滤波是噪声抑制的第一道防线。RC低通滤波,说白了就是一个电阻加一个电容,把高频噪声对地旁路掉。结构简单,但参数选不对,效果天差地别。

2.2.1 基本原理

RC低通滤波的截止频率公式:

f_c = 1 / (2π × R × C)

高于这个频率的信号会被衰减。比如你的编码器输出信号频率是100kHz,那截止频率至少要设到200kHz以上,否则有用信号也会被削掉。

2.2.2 参数选型实战

我一般按这个步骤来:

  1. 确定信号最高频率:比如编码器最高转速下的脉冲频率。
  2. 选择截止频率:取信号最高频率的2~5倍。别取太高,否则滤波效果差;也别取太低,否则信号变形。
  3. 选电阻值:1kΩ~10kΩ之间比较常用。电阻太小,前级驱动电流大;电阻太大,信号压降明显。
  4. 算电容值:根据公式反推。常用的是nF级别。
信号类型 信号频率 推荐R 推荐C 截止频率
编码器差分信号 1MHz 1kΩ 100pF 1.59MHz
模拟电压信号 1kHz 10kΩ 100nF 159Hz
霍尔传感器 10kHz 4.7kΩ 3.3nF 10.3kHz

小技巧:如果信号线上已经有长距离走线,可以在传感器端和接收端各加一级RC滤波。我在一个远距离编码器项目里试过,效果比单级好很多。

2.2.3 避坑指南

我曾经在一个项目里直接用示波器看信号,觉得RC滤波后的波形挺干净,结果一上电机就出问题。后来发现,RC滤波会引入相位延迟,对高速闭环控制影响很大。所以,如果你做的是高带宽伺服控制,RC滤波的截止频率一定要留够余量,或者改用有源滤波器。

2.3 软件滤波实战

硬件滤波搞定了大部分高频噪声,但低频噪声和随机毛刺,还得靠软件来收尾。下面三种方法,是我项目里最常用的。

2.3.1 均值滤波

均值滤波,就是把最近N次采样值加起来除以N。简单、高效,对周期性噪声抑制效果不错。

// 均值滤波示例
#define FILTER_N 10
int filter_buf[FILTER_N];
int filter_index = 0;
long filter_sum = 0;

int mean_filter(int new_value) {
    filter_sum -= filter_buf[filter_index];
    filter_buf[filter_index] = new_value;
    filter_sum += filter_buf[filter_index];
    filter_index = (filter_index + 1) % FILTER_N;
    return filter_sum / FILTER_N;
}

但要注意,N值越大,平滑效果越好,响应也越慢。我一般取4~16之间,具体看系统对实时性的要求。

2.3.2 中值滤波

中值滤波对付毛刺噪声特别管用。它的原理很简单:取连续N次采样的中间值。比如采样值[10, 12, 255, 11, 13],排序后取中间值12,那个255的毛刺就被干掉了。

// 中值滤波示例
#define MEDIAN_N 5

int median_filter(int new_value) {
    static int buf[MEDIAN_N];
    static int index = 0;
    int temp[MEDIAN_N];
    int i, j;
    
    buf[index] = new_value;
    index = (index + 1) % MEDIAN_N;
    
    // 复制并排序
    for(i = 0; i < MEDIAN_N; i++) temp[i] = buf[i];
    for(i = 0; i < MEDIAN_N - 1; i++) {
        for(j = i + 1; j < MEDIAN_N; j++) {
            if(temp[i] > temp[j]) {
                int t = temp[i];
                temp[i] = temp[j];
                temp[j] = t;
            }
        }
    }
    return temp[MEDIAN_N / 2];
}

我的习惯:均值滤波和中值滤波搭配使用。先用中值滤波去掉毛刺,再用均值滤波平滑波形。我在一个力传感器项目里这么干过,效果比单独用一种好得多。

2.3.3 卡尔曼滤波

卡尔曼滤波,听起来高大上,其实核心思想就一句话:用模型预测+测量修正,不断逼近真实值。它特别适合处理有随机噪声的连续信号,比如编码器速度、加速度计算。

一维卡尔曼滤波的五个核心公式:

// 一维卡尔曼滤波实现
typedef struct {
    float Q;  // 过程噪声协方差
    float R;  // 测量噪声协方差
    float x;  // 估计值
    float P;  // 估计误差协方差
    float K;  // 卡尔曼增益
} kalman_1d_t;

float kalman_filter(kalman_1d_t *kf, float measurement) {
    // 预测
    kf->P = kf->P + kf->Q;
    
    // 更新
    kf->K = kf->P / (kf->P + kf->R);
    kf->x = kf->x + kf->K * (measurement - kf->x);
    kf->P = (1 - kf->K) * kf->P;
    
    return kf->x;
}

// 使用示例
kalman_1d_t kf = {.Q = 0.01, .R = 0.1, .x = 0, .P = 1};
float filtered = kalman_filter(&kf, raw_value);

参数调优是关键。Q值越大,滤波器越相信测量值,响应快但噪声多;R值越大,滤波器越相信预测值,平滑但滞后。我一般先设Q=0.01、R=0.1,然后根据实际波形微调。

实战建议:卡尔曼滤波不适合所有场景。如果系统是非线性的,或者计算资源有限,用扩展卡尔曼或者干脆用互补滤波。我在一个低成本MCU项目里试过,卡尔曼滤波跑一次要几百微秒,后来换成互补滤波,效果差不多,时间省了一半。

2.4 三种软件滤波对比

滤波方法 适用场景 优点 缺点 计算量
均值滤波 周期性噪声、平稳信号 简单、高效 对毛刺不敏感、有滞后 低
中值滤波 随机毛刺、脉冲噪声 抗毛刺能力强 平滑效果一般 中
卡尔曼滤波 动态系统、连续信号 自适应、精度高 参数调优复杂、计算量大 高

2.5 本章知识体系

下面这张图,把噪声抑制的整体思路串起来了。从源头分析,到硬件滤波,再到软件滤波,每一步都有明确的定位。

信号采集端噪声抑制知识体系 噪声来源分析 硬件滤波(RC低通) 软件滤波 电磁干扰(EMI) 电源纹波 机械振动/接触不良 截止频率计算 R/C参数选型 多级滤波/相位延迟 均值滤波 中值滤波 卡尔曼滤波 核心原则:先硬件后软件,硬件干掉80%,软件兜底20% 根据信号特性和系统资源,选择最合适的滤波组合

噪声抑制这件事,说白了就是一层一层地过滤。硬件RC滤波挡掉高频干扰,软件均值滤波平滑周期性波动,中值滤波掐掉随机毛刺,卡尔曼滤波在动态系统中做最优估计。没有万能的方法,只有最合适的组合。

我在实际项目中,通常先用示波器看原始信号的噪声特征——是高频振荡还是随机毛刺?频率范围是多少?然后根据这些信息,决定用哪种滤波方案。记住一句话:先诊断,再开药方。