一阶低通滤波入门教程:从原理到单片机 C 代码实现
在单片机项目中,我们经常会读取各种传感器数据,例如 ADC 电压、温度、电流、距离、速度、角度等。
但是实际采集到的数据通常不是完全稳定的,而是会有一定抖动。
例如电压真实值可能是 3.30V,但 ADC 采集出来可能是:
text3.28、3.31、3.29、3.33、3.30、3.27如果直接使用这些原始数据,显示会跳动,控制系统也可能抖动。
所以我们需要滤波算法,让数据更加平滑。
本文介绍一种单片机中非常常用、非常简单的滤波方法:一阶低通滤波。
一、什么是一阶低通滤波?
一阶低通滤波是一种非常常见的数字滤波算法。
它的作用是:
text
保留低频变化,削弱高频抖动。
通俗理解就是:
text
让慢慢变化的数据保留下来,
把快速乱跳的噪声压下去。
例如温度、电压、电池电量这类数据,本身变化比较慢,但采样时会有小幅抖动,一阶低通滤波就非常适合。
二、为什么叫"低通"?
"低通"的意思是:
text
低频信号可以通过,高频信号会被削弱。
什么是低频信号?
text
数据变化慢,比如温度慢慢升高。
什么是高频信号?
text
数据变化快,比如传感器随机抖动、毛刺、噪声。
所以低通滤波可以理解成:
让稳定趋势通过,把快速抖动过滤掉。
三、为什么叫"一阶"?
一阶低通滤波只使用:
text
当前输入值
上一次输出值
来计算新的输出值。
它不像复杂滤波器那样需要很多历史数据,也不需要复杂矩阵运算。
因此它非常适合单片机使用。
四、一阶低通滤波核心公式
一阶低通滤波最常见的公式是:
text
y = α * x + (1 - α) * y_last
其中:
| 符号 | 含义 |
|---|---|
| x | 当前输入值,也就是当前传感器原始数据 |
| y | 当前滤波后的输出值 |
| y_last | 上一次滤波后的输出值 |
| α | 滤波系数,范围一般是 0 到 1 |
也可以写成:
text
y = y_last + α * (x - y_last)
这两个公式本质是一样的。
第二种写法更容易理解:
text
新的输出 = 旧的输出 + 一部分误差
其中:
text
x - y_last
表示当前输入值和上一次滤波结果之间的差距。
五、α 参数怎么理解?
α 是一阶低通滤波最重要的参数。
它决定了滤波结果更相信新数据,还是更相信历史数据。
| α 大小 | 效果 |
|---|---|
| α 越大 | 越相信新数据,响应越快,但抖动更明显 |
| α 越小 | 越相信旧数据,数据更平滑,但响应更慢 |
例如:
text
α = 0.8
表示本次输出大部分来自新数据,响应快,但抖动也比较明显。
text
α = 0.1
表示本次输出更多参考历史结果,数据会很平滑,但变化会有延迟。
六、用生活例子理解 α
假设传感器原始值突然从 0 变成 100。
如果:
text
α = 1
那么输出会立刻变成 100,完全不滤波。
如果:
text
α = 0.5
输出变化可能是:
text
0 → 50 → 75 → 87.5 → 93.75 → ...
如果:
text
α = 0.1
输出变化可能是:
text
0 → 10 → 19 → 27.1 → 34.39 → ...
可以看到:
text
α 越小,变化越慢,但越平滑。
α 越大,变化越快,但越容易抖。
七、一阶低通滤波 C 语言代码
最简单的写法如下:
c
float LowPass_Filter(float input)
{
static float output = 0.0f;
float alpha = 0.2f;
output = alpha * input + (1.0f - alpha) * output;
return output;
}
这段代码中:
text
input:当前原始数据
output:滤波后的输出结果
alpha:滤波系数
由于 output 使用了 static,所以它会保存上一次滤波结果。
八、另一种更直观的写法
一阶低通滤波也可以写成:
c
float LowPass_Filter(float input)
{
static float output = 0.0f;
float alpha = 0.2f;
output = output + alpha * (input - output);
return output;
}
这和下面这个公式对应:
text
y = y_last + α * (x - y_last)
这句代码的含义是:
text
让输出值慢慢靠近输入值。
九、封装成结构体版本
如果项目中有多个传感器都需要滤波,比如电压、速度、温度,就不能只用一个 static output。
这时可以封装成结构体。
1. 定义结构体
c
typedef struct
{
float alpha;
float output;
} LowPassFilter_t;
2. 初始化函数
c
void LowPass_Init(LowPassFilter_t *filter, float alpha, float init_value)
{
filter->alpha = alpha;
filter->output = init_value;
}
3. 更新函数
c
float LowPass_Update(LowPassFilter_t *filter, float input)
{
filter->output = filter->output
+ filter->alpha * (input - filter->output);
return filter->output;
}
十、完整可移植代码
low_pass_filter.h
c
#ifndef __LOW_PASS_FILTER_H
#define __LOW_PASS_FILTER_H
typedef struct
{
float alpha;
float output;
} LowPassFilter_t;
void LowPass_Init(LowPassFilter_t *filter, float alpha, float init_value);
float LowPass_Update(LowPassFilter_t *filter, float input);
void LowPass_SetAlpha(LowPassFilter_t *filter, float alpha);
#endif
low_pass_filter.c
c
#include "low_pass_filter.h"
void LowPass_Init(LowPassFilter_t *filter, float alpha, float init_value)
{
filter->alpha = alpha;
filter->output = init_value;
}
float LowPass_Update(LowPassFilter_t *filter, float input)
{
filter->output = filter->output
+ filter->alpha * (input - filter->output);
return filter->output;
}
void LowPass_SetAlpha(LowPassFilter_t *filter, float alpha)
{
if (alpha < 0.0f)
{
alpha = 0.0f;
}
if (alpha > 1.0f)
{
alpha = 1.0f;
}
filter->alpha = alpha;
}
十一、使用示例:ADC 电压滤波
假设我们要对 ADC 采集到的电池电压进行滤波。
1. 定义滤波器对象
c
LowPassFilter_t voltage_filter;
2. 初始化滤波器
c
void User_Init(void)
{
LowPass_Init(&voltage_filter, 0.1f, 0.0f);
}
其中:
text
0.1f 表示滤波比较平滑
0.0f 表示初始输出值为 0
3. 周期读取并滤波
c
void ADC_Task(void)
{
float voltage_raw;
float voltage_filtered;
voltage_raw = ADC_GetVoltage();
voltage_filtered = LowPass_Update(&voltage_filter, voltage_raw);
printf("raw = %.2f, filtered = %.2f\r\n",
voltage_raw,
voltage_filtered);
}
十二、使用示例:电机速度滤波
编码器计算速度时,速度数据经常会跳动。
例如:
text
98、102、97、105、100、96
可以使用一阶低通滤波。
c
LowPassFilter_t speed_filter;
void Speed_Filter_Init(void)
{
LowPass_Init(&speed_filter, 0.2f, 0.0f);
}
float Get_Filtered_Speed(void)
{
float speed_raw;
float speed_filtered;
speed_raw = Encoder_GetSpeed();
speed_filtered = LowPass_Update(&speed_filter, speed_raw);
return speed_filtered;
}
如果发现速度滤波后响应太慢,可以把 α 调大一些,例如:
c
LowPass_SetAlpha(&speed_filter, 0.3f);
十三、使用示例:温度滤波
温度通常变化比较慢,所以 α 可以设置小一点。
c
LowPassFilter_t temp_filter;
void Temp_Filter_Init(void)
{
LowPass_Init(&temp_filter, 0.05f, 25.0f);
}
float Get_Filtered_Temperature(void)
{
float temp_raw;
float temp_filtered;
temp_raw = Sensor_GetTemperature();
temp_filtered = LowPass_Update(&temp_filter, temp_raw);
return temp_filtered;
}
对于温度:
text
α = 0.02 ~ 0.1 都比较常见
十四、α 参数怎么调?
α 是一阶低通滤波的关键。
可以按照下面的方法调。
1. 数据还是很抖
说明新数据占比太大,可以减小 α。
例如:
c
alpha = 0.3f;
改成:
c
alpha = 0.1f;
或者:
c
alpha = 0.05f;
2. 数据响应太慢
说明历史数据占比太大,可以增大 α。
例如:
c
alpha = 0.05f;
改成:
c
alpha = 0.2f;
或者:
c
alpha = 0.3f;
3. 常见 α 参考值
| 应用场景 | 推荐 α |
|---|---|
| 温度滤波 | 0.02 ~ 0.1 |
| 电池电压滤波 | 0.05 ~ 0.2 |
| ADC 电位器 | 0.1 ~ 0.3 |
| 超声波距离 | 0.1 ~ 0.3 |
| 电机速度 | 0.2 ~ 0.5 |
| IMU 角度简单平滑 | 0.1 ~ 0.3 |
| 电流采样 | 0.1 ~ 0.4 |
注意:
α 没有万能值,最终需要根据实际数据抖动情况和响应速度要求调试。
十五、一阶低通滤波的优点
text
1. 代码非常简单
2. 运算量很小
3. 不需要数组缓存
4. 适合实时系统
5. 参数只有一个,容易调试
6. 非常适合单片机项目
十六、一阶低通滤波的缺点
text
1. 会带来一定延迟
2. α 太小会导致响应变慢
3. α 太大滤波效果不明显
4. 对突然出现的大毛刺抑制能力一般
5. 无法像中值滤波那样直接剔除异常值
所以在实际工程中,经常会组合使用:
text
限幅滤波 + 一阶低通滤波
中值滤波 + 一阶低通滤波
十七、加入异常值限制的低通滤波
如果传感器偶尔会出现明显异常值,可以先做限幅判断,再进行低通滤波。
c
float LowPass_Update_WithLimit(LowPassFilter_t *filter,
float input,
float max_delta)
{
float diff;
diff = input - filter->output;
if (diff > max_delta || diff < -max_delta)
{
input = filter->output;
}
filter->output = filter->output
+ filter->alpha * (input - filter->output);
return filter->output;
}
使用示例:
c
voltage_filtered = LowPass_Update_WithLimit(&voltage_filter,
voltage_raw,
0.5f);
表示如果本次电压相对上次滤波结果跳变超过 0.5V,就认为它可能是异常值。
十八、低通滤波和滑动平均滤波的区别
| 对比项 | 一阶低通滤波 | 滑动平均滤波 |
|---|---|---|
| 是否需要数组 | 不需要 | 需要 |
| 参数 | α | 窗口长度 N |
| 运算量 | 很小 | 随 N 增大 |
| 内存占用 | 很小 | 需要缓存 N 个数据 |
| 响应速度 | 由 α 决定 | 由 N 决定 |
| 实现难度 | 非常简单 | 简单 |
| 实时性 | 很好 | 较好 |
简单理解:
text
低通滤波:用一个系数控制新旧数据比例。
滑动平均:保留最近 N 个数据求平均。
如果是资源很紧张的单片机,一阶低通滤波更加轻量。
十九、低通滤波和卡尔曼滤波的区别
| 对比项 | 一阶低通滤波 | 一阶卡尔曼滤波 |
|---|---|---|
| 核心思想 | 固定比例平滑 | 动态估计 |
| 参数 | α | q、r、p |
| 调参难度 | 简单 | 中等 |
| 运算量 | 很小 | 小 |
| 代码复杂度 | 很低 | 稍高 |
| 适合入门 | 非常适合 | 适合进阶 |
公式对比:
c
// 一阶低通滤波
output = output + alpha * (input - output);
// 一阶卡尔曼滤波
x = x + k * (measurement - x);
可以看到两者形式很像。
区别是:
text
低通滤波的 alpha 是固定的;
卡尔曼滤波的 k 是动态计算的。
所以可以简单理解为:
一阶低通滤波是固定权重滤波;
一阶卡尔曼滤波是动态权重滤波。
二十、一阶低通滤波的工程注意事项
1. 初始值不要随便设
如果初始值和真实值差太多,滤波输出会慢慢爬过去。
例如电池电压真实是 12V,你初始化为 0:
c
LowPass_Init(&voltage_filter, 0.05f, 0.0f);
那么刚开始输出会从 0 慢慢接近 12,前期数据不准确。
更推荐:
c
float first_voltage = ADC_GetVoltage();
LowPass_Init(&voltage_filter, 0.05f, first_voltage);
2. 控制系统中不要滤得太狠
如果是显示数据,可以滤得平滑一点。
但是如果用于控制,比如:
text
电机速度控制
姿态控制
电流环控制
就不能让滤波延迟太大。
否则控制器拿到的是"落后的数据",容易导致控制效果变差。
3. α 不要超过 0 到 1
一般情况下:
text
0 < α < 1
如果:
text
α = 1
表示完全相信新数据,不滤波。
如果:
text
α = 0
表示输出永远不变。
4. 不同传感器要用不同滤波器对象
不要多个传感器共用一个 static output。
错误示例:
c
float LowPass_Filter(float input)
{
static float output = 0.0f;
float alpha = 0.2f;
output = output + alpha * (input - output);
return output;
}
如果电压和速度都调用这个函数,会共用同一个 output,结果会互相影响。
正确做法是使用结构体版本:
c
LowPassFilter_t voltage_filter;
LowPassFilter_t speed_filter;
LowPassFilter_t temp_filter;
二十一、适合初学者记忆的版本
刚开始学习时,只需要记住:
text
output 是滤波后的值
input 是当前原始值
alpha 越大,响应越快
alpha 越小,数据越平滑
核心代码:
c
output = output + alpha * (input - output);
这一句就够用了。
二十二、最终推荐模板
c
typedef struct
{
float alpha;
float output;
} LowPassFilter_t;
void LowPass_Init(LowPassFilter_t *filter, float alpha, float init_value)
{
if (alpha < 0.0f)
{
alpha = 0.0f;
}
if (alpha > 1.0f)
{
alpha = 1.0f;
}
filter->alpha = alpha;
filter->output = init_value;
}
float LowPass_Update(LowPassFilter_t *filter, float input)
{
filter->output = filter->output
+ filter->alpha * (input - filter->output);
return filter->output;
}
调用示例:
c
LowPassFilter_t filter;
void User_Init(void)
{
float first_value;
first_value = Sensor_Read();
LowPass_Init(&filter, 0.2f, first_value);
}
void User_Task(void)
{
float raw_value;
float filtered_value;
raw_value = Sensor_Read();
filtered_value = LowPass_Update(&filter, raw_value);
printf("raw = %.2f, filtered = %.2f\r\n",
raw_value,
filtered_value);
}
二十三、总结
一阶低通滤波是单片机中最常用的滤波算法之一。
它的核心公式是:
text
y = α * x + (1 - α) * y_last
或者:
text
y = y_last + α * (x - y_last)
它的核心思想是:
text
让输出值慢慢靠近输入值。
参数 α 决定滤波效果:
text
α 越大,响应越快,但更容易抖。
α 越小,越平滑,但响应更慢。
一阶低通滤波非常适合:
text
ADC 采样
电池电压
温度数据
电流检测
距离测量
电机速度
简单角度平滑
对于单片机入门来说,掌握下面这一句代码就已经可以解决很多数据抖动问题:
c
output = output + alpha * (input - output);
如果你的数据只是普通小幅抖动,优先使用一阶低通滤波。
如果数据会偶尔出现大毛刺,可以组合使用:
text
限幅滤波 + 一阶低通滤波
如果数据需要更智能的动态估计,可以进一步学习:
text
一阶卡尔曼滤波