中位值平均滤波先取窗口内采样中位数剔除脉冲,再对多个中位数滑动平均抑制跳变;因脉冲排序后被挤至两端不参与中位计算,后续平均又平滑阶梯响应,故抗干扰优于单纯中位或均值。

中位值平均滤波是什么,为什么比单纯中位值或均值更抗脉冲干扰
中位值平均滤波(Median-Average Filter)不是标准库函数,而是一种组合策略:先对窗口内采样做排序取中位数,再对多个中位数做滑动平均。它比纯中位值滤波响应稍慢但更平滑,比纯均值滤波更能抵抗单点脉冲——因为脉冲在排序后大概率被挤到两端,不参与中位数计算;而后续的平均又抑制了中位值跳变带来的阶梯感。
关键点在于:中位值阶段剔除异常点,平均阶段压制残留抖动。对ADC采集的温度、电压等缓变信号特别有效,尤其当毛刺是孤立尖峰(如电源扰动、IO串扰)时。
用 std::nth_element 实现高效中位值提取(避免全排序)
std::nth_element 是 C++ 标准算法里最适合中位值场景的函数:它能在 O(n) 平均时间复杂度内将第 k 小元素放到正确位置,且左边元素 ≤ 它、右边 ≥ 它,无需完整排序。对奇数长度窗口(如 7 点),中位数就是索引为 size/2 的元素;偶数长度(如 8 点)通常取 size/2-1 和 size/2 的平均,但实际中多用奇数窗口简化逻辑。
- 窗口大小建议选 5~15 之间的奇数(如
WINDOW_SIZE = 7),太小抗干扰弱,太大延迟高、内存开销增 - 别用
std::sort—— 每次都全排,O(n log n),对嵌入式 MCU(如 STM32F1)可能成为瓶颈 - 注意
std::nth_element会修改原容器顺序,若需保留原始采样序列,得拷贝一份再操作
int window[7]; // 假设 window 已填入最新 7 次 ADC 值 std::nth_element(window, window + 3, window + 7); // 第 4 小(索引 3)即中位数 int median = window[3];
滑动中位值队列 + 环形缓冲区管理(避免动态分配)
中位值平均需要维护一个“中位值历史队列”,比如最近 5 个窗口的中位数再求均值。直接用 std::vector 或 std::deque 在裸机环境容易触发 malloc,应改用静态环形缓冲区。
立即学习“C++免费学习笔记(深入)”;
- 定义两个固定数组:
int adc_raw[WINDOW_SIZE]存原始采样,int medians[MEDIAN_HISTORY]存中位值历史(如MEDIAN_HISTORY = 5) - 用
uint8_t raw_head和uint8_t med_idx分别管理原始窗口和中位值队列的写入位置,每次覆盖最老数据 - 中位值平均输出 =
sum(medians) / MEDIAN_HISTORY,整数除法可加+ MEDIAN_HISTORY/2实现四舍五入 - 避免浮点运算:全部用
int32_t累加,最后再右移或除法,防止 ARM Cortex-M0/M3 上浮点性能拖累
ADC毛刺真实场景下的参数调优与陷阱
实测发现,单纯增大窗口未必更好。某 STM32L4 采集电池电压时,用 11 点中位值平均反而出现低频振荡——原因是毛刺不是完全随机,而是与 PWM 开关同步,形成周期性伪脉冲,导致中位值在两个相近值间反复切换。
- 先用示波器或逻辑分析仪抓 ADC 引脚波形,确认毛刺宽度(如 1μs 尖峰 vs 100μs 平顶干扰),据此设置采样间隔:毛刺宽则加大采样间隔,避开重叠
- 若毛刺有规律(如每 10ms 出现一次),可考虑异步采样:在预期毛刺间隙启动转换,比滤波更治本
- 慎用过大的
MEDIAN_HISTORY:5~7 足够,超过 9 后平均延迟明显,对快速变化信号(如电机电流)会滞后甚至失真 - 硬件配合不能少:PCB 上 ADC 参考源去耦电容要够(10μF + 100nF)、模拟地数字地单点连接、采样前加 RC 低通(如 1kΩ+100nF → 截止频率 ≈ 1.6MHz)能滤掉高频噪声,让软件滤波更轻松
真正难的不是写对那几行中位值代码,而是判断毛刺来源——是 layout 问题?电源纹波?还是外部 ESD?软件滤波只是最后一道防线。


















