2. MPU6050模块¶
2.1. MPU6050 简介¶
YH-MPU6050 是野火科技推出的六轴传感器模块,见图2-1,它采用 InvenSense 公司的 MPU6050 作为主芯片, 能同时检测三轴加速度、三轴陀螺仪(三轴角速度)的运动数据以及温度数据。利用 MPU6050 芯片内部的 DMP 模块(Digital Motion Processor 数字运动处理器), 可对传感器数据进行滤波、融合处理,直接通过 IIC 接口向主控器输出姿态解算后的数据,降低主控器的运算量。其姿态解算频率最高可达 200Hz, 非常适合用于对姿态控制实时要求较高的领域。常见应用于手机、智能手环、四轴飞行器、计步器等的姿态检测。
图2 - 1 MPU6050模块外观
2.2. MPU6050模块的引脚功能说明¶
该模块引出的8个引脚功能说明见下表,
序号 |
引脚名称 |
说明 |
---|---|---|
1 |
VCC |
3.3/5V电源输入 |
2 |
GND |
地线 |
3 |
SCL |
I2C从时钟信号线SCL (模块上已接上拉电阻) |
4 |
SDA |
I2C从数据信号线SDA (模块上已接上拉电阻) |
5 |
XDA |
I2C主串行数据信号线,用于外接传感器(模块上已接上拉电阻) |
6 |
XCL |
I2C主串行时钟信号线,用于外接传感器(模块上已接上拉电阻) |
7 |
AD0 |
从机地址设置引脚 :1.接地或悬空时, 地址为0x68;2.接VCC时,地址为0x69 |
8 |
INT |
中断输出引脚 |
其中的SDA/SCL、XDA/XCL通讯引脚分别为两组I2C信号线。当模块与外部主机通讯时,使用SDA/SCL,如与STM32芯片通讯; 而XDA/XCL则用于MPU6050芯片与其它I2C传感器通讯时使用,例如使用它与磁场传感器连接,MPU6050模块可以把从主机SDA/SCL接收的数据或命令通过XDA/XCL引脚转发到磁场传感器中。 但实际上这种功能比较鸡肋,控制麻烦且效率低,一般会直接把磁场传感器之类的I2C传感器直接与MPU6050挂载在同一条总线上(即都连接到SDA/SCL),使用主机直接控制。
2.3. 硬件原理图说明¶
MPU6050模块的硬件原理图见下图2 - 2,
图2 - 2 MPU6050模块原理图
它的硬件非常简单,SDA与SCL被引出方便与外部I2C主机连接,看图中的右上角,可知该模块的I2C通讯引脚SDA及SCL已经连接了上拉电阻, 因此它与外部I2C通讯主机通讯时直接使用导线连接起来即可;而MPU6050模块与其它传感器通讯使用的XDA、XCL引脚没有接上拉电阻,要使用时需要注意。 模块自身的I2C设备地址可通过AD0引脚的电平控制,当AD0接地时,设备地址为0x68(七位地址),当AD0接电源时,设备地址为0x69(七位地址)。 另外,当传感器有新数据的时候会通过INT引脚通知STM32。
由于MPU6050检测时是基于自身中心坐标系的,见图2 - 3,它表示的坐标系及旋转符号标出了MPU6050传感器的XYZ轴的加速度有角速度的正方向。所以在安装模块时,您需要考虑它与所在设备的坐标系统的关系。
图2 - 3 MPU6050传感器的坐标及方向
2.4. 连接方式¶
本模块配套STM32驱动程序,可直接使用野火F103霸道、F103指南者及F429挑战者开发板进行测试。按要求使用杜邦线把模块连接到开发板,并下载程序即可, 其中野火F429挑战者与霸道开发板板载了MPU6050芯片,不需要外接模块,此处直接列出其引脚关系供使用STM32F1的用户参考。
序号 |
引脚名称 |
与F103霸道、F103指南者、F429开发板连接 |
F103-MINI开发板 |
---|---|---|---|
1 |
VCC |
接3.3V或5V |
接3.3V或5V |
2 |
GND |
GND |
GND |
3 |
SCL |
PB6 |
PA2 (软件IIC) |
4 |
SDA |
PB7 |
PA3(软件IIC) |
5 |
AD0 |
悬空或接地 |
悬空或接地 |
6 |
INT |
悬空或接地 |
悬空或接地 |
2.5. 硬件STM32-MPU6050例程介绍及实验现象¶
硬件I2C,MPU6050基本驱动程序,不包含DMP功能,没有移植官方驱动程序。本程序通过串口输出简单测量数据,没有驱动液晶显示。(不支持匿名上位机可视数据)。
(使用硬件I2C时不能与液晶屏同时使用,因为FSMC的NADV与I2C1的SDA 是同一个引脚,互相影响了)
2.5.1. 实验现象¶
按照上面的连接方式将模块与开发板连接之后,通过USB线将开发板与电脑连接并上电,下载程序后打开电脑上的野火多功能调试助手, 打开与开发板连接的串口后会有MPU6050不断传来的数据信息,见下图2 - 4。 同时板子上的 LED 灯也会缓慢闪烁,每1秒钟翻转一次状态。
图2 - 4 MPU6050传感器的坐标及方向
2.5.2. 硬件STM32-MPU6050例程介绍¶
初始化硬件I2C
本实验中的I2C驱动与MPU6050驱动分开主要是考虑到扩展其它传感器时的通用性,如使用磁场传感器、气压传感器都可以使用同样一个I2C驱动, 这个驱动只要给出针对不同传感器时的不同读写接口即可。关于STM32的I2C驱动原理请参考野火STM32教程《零死角玩转STM32》中读写EEPROM的章节, 本章讲解的I2C驱动主要针对接口封装讲解,细节不再赘述。本实验中的I2C硬件定义在 bsp_i2c.h 中,具体如下,这些宏根据传感器使用的I2C硬件封装起来了。
1 2 3 4 5 6 7 8 9 10 | /**************************I2C参数定义,I2C1或I2C2********************************/
#define SENSORS_I2Cx I2C1
#define SENSORS_I2C_APBxClock_FUN RCC_APB1PeriphClockCmd
#define SENSORS_I2C_CLK RCC_APB1Periph_I2C1
#define SENSORS_I2C_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define SENSORS_I2C_GPIO_CLK RCC_APB2Periph_GPIOB
#define SENSORS_I2C_SCL_PORT GPIOB
#define SENSORS_I2C_SCL_PIN GPIO_Pin_6
#define SENSORS_I2C_SDA_PORT GPIOB
#define SENSORS_I2C_SDA_PIN GPIO_Pin_7
|
接下来利用这些宏对I2C进行初始化,初始化过程与I2C读写EEPROM中的无异,见如下,
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 | static void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 I2C1 有关的时钟 */
SENSORS_I2C_APBxClock_FUN ( SENSORS_I2C_CLK, ENABLE );
SENSORS_I2C_GPIO_APBxClock_FUN ( SENSORS_I2C_GPIO_CLK, ENABLE );
/* PB6-I2C1_SCL、PB7-I2C1_SDA*/
GPIO_InitStructure.GPIO_Pin = SENSORS_I2C_SCL_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(SENSORS_I2C_SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = SENSORS_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(SENSORS_I2C_SDA_PORT, &GPIO_InitStructure);
}
/**
* @brief I2C 工作模式配置
* @param 无
* @retval 无
*/
static void I2C_Mode_Configu(void)
{
I2C_InitTypeDef I2C_InitStructure;
/* I2C 配置 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
/* I2C的寻址模式 */
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
/* 通信速率 */
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
/* I2C1 初始化 */
I2C_Init(SENSORS_I2Cx, &I2C_InitStructure);
/* 使能 I2C1 */
I2C_Cmd(SENSORS_I2Cx, ENABLE);
}
|
MPU6050的寄存器定义
MPU6050有各种各样的寄存器用于控制工作模式,我们把这些寄存器的地址、寄存器位使用宏定义到了 mpu6050.h 文件中了,代码如下。
1 2 3 4 5 6 7 8 9 10 11 | //模块的A0引脚接GND,IIC的7位地址为0x68,若接到VCC,需要改为0x69
#define MPU6050_SLAVE_ADDRESS (0x68<<1) //MPU6050器件读地址
#define MPU6050_WHO_AM_I 0x75
#define MPU6050_SMPLRT_DIV 0 //8000Hz
#define MPU6050_DLPF_CFG 0
#define MPU6050_GYRO_OUT 0x43 //MPU6050陀螺仪数据寄存器地址
#define MPU6050_ACC_OUT 0x3B //MPU6050加速度数据寄存器地址
// ... 这里省略了很多MPU6050的寄存器宏定义
// ... ...
|
初始化MPU6050
根据MPU6050的寄存器功能定义,我们使用I2C往寄存器写入特定的控制参数,代码如下,
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 | /**
* @brief 写数据到MPU6050寄存器
* @param
* @retval
*/
void MPU6050_WriteReg(u8 reg_add,u8 reg_dat)
{
I2C_ByteWrite(reg_dat,reg_add);
}
/**
* @brief 从MPU6050寄存器读取数据
* @param
* @retval
*/
void MPU6050_ReadData(u8 reg_add,unsigned char* Read,u8 num)
{
I2C_BufferRead(Read,reg_add,num);
}
/**
* @brief 初始化MPU6050芯片
* @param
* @retval
*/
void MPU6050_Init(void)
{
int i=0,j=0;
//在初始化之前要延时一段时间,若没有延时,则断电后再上电数据可能会出错
for(i=0;i<1000;i++)
{
for(j=0;j<1000;j++)
{
;
}
}
MPU6050_WriteReg(MPU6050_RA_PWR_MGMT_1, 0x00); //解除休眠状态
MPU6050_WriteReg(MPU6050_RA_SMPLRT_DIV , 0x07); //陀螺仪采样率
MPU6050_WriteReg(MPU6050_RA_CONFIG , 0x06);
MPU6050_WriteReg(MPU6050_RA_ACCEL_CONFIG , 0x01); //配置加速度传感器工作在2G模式
MPU6050_WriteReg(MPU6050_RA_GYRO_CONFIG, 0x18); //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
}
|
这段代码首先使用 MPU6050_ReadData 及MPU6050_WriteReg 函数封装了I2C的底层读写驱动,接下来用它们在 MPU6050_Init 函数中向MPU6050寄存器写入控制参数,设置了MPU6050的采样率、量程(分辨率)等。
读传感器ID
初始化后,可通过读取它的“WHO AM I”寄存器内容来检测硬件是否正常,该寄存器存储了ID号0x68,见代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /**
* @brief 读取MPU6050的ID
* @param
* @retval 正常返回1,异常返回0
*/
uint8_t MPU6050ReadID(void)
{
unsigned char Re = 0;
MPU6050_ReadData(MPU6050_RA_WHO_AM_I,&Re,1); //读器件地址
if (Re != 0x68) {
MPU_ERROR("检测不到MPU6050模块,请检查模块与开发板的接线");
return 0;
} else {
MPU_INFO("MPU6050 ID = %d\r\n",Re);
return 1;
}
}
|
读取原始数据
若传感器检测正常,就可以读取它数据寄存器获取采样数据了,见代码如下。
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 | /**
* @brief 读取MPU6050的加速度数据
* @param
* @retval
*/
void MPU6050ReadAcc(short *accData)
{
u8 buf[6];
MPU6050_ReadData(MPU6050_ACC_OUT, buf, 6);
accData[0] = (buf[0] << 8) | buf[1];
accData[1] = (buf[2] << 8) | buf[3];
accData[2] = (buf[4] << 8) | buf[5];
}
/**
* @brief 读取MPU6050的角加速度数据
* @param
* @retval
*/
void MPU6050ReadGyro(short *gyroData)
{
u8 buf[6];
MPU6050_ReadData(MPU6050_GYRO_OUT,buf,6);
gyroData[0] = (buf[0] << 8) | buf[1];
gyroData[1] = (buf[2] << 8) | buf[3];
gyroData[2] = (buf[4] << 8) | buf[5];
}
/**
* @brief 读取MPU6050的原始温度数据
* @param
* @retval
*/
void MPU6050ReadTemp(short *tempData)
{
u8 buf[2];
MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2); //读取温度值
*tempData = (buf[0] << 8) | buf[1];
}
/**
* @brief 读取MPU6050的温度数据,转化成摄氏度
* @param
* @retval
*/
void MPU6050_ReturnTemp(float*Temperature)
{
short temp3;
u8 buf[2];
MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2); //读取温度值
temp3= (buf[0] << 8) | buf[1];
*Temperature=((double) (temp3 /340.0))+36.53;
}
|
其中以上前三个函数:MPU6050ReadAcc、MPU6050ReadGyro 和 MPU6050ReadTemp 分别用于读取三轴加速度、角速度及温度值,这些都是原始的ADC数值(16位长), 对于加速度和角速度,把读取得的ADC值除以分辨率,即可求得实际物理量数值。 最后一个函数 MPU6050_ReturnTemp 展示了温度ADC值与实际温度值间的转换,它是根据MPU6050的说明给出的转换公式进行换算的, 注意陀螺仪检测的温度会受自身芯片发热的影响,严格来说它测量的是自身芯片的温度,所以用它来测量气温是不太准确的。 对于加速度和角速度值我们没有进行转换,在下一小节中我们直接利用这些数据交给 DMP 单元,求解出姿态角。
main函数
最后我们来看看本实验的main函数:
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 | /**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
/* 串口通信初始化 */
USART_Config();
//I2C初始化
I2C_Bus_Init();
//MPU6050初始化
MPU6050_Init();
//检测MPU6050
if( MPU6050ReadID() == 0 )
{
printf("\r\n没有检测到MPU6050传感器!\r\n");
LED_RED;
while(1); //检测不到MPU6050 会红灯亮然后卡死
}
/* 配置SysTick定时器和中断 */
SysTick_Init(); //配置 SysTick 为 1ms 中断一次,在中断里读取传感器数据
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //启动定时器
while(1)
{
if( task_readdata_finish ) //task_readdata_finish = 1 表示读取MPU6050数据完成
{
printf("加速度:%8d%8d%8d",Acel[0],Acel[1],Acel[2]);
printf(" 陀螺仪%8d%8d%8d",Gyro[0],Gyro[1],Gyro[2]);
printf(" 温度%8.2f\r\n",Temp);
task_readdata_finish = 0; // 清零标志位
}
}
}
|
在main函数里,调用 I2C_Bus_Init、MPU6050_Init 及 MPU6050ReadID 函数后,在 while 主循环中使用串口打印加速度、角速度及温度值到电脑端。
本实验中控制MPU6050并没有使用中断引脚进行检测,我们是利用 SysTick 定时器进行计时,每隔一段时间就会读取 MPU6050 的数据寄存器获取采样数据,并且将 task_readdata_finish 标志位置 1, 该标志位置 1 后就可以在主循环中将MPU6050的加速度、角速度及温度值打印出来,再将 task_readdata_finish 重新置0,等待它再一次被置 1、然后再一次打印数据。
那么标志位 task_readdata_finish 是在哪里被置位的呢?答案是在 SysTick 定时器中断服务函数当中。 我们在进入主循环之前就已经配置SysTick定时器和中断、并且启动了它,使得它每 1ms 它都会产生一次中断,去执行中断服务函数。该中断服务函数如下。
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 | #define TASK_DELAY_NUM 2 //总任务个数,可以自己根据实际情况修改
#define TASK_DELAY_0 1000 //任务0延时 1000*1 毫秒后执行:翻转LED
#define TASK_DELAY_1 500 //任务1延时 500*1 毫秒后执行:MPU6050任务
uint32_t Task_Delay_Group[TASK_DELAY_NUM]; //任务数组,用来计时、并判断是否执行对应任务
/* 执行任务标志:读取MPU6050数据 */
// - 标志置 1表示读取MPU6050数据完成,需要在主循环处理MPU6050数据
// - 标志置 0表示未完成读取MPU6050数据,需要在中断中读取MPU6050数据
int task_readdata_finish;
void SysTick_Handler(void)
{
int i;
for(i=0; i<TASK_DELAY_NUM; i++)
{
Task_Delay_Group[i] ++; //任务计时,时间到后执行
}
/* 处理任务0 */
if(Task_Delay_Group[0] >= TASK_DELAY_0) //判断是否执行任务0
{
Task_Delay_Group[0] = 0; //置0重新计时
/* 任务0:翻转LED */
LED2_TOGGLE;
}
/* 处理任务1 */
if(Task_Delay_Group[1] >= TASK_DELAY_1) //判断是否执行任务1
{
Task_Delay_Group[1] = 0; //置0重新计时
/* 任务1:MPU6050任务 */
if( ! task_readdata_finish )
{
MPU6050ReadAcc(Acel);
MPU6050ReadGyro(Gyro);
MPU6050_ReturnTemp(&Temp);
task_readdata_finish = 1; //标志位置1,表示需要在主循环处理MPU6050数据
}
}
/* 处理任务2 */
//添加任务需要修改任务总数的宏定义 TASK_DELAY_NUM
//并且添加定义任务的执行周期宏定义 TASK_DELAY_x(x就是一个编号),比如 TASK_DELAY_2
}
|
SysTick中断服务函数的代码中,我们用for语句将变量 Task_Delay_Group[1] 自加1,使用 TASK_DELAY_1 变量来控制定时时间, 当 Task_Delay_Group[1] >= TASK_DELAY_1 时说明定时时间到了,就将 Task_Delay_Group[1] 重新置0,让它重新计时, 并且读取 MPU6050 的加速度、角速度及温度数据后将标志位 task_readdata_finish 置 1,表示数据读取完成, 然后在执行完中断服务函数之后,我们就回到主循环把读取到的数据打印出来。