15. 硬件I2C驱动MPU6050模块
一、MPU6050简介
MPU6050 是由 InvenSense 公司生产的全球首款整合性六轴运动处理模块。它内带 3 轴陀螺仪和 3 轴加速度传感器,并且含有一个第二 I2C 接口,可用于连接外部磁力传感器。MPU6050 可以实时获取运动物体在三维坐标系内的偏转角度,包括绕 X 轴偏转的 roll 角、绕 Y 轴偏转的 pitch 角以及绕 Z 轴偏转的 yaw 角。
MPU6050 还带有数字运动处理器(DMP: Digital Motion Processor)硬件加速引擎,通过主 I2C 接口,它可以向应用端输出完整的 9 轴姿态融合演算数据。有了 DMP,我们可以使用InvenSense 公司提供的运动处理资料库,非常方便地实现姿态解算,降低了运动处理运算对操作系统的负荷,同时大大降低了开发难度。
二、MPU6050结构框图
其中,SCL 和 SDA 是连接 MCU 的 I2C 接口,MCU 通过这个 I2C 接口来控制 MPU6050,另外还有一个 I2C 接口:AUX_CL 和 AUX_DA,这个接口可用来连接外部从设备,比如磁传感器,这样就可以组成一个九轴传感器。VLOGIC 是 IO 口电压,该引脚最低可以到 1.8V,我们一般直接接 VDD 即可。AD0 是从 I2C 接口(接 MCU)的地址控制引脚,该引脚控制 I2C 地址的最低位。如果接 GND,则 MPU6050的 I2C 地址是:0X68,如果接 VDD,则是 0X69,注意:这里的地址是不包含数据传输的最低位的(最低位用来表示读写)。
三、MPU6050常用寄存器
3.1、电源管理寄存器
位 | 含义 |
---|---|
DEVICE_RESET | 该位置 1,重启内部寄存器到默认值。复位完成后该位自动清 0 |
SLEEP | 该位置 1,MPU6050 进入睡眠模式 |
CYCLE | 当失能 SLEEP 且 CYCLE 位置 1,MPU6050 进入循环模式 |
TEMP_DIS | 该位置 1,失能温度传感器 |
CLKSEL | 3 位无符号数值,指定设备的系统时钟源 |
这个寄存器允许用户配置电源模式和时钟源。还提供了复位整个设备和禁用温度传感器的位。当置 SLEEP 位为 1 时,MPU6050 可以进入低功耗睡眠模式。当失能 SLEEP 位且 CYCLE 位置 1 时,MPU6050 进入循环模式(Cycle Mode)。在循环模式下,设备将在睡眠模式和唤醒模式间循环,根据 LP_WAKE_CTRL(寄存器 108)设定的速率从加速度计采集样品数据。唤醒频率的配置在 Power Management 2 寄存器(寄存器 108)中的 LP_WAKE_CTRL 位中配置。
MPU6050 内部自带 8MHz 的振荡器,作为陀螺仪的基准时钟,还可以选择外部时钟作为 MPU-6050 的时钟源。当内部 8MHz 的振荡器或者外部时钟源被选择作为时钟源,MPU6050 开始在低功耗模式下工作,陀螺仪处于失能状态。上电后,MPU6050 的时钟源默认是内部振荡器。这里建议设备使用外部时钟源作为陀螺仪的基准时钟,以提高稳定性。时钟源可以根据下表选择:
CLKSEL[2:0] | 时钟源 |
---|---|
000 | 内部 8M RC 晶振 |
001 | PLL,使用 X 轴陀螺作为参考 |
010 | PLL,使用 Y 轴陀螺作为参考 |
011 | PLL,使用 Z 轴陀螺作为参考 |
100 | PLL,使用外部 32.768MHz 作为参数 |
101 | PLL,使用外部 19.2MHz 作为参数 |
110 | 保留 |
111 | 关闭时钟,保持时序产生电路复位状态 |
默认是使用内部 8M RC 晶振的,精度不高,所以我们一般选择 X/Y/Z 轴陀螺作为参考的 PLL 作为时钟源,一般设置 CLKSEL=001 即可。
位 | 含义 |
---|---|
LP_WAKE_CTRL | 2 位无符号数值 |
STBY_XA | 该位置 1,加速度计的 X 轴进入待机模式 |
STBY_YA | 该位置 1,加速度计的 Y 轴进入待机模式 |
STBY_ZA | 该位置 1,加速度计的 Z 轴进入待机模式 |
STBY_XG | 该位置 1,陀螺仪的 X 轴进入待机模式 |
STBY_YG | 该位置 1,陀螺仪的 Y 轴进入待机模式 |
STBY_ZG | 该位置 1,陀螺仪的 Z 轴进入待机模式 |
这个寄存器允许用户配置加速度计在低功耗模式下唤起的频率。也允许用户让加速度计和陀螺仪的个别轴进入待机模式。只让 MPU6050 的加速度计进入低功耗模式的步骤如下:
- 置 CYCLE 位为 1
- 置 SLEEP 位为 1
- 置 TEMP_DIS 位为 1
- 置 STBY_XG,STBY_YG,STBY_ZG 位为 1
用户可以使用这个寄存器实现加速度计和陀螺仪的个别轴进入待机模式。如果设备使用陀螺仪的一个轴作为时钟源,但同时这个轴进入待机模式,则时钟源会自动切换到内部 8MHz 振荡器。
3.2、陀螺仪配置寄存器
位 | 含义 |
---|---|
XG_ST | 该位置 1,X 轴进行自检 |
YG_ST | 该位置 1,Y 轴进行自检 |
ZG_ST | 该位置 1,Z 轴进行自检 |
FS_SEL | 2 位无符号数值,选择陀螺仪的满量程范围 |
这寄存器是用来触发陀螺仪自检和配置陀螺仪的满量程范围。FS_SEL 选择陀螺仪的满量程,如下表:
FS_SEL | 量程 |
---|---|
00 | ±250°/S |
01 | ±500°/S |
10 | ±1000°/S |
11 | ±2000°/S |
该寄存器我们只关心 FS_SEL[1:0]这两个位,用于设置陀螺仪的满量程范围。我们一般设置为 3,即 ±2000° /S,因为陀螺仪的 ADC 为 16 位分辨率,所以得到灵敏度为: 65536/4000=16.4LSB/(°/S)。
3.3、加速度传感器配置寄存器
位 | 含义 |
---|---|
XA_ST | 该位置 1,加速度计的 X 轴进行自检 |
YA_ST | 该位置 1,加速度计的 Y 轴进行自检 |
ZA_ST | 该位置 1,加速度计的 Z 轴进行自检 |
AFS_SEL | 2 位无符号数值,选择陀螺仪的满量程范围 |
这寄存器是用来触发加速度计自检和配置加速度计的满量程范围。这个寄存器也可以用于配置数字高通滤波器(DHPF)。
AFS_SEL | 量程 |
---|---|
00 | ±2g |
01 | ±5g |
10 | ±8g |
11 | ±16g |
该寄存器我们只关心 AFS_SEL[1:0] 这两个位,用于设置加速度传感器的满量程范围。我们一般设置为 0,即 ±2g , 因 为 加 速度传感器的 ADC 也是 16 位,所以得到灵敏度为:65536/4=16384LSB/g。
3.4、FIFO使能寄存器
位 | 含义 |
---|---|
TEMP_FIFO_EN | 该位置 1,该位使能 TEMP_OUT_H 和 TEMP_OUT_L(寄存器 65 和 66)可以加载到 FIFO 缓冲区 |
XG_FIFO_EN | 该位置 1,该位使能 GYRO_XOUT_H 和 GYRO_XOUT_L(寄存器 67 和 68)可以加载到 FIFO 缓冲区 |
YG_FIFO_EN | 该位置 1,该位使能 GYRO_YOUT_H 和 GYRO_YOUT_L(寄存器 69 和 70)可以加载到 FIFO 缓冲区 |
ZG_FIFO_EN | 该位置 1,该位使能 GYRO_ZOUT_H 和 GYRO_ZOUT_L(寄存器 71 和 72)可以加载到 FIFO 缓冲区 |
ACCEL_FIFO_EN | 该位置 1,该位使能 ACCEL_XOUT_H、ACCEL_XOUT_L、ACCEL_YOUT_H、ACCEL_YOUT_L、ACCEL_ZOUT_H 和 ACCEL_ZOUT_L(寄存器 59 to 64)可以加载到 FIFO 缓冲区 |
SLV2_FIFO_EN | 该位置 1,该位使能 EXT_SENS_DATA 寄存器(寄存器 73 to 96)和从机 2可以加载到 FIFO 缓冲区 |
SLV1_FIFO_EN | 该位置 1,该位使能 EXT_SENS_DATA 寄存器(寄存器 73 to 96)和从机 1可以加载到 FIFO 缓冲区 |
SLV0_FIFO_EN | 该位置 1,该位使能 EXT_SENS_DATA registers(寄存器 73 to 96)和从机 0 可以加载到 FIFO 缓冲区 |
该寄存器用于控制 FIFO 使能,在简单读取传感器数据的时候,可以不用 FIFO,设置对应位为 0 即可禁止 FIFO,设置为 1,则使能 FIFO。注意加速度传感器的 3 个轴,全由 1个位(ACCEL_FIFO_EN)控制,只要该位置 1,则加速度传感器的三个通道都开启 FIFO了。
3.5、采样率分频寄存器
该寄存器用于 MPU6050 的陀螺仪采样频率输出设置。计算公式是:
这里陀螺仪的输出频率,是 1KHz 或者 8KHz,与数字低通滤波器(DLPF)的设置有关,当 DLPF_CFG=0/7 的时候,频率为 8KHz,其他情况是 1Khz。而且 DLPF 滤波频率一般设置为采样率的一半。采样率,我们假定设置为 50Hz,那么SMPLRT_DIV=1000/50-1=19。
3.6、配置寄存器
位 | 含义 |
---|---|
EXT_SYNC_SET | 3 位无符号数值,配置FSYNC 引脚采样 |
DLPF_CFG | 3 位无符号数值,配置 DLPF 设置 |
这里,我们主要关心数字低通滤波器(DLPF)的设置位,即:DLPF_CFG[2:0],加速度计和陀螺仪,都是根据这三个位的配置进行过滤的。DLPF_CFG 不同配置对应的过滤情况如下:
这里的加速度传感器,输出速率(Fs)固定是 1Khz,而角速度传感器的输出速率(Fs),则根据 DLPF_CFG 的配置有所不同。一般我们设置角速度传感器的带宽为其采样率的一半,如前面所说的,如果设置采样率为 50Hz,那么带宽就应该设置为 25Hz,取近似值 20Hz,就应该设置 DLPF_CFG=100。
3.7、陀螺仪数据输出寄存器
陀螺仪数据输出寄存器总共由 6 个寄存器组成,输出 X/Y/Z 三个轴的陀螺仪传感器数据,高字节在前,低字节在后。
3.8、加速度传感器数据输出寄存器
加速度传感器数据输出寄存器总共由 6 个寄存器组成,输出 X/Y/Z 三个轴的加速度传感器值,高字节在前,低字节在后。
3.9、温度传感器数据输出寄存器
通过读取 0X41(高 8 位)和 0X42(低 8 位)寄存器得到,温度换算公式为:
其中,Temperature 为计算得到的温度值,单位为 ℃,regval 为从 0X41 和 0X42 读到的温度传感器值。
四、MPU6050传感器的使用步骤
4.1、初始化I2C接口
MPU6050 采用 I2C 接口与单片机之间进行通信,所以我们需要先初始化与 MPU6050 连接的 SDA 和 SCL 数据线。
I2C_HandleTypeDef g_i2c1_handle;
/**
* @brief I2C1初始化
*
* @param speed SCL的时钟频率,此值要低于400000
*/
void I2C1_Init(uint32_t speed)
{
g_i2c1_handle.Instance = I2C1; // 使用的I2C
g_i2c1_handle.Init.ClockSpeed = speed; // SCL时钟频率
g_i2c1_handle.Init.DutyCycle= I2C_DUTYCYCLE_2; // 时钟占空比
g_i2c1_handle.Init.OwnAddress1 = 0; // STM32自身设备地址1
g_i2c1_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 地址模式
g_i2c1_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 双地址模式
g_i2c1_handle.Init.OwnAddress2 = 0; // STM32自身设备地址2
g_i2c1_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 通用广播地址
g_i2c1_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 禁止时钟延长模式
HAL_I2C_Init(&g_i2c1_handle);
}
/**
* @brief I2C底层初始化函数
*
* @param hi2c I2C句柄
*/
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
GPIO_InitTypeDef GPIO_InitStruct ={0};
if(hi2c->Instance == I2C1)
{
__HAL_RCC_I2C1_CLK_ENABLE(); // 使能I2C1时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能I2C1对应的GPIO时钟
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // SCL引脚和SDA引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 复用开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP; // 使用上拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 输出速度
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; // 复用功能
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}
4.2、复位MPU6050
这一步让 MPU6050 内部所有寄存器恢复默认值,通过对电源管理寄存器 1(0x6B)的 bit7 写 1 实现。 复位后, 电源管理寄存器 1 恢复默认值(0x40),然后必须设置该寄存器为 0x00,以唤醒 MPU6050,进入正常工作状态。
4.3、设置角速度传感器和加速度传感器的满量程范围
这一步,我们设置两个传感器的满量程范围(FSR),分别通过陀螺仪配置寄存器(0x1B)和加速度传感器配置寄存器(0x1C)设置。我们一般设置陀螺仪的满量程范围为 ±2000dps,加速度传感器的满量程范围为 ±2g。
4.4、设置其它参数
这里,我们还需要配置的参数有:关闭中断、关闭 AUX IIC 接口、禁止 FIFO、设置陀螺仪采样率和设置数字低通滤波器(DLPF)等。
这里我们不用中断方式读取数据,所以关闭中断,然后也没用到 AUX IIC 接口外接其他传感器,所以也关闭这个接口。分别通过中断使能寄存器(0x38)和用户控制寄存器(0x6A)控制。 MPU6050 可以使用 FIFO 存储传感器数据,不过这里我们也没有用到,所以关闭所有 FIFO 通道,这个通过 FIFO 使能寄存器(0x23)控制,默认都是 0(即禁止 FIFO),所以用默认值就可以了。陀螺仪采样率通过采样率分频寄存器(0x19)控制,这个采样率我们一般设置为 50 即可。数字低通滤波器(DLPF)则通过配置寄存器(0x1A)设置,一般设置 DLPF 为带宽的 1/2 即可。
4.6、配置系统时钟源并使能角速度传感器和加速度传感器
系统时钟源同样是通过电源管理寄存器 1(0x6B)来设置,该寄存器的最低三位用于设置系统时钟源选择,默认值是 0(内部 8M RC 震荡),不过我们一般设置为 1,选择 X 轴陀螺 PLL 作为时钟源,以获得更高精度的时钟。同时,使能角速度传感器和加速度传感器,这两个操作通过电源管理寄存器 2(0x6C)来设置,设置对应位为 0 即可开启。
/**
* @brief MPU6050初始化函数
*
* @param hi2c I2C句柄
*/
void MPU6050_Init(I2C_HandleTypeDef *hi2c)
{
MPU6050_WriteRegister(hi2c, MPU6050_PWR_MGMT_1, 0x01); // 电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
MPU6050_WriteRegister(hi2c, MPU6050_PWR_MGMT_2, 0x00); // 电源管理寄存器2,保持默认值0,所有轴均不待机
MPU6050_WriteRegister(hi2c, MPU6050_SMPLRT_DIV, 0x09); // 采样率分频寄存器,配置采样率
MPU6050_WriteRegister(hi2c, MPU6050_CONFIG, 0x06); // 配置寄存器,配置DLPF
MPU6050_WriteRegister(hi2c, MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪配置寄存器,选择满量程为±2000°/s
MPU6050_WriteRegister(hi2c, MPU6050_ACCEL_CONFIG, 0x18); // 加速度计配置寄存器,选择满量程为±16g
}
4.7、读取加速度传感器、角速度传感器和温度传感器的数据
陀螺仪数据输出寄存器,总共有 6 个寄存器组成,地址为: 0x43 ~ 0x48,通过读取这 6 个寄存器,就可以读到陀螺仪 X/Y/Z 轴的值,比如 X 轴的数据,可以通过读取 0x43(高 8 位)和 0x44(低 8 位)寄存器得到,其他轴以此类推。
同样,加速度传感器数据输出寄存器,也有 6 个,地址为: 0x3B~0x40,通过读取这 6 个寄存器,就可以读到加速度传感器 X/Y/Z 轴的值,比如读 X 轴的数据,可以通过读取 0x3B(高 8 位)和 0x3C(低 8 位)寄存器得到,其他轴以此类推。
最后,温度传感器的值,可以通过读取 0x41(高 8 位)和 0x42(低 8 位)寄存器得到,温度换算公式为:
其中, Temperature 为计算得到的温度值,单位为 ℃, regval 为从 0x41 和 0x42 读到的温度传感器值。
typedef struct MPU6050_DataStruct
{
int16_t accel_x;
int16_t accel_y;
int16_t accel_z;
int16_t gyro_x;
int16_t gyro_y;
int16_t gyro_z;
} MPU6050_DataStruct;
void MPU6050_GetData(I2C_HandleTypeDef *hi2c, MPU6050_DataStruct *data)
{
uint8_t highData = 0, lowData = 0; // 定义数据高8位和低8位的变量
highData = MPU6050_ReadRegister(hi2c, MPU6050_ACCEL_XOUT_H); // 读取加速度计X轴的高8位数据
lowData = MPU6050_ReadRegister(hi2c, MPU6050_ACCEL_XOUT_L); // 读取加速度计X轴的低8位数据
data->accel_x = (highData << 8) | lowData; // 数据拼接,通过输出参数返回
highData = MPU6050_ReadRegister(hi2c, MPU6050_ACCEL_YOUT_H); // 读取加速度计Y轴的高8位数据
lowData = MPU6050_ReadRegister(hi2c, MPU6050_ACCEL_YOUT_L); // 读取加速度计Y轴的低8位数据
data->accel_y = (highData << 8) | lowData; // 数据拼接,通过输出参数返回
highData = MPU6050_ReadRegister(hi2c, MPU6050_ACCEL_ZOUT_H); // 读取加速度计Z轴的高8位数据
lowData = MPU6050_ReadRegister(hi2c, MPU6050_ACCEL_ZOUT_L); // 读取加速度计Z轴的低8位数据
data->accel_z = (highData << 8) | lowData; // 数据拼接,通过输出参数返回
highData = MPU6050_ReadRegister(hi2c, MPU6050_GYRO_XOUT_H); // 读取陀螺仪X轴的高8位数据
lowData = MPU6050_ReadRegister(hi2c, MPU6050_GYRO_XOUT_L); // 读取陀螺仪X轴的低8位数据
data->gyro_x = (highData << 8) | lowData; // 数据拼接,通过输出参数返回
highData = MPU6050_ReadRegister(hi2c, MPU6050_GYRO_YOUT_H); // 读取陀螺仪Y轴的高8位数据
lowData = MPU6050_ReadRegister(hi2c, MPU6050_GYRO_YOUT_L); // 读取陀螺仪Y轴的低8位数据
data->gyro_y = (highData << 8) | lowData; // 数据拼接,通过输出参数返回
highData = MPU6050_ReadRegister(hi2c, MPU6050_GYRO_ZOUT_H); // 读取陀螺仪Z轴的高8位数据
lowData = MPU6050_ReadRegister(hi2c, MPU6050_GYRO_ZOUT_L); // 读取陀螺仪Z轴的低8位数据
data->gyro_z = (highData << 8) | lowData; // 数据拼接,通过输出参数返回
}
五、原理图
从原理图可以看出,MPU6050 模块的 AD0 接在 GND 上,所以 MPU6050 的器件地址是:0X68。
六、程序源码
MPU6050 设备地址:
#define MPU6050_ADDRESS 0x68
MPU6050 设备地址:
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
MPU6050 写寄存器函数:
/**
* @brief MPU6050写寄存器函数
*
* @param hi2c I2C句柄
* @param address 寄存器地址
* @param data 待写入的数据
*/
void MPU6050_WriteRegister(I2C_HandleTypeDef *hi2c, uint8_t address, uint8_t data)
{
HAL_I2C_Mem_Write(hi2c, MPU6050_ADDRESS << 1, address, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
HAL_Delay(5);
}
MPU6050 读寄存器函数:
/**
* @brief MPU6050读寄存器函数
*
* @param hi2c I2C句柄
* @param address 寄存器地址
* @return uint8_t 读取到的数据
*/
uint8_t MPU6050_ReadRegister(I2C_HandleTypeDef *hi2c, uint8_t address)
{
uint8_t data = 0;
HAL_I2C_Mem_Read(hi2c, MPU6050_ADDRESS << 1, address, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
return data;
}
main() 函数:
int main(void)
{
MPU6050_DataStruct data;
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
USART1_Init(115200);
I2C1_Init(400000);
MPU6050_Init(&g_i2c1_handle);
while (1)
{
MPU6050_GetData(&g_i2c1_handle, &data);
printf("accel_x:%d, accel_y:%d, accel_z:%d\r\n", data.accel_x, data.accel_y, data.accel_z);
printf("gyro_x:%d, gyro_y:%d, gyro_z:%d\r\n", data.gyro_x, data.gyro_y, data.gyro_z);
HAL_Delay(1000);
}
return 0;
}