第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 参数选型实战
我一般按这个步骤来:
- 确定信号最高频率:比如编码器最高转速下的脉冲频率。
- 选择截止频率:取信号最高频率的2~5倍。别取太高,否则滤波效果差;也别取太低,否则信号变形。
- 选电阻值:1kΩ~10kΩ之间比较常用。电阻太小,前级驱动电流大;电阻太大,信号压降明显。
- 算电容值:根据公式反推。常用的是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滤波挡掉高频干扰,软件均值滤波平滑周期性波动,中值滤波掐掉随机毛刺,卡尔曼滤波在动态系统中做最优估计。没有万能的方法,只有最合适的组合。
我在实际项目中,通常先用示波器看原始信号的噪声特征——是高频振荡还是随机毛刺?频率范围是多少?然后根据这些信息,决定用哪种滤波方案。记住一句话:先诊断,再开药方。