文章目录
- 一、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框图
- 信号线路
- SDA(Serial Data Line,串行数据线):双向数据线,负责在设备间传输数据,比如传感器向主控芯片发送采集的温湿度信息,或主控芯片向存储设备写入指令 。
- SCL(Serial Clock Line,串行时钟线):由主设备产生时钟信号,为数据传输提供同步时序,像 I²C 总线通信时,主设备通过 SCL 控制数据在 SDA 上的收发节奏 。
- SMBALERT(System Management Bus Alert,系统管理总线报警线 ):可用于设备触发报警、通知等功能,例如监测芯片检测到异常电压时,通过该线向主控发送报警信号 。
- 核心功能模块
- 数据控制模块:协调 SDA 线上的数据收发,决定何时发送、接收数据,以及处理数据传输中的逻辑,比如判断数据帧的起始、停止等状态 。
- 时钟控制模块:依据 SCL 线输入的时钟信号,管理总线通信时序,同时结合时钟控制寄存器(CCR)配置,调整时钟频率等参数,适配不同设备通信需求 。
- 数据移位寄存器:在数据传输时,实现数据的串行 - 并行转换。发送数据时,将并行数据逐位串行输出到 SDA;接收数据时,把 SDA 传来的串行数据转为并行,存入数据寄存器(DATA REGISTER ) 。
- 比较器:用于地址匹配,将总线上接收到的从设备地址,与自身地址寄存器、双地址寄存器中存储的地址对比,判断是否为目标设备,若匹配则响应通信 。
- 帧错误校验(PEC,Packet Error Checking )计算单元:对传输的数据帧进行校验计算,生成校验码,也能对比接收到的 PEC 寄存器内校验码,检测数据传输是否出错,保障数据完整性 。
- 寄存器组
- 自身地址寄存器、双地址寄存器:存储 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基本结构
- 模块分工
时钟控制器:生成或同步 SCL(时钟线)信号,就像 “节拍器”,规定数据收发的节奏,让通信双方按同一节奏干活 。
数据控制器:管数据收发逻辑,决定啥时候发数据、咋接收数据,是数据通信的 “指挥官” 。
移位寄存器:数据 “转换器”—— 发送时,把 数据寄存器(DR) 里的并行数据,拆成串行一位一位发;接收时,把 SDA 收的串行数据,重新拼成并行存到 DR,适配总线串行传输的规则 。
数据寄存器(DR):临时存数据的 “小仓库”,发数据时从这取,收数据时存这,方便 CPU 或其他模块读写 。
GPIO(通用输入输出):芯片对外的 “手”,把内部时钟(SCL)、数据(SDA)信号送出去,也能从外部收信号,实现芯片和外部设备(比如传感器、屏幕 )的连接 。
开关控制:像 “总闸”,可能用来打开 / 关闭 I²C 功能、切换工作模式(比如主设备 / 从设备模式 ),灵活控制总线通断 。
2.通信流程
比如单片机要给传感器发指令、收数据:
- 发数据:CPU 把指令放进
DR
→移位寄存器
把并行指令拆成串行 →数据控制器
配合时钟控制器
的节奏,通过GPIO
从SDA
把指令发出去,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)
- 例如:MPU6050 默认地址为
-
应答机制
:
- 从机接收到地址后,若匹配则拉低 SDA(发送 ACK=0)
- 主机检测到 ACK 后继续通信,否则终止
4. 数据传输阶段
写操作(主机→从机)
c
运行
MyI2C_SendByte(MPU6050_WHO_AM_I); // 发送寄存器地址
MyI2C_ReceiveAck(); // 等待应答
MyI2C_SendByte(data); // 发送数据
MyI2C_ReceiveAck(); // 等待应答
-
数据发送流程
:
- 主机在 SCL 低电平时将数据位放到 SDA 上
- 拉高 SCL,从机在 SCL 高电平期间读取数据
- 拉低 SCL,准备发送下一位
- 重复 8 次,完成 1 字节传输
-
应答规则:每字节传输后,接收方需发送 ACK/NACK
读操作(从机→主机)
c
运行
MyI2C_ReceiveByte(); // 接收数据
MyI2C_SendAck(1); // 发送非应答(停止接收)
-
数据接收流程
:
- 主机释放 SDA(拉高)
- 从机在 SCL 低电平时将数据位放到 SDA 上
- 主机在 SCL 高电平期间读取数据
- 拉低 SCL,准备接收下一位
- 重复 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. 关键时序细节
-
数据有效性:SDA 线上的数据必须在 SCL 高电平期间保持稳定
-
边沿触发:SCL 的上升沿触发数据采样,下降沿允许数据变化
-
应答位处理
:
- 发送方发送完 8 位数据后,必须释放 SDA
- 接收方在第 9 个时钟周期发送 ACK/NACK
-
时钟拉伸
(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 () - 传感器初始化
功能流程:
- 开启 I2C2 和 GPIOB 时钟,配置 PB10/PB11 为复用开漏输出(硬件 I²C 专用引脚)
- 初始化 I2C2 参数:
- 模式:I2C 模式
- 速率:50kHz(标准模式)
- 应答:使能 7 位地址应答
- 配置 MPU6050 寄存器:
- 唤醒芯片(
PWR_MGMT_1 = 0x01
) - 设置加速度计量程 ±16g(
ACCEL_CONFIG = 0x18
) - 设置陀螺仪量程 ±2000°/s(
GYRO_CONFIG = 0x18
)
- 唤醒芯片(
调用示例:
c
运行
MPU6050_Init(); // 初始化后即可读取传感器数据
2.2 MPU6050_WriteReg () - 写寄存器
通信流程:
- 生成 I²C 起始信号
- 发送从机写地址(0xD0),等待 EV6 事件
- 发送寄存器地址,等待 EV8 事件
- 发送数据,等待 EV8_2 事件
- 生成停止信号
参数说明:
RegAddress
:寄存器地址(如MPU6050_PWR_MGMT_1 = 0x6B
)Data
:写入数据(如0x01
表示唤醒芯片)
代码示例:
c
运行
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 设置采样率分频
2.3 MPU6050_ReadReg () - 读寄存器
通信流程:
- 生成起始信号→发送写地址→发送寄存器地址(第一次通信)
- 生成重复起始信号→发送读地址→接收数据(第二次通信)
- 接收前禁用应答,生成停止信号
关键逻辑:
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现象
一、正常运行现象
-
系统初始化阶段
- OLED 屏幕第 1 行显示
ID:68
(MPU6050 默认 ID 为 0x68),若 ID 显示为其他值(如 0xFF),则说明通信异常。 - 硬件连接正常时,MPU6050 芯片表面轻微发热(工作电流约 3.6mA)。
- OLED 屏幕第 1 行显示
-
数据采集与显示阶段
-
加速度计数据(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
-