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;
}