文章目录

  • 一、I2C通信
    • 1.1 I2C
    • 1.2硬件电路
    • 1.3I2C时序基本单元
    • 1.4I2C时序
  • 二、MPU6050
    • 2.1简介
    • 2.2MPU6050参数
    • 2.3硬件电路
    • 2.4MPU6050框图
  • 三、I2C外设(硬件)
    • 3.1简介
    • 3.2I2C框图
    • 3.3I2C基本结构
    • 3.4主机发送
    • 3.5主机接收
    • 3.6软件/硬件波形对比
      • 1. 时序精度
      • 2. 信号稳定性
      • 3. 速率与效率
      • 4. 波形一致性
  • 四、软件I2C读写MPU6050
    • 4.1接线图
    • 4.2代码
      • **1. 初始化阶段**
      • **2. 起始信号(Start Condition)**
      • **3. 寻址阶段**
      • **4. 数据传输阶段**
        • **写操作(主机→从机)**
        • **读操作(从机→主机)**
      • **5. 停止信号(Stop Condition)**
      • **6. 完整通信示例(读 MPU6050 的 WHO_AM_I 寄存器)**
      • **7. 关键时序细节**
      • 1. 配置类寄存器
      • 2. 数据输出寄存器
      • 3. 电源与识别寄存器
    • 4.3相关API
    • 4.4现象
  • 五、硬件I2C读写MPU6050
    • 5.1接线图
    • 5.2代码
    • 5.3相关API
        • **1. 核心功能 API 列表**
        • **2. 关键 API 详细说明**
          • **2.1 MPU6050_Init () - 传感器初始化**
          • **2.2 MPU6050_WriteReg () - 写寄存器**
          • **2.3 MPU6050_ReadReg () - 读寄存器**
          • **2.4 MPU6050_GetData () - 获取传感器数据**
        • **3. 硬件 I²C 与软件模拟的差异**
    • 5.4现象
        • **一、正常运行现象**

一、I2C通信

1.1 I2C

在这里插入图片描述

1.2硬件电路

在这里插入图片描述

1.3I2C时序基本单元

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.4I2C时序

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

二、MPU6050

2.1简介

在这里插入图片描述

2.2MPU6050参数

在这里插入图片描述

2.3硬件电路

在这里插入图片描述

2.4MPU6050框图

在这里插入图片描述

  • 传感器部分:3 轴加速度计(X/Y/Z Accel)、3 轴陀螺仪(X/Y/Z Gyro)、温度传感器(Temp Sensor),用于采集运动及环境数据,且各传感器有自测试(Self test)功能。
  • 信号处理:经 ADC(模数转换器)转换模拟信号,再由 Signal Conditioning(信号调理)模块处理 。
  • 接口与控制:有多种串行接口(Slave I2C and SPI、Master I2C 等),还有中断(Interrupt Status Register)、FIFO(先入先出存储器)、配置寄存器(Config Registers )等,用于数据传输、控制和配置,Digital Motion Processor(DMP,数字运动处理器 )可辅助处理运动数据,Bias & LDO 是偏置和低压差稳压器相关模块,保障芯片供电和信号稳定 ,常用于运动检测、姿态感知等场景,像无人机、穿戴设备里的姿态控制 。

三、I2C外设(硬件)

3.1简介

在这里插入图片描述

3.2I2C框图

在这里插入图片描述

  1. 信号线路
    • SDA(Serial Data Line,串行数据线):双向数据线,负责在设备间传输数据,比如传感器向主控芯片发送采集的温湿度信息,或主控芯片向存储设备写入指令 。
    • SCL(Serial Clock Line,串行时钟线):由主设备产生时钟信号,为数据传输提供同步时序,像 I²C 总线通信时,主设备通过 SCL 控制数据在 SDA 上的收发节奏 。
    • SMBALERT(System Management Bus Alert,系统管理总线报警线 ):可用于设备触发报警、通知等功能,例如监测芯片检测到异常电压时,通过该线向主控发送报警信号 。
  2. 核心功能模块
    • 数据控制模块:协调 SDA 线上的数据收发,决定何时发送、接收数据,以及处理数据传输中的逻辑,比如判断数据帧的起始、停止等状态 。
    • 时钟控制模块:依据 SCL 线输入的时钟信号,管理总线通信时序,同时结合时钟控制寄存器(CCR)配置,调整时钟频率等参数,适配不同设备通信需求 。
    • 数据移位寄存器:在数据传输时,实现数据的串行 - 并行转换。发送数据时,将并行数据逐位串行输出到 SDA;接收数据时,把 SDA 传来的串行数据转为并行,存入数据寄存器(DATA REGISTER ) 。
    • 比较器:用于地址匹配,将总线上接收到的从设备地址,与自身地址寄存器、双地址寄存器中存储的地址对比,判断是否为目标设备,若匹配则响应通信 。
    • 帧错误校验(PEC,Packet Error Checking )计算单元:对传输的数据帧进行校验计算,生成校验码,也能对比接收到的 PEC 寄存器内校验码,检测数据传输是否出错,保障数据完整性 。
  3. 寄存器组
    • 自身地址寄存器、双地址寄存器:存储 I²C 设备自身的地址信息,支持单地址或双地址模式,方便主设备寻址,比如一个从设备可设置两个不同地址,供主设备灵活访问 。
    • 帧错误校验(PEC)寄存器:存放用于错误校验的相关数据,配合 PEC 计算单元,完成数据帧的校验工作 。
    • 时钟控制寄存器(CCR):配置时钟相关参数,像设置时钟分频系数,调节 SCL 线的时钟频率,满足不同通信速率要求,比如高速模式、标准模式切换 。
    • 控制寄存器(CR1&CR2):控制 I²C 总线的工作模式、使能中断等功能,例如通过 CR1 开启 I²C 模块、使能特定中断,CR2 配置地址模式等 。
    • 状态寄存器(SR1&SR2 ):实时反馈 I²C 总线的工作状态,如是否检测到起始信号、数据是否发送完成、有无错误发生等,为主控判断通信进程提供依据 。
    • 控制逻辑电路:作为 I²C 总线的 “指挥中心”,整合各模块工作,根据寄存器配置和总线状态,调度数据传输、时钟管理、地址识别等操作,还能触发中断(如数据收发完成中断、错误中断 ),向 DMA(直接内存访问 )发出请求并响应,让数据高效传输,减轻 CPU 负担,广泛应用于嵌入式系统中芯片间低速数据交互,像单片机与传感器、EEPROM 等设备的通信 。

3.3I2C基本结构

在这里插入图片描述

  1. 模块分工
    时钟控制器:生成或同步 SCL(时钟线)信号,就像 “节拍器”,规定数据收发的节奏,让通信双方按同一节奏干活 。
    数据控制器:管数据收发逻辑,决定啥时候发数据、咋接收数据,是数据通信的 “指挥官” 。
    移位寄存器:数据 “转换器”—— 发送时,把 数据寄存器(DR) 里的并行数据,拆成串行一位一位发;接收时,把 SDA 收的串行数据,重新拼成并行存到 DR,适配总线串行传输的规则 。
    数据寄存器(DR):临时存数据的 “小仓库”,发数据时从这取,收数据时存这,方便 CPU 或其他模块读写 。
    GPIO(通用输入输出):芯片对外的 “手”,把内部时钟(SCL)、数据(SDA)信号送出去,也能从外部收信号,实现芯片和外部设备(比如传感器、屏幕 )的连接 。
    开关控制:像 “总闸”,可能用来打开 / 关闭 I²C 功能、切换工作模式(比如主设备 / 从设备模式 ),灵活控制总线通断 。

2.通信流程

比如单片机要给传感器发指令、收数据:

  • 发数据:CPU 把指令放进 DR移位寄存器 把并行指令拆成串行 → 数据控制器 配合 时钟控制器 的节奏,通过 GPIOSDA 把指令发出去,SCL 同步发时钟信号让传感器 “对节奏” 。
  • 收数据:传感器通过 SDA 回传串行数据 → GPIO 收到后,移位寄存器 把串行数据拼成并行存到 DR → CPU 从 DR 里读数据,完成通信 。

3.4主机发送

在这里插入图片描述

3.5主机接收

在这里插入图片描述

完整代码示例(STM32 硬件 I²C 写操作)
假设使用 I2C1,连接从机地址 0x50(7 位地址,实际发送时会左移 1 位 + 读写位),你可根据实际需求修改。

c
运行
#include "stm32f10x.h"// 从机地址(7位),实际通信时会自动左移1位 + 读写位
#define I2C_SLAVE_ADDR   0x50  /*** @brief  等待 I²C 事件,对应时序图的 EVx* @param  I2Cx: I2C1/I2C2 外设* @param  event: 要等待的事件(如 I2C_EVENT_MASTER_MODE_SELECT)* @retval 无(超时可自行加逻辑,这里简化处理)*/
void I2C_WaitEvent(I2C_TypeDef* I2Cx, uint32_t event)
{// 简单超时机制(实际项目建议加更完善的超时处理)uint32_t timeout = 0xFFFF;while (I2C_CheckEvent(I2Cx, event) != SUCCESS){if (timeout-- == 0) break; // 超时退出}
}/*** @brief  I²C 初始化(配置为 7 位地址、标准模式 100kHz)* @param  无* @retval 无*/
void I2C_Init_Master(void)
{I2C_InitTypeDef I2C_InitStruct;GPIO_InitTypeDef GPIO_InitStruct;// 1. 使能时钟:I2C1 + 对应 GPIO 时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 假设用 PB6(SCL)/PB7(SDA)// 2. 配置 GPIO:复用开漏输出GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// 3. 配置 I²CI2C_InitStruct.I2C_Mode = I2C_Mode_I2C;          // I²C 模式I2C_InitStruct.I2C_ClockSpeed = 100000;          // 100kHz 标准模式I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;  // 占空比 2(Tlow/Thigh = 2)I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;         // 使能应答I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7 位地址I2C_InitStruct.I2C_OwnAddress1 = 0x00;           // 主机自身地址(从机模式才用,这里随意填)I2C_Init(I2C1, &I2C_InitStruct);// 4. 使能 I²CI2C_Cmd(I2C1, ENABLE);
}/*** @brief  硬件 I²C 写一个字节到从机寄存器* @param  regAddr: 从机内部寄存器地址(如 MPU6050 的 0x6B)* @param  data: 要写入的数据(1 字节)* @retval 无* @note   严格对应时序图 7 位主发送流程:S → 地址+A → 数据1+A → 数据2+A → ... → P*/
void I2C_WriteByte(uint8_t regAddr, uint8_t data)
{// 1. 发送起始条件(S),等待 EV5I2C_GenerateSTART(I2C1, ENABLE);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); // 等 EV5(SB=1)// 2. 发送从机地址 + 写方向(7位地址<<1 | 0),等待 EV6I2C_Send7bitAddress(I2C1, I2C_SLAVE_ADDR, I2C_Direction_Transmitter);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // 等 EV6(ADDR=1)// 3. 发送寄存器地址(数据1),等待 EV8_1I2C_SendData(I2C1, regAddr);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); // 等 EV8_1(TXE=1, BTF=0)// 4. 发送数据(数据2),等待 EV8_2I2C_SendData(I2C1, data);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // 等 EV8_2(TXE=1, BTF=1)// 5. 发送停止条件(P)I2C_GenerateSTOP(I2C1, ENABLE);
}int main(void)
{// 初始化硬件 I²CI2C_Init_Master();// 示例:往从机 0x50 的 0x10 寄存器写 0xAAI2C_WriteByte(0x10, 0xAA);while (1){// 主循环可做其他事}
}

代码与时序图的逐步骤对应
对照你提供的7 位主发送时序图,每一步代码都对应时序图的节点:

时序图阶段 代码操作 对应事件(EVx) 硬件状态说明
S(起始) I2C_GenerateSTART(I2C1, ENABLE) EV5 (SB=1) 起始条件发送成功,总线进入忙状态
地址 + A I2C_Send7bitAddress(…, 写方向) EV6 (ADDR=1) 从机地址发送完成,收到从机应答
数据 1 + A I2C_SendData(regAddr) EV8_1 (TXE=1, BTF=0) 数据寄存器空,移位寄存器非空(正在发送)
数据 2 + A I2C_SendData(data) EV8_2 (TXE=1, BTF=1) 数据发送完成,移位寄存器和数据寄存器都空
P(停止) I2C_GenerateSTOP(I2C1, ENABLE) - 停止条件发送,总线释放

EV5:检测 SR1 寄存器的 SB 位,确保起始信号发出去了。
EV6:检测 SR1 的 ADDR 位 + SR2 的 TXE 位,确保从机地址发完且收到应答。
EV8_1:检测 SR1 的 TXE=1 且 BTF=0,说明 “数据寄存器空了,但移位寄存器还在发之前的数据”。
EV8_2:检测 SR1 的 TXE=1 且 BTF=1,说明 “移位寄存器和数据寄存器都空了,数据发完了”。
扩展:读操作怎么写?(对应时序图 “重复起始”)
如果要实现读操作(比如先写寄存器地址,再读数据),需要重复起始条件(对应时序图的 “Sr”),核心逻辑如下(简化示例):

c
运行
uint8_t I2C_ReadByte(uint8_t regAddr)
{uint8_t data;// 1. 先写寄存器地址(同写操作前 3 步)I2C_GenerateSTART(I2C1, ENABLE);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, I2C_SLAVE_ADDR, I2C_Direction_Transmitter);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C1, regAddr);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // 2. 重复起始条件(Sr),切换为读方向I2C_GenerateSTART(I2C1, ENABLE);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); // 3. 发送从机地址 + 读方向(7位地址<<1 | 1),等待 EV6(读模式)I2C_Send7bitAddress(I2C1, I2C_SLAVE_ADDR, I2C_Direction_Receiver);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); // 4. 接收数据(最后一个字节发非应答 + 提前发停止)I2C_AcknowledgeConfig(I2C1, DISABLE); // 最后一个字节不应答I2C_GenerateSTOP(I2C1, ENABLE);       // 提前发停止I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED); // 等 EV7data = I2C_ReceiveData(I2C1);         // 读数据寄存器// 5. 恢复应答(方便下次通信)I2C_AcknowledgeConfig(I2C1, ENABLE);  return data;
}

关键差异:读操作需要 “重复起始” 切换方向,且最后一个字节要关闭应答 + 提前发停止,对应时序图的 “读模式收尾”。
总结:把时序图转代码的核心思路
拆分时序节点:把时序图按 S→地址→数据→P 拆成步骤,每个步骤对应一个硬件操作(发起始、发地址、发数据、发停止)。
等待事件(EVx):每个操作后,必须等硬件状态寄存器(SR1/SR2)的标志位,确保时序图的 “事件” 完成。
硬件自动处理电平:你不用手动控制 SDA/SCL,调用 I2C_GenerateSTART、I2C_SendData 等函数,硬件会自动翻转引脚电平,匹配时序图。

这样,不管是给 MPU6050 写寄存器,还是驱动其他 I²C 设备(OLED、EEPROM 等),都可以用这套 “拆分时序节点 + 等待 EVx” 的思路,把时序图转化为稳定的硬件 I²C 代码。

3.6软件/硬件波形对比

在这里插入图片描述

1. 时序精度

  • 硬件 I²C:由硬件外设按固定时钟逻辑生成波形,像 “精准节拍器”,SCL(时钟线)、SDA(数据线)的电平翻转严格对齐硬件时钟,时序误差极小、一致性高 。比如 STM32 的硬件 I²C,能稳定输出标准模式(100kHz)、快速模式(400kHz+ )的精准时序。
  • 软件模拟 I²C:靠 GPIO 引脚翻转 + 软件延时模拟时序,受 CPU 负载、延时函数精度(如普通delay函数的误差 )影响,时序误差大 。比如模拟 100kHz 速率时,软件延时的微小偏差会让 SCL 周期、占空比 “跑偏”,导致波形时序不标准。

2. 信号稳定性

  • 硬件 I²C:硬件电路直接驱动总线,输出能力稳定,SCL/SDA 的电平幅值、边沿跳变清晰干净,抗干扰强 。即使总线挂多个设备,硬件驱动也能保障信号质量。
  • 软件模拟 I²C:依赖 GPIO 软件控制,输出驱动能力弱(尤其频繁切换电平,易受总线电容、上拉电阻影响 ),波形易出现 “毛刺”“边沿变缓” 。比如高电平拉不上去、低电平释放慢,导致信号识别困难。

3. 速率与效率

  • 硬件 I²C:速率上限高(支持标准 100kHz、快速 400kHz,甚至高速模式 ),且硬件自动处理时序,CPU 几乎不参与 ,能边通信边干其他任务,效率拉满。
  • 软件模拟 I²C:速率受软件延时、CPU 运算限制,通常难超 100kHz ,且 CPU 得全程 “盯着” GPIO 翻转,占用大量资源,干不了别的活,效率低。

4. 波形一致性

  • 硬件 I²C:同一 MCU 同一配置下,每次通信波形高度一致,像 “复制粘贴”,天生适配标准 I²C 协议。
  • 软件模拟 I²C:受代码执行环境(如中断干扰、任务切换 )影响,不同次通信波形可能有差异 ,比如延时被打断,导致 SCL 周期忽长忽短。

四、软件I2C读写MPU6050

4.1接线图

在这里插入图片描述

4.2代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"uint8_t ID;								//定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化MPU6050_Init();		//MPU6050初始化/*显示ID号*/OLED_ShowString(1, 1, "ID:");		//显示静态字符串ID = MPU6050_GetID();				//获取MPU6050的ID号OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);		//获取MPU6050的数据OLED_ShowSignedNum(2, 1, AX, 5);					//OLED显示数据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);}
}

MyI2C.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"/*引脚配置层*//*** 函    数:I2C写SCL引脚电平* 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平*/
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平Delay_us(10);												//延时10us,防止时序频率超过要求
}/*** 函    数:I2C写SDA引脚电平* 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue为1时,需要置SDA为高电平*/
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性Delay_us(10);												//延时10us,防止时序频率超过要求
}/*** 函    数:I2C读SDA引脚电平* 参    数:无* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1*/
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);		//读取SDA电平Delay_us(10);												//延时10us,防止时序频率超过要求return BitValue;											//返回SDA电平
}/*** 函    数:I2C初始化* 参    数:无* 返 回 值:无* 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化*/
void MyI2C_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为开漏输出/*设置默认电平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}/*协议层*//*** 函    数:I2C起始* 参    数:无* 返 回 值:无*/
void MyI2C_Start(void)
{MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}/*** 函    数:I2C终止* 参    数:无* 返 回 值:无*/
void MyI2C_Stop(void)
{MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}/*** 函    数:I2C发送一个字节* 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF* 返 回 值:无*/
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位{/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/MyI2C_W_SDA(!!(Byte & (0x80 >> i)));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDAMyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据}
}/*** 函    数:I2C接收一个字节* 参    数:无* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF*/
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位{MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDAif (MyI2C_R_SDA()){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA}return Byte;							//返回接收到的一个字节数据
}/*** 函    数:I2C发送应答位* 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答* 返 回 值:无*/
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit);					//主机把应答位数据放到SDA线MyI2C_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
}/*** 函    数:I2C接收应答位* 参    数:无* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答*/
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;							//定义应答位变量MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送MyI2C_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDAAckBit = MyI2C_R_SDA();					//将应答位存储到变量里MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块return AckBit;							//返回定义应答位变量
}

1. 初始化阶段

c

运行

MyI2C_Init();  // 初始化I2C引脚
  • 硬件准备:将 SCL (PB10) 和 SDA (PB11) 配置为开漏输出,并默认拉高(释放总线)
  • 关键配置:开漏输出允许线与特性,支持多设备共享总线

2. 起始信号(Start Condition)

c

运行

MyI2C_Start();
  • 时序要求:SCL 为高电平时,SDA 由高变低

  • 代码实现

    c

    运行

    MyI2C_W_SDA(1);  // 确保SDA为高
    MyI2C_W_SCL(1);  // 确保SCL为高
    MyI2C_W_SDA(0);  // SDA拉低,产生起始边沿
    MyI2C_W_SCL(0);  // SCL拉低,准备发送数据
    
  • 作用:通知总线上所有从机 “通信开始”,并使总线进入占用状态

3. 寻址阶段

c

运行

MyI2C_SendByte(0xD0);  // 发送从机地址+写位(0)
MyI2C_ReceiveAck();    // 接收从机应答
  • 地址构成

    :7 位从机地址 + 1 位读写位(0 = 写,1 = 读)

    • 例如:MPU6050 默认地址为0x68,写操作时发送0xD0(0x68<<1 | 0)
  • 应答机制

    • 从机接收到地址后,若匹配则拉低 SDA(发送 ACK=0)
    • 主机检测到 ACK 后继续通信,否则终止

4. 数据传输阶段

写操作(主机→从机)

c

运行

MyI2C_SendByte(MPU6050_WHO_AM_I);  // 发送寄存器地址
MyI2C_ReceiveAck();                // 等待应答
MyI2C_SendByte(data);              // 发送数据
MyI2C_ReceiveAck();                // 等待应答
  • 数据发送流程

    1. 主机在 SCL 低电平时将数据位放到 SDA 上
    2. 拉高 SCL,从机在 SCL 高电平期间读取数据
    3. 拉低 SCL,准备发送下一位
    4. 重复 8 次,完成 1 字节传输
  • 应答规则:每字节传输后,接收方需发送 ACK/NACK

读操作(从机→主机)

c

运行

MyI2C_ReceiveByte();  // 接收数据
MyI2C_SendAck(1);     // 发送非应答(停止接收)
  • 数据接收流程

    1. 主机释放 SDA(拉高)
    2. 从机在 SCL 低电平时将数据位放到 SDA 上
    3. 主机在 SCL 高电平期间读取数据
    4. 拉低 SCL,准备接收下一位
    5. 重复 8 次,完成 1 字节传输
  • 应答控制

    • 主机若需要继续接收数据,发送 ACK (0)
    • 主机若接收完毕,发送 NACK (1)

5. 停止信号(Stop Condition)

c

运行

MyI2C_Stop();
  • 时序要求:SCL 为高电平时,SDA 由低变高

  • 代码实现

    c

    运行

    MyI2C_W_SDA(0);  // 确保SDA为低
    MyI2C_W_SCL(1);  // 拉高SCL
    MyI2C_W_SDA(1);  // SDA拉高,产生停止边沿
    
  • 作用:释放总线,结束本次通信

6. 完整通信示例(读 MPU6050 的 WHO_AM_I 寄存器)

c

运行

uint8_t ReadMPU6050ID(void) {uint8_t id;// 写阶段:指定寄存器地址MyI2C_Start();                 // 起始信号MyI2C_SendByte(0xD0);          // 发送从机写地址MyI2C_ReceiveAck();            // 等待应答MyI2C_SendByte(MPU6050_WHO_AM_I);  // 发送寄存器地址MyI2C_ReceiveAck();            // 等待应答// 读阶段:读取寄存器值MyI2C_Start();                 // 重新发起始信号(Re-Start)MyI2C_SendByte(0xD1);          // 发送从机读地址MyI2C_ReceiveAck();            // 等待应答id = MyI2C_ReceiveByte();      // 读取数据MyI2C_SendAck(1);              // 发送非应答(停止接收)MyI2C_Stop();                  // 停止信号return id;
}

7. 关键时序细节

  1. 数据有效性:SDA 线上的数据必须在 SCL 高电平期间保持稳定

  2. 边沿触发:SCL 的上升沿触发数据采样,下降沿允许数据变化

  3. 应答位处理

    • 发送方发送完 8 位数据后,必须释放 SDA
    • 接收方在第 9 个时钟周期发送 ACK/NACK
  4. 时钟拉伸

    (Clock Stretching):

    • 从机若处理不及,可拉低 SCL 强制主机等待

MyI2C.h

#ifndef __MYI2C_H
#define __MYI2C_Hvoid MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);#endif

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS		0xD0		//MPU6050的I2C从机地址/*** 函    数:MPU6050写寄存器* 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF* 返 回 值:无*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start();						//I2C起始MyI2C_SendByte(MPU6050_ADDRESS);	//发送从机地址,读写位为0,表示即将写入MyI2C_ReceiveAck();					//接收应答MyI2C_SendByte(RegAddress);			//发送寄存器地址MyI2C_ReceiveAck();					//接收应答MyI2C_SendByte(Data);				//发送要写入寄存器的数据MyI2C_ReceiveAck();					//接收应答MyI2C_Stop();						//I2C终止
}/*** 函    数:MPU6050读寄存器* 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 返 回 值:读取寄存器的数据,范围:0x00~0xFF*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;MyI2C_Start();						//I2C起始MyI2C_SendByte(MPU6050_ADDRESS);	//发送从机地址,读写位为0,表示即将写入MyI2C_ReceiveAck();					//接收应答MyI2C_SendByte(RegAddress);			//发送寄存器地址MyI2C_ReceiveAck();					//接收应答MyI2C_Start();						//I2C重复起始MyI2C_SendByte(MPU6050_ADDRESS | 0x01);	//发送从机地址,读写位为1,表示即将读取MyI2C_ReceiveAck();					//接收应答Data = MyI2C_ReceiveByte();			//接收指定寄存器的数据MyI2C_SendAck(1);					//发送应答,给从机非应答,终止从机的数据输出MyI2C_Stop();						//I2C终止return Data;
}/*** 函    数:MPU6050初始化* 参    数:无* 返 回 值:无*/
void MPU6050_Init(void)
{MyI2C_Init();									//先初始化底层的I2C/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		//电源管理寄存器2,保持默认值0,所有轴均不待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		//采样率分频寄存器,配置采样率MPU6050_WriteReg(MPU6050_CONFIG, 0x06);			//配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	//陀螺仪配置寄存器,选择满量程为±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	//加速度计配置寄存器,选择满量程为±16g
}/*** 函    数:MPU6050获取ID号* 参    数:无* 返 回 值:MPU6050的ID号*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}/*** 函    数:MPU6050获取数据* 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 返 回 值:无*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t DataH, DataL;								//定义数据高8位和低8位的变量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

MPU6050.h

#ifndef __MPU6050_H
#define __MPU6050_Hvoid MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif

MPU6050_reg.h

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H#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

1. 配置类寄存器

  • MPU6050_SMPLRT_DIV(0x19):采样率分频寄存器,设置传感器数据输出的采样率,分频值 = 寄存器值 + 1,影响加速度计、陀螺仪数据更新速度。
  • MPU6050_CONFIG(0x1A):配置寄存器,控制低通滤波(LPF)参数,决定加速度计、陀螺仪原始数据的滤波频率,过滤高频噪声。
  • MPU6050_GYRO_CONFIG(0x1B):陀螺仪配置寄存器,设置陀螺仪量程(如 ±250°/s、±500°/s 等)、自检使能等。
  • MPU6050_ACCEL_CONFIG(0x1C):加速度计配置寄存器,设置加速度计量程(如 ±2g、±4g 等)、自检使能等。

2. 数据输出寄存器

  • 加速度计数据MPU6050_ACCEL_XOUT_H~ZOUT_L,0x3B~0x40):存储 X/Y/Z 轴加速度原始数据(高 8 位 + 低 8 位),需结合量程换算为实际加速度值(如 g 为单位 )。
  • 温度数据MPU6050_TEMP_OUT_H~L,0x41~0x42):存储内部温度传感器原始数据,通过公式换算为摄氏 / 华氏温度。
  • 陀螺仪数据MPU6050_GYRO_XOUT_H~ZOUT_L,0x43~0x48):存储 X/Y/Z 轴角速度原始数据(高 8 位 + 低 8 位),结合量程换算为实际角速度(如 °/s 为单位 )。

3. 电源与识别寄存器

  • MPU6050_PWR_MGMT_1(0x6B):电源管理寄存器 1,控制设备唤醒、睡眠模式,设置时钟源(如内部 8MHz 振荡器、外部时钟 )。
  • MPU6050_PWR_MGMT_2(0x6C):电源管理寄存器 2,控制加速度计、陀螺仪各轴的待机模式,实现低功耗配置。
  • MPU6050_WHO_AM_I(0x75):设备 ID 寄存器,读取固定值(通常 0x68 ),用于检测设备是否正常连接、通信。

4.3相关API

1. 引脚配置层 API

函数名功能描述参数说明返回值
MyI2C_Init()初始化 I²C 引脚(PB10/SCL、PB11/SDA)为开漏输出,并释放总线
MyI2C_W_SCL(uint8_t BitValue)设置 SCL 引脚电平(0 = 低,1 = 高),控制时钟线BitValue:0 或 1
MyI2C_W_SDA(uint8_t BitValue)设置 SDA 引脚电平(0 = 低,1 = 高),控制数据线BitValue:0 或 1
MyI2C_R_SDA(void)读取 SDA 引脚当前电平,用于接收从机数据或应答uint8_t:0 或 1

2. 协议层 API

函数名功能描述参数说明返回值
MyI2C_Start()产生 I²C 起始信号(SCL 高电平时,SDA 由高变低),标志通信开始
MyI2C_Stop()产生 I²C 停止信号(SCL 高电平时,SDA 由低变高),释放总线
MyI2C_SendByte(uint8_t Byte)发送 1 字节数据(高位先传),逐位输出到 SDA 线,并等待从机应答Byte:待发送的数据(0x00~0xFF)
MyI2C_ReceiveByte(void)接收 1 字节数据(高位先收),从 SDA 线读取从机发送的数据uint8_t:接收到的数据
MyI2C_SendAck(uint8_t AckBit)发送应答位(控制 SDA),告知从机是否继续发送数据AckBit:0 = 应答(继续),1 = 非应答(停止)
MyI2C_ReceiveAck(void)接收从机应答位,判断从机是否成功接收数据

4.4现象

软件与硬件现象相同

五、硬件I2C读写MPU6050

5.1接线图

在这里插入图片描述

5.2代码

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"uint8_t ID;								//定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化MPU6050_Init();		//MPU6050初始化/*显示ID号*/OLED_ShowString(1, 1, "ID:");		//显示静态字符串ID = MPU6050_GetID();				//获取MPU6050的ID号OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);		//获取MPU6050的数据OLED_ShowSignedNum(2, 1, AX, 5);					//OLED显示数据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);}
}

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS		0xD0		//MPU6050的I2C从机地址/*** 函    数:MPU6050等待事件* 参    数:同I2C_CheckEvent* 返 回 值:无*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout;Timeout = 10000;									//给定超时计数时间while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//循环等待指定事件{Timeout --;										//等待时,计数值自减if (Timeout == 0)								//自减到0后,等待超时{/*超时的错误处理代码,可以添加到此处*/break;										//跳出等待,不等了}}
}/*** 函    数:MPU6050写寄存器* 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF* 返 回 值:无*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8I2C_SendData(I2C2, Data);												//硬件I2C发送数据MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2I2C_GenerateSTOP(I2C2, ENABLE);											//硬件I2C生成终止条件
}/*** 函    数:MPU6050读寄存器* 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 返 回 值:读取寄存器的数据,范围:0x00~0xFF*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成重复起始条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);		//硬件I2C发送从机地址,方向为接收MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);		//等待EV6I2C_AcknowledgeConfig(I2C2, DISABLE);									//在接收最后一个字节之前提前将应答失能I2C_GenerateSTOP(I2C2, ENABLE);											//在接收最后一个字节之前提前申请停止条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);				//等待EV7Data = I2C_ReceiveData(I2C2);											//接收数据寄存器I2C_AcknowledgeConfig(I2C2, ENABLE);									//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作return Data;
}/*** 函    数:MPU6050初始化* 参    数:无* 返 回 值:无*/
void MPU6050_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);		//开启I2C2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为复用开漏输出/*I2C初始化*/I2C_InitTypeDef I2C_InitStructure;						//定义结构体变量I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;				//模式,选择为I2C模式I2C_InitStructure.I2C_ClockSpeed = 50000;				//时钟速度,选择为50KHzI2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;		//时钟占空比,选择Tlow/Thigh = 2I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;				//应答,选择使能I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//应答地址,选择7位,从机模式下才有效I2C_InitStructure.I2C_OwnAddress1 = 0x00;				//自身地址,从机模式下才有效I2C_Init(I2C2, &I2C_InitStructure);						//将结构体变量交给I2C_Init,配置I2C2/*I2C使能*/I2C_Cmd(I2C2, ENABLE);									//使能I2C2,开始运行/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);				//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);				//电源管理寄存器2,保持默认值0,所有轴均不待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);				//采样率分频寄存器,配置采样率MPU6050_WriteReg(MPU6050_CONFIG, 0x06);					//配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);			//陀螺仪配置寄存器,选择满量程为±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);			//加速度计配置寄存器,选择满量程为±16g
}/*** 函    数:MPU6050获取ID号* 参    数:无* 返 回 值:MPU6050的ID号*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}/*** 函    数:MPU6050获取数据* 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 返 回 值:无*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t DataH, DataL;								//定义数据高8位和低8位的变量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

MPU6050.h

#ifndef __MPU6050_H
#define __MPU6050_Hvoid MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif

MPU6050_reg.h

5.3相关API

1. 核心功能 API 列表
函数名功能描述参数说明返回值
MPU6050_Init()初始化 MPU6050 传感器(配置 I²C 接口、唤醒芯片、设置量程等)
MPU6050_GetID()读取 MPU6050 的设备 ID(用于验证设备连接)uint8_t:设备 ID(正常为 0x68)
MPU6050_WriteReg()向 MPU6050 指定寄存器写入数据RegAddress:寄存器地址 Data:写入数据(0x00~0xFF)
MPU6050_ReadReg()从 MPU6050 指定寄存器读取数据RegAddress:寄存器地址uint8_t:读取的数据
MPU6050_GetData()获取加速度计和陀螺仪的原始数据(16 位)AccX/Y/Z:加速度计数据指针 GyroX/Y/Z:陀螺仪数据指针
MPU6050_WaitEvent()等待 I²C 通信事件完成(内部用于硬件 I²C 的状态检测)I2Cx:I²C 外设指针 I2C_EVENT:目标事件
2. 关键 API 详细说明
2.1 MPU6050_Init () - 传感器初始化

功能流程

  1. 开启 I2C2 和 GPIOB 时钟,配置 PB10/PB11 为复用开漏输出(硬件 I²C 专用引脚)
  2. 初始化 I2C2 参数:
    • 模式:I2C 模式
    • 速率:50kHz(标准模式)
    • 应答:使能 7 位地址应答
  3. 配置 MPU6050 寄存器:
    • 唤醒芯片(PWR_MGMT_1 = 0x01
    • 设置加速度计量程 ±16g(ACCEL_CONFIG = 0x18
    • 设置陀螺仪量程 ±2000°/s(GYRO_CONFIG = 0x18

调用示例

c

运行

MPU6050_Init();  // 初始化后即可读取传感器数据
2.2 MPU6050_WriteReg () - 写寄存器

通信流程

  1. 生成 I²C 起始信号
  2. 发送从机写地址(0xD0),等待 EV6 事件
  3. 发送寄存器地址,等待 EV8 事件
  4. 发送数据,等待 EV8_2 事件
  5. 生成停止信号

参数说明

  • RegAddress:寄存器地址(如MPU6050_PWR_MGMT_1 = 0x6B
  • Data:写入数据(如0x01表示唤醒芯片)

代码示例

c

运行

MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);  // 设置采样率分频
2.3 MPU6050_ReadReg () - 读寄存器

通信流程

  1. 生成起始信号→发送写地址→发送寄存器地址(第一次通信)
  2. 生成重复起始信号→发送读地址→接收数据(第二次通信)
  3. 接收前禁用应答,生成停止信号

关键逻辑

c

运行

uint8_t Data = MPU6050_ReadReg(MPU6050_WHO_AM_I);  // 读设备ID
2.4 MPU6050_GetData () - 获取传感器数据

数据处理

  • 每个轴数据由 2 字节组成(高 8 位 + 低 8 位),需拼接为 16 位有符号数

  • 示例(加速度 X 轴):

    c

    运行

    DataH = MPU6050_ReadReg(0x3B);  // 高字节
    DataL = MPU6050_ReadReg(0x3C);  // 低字节
    *AccX = (DataH << 8) | DataL;   // 拼接为16位
    
  • 量程与数值转换:

    • 加速度 ±16g:1LSB = 16g / 32768 ≈ 0.000488g
    • 陀螺仪 ±2000°/s:1LSB = 2000°/s/ 32768 ≈ 0.061°/s
3. 硬件 I²C 与软件模拟的差异
特性硬件 I²C(当前代码)软件模拟 I²C(之前代码)
时序控制由硬件外设自动生成,精度高依赖软件延时,精度较低
CPU 占用率低(通信时 CPU 可处理其他任务)高(需全程控制 GPIO 翻转)
速率上限支持高速模式(400kHz+)通常≤100kHz
代码复杂度需配置 I²C 外设寄存器,逻辑较复杂仅操作 GPIO,代码更简洁
稳定性抗干扰能力强,适合高速通信易受 CPU 负载影响,需额外防抖

5.4现象

一、正常运行现象
  1. 系统初始化阶段

    • OLED 屏幕第 1 行显示 ID:68(MPU6050 默认 ID 为 0x68),若 ID 显示为其他值(如 0xFF),则说明通信异常。
    • 硬件连接正常时,MPU6050 芯片表面轻微发热(工作电流约 3.6mA)。
  2. 数据采集与显示阶段

    • 加速度计数据(AX/AY/AZ):

      • 静止时,Z 轴数据约为 + 16384(对应 + 1g,因重力方向竖直向下),X/Y 轴接近 0(±50 以内)。
      • 倾斜传感器时,对应轴数据线性变化(如 X 轴倾斜 45°,AX≈16384×sin45°≈11585)。
    • 陀螺仪数据(GX/GY/GZ) :

      • 静止时,各轴数据接近 0(±50 以内),旋转传感器时,对应轴数据随角速度变化(如绕 Z 轴顺时针旋转,GZ 为正值)。
    • OLED 显示格式(示例):

      plaintext

      ID:68
      AX:+1234  GX:-056
      AY:-0456  GY:+078
      AZ:+16384 GZ:-012
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/85084.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/85084.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/85084.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 Azure LLM Functions 与 Elasticsearch 构建更智能的查询体验

作者&#xff1a;来自 Elastic Jonathan Simon 及 James Williams 试用这个示例房地产搜索应用&#xff0c;它结合了 Azure Gen AI LLM Functions 与 Elasticsearch&#xff0c;提供灵活的混合搜索结果。在 GitHub Codespaces 中查看逐步配置和运行该示例应用的方法。 更多阅读…

模糊查询 的深度技术解析

以下是 模糊查询 的深度技术解析&#xff0c;涵盖核心语法、通配符策略、性能优化及实战陷阱&#xff1a; &#x1f50d; 一、核心运算符&#xff1a;LIKE SELECT * FROM 表名 WHERE 列名 LIKE 模式字符串;&#x1f3af; 二、通配符详解 通配符作用示例匹配案例%任意长度字符…

[论文阅读] (39)EuroSP25 CTINEXUS:基于大模型的威胁情报知识图谱自动构建

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

强化学习三大分类

核心目标&#xff1a; 教会一个智能体&#xff08;比如机器人、游戏AI、推荐系统&#xff09;通过试错和奖励&#xff0c;学会在某个环境中完成特定任务的最佳策略。 核心角色&#xff1a; 智能体 (Agent)&#xff1a; 学习者&#xff0c;比如玩游戏的小人、控制温度的空调系…

城市排水生命线安全运行监测项目

近年来&#xff0c;城市内涝、污水溢流等问题频发&#xff0c;让排水管网这一"城市生命线"的安全运行备受关注。如何让地下的"毛细血管"更智能、更可靠&#xff1f;本文将带您深入解析城市排水生命线安全运行监测项目的建设逻辑与技术内核&#xff0c;看科…

LeetCode - 34. 在排序数组中查找元素的第一个和最后一个位置

题目 34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09; 思路 查找左边界 初始化 left 0, right nums.size() - 1 当 left < right 时循环&#xff1a; 计算中点 mid left (right - left) / 2 如果 nums[mid] < target…

Tesollo四指灵巧手DG-4F:18自由度与多种抓取模式结合实现高精度操作

Tesollo四指灵巧手 DG-4F 是一款具备 18 自由度的多模态末端执行器&#xff0c;采用模块化结构设计&#xff0c;融合人手灵活性与夹爪高效性特点。该产品兼容 Universal Robots、Techman、Doosan Robotics、Rainbow Robotics 等主流机器人平台&#xff0c;适用于工业自动化、科…

深入浅出JavaScript 原型链:对象继承的“隐形链条”

深入浅出JavaScript 原型链&#xff1a;对象继承的“隐形链条” 在 JavaScript 的世界里&#xff0c;原型链&#xff08;Prototype Chain&#xff09;是一个核心概念。它如同一条隐形的链条&#xff0c;连接着所有对象&#xff0c;使得代码能够高效地共享属性和方法。理解原型…

LINUX中MYSQL的使用

LINUX中MYSQL的使用 MYSQL的数据类型 bool&#xff1a; 布尔类型 0 或者 1 CHAR&#xff1a; 单字符的字符 CHAR&#xff08;n&#xff09;:多字节字符 VARCHAR&#xff08;n&#xff09;&#xff1a;可变长度的字符型 TINYINT &#xff1a; 单字节整型 SMALLINT&#x…

打卡第48天:随机函数与广播机制

知识点回顾&#xff1a; 随机张量的生成&#xff1a;torch.randn函数卷积和池化的计算公式&#xff08;可以不掌握&#xff0c;会自动计算的&#xff09;pytorch的广播机制&#xff1a;加法和乘法的广播机制 ps&#xff1a;numpy运算也有类似的广播机制&#xff0c;基本一致 …

学习昇腾开发的第四天--基本指令

1、查看npu当前状态信息 npu-smi info 2、查看NPU的ID npu-smi info -l3、调用python python3 4、修改用户名 su - HwHiAiUser 5、查看cann版本 cat /usr/local/Ascend/ascend-toolkit/latest/compiler/version.info 6、删除文件夹 sudo rm -rf HelloWorld7、在本地环…

vue3 - 自定义hook

自定义hook 简单点来说就是将人物或者订单的所有数据和方法放在一个ts文件里面 这样便于维护 假如一个人只需要管 人物的模块 那他只需要操作usePerson.ts文件就可以了 //useDog.ts import { ref,reactive} from vue; import axios from axios;export default function(){…

【python】bash: !‘: event not found

报错 # 2. 测试smplx是否工作&#xff08;可能不需要chumpy&#xff09; python -c "import smplx; print(✅ smplx works!)"bash: !: event not found 分析 这是bash的历史扩展问题&#xff0c;感叹号被解释为历史命令。用这些方法解决&#xff1a; &#x1f680…

【Python打卡Day47】注意力热力图可视化@浙大疏锦行

可视化空间注意力热力图的意义&#xff1a; 提升模型可解释性 热力图能直观展示模型决策的依据区域&#xff0c;破除深度学习"黑箱"困境。例如在图像识别中&#xff0c;可以看到模型识别"猫"是因为关注了猫耳和胡须区域&#xff0c;识别"禁止通行&qu…

树状数组 2

L - 树状数组 2 洛谷 - P3368 Description 如题&#xff0c;已知一个数列&#xff0c;你需要进行下面两种操作&#xff1a; 将某区间每一个数加上 x&#xff1b; 求出某一个数的值。 Input 第一行包含两个整数 N、M&#xff0c;分别表示该数列数字的个数和操作的总个数。…

YOLOv2 技术详解:目标检测的又一次飞跃

&#x1f9e0; YOLOv2 技术详解&#xff1a;目标检测的又一次飞跃 一、前言 在 YOLOv1 提出后&#xff0c;虽然实现了“实时性 单阶段”的突破&#xff0c;但其在精度和小物体检测方面仍有明显不足。为了弥补这些缺陷&#xff0c;Joseph Redmon 等人在 2017 年提出了 YOLOv2…

JAFAR Jack up Any Feature at Any Resolution

GitHub PaPer JAFAR: Jack up Any Feature at Any Resolution 摘要 基础视觉编码器已成为各种密集视觉任务的核心组件。然而&#xff0c;它们的低分辨率空间特征输出需要特征上采样以产生下游任务所需的高分辨率模式。在这项工作中&#xff0c;我们介绍了 JAFAR——一种轻量级…

SamWaf 开源轻量级网站防火墙源码(源码下载)

SamWaf网站防火墙是一款适用于小公司、工作室和个人网站的开源轻量级网站防火墙&#xff0c;完全私有化部署&#xff0c;数据加密且仅保存本地&#xff0c;一键启动&#xff0c;支持Linux&#xff0c;Windows 64位,Arm64。 主要功能&#xff1a; 代码完全开源 支持私有化部署…

79Qt窗口_QDockWidget的基本使用

目录 4.1 浮动窗⼝的创建 4.2 设置停靠的位置 浮动窗⼝ 在 Qt 中&#xff0c;浮动窗⼝也称之为铆接部件。浮动窗⼝是通过 QDockWidget类 来实现浮动的功能。浮动窗 ⼝⼀般是位于核⼼部件的周围&#xff0c;可以有多个。 4.1 浮动窗⼝的创建 浮动窗⼝的创建是通过 QDockWidget…

UE/Unity/Webgl云渲染推流网址,如何与外部网页嵌套和交互?

需求分析&#xff1a;用threejs开发的数字孪生模型&#xff0c; 但是通过webgl技术网页中使用&#xff0c;因为模型数据量大&#xff0c;加载比较慢&#xff0c;且需要和其他的业务系统进行网页嵌套和交互&#xff0c;使用云渲染技术形成的推流网址&#xff0c;如何与外部网页嵌…