STM32之IIC详细解析

原理介绍不多说,直接分析

先看IIC的一些特性

由下图可以知道,IIC的传输是同步传输的,具有强制性的起始信号、停止信号、ACK响应信号、7位的从机地址。但是时钟拉伸(每一个时钟脉冲的时间)、软件复位等可以用户自定义,还可以选择10位从机地址的设备,这解决了IIC只能接128个设备的限制问题。并且在挂载多个从机的情况下,可以设置起始字节(start byte)。
在这里插入图片描述

关于总线的逻辑0和逻辑1

由于可以连接各种各样的设备,总线的逻辑上的“0”(LOW)和“1”(HIGH)的级别不是固定的,而是依赖于VDD的相关级别。输入参考级别设置一般为VDD的30%和70%;

VIL为0.3VDD逻辑0
VIH为0.7VDD逻辑1

数据传输的有效性

由以下时序图可知,如果要数据能够有效传输,要先使数据线稳定,换个说法就是要先发数据,再给时钟脉冲(上升沿),当SCL为低电平时传输的数据允许变更。
并且SDA线上传输的每个字节必须是8位长。每次传输可以传输的字节数不受限制。每个字节后面必须有一个确认位。
在这里插入图片描述

完整的数据传输时序图

如图是I2C的一个完整的数据传输的时序图,在这种传输中,就可以实现读/写格式的各种组合。

在这里插入图片描述

起始和结束

在这里插入图片描述

红框处分别为起始信号和停止信号

  1. 起始信号的产生过程:保持SCL为高电平,SDA产生一个下降沿,然后SCL拉低,一个START信号产生;
void i2c_Start(void)
{
	EEPROM_I2C_SDA_1();
	EEPROM_I2C_SCL_1();
	i2c_Delay();
	EEPROM_I2C_SDA_0();
	i2c_Delay();
	EEPROM_I2C_SCL_0();
	i2c_Delay();
}
  1. 停止信号的产生过程:保持SCL为高电平,SDA产生一个上升沿,一个STOP信号产生。
void i2c_Stop(void)
{
	EEPROM_I2C_SDA_0();
	EEPROM_I2C_SCL_1();
	i2c_Delay();
	EEPROM_I2C_SDA_1();
}

除此之外,还有发送数据和接收数据的时序。

发送和接收

启动IIC传输后发送的第一个字节
在这里插入图片描述

发送数据的过程:

在这里插入图片描述

一个CLK对应发送一个bit,先将SCL拉低,然后准备好要发送的数据,然后将SCL拉高,产生一个CLK时钟脉冲,数据就能直接发往从机;其中(ucByte & 0x80)表示从高位(MSB)开始发送直到最低位(LSB),每发送完一个位后ucByte左移一位,确保每一位的数据都能正确发送,最后将SCL重新拉低。

void i2c_SendByte(uint8_t ucByte)
{
	uint8_t i;

	for (i = 0; i < 8; i++)
	{		
		EEPROM_I2C_SCL_0();	//这个可以不用,因为最后SCL清0了
		i2c_Delay();
		
		if (ucByte & 0x80)
		{
			EEPROM_I2C_SDA_1();
		}
		else
		{
			EEPROM_I2C_SDA_0();
		}
		
		EEPROM_I2C_SCL_1();
		i2c_Delay();
		
		ucByte <<= 1;	/* 左移一个bit */
		
		EEPROM_I2C_SCL_0();
		i2c_Delay();
	}
}

接收数据的过程:

还是这张图

在这里插入图片描述

一个CLK对应发送一个bit,先将SCL拉低,然后先进行数据为的左移,此时由从机发来的数据已经准备好发往主机了,或者说已经到主机家门口了,主机只要将SCL拉高,产生一个SCK信号即一个时钟脉冲,就可以接收到一个bit,然后用EEPROM_I2C_SDA_READ()读取这个bit,bit == 1则加1,bit ==0就不做操作。每次循环都是先左移再接收数据,这种顺序,在第一次左移时,对数据没有影响;在之后的每次循环,可以保证每个bit写入正确的位置,防止数据位错位。等循环结束后,将数据取出,就完成了一次8bit数据的读取。

uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value = 0;

	for (i = 0; i < 8; i++)
	{
		EEPROM_I2C_SCL_0();//这个可以不用,因为最后SCL清0了
		i2c_Delay();
		
		value <<= 1;
		
		EEPROM_I2C_SCL_1();
		i2c_Delay();
		
		if (EEPROM_I2C_SDA_READ())
		{
			value++;
		}
		
		EEPROM_I2C_SCL_0();
		i2c_Delay();
	}
	return value;
}

ACK信号和NACK信号

在这里插入图片描述

ACK信号:在大于一个时钟脉冲的时间保持SDA为低电平

void i2c_Ack(void)
{
	EEPROM_I2C_SDA_0();	/* CPU驱动SDA = 0 */
	i2c_Delay();
	EEPROM_I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	EEPROM_I2C_SCL_0();
	i2c_Delay();
	EEPROM_I2C_SDA_1();	/* CPU释放SDA总线 */
}

NACK信号:在大于一个时钟脉冲的时间保持SDA为高电平

void i2c_NAck(void)
{
	EEPROM_I2C_SDA_1();	/* CPU驱动SDA = 1 */
	i2c_Delay();
	EEPROM_I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	EEPROM_I2C_SCL_0();
	i2c_Delay();	
}

读取一个ACK信号

在这里插入图片描述

该函数用于读取ACK应答信号,并返回读取的状态。
过程也是老样子,在一个时钟脉冲下完成的。在产生时钟脉冲前,要释放SDA总线,即将SDA拉高。(因为SDA返回0表示正确应答,返回1则表示无IIC设备响应,所以要先保证它在未响应状态),然后给一个时钟脉冲,并判断SDA的值,SDA返回0表示正确应答,返回1则表示无IIC设备响应。

存在一个迷惑:CPU释放SDA总线的时候,SDA = 1或者 SDA = 0都可以保证程序运行,但是不加IIC就跑不起来。

uint8_t i2c_WaitAck(void)
{
	uint8_t re;
	
    EEPROM_I2C_SCL_0();//这个可以不用,因为最后SCL清0了
	i2c_Delay();
    
	EEPROM_I2C_SDA_1();	/* CPU释放SDA总线 */
	i2c_Delay();
    
	EEPROM_I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();
    
	if (EEPROM_I2C_SDA_READ())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
    
	EEPROM_I2C_SCL_0();
	i2c_Delay();
	return re;
}

向从机发送数据的完整过程

发送一个8bit数据

在这里插入图片描述

发送多个8bit数据

在这里插入图片描述

总体的一个过程:

  1. 主机发送start信号;
  2. 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读;
  3. 从机返回ACK响应信号;
  4. 主机发送要给从机写入数据的地址;(有的设备不用)
  5. 从机返回ACK响应信号;
  6. 主机发送数据;
  7. 从机返回ACK响应信号;
  8. 重复第6和7步,直到从机返回一个NACK非响应信号;
  9. 主机发送停止信号,结束数据传输。

读取从机发来的数据

读一个8bit数据

在这里插入图片描述

读多个8bit数据

在这里插入图片描述
总体的一个过程:

​ 先向从机写入从机地址,因为IIC总线上可能不止一台从机设备,只有地址相等的从机才会返回应答,才可以进行之后的步骤;之后发送要读取数据的地址;再重启IIC总线,发送从机地址并改为读数据模式;接下来就可以读取数据直到主机发送非响应信号为止。

  1. 主机发送start信号;
  2. 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读;
  3. 从机返回ACK响应信号;
  4. 主机发送要给从机写入数据的地址;(有的设备不用)
  5. 从机返回ACK响应信号;
  6. 重新启动IIC总线,发送start信号;(前面步骤的目的向从机传送地址,下面开始读取数据)
  7. 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读;
  8. 从机返回ACK响应信号;
  9. 主机接收数据;
  10. 从机返回ACK响应信号;
  11. 重复第9和01步,直到从机返回一个NACK非响应信号;
  12. 主机发送停止信号,结束数据传输。

检测IIC设备是否存在

要检测IIC设备是否存在,只需要向设备发送设备地址,然后读取设备是否返回应答信号即可。
返回值 0 表示正确即对应地址的设备存在, 返回1表示未探测到IIC设备。

uint8_t ee_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;
	
	i2c_Start();		/* 发送启动信号 */

	/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
	i2c_SendByte(_Address | EEPROM_I2C_WR);
	ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */

	i2c_Stop();			/* 发送停止信号 */

	i2c_NAck();	/*若输入的是读地址,需要产生非应答信号*/
	
	return ucAck;
}

其他事项

以上代码的一些相关宏定义

#define EEPROM_I2C_SCL_1()  GPIO_SetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SCL_PIN)		/* SCL = 1 */
#define EEPROM_I2C_SCL_0()  GPIO_ResetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SCL_PIN)	/* SCL = 0 */
	
#define EEPROM_I2C_SDA_1()  GPIO_SetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN)		/* SDA = 1 */
#define EEPROM_I2C_SDA_0()  GPIO_ResetBits(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN)	/* SDA = 0 */
	
#define EEPROM_I2C_SDA_READ()  GPIO_ReadInputDataBit(EEPROM_GPIO_PORT_I2C, EEPROM_I2C_SDA_PIN)	/* 读SDA口线状态 */

在模拟IIC时,引脚最好加上拉电阻,并将引脚配置为开漏输出。

如果要使用10位地址的从机设备,以下是对应的时序图

发送数据

在这里插入图片描述

接收数据

在这里插入图片描述
由以上时序图可知,无论是发送还是接收的时序图,只比7bit地址对应的发送和接收时序多了【TARGET ADDRESS 2nd BYTE】的部分,即在发送完10bit地址的高7位后,还要进行一次剩下地址的位的发送。

<think>好的,我需要详细分析用户的问题,关于IIC通信中时钟线的释放电平以及主机和从机在应答信号中的交互细节。用户具体问的是:IIC协议中时钟线是高电平释放还是低电平释放,主机如何通过SCL控制应答信号,从机何时拉低时钟线进行ACK确认。 首先,回顾之前提供的引用内容,特别是引用[3]提到“主机释放SDA控制权(SDA拉高),等待从机应答信号”,同时引用[4]指出“SCL的拉高和拉低时机由主设备控制”。这说明在IIC通信中,主机始终控制SCL的状态,包括拉高和拉低。而应答信号(ACK/NACK)发生在第9个时钟周期,此时主机释放SDA线,从机通过拉低SDA来确认ACK。 用户的问题主要集中在时钟线的释放电平,即SCL在ACK阶段是高还是低。根据引用[3],主机在发送完数据后会释放SDA,但SCL的控制权始终在主机。在ACK阶段,主机会生成第9个时钟脉冲,即拉高SCL,此时从机需要在这个高电平期间拉低SDA。因此,SCL在ACK阶段是被主机拉高的,而不是释放为高电平。释放通常指放弃控制,但在这里主机仍然主动控制SCL,只是将SDA释放给从机。 接下来,关于主机如何通过SCL控制应答信号。主机在传输完8位数据后,继续控制SCL生成第9个时钟周期。此时主机释放SDA(拉高),从机需要在SCL高电平期间拉低SDA作为ACK。如果从机没有拉低,则视为NACK。主机会检测SDA的状态,从而判断是否继续传输。 至于从机何时拉低时钟线进行ACK确认,根据引用[4],SCL的拉高和拉低由主机控制,从机只能通过SDA响应,不能直接控制SCL。因此,从机不会拉低时钟线,而是在SCL被主机拉高时,在SDA线上拉低以发送ACK。如果存在时钟延展的情况,从机可能在SCL低电平时保持它,但这属于特殊处理,不是常规ACK机制。 需要验证是否有信息冲突或遗漏。例如,引用[3]提到主机在退出函数前需要将CLK拉低,这可能影响后续传输,但ACK阶段本身SCL由主机控制。因此,在正常ACK过程中,主机保持SCL的控制,从机仅通过SDA响应。 总结:在ACK阶段,主机拉高SCL,从机在此时拉低SDA。主机不释放SCL的控制权,始终主动驱动其电平变化。从机仅通过SDA进行ACK,不会主动控制SCL,除非在时钟延展的特殊情况下。</think>#### IIC协议时钟线释放电平与ACK交互机制详解 ##### 一、时钟线(SCL)的释放电平本质 1. **核心原则** **主机始终控制SCL电平状态**,不存在"释放SCL电平"的概念,只有**释放SDA控制权**的操作[^3]。 - **正常传输阶段**:主机通过主动拉高/拉低SCL生成时钟脉冲 - **ACK阶段**:主机继续控制SCL,**第9个时钟脉冲仍由主机主动拉高** 2. **电平状态解析** | 操作阶段 | SCL电平 | 控制方 | 关键动作 | |----------------|---------|--------|------------------------------| | 数据传输 | 高低交替| 主机 | 在SCL低电平时改变SDA[^2] | | ACK检测 | 高电平 | 主机 | 从机在SCL高时拉低SDA[^1] | | 时钟延展 | 低电平 | 从机干预| 从机通过保持SCL低实现延时[^4] | ##### 二、主机通过SCL控制ACK的关键步骤 1. **时钟生成规则** $$ \text{SCL}\_{ACK} = \text{主机生成的固定高电平脉冲} $$ (如图示:第9个时钟周期完全由主机驱动) 2. **ACK检测流程** ```c // 伪代码流程 void Master_SendByte() { for(int i=0; i<8; i++){ // 发送8位数据 SCL = 0; // 主机拉低SCL准备数据变化 SDA = bit_value; // 设置数据位 SCL = 1; // 主机拉高SCL触发从机采样 } // 进入ACK阶段 SCL = 0; // 主机拉低SCL结束数据位 SDA = 1; // 主机释放SDA控制权[^3] SCL = 1; // 主机主动生成ACK检测时钟(第9脉冲) if(SDA == 0) { // 在SCL高电平期间检测ACK // 收到有效ACK } SCL = 0; // 主机结束ACK阶段 } ``` 3. **时序参数要求** - **建立时间**($t_{SU:DAT}$):SDA在SCL上升沿前需稳定≥250ns(标准模式) - **保持时间**($t_{HD:DAT}$):SCL下降沿后SDA至少保持300ns ##### 三、从机ACK响应机制 1. **响应时间窗口** - **起始点**:SCL被主机拉高后,需等待$t_{AA}$(从机应答时间,≤3.45μs) - **有效区间**:必须在SCL高电平持续期间完成SDA拉低操作 2. **硬件实现原理** ```verilog // 从机IIC模块片段(简化的硬件描述) always @(posedge SCL) begin if(byte_counter == 8) begin // 检测到第8位传输完成 SDA_driver <= 1'b0; // 在SCL上升沿触发ACK响应 end end ``` 3. **异常处理机制** - **NACK生成**:从机不拉低SDA(保持高阻态) - **超时保护**:主机设置超时计数器(通常>4.7μs) ##### 四、时钟延展与ACK的关联 1. **特殊场景交互** ```sequence 主机->从机: 发送数据字节 主机->从机: 拉低SCL结束数据位 从机->主机: 检测到SCL低电平后保持SCL低 Note over 从机: 处理数据(延长低电平时间) 从机->主机: 释放SCL(恢复高电平) 主机->从机: 检测SCL高后继续传输 ``` 2. **设计注意事项** - 上拉电阻值需满足$R_p < \frac{t_{LOW}}{0.8473C_b}$($C_b$为总线电容) - 高速模式需使用施密特触发器消除振铃 ##### 五、实际波形验证方法 1. **示波器测量点** $$ \text{ACK有效性} = (V_{SDA} < 0.3V_{DD}) \cap (t_{SU:ACK} > 250ns) $$ (在SCL高电平中点位置测量SDA电压) 2. **典型故障波形** - **ACK抖动**:SDA在SCL高电平期间出现跳变(检查从机驱动电路) - **SCL下沉不足**:低电平电压>0.4V(检查从机的下拉能力)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值