I2C协议
(一)物理层
1. 原理
I2C 总线,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。
通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
2. 总体特征
连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能。上拉能力即上升沿时间由外部电源和上拉电阻决定, 下降沿由器件OD产生, 速度快。因此IIC的通信速率由上拉能力决定。
I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这 个地址来确定与哪个器件进行通信。
I2C 总线上数据的传输速率在标准模式下可达 100kbit/s ,在快速模式下可达 400kbit/s ,在高速模式下可达 3.4Mbit/s ,连接到总线的接口数量只由总线电容是 400pF 的限制决定。
I2C 总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。
3. 电气限制
最大device数量
IIC协议本身没有严格规定总线上device最大数目, 从理论上看, IIC能挂的device数目取决于能表示的最大地址空间, 在7位地址模式下, 减去0x00地址不可用, 理论上可以挂$2^7 -1 = 127$个设备。
但是IIC规定了总线电容不能超过400pF(具体与通信速率有关如下图)。
由于器件的管脚都是有输入电容的,PCB上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8个器件。
规定电容大小的原因: IIC的OD(开漏)要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。
传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。
上拉电阻的选取
由于I2C接口采用Open Drain机制,器件本身只能输出低电平,无法主动输出高电平,只能通过外部上拉电阻Rp将信号线拉至高电平。
上拉电阻牵扯到两方面问题, 两者相互矛盾:
- 一个是功耗问题, 要求功耗低就得增大电阻(减小电流)
- 一个是速度问题, 要求速度快就得减小电阻(减小总线RC值提高上拉能力)
I2C上拉电阻确定有一个计算公式:
$$R_{min}={Vdd(min)-0.4V}/3mA $$
$$R_{max}=(T/0.874) * C$$
通信速率为100KHz时T=1us, 速率为400KHz时T=0.3us , C为总线电容。
电源电压限制了上拉电阻的最小值 ; 负载电容(总线电容)限制了上拉电阻的最大值
如果上拉电阻值过小,Vcc灌入端口的电流(Ic)变大,这样会导致器件开漏输出的MOS管(三极管)不完全导通($I_b*β<I_c$),由饱和状态变成放大状态,这样端口输出的低电平值增大(I2C协议规定,端口输出低电平的最高允许值为0.4V)。
如果上拉电阻过大,加上线上的总线电容,由于RC影响,会带来上升时间的增大(下降沿是芯片内的晶体管,是有源驱动,速度较快;上升沿是无源的外接电阻,速度慢),而且上拉电阻过大,即引起输出阻抗的增大,当输出阻抗和负载的阻抗可以比拟的时,则输出的高电平会分压而减少。
I2C协议还定义了串联在SDA、SCL线上电阻Rs。该电阻的作用是,有效抑制总线上的干扰脉冲进入从设备,提高可靠性。这个电阻的选择一般在100~200ohm左右。当然,这个电阻并不是必须的,在恶劣噪声环境中,可以选用。
(二)协议层
1. 起始和停止条件
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。
起始和结束信号总是由主设备产生。
- 总线在空闲状态 时,SCL和SDA都保持着高电平,
- 当SCL为高而SDA由高到低的跳变,表示产生一个起始条件;
- 当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。
- 在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;
- 而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
2. 数据有效性
SDA线上的数据必须在SCL的高电平时保持稳定。
SDA线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。
发送到 SDA 线上的每个字节必须为 8 位,每次传输可以发送的字节数量不受限制 ,每个字节后必须跟一个响应位 ,首先传输的是数据的最高位(见下图MSB )
如果从机要完成一些其他功能后(如中断)才能接收或发送下一个完整的数据字节 ,可以使时钟线 SCL 保持低电平迫使主机进入等待状态 ,当从机准备好接收下一个数据字节时释放时钟线 SCL, 数据传输继续。
3. 响应
数据传输必须带响应,相关的响应SCL时钟脉冲由主机产生,在响应的时钟脉冲期间,发送器释放 SDA 线(输出高阻态使SDA线被上拉电阻拉高)。在响应的时钟脉冲期间,接收器必须将 SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。 必须考虑建立和保持时间。
4. 寻址
7位地址格式
在起始条件 S 后 ,发送了一个从机地址SLAVE ADDRESS, 这个地址共有 7 位,紧接着的第 8 位是数据方向位[R/W], 0 表示写,1表示读。接下来的一个bit是应答位NACK/ACK,当这个帧中前面8bits发送完后,接收端获得SDA控制权,此时接收设备应该在第9个时钟脉冲之前回复一个ACK(将SDA拉低)以表示接收正常,如果接收设备没有将SDA拉低,则说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由master来决定如何处理(stop或repeated start condition)。
10位地址格式
10 位从机地址是由在起始条件 S 或重复起始条件 Sr 后的头两个字节组成。
第一个字节的头 7 位是 11110XX 的组合 ,其中:最后两位 XX 是 10 位地址的两个最高位 MSB
第一个字节的第 8 位是 R/ W 位, 决定了报文的方向 :0 表示写, 1 表示读。
如果 R/ W 位是 0 则下一个字节是 10 位从机地址剩下的 8 位;
如果 R/ W 位是 1 则下一个字节是从机发送给主机的数据。
仲裁过程
主机只能在总线空闲的时侯启动传输。两个或多个主机可能在起始条件的最小持续时间内产生一个起始条件,结果在总线上产生一个规定的起始条件。
当 SCL 线是高电平时,仲裁在 SDA 线发生。这样,在其他主机发送低电平时,发送高电平的主机将退出竞争, 因为总线上的电平与它自己的电平不相同。
仲裁可以持续多位。第一个阶段是比较地址位,如果每个主机都尝试寻址相同的器件,当主机作发送器时仲裁会继续比较数据位,当主机作接收器时仲裁会继续比较响应位。
IIC总线的地址和数据信息由赢得仲裁的主机决定,因此在仲裁过程中不会丢失信息。
丢失仲裁的主机可以产生时钟脉冲,直到丢失仲裁的该字节末尾。
可以看到在起始信号的第3个时钟周期低电平时,DATA1的SDA输出高电平,而DATA2向SDA输出低电平,SDA总线保持低电平,仲裁给到DATA2,即DATA1的主机退出竞争,不再发送数据。DATA1主机赢得仲裁,SDA总线已传送的数据和DATA1发送的数据保持一致。
5. 读数据
7位寻址为例 CPU作为主接收器
第一步,主机发送一个起始信号S。
第二步,主机发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+R/W。最低位为1表示读,为0表示写。
第三步,从机产生一个ACK应答信号。
//第四步,主机发送寄存器地址。
//第五步,从机产生一个ACK应答信号。
//第六步,主机再次发送一个起始信号。
//第七步,主机发送7bit从机地址,即7bit+R/W。最低位为1表示读,为0表示写。
//第八步,从机产生一个ACK应答信号。
第九步,主机读取一个字节(8bit)的数据(相当于从机发送一个字节)。
第十步,CPU产生一个ACK应答信号。
//第十一步,读取一个CRC校验码。
第十二步,CPU产生一个NACK无应答信号。
第十三步,主机产生一个停止信号。
常规的使用就是第一、二、三、九、十、十二、十三步。 注释掉的是单片机继承的硬件IIC整个完整的通讯过程。
6. 写数据
CPU作为主发送器
第一步,主机发送一个起始信号。
第二步,发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+R/W。最低位为1表示读,为0表示写。
第三步,从机产生一个ACK应答信号。
//第四步,主机发送寄存器地址,8bit数据。
//第五步,从机产生一个ACK应答信号。
第六步,主机发送一个字节(8bit)数据。
第七步,从机产生一个ACK应答信号。
//第八步,主机发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。
第九步,从机既可以发送一个应答信号,也可以发送一个无应答信号。
第十步,主机发送一个停止信号。
(三)单片机IIC通讯
1. 软件模拟
CPU与EEPROM通讯为例
相关函数:
void I2C_Delay(void); //延时,以防单片机速度太快
void I2C_Start(void); //起始信号
void I2C_Stop(void); //终止信号
u8 I2C_SendByte(u8 data); //写一个字节
u8 I2C_ReadByte(void); //读一个字节
void EEPROM_Write(u8 address, u8 data); //往EEPROM的一个地址写一个数据
u8 EEPROM_Read (u8 address); //读EEPROM的一个地址的数据
stm32硬件I2C配置流程:
-
初始化GPIO
-
void I2c_Init(void); //初始化GPIO
-
模拟总线SDA, SCL ,GPIO引脚开漏输出
-
-
写起始信号函数
-
void I2C_Start(void); //起始信号
-
SDA, SCL拉高 , 延时
SDA = 1; SCL = 1; delay();
-
SDA由高变低 , 延时
SDA = 0; delay();
-
SCL拉低,延时等待发送/接收
SCL = 0; delay();
-
-
写终止信号函数
-
void I2C_Stop(void); //终止信号
-
SCL拉低后将SDA拉低 (SCL低电平时SDA可变),延时
SCL = 0; SDA = 0; delay();
-
SCL拉高 , 延时
SCL = 1; delay();
-
SDA由低变高 , 延时
SDA = 1; delay();
-
-
等待应答信号函数
u8 I2C_WaitToAck(void); //读取器件ACK应答
-
产生/不产生ACK应答
void I2C_Ack(void);
void I2C_NoAck(void);
-
写一个字节
-
读一个字节
//延时
static void I2c_Delay(void)
{
/*CPU主频72MHz
10: SCL频率205KHz
7: SCL频率347KHz,高电平1.5us,低电平2.87us;
5: SCL频率421KHz,高电平1.25us,低电平2.375us;*/
u8 i;
for(i = 0; i < 10; i++);
}
//起始信号
void I2C_Start(void)
{
I2C_SDA_High(); //SDA=1
I2C_SCL_High(); //SCL=1
I2C_Delay();
I2C_SDA_Low();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
}
//终止信号
void I2C_Stop(void)
{
I2C_SDA_Low();
I2C_SCL_High();
I2C_Delay();
I2C_SDA_High();
I2C_Delay();
}
//写一个字节
u8 I2C_SendByte(uint8_t Byte)
{
uint8_t i;
/* 先发送高位字节 */
for(i = 0 ; i < 8 ; i++)
{
if(Byte & 0x80)
{
I2C_SDA_High();
}
else
{
I2C_SDA_Low();
}
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
if(i == 7)
{
I2C_SDA_High(); /* 释放SDA总线 */
}
Byte <<= 1; /* 左移一位 */
I2C_Delay();
}
}
//读取一个字节
u8 I2C_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 先读取最高位即bit7 */
value = 0;
for(i = 0 ; i < 8 ; i++)
{
value <<= 1;
I2C_SCL_High();
I2C_Delay();
if(I2C_SDA_READ())
{
value++;
}
I2C_SCL_Low();
I2C_Delay();
}
return value;
}
//产生一个ACK应答信号
void I2C_Ack(void)
{
I2C_SDA_Low();
I2C_Delay();
I2C_SCL_High(); //cpu产生一个时钟
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
I2C_SDA_High();
}
//产生一个非ACK信号
void I2C_NoAck(void)
{
I2C_SDA_High();
I2C_Delay();
I2C_SCL_High(); //cpu产生一个时钟
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
}
//产生时钟读取器件ACK应答信号
u8 I2C_WaitToAck(void)
{
u8 redata;
I2C_SDA_High(); //CPU释放SDA总线
I2C_Delay();
I2C_SCL_High(); //SCL=1此时器件会返回ACK应答
I2C_Delay();
if(I2C_SDA_READ()) //CPU读取SDA口线状态
{
redata = 1;
}
else
{
redata = 0;
}
I2C_SCL_Low();
I2C_Delay();
return redata;
}
2. 硬件外设(stm32)
(1)主发送器
(2)主接收器
(3)配置流程
与EEPROM通讯为例
- 初始化IIC相关GPIO
- 配置IIC外设的工作模式
- 编写IIC写入EEPROM的函数
- 编写IIC读取EEPROM的函数
- 使用read函数及write函数进行读写校验
- 编写page write及seq read函数并校验(页写)
(4)重要函数
- IIC初始化结构体
typedef struct{
uint32_t I2C_ClockSpeed; //设置SCL时钟频率 <40,000
uint32_t I2C_Mode; //工作模式 I2C模式或SMBUS模式
uint32_t I2C_DutyCycle; //指定时钟占空比 低/高=2/1或16/9
uint32_t I2C_OwnAddress1; //指定自身的I2C设备地址
uint32_t I2C_Ack; //使能或关闭响应(一般使能)
uint32_t I2C_AcknowledgedAddress; //指定地址长度 7位/10位
} I2C_InitTypeDef;
参考资料:
原理:《IIC总线协议中文版PDF》
设备数量: 百度知道
上拉电阻: CSDN
代码实现:博客园
学习:野火、正点原子、普中科技
修改时间:2022年01月07日
标签:SCL,总线,void,通信协议,详解,SDA,IIC,主机,I2C From: https://www.cnblogs.com/Ethan-Code/p/16783114.html