STM32 MPU6050 六轴陀螺仪教程(HAL 库零基础入门)
本教程将详细介绍如何在 STM32 微控制器上使用 HAL 库驱动 MPU6050 六轴姿态传感器,适合零基础的初学者学习。内容涵盖基础知识、硬件连接、开发环境配置、驱动编写、数据处理、示例代码以及调试与优化等方面。通过本教程,读者将了解 MPU6050 的工作原理,掌握 STM32 I2C 通信的使用方法,并能够读取 MPU6050 的加速度、角速度和温度等数据,进而进行姿态角的计算。
1. 基础知识
MPU6050 传感器工作原理
MPU6050 是一款常见的 6轴惯性测量单元(IMU),集成了 三轴加速度计 和 三轴陀螺仪 于单一芯片,可同时测量物体在 X、Y、Z 三个方向的 加速度 和绕这三轴的 角速度 (〖STM32-HAL库〗MPU6050姿态传感器_stm32 hal mpu6050-CSDN博客)。此外,MPU6050 内置有一个温度传感器用于测温和温度补偿,还有一个可用于姿态解算的数字运动处理器(DMP)。它通过 I2C 总线与主控MCU通信,将测得的数据传输给微控制器处理。
MPU6050 广泛应用于机器人、无人机、移动设备等领域,用于测量设备姿态和运动状态 (〖STM32-HAL库〗MPU6050姿态传感器_stm32 hal mpu6050-CSDN博客)。其优点是集成度高、体积小、成本低且功耗低,可以提供高达 ±16g 的加速度量程和 ±2000°/s 的角速度量程选择,以及可编程的数字低通滤波器。但需要注意的是,它无法直接给出绝对的空间方位,需要借助磁力计等传感器才能计算航向角(偏航角),输出的位置也需要对加速度进行两次积分,误差会快速累积 (〖STM32-HAL库〗MPU6050姿态传感器_stm32 hal mpu6050-CSDN博客)。
I2C 通信协议介绍
MPU6050 通过 I2C (Inter-Integrated Circuit) 总线与 STM32 通信。I2C 是由 Philips 公司开发的一种简易的双线串行通信协议,旨在减少芯片间连接线,简化硬件设计 (Arduino 通过 I2C 驱动 LCD1602 液晶屏 – Arduino 实验室)。I2C 总线上只有两根信号线:
在 I2C 总线中,每个从设备都有唯一的7位地址用于识别,主设备通过地址来选择与哪一个从设备通信 (Arduino 通过 I2C 驱动 LCD1602 液晶屏 – Arduino 实验室)。I2C 通信基本流程如下:
- 起始信号:主机拉低 SDA 并在时钟保持高电平期间拉低 SDA,通知总线上设备开始通信。
- 发送地址:主机发送7位从设备地址(最高有效位先)和1位读/写标志位(低位为1表示读,为0表示写)。MPU6050 的7位地址默认是
0x68
(二进制1101000
)或0x69
(1101001
),取决于其 AD0 引脚电平状态。 - 应答 (ACK):每字节发送后,由接收方在第9个时钟周期拉低 SDA 发送 ACK 表示成功接收。若接收方不拉低 SDA表示非应答 (NACK)。
- 数据传输:根据读/写标志,由主机或从机在 SDA 上发送数据,每发送一字节后由另一方ACK确认。
- 停止信号:主机在时钟线高电平时拉高 SDA,表示结束通信。
I2C 是一种半双工通信,支持多个设备连接在同一总线上。由于总线采用开漏输出配置,通常需要上拉电阻将总线维持在高电平 (Implementation of MPU6050 with STM32 – Get-To-Byte) (Implementation of MPU6050 with STM32 – Get-To-Byte)。许多传感器模块(包括常见的 MPU6050 模块)上已经集成了适当阻值的上拉电阻,以确保 I2C 总线正确工作。如果没有上拉电阻,需在 SDA 和 SCL 引脚各接一个(通常 4.7kΩ 左右)的上拉到电源电压。
在本教程中,STM32 将作为 I2C 主设备,MPU6050 作为从设备。我们会使用 STM32 的硬件 I2C 外设通过 HAL 驱动与传感器通信,实现对 MPU6050 内部寄存器的读写。
STM32 基本概述
STM32 是意法半导体(STMicroelectronics)公司基于 ARM Cortex-M 内核的微控制器系列,具有性能强大、片上外设丰富等特点。本文以 STM32F103C8T6 型号为例,它属于 STM32 F1系列,内置 Cortex-M3 内核,主频 72MHz,配备有 64KB Flash 和 20KB SRAM,以及丰富的外设(如 GPIO、USART、I2C、SPI、ADC 等)。STM32F103C8T6 因性能适中且价格低廉,被广泛应用于入门开发板(例如俗称的“蓝板/Blue Pill”)。
使用 STM32 开发时,我们需要一个开发环境和工具链。常见的STM32开发环境有:
无论使用哪种 IDE,一般都通过 ST-LINK 调试器将程序下载到 STM32 芯片中。在开始编程之前,我们需要熟悉 STM32 的 HAL (Hardware Abstraction Layer) 库。HAL 库是 ST 提供的对底层寄存器的高级封装,使我们可以通过调用 API 函数来配置和使用外设,而无需直接操作寄存器,这对初学者更加友好。
小结:在进入实战之前,请确保理解 MPU6050 的基本功能和 I2C 通信机制,并搭建好 STM32 的开发环境(安装 STM32CubeIDE 或 Keil,并配置好编译器和下载工具)。接下来我们将介绍传感器与STM32的连接方法。
2. 硬件连接
MPU6050 与 STM32 的连线方式 (以 STM32F103C8T6 为例)
在实际硬件接线中,我们通常使用 MPU6050 的现成模块(如 GY-521 模块),它引出了传感器的电源引脚和 I2C 引脚,方便与微控制器连接。下面以 STM32F103C8T6(Blue Pill 开发板)为例说明连接方法:
(Implementation of MPU6050 with STM32 – Get-To-Byte)图1:STM32F103C8T6 (Blue Pill) 与 MPU6050 模块的连接示意图。红色和黑色线分别为电源正负,蓝色和黄色线为 I2C 数据线(SDA)和时钟线(SCL),AD0 接地使地址为 0x68。
VCC
引脚接 STM32 的 3.3V
(不要接5V,MPU6050 芯片工作电压为 2.3V~3.4V),GND
接 STM32 的 GND
(How to Integrate the MPU6050 With the STM32 Blue Pill : 4 Steps – Instructables)。大部分 GY-521 模块带有低压稳压器,可以接受5V供电并输出3.3V给芯片,但直接使用3.3V供电可以减少不必要的损耗和干扰。SCL
引脚连接 STM32 的 PB6
引脚,将 SDA
引脚连接 STM32 的 PB7
引脚 (How to Integrate the MPU6050 With the STM32 Blue Pill : 4 Steps – Instructables)。在 STM32F103C8T6 中,PB6
和 PB7
是 I2C1 外设的默认引脚(无需重映射即可使用)。如果使用其他型号 STM32,请查阅其数据手册确认 I2C 引脚。AD0
引脚用于设置 I2C 地址最低位。将 AD0 接地(逻辑低)则传感器7位地址为 0x68
,接高电平则地址为 0x69
。典型的 GY-521 模块上 AD0 常默认接地。如果需要在一条总线上连接两个 MPU6050,可将其中一个模块的 AD0 拉高以改变地址。INT
引脚,当发生数据就绪、中断事件等时会拉高/拉低。若需要使用 MPU6050 的中断功能(如数据准备好中断或 FIFO 溢出中断),可以将 INT 引脚连接到 STM32 的任意可用 GPIO 引脚并配置为输入中断模式。但对于基础数据读取,本教程不强制使用该引脚。XDA
/XCL
是 MPU6050 的辅助 I2C 接口引脚(用于连接磁力计等外部传感器),在本例中不使用,可留空。供电注意事项:确认 MPU6050 模块上的供电电压范围。有些模块标注可接5V(内部有3.3V稳压器),有些则需直接供3.3V。切勿将3.3V的MPU6050芯片直接接到5V,否则可能损坏传感器。由于 STM32F103 的GPIO工作在3.3V逻辑电平,直接与3.3V供电的MPU6050匹配,无需电平转换。I2C 的 SDA/SCL 线如果模块未带上拉电阻,需要在STM32侧使用上拉电阻将其拉至3.3V,不过大部分模块已经包含了上拉电阻,可以稳定地保持总线高电平 (Implementation of MPU6050 with STM32 – Get-To-Byte) (Implementation of MPU6050 with STM32 – Get-To-Byte)。
模块电路分析
常见的 MPU6050 模块(如 GY-521)的电路设计要点如下:
了解模块电路有助于调试。例如:如果I2C通信不稳定,可能需要检查模块是否供电良好、上拉是否到位等。
3. 开发环境
使用 Keil 或 STM32CubeIDE
如前所述,我们推荐初学者使用 STM32CubeIDE 进行开发,因为它集成了 CubeMX 图形化配置工具和 HAL 库,使用方便。下面是开发环境的基本搭建步骤:
- 安装 STM32CubeIDE:从 ST 官方网站下载并安装 STM32CubeIDE。安装过程中会附带 GNU ARM 工具链和 ST-LINK 驱动。
- 安装支持包:首次启动 CubeIDE 时,可以通过 STM32CubeMX 的包管理器安装相应芯片系列的固件包(如 STM32F1 Series Pack),其中包含 HAL 库代码和示例。
- 使用 ST-LINK:准备一个 ST-LINK/V2 仿真器(或者开发板上自带的ST-LINK),通过 USB 连接电脑,以便下载程序和调试。
如果使用 Keil µVision,则需要安装 MDK-ARM 和 STM32F1 的设备包,并可选安装 CubeMX 独立版用于生成初始化代码。Keil 相对来说调试体验很好,但需注意免费版代码大小限制。无论 CubeIDE 还是 Keil,后台调用的都是 ARM 编译工具链,代码本身并无差异。
配置 STM32CubeMX 工程
使用 CubeIDE 新建一个 STM32F103C8T6 工程时,可以选择启用 CubeMX 配置界面。按照下面步骤配置工程外设:
- 选择芯片:在 CubeMX 界面,选择 STM32F103C8T6 或相应开发板模板。进入引脚配置界面。
- 时钟配置:设置系统时钟为 72MHz(启用外部晶振8MHz并配置PLL,或使用内部 HSI 8MHz+PLL)。
- I2C1 外设:在 Connectivity 中找到 I2C1,将其模式设置为 I2C。CubeMX 会自动分配 I2C1 的 SDA/SCL 引脚(默认 F103C8T6 为 PB6/PB7)。将 I2C1 的时钟频率设置为标准模式 100kHz(或 Fast Mode 400kHz,根据需要,初学建议100kHz)。
- USART1 外设 (可选):为实现串口打印调试信息,可以在 Connectivity 中启用 USART1(TX 默认映射到 PA9)。配置波特率如 115200bps,并启用异步模式。
- 生成代码:在CubeMX中点击生成代码,CubeIDE 将生成初始化代码框架,包括
main.c
、时钟配置函数、I2C和USART初始化函数等。
此时生成的项目已经包含 HAL 库和基本的系统初始化代码。打开 main.c
,你会看到类似如下的代码框架:
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
// ... 用户代码开始
while (1) {
// 主循环
}
}
其中 MX_I2C1_Init()
和 MX_USART1_UART_Init()
是 CubeMX 根据参数生成的初始化函数,我们无需手动修改这些函数,但可以查看其内容了解配置是否正确。例如 MX_I2C1_Init()
内会配置 I2C 时钟、地址模式(7位地址)等参数,并调用 HAL_I2C_Init()
来使能I2C外设。
HAL 库的基本使用
HAL 库通过 HAL_I2C_Xxx
函数族来完成 I2C 通信。常用的函数包括:
HAL_I2C_Master_Transmit()
/ HAL_I2C_Master_Receive()
:主机模式下发送/接收数据块。HAL_I2C_Mem_Write()
/ HAL_I2C_Mem_Read()
:主机模式下对具有寄存器地址的从设备进行读写(适用于传感器类设备)。HAL_I2C_IsDeviceReady()
:检测从设备是否应答(可用于检查设备连接)。HAL_I2C_Master_Transmit_DMA
等)。在驱动 MPU6050 时,我们会频繁用到 寄存器读写,因此 HAL_I2C_Mem_Read/Write
将非常方便。它的参数包括:I2C句柄、设备地址、寄存器地址、地址长度(一般为1字节寄存器地址)、数据缓冲区、数据长度、超时。使用之前,需要确保 hi2c1
(I2C句柄)已经通过 MX_I2C1_Init()
初始化。
在下一章节,我们将基于 HAL 函数开始编写 MPU6050 的驱动代码,包括初始化和数据读取。请确认硬件已正确连接,工程配置无误,并安装好了 HAL 库环境。
4. 驱动编写
下面我们逐步编写 MPU6050 的驱动代码。为简洁起见,我们直接在 main.c
中实现所需功能(也可以将驱动封装成独立 .c/.h 文件)。驱动主要包括:I2C 初始化(已由 CubeMX 完成),MPU6050 寄存器配置初始化,以及数据读取函数。此外,我们会简要介绍 MPU6050 的 FIFO 和中断配置,但初学阶段先以轮询读取为主。
初始化 I2C 外设
由于使用 CubeMX 已经完成了 I2C 的初始化配置,这里不需要手动设置 I2C 寄存器。HAL 库初始化 I2C 时已经配置好时钟速度、引脚模式和GPIO上拉等。一般需要注意的是:
HAL_I2C_DeInit()
或手动控制 SCL/SDA 产生时钟恢复。但一般新的 MCU 上电不会有此问题。CubeMX 生成的 MX_I2C1_Init()
通常如下(简化):
static void MX_I2C1_Init(void) {
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100 kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // Fm模式占空比,可忽略
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
}
以上设置确保 I2C1 以主机模式运行,地址模式为7位。如果需要在运行时进一步验证 I2C 通信,可以使用 HAL_I2C_IsDeviceReady(&hi2c1, devAddress, tryTimes, timeout)
来轮询设备是否应答。devAddress
应为 8位地址(7位左移1位后的值)。对于 MPU6050,如果 AD0 接地,其7位地址是 0x68,则8位地址是 0x68 << 1 = 0xD0
(How to interface MPU6050 (GY-521) with STM32)。
MPU6050 寄存器初始化
MPU6050 上电后大部分寄存器为默认值,但有些需要配置才能正常工作。例如,MPU6050 上电默认进入休眠模式,需要唤醒;还需设置采样率、量程等。初始化流程通常如下:
- 检测设备:读取 WHO_AM_I 寄存器(地址
0x75
),该寄存器应返回设备ID0x68
,用于确认 I2C 通信和设备正常 (How to interface MPU6050 (GY-521) with STM32) (How to interface MPU6050 (GY-521) with STM32)。 - 解除休眠:配置 PWR_MGMT_1 寄存器(地址
0x6B
)。将该寄存器所有位写0可以唤醒设备(清除睡眠位),并选择内部8MHz时钟 (How to interface MPU6050 (GY-521) with STM32)。默认复位值是 0x40(睡眠模式置位),因此需要清零该位。 - 设置采样率:可选地,通过 SMPLRT_DIV 寄存器(地址
0x19
)设置陀螺仪输出分频,从而得到陀螺仪/加速度的采样率。采样率公式为:SampleRate = GyroOutputRate / (1 + SMPLRT_DIV)
(How to interface MPU6050 (GY-521) with STM32)。Gyro输出默认速率8kHz(当数字低通滤波器DLPF禁用时)。比如希望采样率1kHz,可将 SMPLRT_DIV 设置为7(即 8kHz/(1+7)=1kHz)。 - 配置数字低通滤波 (DLPF):通过 CONFIG 寄存器(地址
0x1A
)设置DLPF模数。DLPF可以滤除高频噪声并降低陀螺仪采样率。当 DLPF设置为0(或默认复位值0),陀螺仪输出率为8kHz,加速度为1kHz。可以根据需要设置1~6对应不同截止频率(如典型地设置为3,获得加速度带宽44Hz、陀螺仪带宽42Hz,采样率1kHz)。 - 设置量程:配置 GYRO_CONFIG (
0x1B
) 和 ACCEL_CONFIG (0x1C
) 寄存器,选择陀螺仪和加速度计的满量程范围。复位默认值都是0,即陀螺仪满量程 ±250°/s,加速度计满量程 ±2g (How to interface MPU6050 (GY-521) with STM32) (How to interface MPU6050 (GY-521) with STM32)。在初学阶段可以保持默认最小量程以获得最大精度。如需更大动态范围,可修改相应寄存器低2位(FS_SEL或AFS_SEL字段)。 - 启用传感器:以上配置完成后,MPU6050的加速度计和陀螺仪即处于激活测量状态。此时可以选择性配置中断和FIFO(下节介绍)或者直接开始读取数据。
使用 HAL 库实现上述初始化过程的示例代码如下:
/* MPU6050 I2C设备地址定义 */
#define MPU6050_ADDR (0x68 << 1) // 若AD0接地,7位地址0x68,左移1位得到8位地址0xD0
#define WHO_AM_I_REG 0x75 // WHO_AM_I寄存器地址,默认值0x68
#define PWR_MGMT_1_REG 0x6B // 电源管理寄存器1
#define SMPLRT_DIV_REG 0x19 // 采样率分频寄存器
#define CONFIG_REG 0x1A // 配置寄存器(含DLPF设置)
#define GYRO_CONFIG_REG 0x1B // 陀螺仪配置寄存器
#define ACCEL_CONFIG_REG 0x1C // 加速度计配置寄存器
/* 初始化 MPU6050 */
HAL_StatusTypeDef MPU6050_Init(void) {
uint8_t check, data;
HAL_StatusTypeDef res;
// 1. 读取 WHO_AM_I 寄存器,检查设备ID是否正确 (0x68)
res = HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, WHO_AM_I_REG, 1, &check, 1, 100);
if(res != HAL_OK || check != 0x68) {
return HAL_ERROR; // 通信失败或ID不符
}
// 2. 解除休眠,将 PWR_MGMT_1 寄存器写0
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, PWR_MGMT_1_REG, 1, &data, 1, 100);
HAL_Delay(10); // 小延迟,等待芯片唤醒稳定
// 3. 设置采样率分频器 SMPLRT_DIV (比如设置成7获得1kHz采样率)
data = 0x07;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, SMPLRT_DIV_REG, 1, &data, 1, 100);
// 4. 配置DLPF,在CONFIG寄存器中设置数字低通滤波器 (例如0x03,Accel带宽44Hz)
data = 0x03;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, CONFIG_REG, 1, &data, 1, 100);
// 5. 配置陀螺仪满量程范围 ±250°/s (0x00) 和加速度计满量程范围 ±2g (0x00)
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, GYRO_CONFIG_REG, 1, &data, 1, 100);
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, ACCEL_CONFIG_REG, 1, &data, 1, 100);
return HAL_OK;
}
上述 MPU6050_Init()
函数依次进行了寄存器配置。其中WHO_AM_I返回值检查很重要,0x68 是 MPU6050 正确应答的标志 (How to interface MPU6050 (GY-521) with STM32)。如果不匹配,通常意味着传感器未正确连接或地址设置错误(需检查AD0接线和 MPU6050_ADDR 定义)。初始化过程中每次 I2C 写入都可以检查返回值以确保通信正常,这里简化处理。寄存器配置选择了最基础的设置,使输出数据未经内部DMP处理,直接提供原始测量值。
读取加速度、角速度、温度数据的方法
MPU6050 将传感器数据存储在连续的寄存器中,支持一次读取多字节以提高效率。主要的数据寄存器及顺序如下:
因此,可以一次性读取这14字节(0x3B~0x48)的数据,然后解析出各物理量。为了清晰起见,我们也可以分别编写读取加速度、陀螺仪、温度的函数:
int16_t Accel_X_RAW, Accel_Y_RAW, Accel_Z_RAW;
int16_t Gyro_X_RAW, Gyro_Y_RAW, Gyro_Z_RAW;
int16_t Temp_RAW;
// 读取加速度计三轴原始数据
void MPU6050_Read_Accel(void) {
uint8_t buf[6];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B, 1, buf, 6, 100);
// 拼接高低字节为 16 位有符号值
Accel_X_RAW = (int16_t)(buf[0] << 8 | buf[1]);
Accel_Y_RAW = (int16_t)(buf[2] << 8 | buf[3]);
Accel_Z_RAW = (int16_t)(buf[4] << 8 | buf[5]);
}
// 读取陀螺仪三轴原始数据
void MPU6050_Read_Gyro(void) {
uint8_t buf[6];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x43, 1, buf, 6, 100);
Gyro_X_RAW = (int16_t)(buf[0] << 8 | buf[1]);
Gyro_Y_RAW = (int16_t)(buf[2] << 8 | buf[3]);
Gyro_Z_RAW = (int16_t)(buf[4] << 8 | buf[5]);
}
// 读取温度原始数据
void MPU6050_Read_Temp(void) {
uint8_t buf[2];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x41, 1, buf, 2, 100);
Temp_RAW = (int16_t)(buf[0] << 8 | buf[1]);
}
上述函数使用 HAL_I2C_Mem_Read
从相应起始寄存器连续读取所需字节数,将结果存入缓冲区,然后通过移位和按位或组合高低字节为 int16_t
类型的原始数据。需要注意 MPU6050 输出的数据为**16位有符号整数(补码)**格式 (MPU 6050 Gyro,Accelerometer Communication With Arduino (Atmega328p) : 5 Steps – Instructables)。HAL 库读取的是无符号 uint8_t
,组合后转换成 int16_t
才能正确表示负值。
例如,对于加速度计X轴数据:ACCEL_XOUT_H
和 ACCEL_XOUT_L
组成一个16位值。如果设备静止且Z轴朝上,则 X、Y轴加速度值应接近0,Z轴约为 +1g(其原始值非0,因为包含重力加速度)。组合时一定要将高字节左移8位,再或上低字节 (How to interface MPU6050 (GY-521) with STM32)。如果顺序或位运算错误,会导致计算出的加速度值不正确。
通过一次性读6字节相比每轴单独读寄存器效率更高 (How to interface MPU6050 (GY-521) with STM32)。HAL_I2C_Mem_Read 支持连续读,所以我们利用这一特性减少I2C启动停止次数。
FIFO 和中断应用
MPU6050 内部有一个 1024 字节的 FIFO(先入先出)缓冲,可以配置将传感器数据批量存入 FIFO,再由主机一次性读取。这对于需要同步多个传感器数据或者降低主机读取频率的应用很有用。FIFO 和中断的典型使用场景:
0x6A
) 中设置 FIFO_EN 位启用 FIFO,在 FIFO_EN (0x23
) 寄存器中选择要存入 FIFO 的数据(加速度、陀螺仪、温度等)。当 FIFO 中存有数据时,可以读取 FIFO_COUNT (0x72,0x73
) 获取字节数,然后通过反复读取 FIFO_R_W (0x74
) 寄存器将数据读出。读完后 FIFO 自动清除相应数据。0x38
) 寄存器的第0位,可以在每次传感器完成一次测量更新数据时,通过 INT 引脚产生中断信号通知主机。主机在中断回调中调用读取函数获取数据,从而避免不断轮询。0x3A
) 的第4位OFLOW_INT)。可以用来监控读取不及时的情况。0x37
) 可调整中断引脚的极性、开漏/推挽等。默认配置下,MPU6050 的 INT 引脚为主动高、推挽输出,且脉冲宽度50us。一般无需更改。对于初学者,FIFO 和中断不是必须掌握的内容,可以暂时略过。但了解其作用有助于后续优化。例如,我们可以让 MPU6050 按 1kHz 采样,把数据存在 FIFO,然后 STM32 每隔 10ms 读取一次 FIFO 中的所有数据;或者每当 INT引脚提示有新数据,就立即读取,实现准实时响应。
本教程主要聚焦于主动轮询读取方法,即主循环中定时调用读取函数获取当前最新的数据。这种方式简单直观,适合入门。在调试通过后,你可以尝试进阶使用中断方式来提高效率。
5. 数据处理
传感器读取的原始数据需要进行一定的处理才能转化为有意义的物理量(如 m/s^2
、°/s
、°C 等),并用于进一步计算姿态角。以下将介绍原始数据的单位转换、滤波与误差校准,以及计算欧拉角(姿态角)的方法。
原始数据转换
加速度计:在寄存器配置为 ±2g 量程时,MPU6050 输出的加速度原始值的灵敏度约为 16384 LSB/g (How to interface MPU6050 (GY-521) with STM32) (How to interface MPU6050 (GY-521) with STM32)。也就是说,16位有符号值 +16384 对应 +1g 加速度(约9.81m/s²),-16384 对应 -1g。例如静止平放时,Z轴大约输出 +16384(+1g),X、Y轴约0。要将原始值转换为以重力加速度 g 为单位的值,可以除以 16384:
float Ax = Accel_X_RAW / 16384.0f;
float Ay = Accel_Y_RAW / 16384.0f;
float Az = Accel_Z_RAW / 16384.0f;
此时 Ax, Ay, Az 的单位为 g。若要换算成 m/s²,再乘以 9.81 即可。
陀螺仪:在 ±250°/s 量程下,陀螺仪的灵敏度为 131 LSB/(°/s) (How to interface MPU6050 (GY-521) with STM32) (How to interface MPU6050 (GY-521) with STM32)。即原始值 +131 对应 +1 °/s 角速度。转换为角速度(°/s)的公式为原始值除以 131:
float Gx = Gyro_X_RAW / 131.0f;
float Gy = Gyro_Y_RAW / 131.0f;
float Gz = Gyro_Z_RAW / 131.0f;
这样得到 Gx, Gy, Gz 就是每轴的旋转角速度(单位:度每秒)。注意:陀螺仪存在零偏,在静止时输出并非精确为0°/s,通常需要后续的校准。
温度:MPU6050 的片内温度传感器输出也是一个 16位值。根据数据手册,其转换关系为: 温度 (°C) = Temp_RAW / 340.0 + 36.53 (MPU 6050 Gyro,Accelerometer Communication With Arduino (Atmega328p) : 5 Steps – Instructables)。也就是说,原始值0对应 36.53°C,增大340对应温度升高1°C。示例计算:
float Temperature = Temp_RAW / 340.0f + 36.53f;
温度传感器主要用于芯片内部温度补偿,测量环境温度也有一定参考价值,但精度一般(±1°C左右)。
通过上述转换,我们将获取到的原始数据转换成更直观的物理量。例如,可以输出 “Ax=0.01g,Gx=1.23°/s” 这样的信息便于分析。
低通滤波和误差补偿
滤波:加速度和陀螺仪数据通常会叠加噪声,高频抖动在数值上体现为来回跳动。为获得平稳的测量值,低通滤波 是常用手段。MPU6050 内置的 DLPF 已经可以在一定程度上滤除噪声(由CONFIG寄存器设置频率)。除此之外,常见的软件滤波方法有:
filtered_value = alpha * new_value + (1-alpha) * prev_filtered
,通过选择 0<alpha<1
平滑数据,alpha 越小平滑程度越高但反应变慢。滤波的程度要折中考虑实时性和平滑度。对于姿态角计算,常使用互补滤波(下一节详述),本质也是一种融合滤波。
误差补偿:包括以下方面:
简单来说,在代码实现中,我们可以做如下处理:
// 简单校准:假设上电时进行了静止校准得到以下偏移值
float Ax_offset = 0.01f, Ay_offset = -0.02f, Az_offset = 0.00f;
float Gx_offset = 0.5f, Gy_offset = -1.2f, Gz_offset = 0.3f;
// 在转换得到物理量后,减去偏移
Ax = Ax - Ax_offset;
Ay = Ay - Ay_offset;
Az = Az - (1.0f + Az_offset); // 注意静止时Az应为+1g,所以可让offset校准到1g
Gx = Gx - Gx_offset;
Gy = Gy - Gy_offset;
Gz = Gz - Gz_offset;
上例是假设通过某种方式得到了偏置值,然后实时扣除。特别地,加速度计的 Z 轴偏置可以让静止时输出等于 +1g。
经过滤波和补偿后,数据将更接近真实物理值且稳定。如果只是做简单演示,可以暂不执行上述校准步骤,直接看原始输出变化即可。
欧拉角计算方法
欧拉角是用三个转角(通常称为偏航角(Yaw)、俯仰角(Pitch)、滚转角(Roll))描述刚体空间姿态的一种方式 (mpu6050姿势融合(欧拉角计算) – 刘梢 – 博客园)。对于一般水平放置的传感器,我们可以这样定义:
不同参考系定义可能略有差异,但上述是常见定义之一。关键是保持一致性。
要计算这些姿态角,我们需要融合加速度计和陀螺仪数据:
利用加速度计计算倾角:在静止或低速运动时,加速度计测得的分量主要是重力。通过重力在各轴的投影,可以算出设备与水平面的夹角。例如:
Pitch_acc = atan2(Ax, sqrt(Ay^2 + Az^2))
(假定 X 轴水平向前,Z 轴竖直向上)Roll_acc = atan2(Ay, Az)
以上公式其实是在计算加速度向量相对于轴的夹角。atan2函数可确保象限正确。计算结果以弧度为单位,可乘 (180/π) 转为度。
利用陀螺仪积分角度:陀螺仪给出的是角速度,将角速度对时间积分即可得到角度变化量。例如在循环中每隔 dt
时间读取一次,若 Gx
为当前X轴角速度(°/s),则角度增量 dPhi = Gx * dt
。将其累加:Phi_gyro += dPhi
就是通过陀螺积分的角度。Pitch、Roll、Yaw 各自用对应轴的角速度积分得到。
融合(互补滤波):加速度计计算的角度在短期内容易受加速度干扰(如突然移动时不再纯粹是重力),但长时间稳定;陀螺仪积分角在短期内准确、响应快,但长时间会漂移。互补滤波利用两者优势,例如每次更新:
const float alpha = 0.98f; // 权重,可以调整
pitch = alpha * (pitch + Gx * dt) + (1 - alpha) * Pitch_acc;
roll = alpha * (roll + Gy * dt) + (1 - alpha) * Roll_acc;
其中 pitch
和 roll
是融合后的结果。pitch + Gx*dt
相当于陀螺积分更新,乘以0.98保持主要来自陀螺仪的信号;Pitch_acc
是由加速度计算出的角度,用较小权重校正漂移。这样可以在保证快速响应的同时慢慢纠正漂移 (mpu6050姿势融合(欧拉角计算) – 刘梢 – 博客园)。
偏航角 (Yaw):由于加速度计无法测量绕Z轴的旋转(重力在Z轴旋转不变),仅靠MPU6050无法计算绝对航向角。纯陀螺仪积分的Yaw会逐渐漂移。一般需要磁力计(电子罗盘)配合,或使用MPU6050的DMP输出四元数进行计算。如果没有磁力计,可以通过陀螺仪短时间获得相对旋转角,但长时间参考意义不大。在两轮平衡小车等短时间姿态稳定应用中,Yaw漂移可暂不理会。
总之,实现姿态解算的关键在于算法。对于零基础入门,可以先尝试用加速度计的值计算静态的倾角:
float pitch_acc = atan2((double)Accel_X_RAW, sqrt(Accel_Y_RAW*(double)Accel_Y_RAW + Accel_Z_RAW*(double)Accel_Z_RAW)) * (180.0/M_PI);
float roll_acc = atan2((double)Accel_Y_RAW, (double)Accel_Z_RAW) * (180.0/M_PI);
这将得到基于重力的俯仰和滚转角度(单位°)。当传感器静止时,这个计算比较准确;运动时会有误差。你可以在代码中打印这个值试验。当掌握读取数据后,可以进一步实现互补滤波融合上述计算值与陀螺积分值来得到动态实时的角度。
一些开源库(如 [JRowberg 的 MPU6050 DMP 库]、RTIMULib 等)已经实现了复杂的姿态融合算法,包括卡尔曼滤波或四元数,但理解其原理需要更多数学知识。对于本教程范围,我们强调基础的思路:传感器测量→数据转换→简单融合。
6. 示例代码
下面给出一个综合示例代码,将前面介绍的初始化、读取和数据处理结合起来。该示例会初始化 MPU6050,并在主循环中读取加速度、陀螺仪和温度数据,转换为物理单位后通过串口打印输出。
/* 包含头文件 */
#include "main.h"
#include <stdio.h>
#include <math.h>
/* MPU6050 寄存器地址宏定义 */
#define MPU6050_ADDR (0x68 << 1) // AD0接地时的I2C地址(8位)
#define WHO_AM_I_REG 0x75
#define PWR_MGMT_1_REG 0x6B
#define SMPLRT_DIV_REG 0x19
#define CONFIG_REG 0x1A
#define GYRO_CONFIG_REG 0x1B
#define ACCEL_CONFIG_REG 0x1C
/* 原始数据全局变量 */
int16_t Accel_X_RAW, Accel_Y_RAW, Accel_Z_RAW;
int16_t Gyro_X_RAW, Gyro_Y_RAW, Gyro_Z_RAW;
int16_t Temp_RAW;
/* 转换后的物理量全局变量 */
float Ax, Ay, Az;
float Gx, Gy, Gz;
float Temperature;
/* 初始化 MPU6050 传感器 */
HAL_StatusTypeDef MPU6050_Init(void) {
uint8_t check, data;
// 检查 WHO_AM_I
if(HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, WHO_AM_I_REG, 1, &check, 1, 100) != HAL_OK) {
return HAL_ERROR;
}
if(check != 0x68) {
return HAL_ERROR; // 设备ID不匹配
}
// 唤醒传感器
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, PWR_MGMT_1_REG, 1, &data, 1, 100);
HAL_Delay(10);
// 设置采样率 1kHz
data = 0x07;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, SMPLRT_DIV_REG, 1, &data, 1, 100);
// 配置 DLPF (44Hz)
data = 0x03;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, CONFIG_REG, 1, &data, 1, 100);
// 陀螺仪 ±250°/s
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, GYRO_CONFIG_REG, 1, &data, 1, 100);
// 加速度计 ±2g
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, ACCEL_CONFIG_REG, 1, &data, 1, 100);
return HAL_OK;
}
/* 读取所有传感器数据 */
void MPU6050_Read_All(void) {
uint8_t buf[14];
// 一次性读取 加速度(6)+温度(2)+陀螺仪(6) 共14字节
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B, 1, buf, 14, 100);
// 加速度
Accel_X_RAW = (int16_t)(buf[0] << 8 | buf[1]);
Accel_Y_RAW = (int16_t)(buf[2] << 8 | buf[3]);
Accel_Z_RAW = (int16_t)(buf[4] << 8 | buf[5]);
// 温度
Temp_RAW = (int16_t)(buf[6] << 8 | buf[7]);
// 陀螺仪
Gyro_X_RAW = (int16_t)(buf[8] << 8 | buf[9]);
Gyro_Y_RAW = (int16_t)(buf[10] << 8 | buf[11]);
Gyro_Z_RAW = (int16_t)(buf[12] << 8 | buf[13]);
// 转换为物理量
Ax = Accel_X_RAW / 16384.0f;
Ay = Accel_Y_RAW / 16384.0f;
Az = Accel_Z_RAW / 16384.0f;
Gx = Gyro_X_RAW / 131.0f;
Gy = Gyro_Y_RAW / 131.0f;
Gz = Gyro_Z_RAW / 131.0f;
Temperature = Temp_RAW / 340.0f + 36.53f;
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
// 初始化MPU6050并检查
if(MPU6050_Init() != HAL_OK) {
printf("MPU6050 Init Failed!\r\n");
Error_Handler();
}
printf("MPU6050 Ready.\r\n");
while (1) {
// 读取传感器数据
MPU6050_Read_All();
// 打印输出到串口
printf("Acc [g]: %.2f, %.2f, %.2f\t", Ax, Ay, Az);
printf("Gyro [deg/s]: %.2f, %.2f, %.2f\t", Gx, Gy, Gz);
printf("Temp: %.2f C\r\n", Temperature);
HAL_Delay(500);
}
}
代码说明:
MPU6050_Read_All()
一次性读取了14字节,将所有需要的数据读入。相比分别读加速度、陀螺仪、温度,可以减少I2C通信次数,提高效率。printf
函数可用于串口打印,这要求重定向 printf
到 USART1。例如在 USART1_UART_Init()
里或通过 fputc
系统调用将字符发送至 huart1
。如果 printf
未配置,可以改用 sprintf
加 HAL_UART_Transmit()
实现。同理,波特率需与PC端串口监视器匹配。编译并下载上述代码到 STM32 后,打开串口监视器(115200 8N1),你应该可以看到持续输出的数值。当晃动传感器时,加速度和陀螺仪值会改变;静止时,X、Y轴加速度接近0,Z轴约为1.00g,陀螺仪接近0,温度则是当前芯片温度(通常在室温下 30~40°C 左右)。例如:
Acc [g]: 0.01, -0.02, 1.00 Gyro [deg/s]: 0.15, -0.05, 0.20 Temp: 36.53 C
表示当前X轴略微指向上(0.01g),Y轴略微指向下(-0.02g),Z轴朝上(1.00g),陀螺仪读数很小(略有噪声),温度约36.53°C。
如果读数不正常(比如全为0或无法变化),需要检查硬件连接和初始化步骤,具体可参考下节调试部分。
7. 调试与优化
在实际开发中,可能会遇到各种问题。这里总结一些常见问题及解决方案,以及进一步优化的建议。
常见问题分析及解决方案
- I2C 通信失败 / MPU6050 无响应:如果
HAL_I2C_Mem_Read
返回错误或 WHO_AM_I 读不到 0x68: - 检查硬件连线,尤其是 SDA/SCL 是否接反,GND 是否共有。
- 确认 MPU6050 供电正常(用万用表测模块 VCC/GND 电压,应约3.3V)。
- 确认 MPU6050 地址设置正确。如果 AD0 接地却使用了 0x69 地址,或反之,都会导致无应答。默认模块 AD0为低,应使用地址0x68(8位0xD0) (How to interface MPU6050 (GY-521) with STM32)。
- I2C 引脚需要上拉电阻。如果用了非模块裸芯片,要加4.7kΩ上下拉;模块通常已自带,但也可以通过示波器观察SDA/SCL电平判断是否上拉。
- STM32 I2C 初始化是否成功,调试时可以用
HAL_I2C_IsDeviceReady(&hi2c1, MPU6050_ADDR, 2, 10)
检查设备应答。 - 读取数据全为固定值:比如加速度一直是+1g或0,陀螺仪全0:
- 可能是 MPU6050 仍在睡眠未唤醒。确保 PWR_MGMT_1 正确写0成功。可尝试增加写入后的延迟或重复写。
- 检查是否每次读取都使用了正确的寄存器地址和长度。一个常见错误是读取时地址大小参数错误或未左移地址。HAL 的 Mem_Read 已经要求地址左移1位,所以定义的 MPU6050_ADDR 应为0xD0 而非0x68。
- 确认没有忽略负值。若未经转换直接打印 int16_t 原始值,看到的可能是一个很大的整数(因为打印当作uint16_t)。要强制转换为有符号或转成物理量打印。
- 数据跳变剧烈:原始数据噪声大是正常现象,特别是陀螺仪零漂明显。例如陀螺仪在静止时每次读都有几度每秒变化,甚至缓慢漂移:
- 这是惯性传感器特性,可以通过滤波改善。启用内部DLPF(如配置寄存器0x1A为4或5,带宽20Hz以下)能降低噪声,但会略滞后信号。
- 自行在代码中做滑动平均或互补滤波处理数据。
- 陀螺仪的漂移可以通过每次上电静置校准。取静止时若干样本的平均值作为偏移量,读数时减去。
- 姿态角计算错误:如果尝试计算 Pitch/Roll 发现某些角度不对:
- 注意
atan2(y, x)
参数顺序和选用正确的轴值。确认所用加速度轴对应定义的物理方向。 - 当 Az 接近0时,计算俯仰角公式可能产生不稳定,此时可以用另一种公式组合(视具体坐标系而定)。确保使用的公式与传感器坐标系匹配。
- 未融合陀螺仪,仅用加速度计算时,在快速运动情况下角度会严重不准。这需要引入陀螺仪数据进行融合。
- 串口打印乱码或不输出:这通常是串口配置问题,与MPU6050无直接关系:
- 检查 USART 波特率设置和电脑端监视器波特率一致。
- 确认
printf
已经正确重定向。如果没有,可用HAL_UART_Transmit
发送字符串替代。 - 如果使用
printf
打印浮点数,需要使能-u _printf_float
(CubeIDE 中在项目设置 -> MCU GCC Linker -> Misc 中添加),否则浮点格式会无法正常输出 (How to interface MPU6050 (GY-521) with STM32)。
调试时,建议分步骤进行。例如先验证 WHO_AM_I 是否正确,然后再进行下一步初始化;逐步读取加速度,观察静放时Z轴是否约=16384 (或1g);然后移动传感器,看数据变化是否合理。如果某一步不对,集中解决该步问题。
此外,使用逻辑分析仪或示波器监听 I2C 时序,可以直观判断是否有 ACK、数据传输是否正确,这是硬件调试的有效手段。如果没有这些工具,可以尝试使用软件I2C库或改变时钟等来进一步排查(不过HAL一般已经处理好了时序和仲裁)。
误差处理与优化建议
当基本功能实现后,可以考虑进一步优化性能和精度:
mpu6050.c/.h
中封装,提供初始化和读取接口。这样主程序更简洁,也方便移植到不同项目中。最后,充分利用社区资源。当遇到问题时,查看 MPU6050 数据手册和寄存器手册是最根本的指导 (Implementation of MPU6050 with STM32 – Get-To-Byte) (Implementation of MPU6050 with STM32 – Get-To-Byte);此外网上有大量教程和示例代码 (【STM32】I2C练习,HAL库读取MPU6050角度陀螺仪 – CSDN博客) (HAL 库开发笔记- I2C 通信(MPU6050) – Power's Wiki)(包括Arduino平台的实现,可参考逻辑)。调试经验需要积累,多实验、多观察信号是关键。
希望通过本教程,你已经成功地让 STM32 读取到了 MPU6050 的六轴数据并对其进行了初步处理。在此基础上,你可以尝试扩展功能,例如将数据用于姿态平衡控制,或者搭配磁力计实现航向角计算等。祝你在嵌入式开发和姿态解算的学习之路上不断进步!
参考资料:
作者:与光同尘 大道至简