@([HAL库]STM32F1系列使用MPU6050(初级使用))
简介
最近开始使用了6轴姿态传感器MPU6050,准备学习一下姿态融合,于是打算写下了这篇博客。使用MPU6050之前,建议先阅读一遍他的寄存器手册,这样初学者能够更进一步了解MPU6050的地址,能够更快速理解配置代码。废话不多说,先看MPU6050的寄存器。
MPU6050主要寄存器
这个寄存器是指定陀螺仪输出速率的除法器,用于生成MPU-6050的采样速率。这个寄存器的地址是0x19
采样率公式:Sample Rate = Gyroscope Output Rate / (1 + SMPLRT_DIV)
Sample Rate :采样频率 Gyroscope Output Rate:陀螺仪输出频率
SMPLRT_DIV:8位无符号值。 采样速率由陀螺仪输出速率除以该值确定。
对于陀螺仪输出频率需要主要一点:当DLPF低通滤波器禁用时(即DLPF_CF为0或7)陀螺仪输出频率为8KHz,当其使能使,陀螺仪输出频率为1KHz。
这个寄存器是用于配置陀螺仪和加速度计的外部帧同步(FSYNC)引脚采样和数字低通滤波器(DLPF)设置。0x1A是这个寄存器的地址,EXT_SYNC_SET是3位无符号值。 配置FSYNC引脚采样,一般情况下是给000(即禁止输入),DLPF_CF是设置低通滤波器的频率。这个频率一般情况下是带宽的 1/2 即可。(具体详细内容可以见寄存器手册)
该寄存器用于触发陀螺仪自检和配置陀螺仪的满量程范围。这个寄存器的地址是0x1B。这个寄存器的通过FS_SEL[1:0]设置了陀螺仪的的量程范围。同时XG_ST,YG_ST,ZG_ST这三个位是用于设置是否开启陀螺仪的XYZ三轴自检查(一般设置0,不进行自检)。
FS_SEL[1:0]有四种量程选择如下图:
通常选用±2000°/S这个量程范围。
该寄存器用于触发加速度计自检和配置加速度计满量程范围。 该寄存器还配置数字高通滤波器(DHPF)(一般都是不适用关闭,给0即可)。寄存器的地址是0x1X。这个寄存器的通过AFS_SEL[1:0]设置了加速度计的的量程范围。同时XA_ST,YA_ST,ZA_ST这三个位是用于设置是否开启加速度计的XYZ三轴自检查(一般设置0,不进行自检)。
AFS_SEL[1:0]有四种量程选择如下图:
通常选用±2g这个量程范围。
该寄存器决定哪些传感器测量值被加载到FIFO缓冲器中。寄存器的地址位0x23,因为本章节是初级使用,因此是禁用这个的给0就行。
此寄存器配置INT引脚中断信号的行为。寄存器地址是0x37。本次没有使用中断,于是这个寄存器也设置为0。
该寄存器允许中断源产生中断。寄存器地址0x38,但是没有使用中断,本章节并不使用中断因此也给0即可。如果使用中断根据寄存器手册配置即可。
这些寄存器存储最近的加速度计测量值。
ACCEL_XOUT:16位2的补码值。 存储最近的X轴加速度计测量值。
ACCEL_YOUT:16位2的补码值。 存储最近的Y轴加速度计测量值。
ACCEL_ZOUT:16位2的补码值。 存储最近的Z轴加速度计测量值。
这些寄存器存储最新的温度传感器测量值。
TEMP_OUT:16位有符号值。 存储最近的温度传感器测量值。
这些寄存器存储最新的陀螺仪测量值。
GYRO_XOUT:16位2的补码值。 存储最近的X轴陀螺仪测量。
GYRO_YOUT:16位2的补码值。 存储最近的Y轴陀螺仪测量。
GYRO_ZOUT:16位2的补码值。 存储最近的Z轴陀螺仪测量值。
此寄存器允许用户启用和禁用FIFO缓冲器、I2C主模式和主I2C接口。寄存器的地址是0X1A,初级使用下FIFO禁用,并且也没有外接AUX IIC传感器,所以也禁用I2C主模式和接口,直接给0。
此寄存器允许用户配置电源模式和时钟源。 它还提供了一个用于重置整个设备的位,以及一个用于禁用温度传感器的位。该寄存器的地址是0x6B。上电初始化时得先使用这个寄存器进行一个重置整个设备,设置最高位DEVICE_RESET位1即重置。
SLEEP置1睡眠,一般0。
CYCLE当此位设置为1并且休眠被禁用时,MPU-60x0将在休眠模式和唤醒模式之间循环,以由LP_WAKE_CTRL(寄存器108)确定的速率从活动传感器获取单个数据样本。
TEMP_DIS当设置为1时,此位禁用温度传感器.
CLKSEL[2:0]寄存器配置时钟源,如图:
一般选者陀螺仪X轴的时钟源。
此寄存器允许用户在加速度计低功耗模式下配置唤醒频率。寄存器地址是0x6C,该寄存器主要目的就是为了启动MPU6050的陀螺仪和加速度计。
LP_WAKE_CTRL[1:0]使用在MPU-9050上,因此MPU6050设置为0。
STBY_XA :当设置为1时,此位使X轴加速度计进入待机模式
STBY_YA:当设置为1时,此位使Y轴加速度计进入待机模式
STBY_ZA:当设置为1时,此位使Z轴加速度计进入待机模式
STBY_XG:当设置为1时,此位使X轴陀螺仪进入待机模式。
STBY_YG:当设置为1时,此位使Y轴陀螺仪进入待机模式。
STBY_ZG:当设置为1时,此位使Z轴陀螺仪进入待机模式。
所以以上信号都要设置为0,启动陀螺仪。
到这里以上都是最基本最重要的MPU-6050得得寄存器。还有很多各种功能寄存器需要用在别的场合再进行配置。
STM32CUBE配置
采用STM32F103C8T6作为控制器,该芯片内部有两个硬件IIC,因此采用硬件IIC来控制MPU-6050,以下是配置过程。
配置外部高速晶振。
配置下载模式,我使用的是SWD下载模式。
选择I2C模式。默认引脚是SDA-PB7 SCL-PB6
并且要将I2C时钟频率设置为400KHz。因为MPU-6050手册中写道需要使用400KHz的I2C执行与设备所有寄存器的通信。
配置串口通道1,默认设置即可。
KEIL代码编写
修改HAL库I2C的BUG
在i2c.c文件下 将__HAL_RCC_I2C1_CLK_ENABLE();提前
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C1)
{
/* USER CODE BEGIN I2C1_MspInit 0 */
//I2CBug 将这个使能提前
__HAL_RCC_I2C1_CLK_ENABLE();
/* USER CODE END I2C1_MspInit 0 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/**I2C1 GPIO Configuration
PB6 ------> I2C1_SCL
PB7 ------> I2C1_SDA
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* I2C1 clock enable */
__HAL_RCC_I2C1_CLK_ENABLE();
/* USER CODE BEGIN I2C1_MspInit 1 */
/* USER CODE END I2C1_MspInit 1 */
}
}
设置串口printf重定义
在usart.c的文件底部的/* USER CODE BEGIN 1 /和/ USER CODE END 1 */中间添加以下代码:
报错就是没有添加#include "stdio.h"头文件。
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
MPU-6050配置代码
mpu-6050.h文件
#ifndef __MPU6050_H
#define __MPU6050_H
#include "main.h"
#include "i2c.h"
#define MPU6050_Write_Addr 0xD0 //默认状态 地址为0X69 只有六位数据 使用IC7位寻址 ,第七位根据MPU6050引脚AD0在判断 AD0=0 第七位0 AD0=1 第七位1 第八位I2C读1 写0 我这里使用AD0=0低电平
#define MPU6050_Read_Addr 0xD1
#define MPU_Out_Fre_DIV_Addr 0x19 //采样频率地址 默认8Khz 如果开了低通滤波器 则变为1Khz
#define MPU_Dlpf_Addr 0x1A //低通滤波器地址
#define MPU_Gyro_Addr 0x1B //陀螺仪数据地址 第八七六位 XYZ轴自检给1 不自检给0 第五四两位 用于给陀螺仪量程范围 00-0~250 01-0~500 10-0~1000 11-0~2000 其余位0
#define MPU_Accel_Addr 0x1C //加速计数据地址 第八七六位 XYZ轴自检给1 不自检给0 第五四两位 用于给陀螺仪量程范围 00-±2G 01-±4G 10-±8G 11-±16G 其余位0
#define MPU_Id_Addr 0x75 //MPU ID的地址
#define MPU_Int_Addr 0X38 //mpu中断使能地址
#define MPU_User_Addr 0x6A //MPU用户控制地址 用于控制FIFO I2C主模式
#define MPU_Fifo_En_Addr 0x23 //MPU的FIFO使能地址
#define MPU_Int_Pin_Addr 0x37 //INT中断地址
#define PowerMem_Register1_Addr 0x6B //电源管理1的地址
#define PowerMem_Register2_Addr 0x6C //电源管理2的地址
#define Set_PowerMen_Reset 0x80 //在电源管理 输入 这个数据 为MPU 复位
#define Set_PowerMen_Start_XPPL 0x01 //在电源管理 输入 这个数据 为MPU唤醒 且使用X轴的时钟源
#define Set_Smplrt_Div 0x00 //采样分频
#define Set_Dlpf_Div 0x00 //低通滤波器模式
#define Set_Gyro_Range 0x11 //设置陀螺仪的量程范围
#define Set_Accel_Range 0x00 //设置加速计的量程范围
#define Set_Int_Enable 0x00 //设置关闭全部中断
#define Set_User_I2c 0x00 //禁止FIFO I2C主模式
#define Set_Fifo_Enable 0x00 //禁止FIFO模式
#define Set_Int_Pin_L 0x80 //INT脚低电平有效
#define Sat_Start_Mpu 0x00 //设置启动MPU陀螺仪
//加速度计 数据寄存器地址
#define ACCEL_Xout_H 0x3B
#define ACCEL_Xout_L 0x3C
#define ACCEL_Yout_H 0x3D
#define ACCEL_Yout_L 0x3E
#define ACCEL_Zout_H 0x3F
#define ACCEL_Zout_L 0x40
//陀螺仪 数据寄存器地址
#define GYRO_Xout_H 0x43
#define GYRO_Xout_L 0x44
#define GYRO_Yout_H 0x45
#define GYRO_Yout_L 0x46
#define GYRO_Zout_H 0x47
#define GYRO_Zout_L 0x48
extern signed short ax;
extern signed short ay;
extern signed short az;
extern signed short gx;
extern signed short gy;
extern signed short gz;
extern signed short ax_l;
extern signed short ay_l;
extern signed short az_l;
extern signed short gx_l;
extern signed short gy_l;
extern signed short gz_l;
void I2C_Write(unsigned char dev_addr,unsigned char mem_addr , unsigned char data);
void I2C_Read(unsigned char dev_addr,unsigned char mem_addr , unsigned char *buf,unsigned char len);
void MPU_Set_LPF(uint16_t lpf);
void Set_Mpu_Rate(uint16_t rata);
void MPU_6050_Init(void);
void Read_Mpu_Gyro(void);
void Read_Mpu_Accel(void);
void MPU6050_calibrate(void);
mpu-6050.c文件
#include "mpu6050.h"
#include "stdio.h"
unsigned char I2C_Accel_Buf[6];
unsigned char I2C_Gyro_Buf[6];
//加速度计
signed short ax;
signed short ay;
signed short az;
//陀螺仪
signed short gx;
signed short gy;
signed short gz;
//校准
signed short ax_l=0;
signed short ay_l=0;
signed short az_l=0;
signed short gx_l=0;
signed short gy_l=0;
signed short gz_l=0;
//编写I2C写入
void I2C_Write(unsigned char dev_addr,unsigned char mem_addr , unsigned char data)
{
HAL_I2C_Mem_Write(&hi2c1,dev_addr,mem_addr,I2C_MEMADD_SIZE_8BIT,&data,1,2);
}
//编写I2C读出
void I2C_Read(unsigned char dev_addr,unsigned char mem_addr , unsigned char *buf,unsigned char len)
{
HAL_I2C_Mem_Read(&hi2c1,dev_addr,mem_addr,I2C_MEMADD_SIZE_8BIT,buf,len,2);
}
//初始化MPU6050
void MPU_6050_Init(void)
{
unsigned char temp=0;
reset_MPU6050:
I2C_Write(MPU6050_Write_Addr,PowerMem_Register1_Addr,Set_PowerMen_Reset); //MPU复位
HAL_Delay(50);
I2C_Write(MPU6050_Write_Addr,PowerMem_Register1_Addr,Set_PowerMen_Start_XPPL); //MPU唤醒 使用X轴作为时钟源
I2C_Read(MPU6050_Write_Addr,MPU_Id_Addr,&temp,1); //读器件ID
if(temp != 0x68)
{
goto reset_MPU6050;
}
I2C_Write(MPU6050_Write_Addr,MPU_Gyro_Addr,(Set_Gyro_Range<<3)); //陀螺仪量程范围设置 2000
I2C_Write(MPU6050_Write_Addr,MPU_Accel_Addr,(Set_Accel_Range<<3)); //加速计量程范围设置 ±2G
Set_Mpu_Rate(50); //采样率分频
I2C_Write(MPU6050_Write_Addr,MPU_Int_Pin_Addr,Set_Int_Enable); //关闭所有中断
I2C_Write(MPU6050_Write_Addr,MPU_User_Addr,Set_User_I2c); //关闭I2C主模式
I2C_Write(MPU6050_Write_Addr,MPU_Fifo_En_Addr,Set_Fifo_Enable); //关闭FIFO
I2C_Write(MPU6050_Write_Addr,MPU_Int_Pin_Addr,Set_Int_Pin_L); //INT低电平有效
I2C_Write(MPU6050_Write_Addr,PowerMem_Register2_Addr,Sat_Start_Mpu); //MPU开始工作
}
//读取陀螺仪的数据
void Read_Mpu_Gyro(void)
{
I2C_Read(MPU6050_Write_Addr,GYRO_Xout_H,I2C_Gyro_Buf,6);
gx= (I2C_Gyro_Buf[0]<<8)+I2C_Gyro_Buf[1]-gx_l;
gy= (I2C_Gyro_Buf[2]<<8)+I2C_Gyro_Buf[3]-gy_l;
gz= (I2C_Gyro_Buf[4]<<8)+I2C_Gyro_Buf[5]-gz_l;
}
//读取加速度计的数据
void Read_Mpu_Accel(void)
{
I2C_Read(MPU6050_Write_Addr,ACCEL_Xout_H,I2C_Accel_Buf,6);
ax= (I2C_Accel_Buf[0]<<8)+I2C_Accel_Buf[1]-ax_l;
ay= (I2C_Accel_Buf[2]<<8)+I2C_Accel_Buf[3]-ay_l;
az= (I2C_Accel_Buf[4]<<8)+I2C_Accel_Buf[5]-az_l;
}
//设置采样频率
//返回值:0,设置成功
// 其他,设置失败
void Set_Mpu_Rate(uint16_t rata)
{
uint8_t data;
if(rata>1000)
rata=1000;
if(rata<4)
rata=4;
data= 1000 /rata-1;
I2C_Write(MPU6050_Write_Addr,MPU_Out_Fre_DIV_Addr,data); //采样率分频
MPU_Set_LPF(rata/2); //自动设置LPF为采样率的一半
}
//设置MPU6050的数字低通滤波器
//返回值:0,设置成功
// 其他,设置失败
void MPU_Set_LPF(uint16_t lpf)
{
uint8_t data=0;
if(lpf>=188)data=1;
else if(lpf>=98)data=2;
else if(lpf>=42)data=3;
else if(lpf>=20)data=4;
else if(lpf>=10)data=5;
else data=6;
I2C_Write(MPU6050_Write_Addr,MPU_Dlpf_Addr,data);
}
/*自校准模式
MPU6050校准函数
将IMU水平放置,z轴向上时,启动校准
思路是计算N个周期的平均值,得到校准参数
*/
#define CL_cnt 128
void MPU6050_calibrate(void)
{
unsigned short i;
signed int temp[6] = {0};
for(i=0; i<CL_cnt; i++)
{
HAL_Delay(10);
Read_Mpu_Gyro();
Read_Mpu_Accel();
temp[0] += ax;
temp[1] += ay;
temp[2] += az;
temp[3] += gx;
temp[4] += gy;
temp[5] += gz;
}
ax_l = temp[0]/CL_cnt;
ay_l = temp[1]/CL_cnt;
az_l = temp[2]/CL_cnt - (0xffff>>2); //平放时z轴有重力加速度g,减去g值
gx_l = temp[3]/CL_cnt;
gy_l = temp[4]/CL_cnt;
gz_l = temp[5]/CL_cnt;
}
Main函数
配置初始化和简易自校准,并且在while循环内写入读取陀螺仪于加速度计的函数,使用printf串口打印发送。(如果printf报错,多半是没有在main.c中加入头文件#include “stdio.h”)
MPU_6050_Init();
MPU6050_calibrate();
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("----------------------------------\r\n");
HAL_Delay(500);
Read_Mpu_Gyro();
Read_Mpu_Accel();
printf("ax:%1.3f g,\tgx:%f deg/s\r\n",(float)ax/(0xffff/4),(float)gx/(0xffff/500)); //输出加速度计和陀螺仪的初始数据
}
串口打印
在读取过程中,我摇晃了MPU_6050因此读到的数据有偏差。并且这个是最原始的数据,还未做任何处理。到这里已经完成了本章的所有功能。初级使用MPU-6050读到了原始数据。后续将会使用DMP姿态计算获得俯仰角,翻滚角和偏航角,更进一步姿态解算后使用卡尔曼滤波,令其解算出的角度更加准确。
使用过程中遇到的问题
在使用过程中遇到了极其奇葩的问题,有个型号的MPU-6050模块读到的器件地址不是0x68而是0x98。然后百度了一下发现还真有人遇到了这种神奇的问题,而且还是同一种模块。(有些人能读到数据,有些人读不到,也有些人不确定读到的对不对。。)但是没有任何资料更够解释这个现象。总之还是避免买下面这个模块吧。