嵌入式常用滤波算法与控制算法(5)卡尔曼滤波(下)

第 5 篇:卡尔曼滤波(下)------50 行 C 代码 + MPU6050 实战

上篇把原理讲透了。这篇直接上代码------一维卡尔曼、二维卡尔曼、MPU6050 角度估计,全都有。


1. 一维卡尔曼(不到 50 行)

c 复制代码
// kalman1d.h
typedef struct {
    float x;     // 状态估计值
    float p;     // 估计误差协方差
    float q;     // 过程噪声协方差
    float r;     // 测量噪声协方差
} kalman1d_t;

void  kalman1d_init(kalman1d_t *kf, float q, float r, float init_x);
float kalman1d_update(kalman1d_t *kf, float measurement);
c 复制代码
// kalman1d.c
#include "kalman1d.h"

void kalman1d_init(kalman1d_t *kf, float q, float r, float init_x) {
    kf->x = init_x;
    kf->p = 1.0f;  // 初始不确定性,设为 1(会快速收敛)
    kf->q = q;     // 过程噪声
    kf->r = r;     // 测量噪声
}

float kalman1d_update(kalman1d_t *kf, float z) {
    // === 预测 ===
    // x = x(一维无控制输入,状态不变)
    kf->p = kf->p + kf->q;   // P⁻ = P + Q

    // === 更新 ===
    float k = kf->p / (kf->p + kf->r);    // K = P⁻/(P⁻+R)
    kf->x   = kf->x + k * (z - kf->x);    // x̂ = x̂⁻ + K(z - x̂⁻)
    kf->p   = (1.0f - k) * kf->p;         // P = (1-K)P⁻

    return kf->x;
}

使用示例------超声波测距:

c 复制代码
kalman1d_t dist_kf;

void setup(void) {
    // R = 测距噪声方差。超声波测距精度约 ±2cm,方差 ≈ 4
    // Q = 目标不会瞬间移动,设小一点
    kalman1d_init(&dist_kf, 0.01f, 4.0f, 100.0f);
}

void loop(void) {
    float raw_dist  = ultrasonic_read_cm();   // 超声波原始距离
    float opt_dist  = kalman1d_update(&dist_kf, raw_dist);
    printf("raw=%.1fcm, kalman=%.1fcm\r\n", raw_dist, opt_dist);
    delay_ms(50);
}

2. 二维卡尔曼------同时估计位置和速度

很多场景下,我们不仅想知道"现在在哪",还想知道"现在多快"。

状态向量 x = 位置, 速度

c 复制代码
// kalman2d.h
typedef struct {
    float x[2];     // [位置, 速度]
    float p[2][2];  // 2×2 协方差矩阵
    float q[2][2];  // 过程噪声
    float r;        // 测量噪声(只测位置)
    float dt;       // 采样间隔
} kalman2d_t;

void  kalman2d_init(kalman2d_t *kf, float dt, float q_pos, float q_vel, float r);
float kalman2d_update(kalman2d_t *kf, float measurement);
void  kalman2d_get_state(kalman2d_t *kf, float *pos, float *vel);
c 复制代码
// kalman2d.c
#include "kalman2d.h"
#include <string.h>

void kalman2d_init(kalman2d_t *kf, float dt, float q_pos, float q_vel, float r) {
    kf->dt = dt;
    kf->r  = r;
    memset(kf->x, 0, sizeof(kf->x));
    memset(kf->p, 0, sizeof(kf->p));
    kf->p[0][0] = 1.0f; kf->p[1][1] = 1.0f;  // 初始协方差
    kf->q[0][0] = q_pos; kf->q[1][1] = q_vel; // 对角噪声阵
}

float kalman2d_update(kalman2d_t *kf, float z) {
    float dt = kf->dt;

    // === 预测 ===
    // x⁻[0] = x[0] + x[1]*dt   → 位置 = 位置 + 速度×时间
    // x⁻[1] = x[1]              → 速度不变(匀速模型)
    float x_pred[2];
    x_pred[0] = kf->x[0] + kf->x[1] * dt;
    x_pred[1] = kf->x[1];

    // P⁻ = A·P·Aᵀ + Q
    // A = [[1, dt], [0, 1]]
    float p_pred[2][2];
    p_pred[0][0] = kf->p[0][0] + 2*dt*kf->p[0][1] + dt*dt*kf->p[1][1] + kf->q[0][0];
    p_pred[0][1] = kf->p[0][1] + dt*kf->p[1][1];
    p_pred[1][0] = p_pred[0][1];
    p_pred[1][1] = kf->p[1][1] + kf->q[1][1];

    // === 更新(只测量位置 H=[1,0])===
    float s = p_pred[0][0] + kf->r;  // 新息协方差
    float k0 = p_pred[0][0] / s;     // 卡尔曼增益 K[0]
    float k1 = p_pred[1][0] / s;     // 卡尔曼增益 K[1]

    float y = z - x_pred[0];         // 新息 = 测量值 - 预测值

    kf->x[0] = x_pred[0] + k0 * y;   // 更新位置
    kf->x[1] = x_pred[1] + k1 * y;   // 更新速度

    kf->p[0][0] = (1 - k0) * p_pred[0][0];
    kf->p[0][1] = (1 - k0) * p_pred[0][1];
    kf->p[1][0] = p_pred[1][0] - k1 * p_pred[0][0];
    kf->p[1][1] = p_pred[1][1] - k1 * p_pred[0][1];

    return kf->x[0];
}

void kalman2d_get_state(kalman2d_t *kf, float *pos, float *vel) {
    *pos = kf->x[0];
    *vel = kf->x[1];
}

3. 实战:MPU6050 角度卡尔曼滤波

用二维卡尔曼估计俯仰角------同时得到角度和角速度。

c 复制代码
kalman2d_t angle_kf;

void mpu6050_angle_init(void) {
    // dt = 5ms (200Hz 采样)
    // q_pos = 0.001 (角度过程噪声小)
    // q_vel = 0.003 (角速度噪声稍大)
    // r = 0.03 (加速度计推算角度的噪声,实际测出的方差)
    kalman2d_init(&angle_kf, 0.005f, 0.001f, 0.003f, 0.03f);
}

float mpu6050_get_angle(void) {
    float ax, ay, az, gx, gy, gz;
    mpu6050_read_all(&ax, &ay, &az, &gx, &gy, &gz);

    // 加速度计推算的角度作为"测量值"
    float accel_angle = atan2f(ay, az) * 180.0f / 3.14159f;

    // 卡尔曼融合
    float angle = kalman2d_update(&angle_kf, accel_angle);

    // 注意:这里把角速度信息用在了预测模型里(匀速模型)
    // 更精确的做法是把 gyro 作为控制输入 u[k] 放进预测方程
    return angle;
}

效果对比:

makefile 复制代码
纯加速度计:  ±3° 晃动(高频振动)
纯陀螺仪:    持续漂移(1分钟漂5°)
互补滤波:    ±1°
卡尔曼:      ±0.5°

4. 卡尔曼滤波的坑

坑 1:Q 和 R 的初始值选错导致发散 → 解决办法:先用传感器数据手册算 R,Q 从 0.001 开始调

坑 2:协方差矩阵不对称导致数值不稳定 → 解决办法:强制对称 P[0][1] = P[1][0] = (P[0][1]+P[1][0])/2

坑 3:非线性系统用线性卡尔曼 → 线性卡尔曼假设 A·x+B·u,如果你的系统不是线性的(比如四旋翼姿态),需要用扩展卡尔曼(EKF)


下一篇:互补滤波------陀螺仪+加速度计的最佳拍档,比卡尔曼更简单实用

相关推荐
Lumos1861 小时前
嵌入式常用滤波算法与控制算法(6)互补滤波
算法
Tisfy1 小时前
LeetCode 3536.两个数字的最大乘积:O(1)空间维护max2
数学·算法·leetcode·题解
知识分享小能手1 小时前
统计学学习教程,从入门到精通,一元线性回归 —— 知识点详解(17)
学习·算法·线性回归
艾斯特_2 小时前
Function Calling与工具调用:让模型从回答走向执行
人工智能·python·算法·ai
hold?fish:palm2 小时前
11 滑动窗口最大值
数据结构·算法
xvhao20132 小时前
T750414 【游戏】计算24点 题解
数据结构·c++·算法·游戏
手写码匠3 小时前
Android 17 灵魂拷问深度解析:隐私、大屏、AI 端侧全面适配实战
人工智能·深度学习·算法·aigc
ysa0510303 小时前
优先队列贪心dp
c++·笔记·算法·板子
道影子4 小时前
《道德经》031兵者不祥,胜以丧礼处之
人工智能·深度学习·算法