目录

一、硬件电路

二、I2C时序基本单元

三、波形图分析

四、软件模拟时序代码

1.SCL和SDA引脚的初始化

2.SCL和SDA的高低电平状态读写函数

3.IIC起始和终止函数

4.发送一个字节

5接收一个字节

6.应答


         I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线,两根通信线:串行时钟线SCL(Serial Clock)、串行数据线SDA(Serial Data),是一种同步,半双工带数据应答支持总线挂载多设备(一主多从、多主多从)。前面学习了I2C的通信协议,现在在这里做一个对学习笔记的简要总结吧。

一、硬件电路

1.所有I2C设备的SCL连在一起,SDA连在一起

2.设备的SCL和SDA均要配置成开漏输出模式

3.SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

        为了总线没有协调好导致电源短路的问题,I2C的设计是紧张所有设备输出强上拉的高电平,采用外置弱上拉电阻加开漏输出的电路结构

        主机对SCL线完全掌控,从机不允许控制SCL线,在空闲状态下,主机可以主动发起对SDA的控制,只有在从机发送数据和从机应答的时候,主机才会转交SDA的控制权给从机。对于SDA线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后或者从机应答的时候,从机才能短暂的获取SDA的控制权。

一主多从模型 

二、I2C时序基本单元

起始条件:SCL高电平期间,SDA从高电平切换到低电平

终止条件:SCL高电平期间,SDA从低电平切换到高电平

        在I2C总线处于空闲状态时,SCL和SDA都处于高电平状态,SCL和SDA由外挂的上拉电阻拉高至高电平。当总线准备进行数据收发时,SCL处于高电平不动,SDA由高电平切换到低电平产生一个下降沿。当从机捕获到SCL高电平,SDA下降沿信号时,就会进行自身的复位,等待主机的召唤,然后在SDA下降沿之后主机要再把SCL由高电平切换到低电平。一方面是占用这个总线,另一方面是为了基本时序单元的拼接,之后会保证除了起始和终止条件,每个时序单元的SCL都是以低电平开始,低电平结束。终止条件是SCL先从低电平切换到高电平,然后SDA在由低电平切换到高电平,产生一个上升沿触发终止条件。终止条件之后,SCL和SDA都是高电平回到最初的状态。

起始和终止时序图

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

        起始条件之后,第一个字节必须由主机发送,在SCL低电平时,主机如果想发送0就拉低SDA到低电平,如果想发送1就拉高SDA到高电平。在SCL低电平期间,允许改变SDA的电平,当SDA变化好了后,SCL切换到高电平,在高平期间,是从机读取SDA的时候,所以高电平期间,SDA不允许变化,这时从机要尽快的读取SDA电平。然后SCL再由高电平切换到低电平这时要传输0或1就相应的改变SDA的电平就可以了这样不停的下去数据就可以传输下去来。(高位先行)

发送一个字节时序图

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

       主机在接收前需要释放SDA的控制权,从机就取得了SDA的控制权。从机要发送1,就让SDA切换到高电平,从机要发送0,就把SDA拉低。低电平变换数据,SCL高电平读取数据。

 

接收一个字节时序图

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

       在调用发送一个字节之后,就要紧跟着调用接收应答的时序,用来判断从机有没有收到刚才给它的数据。如果从机收到了,在主机释放SDA的时候,从机就应该立刻把SDA拉到低电平,然后在SCL高电平期间,主机读取应答位。如果应答位为0,说明从机收到了数据。

       在接收一个字节后,也要给从机发送一个应答位,目的是告诉从机它是不是要继续发,如果得到了主机的应答那从机还会继续发送,如果从机没有得到应答,那从机就不会发送数据了并释放SDA把SDA的控制权交给主机。 

应答时序图

三、波形图分析

        I2C的完整时序主要有指定地址写、当前地址读和指定地址读3种。主机在通信之前会发送一个从机的ID号,如果这个ID号和从机的一样那就相应主机的读写操作,如果不一样那从机就不会理会主机。从机设备地址在I2C协议里分为7位地址和10位地址,但是7位比较常用,这里主要将7位地址的情况。

指定地址写:对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data) 

 

当前地址读:对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

 

指定地址读:对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

 

四、软件模拟时序代码

        在Keil中编写IIC的基本时序代码,根据上面的时序图可以很容易的编写出代码,那下面就开始吧!

1.SCL和SDA引脚的初始化

void MyI2C_Init(void)    //SCL和SDA初始化
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  //开漏输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);  //SCL和SDA开始都置高电平
}

2.SCL和SDA的高低电平状态读写函数

void MyI2C_W_SCL(uint8_t BitValue)  //SCL电平状态设置函数
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
	Delay_us(10);
}

void MyI2C_W_SDA(uint8_t BitValue)  //SDA电平状态设置函数
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
}

uint8_t MyI2C_R_SDA(void)  //SDA读取电平状态函数
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

3.IIC起始和终止函数

void MyI2C_Start(void)  //IIC起始函数
{
	MyI2C_W_SDA(1);   //先拉高SDA再拉高SCL
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

void MyI2C_Stop(void)  //IIC终止函数
{
	MyI2C_W_SDA(0);   //先拉低SDA再拉高SCL再拉高SDA
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

4.发送一个字节

void MyI2C_SendByte(uint8_t Byte) //发送一个字节函数
{
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));  //Byte & 0x80 取出Byte的高位
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

5接收一个字节

        在这里需要解释一下一开始为什么要把SDA置1。第一,I2C的引脚都是开漏输出+弱上拉的配置,主机输出1,并不是强制SDA为高电平,而是释放SDA;第二,I2C是在进行通信,主机释放SDA从机如果在的话它会有义务在此时把SDA再拉低的,所以在这里即使之前主机把SDA置1之后再读取SDA读到的值也可能是0.

uint8_t MyI2C_ReceiveByte(void)  //接收一个字节函数
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);   

	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1);
		if (MyI2C_R_SDA() == 1)
		{
			Byte |= (0x80 >> i);
		}
		MyI2C_W_SCL(0);
	}
	return Byte;
}

6.应答

void MyI2C_SendAck(uint8_t AckBit) //发送应答
{
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck(void)  //接收应答
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;
}

        这样我们就用两个普通的GPIO引脚模拟了I2C的基本时序了,有了这个基础对于不同的芯片只要对应着芯片手册调用上面的函数就可以对芯片进行读写操作了,可以得到我们想要数据和效果了

物联沃分享整理
物联沃-IOTWORD物联网 » 使用软件模拟IIC通信

发表评论