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

先看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位后,还要进行一次剩下地址的位的发送。

物联沃分享整理
物联沃-IOTWORD物联网 » STM32之IIC详细解析

发表评论