目录
一、stm32中I2C库函数介绍(stm32f10x_i2c.h)
前言
上一期讲了硬件I2C的相关基本内容(链接stm32入门-----硬件I2C外设-CSDN博客),本期我们就来学习硬件I2C读写MPU6050的项目实操,在前面我们试过了用软件I2C来去读写MPU6050的操作,这里我们可以去对比看看硬件跟软件操作上有什么不同以及硬件上又有哪方面比软件要强。(视频:STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili)
本期项目代码我已上传至百度网盘,可自行下载
链接:https://pan.baidu.com/s/1W_2qnkWsUkOBzRjhkJhRVg?pwd=0721
提取码:0721
一、stm32中I2C库函数介绍(stm32f10x_i2c.h)
下面这些是关于I2C的库函数,这里我会挑选最常见的以及本期要用到的来去进行详细讲解。
1.初始化
在学过前面这么多结构体初始化的函数,对于结构体的初始化我们已经再属性不过了,下面是I2C外设的结构体初始化的函数。
void I2C_DeInit(I2C_TypeDef* I2Cx);//恢复缺省配置
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);//定义好结构体后初始化
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);//使用默认结构体初始化
一般情况我们都是自己去定义配置结构体再初始化的,下面我们看一个定义好结构体初始化的示例:
I2C_InitTypeDef I2C_initstruct;
I2C_initstruct.I2C_Mode = I2C_Mode_I2C; //选择模式,这里选定使用I2C模式
I2C_initstruct.I2C_ClockSpeed = 100000; //选择时钟的频率,不得大于400kHz
I2C_initstruct.I2C_DutyCycle = I2C_DutyCycle_2;//占空比选择,低电平:高电平,如果当前是标准速度的话,这个是没用的的,如果在快速时钟的时候,这个才会起作用
I2C_initstruct.I2C_Ack = I2C_Ack_Enable; //选择是否给应答
I2C_initstruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//stm32作为从机的时候的响应地址位数
I2C_initstruct.I2C_OwnAddress1 = 0x01; //stm32作为从机的时候地址,我们这里是主机,所以这里随便设一个就行了
I2C_Init(I2C2, &I2C_initstruct);
2.使能操作
我们本期不需要用DMA到使能,所以这里就不过多讲解。
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);//I2C使能
void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState); //开启I2C的DMA使能
void I2C_DMALastTransferCmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
3.生成起始位和结束位标志
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);//产生起始位标志
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);//产生结束位标志
手册解释如下:
4.发送I2C从机地址
下面这个函数其实就是前面学习软件I2C的“点名”操作,硬件也是一样的,同样都是需要进行点名指定的I2C从机来进行通讯。如果有多个从机,主机会执行下面这个函数后会给每一个从机发送Address的数据,然后从机接收到了后就对比自己的IP数据,如果一样的话那么就开始跟主机通讯,其他从机保持沉默。
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
5.发送数据和接收数据
下面这个是发送数据:
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
下面可以看到把数据放入到数据寄存器DR的操作后,然后剩下就是进入到发送数据的流程。
下面这个是接受数据:
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
我们可以看到这个函数的返回值是DR寄存器里面的数据。
6.发送应答位
下面这个函数是用来发送应答位的,当主机接收到了数据之后会向从机发送应答情况,如果应答位是ENABLE表示接收完成返回一个应答反之就是无应答返回 ,手册解释如下
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState)
7.状态检测
下面这个函数是用来检测每次操作一个步骤之后的状态的,比如发送了一个字节后的数据,然后就会进入一个EVx的状态,这里就需要去执行这个状态是否完成,然后再执行下一步,这是一种缓冲机制以保证数据收发的准确性。在上一期我们也是详细讲解过了这个过程了的。
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
下面是这个函数的相关参数:
二、硬件I2C读取MPU6050
实验现象如下,其实跟前面软件I2C读取MPU6050现象是一样的,只是方法不同。(软件I2C读取MPU6050链接:stm32入门-----软件I2C读写MPU6050-CSDN博客)
1.电路连线图
2.主要工程文件
主要工程文件如下,这里我们就没有像软件那部分一样需要建立一个MyI2C的文件来去执行收发数据的底层,这里我们是使用硬件外设配置库函数来去实现I2C通讯的,底层是由硬件来自动执行的,所以就不需要MyI2C这个文件了。
其中MPU6050.c文件是用来配置I2C外设以及MPU6050相关模块的文件,MPU6050_reg.h文件是存放MPU6050相关寄存器数据的文件。
MPU6050_reg.h文件数据如下:
#ifndef __MPU6050_REG_h
#define __MPU6050_REG_h
//这里存放的是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
#endif // !__MPU6050_REG_h
这里我们主要去讲解MPU6050.c这个文件里面的内容,这个是配置I2C外设的主要文件。
3.MPU6050.c代码剖析
(1)检测步骤超时操作
在进入到检测状态的函数的时候,难免会出现意外超时的情况,这里我们就需要去对这个bug进行处理,比如如果超时的情况就放弃这个字节读取或写入的操作。
//检测事件后的超时操作
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT) {
uint32_t time_out = 100000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) {
time_out--;
if (!time_out) {
break;
}
}
}
(2)指定地址写
代码是对应下面这个序列图进行写的,序列图跟代码的步骤是一样的,我们这里使用的是7位。
//指定地址写
void MPU6050_writereg(uint8_t regaddress, uint8_t data) {
//开启I2C发送模式
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件,返回值有SUCCESS和ERROR
//发送地址,点名
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//I2C_SendData()函数也是用来发送的,不过I2C_Send7bitAddress这个可以选择7位的发送和接收地址位两种模式
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件,返回值有SUCCESS和ERROR
//选择寄存器地址
I2C_SendData(I2C2, regaddress); //选择从机的寄存器地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//监测EV8事件,返回值有SUCCESS和ERROR
//发送数据
I2C_SendData(I2C2, data); //选择从机的寄存器地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//监测EV8_2事件表示结束,返回值有SUCCESS和ERROR
I2C_GenerateSTOP(I2C2, ENABLE);
}
(3)指定地址读
//指定地址读
uint8_t MPU6050_readreg(uint8_t regaddress) {
uint8_t result;
//这里指定地址
//开启I2C发送模式
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件,返回值有SUCCESS和ERROR
//选择发送地址,点名
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//I2C_Send7bitAddress这个可以选择7位的发送和接收地址位两种模式,选择方向和从机地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//监测EV6事件,返回值有SUCCESS和ERROR
//选择寄存器地址
I2C_SendData(I2C2, regaddress); //选择从机的寄存器地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//监测EV8事件,返回值有SUCCESS和ERROR
//这里就开始读取这个寄存器的数据
//重新开启
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件,返回值有SUCCESS和ERROR
//发送地址,点名
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//这里选择接收的方向
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//监测EV6事件,返回值有SUCCESS和ERROR
//如果是读取多个字节的时候,那读取最后一个字节之前在这里需要去先去给应答置0,提前终止接收stop置1
//如果读取一个字节的时候,那么要在EV6之后在这里应答ack置0,stop置1
//这里提前设置的终止stop的时候不会影响数据的收到,回等待最后一个字节数据接收完成之后才去执行终止
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//监测EV6事件,返回值有SUCCESS和ERROR
//读取DR
result = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2, ENABLE);
return result;
}
(4)初始化配置
//初始化
void MPU6050_init() {
// I2C_init();
//1.开启时钟,GPIO和I2C的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 2.初始化引脚
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_OD; //选择使用复用开漏输出模式
GPIO_initstruct.GPIO_Pin=GPIO_Pin_11 | GPIO_Pin_10;
GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_initstruct);
// 3.配置初始化I2C外设
I2C_InitTypeDef I2C_initstruct;
I2C_initstruct.I2C_Mode = I2C_Mode_I2C; //选择模式,这里选定使用I2C模式
I2C_initstruct.I2C_ClockSpeed = 100000; //选择时钟的频率,不得大于400kHz
I2C_initstruct.I2C_DutyCycle = I2C_DutyCycle_2;//占空比选择,低电平:高电平,如果当前是标准速度的话,这个是没用的的,如果在快速时钟的时候,这个才会起作用
I2C_initstruct.I2C_Ack = I2C_Ack_Enable; //选择是否给应答
I2C_initstruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//stm32作为从机的时候的响应地址位数
I2C_initstruct.I2C_OwnAddress1 = 0x01; //stm32作为从机的时候地址,我们这里是主机,所以这里随便设一个就行了
I2C_Init(I2C2, &I2C_initstruct);
MPU6050_writereg(MPU6050_PWR_MGMT_1, 0x01); //电源管理器1
MPU6050_writereg(MPU6050_PWR_MGMT_2, 0x00); //电源管理器2
MPU6050_writereg(MPU6050_SMPLRT_DIV, 0x09); //分频器
MPU6050_writereg(MPU6050_CONFIG, 0x06); //配置寄存器
MPU6050_writereg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器
MPU6050_writereg(MPU6050_ACCEL_CONFIG, 0x18);//加速度计配置寄存器
}
(5)获取MPU6050寄存器数据
//获取当前的ID号,也就是MPU6050的I2C地址
uint8_t MPU6050_getID() {
return MPU6050_readreg(MPU6050_WHO_AM_I);
}
//获取MPU6050转换出加速度和角速度的值
void MPU6050_getdata(int16_t* accX, int16_t* accY, int16_t* accZ,
int16_t* gyroX, int16_t* gyroY, int16_t* gyroZ)
{
uint16_t dataH, dataL;
//加速度值数据
dataH = MPU6050_readreg(MPU6050_ACCEL_XOUT_H);
dataL = MPU6050_readreg(MPU6050_ACCEL_XOUT_L);
*accX = (dataH << 8) | dataL;
dataH = MPU6050_readreg(MPU6050_ACCEL_YOUT_H);
dataL = MPU6050_readreg(MPU6050_ACCEL_YOUT_L);
*accY = (dataH << 8) | dataL;
dataH = MPU6050_readreg(MPU6050_ACCEL_ZOUT_H);
dataL = MPU6050_readreg(MPU6050_ACCEL_ZOUT_L);
*accZ = (dataH << 8) | dataL;
//陀螺仪数据
dataH = MPU6050_readreg(MPU6050_GYRO_XOUT_H);
dataL = MPU6050_readreg(MPU6050_GYRO_XOUT_L);
*gyroX = (dataH << 8) | dataL;
dataH = MPU6050_readreg(MPU6050_GYRO_YOUT_H);
dataL = MPU6050_readreg(MPU6050_GYRO_YOUT_L);
*gyroY = (dataH << 8) | dataL;
dataH = MPU6050_readreg(MPU6050_GYRO_ZOUT_H);
dataL = MPU6050_readreg(MPU6050_GYRO_ZOUT_L);
*gyroZ = (dataH << 8) | dataL;
}
4.主函数代码
这部分其实跟软件I2C读写MPU6050的是一样的。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t ID;
int16_t aX, aY, aZ, gX, gY, gZ;
int main(void)
{
OLED_Init();
MPU6050_init();
OLED_ShowString(1, 1, "ID:");
ID = MPU6050_getID();
OLED_ShowHexNum(1, 4, ID, 2);
while (1) {
MPU6050_getdata(&aX, &aY, &aZ, &gX, &gY, &gZ);
OLED_ShowSignedNum(2, 1, aX, 5);
OLED_ShowSignedNum(3, 1, aY, 5);
OLED_ShowSignedNum(4, 1, aZ, 5);
OLED_ShowSignedNum(2, 8, gX, 5);
OLED_ShowSignedNum(3, 8, gY, 5);
OLED_ShowSignedNum(4, 8, gZ, 5);
}
}
以上就是本期的全部内容了,我们下次见!
今日壁纸: