stm32入门-----硬件I2C读写MPU6050

 目录

前言

 一、stm32中I2C库函数介绍(stm32f10x_i2c.h)

1.初始化

2.使能操作

3.生成起始位和结束位标志

4.发送I2C从机地址

5.发送数据和接收数据

6.发送应答位

7.状态检测

二、硬件I2C读取MPU6050

1.电路连线图

2.主要工程文件 

3.MPU6050.c代码剖析

(1)检测步骤超时操作

(2)指定地址写

(3)指定地址读

(4)初始化配置

(5)获取MPU6050寄存器数据

4.主函数代码


前言

        上一期讲了硬件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);
	}
}

以上就是本期的全部内容了,我们下次见!

今日壁纸:

  • 34
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: STM32F407是一款强大的微控制器,可以通过软件模拟I2C总线来读写MPU6050传感器。首先,我们需要设置相关的GPIO引脚来模拟I2C的时钟(SCL)和数据线(SDA)。然后,我们可以通过相应的软件算法来模拟I2C的时序,并使用GPIO引脚来模拟信号的传输。 在进行软件模拟I2C读写MPU6050之前,我们需要先了解MPU6050的寄存器结构和通讯协议。MPU6050内部有多个寄存器,它们存储着各种传感器的原始数据和配置信息。通讯协议使用I2C,我们需要根据MPU6050的地址和寄存器地址来发送读写命令。 首先,发送开始信号。通过GPIO引脚模拟SCL和SDA,在SCL为高电平,SDA从高电平转为低电平时即可认为发送了开始信号。然后,发送MPU6050的设备地址,该地址为7位,由3位固定的值和一个可选择的读写位组成。接下来,发送要访问的寄存器地址,该地址也为7位。再发送读写命令位,0表示写入,1表示读取。 接下来,我们可以进行读写操作。对于写操作,我们发送要写入的数据到寄存器中。对于读操作,我们需要发送重复启动信号,然后再发送MPU6050的设备地址和寄存器地址,并设置读取命令位。此时,我们将把数据读取到SDA引脚上。 最后,发送停止信号。通过GPIO引脚模拟SCL和SDA,在SCL为高电平时将SDA从低电平转为高电平即可认为发送了停止信号。 以上是对于STM32F407软件模拟I2C读写MPU6050传感器的简要步骤介绍。具体实现过程中,需要根据实际需求进行相应的编码和调试。 ### 回答2: stm32f407是一款高性能的ARM Cortex-M4微控制器,可以通过软件模拟I2C总线来读写MPU6050传感器。 首先,我们需要了解MPU6050传感器的通信协议和寄存器地址。MPU6050使用I2C总线进行通信,需要通过开始信号、设备地址、寄存器地址、数据等步骤完成数据的读写。 在stm32f407上,我们可以使用GPIO引脚来模拟I2C总线的SCL(时钟线)和SDA(数据线)。我们需要配置SCL和SDA引脚为GPIO模式,并在代码中编写相应的功能实现软件模拟I2C总线的读写操作。 对于I2C总线的读操作,首先我们需要发送开始信号,然后发送设备地址加上写命令,接着发送寄存器地址,再发送一个重新开始信号,然后发送设备地址加上读命令,最后读取接收到的数据。 对于I2C总线的写操作,首先也是发送开始信号,然后发送设备地址加上写命令,接着发送寄存器地址,最后发送要写入的数据。 在代码中,我们可以按上述过程编写相应的函数来进行软件模拟的I2C读写操作,并将读取到的数据存储到相应的变量中。 需要注意的是,软件模拟I2C通信可能会导致一定的时序不准确。为了确保通信的稳定性和可靠性,我们可以使用延时函数来控制时序,并根据MPU6050传感器的参数手册调整延时时间。 总之,通过在stm32f407上进行软件模拟的I2C读写操作,我们可以实现对MPU6050传感器的数据读取和写入,从而实现与该传感器的通信和控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fitz&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值