STM32CubeMX教程19 I2C - MPU6050驱动
读者可访问 GitHub - lc-guo/STM32CubeMX-Series-Tutorial 获取原始工程代码
1、准备材料
STM32CubeMX软件(Version 6.10.0)
keil µVision5 IDE(MDK-Arm)
逻辑分析仪nanoDLA
2、实验目标
使用STM32CubeMX软件配置STM32F407开发板的I2C1与MPU6050芯片通信,读取MPU6050的三轴加速度和陀螺仪数据并通过串口打印出来
3、实验流程
3.0、前提知识
本实验重点是理解I2C通信协议,而STM32CubeMX的配置则相对简单,这里不会过于详细全面的介绍I2C通信协议,但是会对所有需要知道的知识做介绍
在我们的开发板上有一颗三轴加速度计和陀螺仪传感器MPU6050,单片机通过I2C1的PB8和PB9两个引脚与MPU6050进行通信,MPU6050还有一个中断引脚,这里为3D_INT引脚,但是本实验仅仅轮询读取加速度计和陀螺仪的数据,并没有用到该引脚中断功能,我们使用的开发板上的MPU6050芯片硬件原理图如下图所示
I2C通信仅需要时钟线SCLK和数据线SDA两根线就可以让主机与挂载在I2C上的从机进行通信和数据交换,一个I2C理论上最多可挂载127个设备(从机地址用7位二进制表示)
为了让主机准确的与众多从机中的一个进行通信,每个从机都会有一个地址,I2C通信时会在通信数据中先发送从机地址,然后对应地址的从机才会响应
而且I2C通信使用的时钟线SCLK和数据线SDA两根线必须要做上拉设置,因为I2C的两个引脚被配置为开漏输出,因此无法输出高电平,I2C总线连线图如下图所示 (注释1)
MPU6050的从机地址由芯片上的AD0引脚确定,当AD0引脚接地时,从机地址为0X68;
当AD0引脚接VCC时,从机地址为0X69;
根据上面MPU6050芯片硬件原理图可知此时MPU6050的从机地址为0X68
根据MPU6050 datasheet 9.3 I2C Communications Protocol 小节可知 (注释2),主机要通过I2C写入/读取MPU6050某一个寄存器一字节的数据,其通信步骤序列应该如下图所示
我们以读取内部寄存器单个字节数据为例做详细介绍,首先确定通信的目的为主机Master从从机Slave内部某个寄存器internal register中读取一个字节数据,以下为详细通信步骤
- 主机时钟线SCLK和数据线SDA两根线产生起始信号(当 SCL 线是高电平时 SDA 线从高电平向低电平切换)
- 接下来时钟线SCLK的8个时钟节拍中,由数据线SDA发送一字节8位的数据,其中高7位为从机的地址,最后一位为0(1表示读操作,0表示写操作)
- 接下来1个时钟节拍,从机应该产生应答信号ACK
- 接下来8个时钟节拍,主机发送一字节8位的数据,该数据为要读取的从机内部寄存器地址
- 接下来1个时钟节拍,从机应该产生应答信号ACK
- 主机重新产生一个起始信号
- 接下来8个时钟节拍,重新发送一字节8位的数据,其中高7位为从机的地址,最后一位为1,表示这次要读
- 接下来1个时钟节拍,从机应该产生应答信号ACK
- 接下来8个时钟节拍,主机读取一字节8位数据
- 接下来1个时钟节拍,主机应该产生应答信号NACK
- 主机时钟线SCLK和数据线SDA两根线产生停止信号(当 SCL 线是高电平时 SDA 线由低电平向高电平切换)
接下来我们用逻辑分析仪捕获下主机使用I2C1读取MPU6050寄存器WHO_AM_I(0X75)时,时钟线SCLK和数据线SDA的逻辑电平变化,如下图所示,从图上可知I2C读取读取MPU6050内部寄存器的时序与上面我们所描述的一致
用逻辑分析仪捕获主机使用I2C1向MPU6050寄存器PWR_MGMT_1(0X6B)写入一字节0X00数据时,时钟线SCLK和数据线SDA的逻辑电平变化,如下图所示
为什么上图中从机地址从0X68变为0XD0了?
HAL库中的I2C写入函数HAL_I2C_Mem_Write()和读取函数HAL_I2C_Mem_Read()对传入从机地址DevAddress参数做了要求,该地址必须将数据手册中提到的地址左移才可以调用该接口
0X68(0110 1000)向左移动直到遇到1即为0XD0(1101 0000),在I2C通信中使用上述两个API,从机地址传入0XD0表示对从机地址为0X68的从机进行写操作,传入0XD1表示对从机地址为0X68的从机进行读操作
如下图为HAL库HAL_I2C_Mem_Read()函数说明
上面提到,当启用I2C之后,其I2C_SCL和I2C_SDA两个引脚被配置为了复用功能开漏输出,而开漏输出无法输出高电平,因此I2C_SCL和I2C_SDA两个引脚需要外部上拉,一般的开发板都会考虑到这一点,在设计原理图的时候将使用的I2C两根线给外部上拉到3.3V,如果你使用的是自己设计的板子,请务必记住I2C需要上拉
细心的同学可能又发现了,你上面给出的MPU6050硬件原理图I2C两根线并没有外部上拉呀?
虽然MPU6050硬件原理图I2C两根线没有上拉,但是在开发板的其他I2C通信的芯片上进行了外部上拉,如下图所示
3.1、CubeMX相关配置
3.1.0、工程基本配置
打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示
开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示
详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”
3.1.1、时钟树配置
系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示
3.1.2、外设参数配置
实验需要通过串口来输出与MPU6050进行I2C通信读取的陀螺仪和加速度计数据,因此外设需要初始化USART1和I2C1
单击Pinout & Configuration页面左边Connectivity/USART1选项,然后按照“STM32CubeMX教程9 USART/UART 异步通信”实验中将USART1配置为异步通信模式,无需开启中断,如下图所示
在Pinout & Configuration页面右边芯片引脚预览Pinout view中找到与开发板上MPU6050芯片连接的I2C的两个通信引脚PB8和PB9,左边单击将其分别配置为I2C1_SCL和I2C1_SDA
然后单击Connectivity/I2C1选项,在其模式中选择I2C,在下方Master Features中将 I2C Speed Mode 根据用户需求选择快速模式(400kkHz)或者标准模式(100kHz),这两种模式仅仅影响通信速率,对于本实验两个模式均可随意选择
其他参数保持默认即可,具体配置如下图所示
3.1.3、外设中断配置
本实验无需启用中断,如果需要启用I2C1的中断,请单击System Core/NVIC,然后根据需求勾选I2C1的事件或错误中断,并选择合适的中断优先级即可,具体配置如下图所示
3.2、生成代码
3.2.0、配置Project Manager页面
单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示
详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节
3.2.1、外设初始化调用流程
还是一模一样的流程,与该系列教程所有的外设初始化一致
在生成的工程代码主函数中新增了MX_I2C1_Init()函数,在该函数中实现了对I2C1的模式及参数配置
在MX_I2C1_Init()函数中调用了HAL_I2C_Init()函数使用配置的参数对I2C1进行了初始化
在HAL_I2C_Init()函数中又调用了HAL_I2C_MspInit()函数对I2C1引脚复用设置,I2C1时钟使能,如果开启了中断该函数中还会有中断相关设置及使能
如下图所示为I2C1初始化调用流程
3.2.2、外设中断调用流程
本实验无需中断,因此未启动任何I2C1的中断
3.2.3、添加其他必要代码
需要添加MPU6050的驱动文件,文件中至少应该包括
- MPU6050初始化函数
- MPU6050获取三轴加速度计原始数据函数
- MPU6050获取三轴陀螺仪原始数据函数
注意本实验只使用而不会介绍MPU6050具体驱动文件的原理,具体源代码如下所示 (注释3)
mpu6050.c文件
#include "mpu6050.h"
/**
* @brief MPU6050初始化函数
* @alter 无
* @param 无
* @retval 成功返回0,失败返回1
*/
uint8_t MPU6050_Init(I2C_HandleTypeDef *I2Cx)
{
uint8_t check;
uint8_t Data;
// check device ID WHO_AM_I
HAL_I2C_Mem_Read(I2Cx, MPU6050_ADDR, MPU_DEVICE_ID_REG, 1, &check, 1, I2C_TimeOut);
// 0x68 will be returned by the sensor if everything goes well
if (check == 104)
{
// power management register 0X6B we should write all 0's to wake the sensor up
Data = 0;
HAL_I2C_Mem_Write(I2Cx, MPU6050_ADDR, MPU_PWR_MGMT1_REG, 1, &Data, 1, I2C_TimeOut);
// Set DATA RATE of 1KHz by writing SMPLRT_DIV register
Data = 0x07;
HAL_I2C_Mem_Write(I2Cx, MPU6050_ADDR, MPU_SAMPLE_RATE_REG, 1, &Data, 1, I2C_TimeOut);
// Set accelerometer configuration in ACCEL_CONFIG Register
// XA_ST=0,YA_ST=0,ZA_ST=0, FS_SEL=0 -> 2g
Data = 0x00;
HAL_I2C_Mem_Write(I2Cx, MPU6050_ADDR, MPU_ACCEL_CFG_REG, 1, &Data, 1, I2C_TimeOut);
// Set Gyroscopic configuration in GYRO_CONFIG Register
// XG_ST=0,YG_ST=0,ZG_ST=0, FS_SEL=0 -> 250 /s
Data = 0x00;
HAL_I2C_Mem_Write(I2Cx, MPU6050_ADDR, MPU_GYRO_CFG_REG, 1, &Data, 1, I2C_TimeOut);
return 0;
}
return 1;
}
/**
* @brief MPU6050温度值获取函数
* @alter 无
* @param 无
* @retval 温度值
*/
float MPU_Get_Temperature(void)
{
uint8_t buf[2];
short raw;
float temp;
HAL_I2C_Mem_Read(&MPU6050_I2C, MPU6050_ADDR, MPU_TEMP_OUTH_REG, 1, buf, 2, I2C_TimeOut);
raw=((int16_t)buf[0]<<8)|buf[1];
temp=36.53+((double)raw)/340;
return temp;;
}
/**
* @brief MPU6050陀螺仪值获取函数(三轴原始值)
* @alter 无
* @param gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
* @retval 正常:0,错误:其他
*/
uint8_t MPU_Get_RAW_Gyroscope(int16_t *gx,int16_t *gy,int16_t *gz)
{
uint8_t buf[6],res;
res = HAL_I2C_Mem_Read(&MPU6050_I2C, MPU6050_ADDR, MPU_GYRO_XOUTH_REG, 1, buf, 6, I2C_TimeOut);
if(res==0)
{
*gx=((int16_t)buf[0]<<8)|buf[1];
*gy=((int16_t)buf[2]<<8)|buf[3];
*gz=((int16_t)buf[4]<<8)|buf[5];
}
return res;
}
/**
* @brief MPU6050加速度值获取函数(三轴原始值)
* @alter 无
* @param ax,ay,az:加速度计x,y,z轴的原始读数(带符号)
* @retval 正常:0,错误:其他
*/
uint8_t MPU_Get_RAW_Accelerometer(int16_t *ax,int16_t *ay,int16_t *az)
{
uint8_t buf[6],res;
res = HAL_I2C_Mem_Read(&MPU6050_I2C, MPU6050_ADDR, MPU_ACCEL_XOUTH_REG, 1, buf, 6, I2C_TimeOut);
if(res==0)
{
*ax=((int16_t)buf[0]<<8)|buf[1];
*ay=((int16_t)buf[2]<<8)|buf[3];
*az=((int16_t)buf[4]<<8)|buf[5];
}
return res;
}
mpu6050.h文件
#ifndef __MPU6050_H
#define __MPU6050_H
#include "main.h"
#include "i2c.h"
#endif
#define MPU6050_I2C hi2c1
#define MPU6050_ADDR 0xD0
#define I2C_TimeOut 100
/*MPU6050内部寄存器地址*/
#define MPU_SAMPLE_RATE_REG 0X19 //采样频率分频器
#define MPU_GYRO_CFG_REG 0X1B //陀螺仪配置寄存器
#define MPU_ACCEL_CFG_REG 0X1C //加速度计配置寄存器
#define MPU_ACCEL_XOUTH_REG 0X3B //加速度值,X轴高8位寄存器
#define MPU_TEMP_OUTH_REG 0X41 //温度值高八位寄存器
#define MPU_GYRO_XOUTH_REG 0X43 //陀螺仪值,X轴高8位寄存器
#define MPU_PWR_MGMT1_REG 0X6B //电源管理寄存器1
#define MPU_DEVICE_ID_REG 0X75 //器件ID寄存器
uint8_t MPU6050_Init(I2C_HandleTypeDef *I2Cx);
float MPU_Get_Temperature(void);
uint8_t MPU_Get_RAW_Gyroscope(int16_t *gx,int16_t *gy,int16_t *gz);
uint8_t MPU_Get_RAW_Accelerometer(int16_t *ax,int16_t *ay,int16_t *az);
/*
MPU6050内部所有寄存器地址
#define MPU_SELF_TESTX_REG 0X0D //自检寄存器X
#define MPU_SELF_TESTY_REG 0X0E //自检寄存器Y
#define MPU_SELF_TESTZ_REG 0X0F //自检寄存器Z
#define MPU_SELF_TESTA_REG 0X10 //自检寄存器A
#define MPU_SAMPLE_RATE_REG 0X19 //采样频率分频器
#define MPU_CFG_REG 0X1A //配置寄存器
#define MPU_GYRO_CFG_REG 0X1B //陀螺仪配置寄存器
#define MPU_ACCEL_CFG_REG 0X1C //加速度计配置寄存器
#define MPU_MOTION_DET_REG 0X1F //运动检测阀值设置寄存器
#define MPU_FIFO_EN_REG 0X23 //FIFO使能寄存器
#define MPU_I2CMST_CTRL_REG 0X24 //IIC主机控制寄存器
#define MPU_I2CSLV0_ADDR_REG 0X25 //IIC从机0器件地址寄存器
#define MPU_I2CSLV0_REG 0X26 //IIC从机0数据地址寄存器
#define MPU_I2CSLV0_CTRL_REG 0X27 //IIC从机0控制寄存器
#define MPU_I2CSLV1_ADDR_REG 0X28 //IIC从机1器件地址寄存器
#define MPU_I2CSLV1_REG 0X29 //IIC从机1数据地址寄存器
#define MPU_I2CSLV1_CTRL_REG 0X2A //IIC从机1控制寄存器
#define MPU_I2CSLV2_ADDR_REG 0X2B //IIC从机2器件地址寄存器
#define MPU_I2CSLV2_REG 0X2C //IIC从机2数据地址寄存器
#define MPU_I2CSLV2_CTRL_REG 0X2D //IIC从机2控制寄存器
#define MPU_I2CSLV3_ADDR_REG 0X2E //IIC从机3器件地址寄存器
#define MPU_I2CSLV3_REG 0X2F //IIC从机3数据地址寄存器
#define MPU_I2CSLV3_CTRL_REG 0X30 //IIC从机3控制寄存器
#define MPU_I2CSLV4_ADDR_REG 0X31 //IIC从机4器件地址寄存器
#define MPU_I2CSLV4_REG 0X32 //IIC从机4数据地址寄存器
#define MPU_I2CSLV4_DO_REG 0X33 //IIC从机4写数据寄存器
#define MPU_I2CSLV4_CTRL_REG 0X34 //IIC从机4控制寄存器
#define MPU_I2CSLV4_DI_REG 0X35 //IIC从机4读数据寄存器
#define MPU_I2CMST_STA_REG 0X36 //IIC主机状态寄存器
#define MPU_INTBP_CFG_REG 0X37 //中断/旁路设置寄存器
#define MPU_INT_EN_REG 0X38 //中断使能寄存器
#define MPU_INT_STA_REG 0X3A //中断状态寄存器
#define MPU_ACCEL_XOUTH_REG 0X3B //加速度值,X轴高8位寄存器
#define MPU_ACCEL_XOUTL_REG 0X3C //加速度值,X轴低8位寄存器
#define MPU_ACCEL_YOUTH_REG 0X3D //加速度值,Y轴高8位寄存器
#define MPU_ACCEL_YOUTL_REG 0X3E //加速度值,Y轴低8位寄存器
#define MPU_ACCEL_ZOUTH_REG 0X3F //加速度值,Z轴高8位寄存器
#define MPU_ACCEL_ZOUTL_REG 0X40 //加速度值,Z轴低8位寄存器
#define MPU_TEMP_OUTH_REG 0X41 //温度值高八位寄存器
#define MPU_TEMP_OUTL_REG 0X42 //温度值低8位寄存器
#define MPU_GYRO_XOUTH_REG 0X43 //陀螺仪值,X轴高8位寄存器
#define MPU_GYRO_XOUTL_REG 0X44 //陀螺仪值,X轴低8位寄存器
#define MPU_GYRO_YOUTH_REG 0X45 //陀螺仪值,Y轴高8位寄存器
#define MPU_GYRO_YOUTL_REG 0X46 //陀螺仪值,Y轴低8位寄存器
#define MPU_GYRO_ZOUTH_REG 0X47 //陀螺仪值,Z轴高8位寄存器
#define MPU_GYRO_ZOUTL_REG 0X48 //陀螺仪值,Z轴低8位寄存器
#define MPU_I2CSLV0_DO_REG 0X63 //IIC从机0数据寄存器
#define MPU_I2CSLV1_DO_REG 0X64 //IIC从机1数据寄存器
#define MPU_I2CSLV2_DO_REG 0X65 //IIC从机2数据寄存器
#define MPU_I2CSLV3_DO_REG 0X66 //IIC从机3数据寄存器
#define MPU_I2CMST_DELAY_REG 0X67 //IIC主机延时管理寄存器
#define MPU_SIGPATH_RST_REG 0X68 //信号通道复位寄存器
#define MPU_MDETECT_CTRL_REG 0X69 //运动检测控制寄存器
#define MPU_USER_CTRL_REG 0X6A //用户控制寄存器
#define MPU_PWR_MGMT1_REG 0X6B //电源管理寄存器1
#define MPU_PWR_MGMT2_REG 0X6C //电源管理寄存器2
#define MPU_FIFO_CNTH_REG 0X72 //FIFO计数寄存器高八位
#define MPU_FIFO_CNTL_REG 0X73 //FIFO计数寄存器低八位
#define MPU_FIFO_RW_REG 0X74 //FIFO读写寄存器
#define MPU_DEVICE_ID_REG 0X75 //器件ID寄存器
*/
在keil中添加.c/.h文件步骤如下图所示
然后将源代码复制到新建的.c/.h文件中,最后将.c文件添加到工程即可,如下图所示
在主函数中初始化MPU6050,主循环中获取原始的三轴加速度数据和三轴陀螺仪数据,并通过串口打印信息
源代码如下
/*main.c中定义的变量*/
int16_t ax,ay,az,gx,gy,gz;
/*初始化代码*/
while(MPU6050_Init(&hi2c1) != HAL_OK){HAL_Delay(1);}
/*主循环中代码*/
MPU_Get_RAW_Gyroscope(&ax,&ay,&az);
MPU_Get_RAW_Accelerometer(&gx,&gy,&gz);
printf("ax:%d,ay:%d,az:%d,",ax,ay,az);
printf("gx:%d,gy:%d,gz:%d\r\n",gx,gy,gz);
HAL_Delay(100);
4、常用函数
/*I2C读函数*/
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
/*I2C写函数*/
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
5、烧录验证
烧录程序,上电后开启串口,可以看到源源不断的输出当前MPU6050三轴加速度数据和三轴陀螺仪数据,移动开发板会发现数据有规律改变,如下图所示为串口输出的数据
6、注释详解
注释1:图片来源 I2C通信协议介绍 - 知乎
注释2:MPU6050 Datasheet https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf
注释3:MPU6050驱动库参考 https://github.com/leech001/MPU6050