附录:MCU 代码风格
算法能跑起来只是第一步。真正的工程代码还要考虑初始化、溢出、单位、异常输入和可测试性。
每个滤波器都应该有状态结构体
不要把所有变量写成散乱的全局变量。更推荐:
#include <stdint.h>
typedef struct {
float y; /* 上一次滤波输出 */
uint8_t init; /* 是否已经用第一笔数据初始化 */
} Lowpass1st;
float lowpass1st_update(Lowpass1st *f, float x, float alpha)
{
if (!f->init) {
/* 第一次调用时直接采用输入值,避免从 0 慢慢爬升。 */
f->y = x;
f->init = 1;
return x;
}
/* 一阶滞后更新:只修正当前误差的一部分。 */
f->y += alpha * (x - f->y);
return f->y;
}
这样一个项目里可以同时创建多个滤波器实例,互不干扰。
初始化很重要
很多滤波器第一次输出不能默认从 0 开始,否则会产生很长的启动过渡。通常第一次采样时直接让输出等于输入。
注意整数溢出
ADC 是整数,累加时很容易溢出。
例如 12 位 ADC 最大值 4095,累加 1000 次最大约 4095000,已经超过 16 位整数范围。
定点数思想
没有 FPU 的 MCU 上,可以把小数放大成整数。例如把电压单位从 V 改成 mV,或者把系数放大 1024 倍。
一阶滞后可以写成:
其中 alpha_q = alpha * 1024。
定点实现要特别注意符号、移位和中间变量位宽。
单位要统一
滤波前后最好明确单位:
- ADC 原始码值
- mV
- 摄氏度
- Pa
- g
- deg/s
同一个滤波器内部不要混用单位。单位混乱会让阈值和参数失去意义。
保留原始数据
调试和写教材示例时,不要只保留滤波后的值。至少同时记录:
如果是报警逻辑,还应记录阈值、状态和消抖计数。记录方式可以是数组、日志文件、仿真表格或调试输出,不必把调试输出作为主线。
参数要集中管理
不要把 0.1、20、5.0 这样的数字散落在代码中。
#define TEMP_LPF_ALPHA 0.08f
#define KEY_DEBOUNCE_MS 20
#define BAT_LOW_ON_MV 3300
#define BAT_LOW_OFF_MV 3450
参数名本身就是文档。半年后回来看,也能知道每个数字的意义。