这几节都是通信协议
I2C
在 I²C 硬件电路里用开漏输出,主要是为了避免总线冲突、适配多设备通信,具体原因如下:
- 防止短路损坏:I²C 总线支持多主 / 从设备共存,若用推挽输出,不同设备同时输出高、低电平,会使电源与地直接短路(如一个设备 “推” 高电平,另一个 “挽” 低电平 ),引发过大电流,烧坏芯片;开漏输出高电平时为高阻态,依赖外部上拉电阻拉至高电平,可规避此风险。
- 实现 “线与” 功能:开漏输出下,多个设备连总线,只要有一个设备拉低总线,整体就为低电平(“线与” 逻辑 ),能让多设备协调总线使用权(如总线仲裁,检测到总线被拉低,就知已有设备占用 ),契合 I²C 多设备共享总线的需求。
#include "stm32f10x.h" // Device header
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA()
{
uint8_t BitValue;
BitValue=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
void MyI2C_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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);
GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}
void MyI2C_Start()
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop()
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(Byte&(0x80>>i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiverByte()
{
uint8_t i,Byte=0x00;
MyI2C_W_SDA(1);
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1){Byte|=(0x80>>i);}
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck()
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit=MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
可能会有疑问,在函数中似乎有多余的操作,这是为了与下一个时序单元连接。例如在起始条件中,仅仅要求SCL高电平期间,SDA从高电平切换到低电平,但我们把SCL也切换到了低电平,这是为了与发送和接收连接。
还可能对于接收字节有疑问,为什么没有数据放到SDA的1部分?我们这是写的主机的接收函数,从主机角度来看,只需要读取就行了,从机已经将数据放在SDA上了
软件I2C读写MPU6050
传感器前端
- 加速度计(X/Y/Z Accel)、陀螺仪(X/Y/Z Gyro):采集三维空间的加速度、角速度信号,是运动数据的 “源头”,比如检测设备的倾斜、旋转。
- Temp Sensor(温度传感器):辅助采集环境温度,可用于传感器补偿(部分场景下温度会影响传感精度)。
- Self test(自检模块):对加速度计、陀螺仪单独做自检,判断硬件是否正常,像系统启动时快速排查传感器故障。
- ADC(模数转换器):把加速度计、陀螺仪、温度传感器输出的模拟电信号,转换成数字信号,方便后续处理。
信号处理与控制
- Signal Conditioning(信号调理):对 ADC 转换后的原始数字信号做滤波、校准等预处理,优化信号质量,让数据更稳定、准确。
- Digital Motion Processor(DMP,数字运动处理器):核心 “大脑” 之一,能独立运行运动算法(比如姿态解算),减轻主控制器的计算负担,直接输出融合后的运动数据(如四元数、欧拉角 )。
寄存器与存储
- Sensor Registers(传感器寄存器)、Config Registers(配置寄存器):存储传感器的工作参数(如量程、采样率)、配置指令,主控制器通过读写这些寄存器,设置传感器工作模式。
- FIFO(先入先出队列):临时缓存运动数据,可批量读取,适合高频率数据采集场景,避免数据丢失或频繁中断主控制器。
- Interrupt Status Register(中断状态寄存器):标记中断触发源(比如数据就绪、运动阈值触发),主控制器可快速查询中断原因,响应特定事件(如设备突然晃动)。
通信接口
- Slave I2C and SPI Serial Interface(从机 I2C/SPI 接口):作为从设备,让主控制器(如单片机)通过 I2C 或 SPI 协议,读写寄存器、获取数据,是 “外部主控与 MPU - 60X0 交互” 的通道。
- Master I2C Serial Interface(主机 I2C 接口):MPU - 60X0 可作为 I2C 主机,扩展连接其他从设备(比如磁力计),实现多传感器融合,构建更完整的运动感知系统。
- Serial Interface Bypass Mux(串行接口复用器):灵活切换通信路径,适配不同系统的硬件连接需求,比如某些场景下直接透传辅助设备(AUX 相关)的数据。
电源与时钟
- CLOCK(时钟模块):接收 CLKIN 输入,为整个芯片提供时序基准,保障各模块同步工作;CLKOUT 可输出时钟,供外部设备使用。
- Bias & LDO(偏置与低压差稳压器):为内部电路提供稳定电源,维持传感器、处理器的正常工作电压,保障性能一致性。
- Charge Pump(电荷泵):可能用于生成特定电压(比如为传感器供电、满足模拟电路需求 ),优化电源效率或适配特殊模块。
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPu6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS|0x01);
MyI2C_ReceiveAck();
Data=MyI2C_ReceiverByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
void MPU6050_Init()
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
MPU6050_WriteReg(MPU6050_CONFIG,0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}
uint8_t MPU6050_GetID()
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
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;
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;
}
硬件I2C读写MPU6050
一、核心信号与模块分区
- SDA(Serial Data Line,串行数据线):双向线,传输数据、地址、控制信息,是 I²C 通信的 “数据通道”。
- SCL(Serial Clock Line,串行时钟线):由主机产生时钟信号,同步 SDA 上的数据传输,保障通信时序一致。
- SMBALERT(System Management Bus Alert,系统管理总线警报):可选信号线,用于从设备主动向主机发送警报(如故障、数据就绪),属于拓展功能。
二、各模块功能详解
时钟控制链路
- 时钟控制(Clock Control):接收 SCL 信号,结合 时钟控制寄存器(CCR) 配置(如时钟分频、速率模式),为整个 I²C 模块提供时序基准,决定通信速率(标准模式、快速模式等 )。
- 控制寄存器(CR1&CR2):存储 I²C 工作模式、使能 / 禁用、中断配置等核心参数,是 “软件配置 I²C 功能” 的入口(比如开启 I²C、设置地址模式 )。
- 状态寄存器(SR1&SR2):实时反馈 I²C 通信状态(如总线忙、数据是否发送完成、是否检测到从机应答 ),主程序通过读取这些寄存器,判断通信进度和结果。
数据处理链路
- 数据控制(Data Control):管理 SDA 线上的数据收发,协调 “发送 / 接收” 状态切换,确保数据按 I²C 协议(起始位、地址位、数据位、停止位 )有序传输。
- 数据移位寄存器(Data Shift Register):串行 ↔ 并行数据转换的 “桥梁”:发送时,把 数据寄存器(DATA REGISTER) 的并行数据逐位移到 SDA;接收时,把 SDA 串行数据拼接成并行值,存回数据寄存器,供 CPU 读取。
- 比较器(Comparator):配合 自身地址寄存器(含双地址寄存器),判断总线上的目标地址是否与自身匹配:若匹配,触发从机响应(如应答、进入数据收发模式 ),实现 “主机寻址、从机识别” 的通信基础。
- 帧错误校验(PEC)计算 / 寄存器:自动生成或校验数据包的 PEC(类似 CRC 校验),检测通信过程中的数据错误,保障传输可靠性(部分 I²C 模式下可选启用 )。
控制与协作
- 控制逻辑电路(Control Logic Circuit):I²C 模块的 “总指挥”,整合时钟、数据、寄存器状态,协调中断触发(如数据收发完成、地址匹配 )、DMA 请求(批量数据传输时,直接与内存交互,减轻 CPU 负担 ),让 I²C 通信与系统其他模块(如 CPU、内存 )高效协作。
#include "stm32f10x.h" // Device header
#include "MPu6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
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)
break;
}
}
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2,MPU6050_ADDRESS);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2,Data);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2,ENABLE);
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2,MPU6050_ADDRESS);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
Data=I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2,ENABLE);
return Data;
}
void MPU6050_Init()
{
// MyI2C_Init();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed=50000;
I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1=0x00;
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
MPU6050_WriteReg(MPU6050_CONFIG,0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);
}
uint8_t MPU6050_GetID()
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
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;
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;
}
要注意接收字节时的EV6_1、EV7_1