1. 项目概述
最近在做一个基于STM32的平衡小车项目,需要用到MPU6050陀螺仪模块来获取姿态数据。经过一番折腾,终于用STM32F103C8T6最小系统板成功采集到了MPU6050的原始数据并计算出俯仰角、横滚角等参数。整个过程踩了不少坑,这里把完整的实现过程记录下来,希望能帮到有同样需求的开发者。
MPU6050是一款非常经典的6轴运动处理传感器,内部集成了3轴加速度计和3轴陀螺仪,还自带温度传感器。它通过I2C接口与主控通信,价格只要十几块钱,性价比极高。我使用的是常见的GY-521模块,搭配STM32F103C8T6最小系统板,通过CubeMX配置生成工程框架,再添加MPU6050驱动代码实现数据采集。
2. 硬件准备与连接
2.1 所需硬件清单
- STM32F103C8T6最小系统板(蓝色板)
- GY-521 MPU6050模块
- USB转TTL模块(如CH340)
- 杜邦线若干
- 5V电源(可用USB供电)
2.2 硬件连接说明
MPU6050模块与STM32的连接非常简单,主要注意I2C接口的对应关系:
code复制MPU6050 STM32F103C8T6
VCC -> 3.3V
GND -> GND
SCL -> PB6
SDA -> PB7
ADO -> GND(固定I2C地址为0x68)
USB转TTL模块的连接:
code复制USB-TTL STM32
RX -> PA9 (USART1_TX)
TX -> PA10 (USART1_RX)
GND -> GND
5V -> 5V(可选,可直接用USB供电)
注意:MPU6050的ADO引脚接GND将I2C地址固定为0x68,如果接VCC则地址为0x69。这个细节很容易被忽略,导致I2C通信失败。
3. CubeMX工程配置
3.1 时钟配置
首先在CubeMX中创建一个新工程,选择STM32F103C8T6型号。时钟配置是关键,我使用的是8MHz外部晶振,经过PLL倍频到72MHz系统时钟:
- 在RCC配置中启用HSE(外部高速时钟)
- 在Clock Configuration选项卡中:
- 设置PLL Source为HSE
- 设置PLLMUL为9倍频(8MHz*9=72MHz)
- 系统时钟选择PLLCLK
- APB1 Prescaler设为2(36MHz)
- APB2 Prescaler设为1(72MHz)
3.2 I2C接口配置
MPU6050通过I2C1接口通信,配置如下:
- 在Connectivity中选择I2C1
- 模式选择I2C
- 参数保持默认:
- Clock Speed: 100kHz
- Duty Cycle: 2
- Addressing Mode: 7-bit
- 引脚自动分配为PB6(SCL)和PB7(SDA)
3.3 USART配置
为了通过串口输出数据,配置USART1:
- 在Connectivity中选择USART1
- 模式选择Asynchronous
- 参数配置:
- Baud Rate: 115200
- Word Length: 8 Bits
- Parity: None
- Stop Bits: 1
- 引脚自动分配为PA9(TX)和PA10(RX)
3.4 生成工程代码
完成上述配置后,在Project Manager中设置工程名称和路径,Toolchain选择MDK-ARM(Keil),然后点击GENERATE CODE生成工程。
4. MPU6050驱动实现
4.1 驱动文件结构
在工程目录下新建Hardware文件夹,添加以下文件:
- MPU6050.c:驱动实现
- MPU6050.h:驱动头文件
- inv_mpu.c:官方DMP库(可选)
- inv_mpu_dmp_motion_driver.c:官方DMP库(可选)
4.2 关键寄存器配置
MPU6050通过配置寄存器来设置工作模式,主要寄存器如下:
| 寄存器 | 地址 | 功能说明 |
|---|---|---|
| PWR_MGMT_1 | 0x6B | 电源管理,bit6=1睡眠,0唤醒 |
| SMPLRT_DIV | 0x19 | 采样率分频器 |
| CONFIG | 0x1A | 低通滤波器配置 |
| GYRO_CONFIG | 0x1B | 陀螺仪量程配置 |
| ACCEL_CONFIG | 0x1C | 加速度计量程配置 |
| INT_ENABLE | 0x38 | 中断使能 |
4.3 初始化函数实现
c复制uint8_t MPU6050_Init(void)
{
uint8_t res;
// 1. 复位设备
MPU6050_Write_Byte(PWR_MGMT_1, 0x80);
HAL_Delay(100);
// 2. 唤醒设备,选择时钟源
MPU6050_Write_Byte(PWR_MGMT_1, 0x00);
HAL_Delay(100);
// 3. 设置陀螺仪量程 ±2000°/s
MPU6050_Write_Byte(GYRO_CONFIG, 0x18);
// 4. 设置加速度计量程 ±16g
MPU6050_Write_Byte(ACCEL_CONFIG, 0x18);
// 5. 设置低通滤波器带宽 5Hz
MPU6050_Write_Byte(CONFIG, 0x06);
// 6. 设置采样率 1kHz/(1+7)=125Hz
MPU6050_Write_Byte(SMPLRT_DIV, 0x07);
// 7. 关闭所有中断
MPU6050_Write_Byte(INT_ENABLE, 0x00);
// 8. 检查设备ID
res = MPU6050_Read_Byte(WHO_AM_I);
if(res == 0x68) return 0;
else return 1;
}
4.4 数据读取函数
c复制void MPU6050_GetData(MPU6050_Data *mpu_data)
{
uint8_t buf[14];
int16_t raw[7];
// 读取14个字节的传感器数据
MPU6050_Read_Len(ACCEL_XOUT_H, 14, buf);
// 合并高低字节
raw[0] = ((int16_t)buf[0] << 8) | buf[1]; // Accel X
raw[1] = ((int16_t)buf[2] << 8) | buf[3]; // Accel Y
raw[2] = ((int16_t)buf[4] << 8) | buf[5]; // Accel Z
raw[3] = ((int16_t)buf[6] << 8) | buf[7]; // Temp
raw[4] = ((int16_t)buf[8] << 8) | buf[9]; // Gyro X
raw[5] = ((int16_t)buf[10] << 8) | buf[11]; // Gyro Y
raw[6] = ((int16_t)buf[12] << 8) | buf[13]; // Gyro Z
// 转换为实际物理量
mpu_data->Accel_X = (float)raw[0] / 2048.0f; // ±16g量程,灵敏度2048LSB/g
mpu_data->Accel_Y = (float)raw[1] / 2048.0f;
mpu_data->Accel_Z = (float)raw[2] / 2048.0f;
mpu_data->Temp = (float)raw[3] / 340.0f + 36.53f; // 温度转换公式
mpu_data->Gyro_X = (float)raw[4] / 16.4f; // ±2000°/s量程,灵敏度16.4LSB/(°/s)
mpu_data->Gyro_Y = (float)raw[5] / 16.4f;
mpu_data->Gyro_Z = (float)raw[6] / 16.4f;
// 计算俯仰角和横滚角(简单计算,未滤波)
mpu_data->Pitch = atan2(mpu_data->Accel_Y, mpu_data->Accel_Z) * 180.0f / PI;
mpu_data->Roll = atan2(-mpu_data->Accel_X, mpu_data->Accel_Z) * 180.0f / PI;
}
5. 主程序实现
5.1 串口重定向
为了使用printf函数输出数据,需要在usart.c中添加以下代码:
c复制#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
5.2 主函数实现
c复制int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MPU6050_Data mpu_data;
if(MPU6050_Init())
{
printf("MPU6050 Init Failed!\r\n");
while(1);
}
printf("MPU6050 Init Success!\r\n");
while (1)
{
MPU6050_GetData(&mpu_data);
printf("Accel: X=%.2fg, Y=%.2fg, Z=%.2fg\r\n",
mpu_data.Accel_X, mpu_data.Accel_Y, mpu_data.Accel_Z);
printf("Gyro: X=%.2f°/s, Y=%.2f°/s, Z=%.2f°/s\r\n",
mpu_data.Gyro_X, mpu_data.Gyro_Y, mpu_data.Gyro_Z);
printf("Temp: %.2f°C\r\n", mpu_data.Temp);
printf("Angle: Pitch=%.2f°, Roll=%.2f°\r\n",
mpu_data.Pitch, mpu_data.Roll);
printf("--------------------------------\r\n");
HAL_Delay(200); // 200ms刷新一次
}
}
6. 常见问题与解决方案
6.1 I2C通信失败
现象:MPU6050初始化失败,读取WHO_AM_I寄存器返回值不正确。
可能原因:
- 硬件连接错误,特别是SDA和SCL线接反
- I2C地址设置错误(ADO引脚未正确连接)
- 上拉电阻缺失(部分模块需要外接4.7k上拉电阻)
- 电源不稳定(建议使用3.3V稳压电源)
解决方案:
- 检查硬件连接,确认SDA和SCL对应关系
- 确保ADO引脚接地(地址0x68)或接VCC(地址0x69)
- 在SDA和SCL线上添加4.7k上拉电阻
- 用示波器检查I2C波形是否正常
6.2 数据跳动严重
现象:输出的加速度和角速度数据跳动很大。
可能原因:
- 未启用低通滤波器
- 采样率设置不合理
- 传感器未校准
- 机械振动干扰
解决方案:
- 在CONFIG寄存器中设置合适的低通滤波器带宽
- 调整SMPLRT_DIV寄存器设置合适的采样率
- 进行传感器校准,计算零偏
- 将传感器固定在稳定的平台上
6.3 角度计算不准确
现象:计算出的俯仰角和横滚角随时间漂移。
可能原因:
- 仅使用加速度计计算角度,未融合陀螺仪数据
- 未进行传感器校准
- 动态情况下加速度计数据不可靠
解决方案:
- 实现互补滤波或卡尔曼滤波融合加速度和陀螺仪数据
- 校准加速度计和陀螺仪的零偏
- 考虑使用MPU6050内置的DMP(数字运动处理器)
7. 进阶优化建议
7.1 使用DMP获取稳定姿态
MPU6050内置了DMP(Digital Motion Processor),可以直接输出融合后的四元数,比原始数据更加稳定。要使用DMP功能,需要:
- 加载官方提供的DMP固件库
- 初始化DMP并设置参数
- 读取DMP计算出的四元数
- 将四元数转换为欧拉角
7.2 实现卡尔曼滤波
对于要求更高的应用,可以自己实现卡尔曼滤波算法来融合加速度计和陀螺仪数据:
c复制void Kalman_Update(Kalman *kalman, float acc, float gyro, float dt)
{
// 预测步骤
kalman->angle += dt * (gyro - kalman->bias);
kalman->P[0][0] += dt * (dt * kalman->P[1][1] - kalman->P[0][1] - kalman->P[1][0] + kalman->Q_angle);
kalman->P[0][1] -= dt * kalman->P[1][1];
kalman->P[1][0] -= dt * kalman->P[1][1];
kalman->P[1][1] += kalman->Q_bias * dt;
// 更新步骤
float y = acc - kalman->angle;
float S = kalman->P[0][0] + kalman->R_measure;
float K[2];
K[0] = kalman->P[0][0] / S;
K[1] = kalman->P[1][0] / S;
kalman->angle += K[0] * y;
kalman->bias += K[1] * y;
float P00_temp = kalman->P[0][0];
float P01_temp = kalman->P[0][1];
kalman->P[0][0] -= K[0] * P00_temp;
kalman->P[0][1] -= K[0] * P01_temp;
kalman->P[1][0] -= K[1] * P00_temp;
kalman->P[1][1] -= K[1] * P01_temp;
}
7.3 传感器校准
为了提高测量精度,建议在使用前对传感器进行校准:
- 将传感器水平静止放置
- 采集100-200个样本数据
- 计算加速度计和陀螺仪的平均值作为零偏
- 在实际测量中减去零偏值
校准代码示例:
c复制void MPU6050_Calibrate(MPU6050_Calibration *cal)
{
int32_t acc_sum[3] = {0}, gyro_sum[3] = {0};
uint16_t i;
for(i = 0; i < 200; i++)
{
MPU6050_Read_RawData();
acc_sum[0] += MPU6050_ACCEL_XOUT;
acc_sum[1] += MPU6050_ACCEL_YOUT;
acc_sum[2] += MPU6050_ACCEL_ZOUT;
gyro_sum[0] += MPU6050_GYRO_XOUT;
gyro_sum[1] += MPU6050_GYRO_YOUT;
gyro_sum[2] += MPU6050_GYRO_ZOUT;
HAL_Delay(10);
}
cal->acc_offset[0] = acc_sum[0] / 200;
cal->acc_offset[1] = acc_sum[1] / 200;
cal->acc_offset[2] = acc_sum[2] / 200 - 4096; // 假设Z轴正方向有1g重力
cal->gyro_offset[0] = gyro_sum[0] / 200;
cal->gyro_offset[1] = gyro_sum[1] / 200;
cal->gyro_offset[2] = gyro_sum[2] / 200;
}
8. 实际应用中的注意事项
-
电源稳定性:MPU6050对电源噪声敏感,建议在电源引脚就近放置0.1μF去耦电容。
-
安装位置:尽量将传感器安装在设备的旋转中心,避免因安装位置偏移引入额外的加速度。
-
机械固定:确保传感器牢固固定,任何松动都会导致测量误差。
-
温度影响:陀螺仪零偏会随温度变化,高精度应用需要考虑温度补偿。
-
动态响应:在快速运动时,加速度计测量的"重力"方向会受运动加速度影响,需要靠陀螺仪来补偿。
-
磁场干扰:虽然MPU6050不含磁力计,但强磁场可能影响其内部电路,应避免安装在电机等强磁场附近。
经过实际测试,这套方案在平衡小车、飞行器姿态控制等应用中表现良好。通过合理设置滤波参数和融合算法,可以获得稳定可靠的角度数据。
