Skip to content

LV001-I2C简介

一、I2C简介

1. I2C介绍

I2C 就是 I2C ( Inter-Integrated Circuit )总线,是一种多主从架构串行通信总线。在1980年由 PHILIPS (飞利浦)公司开发的两线式串行、同步、半双工、多主多从的通信总线,用于连接微控制器及其外围设备。它可以让主板、嵌入式系统或手机连接低速周边设备, 如今在嵌入式领域是非常常见。

它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送数据, 标准 I2C 总线传输速率可到 100kbps ,高速 I2C 总线一般可达 400kbps 以上。

主要用于近距离、低速的芯片之间的通信; I2C 总线有两根双向的信号线一根数据线 SDA 用于收发数据,一根时钟线 SCL 用于通信双方时钟的同步; I2C 总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用。

image-20220903122402843

每个接到I2C总线上的器件都有一个唯一的地址,主机与其他器件进行数据传输的时候,在I2C总线上发送数据的称之为发送器,总线上接收数据的器件被称为接收器

2. 模拟I2C与硬件I2C

因为I2C协议比较简单,常常用GPIO来模拟I2C时序,这种方法称为模拟I2C。如果使用MCU的I2C控制器,设置好I2C控制器, I2C控制器就自动实现协议时序,这种方式称为硬件I2C。因为I2C设备的速率比较低,通常两种方式都可以,模拟I2C方便移植,硬件I2C工作效率相对较高。

3. I2C 总线特点

(1)I2C 总线是一种多主机总线,连接在 I2C 总线上的器件分为主机和从机两种,主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时, I2C 也具备冲突检测和仲裁的功能来决定由哪个设备占用总线,防止错误产生。

(2)每个连接到 I2C 总线上的器件都有一个唯一的地址( 7bit ),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作; I2C 总线在通信时总线上发送数据的器件称为发送器,接收数据的器件称为接收器

(3)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态, 而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

(4)具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s , 高速模式下可达 3.4Mbit/s ,但目前大多 I2C 设备尚不支持高速模式。

连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。

4. 上拉电阻

4.1 为什么需要上拉电阻?

image-20220903122435393

我们见到的I2C总线都是要连接上拉电阻的,为什么?简单来说,核心原因在于:I2C的SDA和SCL引脚是“开漏输出”或“开集输出”。他们只能将总线拉成低电平,不能主动输出高电平。

  • 什么是“开漏输出?

可以把一个开漏输出的内部结构想象成一个简单的开关(实际上是一个MOSFET)串联一个到地的端口。当芯片想输出逻辑‘0’(低电平)时,它就闭合这个开关,将输出线直接连接到GND(地)。这时,线路被强行拉低到0V左右,形成一个明确的低电平。当芯片想输出逻辑‘1’(高电平)时,它就断开这个开关。这时,输出线与GND断开,处于高阻抗状态(可以理解为“悬空”或“浮空”)。它自己没有能力把输出线拉到高电平。

  • 问题来了:如何产生高电平?

既然芯片在输出‘1’时是“悬空”的,那么这条线路上就没有确定的电压,极易受到外界干扰,根本无法传递可靠的‘1’信号。为了解决这个问题,我们就需要外部的帮助。这个“外部的帮助”就是上拉电阻

我们在输出线(SDA和SCL)与电源电压(VCC)之间各接一个电阻。当所有连接到总线上的芯片都断开它们的开关(即都想输出‘1’)时,这个上拉电阻会把线路的电压拉向VCC,从而在总线上产生一个确定的高电平。

当任何一个芯片闭合它的开关(想输出‘0’)时,其开漏输出级会导通,形成一个对地的低阻抗路径。该路径会从电源 VCC 通过上拉电阻 Rp 吸纳电流(即灌电流),从而在 Rp 上产生压降,将总线电压钳位至逻辑低电平。这一过程确保了在存在上拉的情况下,总线仍能被可靠地驱动至有效的低电平状态。另外虽然电流会从VCC通过电阻流到GND,但由于电阻的存在,电流不会无限大,从而保护了电路。

把I2C总线想象成一根弹簧门(比如超市的入口门)。

  • 开漏输出就像是一个人,他只能推门(把门关上,对应输出低电平),但不能拉门(把门打开)。
  • 上拉电阻就像是门上自带的一根弹簧,总是试图把门拉开(对应拉到高电平)。
  • 当没有人推门时,弹簧会把门保持在全开的状态(高电平)。
  • 当有人想关门时,他只需要推门,克服弹簧的拉力,就能把门关上(低电平)。

4.2 一般多大?

上拉电阻的阻值没有一个绝对固定的值,它需要在一个范围内进行权衡选择。典型值通常在 1kΩ 到 10kΩ 之间,常见的是 4.7kΩ

4.2.1 选择依据是什么?

电阻值的选择主要是在速度功耗之间取得平衡,并受总线电容的影响。

  • 1. 电阻不能太小(有下限)

原因:功耗。 当总线为低电平时,会形成一个从VCC通过上拉电阻到GND的电流通路。根据欧姆定律 I = VCC / Rp,电阻越小,电流越大。例如,在5V系统中使用1kΩ电阻,低电平时电流为 5V / 1000Ω = 5mA。如果总线长时间保持低电平,这个功耗是不可忽视的。对于电池供电设备,这尤其重要。此外,过大的电流也可能超过芯片输出级的最大灌电流能力。

结论: 电阻不能太小,否则功耗大,可能损坏芯片。

  • 2. 电阻不能太大(有上限)

原因:速度。 总线不是理想的,它存在对地的寄生电容(来自导线、芯片引脚等)。这个电容(Cb)和上拉电阻(Rp)构成了一个RC低通滤波电路。当信号从低电平变为高电平时,VCC通过Rp给Cb充电。充电的速度由时间常数 τ = Rp * Cb 决定。Rp越大,充电越慢,信号上升沿就越平缓。如果上升沿太慢,可能会超过I2C协议规定的时间,导致信号在时钟沿到来时还未达到稳定的高电平,从而引发通信错误。

结论: 电阻不能太大,否则会限制总线的最高通信速度。

4.2.2 如何选择合适的值?
  • 参考数据手册: 首先查看使用的MCU或器件的数据手册,它通常会给出一个建议范围。

  • 考虑总线电容: 估算总线的寄生电容。线路越长、连接的设备越多,电容就越大(通常估计在100-400pF之间)。

  • 根据速度需求:

(1)标准模式(100kbps): 对电阻要求宽松,4.7kΩ或10kΩ非常常见。

(2)快速模式(400kbps): 需要更小的电阻来保证边沿速度,常用2.2kΩ或4.7kΩ。

(3)高速模式(1Mbps以上): 通常需要更小的电阻(如1kΩ)和更复杂的设计。

4.3 有什么好处?

  • “线与”功能: 由于所有设备都是开漏输出并共享上拉电阻,这意味着只要任何一个设备输出低电平(‘0’),整条总线就是低电平。只有所有设备都输出高电平(‘1’)时,总线才是高电平。这实现了一个硬件上的“与”逻辑,对于多主机的仲裁和时钟同步机制至关重要。
  • 防止总线冲突: 如果两个主机同时发送数据,一个发‘0’,一个发‘1’,发‘0’的设备会成功地将总线拉低,而发‘1’的设备在检测到总线状态与自己预期不符时,会知道发生了冲突并退出。如果没有这个“线与”特性,一个设备输出高电平,另一个输出低电平,将会形成电源到地的短路,烧毁芯片。
  • 兼容不同电压的设备: 总线上的高电平电压由上拉电阻所接的电源(VCC)决定。这意味着一个3.3V的设备和一个5V的设备可以很容易地共享同一条I2C总线,只要它们各自的开漏输出能承受对方的上拉电压即可。

二、基本原理?

1. 一个实例

关于I²C协议,我在学习的时候在一个资料上看到了一个例子,大概就是老师( MCU)将球(数据)传给众多学生中的一个(众多外设设备中的一个)。

image-20230502083209035
  • 首先老师将球踢给某学生, 即主机发送数据给从机,步骤如下:

(1)老师:开始了(start);

(2)老师: A!我要发球给你! (地址/方向);

(3)学生A:到! (回应);

(4)老师把球发出去(传输) ;

(5)A收到球之后,应该告诉老师一声(回应) ;

(6)老师:结束(停止) ;

  • 接着老师让学生把球传给自己,即从机发送数据给主机,步骤如下:

(1)老师:开始了(start);

(2)老师: B!把球发给我! (地址/方向);

(3) 学生B:到!

(4)B把球发给老师(传输) ;

(5)老师收到球之后,给B说一声,表示收到球了(回应) ;

(6)老师:结束(停止) 。

2. 实例总结

从上面的例子可知, 都是老师(主机) 主导传球, 按照规范的流程(通信协议),以保证传球的准确性,收发球的流程总结如下:

(1)老师说开始了,表示开始信号(start);

(2)老师提醒某个学生要发球,表示发送地址和方向(address/read/write);

(3)该学生回应老师(ack);

(4)老师发球/接球,表示数据的传输;

(5)收到球要回应:回应信号(ACK);

(6)老师说结束,表示I2C传输结束(P)。

以上就是I2C的传输协议,如果是软件模拟I2C,需要依次实现每个步骤。 因此,还需要知道每一步的具体细节,比如什么时候的数据有效, 开始信号怎么表示。

三、I2C 协议相关术语

这一部分会结合实例代码来说明,主要是通过模拟I2C来学习。

1. 相关宏定义

下边的软件模拟的平台为 STM32F103ZET6 ,所出现的宏定义有:

c
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} /* 设置GPIO为输入 */
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} /* 设置GPIO为输出 */

//IO操作函数	 
#define IIC_SCL    PBout(6) /* SCL时钟线 */
#define IIC_SDA    PBout(7) /* SDA输出 */	 
#define READ_SDA   PBin(7)  /* SDA输入 */

2. 空闲状态

I2C 总线的 SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态(对外为高阻态),即释放总线,由两条信号线各自的上拉电阻把电平拉高。

image-20220903122435393

2. 起始信号和停止信号

2.1相关概念

  • 起始信号(S):当 SCL 为高期间, SDA 由高到低的跳变;起始信号是一种电平跳变时序信号,而不是一个电平信号。

  • 停止信号(P):当 SCL 为高期间, SDA 由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

image-20220903122708365

**【注意】**起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态停止信号产生后总线处于空闲状态

2.2 软件模拟

2.2.1 起始信号模拟
c
/* 产生IIC起始信号 */
void IIC_Start(void)
{
	SDA_OUT();     /* SDA 数据线设置为输出 */
	IIC_SDA=1;	   /* SDA 数据线拉高表示空闲状态 */  
	IIC_SCL=1;     /* SCL 时钟线拉高表示空闲状态 */  
	delay_us(4);   
 	IIC_SDA=0;     /* START:when CLK is high, DATA change form high to low  */
	delay_us(4);
	IIC_SCL=0;     /* 钳住I2C总线(拉低SCL时钟线,SDA数据线也为低),准备发送或接收数据  */
}
2.2.2 停止信号模拟
c
/* 产生IIC停止信号 */
void IIC_Stop(void)
{
	SDA_OUT();   /* SDA 数据线设置为输出 */
	IIC_SCL=0;   /* SCL 时钟线拉低 */  
	IIC_SDA=0;   /* STOP: when CLK is high,DATA change form low to high (为停止信号做准备)*/
 	delay_us(4);
	IIC_SCL=1;   /* SCL 时钟线拉高,并保持高电平 */  
	IIC_SDA=1;   /* SDA 数据线原来为低,现在拉高,产生一个由低到高的跳变,表示I2C总线停止信号 */
	delay_us(4);							   	
}

3. 应答信号

3.1 相关概念

发送器每发送一个字节(8位数据),每次传输后由接收器(从机)反馈一个应答信号,以确认从机是否正常接收了数据。 当主机发送了8位数据后,会再产生一个时钟, 也就是在时钟的第 9 个脉冲(前 8 个表示传输 8 位数据)期间释放 SDA 数据线,主机由输出转为读取,读取SDA电平, 这个时候SDA数据线在上拉电阻的影响下,此时SDA默认为高,必须从机拉低, 以确认收到数据。

  • 应答信号为低电平时,规定为有效应答位( ACK 简称应答位),表示接收器已经成功地接收了该字节。
  • 应答信号为高电平时,规定为非应答位( NACK ),一般表示接收器接收该字节没有成功,非应答位可以用于告诉主机结束数据发送。
image-20220903124720178

当 I2C 主机发送完 8 位数据以后会释放SDA总线,将 SDA 设置为输入状态,等待 I2C 从机应答,也就是等到 I2C 从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败。

【注意】

(1)为了传输一个ACK位,主机端需要产生一个时钟脉冲,也就是第九个时钟脉冲;

(2)对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间SDA为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个 NACK 信号(即不发出ACK信号),以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号 P 。

3.2 软件模拟

3.2.1 产生ACK应答信号
  • 从机应答信号模拟(这种情况一般是从从机读数据,主机产生应答,告诉从机我读完了)
c
/* 产生ACK应答 */
void IIC_Ack(void)
{
	IIC_SCL=0;    /* SCL 时钟线拉低 */
	SDA_OUT();    /* SDA 数据线设置为输出 */
	IIC_SDA=0;    /* SDA 数据线拉低(应答,第9个脉冲期间保持低电平) */
	delay_us(2); 
	/* SCL 时钟线产生一个脉冲(第9个,低->高->低) */
	IIC_SCL=1;    /* SCL 时钟线拉高 */
	delay_us(2);
	IIC_SCL=0;    /* SCL 时钟线拉低 */
}
3.2.2 不产生ACK应答信号
  • 从机非应答信号模拟(这种情况一般是从从机读数据,主机不产生应答,这时候要注意接收应答的一方若是一直等待的话可能就会有问题了)
c
/* 不产生ACK应答 */
void IIC_NAck(void)
{
	IIC_SCL=0;   /* SCL 时钟线拉低 */
	SDA_OUT();   /* SDA 数据线设置为输出 */
	IIC_SDA=1;   /* SDA 数据线拉高(不应答,第9个脉冲期间保持高电平) */
	delay_us(2);
	/* SCL 时钟线产生一个脉冲(第9个)(第9个,低->高->低) */
	IIC_SCL=1;   /* SCL 时钟线拉高 */
	delay_us(2);
	IIC_SCL=0;   /* SCL 时钟线拉低 */
}
3.2.3 等待应答信号
  • 主机等待应答信号(这种情况一般是向从机写数据,从机会产生应答,从机的应答不需要我们来管,读到应答信号的时候表示写入完成,从机收到了数据)
c
unsigned char IIC_Wait_Ack(void)
{
	unsigned char ucErrTime=0;
	SDA_IN();      // SDA设置为输入  
	IIC_SDA=1;delay_us(1);	   
	IIC_SCL=1;delay_us(1);	 
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0; // 时钟输出0 	   
	return 0;  
}

4. 数据传送

4.1 相关规则

I2C 总线进行数据传送时

  • 时钟线 SCL 上的信号为高电平期间:接收器从数据线上读取一位数据,数据线 SDA 上的数据必须保持稳定,不允许发生变化。也就是, SCL为高电平时表示有效数据, SDA为高电平表示“ 1”,低电平表示“0”。

  • 时钟线 SCL 上的信号为低电平期间:数据线上的高电平或低电平状态允许变化,只有在这种情况下发送器才能向数据线上发送一位数据。也就是 SCL为低电平时表示无效数据,此时SDA可以进行电平切换,为下次数据表示做准备。

image-20220903124206035

在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据,数据位的传输是边沿触发。

I2C 总线通信时每个字节为 8 位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送 1 位应答位来回应发送器即一帧共有 9 位。

【注意】数据在 SCL 的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。

4.2 软件模拟

4.2.1 发送一个字节
c
/* 发送1个字节,txd表示要发送的一个字节数据 */		  
void IIC_Send_Byte(unsigned char txd)
{                        
    unsigned char t;   
	SDA_OUT(); 	    
    IIC_SCL=0;             /* 拉低 SCL 时钟线,准备开始数据传输(SCL为低时 SDA上的信号才允许变化) */
    for(t=0;t<8;t++)       /* 循环发送8位数据 */
    {              
        //IIC_SDA=(txd&0x80)>>7;
		if((txd&0x80)>>7)  /* 获取最高位(0x80=1000 0000),将最高位右移7位到最低位。再判断这一位0还是为1 */
			IIC_SDA=1;
		else
			IIC_SDA=0;
		txd<<=1; 	        /* 将下一位左移到最高位,为下一次发送做准备 */  
		delay_us(2);
		/* 数据准备完毕,接下来SCL产生一个 低->高->低 的脉冲变化,完成一位数据发送 */
		IIC_SCL=1;          /* SCL 时钟线拉高 */
		delay_us(2); 
		IIC_SCL=0;	        /* SCL 时钟线拉低 */
		delay_us(2);
    }	 
}
4.2.2 读取一个字节
c
/* 读1个字节,ack=1时,发送ACK,ack=0,发送nACK */   
unsigned char IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();              /* SDA 数据线设置为输入 */
    for(i=0;i<8;i++ )      /* 循环读取8位数据 */
	{
        IIC_SCL=0;         /* SCL 时钟线拉低 */
        delay_us(2);
		IIC_SCL=1;         /* SCL 时钟线拉高(高电平期间从数据线SDA读取1位数据) */
        receive<<=1;       /* 数据左移,因为发送方先发的是高位 */
        if(READ_SDA) receive++;   /* 读到的是1的话,最低位加1 */
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck();        /* 发送nACK(不应答) */
    else
        IIC_Ack();         /* 发送ACK(应答) */   
    return receive;
}

5. 总线仲裁机制

总线上可能挂接有多个器件,有时会发生两个或多个主器件同时想占用总线的情况,这种情况叫做总线竞争。I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,其仲裁原则是:当多个主器件同时想占用总线时,如果某个主器件发送高电平,而另一个主器件发送低电平,则发送电平与此时SDA总线电平不符的那个器件将自动关闭其输出级

总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位的比较,从而确保了竞争仲裁的可靠性。由于是利用I2C总线上的信息进行仲裁,因此不会造成信息的丢失。

6. 地址及数据方向

I2C总线上的每个设备都有自己的独立地址,主机发起通讯时,通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机。设备地址可以是7位或10位。 紧跟设备地址的一个数据位R/W用来表示数据传输方向, 数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。

image-20230507104655753

五、 I2C 读写时序

从机接收到匹配的地址后,主机或从机会返回一个应答( ACK )或非应答( NACK )信号,只有接收到应答信号后,主机才能继续发送或接收数据。

1. I2C 写时序

主机写数据到从机的时序如下图所示:

image-20220903151323709
  • (1)主机发送一个起始信号S,表示启动I2C;
  • (2)发送从机 7 位地址和 1 位读写标志,读写标志为0,表示写;
  • (3)主机释放 SDA 数据线(拉高),便于后续接收从机的回应,若有从机匹配到主机发送的地址,则从机会拉低 SDA 线作为ACK(有效应答);
  • (4)接收到应答后,主机重新拉低 SDA,准备发送数据,然后开始传输8位数据,先发高位,再发低位;
  • (5)8 位数据传输完成后,主机释放SDA线,便于接收从机发出的应答或者非应答信号;
  • (6)从机收到数据后,拉低 SDA数据线作为 ACK (应答信号),告诉主机数据接收成功;
  • (7)主机发出停止信号,表示结束I2C通信。

2. I2C 读时序

主机由从机中读取数据(从机写数据到主机)的时序如下图:

image-20220903151831570
  • (1)主机发送一个起始信号S,表示启动I2C;
  • (2)发送从机7位地址和1位读写标志,读写标志为1表示读;
  • (3)主机释放 SDA 数据线便于后续接收从机的回应,当有从机匹配到地址时,从机会拉低SDA数据线作为 ACK(有效应答);
  • (4)然后从机继续拉高SDA线,准备开始使用SDA数据线传输 8 位数据给主机;
  • (5)8 位数据传输完毕后,从机释放SDA数据线(拉高),便于接收主机的回应;
  • (6)主机接收到数据;
  • (7)主机获得SDA线控制权限,并拉低SDA数据线作为 ACK (有效应答)告诉从机数据接收成功;
  • (8)主机发出停止信号,表示结束I2C。

3. I2C 复合通信

我们操作 I2C 设备的时候,多数情况都是先向设备写入数据,后续就是从设备中读取数据了,主机先向从机发送数据,然后从机再向主机发送数据的时序如下图:

image-20220903153506965

4. 完整传输流程

image-20230502090201529

I2C完整传输流程如下:

(1)SDA和SCL开始都为高, 然后主机将SDA拉低, 表示开始信号;

(2)在接下来的8个时间周期里,主机控制SDA的高低, 发送从机地址。 其中第8位如果为0, 表示接下来是写操作,即主机传输数据给从机; 如果为1,表示接下来是读操作,即从机传输数据给主机; 另外, 数据传输是从最高位到最低位,因此传输方式为MSB( Most Significant Bit)。

(3)总线中对应从机地址的设备,发出应答信号;

(4)在接下来的8个时间周期里,如果是写操作,则主机控制SDA的高低;如果是读操作,则从机控制SDA的高低;

(5)每次传输完成, 接收数据的设备, 都发出应答信号;

(6)最后, 在SCL为高时, 主机由低拉高SDA, 表示停止信号,整个传输结束;