硬件准备

器件 型号 数量
主控 STM32F103C8T6 最小系统板 1
传感器 MPU6050 模块(GY-521) 1
下载器 ST-Link V2 1
连接线 杜邦线 4 根 4

硬件连接(I2C1)

1
2
3
4
5
STM32F103C8T6          MPU6050 (GY-521)
PB6 ──── SCL ──── SCL
PB7 ──── SDA ──── SDA
3.3V ──── VCC ──── VCC
GND ──── GND ──── GND

I2C1 复用引脚:PB6=SCL, PB7=SDA。MPU6050 默认 7 位地址 0x68 << 1 = 0xD0(AD0 接地)。

CubeMX 配置

外设 配置 说明
I2C1 Fast Mode 400KHz, PB6/PB7 MPU6050 通信
USART1 115200-8-N-1, PA9(TX) 串口输出角度
TIM2 8ms 定时中断, 72MHz→PSC=71, ARR=7999 125Hz 采样节拍
SYS Debug: Serial Wire ST-Link 调试

Clock: HSE 8MHz → PLL ×9 = 72MHz, APB1=36MHz, APB2=72MHz。

软件架构

1
2
3
4
5
main.c
├─ MPU6050_Init() HAL_I2C 初始化传感器寄存器
├─ MPU6050_Read_All() HAL_I2C_Mem_Read 批量读 14 字节
├─ Complementary_Filter() 互补滤波融合
└─ HAL_TIM_PeriodElapsedCallback() 定时中断 → 读数据 → 算角度 → 串口输出

代码实现

1. MPU6050 寄存器宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* mpu6050.h */
#ifndef __MPU6050_H
#define __MPU6050_H

#include "stm32f1xx_hal.h"

#define MPU6050_ADDR 0x68 // 7位地址, AD0=0

/* 寄存器 */
#define SMPLRT_DIV 0x19 // 采样率分频
#define CONFIG 0x1A // 低通滤波配置
#define GYRO_CONFIG 0x1B // 陀螺仪量程
#define ACCEL_CONFIG 0x1C // 加速度计量程
#define ACCEL_XOUT_H 0x3B // 加速度高字节起始
#define PWR_MGMT_1 0x6B // 电源管理

void MPU6050_Init(I2C_HandleTypeDef *hi2c);
void MPU6050_Read_All(I2C_HandleTypeDef *hi2c, int16_t *accel, int16_t *gyro);

#endif

2. MPU6050 驱动(HAL 实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* mpu6050.c */
#include "mpu6050.h"

/* 写单个寄存器 */
static void MPU6050_WriteReg(I2C_HandleTypeDef *hi2c, uint8_t reg, uint8_t data) {
HAL_I2C_Mem_Write(hi2c, MPU6050_ADDR << 1, reg,
I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
}

/* 初始化 */
void MPU6050_Init(I2C_HandleTypeDef *hi2c) {
MPU6050_WriteReg(hi2c, PWR_MGMT_1, 0x00); // 退出睡眠
HAL_Delay(100);
MPU6050_WriteReg(hi2c, SMPLRT_DIV, 0x07); // 1KHz / (1+7) = 125Hz
MPU6050_WriteReg(hi2c, CONFIG, 0x06); // 低通滤波 ~5Hz
MPU6050_WriteReg(hi2c, GYRO_CONFIG, 0x18); // ±2000°/s
MPU6050_WriteReg(hi2c, ACCEL_CONFIG, 0x10); // ±8g
}

/* 批量读取加速度+陀螺仪(14 字节) */
void MPU6050_Read_All(I2C_HandleTypeDef *hi2c, int16_t *accel, int16_t *gyro) {
uint8_t buf[14];

HAL_I2C_Mem_Read(hi2c, MPU6050_ADDR << 1, ACCEL_XOUT_H,
I2C_MEMADD_SIZE_8BIT, buf, 14, 200);

accel[0] = (int16_t)(buf[0] << 8 | buf[1]); // AX
accel[1] = (int16_t)(buf[2] << 8 | buf[3]); // AY
accel[2] = (int16_t)(buf[4] << 8 | buf[5]); // AZ

gyro[0] = (int16_t)(buf[8] << 8 | buf[9]); // GX
gyro[1] = (int16_t)(buf[10] << 8 | buf[11]); // GY
gyro[2] = (int16_t)(buf[12] << 8 | buf[13]); // GZ
}

3. 姿态解算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* attitude.h */
#ifndef __ATTITUDE_H
#define __ATTITUDE_H

#include <math.h>

#define M_PI_F 3.14159265f

typedef struct {
float roll;
float pitch;
float yaw;
} Attitude_t;

extern Attitude_t atti;

void Attitude_Complementary(int16_t *accel, int16_t *gyro, float dt);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* attitude.c */
#include "attitude.h"

Attitude_t atti = {0};

/*
* 互补滤波
* angle = 0.96 * (angle + gyro*dt) + 0.04 * accel_angle
*
* 加速度计:低频准,高频噪声大
* 陀螺仪: 高频准,低频漂移
* 互补滤波:取各自长处
*/
void Attitude_Complementary(int16_t *accel, int16_t *gyro, float dt) {
float ax = accel[0], ay = accel[1], az = accel[2];
float gx = gyro[0], gy = gyro[1], gz = gyro[2];

// 加速度计角度
float accel_pitch = atan2f(-ax, sqrtf(ay * ay + az * az)) * 180.0f / M_PI_F;
float accel_roll = atan2f( ay, az) * 180.0f / M_PI_F;

// 陀螺仪角速度 → 度/秒
float gyro_rate_x = gx / 16.384f; // ±2000°/s → 16.384 LSB/°/s
float gyro_rate_y = gy / 16.384f;
float gyro_rate_z = gz / 16.384f;

// 互补滤波
#define ALPHA 0.96f
atti.roll = ALPHA * (atti.roll + gyro_rate_x * dt) + (1.0f - ALPHA) * accel_roll;
atti.pitch = ALPHA * (atti.pitch + gyro_rate_y * dt) + (1.0f - ALPHA) * accel_pitch;
atti.yaw += gyro_rate_z * dt; // 偏航仅靠陀螺仪积分
}

3.2 卡尔曼滤波(进阶方案)

互补滤波简单但参数固定。卡尔曼滤波能自适应收敛,精度更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* kalman.h */
#ifndef __KALMAN_H
#define __KALMAN_H

typedef struct {
float Q_angle; // 姿态过程噪声
float Q_gyro; // 陀螺仪过程噪声
float R_measure; // 测量噪声(加速度计)

float angle; // 最优估计角度
float bias; // 陀螺仪零偏估计
float rate; // 无偏角速度

float P[2][2]; // 误差协方差矩阵
} Kalman_t;

void Kalman_Init(Kalman_t *k, float Q_angle, float Q_gyro, float R_measure);
float Kalman_Update(Kalman_t *k, float measured_angle, float gyro_rate, float dt);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* kalman.c */
#include "kalman.h"

void Kalman_Init(Kalman_t *k, float Q_angle, float Q_gyro, float R_measure) {
k->Q_angle = Q_angle;
k->Q_gyro = Q_gyro;
k->R_measure = R_measure;

k->angle = 0.0f;
k->bias = 0.0f;
k->rate = 0.0f;

k->P[0][0] = 0.0f;
k->P[0][1] = 0.0f;
k->P[1][0] = 0.0f;
k->P[1][1] = 0.0f;
}

/*
* 2 状态卡尔曼滤波(单轴)
*
* 状态向量 X = [angle, gyro_bias]^T
*
* 预测(先验):
* angle = angle + (gyro - bias) * dt
* bias = bias (不变)
* P = F·P·F^T + Q
*
* 更新(后验):
* y = z - H·X (测量残差 = 加速度计角度 - 预测角度)
* S = H·P·H^T + R (残差协方差)
* K = P·H^T / S (卡尔曼增益)
* X = X + K·y (状态更新)
* P = (I - K·H)·P (协方差更新)
*/
float Kalman_Update(Kalman_t *k, float measured_angle, float gyro_rate, float dt) {
/* ───── 预测阶段(先验) ───── */
k->rate = gyro_rate - k->bias;
k->angle += k->rate * dt;

// P = F·P·F^T + Q
// F = [[1, -dt], [0, 1]]
k->P[0][0] += dt * (dt * k->P[1][1] - k->P[0][1] - k->P[1][0] + k->Q_angle);
k->P[0][1] -= dt * k->P[1][1];
k->P[1][0] -= dt * k->P[1][1];
k->P[1][1] += k->Q_gyro * dt;

/* ───── 更新阶段(后验) ───── */
// y = z - H·X, H = [1, 0] → y = measured - angle
float y = measured_angle - k->angle;

// S = H·P·H^T + R = P[0][0] + R
float S = k->P[0][0] + k->R_measure;

// K = P·H^T / S
float K0 = k->P[0][0] / S; // 角度增益
float K1 = k->P[1][0] / S; // 零偏增益

// X = X + K·y
k->angle += K0 * y;
k->bias += K1 * y;

// P = (I - K·H)·P
float P00_temp = k->P[0][0];
float P01_temp = k->P[0][1];
k->P[0][0] -= K0 * P00_temp;
k->P[0][1] -= K0 * P01_temp;
k->P[1][0] -= K1 * P00_temp;
k->P[1][1] -= K1 * P01_temp;

return k->angle;
}

主函数集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* main.c 中替换互补滤波 */
#include "kalman.h"

Kalman_t kalman_x, kalman_y; // 两个轴的卡尔曼滤波器

int main(void) {
// ... 外设初始化 ...

// 参数调优:Q_angle=0.001, Q_gyro=0.003, R_measure=0.03
Kalman_Init(&kalman_x, 0.001f, 0.003f, 0.03f);
Kalman_Init(&kalman_y, 0.001f, 0.003f, 0.03f);

// ... loop ...
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance != TIM2) return;

static uint8_t cnt = 0;
static int16_t accel[3], gyro[3];

MPU6050_Read_All(&hi2c1, accel, gyro);

// 加速度计角度
float accel_roll = atan2f(accel[1], accel[2]) * 180.0f / M_PI_F;
float accel_pitch = atan2f(-accel[0], sqrtf(accel[1]*accel[1] + accel[2]*accel[2])) * 180.0f / M_PI_F;

// 陀螺仪角速度
float gyro_x = gyro[0] / 16.384f;
float gyro_y = gyro[1] / 16.384f;

// 卡尔曼融合
atti.roll = Kalman_Update(&kalman_x, accel_roll, gyro_x, 0.008f);
atti.pitch = Kalman_Update(&kalman_y, accel_pitch, gyro_y, 0.008f);
atti.yaw += gyro[2] / 16.384f * 0.008f;

cnt++;
if (cnt >= 25) {
cnt = 0;
printf("Roll:%.2f Pitch:%.2f Yaw:%.2f\r\n", atti.roll, atti.pitch, atti.yaw);
}
}

滤波算法对比

特性 互补滤波 卡尔曼滤波
计算量 极小(3 行运算) 中等(矩阵运算)
参数调节 1 个参数 ALPHA 3 个参数 Q_angle/Q_gyro/R
收敛速度 固定 自适应(增益自动调整)
精度 中等
零偏估计 自动估计并补偿陀螺仪零偏
RAM 12 字节 ~40 字节/轴
适合场景 实时性要求高, STM32F0 精度要求高, STM32F1/F4

卡尔曼参数调优

参数 增大效果 减小效果
Q_angle 更信任模型预测,响应快但更抖 更平滑但响应慢
Q_gyro 更快追踪陀螺仪零偏变化 零偏估计更稳定
R_measure 更不信任加速度计,抗振动好 更快收敛到加速度计角度

推荐起步值Q_angle=0.001, Q_gyro=0.003, R=0.03

4. 主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/* main.c */
#include "stm32f1xx_hal.h"
#include "mpu6050.h"
#include "attitude.h"
#include <stdio.h>

I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart1;
TIM_HandleTypeDef htim2;

/* 外设初始化(CubeMX 生成,此处简化) */
static void MX_I2C1_Init(void) {
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
}

static void MX_USART1_UART_Init(void) {
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX;
HAL_UART_Init(&huart1);
}

static void MX_TIM2_Init(void) {
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72MHz / (71+1) = 1MHz
htim2.Init.Period = 7999; // 1MHz / (7999+1) = 125Hz → 8ms
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2); // 启动定时中断
}

/* printf 重定向到串口 */
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 10);
return ch;
}

/* HAL I2C 底层初始化 */
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) {
GPIO_InitTypeDef GPIO_Init = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_I2C1_CLK_ENABLE();
GPIO_Init.Pin = GPIO_PIN_6 | GPIO_PIN_7;
GPIO_Init.Mode = GPIO_MODE_AF_OD;
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_Init);
}

/* 定时器中断回调 → 125Hz 采样 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance != TIM2) return;

static uint8_t cnt = 0;
static int16_t accel[3], gyro[3];

MPU6050_Read_All(&hi2c1, accel, gyro);
Attitude_Complementary(accel, gyro, 0.008f); // dt = 8ms

cnt++;
if (cnt >= 25) { // 125Hz / 25 = 5Hz 输出
cnt = 0;
printf("Roll:%.2f Pitch:%.2f Yaw:%.2f\r\n",
atti.roll, atti.pitch, atti.yaw);
}
}

int main(void) {
HAL_Init();
SystemClock_Config(); // CubeMX 生成:72MHz
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();

MPU6050_Init(&hi2c1);

printf("STM32F103 + MPU6050 (HAL) 姿态解算\r\n");

while (1) {
/* 主循环空闲,数据处理在定时中断完成 */
}
}

关键 HAL API 对比

功能 标准库 (SPL) HAL 库
写寄存器 I2C_GenerateSTARTI2C_SendData…(6 步) HAL_I2C_Mem_Write(1 步)
读寄存器 同上,手动序列 HAL_I2C_Mem_Read(1 步)
串口发送 USART_SendData HAL_UART_Transmit
定时中断 TIM_ITConfig HAL_TIM_Base_Start_IT
引脚初始化 GPIO_Init HAL_GPIO_Init

运行结果

串口助手输出(115200bps):

1
2
3
4
STM32F103 + MPU6050 (HAL) 姿态解算
Roll:1.23 Pitch:-0.56 Yaw:45.12
Roll:1.18 Pitch:-0.52 Yaw:45.20
Roll:1.15 Pitch:-0.49 Yaw:45.28

转动模块,Roll/Pitch 实时变化,Yaw 持续积分(无磁力计会漂移)。

调参指南

参数 位置 作用 建议
ALPHA attitude.c 陀螺仪权重 静止 0.90, 动态 0.98
低通滤波 0x06 mpu6050.c 去抖 5Hz(0x06) 适合静态, 20Hz(0x03) 适合动态
采样率分频 0x07 mpu6050.c 输出频率 125Hz 够用, 快速运动可改 200Hz
量程 GYRO 0x18 mpu6050.c 陀螺仪满量程 ±2000°/s 通用, 云台可改 ±250°/s
定时器周期 main.c 采样间隔 8ms(125Hz) 配合采样率必须一致

扩展方向

方向 方案 难度
DMP 解算 用 MPU6050 内置 DMP 输出四元数,省去互补滤波
OLED 显示 0.96寸 SSD1306 I2C OLED 实时显示 Roll/Pitch
上位机波形 串口发原始数据 → Python matplotlib 画波形
卡尔曼滤波 替代互补滤波,精度更高但计算量大
云台控制 角度作反馈 → PID → PWM 控制舵机
蓝牙无线 HC-05 透传 → 手机 App 显示姿态
FreeRTOS 采集中断 → 滤波任务 → 通信任务 分离

常见问题

现象 原因 解决
HAL_I2C_Mem_Read 返回 HAL_ERROR I2C 无应答 检查接线 / 地址写对 0x68<<1
角度一直 0 MPU6050 未初始化 先写 PWR_MGMT_1=0x00 唤醒
Roll/Pitch 抖 低通滤波太弱 降低 CONFIG 值(如 0x06→0x05)
Yaw 几秒就漂几十度 无磁力计,正常 加 HMC5883L 磁力计融合
数据乱跳 电源噪声 VCC/GND 加 100nF + 10μF 电容