stm32单片机个人学习笔记15(I2C通信协议)
- 游戏开发
- 2025-08-23 20:45:01

前言
本篇文章属于stm32单片机(以下简称单片机)的学习笔记,来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。
STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili
一、I2C通信通信协议要在硬件和软件上都有规定
1. 硬件电路规定SCL时钟线无论在什么时刻都是由主机控制,从机没有控制权利
SDA数据线只有在从机发送数据和从机应答的时候,从机才短暂地拥有控制权
注意SCL和SDA均要配置成开漏输出模式
采取强下拉和弱上拉的模式,根据杆子比喻来理解,弹簧吊着杆子,低电平往下拉杆子变为低电平,高电平放手,弹簧将其拉至高电平,这里电路中电阻R起的就是弹簧的作用,将引脚进行弱上拉
补充:
推挽输出既可以输出高电平也可以输出低电平
开漏输出可以输出低电平和高阻态,想要输出高电平可以外接上拉电阻
上拉输入没有外部输入的时候默认输入为高电平
下拉输入没有外部输入的时候默认输入为低电平
浮空输入没有外部输入的时候,单片机读取到的值处于不确定状态,即浮动,一会儿1,一会儿0,
只有输入了一个高/低电平才会确定下来。因此容易受到外部干扰
2.软件规定I2C时序基本单元
注意是高位先行
发送应答和接收应答
3.I2C时序上电时,寄存器指针默认指向0地址,当对寄存器内的数据进行读操作或写操作时,指针移动到这个位置并自增1(注意要将数据写入后或读出后才自增只说明要进行读操作或写操作是不自增的),指向下一个地址,由于该时序不能读指定地址内的数据,因此不常用,下面这个更常用
该时序是一个复合时序,先进行指定地址写,再进行当前地址读,原理是执行指定地址写时,地址就会指向该地址,又因为还没有写入数据,因此指针还没有自增,还是指向这个地址,再采取当前地址读的操作时,就会读出这个地址内的数据
常用的是第一个指定地址写时序和第三个指定地址读时序
如果要在寄存器的连续地址写入数据,第一个时序执行完后,再重复时序后面发送数据的内容即可
如果要在寄存器的连续地址读出数据,第三个时序执行完后,主机应答为0,从机继续发送数据,不要从机继续发送数据时,主机应答为1即可,然后停止
二、MPU6050 1.简介6轴是3轴加速度、3轴角速度
9轴是3轴加速度、3轴角速度、3轴磁场强度
10轴是3轴加速度、3轴角速度、3轴磁场强度、1轴气压强度
2.参数 3.硬件电路 4.框图自测响应=自测使能时的数据-自测失能时的数据
自测响应在一定范围内说明芯片正常
三、软件I2C读写MPU6050 1.I2C (1)封装SCL、SDA操作函数 #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(void) { uint8_t BitValue; BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); Delay_us(10); return BitValue; } (2)I2C时序单元 void MyI2C_Start(void) { MyI2C_W_SDA(1); MyI2C_W_SCL(1); MyI2C_W_SDA(0); MyI2C_W_SCL(0); }开始时序
先拉高SDA是为了防止如果拉高前SDA是低电平,先拉高SCL,那么就会误读为停止时序
void MyI2C_Stop(void) { 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_ReceiveByte(void) { uint8_t Byte = 0; uint8_t i; 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(void) { uint8_t AckBit; MyI2C_W_SDA(1); MyI2C_W_SCL(1); AckBit = MyI2C_R_SDA(); MyI2C_W_SCL(0); return AckBit; }接收应答
2.MPU6050 (1)寄存器地址(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 (2)MPU6050.c #include "stm32f10x.h" // Device header #include "MyI2C.h" #include "MPU6050_Reg.h" #define MPU6060_ADDRESS 0xD0 void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data) { MyI2C_Start(); MyI2C_SendByte(MPU6060_ADDRESS); MyI2C_ReceiveAck(); MyI2C_SendByte(RegAddress); MyI2C_ReceiveAck(); MyI2C_SendByte(Data); MyI2C_ReceiveAck(); MyI2C_Stop(); } uint8_t MPU6050_ReadReg(uint8_t RegAddress) { uint8_t Byte; MyI2C_Start(); MyI2C_SendByte(MPU6060_ADDRESS); MyI2C_ReceiveAck(); MyI2C_SendByte(RegAddress); MyI2C_ReceiveAck(); MyI2C_Start(); MyI2C_SendByte(MPU6060_ADDRESS | 0x01); MyI2C_ReceiveAck(); Byte = MyI2C_ReceiveByte(); MyI2C_SendAck(1); MyI2C_Stop(); return Byte; } void MPU6050_Init(void) { MyI2C_Init(); MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 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(void) { 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; }初始化先配置MPU6050的寄存器,获取数据就读取MPU6050的寄存器即可
四、硬件读写MPU6050 1.I2C通信外设软件I2C较为灵活,硬件I2C性能较好
I2C框图
I2C基本结构
主机发送
主机接收
2.代码部分配置I2C外设+调用I2C库函数发送数据即可
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data) { I2C_GenerateSTART(I2C2, ENABLE); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2, MPU6060_ADDRESS, I2C_Direction_Transmitter); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C2, RegAddress); 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 Byte; I2C_GenerateSTART(I2C2, ENABLE); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2, MPU6060_ADDRESS, I2C_Direction_Transmitter); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C2, RegAddress); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); I2C_GenerateSTART(I2C2, ENABLE); MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C2, MPU6060_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); Byte = I2C_ReceiveData(I2C2); I2C_AcknowledgeConfig(I2C2, ENABLE); return Byte; } void MPU6050_Init(void) { 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_2, 0x00); MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); MPU6050_WriteReg(MPU6050_CONFIG, 0x06); MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); }其它未展示部分与软件I2C部分相同
其中用到一个超时退出机制,防止等待标志位而标志位迟迟不出现导致程序卡死
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT) { uint32_t TimeOut; TimeOut = 100000; while(I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) { TimeOut --; if (TimeOut == 0) { break; } } }stm32单片机个人学习笔记15(I2C通信协议)由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“stm32单片机个人学习笔记15(I2C通信协议)”
上一篇
C语言04