基于STM32F103ZET6的MPU6050卡尔曼滤波角度测量系统

基于STM32F103ZET6的MPU6050卡尔曼滤波角度测量系统

一、系统架构设计

1.1 硬件配置

主控芯片:STM32F103ZET6(72MHz,512KB Flash,64KB RAM)
姿态传感器:MPU6050(6轴:3轴加速度计 + 3轴陀螺仪)
通信接口:I2C1(PB6-SCL,PB7-SDA)
显示模块:0.96寸OLED(I2C2,PB10-SCL,PB11-SDA)
调试接口:USART1(PA9-TX,PA10-RX)
电源管理:3.3V LDO,100nF去耦电容

1.2 卡尔曼滤波原理

状态方程:X(k) = A·X(k-1) + B·U(k) + W(k)
观测方程:Z(k) = H·X(k) + V(k)

状态向量:X = [角度, 角速度]ᵀ
控制输入:U = 陀螺仪测量值
观测值:Z = 加速度计计算的角度

卡尔曼增益:K(k) = P(k|k-1)·Hᵀ·(H·P(k|k-1)·Hᵀ + R)⁻¹
状态更新:X(k|k) = X(k|k-1) + K(k)·(Z(k) - H·X(k|k-1))
协方差更新:P(k|k) = (I - K(k)·H)·P(k|k-1)

二、完整工程源码

2.1 主程序(main.c)

/**
  * @file main.c
  * @brief STM32F103ZET6 + MPU6050 卡尔曼滤波角度测量
  * @version 2.0
  */
#include "stm32f10x.h"
#include "mpu6050.h"
#include "kalman.h"
#include "oled.h"
#include "uart.h"
#include "delay.h"
#include "math.h"

/* 系统参数 */
#define SAMPLE_RATE_HZ      200     // 采样率200Hz
#define DT                  0.005f   // 采样周期5ms (1/200Hz)
#define RAD_TO_DEG          57.295779513f  // 弧度转角度

/* 全局变量 */
KalmanFilter kalman_roll;   // 横滚角卡尔曼滤波器
KalmanFilter kalman_pitch;  // 俯仰角卡尔曼滤波器
MPU6050_Data mpu_data;
AngleData angle_data;
SystemStatus sys_status;

/* 角度数据结构 */
typedef struct {
    float roll;             // 横滚角(绕X轴)
    float pitch;            // 俯仰角(绕Y轴)
    float yaw;              // 航向角(绕Z轴)
    float roll_rate;        // 横滚角速度
    float pitch_rate;       // 俯仰角速度
    float yaw_rate;         // 航向角速度
    uint8_t is_valid;       // 数据有效性
} AngleData;

/* 系统状态 */
typedef struct {
    uint32_t sample_count;  // 采样计数
    uint32_t error_count;   // 错误计数
    float cpu_usage;        // CPU使用率
    uint8_t mpu_connected;  // MPU6050连接状态
} SystemStatus;

int main(void)
{
    /* 1. 系统初始化 */
    System_Init();
    
    /* 2. 外设初始化 */
    Delay_Init();
    OLED_Init();
    MPU6050_Init();
    UART_Init(115200);
    Kalman_Init(&kalman_roll);
    Kalman_Init(&kalman_pitch);
    
    /* 3. 检查MPU6050连接 */
    if (MPU6050_TestConnection())
    {
        sys_status.mpu_connected = 1;
        OLED_ShowString(0, 0, "MPU6050 Connected");
        UART_SendString("MPU6050 Connected!\r\n");
    }
    else
    {
        sys_status.mpu_connected = 0;
        OLED_ShowString(0, 0, "MPU6050 Error!");
        UART_SendString("MPU6050 Connection Failed!\r\n");
        while(1);
    }
    
    OLED_Refresh();
    Delay_Ms(1000);
    
    /* 4. 主循环 */
    while(1)
    {
        /* 4.1 读取MPU6050数据 */
        if (MPU6050_ReadData(&mpu_data))
        {
            /* 4.2 计算加速度计角度(静态) */
            float accel_roll = atan2(mpu_data.ay, mpu_data.az) * RAD_TO_DEG;
            float accel_pitch = atan2(-mpu_data.ax, sqrt(mpu_data.ay*mpu_data.ay + mpu_data.az*mpu_data.az)) * RAD_TO_DEG;
            
            /* 4.3 陀螺仪数据转换(转为度/秒) */
            float gyro_roll_rate = mpu_data.gx * 250.0f / 32768.0f;  // ±250°/s量程
            float gyro_pitch_rate = mpu_data.gy * 250.0f / 32768.0f;
            float gyro_yaw_rate = mpu_data.gz * 250.0f / 32768.0f;
            
            /* 4.4 卡尔曼滤波 */
            float kalman_roll_out = Kalman_Update(&kalman_roll, accel_roll, gyro_roll_rate, DT);
            float kalman_pitch_out = Kalman_Update(&kalman_pitch, accel_pitch, gyro_pitch_rate, DT);
            
            /* 4.5 更新角度数据 */
            angle_data.roll = kalman_roll_out;
            angle_data.pitch = kalman_pitch_out;
            angle_data.yaw += gyro_yaw_rate * DT;  // 航向角直接积分
            angle_data.roll_rate = gyro_roll_rate;
            angle_data.pitch_rate = gyro_pitch_rate;
            angle_data.yaw_rate = gyro_yaw_rate;
            angle_data.is_valid = 1;
            
            /* 4.6 显示数据 */
            OLED_ShowAngle(angle_data);
            
            /* 4.7 串口输出 */
            if (sys_status.sample_count % 20 == 0)  // 每20次采样输出一次(10Hz)
            {
                UART_SendAngleData(angle_data);
            }
            
            sys_status.sample_count++;
        }
        else
        {
            sys_status.error_count++;
            OLED_ShowString(0, 6, "Read Error!");
        }
        
        /* 4.8 延时控制采样率 */
        Delay_Ms(5);  // 200Hz采样率
    }
}

/* 系统初始化 */
void System_Init(void)
{
    /* 系统时钟配置:72MHz */
    SystemClock_Init();
    
    /* 中断优先级分组 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    /* 初始化全局变量 */
    memset(&angle_data, 0, sizeof(AngleData));
    memset(&sys_status, 0, sizeof(SystemStatus));
    sys_status.sample_count = 0;
    sys_status.error_count = 0;
    sys_status.mpu_connected = 0;
}

2.2 MPU6050驱动(mpu6050.c)

/**
  * @file mpu6050.c
  * @brief MPU6050驱动(I2C接口)
  */
#include "mpu6050.h"
#include "i2c.h"
#include "delay.h"

/* MPU6050寄存器定义 */
#define MPU6050_ADDR        0x68  // I2C地址(AD0=0)
#define WHO_AM_I           0x75  // 器件ID寄存器
#define PWR_MGMT_1         0x6B  // 电源管理寄存器1
#define SMPLRT_DIV         0x19  // 采样率分频寄存器
#define CONFIG             0x1A  // 配置寄存器
#define GYRO_CONFIG        0x1B  // 陀螺仪配置寄存器
#define ACCEL_CONFIG       0x1C  // 加速度计配置寄存器
#define FIFO_EN            0x23  // FIFO使能寄存器
#define INT_ENABLE         0x38  // 中断使能寄存器
#define INT_STATUS         0x3A  // 中断状态寄存器
#define ACCEL_XOUT_H       0x3B  // 加速度计X轴高字节
#define GYRO_XOUT_H        0x43  // 陀螺仪X轴高字节

/* 初始化MPU6050 */
uint8_t MPU6050_Init(void)
{
    uint8_t res;
    
    /* 1. 初始化I2C */
    I2C_Init();
    
    /* 2. 唤醒MPU6050 */
    MPU6050_WriteReg(PWR_MGMT_1, 0x00);  // 解除睡眠模式
    Delay_Ms(100);
    
    /* 3. 设置采样率分频 */
    MPU6050_WriteReg(SMPLRT_DIV, 0x07);  // 采样率 = 陀螺仪输出率/(1+SMPLRT_DIV) = 1kHz/(1+7)=125Hz
    
    /* 4. 配置低通滤波器 */
    MPU6050_WriteReg(CONFIG, 0x06);      // 5Hz低通滤波
    
    /* 5. 配置陀螺仪量程 ±250°/s */
    MPU6050_WriteReg(GYRO_CONFIG, 0x00);
    
    /* 6. 配置加速度计量程 ±2g */
    MPU6050_WriteReg(ACCEL_CONFIG, 0x00);
    
    /* 7. 关闭FIFO */
    MPU6050_WriteReg(FIFO_EN, 0x00);
    
    /* 8. 关闭中断 */
    MPU6050_WriteReg(INT_ENABLE, 0x00);
    
    /* 9. 验证器件ID */
    res = MPU6050_ReadReg(WHO_AM_I);
    if (res != 0x68)
    {
        return 0;  // 初始化失败
    }
    
    return 1;  // 初始化成功
}

/* 测试MPU6050连接 */
uint8_t MPU6050_TestConnection(void)
{
    uint8_t who_am_i = MPU6050_ReadReg(WHO_AM_I);
    return (who_am_i == 0x68) ? 1 : 0;
}

/* 读取MPU6050数据 */
uint8_t MPU6050_ReadData(MPU6050_Data* data)
{
    uint8_t buf[14];
    
    /* 读取14个寄存器(加速度计+陀螺仪) */
    if (MPU6050_ReadRegs(ACCEL_XOUT_H, buf, 14))
    {
        /* 解析加速度计数据(16位有符号) */
        data->ax = (int16_t)((buf[0] << 8) | buf[1]);
        data->ay = (int16_t)((buf[2] << 8) | buf[3]);
        data->az = (int16_t)((buf[4] << 8) | buf[5]);
        
        /* 跳过温度数据(2字节) */
        
        /* 解析陀螺仪数据(16位有符号) */
        data->gx = (int16_t)((buf[8] << 8) | buf[9]);
        data->gy = (int16_t)((buf[10] << 8) | buf[11]);
        data->gz = (int16_t)((buf[12] << 8) | buf[13]);
        
        return 1;
    }
    
    return 0;
}

/* 写寄存器 */
void MPU6050_WriteReg(uint8_t reg, uint8_t data)
{
    I2C_Start();
    I2C_SendByte(MPU6050_ADDR << 1);  // 写地址
    I2C_WaitAck();
    I2C_SendByte(reg);
    I2C_WaitAck();
    I2C_SendByte(data);
    I2C_WaitAck();
    I2C_Stop();
}

/* 读寄存器 */
uint8_t MPU6050_ReadReg(uint8_t reg)
{
    uint8_t data;
    
    I2C_Start();
    I2C_SendByte(MPU6050_ADDR << 1);  // 写地址
    I2C_WaitAck();
    I2C_SendByte(reg);
    I2C_WaitAck();
    
    I2C_Start();
    I2C_SendByte((MPU6050_ADDR << 1) | 0x01);  // 读地址
    I2C_WaitAck();
    data = I2C_ReadByte(0);  // 发送NACK
    I2C_Stop();
    
    return data;
}

/* 连续读多个寄存器 */
uint8_t MPU6050_ReadRegs(uint8_t reg, uint8_t* buf, uint8_t len)
{
    I2C_Start();
    I2C_SendByte(MPU6050_ADDR << 1);  // 写地址
    I2C_WaitAck();
    I2C_SendByte(reg);
    I2C_WaitAck();
    
    I2C_Start();
    I2C_SendByte((MPU6050_ADDR << 1) | 0x01);  // 读地址
    I2C_WaitAck();
    
    for (uint8_t i = 0; i < len; i++)
    {
        if (i == len - 1)
            buf[i] = I2C_ReadByte(0);  // 最后一个字节发送NACK
        else
            buf[i] = I2C_ReadByte(1);  // 发送ACK继续读
    }
    
    I2C_Stop();
    return 1;
}

/* 设置陀螺仪偏移校准 */
void MPU6050_Calibrate(void)
{
    int32_t gyro_x_offset = 0, gyro_y_offset = 0, gyro_z_offset = 0;
    MPU6050_Data data;
    
    /* 采集100次数据求平均 */
    for (int i = 0; i < 100; i++)
    {
        MPU6050_ReadData(&data);
        gyro_x_offset += data.gx;
        gyro_y_offset += data.gy;
        gyro_z_offset += data.gz;
        Delay_Ms(10);
    }
    
    /* 计算偏移量 */
    gyro_x_offset /= 100;
    gyro_y_offset /= 100;
    gyro_z_offset /= 100;
    
    /* 保存偏移量(在实际应用中应保存到EEPROM) */
    // 后续使用时减去偏移量
}

2.3 卡尔曼滤波算法(kalman.c)

/**
  * @file kalman.c
  * @brief 卡尔曼滤波算法实现(针对角度测量)
  */
#include "kalman.h"
#include "math.h"

/* 初始化卡尔曼滤波器 */
void Kalman_Init(KalmanFilter* kf)
{
    /* 状态向量初始化 */
    kf->x[0] = 0.0f;  // 角度
    kf->x[1] = 0.0f;  // 角速度
    
    /* 状态协方差矩阵初始化 */
    kf->P[0][0] = 1.0f; kf->P[0][1] = 0.0f;
    kf->P[1][0] = 0.0f; kf->P[1][1] = 1.0f;
    
    /* 过程噪声协方差矩阵Q */
    kf->Q[0][0] = 0.001f; kf->Q[0][1] = 0.0f;
    kf->Q[1][0] = 0.0f;  kf->Q[1][1] = 0.003f;
    
    /* 观测噪声协方差R */
    kf->R = 0.5f;  // 加速度计测量噪声
    
    /* 状态转移矩阵A */
    kf->A[0][0] = 1.0f; kf->A[0][1] = 1.0f;  // 角度 = 上一时刻角度 + 角速度*dt
    kf->A[1][0] = 0.0f; kf->A[1][1] = 1.0f;  // 角速度保持不变
    
    /* 观测矩阵H */
    kf->H[0] = 1.0f;  // 观测角度
    kf->H[1] = 0.0f;  // 不观测角速度
    
    /* 控制输入矩阵B */
    kf->B[0] = 1.0f;  // 角度受角速度影响
    kf->B[1] = 0.0f;  // 角速度不受控制输入影响
}

/* 卡尔曼滤波更新 */
float Kalman_Update(KalmanFilter* kf, float z_measure, float u_control, float dt)
{
    /* 1. 预测步骤 */
    float x_pred[2];  // 预测状态
    float P_pred[2][2];  // 预测协方差
    
    /* 更新状态转移矩阵(考虑dt) */
    kf->A[0][1] = dt;  // 角度 = 角度 + 角速度*dt
    
    /* 状态预测:x_pred = A * x + B * u */
    x_pred[0] = kf->A[0][0] * kf->x[0] + kf->A[0][1] * kf->x[1] + kf->B[0] * u_control;
    x_pred[1] = kf->A[1][0] * kf->x[0] + kf->A[1][1] * kf->x[1] + kf->B[1] * u_control;
    
    /* 协方差预测:P_pred = A * P * A^T + Q */
    // 简化计算:P_pred = A * P * A^T + Q
    P_pred[0][0] = kf->A[0][0]*kf->P[0][0]*kf->A[0][0] + kf->A[0][1]*kf->P[1][0]*kf->A[0][0] +
                   kf->A[0][0]*kf->P[0][1]*kf->A[0][1] + kf->A[0][1]*kf->P[1][1]*kf->A[0][1] + kf->Q[0][0];
    P_pred[0][1] = kf->A[0][0]*kf->P[0][0]*kf->A[1][0] + kf->A[0][1]*kf->P[1][0]*kf->A[1][0] +
                   kf->A[0][0]*kf->P[0][1]*kf->A[1][1] + kf->A[0][1]*kf->P[1][1]*kf->A[1][1] + kf->Q[0][1];
    P_pred[1][0] = kf->A[1][0]*kf->P[0][0]*kf->A[0][0] + kf->A[1][1]*kf->P[1][0]*kf->A[0][0] +
                   kf->A[1][0]*kf->P[0][1]*kf->A[0][1] + kf->A[1][1]*kf->P[1][1]*kf->A[0][1] + kf->Q[1][0];
    P_pred[1][1] = kf->A[1][0]*kf->P[0][0]*kf->A[1][0] + kf->A[1][1]*kf->P[1][0]*kf->A[1][0] +
                   kf->A[1][0]*kf->P[0][1]*kf->A[1][1] + kf->A[1][1]*kf->P[1][1]*kf->A[1][1] + kf->Q[1][1];
    
    /* 2. 更新步骤 */
    /* 计算卡尔曼增益:K = P_pred * H^T * (H * P_pred * H^T + R)^-1 */
    float S = kf->H[0] * P_pred[0][0] * kf->H[0] + kf->H[1] * P_pred[1][0] * kf->H[0] + kf->R;
    float K[2];
    K[0] = (P_pred[0][0] * kf->H[0] + P_pred[0][1] * kf->H[1]) / S;
    K[1] = (P_pred[1][0] * kf->H[0] + P_pred[1][1] * kf->H[1]) / S;
    
    /* 更新状态:x = x_pred + K * (z - H * x_pred) */
    float y = z_measure - (kf->H[0] * x_pred[0] + kf->H[1] * x_pred[1]);  // 观测残差
    kf->x[0] = x_pred[0] + K[0] * y;
    kf->x[1] = x_pred[1] + K[1] * y;
    
    /* 更新协方差:P = (I - K * H) * P_pred */
    float I_KH[2][2];
    I_KH[0][0] = 1.0f - K[0] * kf->H[0];
    I_KH[0][1] = -K[0] * kf->H[1];
    I_KH[1][0] = -K[1] * kf->H[0];
    I_KH[1][1] = 1.0f - K[1] * kf->H[1];
    
    float P_new[2][2];
    P_new[0][0] = I_KH[0][0]*P_pred[0][0] + I_KH[0][1]*P_pred[1][0];
    P_new[0][1] = I_KH[0][0]*P_pred[0][1] + I_KH[0][1]*P_pred[1][1];
    P_new[1][0] = I_KH[1][0]*P_pred[0][0] + I_KH[1][1]*P_pred[1][0];
    P_new[1][1] = I_KH[1][0]*P_pred[0][1] + I_KH[1][1]*P_pred[1][1];
    
    kf->P[0][0] = P_new[0][0]; kf->P[0][1] = P_new[0][1];
    kf->P[1][0] = P_new[1][0]; kf->P[1][1] = P_new[1][1];
    
    return kf->x[0];  // 返回滤波后的角度
}

/* 简化版一维卡尔曼滤波(仅角度) */
float Kalman_1D_Update(float* x_est, float* p_est, float z_meas, float q, float r, float u, float dt)
{
    /* 预测 */
    float x_pred = *x_est + u * dt;
    float p_pred = *p_est + q;
    
    /* 更新 */
    float k = p_pred / (p_pred + r);
    *x_est = x_pred + k * (z_meas - x_pred);
    *p_est = (1 - k) * p_pred;
    
    return *x_est;
}

2.4 I2C驱动(i2c.c)

/**
  * @file i2c.c
  * @brief I2C软件模拟驱动
  */
#include "i2c.h"
#include "delay.h"

/* I2C引脚定义 */
#define I2C_SCL_PIN    GPIO_Pin_6
#define I2C_SCL_PORT   GPIOB
#define I2C_SDA_PIN    GPIO_Pin_7
#define I2C_SDA_PORT   GPIOB

/* 初始化I2C */
void I2C_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    /* 配置SCL和SDA为开漏输出 */
    GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  // 开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    /* 拉高总线 */
    I2C_SCL_H();
    I2C_SDA_H();
}

/* 起始信号 */
void I2C_Start(void)
{
    I2C_SDA_H();
    I2C_SCL_H();
    Delay_Us(5);
    I2C_SDA_L();
    Delay_Us(5);
    I2C_SCL_L();
}

/* 停止信号 */
void I2C_Stop(void)
{
    I2C_SCL_L();
    I2C_SDA_L();
    Delay_Us(5);
    I2C_SCL_H();
    Delay_Us(5);
    I2C_SDA_H();
    Delay_Us(5);
}

/* 等待应答 */
uint8_t I2C_WaitAck(void)
{
    uint8_t ack;
    
    I2C_SDA_H();  // 释放SDA
    Delay_Us(5);
    I2C_SCL_H();
    Delay_Us(5);
    
    ack = GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN);
    
    I2C_SCL_L();
    Delay_Us(5);
    
    return ack;  // 0=ACK, 1=NACK
}

/* 发送字节 */
void I2C_SendByte(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        I2C_SCL_L();
        Delay_Us(5);
        
        if (data & 0x80)
            I2C_SDA_H();
        else
            I2C_SDA_L();
        
        data <<= 1;
        Delay_Us(5);
        I2C_SCL_H();
        Delay_Us(5);
    }
    
    I2C_SCL_L();
    I2C_WaitAck();  // 等待从设备应答
}

/* 接收字节 */
uint8_t I2C_ReadByte(uint8_t ack)
{
    uint8_t data = 0;
    
    I2C_SDA_H();  // 释放SDA,准备接收
    
    for (uint8_t i = 0; i < 8; i++)
    {
        I2C_SCL_L();
        Delay_Us(5);
        I2C_SCL_H();
        Delay_Us(5);
        
        data <<= 1;
        if (GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_PIN))
            data |= 0x01;
        
        Delay_Us(5);
    }
    
    I2C_SCL_L();
    
    /* 发送应答 */
    if (ack)
        I2C_SDA_L();  // ACK
    else
        I2C_SDA_H();  // NACK
    
    Delay_Us(5);
    I2C_SCL_H();
    Delay_Us(5);
    I2C_SCL_L();
    
    return data;
}

2.5 OLED显示(oled.c)

/**
  * @file oled.c
  * @brief OLED显示角度数据
  */
#include "oled.h"
#include "i2c.h"
#include "font.h"

/* OLED显存 */
static uint8_t OLED_GRAM[128][8];

/* 显示角度数据 */
void OLED_ShowAngle(AngleData angle)
{
    char str[20];
    
    OLED_Clear();
    
    /* 第一行:系统标题 */
    OLED_ShowString(0, 0, "MPU6050 Kalman");
    
    /* 第二行:横滚角 */
    sprintf(str, "Roll:%6.1f", angle.roll);
    OLED_ShowString(0, 2, str);
    
    /* 第三行:俯仰角 */
    sprintf(str, "Pitch:%5.1f", angle.pitch);
    OLED_ShowString(0, 4, str);
    
    /* 第四行:角速度 */
    sprintf(str, "Rate:%5.1f", angle.roll_rate);
    OLED_ShowString(0, 6, str);
    
    OLED_Refresh();
}

/* 显示原始数据 */
void OLED_ShowRawData(MPU6050_Data data)
{
    char str[20];
    
    OLED_Clear();
    
    OLED_ShowString(0, 0, "Raw Data:");
    
    sprintf(str, "AX:%6d", data.ax);
    OLED_ShowString(0, 2, str);
    
    sprintf(str, "AY:%6d", data.ay);
    OLED_ShowString(0, 4, str);
    
    sprintf(str, "AZ:%6d", data.az);
    OLED_ShowString(0, 6, str);
    
    OLED_Refresh();
}

2.6 串口输出(uart.c)

/**
  * @file uart.c
  * @brief 串口输出角度数据
  */
#include "uart.h"
#include "stdio.h"

/* 发送角度数据 */
void UART_SendAngleData(AngleData angle)
{
    char buffer[128];
    
    sprintf(buffer, "Angle Data:\r\n");
    UART_SendString(buffer);
    
    sprintf(buffer, "Roll: %.2f deg, Pitch: %.2f deg, Yaw: %.2f deg\r\n",
            angle.roll, angle.pitch, angle.yaw);
    UART_SendString(buffer);
    
    sprintf(buffer, "Roll Rate: %.2f deg/s, Pitch Rate: %.2f deg/s, Yaw Rate: %.2f deg/s\r\n",
            angle.roll_rate, angle.pitch_rate, angle.yaw_rate);
    UART_SendString(buffer);
    
    sprintf(buffer, "----------------------------------------\r\n");
    UART_SendString(buffer);
}

/* 发送CSV格式数据(用于MATLAB分析) */
void UART_SendCSVData(AngleData angle, uint32_t timestamp)
{
    char buffer[128];
    
    sprintf(buffer, "%lu,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\r\n",
            timestamp,
            angle.roll, angle.pitch, angle.yaw,
            angle.roll_rate, angle.pitch_rate, angle.yaw_rate);
    
    UART_SendString(buffer);
}

三、Keil工程配置

3.1 工程设置

Target:
  - Device: STM32F103ZE
  - Clock: 8MHz HSE → 72MHz PLL
  - RAM: 64KB
  - Flash: 512KB

C/C++:
  - Define: USE_STDPERIPH_DRIVER, STM32F103xE
  - Include Paths:
    .\
    ..\Libraries\CMSIS\Include
    ..\Libraries\STM32F10x_StdPeriph_Driver\inc
    ..\User
    ..\User\drivers
    ..\User\algorithm

Linker:
  - IROM1: 0x08000000, 0x80000  (512KB Flash)
  - IRAM1: 0x20000000, 0x10000   (64KB RAM)

3.2 编译优化

Optimization Level: -O1
One ELF Section per Function: ✓

四、测试与调试

4.1 测试步骤

/* 测试1:MPU6050连接测试 */
void Test_MPU6050_Connection(void)
{
    if (MPU6050_TestConnection())
        printf("MPU6050 Connected!\r\n");
    else
        printf("MPU6050 Not Found!\r\n");
}

/* 测试2:卡尔曼滤波效果测试 */
void Test_KalmanFilter(void)
{
    MPU6050_Data data;
    AngleData angle;
    KalmanFilter kf_roll, kf_pitch;
    
    Kalman_Init(&kf_roll);
    Kalman_Init(&kf_pitch);
    
    for (int i = 0; i < 100; i++)
    {
        MPU6050_ReadData(&data);
        
        float accel_roll = atan2(data.ay, data.az) * 57.2958f;
        float accel_pitch = atan2(-data.ax, sqrt(data.ay*data.ay + data.az*data.az)) * 57.2958f;
        
        float gyro_roll = data.gx * 250.0f / 32768.0f;
        float gyro_pitch = data.gy * 250.0f / 32768.0f;
        
        float kalman_roll = Kalman_Update(&kf_roll, accel_roll, gyro_roll, 0.005f);
        float kalman_pitch = Kalman_Update(&kf_pitch, accel_pitch, gyro_pitch, 0.005f);
        
        printf("Raw Roll: %.2f, Raw Pitch: %.2f, Kalman Roll: %.2f, Kalman Pitch: %.2f\r\n",
               accel_roll, accel_pitch, kalman_roll, kalman_pitch);
        
        Delay_Ms(5);
    }
}

4.2 调试输出示例

MPU6050 Connected!
Angle Data:
Roll:  15.2 deg, Pitch: -3.5 deg, Yaw:   0.0 deg
Roll Rate:  12.3 deg/s, Pitch Rate:  -2.1 deg/s, Yaw Rate:   0.0 deg/s
----------------------------------------
Angle Data:
Roll:  15.3 deg, Pitch: -3.4 deg, Yaw:   0.1 deg
Roll Rate:  12.5 deg/s, Pitch Rate:  -2.0 deg/s, Yaw Rate:   0.1 deg/s
----------------------------------------

4.3 MATLAB数据分析

% 读取CSV数据进行卡尔曼滤波效果分析
data = csvread('angle_data.csv');

timestamp = data(:,1);
raw_roll = data(:,2);
kalman_roll = data(:,3);

figure;
subplot(2,1,1);
plot(timestamp, raw_roll, 'r.', 'MarkerSize', 10);
hold on;
plot(timestamp, kalman_roll, 'b-', 'LineWidth', 2);
xlabel('Time (ms)');
ylabel('Roll Angle (deg)');
legend('Raw Data', 'Kalman Filtered');
title('Kalman Filter Effect on Roll Angle');
grid on;

subplot(2,1,2);
error = abs(raw_roll - kalman_roll);
plot(timestamp, error, 'g-', 'LineWidth', 1.5);
xlabel('Time (ms)');
ylabel('Estimation Error (deg)');
title('Kalman Filter Estimation Error');
grid on;

参考代码 基于STM32F103ZET6的对mpu6050的数据进行卡尔曼滤波最终得到精确的角度测量值 www.youwenfan.com/contentcsu/56418.html

五、优化建议

5.1 算法优化

  1. 自适应卡尔曼滤波:根据运动状态动态调整Q和R矩阵
  2. 扩展卡尔曼滤波(EKF):处理非线性系统
  3. 无迹卡尔曼滤波(UKF):更精确的协方差传播
  4. 多传感器融合:加入磁力计(电子罗盘)进行航向角校正

5.2 硬件优化

/* 振动补偿 */
void Vibration_Compensation(AngleData* angle)
{
    // 检测高频振动
    static float last_roll = 0;
    float roll_change = fabs(angle->roll - last_roll);
    
    if (roll_change > 10.0f)  // 角度变化过大
    {
        // 降低卡尔曼增益,减少噪声影响
        kalman_roll.R = 2.0f;  // 增大观测噪声
    }
    else
    {
        kalman_roll.R = 0.5f;  // 恢复正常
    }
    
    last_roll = angle->roll;
}

5.3 应用扩展

/* 姿态解算(四元数) */
typedef struct {
    float q0, q1, q2, q3;  // 四元数
} Quaternion;

/* 互补滤波(备用方案) */
float ComplementaryFilter(float accel_angle, float gyro_rate, float alpha)
{
    static float angle = 0;
    angle = alpha * (angle + gyro_rate * DT) + (1 - alpha) * accel_angle;
    return angle;
}

 

专注于matlab/simulink,电子电路,编程