4. 输入信号处理与滤波:硬件去抖与软件去抖、数字滤波算法
各位工程师朋友,咱们今天聊一个很实在的话题——输入信号处理。说实话,我在工业现场摸爬滚打这么多年,见过太多因为信号没处理好导致的“灵异事件”。机器莫名其妙跳变、传感器读数忽高忽低、按键按一下触发十次……这些问题的根源,往往就是信号处理没做到位。
工业控制环境有多恶劣?你想想看,电机启动时的电磁干扰、变频器产生的谐波、现场焊接带来的地电位波动,这些噪声都会叠加到我们的输入信号上。如果不做处理,控制器读到的数据就是“假数据”,控制逻辑再完美也没用。
4.1 硬件去抖:第一道防线
硬件去抖,说白了就是用物理手段把噪声挡在门外。我个人习惯,只要成本允许,一定优先考虑硬件去抖。为什么?因为硬件处理是实时的,不占用CPU资源,可靠性也更高。
最常见的硬件去抖方案就是RC低通滤波器。一个电阻加一个电容,简单粗暴但有效。原理其实不复杂:高频噪声通过电容旁路到地,低频信号则顺利通过。我曾在一条包装生产线上遇到过编码器信号抖动的问题,加了个RC滤波器后,问题立刻消失。
RC滤波器的截止频率 f_c = 1 / (2πRC)。对于机械按键,抖动时间通常为5-20ms,建议将截止频率设置在50-100Hz。
除了RC滤波,施密特触发器也是硬件去抖的好帮手。它利用滞回特性,给信号设定两个不同的阈值——上升沿触发阈值和下降沿触发阈值。这样信号在阈值附近来回跳动时,输出不会跟着乱跳。嗯,这里要注意,施密特触发器的滞回宽度要大于噪声幅度,否则白搭。
4.2 软件去抖:灵活的第二道防线
硬件去抖虽好,但有时候成本受限,或者电路板已经定型了,这时候就得靠软件来救场。软件去抖的核心思想很简单:不轻易相信信号的第一次变化,等它稳定了再说。
最经典的软件去抖算法就是“延时确认法”。检测到信号变化后,先等一段时间(比如10ms),再读取一次。如果两次结果一致,就认为信号有效。我在项目中遇到过一种情况:按键按下时抖动很厉害,但释放时很干净。针对这种不对称抖动,我采用了“按下延时、释放立即”的策略,效果出奇的好。
// 按键去抖 - 延时确认法
#define DEBOUNCE_TIME_MS 10
uint8_t debounce_key(uint8_t key_current) {
static uint8_t key_last = 1; // 假设高电平为释放状态
static uint32_t last_change_time = 0;
static uint8_t key_stable = 1;
if (key_current != key_last) {
last_change_time = get_tick_ms();
key_last = key_current;
} else if (get_tick_ms() - last_change_time > DEBOUNCE_TIME_MS) {
key_stable = key_current;
}
return key_stable;
}
还有一种更鲁棒的方法叫“连续采样法”。连续采样N次,如果N次结果都相同,才认为信号有效。N的取值取决于抖动时间和采样周期。比如抖动时间10ms,采样周期1ms,那N至少取10。
4.3 数字滤波算法:让数据更干净
说完开关量,咱们聊聊模拟量的滤波。工业现场的模拟信号,比如温度、压力、流量,噪声来源五花八门。数字滤波算法就是我们的“数据清洗工具”。
4.3.1 中值滤波:对付脉冲噪声的利器
中值滤波的原理很简单:连续采样N次,排序后取中间值。它对脉冲噪声(比如偶尔出现的尖峰干扰)有奇效。我曾在热处理炉的温度采集中使用中值滤波,炉内偶尔会有电弧干扰,中值滤波直接把那些离谱的尖峰数据给剔除了。
// 中值滤波 - 窗口大小为5
#define MEDIAN_WINDOW 5
uint16_t median_filter(uint16_t new_sample) {
static uint16_t buffer[MEDIAN_WINDOW] = {0};
static uint8_t index = 0;
uint16_t temp[MEDIAN_WINDOW];
uint16_t i, j, tmp;
// 更新环形缓冲区
buffer[index] = new_sample;
index = (index + 1) % MEDIAN_WINDOW;
// 复制并排序
for (i = 0; i < MEDIAN_WINDOW; i++) {
temp[i] = buffer[i];
}
// 冒泡排序(窗口小,效率可接受)
for (i = 0; i < MEDIAN_WINDOW - 1; i++) {
for (j = 0; j < MEDIAN_WINDOW - 1 - i; j++) {
if (temp[j] > temp[j + 1]) {
tmp = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = tmp;
}
}
}
return temp[MEDIAN_WINDOW / 2]; // 返回中间值
}
4.3.2 均值滤波:平滑噪声的经典方法
均值滤波,就是把连续N次采样的值加起来除以N。它对随机噪声(比如热噪声、量化噪声)的抑制效果很好。说白了,就是通过平均来“稀释”噪声的影响。
均值滤波有两种实现方式:一种是每次重新计算所有采样值的平均,另一种是滑动平均(只更新最新数据,去掉最老数据)。我个人更推荐滑动平均,因为它计算量小,而且数据更新更平滑。
// 滑动平均滤波 - 窗口大小为8
#define AVG_WINDOW 8
uint16_t moving_average(uint16_t new_sample) {
static uint16_t buffer[AVG_WINDOW] = {0};
static uint8_t index = 0;
static uint32_t sum = 0;
static uint8_t count = 0;
// 减去最旧的数据
sum -= buffer[index];
// 加入新数据
buffer[index] = new_sample;
sum += new_sample;
index = (index + 1) % AVG_WINDOW;
if (count < AVG_WINDOW) count++;
return (uint16_t)(sum / count);
}
| 特性 | 均值滤波 | 中值滤波 |
|---|---|---|
| 适用噪声类型 | 随机噪声 | 脉冲噪声 |
| 响应速度 | 较慢 | 中等 |
| 计算量 | 小(滑动平均) | 中等(需排序) |
| 边缘保持能力 | 差(会模糊跳变) | 好(保留阶跃) |
4.3.3 卡尔曼滤波:工业界的“智能滤波器”
卡尔曼滤波,听起来很高大上,其实它就是一个能“预测+修正”的智能算法。它不只会滤除噪声,还能根据系统模型预测下一个值,然后用实际测量值来修正预测。说白了,它比普通滤波多了一个“思考”的过程。
我在一个液位控制项目中用过卡尔曼滤波。液位传感器受液面波动影响很大,普通滤波根本压不住。卡尔曼滤波结合了液位变化的物理模型(比如液位变化是连续的,不会突变),效果出奇的好。
// 一维卡尔曼滤波 - 简化版
typedef struct {
float Q; // 过程噪声协方差
float R; // 测量噪声协方差
float P; // 估计误差协方差
float K; // 卡尔曼增益
float X; // 状态估计值
} KalmanFilter_t;
void kalman_init(KalmanFilter_t *kf, float init_value) {
kf->Q = 0.01; // 根据系统特性调整
kf->R = 0.1; // 根据传感器精度调整
kf->P = 1.0;
kf->X = init_value;
}
float kalman_update(KalmanFilter_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;
}
4.4 信号有效性校验:最后的把关
滤波做完了,数据看起来干净了,但还不能直接拿来用。为什么?因为传感器可能坏了、线路可能断了、AD转换可能出错了。信号有效性校验,就是给数据做一次“体检”。
常用的校验方法有几种:
- 范围校验: 检查数据是否在合理范围内。比如4-20mA电流环,正常范围是4-20mA,超出这个范围直接报错。
- 变化率校验: 检查数据变化是否过快。比如温度传感器,1秒内变化超过10度,基本可以判定为异常。
- 一致性校验: 对于冗余传感器,比较多个传感器的读数是否一致。偏差超过阈值就报警。
- CRC校验: 对于通信信号,通过循环冗余校验确保数据在传输过程中没有出错。
// 信号有效性校验示例
typedef enum {
SIGNAL_OK = 0,
SIGNAL_OUT_OF_RANGE,
SIGNAL_RATE_TOO_HIGH,
SIGNAL_STUCK
} SignalStatus_t;
SignalStatus_t validate_signal(float value, float last_value, float min, float max, float max_rate) {
// 范围校验
if (value < min || value > max) {
return SIGNAL_OUT_OF_RANGE;
}
// 变化率校验
if (fabs(value - last_value) > max_rate) {
return SIGNAL_RATE_TOO_HIGH;
}
// 卡死校验(连续多次值不变,可能传感器卡住了)
static uint8_t stuck_count = 0;
if (fabs(value - last_value) < 0.001) {
stuck_count++;
if (stuck_count > 10) {
return SIGNAL_STUCK;
}
} else {
stuck_count = 0;
}
return SIGNAL_OK;
}
知识体系总览
下面这张图,把本章的核心内容串起来了。从信号进入控制器开始,经过硬件去抖、软件去抖、数字滤波、有效性校验,最终得到可靠的数据。每一步都有它的作用,缺一不可。
好了,关于输入信号处理与滤波,咱们就聊到这儿。记住一个原则:不要相信任何未经处理的信号。硬件去抖、软件去抖、数字滤波、有效性校验,这四步走下来,你的控制程序才算真正有了“免疫力”。