滑动平均滤波通过窗口长度匹配50Hz工频周期(如1kHz采样率下N=20)实现等间隔采样点正弦信号均值趋零,从而抑制干扰;但需严格同步采样、防累加溢出、保证环形缓冲区原子性。

滑动平均滤波为什么能抑制50Hz工频干扰
滑动平均滤波本身不是针对频率设计的,但它在采样率固定、干扰周期明确时,可以通过窗口长度匹配干扰周期实现“天然陷波”。对50Hz工频干扰,若采样率为1kHz,则一个工频周期含20个采样点;取窗口长度 N = 20,滑动平均后,连续20个等间隔采样点的正弦信号(相位均匀分布)均值趋近于0——这正是抑制原理。但前提是采样必须严格同步(或至少整数倍过采样),否则效果急剧下降。
- 实际中常选
N = 20、40或60,对应 1kHz、2kHz、3kHz 采样率下的整周期采样 - 若用
N = 16(常见于DMA缓冲对齐),对50Hz抑制会明显变弱,可能残留~3.125Hz的差拍分量 - 浮点运算非必需,但用
int32_t累加+右移(如>>5代替除32)更利于MCU实时性
用环形缓冲区实现低开销滑动平均(C++模板版)
避免每次滤波都拷贝数组,用固定大小环形缓冲区 + 累加器更新是最优解。C++中可封装为轻量模板类,适配 uint16_t(常见ADC结果)或 int32_t(防溢出)。
template<typename t size_t n>
class MovingAverage {
T buffer[N];
size_t head = 0;
int64_t sum = 0; // 防止累加溢出
public:
T push(T new_val) {
sum -= buffer[head];
buffer[head] = new_val;
sum += new_val;
head = (head + 1) % N;
return static_cast<t>(sum / N);
}
};
</t></typename>
- 实例化:
MovingAveragefilter; -
sum用int64_t是因为 20×12bit ADC 最大值约 8万,远超int32_t安全范围 - 不要在中断里调用
std::vector或动态内存操作——裸机或RTOS下必须零堆分配 - 若ADC使用DMA双缓冲,可在半传输/全传输回调中各调用一次
push(),保持节奏稳定
与硬件采样率、定时器配置强耦合的三个关键点
算法效果不取决于代码多漂亮,而取决于它是否和物理采样节拍咬合。以下三点出错,滤波基本失效:
- 采样率误差 > 0.5% 就会导致
N=20窗口无法覆盖完整工频周期——检查时钟源(HSI精度通常±1%,HSE需外接晶振) - 触发源必须是硬件定时器(而非软件延时),且定时器更新事件(UEV)要映射到ADC的触发输入(如
TIMx_TRGO) - 若用HAL库,禁用
HAL_ADC_Start_IT()这类非周期启动方式;改用HAL_ADC_Start_DMA()并设HAL_ADC_STATE_REG_EOC模式确保等间隔
实测发现:比算法更常出问题的是数据类型与溢出处理
很多项目跑着跑着滤波输出突然归零或跳变,90%以上是累加器溢出或隐式类型转换导致。例如:
// ❌ 危险写法:buffer为uint16_t,sum为int32_t,但除法前未转成更大类型 return sum / N; // 若sum为负(因溢出回绕),结果异常 // ✅ 安全写法(见上模板):sum用int64_t,除法前已保证符号正确
- ADC原始值若带符号(如差分模式),务必统一用有符号类型(
int16_t),否则负值被解释为大正数直接冲爆累加器 - 编译器优化等级(-O2/-O3)可能把循环展开并误判依赖关系,对环形索引建议加
volatile或用__attribute__((optimize("O1")))局部降级 - 调试时可用逻辑分析仪抓
ADC_DR和滤波后 GPIO 翻转,确认延迟是否恒定——滑动平均引入固定(N-1)/2采样点延迟,比如N=20延迟9.5ms,这个必须纳入控制环路设计
实际部署时,最易被忽略的是采样时钟稳定性与环形缓冲区的边界原子性。尤其在FreeRTOS任务中调用 push(),若没关调度或加锁,head 可能被撕裂——不如直接放在ADC中断或DMA回调里,彻底规避并发。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











