嵌入式学习系列课程之江协stm32day7实战详解

这几节都是通信协议

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

    1. 传感器前端

    2. 加速度计(X/Y/Z Accel)、陀螺仪(X/Y/Z Gyro):采集三维空间的加速度、角速度信号,是运动数据的 “源头”,比如检测设备的倾斜、旋转。
    3. Temp Sensor(温度传感器):辅助采集环境温度,可用于传感器补偿(部分场景下温度会影响传感精度)。
    4. Self test(自检模块):对加速度计、陀螺仪单独做自检,判断硬件是否正常,像系统启动时快速排查传感器故障。
    5. ADC(模数转换器):把加速度计、陀螺仪、温度传感器输出的模拟电信号,转换成数字信号,方便后续处理。
    6. 信号处理与控制

    7. Signal Conditioning(信号调理):对 ADC 转换后的原始数字信号做滤波、校准等预处理,优化信号质量,让数据更稳定、准确。
    8. Digital Motion Processor(DMP,数字运动处理器):核心 “大脑” 之一,能独立运行运动算法(比如姿态解算),减轻主控制器的计算负担,直接输出融合后的运动数据(如四元数、欧拉角 )。
    9. 寄存器与存储

    10. Sensor Registers(传感器寄存器)、Config Registers(配置寄存器):存储传感器的工作参数(如量程、采样率)、配置指令,主控制器通过读写这些寄存器,设置传感器工作模式。
    11. FIFO(先入先出队列):临时缓存运动数据,可批量读取,适合高频率数据采集场景,避免数据丢失或频繁中断主控制器。
    12. Interrupt Status Register(中断状态寄存器):标记中断触发源(比如数据就绪、运动阈值触发),主控制器可快速查询中断原因,响应特定事件(如设备突然晃动)。
    13. 通信接口

    14. Slave I2C and SPI Serial Interface(从机 I2C/SPI 接口):作为从设备,让主控制器(如单片机)通过 I2C 或 SPI 协议,读写寄存器、获取数据,是 “外部主控与 MPU – 60X0 交互” 的通道。
    15. Master I2C Serial Interface(主机 I2C 接口):MPU – 60X0 可作为 I2C 主机,扩展连接其他从设备(比如磁力计),实现多传感器融合,构建更完整的运动感知系统。
    16. Serial Interface Bypass Mux(串行接口复用器):灵活切换通信路径,适配不同系统的硬件连接需求,比如某些场景下直接透传辅助设备(AUX 相关)的数据。
    17. 电源与时钟

    18. CLOCK(时钟模块):接收 CLKIN 输入,为整个芯片提供时序基准,保障各模块同步工作;CLKOUT 可输出时钟,供外部设备使用。
    19. Bias & LDO(偏置与低压差稳压器):为内部电路提供稳定电源,维持传感器、处理器的正常工作电压,保障性能一致性。
    20. 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,系统管理总线警报):可选信号线,用于从设备主动向主机发送警报(如故障、数据就绪),属于拓展功能。
  • 二、各模块功能详解

    1. 时钟控制链路

    2. 时钟控制(Clock Control):接收 SCL 信号,结合 时钟控制寄存器(CCR) 配置(如时钟分频、速率模式),为整个 I²C 模块提供时序基准,决定通信速率(标准模式、快速模式等 )。
    3. 控制寄存器(CR1&CR2):存储 I²C 工作模式、使能 / 禁用、中断配置等核心参数,是 “软件配置 I²C 功能” 的入口(比如开启 I²C、设置地址模式 )。
    4. 状态寄存器(SR1&SR2):实时反馈 I²C 通信状态(如总线忙、数据是否发送完成、是否检测到从机应答 ),主程序通过读取这些寄存器,判断通信进度和结果。
    5. 数据处理链路

    6. 数据控制(Data Control):管理 SDA 线上的数据收发,协调 “发送 / 接收” 状态切换,确保数据按 I²C 协议(起始位、地址位、数据位、停止位 )有序传输。
    7. 数据移位寄存器(Data Shift Register):串行 ↔ 并行数据转换的 “桥梁”:发送时,把 数据寄存器(DATA REGISTER) 的并行数据逐位移到 SDA;接收时,把 SDA 串行数据拼接成并行值,存回数据寄存器,供 CPU 读取。
    8. 比较器(Comparator):配合 自身地址寄存器(含双地址寄存器),判断总线上的目标地址是否与自身匹配:若匹配,触发从机响应(如应答、进入数据收发模式 ),实现 “主机寻址、从机识别” 的通信基础。
    9. 帧错误校验(PEC)计算 / 寄存器:自动生成或校验数据包的 PEC(类似 CRC 校验),检测通信过程中的数据错误,保障传输可靠性(部分 I²C 模式下可选启用 )。
    10. 控制与协作

    11. 控制逻辑电路(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

    作者:Camellia0311

    物联沃分享整理
    物联沃-IOTWORD物联网 » 嵌入式学习系列课程之江协stm32day7实战详解

    发表回复