1. 限幅滤波法
(1)原理
通过给定的最大偏差值A来实现,如果本次值与上次差值<=A,则本次有效;否则无效,使用上次值代替。
(2)特点
- 优点:能有效克服偶然因素导致的脉冲干扰
- 缺点:无法抑制周期性干扰,平滑性差
(3)例程
#include <stdio.h>
#include <stdlib.h>
// 限幅滤波法(又称程序判断滤波法)
#define FILTER_A 1
int Filter_Value;
int Value;
// 模拟获取AD值
int Get_AD() {
// 在实际应用中,这里应该是从ADC获取数据的代码
// 此处为了演示,返回一个随机值
return rand() % 20;
}
int Filter() {
int NewValue;
NewValue = Get_AD();
if (((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A)) {
return Value;
} else {
return NewValue;
}
}
int main() {
// 初始化Value
Value = Get_AD();
for (int i = 0; i < 10; i++) {
Filter_Value = Filter();
printf("Filtered Value: %d\n", Filter_Value);
Value = Filter_Value; // 更新Value以进行下一次比较
}
return 0;
}2. 算术平均滤波法
(1)原理
连续采用N个值进行算数平均运算
(2)说明
· N值较大时:信号平滑度较高,但灵敏度较低;
· N值较小时:信号平滑度较低,但灵敏度较高;
· N值的选取:一般流量,N=12;压力:N=4
(3)特点
- 优点:适合对一般具有随机干扰的信号(即信号在某一数据范围附近上下波动)进行滤波
- 缺点:不适合待测量变换较快或要求数据计算速度较快的实时控制,较浪费RAM
(4)例程
#include <stdio.h>
#include <stdlib.h> // 用于 rand()
#include <unistd.h> // 用于 sleep(),模拟 delay()
// 算术平均滤波法
#define FILTER_N 12
int Filter_Value;
// 模拟获取AD(模数转换)值
int Get_AD() {
// 实际应用中,这里应为从硬件ADC读取数据的代码
// 此处为演示目的,返回一个随机值
return rand() % 100;
}
// 模拟延时函数
void delay(int milliseconds) {
// 实际嵌入式环境中使用特定的延时函数
// 此处使用 sleep 来模拟,注意单位是秒,所以做相应转换
usleep(milliseconds * 1000);
}
// 算术平均滤波函数
int Filter() {
int i;
int filter_sum = 0;
for (i = 0; i < FILTER_N; i++) {
filter_sum += Get_AD();
delay(1); // 延时1毫秒
}
return (int)(filter_sum / FILTER_N);
}
int main() {
printf("开始进行算术平均滤波...\n");
Filter_Value = Filter();
printf("滤波后的结果是: %d\n", Filter_Value);
return 0;
}3. 中位值滤波法
(1)原理
连续采集N次值(N为奇数),将采样值按大小排列,取中间值为本次有效值
(2)特点
- 优点:能有效克服偶然因素导致的波动干扰,对温度等变换缓慢的被测参数有良好滤波效果
- 缺点:不适合待测量快速变换的场合,如流量、速度等
(3)例程
#include <stdio.h>
#include <stdlib.h> // 用于 rand()
#include <unistd.h> // 用于 usleep(),模拟 delay()
// 中位值滤波法
#define FILTER_N 11 // 使用一个较小的奇数便于演示
int Filter_Value;
// 模拟获取AD(模数转换)值
int Get_AD() {
// 实际应用中,这里应为从硬件ADC读取数据的代码
// 此处为演示目的,返回一个0-99的随机值
return rand() % 100;
}
// 模拟延时函数
void delay(int milliseconds) {
// 实际嵌入式环境中使用特定的延时函数
// 此处使用 usleep 来模拟
usleep(milliseconds * 1000);
}
// 中位值滤波函数
int Filter() {
int filter_buf[FILTER_N];
int i, j;
int filter_temp;
// 1. 连续采样N个数据点
for (i = 0; i < FILTER_N; i++) {
filter_buf[i] = Get_AD();
delay(1);
}
// 2. 对采样值进行排序(这里使用冒泡排序法)
for (j = 0; j < FILTER_N - 1; j++) {
for (i = 0; i < FILTER_N - 1 - j; i++) {
// 如果前一个数大于后一个数,则交换它们的位置
if (filter_buf[i] > filter_buf[i + 1]) {
filter_temp = filter_buf[i];
filter_buf[i] = filter_buf[i + 1];
filter_buf[i + 1] = filter_temp;
}
}
}
// 3. 返回排序后位于中间位置的值
return filter_buf[(FILTER_N - 1) / 2];
}
int main() {
printf("开始进行中位值滤波...\n");
// 为了演示,我们先打印一下原始采样值
printf("原始采样值 (模拟):\n");
for (int k = 0; k < FILTER_N; k++) {
printf("%d ", Get_AD());
}
printf("\n");
Filter_Value = Filter();
printf("滤波后的中位值是: %d\n", Filter_Value);
return 0;
}4. 递推平均滤波法
(1)原理
把连续N个采样值看成一个队列,队列长度固定为N,每次采样把新数据放入队尾,并扔掉队首的一次数据,把队列中的N个数据进行平均计算,即获得新的滤波值
(2)特点
- 优点:对周期性干扰具有良好的抑制作用,平滑度高,适合高频振荡系统;
- 缺点:对偶然出现的脉冲性干扰的抑制作用较差,不适于脉冲干扰较严重的场合,不适于开关电源电路。
(3)例程
#include <stdio.h>
#include <stdlib.h>
// 滑动平均滤波法(或递推平均滤波法)
#define N 12 // 滤波的样本数量
int value_buf[N] = {0}; // 存储数据的环形缓冲区,初始化为0
int current_index = 0; // 当前数据存储的位置索引
// 模拟获取AD(模数转换)值
int get_ad() {
// 实际应用中,这里应为从硬件ADC读取数据的代码
// 此处为演示目的,返回一个0-255的随机值
return rand() % 256;
}
// 滑动平均滤波函数
int filter(void) {
int sum = 0;
int count = 0;
// 1. 获取新值并存入环形缓冲区
// 当 current_index 到达末尾后,会自动回到0,覆盖最旧的数据
value_buf[current_index++] = get_ad();
if (current_index == N) {
current_index = 0; // 实现环形效果
}
// 2. 计算当前缓冲区内所有值的总和
for (count = 0; count < N; count++) {
sum += value_buf[count];
}
// 3. 返回平均值
return (sum / N);
}
int main() {
printf("开始进行滑动平均滤波...\n");
// 模拟连续调用filter函数
for (int i = 0; i < 20; i++) {
int filtered_value = filter();
printf("第 %d 次滤波结果: %d\n", i + 1, filtered_value);
}
return 0;
}5. 中位值平均滤波法
(1)原理
采一组队列去掉最大值和最小值
(2)特点
- 优点:融合了两种滤波的优点。对于偶然出现的脉冲性干扰,可消除由其引起的采样值偏差,对周期干扰有良好的抑制作用,平滑度高,适于高频振荡的系统;
- 缺点:测量速度慢;
(3)例程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 用于 usleep(),模拟 delay()
// 中位值平均滤波法(掐头去尾平均滤波法)
#define N 12 // 采样数量
// 模拟获取AD(模数转换)值
int get_ad() {
// 实际应用中,这里应为从硬件ADC读取数据的代码
// 此处为演示目的,返回一个0-255的随机值
return rand() % 256;
}
// 模拟延时函数
void delay() {
// 实际嵌入式环境中使用特定的延时函数
// 此处使用 usleep 来模拟1毫秒延时
usleep(1000);
}
// 中位值平均滤波函数
int filter(void) {
int value_buf[N];
int i, j, temp;
long sum = 0; // 使用 long 类型防止求和时溢出
// 1. 连续采样N个数据
for (i = 0; i < N; i++) {
value_buf[i] = get_ad();
delay();
}
// 2. 对采样值进行排序(冒泡排序法)
for (j = 0; j < N - 1; j++) {
for (i = 0; i < N - 1 - j; i++) {
// 如果前一个数大于后一个数,则交换
if (value_buf[i] > value_buf[i + 1]) {
temp = value_buf[i];
value_buf[i] = value_buf[i + 1];
value_buf[i + 1] = temp;
}
}
}
// 3. 去掉最大值和最小值,对中间的N-2个数据求和
for (i = 1; i < N - 1; i++) {
sum += value_buf[i];
}
// 4. 返回平均值
if (N > 2) {
return (int)(sum / (N - 2));
} else {
return 0; // 避免除以零的错误
}
}
int main() {
printf("开始进行中位值平均滤波...\n");
int filtered_value = filter();
printf("滤波后的结果是: %d\n", filtered_value);
return 0;
}6. 限幅平均滤波法
(1)原理
限幅+平均
(2)特点
- 优点:融合了两种滤波法的优点,对于偶然出现的脉冲性干扰,可消除由其引起的采样值偏差;
- 缺点:较浪费RAM
(3)例程
#include <stdio.h>
#include <stdlib.h>
#include <math.h> // 用于 fabs()
/**
* @brief 限幅平均滤波法
* 结合了限幅滤波和滑动平均滤波的优点。
* 步骤1:滤除邻近采样点之间跳变超过阈值的脉冲干扰。
* 步骤2:对处理后的数据进行滑动平均,以获得更平滑的结果。
*
* @param input 输入的原始采样数据数组
* @param input_len 输入数据的长度
* @param threshold 限幅阈值,两个有效采样点之间的最大允许差值
* @param window_size 滑动平均的窗口大小
* @param output 输出的滤波结果数组(由调用者分配内存)
* @param output_len 输出结果的长度(通过指针返回)
*/
void amplitude_limiting_moving_average_filter(float *input, int input_len,
float threshold, int window_size,
float *output, int *output_len) {
// --- 参数合法性检查 ---
if (input == NULL || output == NULL || output_len == NULL || input_len <= 0 || window_size <= 0) {
if (output_len) *output_len = 0;
return;
}
// --- 步骤1: 限幅处理 ---
// 动态分配内存,存储经过限幅处理后的有效数据
float *valid_data = (float *)malloc(input_len * sizeof(float));
if (valid_data == NULL) { // 检查内存分配是否成功
*output_len = 0;
return;
}
// 初始化第一个数据点
valid_data[0] = input[0];
float last_valid_value = input[0];
int valid_data_count = 1;
for (int i = 1; i < input_len; i++) {
// 判断当前值与上一个有效值的差的绝对值是否超过阈值
if (fabs(input[i] - last_valid_value) > threshold) {
// 超过阈值,判定为干扰,使用上一个有效值作为当前值
valid_data[valid_data_count++] = last_valid_value;
} else {
// 未超过阈值,是有效数据,更新“上一个有效值”
last_valid_value = input[i];
valid_data[valid_data_count++] = input[i];
}
}
// --- 步骤2: 对有效数据进行滑动平均 ---
// 计算最终输出的数据长度
*output_len = valid_data_count - window_size + 1;
if (*output_len < 1) {
// 如果有效数据量不足以进行一次滑动平均,则清空输出
*output_len = 0;
free(valid_data);
return;
}
// 执行滑动平均计算
for (int i = 0; i < *output_len; i++) {
float sum = 0.0f;
// 累加窗口内的数据
for (int j = 0; j < window_size; j++) {
sum += valid_data[i + j];
}
// 计算平均值并存入输出数组
output[i] = sum / window_size;
}
// 释放动态分配的内存
free(valid_data);
}
// --- 主函数:用于演示和测试 ---
int main() {
float original_data[] = {20.0, 21.0, 20.5, 150.0, 22.0, 21.5, 22.5, -80.0, 23.0};
int data_len = sizeof(original_data) / sizeof(float);
float threshold = 10.0; // 变化阈值超过10.0则认为是干扰
int window_size = 3; // 滑动平均窗口为3
float filtered_data[data_len];
int filtered_len = 0;
printf("原始数据: ");
for (int i = 0; i < data_len; i++) {
printf("%.2f ", original_data[i]);
}
printf("\n");
amplitude_limiting_moving_average_filter(original_data, data_len, threshold,
window_size, filtered_data, &filtered_len);
printf("滤波后数据: ");
for (int i = 0; i < filtered_len; i++) {
printf("%.2f ", filtered_data[i]);
}
printf("\n");
return 0;
}7. 加权递推平均滤波法
(1)原理
对递推平均滤波法的改进,即不同时刻的数据加以不同权值;通常是越接近现时刻的数据,权值越大,新采样值的权重越大,则灵敏度越高,但信号平滑度越低。
(2)特点
- 优点:适用于有较大纯滞后时间常数的对象,和采样周期较短的系统;
- 缺点:对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号;不能迅速反应系统当前所受干扰的严重程度,滤波效果差。
(3)例程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 用于 usleep(),模拟 delay()
// 加权平均滤波法
#define N 12 // 采样数量
// 权重系数表,可以根据需求调整
// 这里是一个简单的线性递增权重,表示越新的数据越重要
const int coe[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 模拟获取AD(模数转换)值
int get_ad() {
// 实际应用中,这里应为从硬件ADC读取数据的代码
// 此处为演示目的,返回一个0-255的随机值
return rand() % 256;
}
// 模拟延时函数
void delay() {
// 实际嵌入式环境中使用特定的延时函数
// 此处使用 usleep 来模拟1毫秒延时
usleep(1000);
}
// 加权平均滤波函数
int filter(void) {
int value_buf[N];
long weighted_sum = 0; // 使用long防止加权和溢出
int sum_of_coeffs = 0; // 权重总和
int i;
// 1. 连续采样N个数据
for (i = 0; i < N; i++) {
value_buf[i] = get_ad();
delay();
}
// 2. 计算加权和以及权重总和
for (i = 0; i < N; i++) {
weighted_sum += value_buf[i] * coe[i];
sum_of_coeffs += coe[i];
}
// 3. 返回加权平均值
if (sum_of_coeffs == 0) {
return 0; // 避免除以零的错误
}
return (int)(weighted_sum / sum_of_coeffs);
}
int main() {
printf("开始进行加权平均滤波...\n");
int filtered_value = filter();
printf("滤波后的结果是: %d\n", filtered_value);
return 0;
}8. 消抖滤波法
(1)原理
- 设置一个滤波计数器,将每次采样值与当前有效值比较;
- 如果采样值=当前有效值,计数器清零;
- 如果不相等,则计数器+1,并判断计数器是否溢出;
- 如果计数器溢出,则将本次值替换当前有效值,并清零计数器;
(2)特点
- 优点:对待测量缓慢变化的参数有较好滤波效果,可避免在临界值附近控制器的反复开关跳动或者显示器上数字抖动;
- 缺点:不适于快速变化的参数测量,如果计数器溢出的采样恰好是干扰值,则会将干扰值当做有效值引入系统;
(3)例程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 用于 usleep(),模拟 delay()
// 加权平均滤波法
#define N 12 // 采样数量
// 权重系数表,可以根据需求调整
// 这里是一个简单的线性递增权重,表示越新的数据越重要
const int coe[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 模拟获取AD(模数转换)值
int get_ad() {
// 实际应用中,这里应为从硬件ADC读取数据的代码
// 此处为演示目的,返回一个0-255的随机值
return rand() % 256;
}
// 模拟延时函数
void delay() {
// 实际嵌入式环境中使用特定的延时函数
// 此处使用 usleep 来模拟1毫秒延时
usleep(1000);
}
// 加权平均滤波函数
int filter(void) {
int value_buf[N];
long weighted_sum = 0; // 使用long防止加权和溢出
int sum_of_coeffs = 0; // 权重总和
int i;
// 1. 连续采样N个数据
for (i = 0; i < N; i++) {
value_buf[i] = get_ad();
delay();
}
// 2. 计算加权和以及权重总和
for (i = 0; i < N; i++) {
weighted_sum += value_buf[i] * coe[i];
sum_of_coeffs += coe[i];
}
// 3. 返回加权平均值
if (sum_of_coeffs == 0) {
return 0; // 避免除以零的错误
}
return (int)(weighted_sum / sum_of_coeffs);
}
int main() {
printf("开始进行加权平均滤波...\n");
int filtered_value = filter();
printf("滤波后的结果是: %d\n", filtered_value);
return 0;
}9. 限幅消抖滤波法
(1)原理
相当于“限幅滤波法”+“消抖滤波法”;
先限幅,后消抖。
(2)特点
- 优点:继承了“限幅”和“消抖”的优点,改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统;
- 缺点:不适合待测量变换快速的场合
(3)例程
#include <stdio.h>
#include <stdlib.h>
#include <math.h> // 用于 abs()
#include <stdbool.h>// 用于 bool 类型
// --- 滤波器参数定义 ---
// 1. 限幅阈值:相邻两次有效采样之间允许的最大差值
#define LIMIT_THRESHOLD 5
// 2. 消抖计数:需要连续多少次采样到新值,才确认更新
#define DEBOUNCE_COUNT 5
// --- 模拟硬件函数 ---
// 模拟获取AD值,带有一些干扰和跳变
int Get_AD() {
static int call_count = 0;
call_count++;
if (call_count < 5) return 100; // 初始稳定值
if (call_count == 5) return 200; // 一个大幅度的脉冲干扰
if (call_count < 10) return 102; // 一个小的、在阈值内的跳变
if (call_count < 15) return 108; // 一个超出阈值的新平台,但有抖动
if (call_count == 15) return 107; // 抖动
// 15次之后,稳定在新值 110
return 110;
}
/**
* @brief 限幅消抖滤波函数
* @details 结合了限幅和消抖功能。
* 首先滤除超出LIMIT_THRESHOLD的脉冲,然后要求新值连续出现DEBOUNCE_COUNT次才被确认为稳定输出。
* @return 滤波后的稳定值。
*/
int filter(void) {
// 使用static变量在函数调用之间保持状态
static int stable_value; // 当前稳定的输出值
static int debounce_count = 0; // 消抖计数器
static bool is_initialized = false; // 仅用于首次初始化
// 首次调用时,用第一个采样值初始化stable_value
if (!is_initialized) {
stable_value = Get_AD();
is_initialized = true;
}
// --- 步骤 1: 读取原始值并进行限幅处理 ---
int raw_value = Get_AD();
int limited_value;
// 如果原始值与当前稳定值的差的绝对值超过阈值,则忽略该原始值
if (abs(raw_value - stable_value) > LIMIT_THRESHOLD) {
limited_value = stable_value; // 使用旧的稳定值代替
} else {
limited_value = raw_value; // 否则,限幅后的值就是原始值
}
// --- 步骤 2: 对限幅后的值进行消抖处理 ---
// 如果限幅后的值与期望的新值(即当前稳定值)不同
if (limited_value != stable_value) {
debounce_count++; // 认为是抖动或一个潜在的新值,计数器加1
// 如果连续抖动的次数达到了要求
if (debounce_count >= DEBOUNCE_COUNT) {
debounce_count = 0; // 重置计数器
stable_value = limited_value; // 正式更新稳定值
}
} else {
// 如果限幅后的值等于当前稳定值,说明信号稳定,清零计数器
debounce_count = 0;
}
return stable_value;
}
int main() {
printf("开始进行限幅消抖滤波...\n");
printf("阈值 = %d, 消抖次数 = %d\n", LIMIT_THRESHOLD, DEBOUNCE_COUNT);
printf("----------------------------------\n");
for (int i = 0; i < 25; i++) {
int filtered_value = filter();
printf("第 %2d 次调用 | 滤波输出: %d\n", i + 1, filtered_value);
}
return 0;
}10. 一阶滞后滤波法
(1)原理
取α=(0,1),filterdata=(1−α)本次采样值+α上次滤波结果
(2)特点
优点:对周期性干扰具有良好的抑制作用,适合波动频率较高的场合;
缺点:有相位滞后,灵敏度低,滞后程度取决于α的值,不能消除滤波频率高于采样频率1/2的干扰信号
(3)例程
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // 用于 bool 类型
// --- 滤波器参数定义 ---
// 滤波系数 ALPHA 决定了滤波的平滑程度。
// ALPHA 越小,滤波结果越平滑,但响应越慢。
// ALPHA 越大,响应越快,但平滑效果越差。
// ALPHA 的取值范围为 (0, 1)。
#define ALPHA 0.1f
// --- 模拟硬件函数 ---
// 模拟一个从100突然跳变到500的信号
int Get_AD() {
static int call_count = 0;
call_count++;
if (call_count < 10) {
return 100; // 初始稳定信号
} else {
return 500; // 信号发生阶跃跳变
}
}
/**
* @brief 一阶滞后滤波函数 (指数移动平均)
* @details 本次输出 = ALPHA * 本次采样 + (1 - ALPHA) * 上次输出
* @return 经过平滑滤波后的整数值
*/
int filter(void) {
// 使用static变量在函数调用之间保持其值(即滤波器的状态)
static float filtered_value;
static bool is_initialized = false;
// 1. 首次调用时,用第一个采样值“预热”或“种子”滤波器
// 这可以避免滤波器从0开始缓慢爬升,使其立即进入有效状态。
if (!is_initialized) {
filtered_value = (float)Get_AD();
is_initialized = true;
}
// 2. 读取新的采样值
int new_value = Get_AD();
// 3. 应用一阶滞后滤波公式
// 使用浮点数进行计算以保持精度
filtered_value = (new_value * ALPHA) + (filtered_value * (1.0f - ALPHA));
// 4. 返回整数部分作为最终结果
return (int)filtered_value;
}
int main() {
printf("开始进行一阶滞后滤波 (ALPHA = %.2f)...\n", ALPHA);
printf("模拟信号从 100 跳变到 500。\n");
printf("----------------------------------------\n");
for (int i = 0; i < 30; i++) {
int result = filter();
printf("第 %2d 次调用 | 滤波输出: %d\n", i + 1, result);
}
return 0;
}