STM32CubeMX-H7-11-IIC读写MPU6050模块(上)-软件IIC协议的解析、封装,实现基本功能获取MPU6050的ID

注:2025年4月4日23:53已经修复本篇文章代码bug,bug已经修复,现在可以正常无误使用

前言

用是最好的学,学习了IIC协议的知识,那么我的要去使用他才更方便我们去理解,本篇文章会给出.c和.h的文件,直接复制粘贴,直接修改引脚和主频,就可以直接使用这套代码区读MPU6050。

IIC和MPU6050这篇文章有点长,所以我分为上中下篇

上篇主要讲解IIC协议和基本使用,获取MPU6050的ID

中篇主要讲解MPU6050模块的使用

下篇主要是硬件IIC驱动MPU6050

IIC协议的简介

IIC协议的优点是两根信号线就可以对多个设备进行通信,缺点是通信速度慢,刚好跟SPI相反。

然后IIC有软件和硬件,软件的话直接用我这套代码就好了。

理解IIC协议的核心就是

SCL为高的时候,SDA会被读取或者使用,此时SDA不能改变

SCL为低的时候,SDA可以变化高低电平来准备下一次SCL为高的时候要用的电平

本篇文章是结合AI协作完成

IIC协议的基本框架 

IIC主要有起始,停止,发送一个字节,接受一个字节,应答信号,接下来我们逐一分析

IIC协议的基本框架完整的.c和.h文件在本小结后面会有,不想看分析的可以直接跳过

1.延时

IIC一般有个几微妙的延时,但是我们在使用HAL库的时候,只有毫秒级的延时,所以这个我们要自己编写

void delay_us(uint32_t us) {
    uint32_t i;
    for (i = 0; i < us * (SYSTEM_MAX); i++) {
        __asm("nop");
    }
}

其中SYSTEM_MAX是系统主频

2.定义引脚状态

// 定义 SCL 和 SDA 引脚
#define SOFT_IIC_SCL_PORT GPIOA  // 请根据实际情况修改
#define SOFT_IIC_SCL_PIN GPIO_PIN_4  // 请根据实际情况修改
#define SOFT_IIC_SDA_PORT GPIOA  // 请根据实际情况修改
#define SOFT_IIC_SDA_PIN GPIO_PIN_5  // 请根据实际情况修改

  头文件出加的定义,确定好哪个引脚,这样可以跳过cubemx的配置

// 设置 SCL 引脚电平,并添加延时
void IIC_SCL_(uint8_t state) {
    if (state)
        HAL_GPIO_WritePin(SOFT_IIC_SCL_PORT, SOFT_IIC_SCL_PIN, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(SOFT_IIC_SCL_PORT, SOFT_IIC_SCL_PIN, GPIO_PIN_RESET);
    delay_us(5);
}

// 设置 SDA 引脚电平,并添加延时
void IIC_SDA_(uint8_t state) {
    if (state)
        HAL_GPIO_WritePin(SOFT_IIC_SDA_PORT, SOFT_IIC_SDA_PIN, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(SOFT_IIC_SDA_PORT, SOFT_IIC_SDA_PIN, GPIO_PIN_RESET);
    delay_us(5);
}

// 读取 SDA 引脚电平
uint8_t IIC_SDA_Read(void) {
    return HAL_GPIO_ReadPin(SOFT_IIC_SDA_PORT, SOFT_IIC_SDA_PIN);
}

软件模拟高低电平,然后只有SDA需要读的操作

3.引脚初始化

// I2C 初始化
void IIC_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能 SCL 和 SDA 引脚所在端口的时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();  // 请根据实际情况修改

    // 配置 SCL 引脚
    GPIO_InitStruct.Pin = SOFT_IIC_SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SOFT_IIC_SCL_PORT, &GPIO_InitStruct);

    // 配置 SDA 引脚
    GPIO_InitStruct.Pin = SOFT_IIC_SDA_PIN;
    HAL_GPIO_Init(SOFT_IIC_SDA_PORT, &GPIO_InitStruct);

    // 初始状态:SCL 和 SDA 都为高电平
    IIC_SCL_(1);
    IIC_SDA_(1);
}

IIC使用的是开漏输出

4.起始信号

// 发送起始信号
void IIC_Start(void) {
    IIC_SDA_(1);
    IIC_SCL_(1);
    IIC_SDA_(0);
    IIC_SCL_(0);
}

SDA先下降,SCL后下降

5.终止信号


// 发送停止信号
void IIC_Stop(void) {
    IIC_SCL_(0);
    IIC_SDA_(0);
    IIC_SCL_(1);
    IIC_SDA_(1);
}

SCL先升高,SDA后升高

6.发送一个字节

// 发送一个字节
void IIC_SendByte(uint8_t data) {
    uint8_t i;
    for (i = 0; i < 8; i++) {
        IIC_SDA_((data & 0x80) >> 7);
        data <<= 1;
        IIC_SCL_(1);
        IIC_SCL_(0);
    }
}

当SCL为高的时候,SDA会被读取,此时SDA不能改变

当SCL为低的时候,SDA才能变化

7.等待应答

// 等待应答信号
uint8_t IIC_WaitAck(void) {
    uint8_t ack;
    IIC_SDA_(1);
    IIC_SCL_(1);
    ack = IIC_SDA_Read();
    IIC_SCL_(0);
    return ack;
}

每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据

8.接收一个字节

// 接收一个字节
uint8_t IIC_ReceiveByte(uint8_t ack) {
    uint8_t i, data = 0;
    IIC_SDA_(1);  // 释放 SDA 线
    for (i = 0; i < 8; i++) {
        IIC_SCL_(1);
        data <<= 1;
        if (IIC_SDA_Read())
            data |= 0x01;
        IIC_SCL_(0);
    }
    if (ack)
        IIC_SDA_(0);  // 发送应答
    else
        IIC_SDA_(1);  // 发送非应答
    IIC_SCL_(1);
    IIC_SCL_(0);
    return data;
}

在读取完后发送一个应答信号,SDA为0是应答,1是非应答

MPU6050模块简介

在编写完IIC框架后,我们开始来使用IIC读取MPU6050的数据

MPU6050 是一款常见的 6轴惯性测量单元(IMU),集成了 三轴加速度计 和 三轴陀螺仪 于单一芯片,可同时测量物体在 X、Y、Z 三个方向的 加速度 和绕这三轴的 角速度 

一般我们就是用来当陀螺仪使用,主要是获取角度

MPU6050的读取过程

1.初始化引脚

// MPU6050 初始化函数
uint8_t MPU6050_Init(void) {
    IIC_Init();  // 初始化 I2C 总线
    return 0;
}

没有返回说明卡死了 

2.获取ID地址

       一般IIC模块都有多个地址,其中看某些特定引脚的电平

        MPU605默认是0x68,当AD0为低的时候

        如果AD0接高电平,那么地址就为0x69

        

// 定义 MPU6050 设备地址
#define MPU6050_ADDR_AD0_LOW 0x68  // AD0 引脚接地时的地址
#define MPU6050_ADDR_AD0_HIGH 0x69 // AD0 引脚接高电平时的地址

// 定义 MPU6050 寄存器地址
#define MPU6050_WHO_AM_I_REG 0x75  // 设备 ID 寄存器地址

 .h处定义的

通信过程(详细讲解基本通信过程)

1.主机发出通信请求,让所有连接在这条总线上的模块做好通信准备

主机:所有人,听我叫编号

挂载在设备上的模块都做好倾听准备

让所有模块准备

  // 发送起始信号
    IIC_Start();

2.主机先发送将要通信的地址

主机:0x68这个编号是谁?在这里吗?

此时是询问挂载在这条总线上是否存在0x68这个地址对应的模块

第一位为0代表是写命令

    // 发送 MPU6050 写地址
    IIC_SendByte((MPU6050_ADDR_AD0_LOW << 1) | 0);

 3.主机等待挂载众设备的回应

主机:等待回应   

这时候有模块举手了

MPU6050把手高高举起

没有就说明这个编号的人不在这里,那么说明自己没有让这个加入或者找错编号了

    // 等待应答
    if (IIC_WaitAck()) {
        IIC_Stop();
        return 0xFF; // 错误返回
    }

4.主机发出读取寄存器的指令
主机:0x68是你是吧,现在请把你的身份证给我看看吧!

每个模块都有唯一ID,就是身份证

当上一条,3处有回应后,这时候的IIC通信只有主机和MPU6050模块,其他模块都不能插嘴

这时候把

    // 发送要读取的寄存器地址
    IIC_SendByte(MPU6050_WHO_AM_I_REG);

5.主机发出指令后,等待从机接收到指令的回应

主机等待MPU6050模块是否听到了问题

MPU6050回应了个OK

用来判断是否接收到了指令

    // 等待应答
    if (IIC_WaitAck()) {
        IIC_Stop();
        return 0xFF; // 错误返回
    }
6.主机切换成读模式

主机:刚才我问的你们只能做手势回答,我要换一下我的问答方式,接下来我问的你们可以说话回答了。那个,MPU6050,刚才你说你的地址是0X68,请把你的身份证拿出来给我们看看呗

主机切换成读模式,1代表读

      // 重新发送起始信号以进行读操作
    IIC_Start();
       // 发送 MPU6050 读地址
    IIC_SendByte((MPU6050_ADDR_AD0_LOW << 1) | 1);

7.主机等待从机回应

MPU6050此时给了个OK的手势

    // 等待应答
    if (IIC_WaitAck()) {
        IIC_Stop();
        return 0xFF; // 错误返回
    }

8.读取ID设备

这时候MPU6050把身份证拿出来给主机看,主机逐一读取

获取8位返回值

因为数据手册表明地址就是8位

所以读完后就可以获得我们想要的数据了

此时就可以结束通话了

 // 读取设备 ID
    device_id = IIC_ReceiveByte(0); // 最后一个字节不需要应答

    // 发送停止信号
    IIC_Stop();

单独完整获取ID的.c和.h代码

MPU6050.c

#include "MPU6050.h"

// MPU6050 初始化函数
uint8_t MPU6050_Init(void) {
    IIC_Init();  // 初始化 I2C 总线
    return 0;
}

// 获取 MPU6050 设备 ID
uint8_t MPU6050_GetDeviceID(void) {
    uint8_t device_id = 0;

    // 发送起始信号
    IIC_Start();

    // 发送 MPU6050 写地址
    IIC_SendByte((MPU6050_ADDR_AD0_LOW << 1) | 0);

    // 等待应答
    if (IIC_WaitAck()) {
        IIC_Stop();
        return 0xFF; // 错误返回
    }

    // 发送要读取的寄存器地址
    IIC_SendByte(MPU6050_WHO_AM_I_REG);

    // 等待应答
    if (IIC_WaitAck()) {
        IIC_Stop();
        return 0xFF; // 错误返回
    }

    // 重新发送起始信号以进行读操作
    IIC_Start();

    // 发送 MPU6050 读地址
    IIC_SendByte((MPU6050_ADDR_AD0_LOW << 1) | 1);

    // 等待应答
    if (IIC_WaitAck()) {
        IIC_Stop();
        return 0xFF; // 错误返回
    }

    // 读取设备 ID
    device_id = IIC_ReceiveByte(0); // 最后一个字节不需要应答

    // 发送停止信号
    IIC_Stop();

    return device_id;
}

MPU6050.H

#ifndef __MPU6050_H
#define __MPU6050_H

#include "SOFT_IIC.h"

// 定义 MPU6050 设备地址
#define MPU6050_ADDR_AD0_LOW 0x68  // AD0 引脚接地时的地址
#define MPU6050_ADDR_AD0_HIGH 0x69 // AD0 引脚接高电平时的地址

// 定义 MPU6050 寄存器地址
#define MPU6050_WHO_AM_I_REG 0x75  // 设备 ID 寄存器地址

// 函数声明
uint8_t MPU6050_Init(void);
uint8_t MPU6050_GetDeviceID(void);

#endif



3.优化代码

根据上述的通信流程,我们可以根据框架,然后编写一个专门的代码,当传入地址,数据寄存器,和指令后,就可以返回我们需要的值


// 向 MPU6050 写入一个字节数据
void IIC_Write_REG(uint8_t addr, uint8_t reg, uint8_t data) {
    IIC_Start();
    IIC_SendByte((addr << 1) | 0);  // 发送写地址
    IIC_WaitAck();
    IIC_SendByte(reg);  // 发送寄存器地址
    IIC_WaitAck();
    IIC_SendByte(data);  // 发送数据
    IIC_WaitAck();
    IIC_Stop();
}

uint8_t IIC_Read_REG(uint8_t Address,uint8_t regaddress) {
    uint8_t data;
    IIC_Start();  // 发送起始信号
    IIC_SendByte(Address<<1|0);  // 发送设备地址和写操作
    IIC_WaitAck();  // 等待 ACK
    IIC_SendByte(regaddress);  // 发送寄存器地址
	IIC_WaitAck();  // 等待 ACK
	IIC_Start();  // 发送起始信号
	IIC_SendByte(Address<<1|1);  // 发送设备地址和读操作
	IIC_WaitAck();  // 等待 ACK
	data = IIC_ReceiveByte(0);  // 读取数据
	IIC_Stop();  // 发送停止信号
    return data;  // 返回读取的数据	
}

此时我们只需要对上述框架封装使用即可 

#define WHO_AM_I     0x75 	// IIC地址寄存器(默认数值0x68,只读) */

// HAL库的读写只需要使用7位地址
#define MPU6050_ADDR_AD0_LOW 0x68	// AD0低电平时7位地址为0X68 iic写时许发送0XD0

// 读取 MPU6050 的设备 ID
uint8_t MPU6050_GetDeviceID(void) {
    uint8_t data;
    data=IIC_Read_REG(MPU6050_ADDR_AD0_LOW, WHO_AM_I);  // 读取设备 ID 寄存器
	return data;  // 返回设备 ID	
}

使用这个代码框架后,我们就可以容易对多个设备获取值

此时的完整代码为

soft_iic.c

#include "SOFT_IIC.h"

// 延时函数,单位:微秒
// 假设系统频率为 80MHz
void delay_us(uint32_t us) {
    uint32_t i;
    for (i = 0; i < us * SYSTEM_MAX ; i++) {
        __asm("nop");
    }
}

// 设置 SCL 引脚电平,并添加延时
void IIC_SCL_(uint8_t state) {
    if (state)
        HAL_GPIO_WritePin(SOFT_IIC_SCL_PORT, SOFT_IIC_SCL_PIN, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(SOFT_IIC_SCL_PORT, SOFT_IIC_SCL_PIN, GPIO_PIN_RESET);
    delay_us(5);
}

// 设置 SDA 引脚电平,并添加延时
void IIC_SDA_(uint8_t state) {
    if (state)
        HAL_GPIO_WritePin(SOFT_IIC_SDA_PORT, SOFT_IIC_SDA_PIN, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(SOFT_IIC_SDA_PORT, SOFT_IIC_SDA_PIN, GPIO_PIN_RESET);
    delay_us(5);
}

// 读取 SDA 引脚电平
uint8_t IIC_SDA_Read(void) {
    return HAL_GPIO_ReadPin(SOFT_IIC_SDA_PORT, SOFT_IIC_SDA_PIN);
}

// I2C 初始化
void IIC_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能 SCL 和 SDA 引脚所在端口的时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();  // 请根据实际情况修改

    // 配置 SCL 引脚
    GPIO_InitStruct.Pin = SOFT_IIC_SCL_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(SOFT_IIC_SCL_PORT, &GPIO_InitStruct);

    // 配置 SDA 引脚
    GPIO_InitStruct.Pin = SOFT_IIC_SDA_PIN;
    HAL_GPIO_Init(SOFT_IIC_SDA_PORT, &GPIO_InitStruct);

    // 初始状态:SCL 和 SDA 都为高电平
    IIC_SCL_(1);
    IIC_SDA_(1);
}

// 发送起始信号
void IIC_Start(void) {
    IIC_SDA_(1);
    IIC_SCL_(1);
    IIC_SDA_(0);
    IIC_SCL_(0);
}

// 发送停止信号
void IIC_Stop(void) {
    IIC_SCL_(0);
    IIC_SDA_(0);
    IIC_SCL_(1);
    IIC_SDA_(1);
}

// 发送一个字节
void IIC_SendByte(uint8_t data) {
    uint8_t i;
    for (i = 0; i < 8; i++) {
        IIC_SDA_((data & 0x80) >> 7);
        data <<= 1;
        IIC_SCL_(1);
        IIC_SCL_(0);
    }
}

// 接收一个字节
uint8_t IIC_ReceiveByte(uint8_t ack) {
    uint8_t i, data = 0;
    IIC_SDA_(1);  // 释放 SDA 线
    for (i = 0; i < 8; i++) {
        IIC_SCL_(1);
        data <<= 1;
        if (IIC_SDA_Read())
            data |= 0x01;
        IIC_SCL_(0);
    }
    if (ack)
        IIC_SDA_(0);  // 发送应答
    else
        IIC_SDA_(1);  // 发送非应答
    IIC_SCL_(1);
    IIC_SCL_(0);
    return data;
}

// 等待应答信号
uint8_t IIC_WaitAck(void) {
    uint8_t ack;
    IIC_SDA_(1);
    IIC_SCL_(1);
    ack = IIC_SDA_Read();
    IIC_SCL_(0);
    return ack;
}

// 向 MPU6050 写入一个字节数据
void IIC_Write_REG(uint8_t addr, uint8_t reg, uint8_t data) {
    IIC_Start();
    IIC_SendByte((addr << 1) | 0);  // 发送写地址
    IIC_WaitAck();
    IIC_SendByte(reg);  // 发送寄存器地址
    IIC_WaitAck();
    IIC_SendByte(data);  // 发送数据
    IIC_WaitAck();
    IIC_Stop();
}

uint8_t IIC_Read_REG(uint8_t Address,uint8_t regaddress) {
    uint8_t data;
    IIC_Start();  // 发送起始信号
    IIC_SendByte(Address<<1|0);  // 发送设备地址和写操作
    IIC_WaitAck();  // 等待 ACK
    IIC_SendByte(regaddress);  // 发送寄存器地址
	IIC_WaitAck();  // 等待 ACK
	IIC_Start();  // 发送起始信号
	IIC_SendByte(Address<<1|1);  // 发送设备地址和读操作
	IIC_WaitAck();  // 等待 ACK
	data = IIC_ReceiveByte(0);  // 读取数据
	IIC_Stop();  // 发送停止信号
    return data;  // 返回读取的数据	
}

soft_iic.h

#ifndef __SOFT_IIC_H
#define __SOFT_IIC_H

#include "gpio.h"

// 定义 SCL 和 SDA 引脚
#define SOFT_IIC_SCL_PORT GPIOA  // 请根据实际情况修改
#define SOFT_IIC_SCL_PIN GPIO_PIN_4  // 请根据实际情况修改
#define SOFT_IIC_SDA_PORT GPIOA  // 请根据实际情况修改
#define SOFT_IIC_SDA_PIN GPIO_PIN_5  // 请根据实际情况修改
#define SYSTEM_MAX 80
// 函数声明
void IIC_Init(void);
void IIC_Start(void);
void IIC_Stop(void);
void IIC_SendByte(uint8_t data);
uint8_t IIC_ReceiveByte(uint8_t ack);
uint8_t IIC_WaitAck(void);
void IIC_Ack(void);
void IIC_NAck(void);
void IIC_Write_REG(uint8_t Address,uint8_t regaddress, uint8_t data);
uint8_t IIC_Read_REG(uint8_t Address,uint8_t regaddress);
// 延时函数声明
void delay_us(uint32_t us);

#endif

MPU6050.c

#include "MPU6050.h"
#include "math.h"
#include "SOFT_IIC.h"
#include <stdio.h>
// MPU6050 初始化函数
uint8_t MPU6050_Init(void) {
    IIC_Init();  // 初始化 I2C 总线

    HAL_Delay(100);  // 等待传感器稳定
    return 0;
}

// 读取 MPU6050 的设备 ID
uint8_t MPU6050_GetDeviceID(void) {
    uint8_t data;
    data=IIC_Read_REG(MPU6050_ADDR_AD0_LOW, WHO_AM_I);  // 读取设备 ID 寄存器
	return data;  // 返回设备 ID	
}

MPU6050.H

#ifndef __MPU6050_H
#define __MPU6050_H

#include "main.h"


// HAL库的读写只需要使用7位地址
#define MPU6050_ADDR_AD0_LOW 0x68	// AD0低电平时7位地址为0X68 iic写时许发送0XD0
#define WHO_AM_I     0x75 	// IIC地址寄存器(默认数值0x68,只读) */


// 函数声明
uint8_t MPU6050_Init(void);
uint8_t MPU6050_GetDeviceID(void);
#endif

 下篇我们开始讲解MPU6050寄存器,然后去获取角度和角加速度等数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值