正点原子stm32f407探索者开发板V2.4
STM32CubeMX软件(Version 6.10.0)
野火DAP仿真器
keil µVision5 IDE(MDK-Arm)
ST-LINK/V2驱动
XCOM V2.6串口助手
逻辑分析仪nanoDLA
使用STM32CubeMX软件配置STM32F407开发板的I2C1与MPU6050芯片通信,读取MPU6050的三轴加速度和陀螺仪数据并通过串口打印出来
本实验重点是理解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中读取一个字节数据,以下为详细通信步骤
接下来我们用逻辑分析仪捕获下主机使用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通信的芯片上进行了外部上拉,如下图所示
打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示
开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示
详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”
系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示
实验需要通过串口来输出与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),这两种模式仅仅影响通信速率,对于本实验两个模式均可随意选择
其他参数保持默认即可,具体配置如下图所示
本实验无需启用中断,如果需要启用I2C1的中断,请单击System Core/NVIC,然后根据需求勾选I2C1的事件或错误中断,并选择合适的中断优先级即可,具体配置如下图所示
单击进入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小节
还是一模一样的流程,与该系列教程所有的外设初始化一致
在生成的工程代码主函数中新增了MX_I2C1_Init()函数,在该函数中实现了对I2C1的模式及参数配置
在MX_I2C1_Init()函数中调用了HAL_I2C_Init()函数使用配置的参数对I2C1进行了初始化
在HAL_I2C_Init()函数中又调用了HAL_I2C_MspInit()函数对I2C1引脚复用设置,I2C1时钟使能,如果开启了中断该函数中还会有中断相关设置及使能
如下图所示为I2C1初始化调用流程
本实验无需中断,因此未启动任何I2C1的中断
需要添加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);
/*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)
烧录程序,上电后开启串口,可以看到源源不断的输出当前MPU6050三轴加速度数据和三轴陀螺仪数据,移动开发板会发现数据有规律改变,如下图所示为串口输出的数据
注释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
更多内容请浏览 STM32CubeMX+STM32F4系列教程文章汇总贴