目录
I2C作为一个通讯协议,它必须在硬件和软件上,都做出规定,用硬件规定连接线路,用软件规定操作总线
1.所有I2C设备(主机和从机)的SCL连在一起,SDA连在一起(接线要求)
3.SCL 和 SDA 各添加一个上拉电阻,阻值一般为4.7KΩ左右(弱上拉)
前言
最近学习总结,对I2C通信协议有大概的了解,花了点时间总结,很高兴能分享出来,感谢支持,有空互三!
希望对大家能有所帮助,欢迎大家指教。
一.I2C协议简介
I2C 也叫 I²C、IIC (Inter-Integrated Circuit) ,集成电路总线是由 Phiilps 公司开发的两线式串行总线,用于连接微控制器及其外围设备,可以实现单片机与外部硬件设备之间的数据传输、控制和监测等功能,采用同步半双工通信方式,I2C 是带数据应答的协议,支持总线挂载多设备(一般采用 “一主多从” 模型本文章介绍的就是该模型),I2C协议的核心是通过两条信号线实现设备之间的数据传输。这两条信号线分别是:
两条信号线:
SDA(Serial Data,串行数据线)
SDA线半双工通信方式用于传输数据,一根线兼具发送和接收,是I2C通信中的数据通道
SCL(Serial Clock,串行时钟线)
I2C 采用同步的时序,SCL线用于提供时钟信号,是I2C通信中的时钟通道
对同步半双工通信有疑问的可以看这篇文章:
51单片机--串口通信(详细教程)_51单片机串口通信-CSDN博客
I2C作为一个通讯协议,它必须在硬件和软件上,都做出规定,用硬件规定连接线路,用软件规定操作总线
I2C协议硬件规定
一主多从模型中协议的规定
- 主机的权利很大,在任何时刻下,主机对SCL线都是完全的控制,另外在空闲状态下,主机也可以主动发起对SDA线的控制,只有在从机发送数据或从机应答时,主机才会转交SDA的控制权给从机。
- 从机的权利比较小,在任何时刻下对SCL线都只能被动的读取,从机不允许控制SCL线,对于SDA线,从机不允许主动发起对SDA线的控制,只有主机主动转交SDA线控制权给从机,才能得到暂时的控制权 。
硬件电路
1.所有I2C设备(主机和从机)的SCL连在一起,SDA连在一起(接线要求)
2.设备的SCL和SDA均要配置成开漏输出模式
开漏输出模式的工作原理
在开漏输出模式下,输出引脚可以处于两种状态:
- 低电平(0):输出引脚被拉低,电流从电源通过引脚流向地。
- 高电平(1):输出引脚处于高阻态(High-Z),不提供电流,信号线的高电平由外部上拉电阻提供。
这种模式的关键在于 ➡️ 信号线的高电平状态是由外部上拉电阻维持的,而不是由输出引脚直接驱动的。因此,多个设备可以共享同一信号线,而不会发生冲突。
弱上拉开漏输出的好处:
- 杜绝电源短路现象
- 避免引脚模式频繁切换
- 产生“线与”现象,有任意一个设备低电平总线就是低电平,只有所有设备都高电平总线才是高电平,利用此特性可以执行多主机模式下的时钟同步和总线仲裁。
3.SCL 和 SDA 各添加一个上拉电阻,阻值一般为4.7KΩ左右(弱上拉)
结合上述电路连接的3个要求
这样设计的原因的👀是主机在接收时输入,发送时输出,从机也一样,如果时序没协调好,就可能会发生两引脚同时处于输出,并产生短路。所以I2C设计为禁止所有设备输出强上拉的高电平,所以设备的SCL和SDA均要配置成开漏输出模式,还有SCL和SDA需各添加一个上拉电阻,阻值一般为4.7kΩ左右。开漏输出就是不输出电压,控制输出低电平0时引脚接地,控制输出高电平1时引脚既不输出高电平,也不输出低电平,为高阻态(不能正常输出1)。
从机地址
在I2C(Inter-Integrated Circuit)通信协议中,从机地址(Slave Address)是用于唯一标识总线上的从设备(Slave Device)的重要参数。主设备(Master Device)通过从机地址来选择与之通信的从设备。
从机地址的格式
I2C协议支持两种地址格式:
-
7位地址:这是最常用的地址格式。7位地址加上一个读/写位(R/W),总共8位。
-
10位地址:虽然较少使用,但I2C协议也支持10位地址。10位地址加上一个读/写位,总共11位。
7位地址格式
-
地址长度:7位地址。
-
读/写位:第8位是读/写位(R/W),用于指示操作类型:
-
0:写操作(Write),主设备向从设备发送数据。
-
1:读操作(Read),主设备从从设备读取数据。
-
10位地址格式
-
地址长度:10位地址。
-
读/写位:第11位是读/写位(R/W),用于指示操作类型:
-
0:写操作(Write),主设备向从设备发送数据。
-
1:读操作(Read),主设备从从设备读取数据。
-
-
总长度:10位地址 + 1位读/写位 = 11位。
从机地址的分配
- 固定地址:许多I2C设备在出厂时已经分配了固定的7位或10位地址。这些地址通常在设备的数据手册中指定。
- 可配置地址:一些I2C设备允许通过引脚或寄存器配置地址。这使得多个相同类型的设备可以连接到同一总线上,而不会发生地址冲突。
如MPU6050是一款常用的三轴加速度和陀螺仪传感器,它通过I2C协议与主控制器通信。为了避免I2C总线上的地址冲突,MPU6050采用了以下方法来配置其从机地址:
MPU6050的I2C地址由其AD0引脚的状态决定。AD0引脚可以连接到地(GND)或电源(VCC),从而改变其I2C地址:
I2C协议软件规定
I2C时序基本单元
在 I2C 总线处于空闲状态时,SCL 和 SDA 都处于高电平状态🙌
// 初始化I2C接口
void IIC_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
GPIO_InitTypeDef GPIO_InitStruct; // 定义GPIO初始化结构体
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 设置为开漏输出模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; // 设置引脚为SCL和SDA
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA
// IIC处于空闲状态,释放SDA、SCL控制线
GPIO_SetBits(GPIOA, GPIO_Pin_3 | GPIO_Pin_4); // 将SCL和SDA设置为高电平
}
起始条件
SCL高电平期间,SDA从高电平切换到低电平
// 设置SCL(串行时钟线)引脚的电平状态
void IIC_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_3, (BitAction)BitValue); // 写入SCL引脚的电平状态
Delay_us(10); // 短暂延时,确保从机能够跟上引脚状态的变化
}
// 设置SDA(串行数据线)引脚的电平状态
void IIC_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); // 写入SDA引脚的电平状态
Delay_us(10); // 短暂延时,确保从机能够跟上引脚状态的变化
}
// 读取SDA引脚的电平状态
uint8_t IIC_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4); // 读取SDA引脚的电平状态
Delay_us(10); // 短暂延时,确保从机能够跟上引脚状态的变化
return BitValue; // 返回读取到的电平状态
}
// 生成I2C起始信号
void IIC_Start(void)
{
IIC_W_SDA(1); // 将SDA设置为高电平
IIC_W_SCL(1); // 将SCL设置为高电平
IIC_W_SDA(0); // 将SDA拉低,产生起始信号
IIC_W_SCL(0); // 保持SCL为低电平
}
终止条件
SCL高电平期间,SDA从低电平切换到高电平
// 生成I2C停止信号
void IIC_Stop(void)
{
// 对SDA先拉低,确保SDA能够产生上升沿
IIC_W_SDA(0); // 将SDA拉低
IIC_W_SCL(1); // 将SCL设置为高电平
IIC_W_SDA(1); // 释放SDA,产生停止信号
}
发送一个字节
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节(SCL低电平SDA放数据,SCL高电平SDA读数据)
// 发送一个字节的数据
void IIC_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++) // 循环8次,发送8位数据
{
IIC_W_SDA(Byte & (0x80 >> i)); // 发送最高位
IIC_W_SCL(1); // 将SCL设置为高电平,允许数据传输
IIC_W_SCL(0); // 将SCL设置为低电平,准备发送下一位数据
}
}
接收一个字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
// 接收一个字节的数据
uint8_t IIC_ReceiveByte(void)
{
uint8_t Byte = 0x00; // 初始化接收到的字节
uint8_t i;
IIC_W_SDA(1); // 将SDA设置为高电平,准备接收数据
for (i = 0; i < 8; i++) // 循环8次,接收8位数据
{
IIC_W_SCL(1); // 将SCL设置为高电平,开始接收数据
Byte = (Byte << 1) | IIC_R_SDA(); // 读取SDA引脚的电平状态,并左移一位
IIC_W_SCL(0); // 将SCL设置为低电平,准备接收下一位数据
}
return Byte; // 返回接收到的字节
}
注:其中SDA线实线部分是主机控制的电平,虚线部分是主机控制的电平,该过程变化形象的展示了主机主动转交SDA线控制权给从机。
发送应答
主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
// 发送应答信号
void IIC_SendAck(uint8_t AckBit)
{
// 0表示应答,1非应答
IIC_W_SDA(AckBit); // 设置SDA引脚的电平状态
IIC_W_SCL(1); // 将SCL设置为高电平,发送应答信号
IIC_W_SCL(0); // 将SCL设置为低电平,结束应答信号
}
接收应答
主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
// 接收应答信号
uint8_t IIC_ReceiveAck(void)
{
uint8_t AckBit;
IIC_W_SDA(1); // 将SDA设置为高电平,准备接收应答信号
IIC_W_SCL(1); // 将SCL设置为高电平,开始接收应答信号
// 0表示应答,1非应答
AckBit = IIC_R_SDA(); // 读取SDA引脚的电平状态
IIC_W_SCL(0); // 将SCL设置为低电平,结束接收应答信号
return AckBit; // 返回接收到的应答信号
}
---------------------------------------------------------------------------------------------------------------------------------
➡️ 观察每一个I2C时序基本单元,每一个时序基本单元完成其相对应的操作,主机都会拉低拽下SCL,一方面是占用这个总线,另一方面是为了方便这些基本单元的拼接。
➡️ 我们保证了除起始和终止条件外,每个时序单元的SCL都是以低电平开始,低电平结束,然后方便这些单元可以拼接起来,SCL可以可以续的上。
I2C完整时序类型
指定地址写
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
分析:起始条件下,I2C发送的第一个字节的定义是高7位表示从机地址,最低位表示读写位,0表示写入操作,1表示读就是出操作,紧跟的单元就是接受从机应答位,第二个字节就可以送到指定设备的内部,一般是寄存器地址或指令控制字等,接着从机应答,第三个字节就可以是寄存器地址下主机所想写入的内容,如果主机不需要再传输了,就可以产生停止条件。
void IIC_WriteReg(uint8_t RegAddress, uint8_t Data)
{
IIC_Start();
IIC_SendByte(Peripheral_Addr);
IIC_ReceiveAck();
IIC_SendByte(RegAddress);
IIC_ReceiveAck();
IIC_SendByte(Data);
IIC_ReceiveAck();
IIC_Stop();
}
当前地址读
对于指定设备,在当前地址指针指示的地址下,读取从机数据
分析:起始条件下,I2C发送的第一个字节的定义是高7位表示从机地址,最低位表示读写位,读写位置0,紧跟的单元就是接受从机应答位,开始第二个字节时,主机将SDA的控制权转交给从机,主机接受其所发送的字节数据,此时读取数据的地址为当前指针所指示的地址,但做不到指定地址进行读取数据。
uint8_t IIC_Read()
{
uint8_t Data;
IIC_Start();
IIC_SendByte(Peripheral_Addr);
IIC_ReceiveAck();
Data = IIC_ReceiveByte();
IIC_SendAck(1);
IIC_Stop();
return Data;
}
指定地址读
对于指定设备,在指定地址下,读取从机数据
分析:该I2C时序是由指定地址写和当前地址读复合而成的,先由指定地址写的前半部分的指定地址时序来确定读取数据的地址,在加上当前地址读时序对指定地址进行读取操作 ➡️(因为是两个时序合成而成的复合时序,其中会存在两个起始条件)。
uint8_t IIC_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
IIC_Start();
IIC_SendByte(Peripheral_Addr);
IIC_ReceiveAck();
IIC_SendByte(RegAddress);
IIC_ReceiveAck();
IIC_Start();
IIC_SendByte(Peripheral_Addr| 0x01);
IIC_ReceiveAck();
Data = IIC_ReceiveByte();
IIC_SendAck(1);
IIC_Stop();
return Data;
}
总结
以上就是学习的关于I2C通信协议的相关内容,通过本文的介绍,我们就学习到I2C的一些常用知识,构成此通信协议最基本的时序单元等,但是在应用实现的过程中理解IC2协议这个才是最重要的。希望大家收获满满,在此感谢大家的观看!!!
最后如果有什么疑问或者文章内容有错的来评论区一起讨论讨论!!!