I2C学习

串行半双工,每个IIC器件都有一个地址, i2C总线在IC之间进行双向数据传送,典型速度100Kbit/S,快速模式达400Kbit/S,后来增加了高速模式达3.4Mbit/S。

它只有两根双向信号线。一根是数据线SDA(serial data I/O),另一根是时钟线SCL(serial clock)。 

1 数据位的有效性规定:

SCL为高电平期间,数据线上的数据必须保持稳定,只有SCL信号为低电平期间,SDA状态才允许变化。如图所示 

I2C学习_第1张图片

10位I2C总线可以挂接更多的10位I2C设备.    

I2C学习_第2张图片

2.I2C的起始和终止信号

SCL线为高电平期间,SDA线由电平向电平的变化表示起始信号; 
SCL线为高电平期间,SDA线由电平向电平的变化表示终止信号; 

I2C学习_第3张图片

3.I2C字节的传送与应答

数据传送时高位在前,低位在后,每次传送的字节数目没有限制。传输操作启动后主控制器件传输的第一字节是地址,其中前面7位指出与哪一个器件进行通信,第8位指出数据传输的方向(发送还是接收)。

为了完成一字节的传送,接收方应该发送一个确认信号ACK给发送方,ACK信号出现在SCK线的第9个时钟脉冲上,有效应答ACK在SDA上呈现低电平,如图。

主控制器件在接收了来自从器件的字节后,如果不准备终止数据传输,他将会发送1个ACK信号给从器件。从器件在其接收到来自主控制器件的字节时,总是发送1个ACK信号给主控制器件,如果从器件还没有准备好再次接收,它可以保持SCK位低电平(总线处于等待状态),直到它准备好为止。

I2C学习_第4张图片

时序的要求

  • 当 SCL 在低电平的时候, SDA 允许变化,也就是说,发送方必须先保持 SCL 是低电平,才可以改变数据线 SDA,输出要发送的当前数据的一位;
  • 当 SCL 在高电平的时候, SDA 绝对不可以变化,因为这个时候,接收方要来读取当前 SDA 的电平信号是 0 还是 1,因此要保证 SDA 的稳定,

4.应答位的作用

主机在发送数据时,每次发送一字节数据,都需要读取从机应答位,当从机空闲可以接收该字节数据时,从机会发出应答(一帧数据的第9位为“0”),当从机正忙于其他工作的处理来不及接收主机发送的数据时,从机会发出非应答(一帧数据的第9位为“1”)主机则应发出终止信号以结束数据的继续传送,主机通过从机发出的应答位来判断从机是否成功接收数据.

当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。 

5 I2C总线的仲裁机制

https://blog.csdn.net/u010027547/article/details/47779975

总线控制遵循“低电平优先”的原则,即谁线发送低电平谁就会掌握对总线的控制权;主控制器通过检测SDA上自身发送的电平来判断是否发生总线仲裁。因此,IIC总线的总线仲裁是考器件自身接口的特殊结构得以实现的。

6 I2C总线数据传送典型信号时序

                       I2C学习_第5张图片

总线数据传送的时序要求

为了保证数据传送的可靠性,标准的总线数据传送有着严格的时序要求,如总线上时钟信号的最小低电平周期为4.7us,最小的高电平周期为4us等。

用单片机的普通I/O口模拟总线的数据传送时,单片机的时钟信号都能满足SDA、SCL上升沿、下降沿的时间要求,因此,在时序模拟时,最重要的是保证典型信号。

I2C总线的工作过程与原理 

1 主控制器————》被控器  发送数据 

(1)主控器在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信的开始; 
(2)主控器接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W组成(此时 R/W=0); 
(3)相对应的被控器收到命令字节后向主控器回馈应答信号 ACK(ACK=0); 
(4)主控器收到被控器的应答信号后开始发送第一个字节的数据; 
(5)被控器收到数据后返回一个应答信号 ACK; 
(6)主控器收到应答信号后再发送下一个数据字节 … … 
(7)当主控器发送最后一个数据字节并收到被控器的 ACK 后,通过向被控器发送一个停止信号P结束本次通信并释放总线。被控器收到P信号后也退出与主控器之间的通信。

                         I2C学习_第6张图片

2 主控器接收数据的过程 
(1)主机发送启动信号后,接着发送命令字节(其中 R/W=1); 
(2)对应的被控器收到地址字节后,返回一个应答信号并向主控器发送数据; 
(3)主控器收到数据后向被控器反馈一个应答信号(ACK=0); 
(4)被控器收到应答信号后再向主控器发送下一个数据 … …; 
(5)当主机完成接收数据后,向被控器发送一个“非应答信号(ACK=1)”,被控器收到ASK=1 的非应答信号后便停止发送; 
(6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信。 

主控器所接收数据的数量是由主控器自身决定,当发送“非应答信号/(ACK=1)”时被控器便结束传送并释放总线(非应答信号的两个作用:前一个数据接收成功,停止从机的再次发送)。

                            I2C学习_第7张图片

I2C总线的时钟同步与总线仲裁

I2C总线的SCL同步时钟脉冲一般都是由主控器发出作为串行数据的移位脉冲。每当SDA上出现一位稳定的数据后,在SCL上发送一个高电平的移位脉冲。

1 SCL 信号的同步

如果被控器希望主控器降低传送速度可以通过将SCL主动拉低延长其低电平时间的方法来通知主控器,当主控器在准备下一次传送发现SCL的电平被拉低时就进行等待,直至被控器完成操作并释放SCL线的控制控制权。

这样以来,主控器实际上受到被控器的时钟同步控制。可见SCL线上的低电平是由时钟低电平最长的器件决定;高电平的时间由高电平时间最短的器件决定。这就是时钟同步,它解决了I2C总线的速度同步。

2 I2C总线上的总线仲裁

注意:谁先发送低电平谁获得总线,不是说谁在第一位发出低电平谁获得总裁,而是在发送数据的过程中出现了数据不一样就会释放总线,然后剩下的控制器就独享总线。

如果在同一个I2C总线系统中存有两个主控器,其时钟信号分别为SCK1、 SCK2,它们都具有控制总线的能力。假设两者都开始要控制总线进行通信,由于“线与”的作用,实际的SCL的波形如图 7.6 所示。在总线做出仲裁之前,两个主控器都会以“线与”的形式共同参与SCL线的使用,速度快的主控器 1 等待落后的主控器 2(如图 7.6)。

                    I2C学习_第8张图片

对于 SDA 线上的信号的使用,两个主控器同样也是按照“线与”的逻辑来影响 SDA 上的电平变化。假设主控器 1 要发送的数据 DATA1 为“101 ……”;主控器 2 要发送的数据DATA2 为“1001 ……”。总线被启动后两个主控器在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,他们就会继续占用总线。在这种情况下总线还是得不到仲裁。

当主控器 1 发送第 3 位数据“1”时(主控器 2 发送“0” ),由于“线与”的结果 SDA 上的电平为“0”,这样当主控器 1 检测自己的输出电平时,就会测到一个与自身不相符的“0”电平。这时主控器 1 只好放弃对总线的控制权;因此主控器2 就成为总线的唯一主宰者。仲裁过程如图 5.2 所示。

不难看出:

① 对于整个仲裁过程主控器 1 和主控器 2 都不会丢失数据;
② 各个主控器没有对总线实施控制的优先级别;
③总线控制随即而定,他们遵循“低电平优先”的原则,即谁先发送低电平谁就会掌握对总线的控制权。根据上面的描述,“时钟同步”与“总线仲裁”可以总结如下规律:

  1. 主控器通过检测 SCL 上的电平来调节与从器件的速度同步问题——时钟同步;
  2. 主控器通过检测SDA上自身发送的电平来判断是否发生总线“冲突”——总线仲裁。

因此, I2C总线的“时钟同步”与“总线仲裁”是靠器件自身接口的特殊结构得以实现的。

I2C总线的控制程序实现

                                       I2C学习_第9张图片

I2C的启动程序

Void   I2CStart  (void)	
{
	SDA = 1;          //释放数据线
	SomeNOP();    //延时
	SCL = 1;          //时钟线拉高
	SomeNOP();   //延时
	SDA = 0;         //数据线拉低
	SomeNOP();   //延时
	SCL = 0;         //时钟线拉低
	SomeNOP();   //延时
}

I2C的停止程序

void I2CStop(void)
{
	SDA = 0;
	SomeNOP();
	SCL = 1;
	SomeNOP();
	SDA = 1;
	SomeNOP();
}

I2C的应答程序

void ACK(void)           	//Acknowledge信号
{
	SDA = 0;                //发送0,应答
	SomeNOP();  
	SCL = 1;
	SomeNOP();         //产生时钟高电平
	SCL = 0;
	SomeNOP();
}
void NACK(void)	       //没有Acknowledge信号
{
	SDA = 1;               //发送1,非应答
	SomeNOP();
	SCL = 1;
	SomeNOP();       //产生时钟高电平
	SCL = 0;
	SomeNOP();
}

检测应答位

bit TestAck()
{
	bit ErrorBit;
	SDA=1;
	SCL=1;
	ErrorBit=SDA;
	SCL=0;
	return(ErrorBit);
}

写操作

/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
bit I2CWrite(unsigned char dat)
{
    bit ack;  //用于暂存应答位的值
    unsigned char mask;  //用于探测字节内某一位值的掩码变量

    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        if ((mask&dat) == 0)  //该位的值输出到SDA上
            I2C_SDA = 0;
        else
            I2C_SDA = 1;
        I2CDelay();
        I2C_SCL = 1;          //拉高SCL
        I2CDelay();
        I2C_SCL = 0;          //再拉低SCL,完成一个位周期
    }
    I2C_SDA = 1;   //8位数据发送完后,主机释放SDA,以检测从机应答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return (~ack); //应答值取反以符合通常的逻辑:
                   //0=不存在或忙或写入失败,1=存在且空闲或写入成功
}

读操作(返回ACK = 1)

/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
unsigned char I2CReadNAK()
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 1;   //8位数据发送完后,拉高SDA,发送非应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非应答位,并保持住总线

    return dat;
}

读操作(返回ACK = 0)

/* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
unsigned char I2CReadACK()
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 0;   //8位数据发送完后,拉低SDA,发送应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return dat;
}

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(嵌入式)