滑动平均滤波须用定长环形数组而非std::vector,因后者在单片机上引发堆操作和hardfault;中位值滤波宜用奇数点+选择法或截断插入排序;二者应先中位后平均以抑制脉冲噪声;ram中缓冲区需放.no_init段保状态。

滑动平均滤波为什么不能直接用 std::vector?
单片机内存极小(尤其 Cortex-M0/M3),std::vector 的动态分配、迭代器、容量管理会引入不可控的堆操作和额外 RAM 开销,还可能触发 HardFault。必须用定长数组 + 索引轮转实现。
实操建议:
- 定义固定大小的环形缓冲区,例如
int16_t buffer[8],配合uint8_t head指针 - 每次新采样覆盖最老值:
buffer[head] = new_sample; head = (head + 1) & 0x07;(位运算比取模快,前提是长度为 2 的幂) - 求平均时遍历整个 buffer,避免维护累加和——虽然牺牲一点性能,但杜绝溢出风险(
int16_t × 8最大 32767×8=262136,超出int32_t安全范围需显式 cast) - 若用累加和优化,务必用
int32_t sum存储,并在更新时:sum = sum - buffer[head] + new_sample;
再更新buffer[head]和head,顺序错会导致结果偏差
中位值滤波在资源受限下怎么快速排序?
对 5–9 点做完整快排或 std::sort 是奢侈的。实际只需找到中间那个数,用「冒泡选中位」或「插入排序截断」更合适。
实操建议:
- 采样点数取奇数(如 5 或 7),避免偶数中位插值带来的浮点或移位开销
- 对 5 点,用 6 次比较完成选择(比完整冒泡少):
// 伪代码:只保留下标 2(中位)的值 if (a[0] > a[1]) swap(a[0],a[1]); if (a[2] > a[3]) swap(a[2],a[3]); if (a[0] > a[2]) swap(a[0],a[2]); // a[0] 是最小 if (a[1] > a[3]) swap(a[1],a[3]); // a[3] 是最大 if (a[1] > a[2]) swap(a[1],a[2]); // 此时 a[2] 即中位
- 若用插入排序,只排到第
(N+1)/2个位置即可停止,不需全排 - 注意:中位值滤波对连续同值毛刺(如 ADC 偶然卡死在某值)无效,需搭配限幅或超时检测
滑动平均和中位值能直接串联吗?顺序有讲究
先中位再平均,比先平均再中位更能抑制脉冲型毛刺。因为中位值能剔除单点异常值,而滑动平均对残留的缓变噪声更有效;反过来,若先平均,异常值已污染多个窗口值,中位就救不回来了。
实操建议:
- 典型组合:5 点中位 → 4 点滑动平均(即两级 FIFO),总延迟 = 5/2 + 4/2 = 4.5 个采样周期
- 避免嵌套过深:三级以上滤波会使响应变慢,对需要快速检测阈值的场景(如过压保护)可能失效
- 若 ADC 采样率是 1kHz,上述组合延迟约 4.5ms——要确认是否满足你的控制环路带宽要求
- 调试时可临时输出原始值、中位后值、最终值,用逻辑分析仪或串口打点验证各级效果
__attribute__((section(".ram_no_init"))) 用来干啥?
有些单片机(如 STM32L 系列)上电后 RAM 会被初始化为 0,但环形缓冲区的 head 指针和 buffer 内容不该被清零——否则上次滤波状态丢失,首几个输出不准。这时需把缓冲区放在未初始化段。
实操建议:
- 声明时加属性(GCC/Clang):
static int16_t buffer[8] __attribute__((section(".ram_no_init"))); static uint8_t head __attribute__((section(".ram_no_init"))); - 同时需在链接脚本中定义
.ram_no_init段,指向 RAM 区域且不执行 zero-init - 若不支持该属性,改用
__attribute__((used))防止被优化掉,并在 startup 代码里跳过对该段的清零循环 - 别忘了给 buffer 赋初值(如全填
0x7FFF),否则上电第一次读可能是随机值
中位值滤波的“中位”不是数学意义的精确中位,而是你选定窗口内排序后的中心样本——它不关心分布,只认位置。这点容易被忽略,导致误以为能消除所有噪声。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











