4. 软件滤波算法:均值滤波、中值滤波、卡尔曼滤波在编码器信号处理中的应用对比

编码器信号这东西,看着是方波,实际上进了单片机引脚之后,啥妖蛾子都能碰上。毛刺、抖动、偶尔丢一个脉冲——你要是直接拿这个信号去换相,电机能抖得像筛子。所以,软件滤波是必须的。

我个人习惯把滤波分成三类:简单粗暴型、稳重型、还有那种“带脑子”的。今天咱们就把均值、中值、卡尔曼这三兄弟拉出来遛遛。

核心观点:没有最好的滤波,只有最合适的场景。换相时刻的实时性要求,决定了你选哪种。

4.1 均值滤波:简单,但有代价

均值滤波的原理,说白了就是“取平均”。你连续采 N 个位置值,加起来除以 N,输出一个平滑后的结果。

// 均值滤波示例(N=8)
#define FILTER_N 8
uint16_t buffer[FILTER_N] = {0};
uint8_t index = 0;
uint32_t sum = 0;

uint16_t mean_filter(uint16_t new_sample) {
    sum -= buffer[index];
    buffer[index] = new_sample;
    sum += new_sample;
    index = (index + 1) % FILTER_N;
    return sum / FILTER_N;
}

这个代码我用了好多年,确实能压住高频噪声。但有个坑——它会把真实的换相边沿也给抹平了。你想想看,电机刚转过一个霍尔边沿,位置本该跳变,均值滤波一平均,跳变变成了斜坡。换相点就滞后了。

我曾经在一个高速无刷电机项目里用均值滤波,转速一上 8000 RPM,换相滞后直接导致相电流畸变,电机啸叫。后来换成中值滤波才解决。

所以,均值滤波适合什么场景?编码器信号只有轻微白噪声,且你对实时性要求不高的时候。比如位置环的慢速反馈,或者转速计算前的预处理。

4.2 中值滤波:抗脉冲干扰的利器

中值滤波就聪明一点。它不取平均,而是把 N 个采样值排序,取中间那个。这样一来,哪怕有一个采样点因为干扰跳到了离谱的值,只要 N 足够大,中间值基本不受影响。

// 中值滤波示例(N=5)
#define MEDIAN_N 5
uint16_t median_filter(uint16_t new_sample) {
    static uint16_t ring[MEDIAN_N] = {0};
    static uint8_t idx = 0;
    uint16_t temp[MEDIAN_N];
    uint8_t i, j;
    uint16_t tmp;

    ring[idx] = new_sample;
    idx = (idx + 1) % MEDIAN_N;

    // 拷贝到临时数组并排序
    for(i = 0; i < MEDIAN_N; i++) temp[i] = ring[i];
    for(i = 0; i < MEDIAN_N - 1; i++) {
        for(j = i + 1; j < MEDIAN_N; j++) {
            if(temp[i] > temp[j]) {
                tmp = temp[i];
                temp[i] = temp[j];
                temp[j] = tmp;
            }
        }
    }
    return temp[MEDIAN_N / 2];
}

嗯,这里要注意——排序会消耗 CPU 时间。N 越大,排序越慢。我一般 N 取 3 或 5,再大就不划算了。

中值滤波最大的优点是保留边沿。一个真实的跳变,在排序后仍然会出现在中间位置附近,不会像均值那样被拉成斜坡。所以,换相信号处理,我首选中值滤波。

我的经验:在霍尔传感器换相应用中,N=3 的中值滤波就能干掉 90% 的随机毛刺,而且延迟只有 1-2 个采样周期。性价比极高。

4.3 卡尔曼滤波:带“脑子”的滤波器

卡尔曼滤波听起来高大上,其实它的核心就一句话:用模型预测 + 测量修正。它不只看当前采样值,还根据上一时刻的状态和系统模型,猜一下当前应该是什么值,然后再用测量值去修正这个猜测。

对于编码器信号,我们可以把电机位置建模成一个匀速运动模型。卡尔曼滤波就能在噪声中估计出真实位置,甚至还能预测下一时刻的位置。

// 一维卡尔曼滤波(位置估计)
typedef struct {
    float x;      // 估计位置
    float P;      // 估计误差协方差
    float Q;      // 过程噪声协方差
    float R;      // 测量噪声协方差
} Kalman1D;

void kalman_init(Kalman1D *k, float init_x, float init_P, float Q, float R) {
    k->x = init_x;
    k->P = init_P;
    k->Q = Q;
    k->R = R;
}

float kalman_update(Kalman1D *k, float z) {
    // 预测
    float P_pred = k->P + k->Q;
    // 卡尔曼增益
    float K = P_pred / (P_pred + k->R);
    // 更新
    k->x = k->x + K * (z - k->x);
    k->P = (1 - K) * P_pred;
    return k->x;
}

你看,代码量其实不大。但调参是个技术活——Q 和 R 怎么设?

  • Q(过程噪声):设大了,滤波器响应快但噪声多;设小了,响应慢但平滑。
  • R(测量噪声):设大了,滤波器更相信模型;设小了,更相信传感器。

我记得第一次调卡尔曼滤波时,Q 和 R 设反了,结果滤波后的位置比原始信号还抖。后来才明白,R 应该根据编码器数据手册的精度来设,Q 则根据你对电机模型的信任程度来调。

避坑指南:卡尔曼滤波在换相时刻有一个致命问题——如果模型预测的位置和实际换相位置偏差太大,滤波器会“犹豫”,导致换相延迟。我曾经在低速大负载场景下吃过这个亏,后来加了换相时刻的强制修正才解决。

4.4 三种滤波的对比与选型

说了这么多,到底怎么选?我整理了一个表格,你一看就明白。

特性 均值滤波 中值滤波 卡尔曼滤波
计算开销 低(仅加法与除法) 中(需排序) 中高(浮点运算)
延迟 高(N/2 采样周期) 低(约 1-2 周期) 低(可调)
抗脉冲干扰 差 优秀 良好
边沿保持 差(抹平跳变) 好 好(需调参)
适用场景 慢速位置环、转速预处理 霍尔换相、脉冲计数 高精度位置估计、速度预测
调参难度 低(只需选 N) 低(只需选 N) 高(Q、R 需调试)

说白了,如果你只想快速解决问题,选 N=3 的中值滤波。如果你对位置精度有极致要求,而且 CPU 算力够,卡尔曼滤波值得一试。均值滤波嘛——我建议只在非实时路径上用。

4.5 本章知识体系

下面这张图把三种滤波的核心逻辑和适用边界画清楚了,你可以存下来当参考。

编码器信号软件滤波算法对比框架 编码器信号软件滤波算法对比 原始编码器信号 均值滤波 取 N 个采样平均 中值滤波 排序取中间值 卡尔曼滤波 预测 + 测量修正 特性 • 计算开销:低 • 延迟:高 • 边沿保持:差 • 抗脉冲:差 特性 • 计算开销:中 • 延迟:低 • 边沿保持:好 • 抗脉冲:优秀 特性 • 计算开销:中高 • 延迟:低(可调) • 边沿保持:好 • 抗脉冲:良好 推荐场景 慢速位置环 推荐场景 霍尔换相、脉冲计数 推荐场景 高精度位置估计 选型建议:换相场景优先选中值滤波 追求精度且算力充裕时考虑卡尔曼滤波

最后说一句实在的——别在滤波上过度设计。我见过有人把卡尔曼滤波调了两周,最后发现换相抖动是因为霍尔安装间隙太大。先确认硬件没问题,再谈软件滤波。这是我一贯的原则。