Skip to content

LV010-I2C时序与软件模拟 ​

这一篇讲一次 I2C 传输从开始到结束的完整时序:先用一个直观例子建立整体流程的印象,再逐个定义空闲、起始、停止、应答等信号并给出软件模拟(GPIO 位操作)实现,最后给出写、读、连续读、复合四种完整时序。总线的电气特性(上拉电阻、线与原理)与总线特点见 60-通信专题/01-I2C协议/LV001-I2C简介.md。

一、基本原理 ​

1. 一个实例 ​

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

老师与学生传球类比
  • 首先老师将球踢给某学生, 即主机发送数据给从机,步骤如下:

(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,需要依次实现每个步骤。 因此,还需要知道每一步的具体细节,比如什么时候的数据有效, 开始信号怎么表示。

3. 主机、从机、发送器、接收器 ​

这四个概念描述的是两个不同维度,不能混为一谈:

  • 主机 / 从机:描述总线控制角色。主机发起传输、发送 START/STOP 和设备地址,并产生 SCL;从机被主机寻址后参与传输。
  • 发送器 / 接收器:描述当前字节的数据方向。发送器把 8 位数据放到 SDA 上,接收器读取数据,并在第 9 个时钟给出 ACK 或 NACK。一次通信中,发送器和接收器可以随读写方向改变。

以 STM32 控制 AT24C02 为例,STM32 始终是主机,AT24C02 始终是从机;读数据时不会改变主从角色,SCL 仍由 STM32 产生:

阶段STM32AT24C02谁应答
STM32 发送设备地址和读写方向主机 + 发送器从机 + 接收器AT24C02 回 ACK
STM32 写数据主机 + 发送器从机 + 接收器AT24C02 每收到一个字节后回 ACK
STM32 读数据主机 + 接收器从机 + 发送器STM32 应答:继续读回 ACK,最后一个字节回 NACK 后发送 STOP

因此,第 9 个时钟由谁应答,取决于谁是当前字节的接收器,而不是固定由主机或从机应答。

STM32 与 AT24C02 在写事务和读事务中的主机/从机角色保持不变;发送器与接收器随读写方向互换,第 9 个时钟由当前的接收器给出 ACK 或 NACK

二、信号定义与软件模拟 ​

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

1. 平台与 GPIO 配置 ​

1.1 相关宏定义 ​

下面软件模拟所用的平台是 STM32F103ZET6,涉及的宏定义如下:

c
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)4<<28;} /* 浮空输入,使用外部上拉 */
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)7<<28;} /* 50 MHz 开漏输出 */

//IO操作函数
#define IIC_SCL    PBout(6) /* SCL时钟线,同样须配置为开漏输出 */
#define IIC_SDA    PBout(7) /* SDA输出:写0拉低,写1释放 */
#define READ_SDA   PBin(7)  /* SDA输入 */

STM32F1 的 CRL 配置值 0x7 对应 50 MHz 开漏输出,0x4 对应浮空输入。外部上拉电阻不可省略;若要支持时钟拉伸,还必须在释放 SCL 后回读并等待它实际变高,本文基础示例未实现这一功能。

1.2 为什么 GPIO 要配置成开漏? ​

SDA/SCL 引脚要配置成开漏输出,这是由 I2C 协议决定的,而不是由 MCU 决定的——I2C 规范在电气层就规定总线必须是开漏/开集结构,GPIO 模拟 I2C 时只是在服从这个规定。协议里的三个机制都建立在"只能拉低、不能推高"之上:

(1)应答(ACK):主机发完 8 位后必须释放总线,第 9 个时钟由从机拉低 SDA 表示"收到"。若主机是推挽输出高,从机的 ACK 就成了"拉低对抗推高",两个 MOS 对导形成电源到地的直通电流;

(2)多主仲裁与时钟拉伸:两个主机同时发送,一个发 1 一个发 0,总线呈现 0,发 1 的一方检测到与总线不符后自动退出——线与是无损仲裁的基础;从机拉住 SCL 时主机只能等待,同样要求主机的"1"是释放而不是推高;

(3)SDA 是双向线:"让出总线"就是输出 1(高阻态),线上由上拉电阻托高,下一个要说话的设备随时能拉低,不存在两个输出级同时驱动的可能。

落到代码上有两种实现方式:

(1)真开漏:把 GPIO 配置成开漏输出模式(STM32 的 GPIO_MODE_OUTPUT_OD,本节寄存器宏使用配置值 0x7)——写 0 拉低、写 1 即释放,读电平直接读 IDR,全程无需切换方向,这是规范形态;

(2)软件等效:输出寄存器始终保持为 0,在"推挽输出 0"与"浮空输入(高阻)"之间切换,分别实现拉低与释放。不能在推挽输出模式下写 1 来代替释放,否则从机拉低 SDA 或拉伸 SCL 时会形成输出冲突。本文示例采用第一种方式,保留方向切换宏只是为了在读数据、等待应答时切换为输入。

硬件 I2C 外设的输出级本身就是开漏结构,这个问题天然不存在——这也解释了"为什么模拟 I2C 的代码里总有一堆方向切换宏,而硬件 I2C 一个都没有"。开漏输出的内部结构与线与原理,详见 LV001-I2C简介 第二章、第三章。

2. 空闲状态 ​

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

I2C 总线空闲状态

3. 起始信号和停止信号 ​

3.1 相关概念 ​

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

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

起始信号与停止信号

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

3.2 软件模拟 ​

3.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数据线也为低),准备发送或接收数据  */
}
3.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 时钟线拉高,并保持高电平 */
	delay_us(4); /* 满足停止信号建立时间 tSU;STO */
	IIC_SDA=1;   /* SDA 数据线原来为低,现在释放,产生一个由低到高的跳变,表示I2C总线停止信号 */
	delay_us(4);
}

4. 应答信号 ​

4.1 相关概念 ​

发送器每发送一个字节(8 位数据),后面紧跟 1 位应答位:由接收器给出应答,用来确认这一字节是否被正常接收——接收器既可能是从机(主机写数据时),也可能是主机(主机读数据时)。发送完 8 位后,发送器释放 SDA(此时由上拉电阻把线拉成高电平),然后在时钟的第 9 个脉冲期间读取 SDA 电平:被拉低即为应答,保持高电平即为非应答。

  • 应答信号为低电平时,规定为有效应答位( ACK 简称应答位),表示接收器已经成功地接收了该字节。

  • 应答信号为高电平时,规定为非应答位(NACK),可表示接收器不能或不希望继续接收,也可由主机在读完最后一个字节后主动发出;NACK 不一定意味着传输出错。

ACK 与 NACK 应答

当主机发送完 8 位数据以后会释放 SDA 总线,把 SDA 设置为输入状态,等待应答。应答信号由接收器发出——主机写数据时是某个从机,主机读数据时则是主机自己。应答所需的时钟由主机提供:主机发完 8 位后紧跟的第 9 个时钟脉冲就是给应答位用的。接收器把 SDA 拉低表示应答(通信成功),保持高电平表示非应答。

【注意】

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

(2)有效应答位 ACK 的要求是:接收器在第 9 个时钟脉冲的低电平期间就把 SDA 拉低,并保证在该脉冲的高电平期间 SDA 始终为稳定的低电平(也就是要提前建立好)。如果接收器是主控器,它在收到最后一个字节后应发送 NACK(不拉低 SDA),通知被控发送器结束发送并释放 SDA,以便主机随后发出停止信号 P。

4.2 软件模拟 ​

4.2.1 产生 ACK 应答信号 ​
  • 主机应答信号模拟(主机从从机读数据时,第 9 个时钟由主机拉低 SDA,告诉从机"这一字节收到了,请继续发";真正表示"读完了"的是下面的 NACK)
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 时钟线拉低 */
}
4.2.2 不产生 ACK 应答信号 ​
  • 主机非应答信号模拟(主机读数据时,最后一个字节读完就发 NACK:不拉低 SDA,告诉从机"到此为止、释放数据线",随后主机发出停止信号 P)
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 时钟线拉低 */
}
4.2.3 等待应答信号 ​
  • 主机等待应答信号(向从机写数据时,由从机在第 9 个时钟把 SDA 拉低作为应答;主机把 SDA 设为输入去读这一位,读到低电平说明从机已经收到该字节,超时读不到则报错并释放总线)
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;
}

5. 数据传送 ​

5.1 相关规则 ​

I2C 总线进行数据传送时

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

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

数据有效性

在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位串行传送。数据是否有效由电平定义——SCL 高电平期间采样,SDA 只允许在 SCL 低电平期间改变;真正按"边沿触发"判定的只有起始(S)和停止(P)两种信号。

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

【注意】SDA 必须在 SCL 上升沿到来之前就准备好(满足数据建立时间 tSU;DAT),并且在整个 SCL 高电平期间保持稳定;除起始信号和停止信号外,SDA 只能在 SCL 为低电平时改变。

上面说的"发送器"既可能是主机也可能是从机:主机读数据时,往 SDA 上放这 8 位的其实是从机。但无论方向怎么换,时钟永远只由主机产生——从机的 SCL 引脚只有输入,没有驱动时钟的能力。方向反转后两边的具体分工见 5.3。

5.2 软件模拟 ​

5.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);
    }
}
5.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.3 方向反转:谁打时钟,谁放数据 ​

5.2 的两段代码默认"主机发、从机收"。一旦方向反过来(主机读 AT24C02),很容易冒出一个疑问:数据既然是从机发出来的,SCL 是不是也该由从机产生?

答案是不。SCL 在任何时刻都只由主机驱动,从机的 SCL 引脚只有输入。从机做的事只有一件:把 SCL 当成移位时钟,在它的节拍下把自己的数据一位一位"放"到 SDA 上,收到 ACK 后再把地址计数器 +1。它既不能决定快慢,也不能自己开始或结束——协议甚至允许主机在任意位置把 SCL 停住,从机就跟着停在原地,这也是软件模拟可以随手插入 delay_us() 的原因。

(1)驱动权按位交接。SDA 是一根双向线,同一时刻只能有一方驱动:

阶段SCL 谁驱动SDA 谁驱动
8 个数据位主机从机
第 9 位(应答)主机主机:拉低为 ACK,释放为 NACK

这就是 IIC_Read_Byte() 一进来就 SDA_IN() 的原因——把引脚切成浮空输入,等于把 SDA 让给从机;这 8 个时钟里主机只操纵 SCL,碰都不碰 SDA。收完 8 位后,由 IIC_Ack() / IIC_NAck() 里的 SDA_OUT() 把线抢回来,主机才能在低电平期间拉低或释放 SDA 来应答。

(2)一位数据的完整动作。以主机读为例,四个相位循环一次就是 1 位:

相位SCLSDA谁在动作
准备低从机把当前位放上线并保持稳定从机输出
有效低 → 高保持稳定主机打时钟
采样高主机读走这一位主机读取
换位高 → 低从机换上下一位从机输出

一位数据的四个相位:SDA 只在 SCL 低电平期间换位,主机在 SCL 高电平期间采样;一个字节由 9 个脉冲组成,前 8 位由从机驱动 SDA,第 9 位由主机回应

(3)"从机准备好数据、主机来一个脉冲就传过去"这个理解方向是对的,只需补两点:一是数据由从机在 SCL 低电平期间放上线,由主机在 SCL 高电平期间采样,两边各用半个周期,所以不会打架;二是一个字节要 9 个脉冲(8 位数据 + 1 位应答),不是 8 个。

(4)收尾。最后一个字节收完,主机回 NACK 并释放 SDA,从机据此停止输出、释放数据线,主机随即发 STOP。整个过程里 SCL 的每一个脉冲都由 STM32 打出,AT24C02 从头到尾没有产生过一个时钟。

6. 总线仲裁机制 ​

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

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

总线仲裁所依赖的"线与"电气特性,详见 LV001-I2C简介 第三章。

7. 地址及数据方向 ​

I2C 总线上的每个设备都有自己的独立地址,主机发起通信时先通过 SDA 发送设备地址(SLAVE_ADDRESS)来寻址从机。地址可以是 7 位,也可以用 10 位扩展地址。紧随地址的 1 位 R/W 表示数据方向:为"1"时主机从从机读数据,为"0"时主机向从机写数据(该位是从主机视角命名的)。

地址与数据方向

三、I2C 读写时序 ​

地址匹配后,接收方会在第 9 个时钟返回应答(ACK)或非应答(NACK)信号;只有收到应答,主机才能继续发送或接收数据。

1. I2C 写时序 ​

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

I2C 写时序
  • (1)主机发送一个起始信号S,表示启动I2C;

  • (2)发送从机 7 位地址和 1 位读写标志,读写标志为0,表示写;

  • (3)主机释放 SDA 数据线(拉高),便于后续接收从机的回应,若有从机匹配到主机发送的地址,则从机会拉低 SDA 线作为ACK(有效应答);

  • (4)接收到应答后,主机先把 SCL 拉低,再按首位数据是 0 还是 1 设置 SDA,然后开始传输 8 位数据,先发高位,再发低位;

  • (5)8 位数据传输完成后,主机释放SDA线,便于接收从机发出的应答或者非应答信号;

  • (6)从机收到数据后,拉低 SDA数据线作为 ACK (应答信号),告诉主机数据接收成功;

  • (7)主机发出停止信号,表示结束I2C通信。

2. I2C 读时序 ​

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

I2C 读时序
  • (1)主机发送一个起始信号S,表示启动I2C;

  • (2)发送从机7位地址和1位读写标志,读写标志为1表示读;

  • (3)主机释放 SDA 数据线便于后续接收从机的回应,当有从机匹配到地址时,从机会拉低SDA数据线作为 ACK(有效应答);

  • (4)主机释放 SDA 线后,改由从机驱动 SDA,逐位送出 8 位数据(高位在前);

  • (5)每送完 8 位,从机释放 SDA 线,把第 9 个时钟让给接收方(主机)回应;

  • (6)主机在第 9 个时钟到来前的 SCL 低电平期间设置 SDA:如果还要继续读,就拉低 SDA,并在第 9 个时钟高电平期间保持低电平以回 ACK;如果这是最后一个字节,则释放 SDA,使其保持高电平以回 NACK,表示不再需要数据;

  • (7)主机始终控制 SCL;发出 NACK 后,从机释放 SDA,主机接着发出停止信号 P,结束本次读操作。

3. I2C 连续读时序 ​

实际读 EEPROM 时很少只读 1 个字节:一次读一页、一次读一个结构体都是常事。连续读(Sequential Read)在单字节读的基础上只多了一条规则——每收完一个字节,由主机用应答位决定「还要不要下一个」:

I2C 连续读时序

  • (1)主机发起始信号 S,接着发 0xA0(器件地址 + 写方向)和要读的字节地址,两次都由从机回 ACK。这两步是"伪写",只为把从机内部的地址指针挪到目标位置,并不写入数据;

  • (2)主机发重复起始信号 Sr(而不是停止信号 P),再把方向位换成读(0xA1),从机回 ACK。用 Sr 不用 P 的原因:P 会让从机结束本次会话、地址指针作废,之前的伪写就白做了;

  • (3)从机开始驱动 SDA,逐位送出地址指针所指的那个字节(图中 D0),高位在前;

  • (4)主机收完 8 位后在第 9 个时钟回 ACK,意思是"这一字节收到了,接着送";从机收到后把内部地址指针 +1,接着送出下一个字节 D1;

  • (5)如此重复。读到最后一个字节(Dn)时,主机回 NACK(不拉低 SDA,靠上拉保持高电平),从机据此释放 SDA、停止输出;

  • (6)主机发停止信号 P,本次连续读结束。

图里有三处容易读错,可以对着波形确认:

位置谁驱动含义
0xA0、地址、0xA1 之后的三个 ACK从机地址阶段的应答,说明从机在线、已接受本次寻址
D0、D1 之后的 ACK主机主机的"继续读"指令,每 ACK 一次就多收一个字节
Dn 之后的 NACK主机主机的"到此为止",紧接着必须是 STOP 或 Sr

地址指针自动递增,所以连续读收上来的是一段连续地址的内容:buf[0] 是地址 addr 上的字节,buf[1] 是 addr + 1 上的字节,依此类推。主机不能在中途"跳"到别的地址,想换地址只能重新走一遍 Sr + 地址。

这条规则不是从时序图推出来的,手册里写得很直接(AT24C02B 数据手册 5126H–SEEPR–8/07,第 10 页 8. Read Operations → SEQUENTIAL READ,对应波形见第 12 页 Figure 8-6. Sequential Read):

After the microcontroller receives a data word, it responds with an acknowledge. As long as the EEPROM receives an acknowledge, it will continue to increment the data word address and serially clock out sequential data words. When the memory address limit is reached, the data word address will "roll over" and the sequential read will continue.

译:主机每收到一个数据字就回一个 ACK;只要 EEPROM 收到 ACK,它就把数据字地址加一,继续串行送出下一个数据字;地址到顶后回卷,连续读照常进行。

【注意】读与写的回卷规则不同,手册把两者分开写,别混用:

操作指针走到底之后手册原文
读0xFF → 0x00,整片循环,跨页不回头the last byte of the last memory page to the first byte of the first page
写0x17 → 0x10,只在 8 字节页内循环,超出部分覆盖本页开头the last byte of the current page to the first byte of the same page

所以 8 字节的页边界限制只约束页写,连续读可以一路 ACK 跨页读到底。

连续读与连续写的差别只在"谁来踩刹车":

c
/* 连续读:前 len-1 个字节回 ACK 要求继续,最后一个回 NACK 停止 */
for (i = 0; i < len; i++)
{
    buf[i] = IIC_Read_Byte((i + 1 < len) ? 1 : 0);
}
IIC_Stop();
c
/* 连续写:每个字节后都由从机回 ACK,全部发完由主机发 STOP */
for (i = 0; i < len; i++)
{
    IIC_Send_Byte(buf[i]);
    if (IIC_Wait_Ack())      /* 从机没应答(忙/地址错), 补一个 STOP 收场 */
    {
        IIC_Stop();
        return;
    }
}
IIC_Stop();

读方向想停下来必须靠 NACK(从机不会自己停),写方向想停下来直接发 STOP 即可(从机照常回 ACK)。一句话记法:接收方是主机时,NACK 是刹车;接收方是从机时,STOP 是刹车。

4. I2C 复合通信 ​

操作 I2C 设备时,多数情况是"先写后读":主机先写入寄存器地址或命令,再读取设备返回的数据。这种"写一段 + 读一段"的复合时序如下图,两段之间用重复起始信号 Sr(而不是停止信号 P)切换方向,以保证总线不被其他主机抢走:

I2C 复合通信时序

5. 完整传输流程 ​

I2C 完整传输流程

I2C完整传输流程如下:

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

(2)接下来的 8 个时钟周期由主机控制 SDA:前 7 位是从机地址,第 8 位是读写标志 R/W——为 0 表示接下来主机写数据给从机,为 1 表示主机从从机读数据。地址和数据都是高位先发(MSB first)。

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

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

(5)每传完一个字节,接收数据的设备都在第 9 个时钟回 ACK 或 NACK;主机读取最后一个字节时通常回 NACK,再结束传输;

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

四、示波器波形 ​

1. 写操作波形 ​

将示波器的 CH1、CH2 分别接到 SDA 和 SCL(探头地线必须与设备共地),得到波形并分析如图。注意两张图的探头接法是相反的,看图前先认准哪一路是时钟:

img

图 1:CH1(黄,下方)是 SCL,CH2(青,上方)是 SDA;时基 25.0us/div,两通道均 200mV/div。

按位重绘一遍,读起来更直观:

I2C 写事务波形重绘:起始条件之后是 7 位地址 0111100 加写位 0,从机应答;随后连续两个字节 00101110(0x2E)各带一个应答;一个 SCL 周期 10us,约 100kHz

从图中可知时序如下:

(1)由主机发起,在SCL为高电平时,SDA由高到低切变,形成开始信号;

(2)接着是7位地址和一位读写标志,这里7位地址为0111100,即0x3C;最后一位为0表示写操作;

(3)接着在下一个时钟,主机以高电平状态释放SDA,这时从机响应,将SDA拉低了;

(4)接着连续两个字节都是00101110(即0x2E),每个字节后跟一个从机应答;0x2E正是“.”号的ASCII码,符合预期输出(连发两个点);

(5)后面还有其它数据和最后的停止位,但整屏只有 12 格 × 25us = 300us,装不下整帧,图右侧停在 SCL 低电平、SDA 低电平,所以看不到停止位。

从图中可知,纵向一格是200mV,SDA、SCL 的摆幅约 1.8 格,即 1.8 × 200mV ≈ 350mV;因为探头衰减开关拨在 ×10,而示波器端仍按 1× 显示,所以实际电平约为 3.5V(理论值 3.3V,偏高约 6%,属探头未校准或电源实测偏高)。横向一格是25us,10个时钟周期大概用了4格,即 4 × 25us = 100us,平均每个时钟周期是10us,可算出 SCL 频率为 1/10us = 100kHz,对应总线原始位速率约为 100 kbit/s(有效数据吞吐率还需扣除地址、应答和起止条件的开销)。

【第10个时钟】

另外,还有一个容易被忽略的细节:每个应答位(第 9 个时钟)结束、进入第 10 个时钟的低电平时,SDA 会先短暂回到高电平,然后才被拉低。这属于正常现象,本质是应答结束后 SDA 的控制权从从机交回主机:

① 第 9 个时钟(ACK 位)期间,SDA 由从机驱动为低;主机这时不驱动 SDA,只是等从机应答;

② 第 9 个 SCL 下降沿之后,从机应答完成并释放 SDA。此时总线上没有任何器件在驱动它,电平完全由上拉电阻决定,所以 SDA 被拉高;

③ 主机紧接着要在第 10 个时钟的低电平期间放上下一位数据。如果这一位是 0,主机又把 SDA 拉低——画面上就变成了“短暂拉高、再拉低”。

有几点需要注意:

  • 这段高电平只是总线换手的空档,不是协议规定的信号。它的宽度取决于从机释放 SDA 的快慢、主机设置下一位的快慢以及软件延时;在软件模拟 I2C 中也可能达到微秒级;
  • 它完全落在 SCL 低电平期间,而 I2C 只在 SCL 高电平期间采样 SDA,所以不影响数据正确性,也不代表程序或电路有问题;
  • 如果下一位是 1,主机不需要拉低,SDA 就一直保持高,看起来就没有“再拉低”这个动作;
  • 如果下一位是 1,SDA 可以在第 10 个 SCL 上升沿之后继续保持高电平,这属于正常数据传输;只有当主机在 SCL 已为高电平后才将 SDA 拉低时,才会形成额外的 START/Sr,破坏当前数据位时序。STOP 则要求 SDA 在 SCL 高电平期间由低变高,与这里描述的下降沿不同。

前面真实的示波器图为什么容易看不到它:图 1 的时基是 25us/div,一个 SCL 周期才 10us,这段窗口在屏幕上不到一个像素宽,直接被显示压缩掉了。要观察它,得把时基打到 200ns/div ~ 500ns/div,触发源选 SCL 下降沿,用 Single 单次捕获。重绘图里这一段是夸张放大画的,只为让现象可见,不代表真实比例。

下面三张是同一段总线传输的连续抓取,时基 10.00us/div,每屏 12 格 × 10us = 120us;屏与屏之间前后有重叠,可以顺着时钟数到第 9、第 10 个脉冲的位置对照着看:

I2C 实测波形 第 1 屏I2C 实测波形 第 2 屏I2C 实测波形 第 3 屏
① 第 1 屏② 第 2 屏③ 第 3 屏

2. 读操作波形 ​

另外,对于读取从设备内容,基本流程是:主设备先往从设备写一个命令(先发“从机地址 + 写位”,再发命令字/存储地址),然后发重复起始条件 Sr(不发停止位,直接在总线上再发一次起始),再发一次“从机地址 + 读位”,之后才由从设备发送数据。过程类似,不再具体分析了。

下图示例中,主机先向从机写入从机地址(写)和命令字,然后重新开始(即重复起始条件 Sr)并进入读取周期。

img

图 2:CH1(黄,下方)是 SDA,CH2(青,上方)是 SCL;时基 50.0us/div。完整过程为:起始 → 写地址字节 0x3C → 应答 → 命令字 0x31 → 应答 → 重复起始 Sr → 读地址字节 0x3D → 应答 → 数据 0x68(字符 'h')。

本图标的都是 8 位地址字节:00111100(0x3C)最低位为 0 表示写,00111101(0x3D)最低位为 1 表示读,两者对应的 7 位从机地址都是 0011110(0x1E)。原图有三处容易误导:一是读阶段 ID 仍印着 00111100,却注明“末位为 1 表示读”,自相矛盾,正确值应为 00111101(0x3D);二是命令字处的二进制印成了 00010001,与实际波形 00110001(即 0x31,字符 '1')不符;三是上一张写操作图直接把 0111100 标成 7 位地址 0x3C,与本图的 8 位字节口径不同,不要把两张图的 0x3C 当成同一个从机地址。

同样把这笔事务按位重绘:

I2C 读事务波形重绘:写阶段发送写地址字节 0x3C 和命令字 0x31,两次均由从机应答;随后重复起始 Sr,发送读地址字节 0x3D,从机应答后送出数据 0x68(字符 'h')

分析波形可检测出I2C通信工作是否正常,是否符合预期,对我们编程调试诊断有辅助作用。


一个真实器件(AT24C02)的 5 种完整读写时序逐步分析,见 60-通信专题/01-I2C协议/LV020-AT24C02读写时序分析.md。

参考资料:

转载:用示波器对单片机I2C时序进行图形波形分析的试验小结 - FusionHEX_M - 博客园

用示波器怎么抓I2C波形 #I2C #示波器 #示波器使用教程 #硬件工程师 #单片机开发,数码,其他数码产品,好看视频