一阶低通滤波入门教程:从原理到单片机 C 代码实现

一阶低通滤波入门教程:从原理到单片机 C 代码实现

在单片机项目中,我们经常会读取各种传感器数据,例如 ADC 电压、温度、电流、距离、速度、角度等。

但是实际采集到的数据通常不是完全稳定的,而是会有一定抖动。

例如电压真实值可能是 3.30V,但 ADC 采集出来可能是:

text 复制代码
3.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 复制代码
一阶卡尔曼滤波
相关推荐
kcuwu.3 小时前
KNN算法技术博客
算法
叼烟扛炮3 小时前
C++ 知识点02 输入输出
开发语言·c++·算法·输入输出
qcx233 小时前
深度解析Deepseek V4:1M 上下文不是军备竞赛,是养 Agent 的人才知道的痛
java·开发语言
小此方3 小时前
Re:思考·重建·记录 现代C++ C++11篇(六) 从 shared_ptr 到 weak_ptr:起底智能指针的引用计数与循环引用之痛
开发语言·c++·c++11·现代c++
晨非辰3 小时前
吃透C++两大默认成员函数:const成员函数、 & 取地址运算符重载
java·大数据·开发语言·c++·人工智能·后端·面试
学会去珍惜3 小时前
8天学会C语言编程第2天:变量、数据类型和输入/输出,3分钟上手
c语言·实战·变量·编程入门·输入输出
我滴老baby3 小时前
多智能体协作系统设计当AI学会团队合作效率翻十倍
android·开发语言·人工智能
落雪寒窗-3 小时前
Python进阶核心路线(工程向)
开发语言·python
humcomm3 小时前
全栈开发技术栈的最新进展(2026年视角)
开发语言·架构