I2C总线通信

1、I2C总线简介

  I2C总线是一种由PHILIPS公司开发的两线串行通讯总线,用于连接为控制器及其外围设备。

  I2C串行通讯总线由两条线组成:

  时钟线SCL。

  数据线SDA。

  时钟线SCL用来同步数据的传输,数据线SDA用来传输或读取数据。

  I2C总线通信设备之间常用连接方式如下:

   总线一般是指多个设备共用的信号线,比如上图中的SCL总线和SDA总线,这两个总线组成了I2C通讯总线,可以在这两个总线上挂载多个设备。每一个连接到I2C总线上的设备都有一个独立设备地址,主机通过设备地址识别响应的设备并与之通信。

  IC2总线一般需要使用上拉电阻接到电源端,这是因为I2C的设备的内部结构一般是开漏的,在平常状态时为高组态,状态时不确定的,通过上拉电阻将其上拉到高电平,使用这种方式也可以提高I2C总线的驱动能力。

  I2C总线有三种传输模式:

  标准模式:通讯速率为100Kbit/s。

  快速模式:通讯速率为400Kbit/s。

  高速模式:通讯速率为3.4Mbit/s。

  一般使用标准或快速这两个模式进行通讯,这是应该目前大多数I2C设备并不支持高速模式,具体使用哪种模式可以根据I2C的设备的规格资料进行查询。

2、I2C通讯协议

  I2C通讯协议包含起始信号、设备地址和读写信号、读或写数据信息、响应或非响应信号、结束信号。

  起始信号:

  起始信号由主机产生,主机通过在SCL为高电平的时候将SDA从输出高电平变为输出低电平来触发一个I2C的起始信号,如下图:

   主机在产生完起始信号之后,通过拉低SCL时钟线使总线处于空闲状态。主机产生起始信号的程序如下:

1 void I2C_START(void)
2 {      
3     I2C_SDA(1);
4     I2C_SCL(1);  
5     I2C_DELAY(2); 
6     I2C_SDA(0); 
7     I2C_DELAY(2);          
8     I2C_SCL(0);        
9 }

  程序首先将SDA和SCL输出高电平,然后通过给SDA输出低电平触发起始信号,最后拉低SCL使总线处于空闲状态(也有一种说法是钳住总线)。

   停止信号:

  当不需要再进行I2C通讯时,主机产生一个停止信号来结束I2C通讯。主机通过在SCL为高电平时将SDA从输出低电平变为输出高电平来触发一个I2C结束信号,如下图:

   主机产生停止信号的程序如下:

1 void I2C_STOP(void)
2 {      
3     I2C_SCL(0); 
4     I2C_SDA(0);     
5     I2C_DELAY(2); 
6     I2C_SCL(1); 
7     I2C_DELAY(2);          
8     I2C_SDA(1);        
9 }

  程序首先将SCL和SDA输出低电平使总线处于空闲状态,然后使SCL输出高电平,最后再将SDA从输出低电平变为输出高电平,这样不仅产生了一个停止信号,并且还释放了总线(SCL和SDA都为高电平)。

数据采样

  主机发送启动信号之后,就可以向从机设备传输数据了。在传输数据时,每经过一个SCL时钟周期传输一个bit的数据,如果是主机写数据到从机,那么主机通过SDA在一个SCL时钟周期内传输一个bit给从机;如果是主机从从机中读取数据,那么主机通过SDA在一个SCL时钟周期内读取一个bit的数据。

  从I2C的起始信号和结束信号的介绍中可以看到,在SCL为高电平时,SDA电平的变换会触发起始信号或停止信号,所以在数据的传输过程中,不能在SCL为高电平时改变SDA的电平状态,也就是说SDA线上的数据应该在SCL为高电平的时候保持稳定。如下图:

  I2C总线通讯时数据的采样发生在SCL时钟周期的高电平时间段,数据的改变发生在SCL时钟周期的低电平时间段。也就是说数据是在SCL为高电平的时候传输或读取,在SCL为低电平的时候根据传输的数据改变SDA的电平状态。

  I2C总线在传输数据时,一般是先发送高位字节的数据,最后发送低位字节的数据。

  主机向从机发送一个字节的程序如下:

 1 uint8_t I2C_WRITE_BYTE(uint8_t dat)
 2 {   
 3     uint8_t i;
 4     
 5     I2C_SCL(0);    
 6     I2C_SDA(0);//释放总线
 7     I2C_DELAY(1); 
 8     
 9     for(i = 0;i < 8;i ++)
10     {  
11         if(dat & 0x80)
12         {
13             I2C_SDA(1);        
14         }
15         else
16         {
17             I2C_SDA(0);    
18         }
19         dat <<= 1;
20         I2C_DELAY(4);   
21         I2C_SCL(1); 
22         I2C_DELAY(8); 
23         I2C_SCL(0); 
24     }
25        
26     i = I2C_WAIT_ACK();
27     
28     return i;
29 }

  发送程序第11~18行是根据要传输的数据dat来决定SDA的电平状态,这是在SCL为低电平的时候处理的,在SCL为高电平的时候保持SDA的输出状态不变。第26行是等待从机反馈应答信号,在后面会讲到应答信号的作用。第28行是返回的参数用来判断是否收到应答信号,如果没有收到应答信号则退出。

  主机从从机读取一个字节的程序如下:

 1 uint8_t I2C_READ_BYTE(uint8_t ack)
 2 {   
 3     uint8_t i;
 4     uint8_t dat;
 5     
 6     I2C_SCL(0); 
 7     I2C_SDA_IN();
 8     I2C_SDA(0);       
 9     I2C_DELAY(1); 
10     
11     for(i = 0;i < 8;i ++)
12     {  
13         I2C_SCL(1); 
14         I2C_DELAY(4); 
15         dat <<= 1;
16         if(RD_I2C_SDA != 0)
17         {
18             dat |= 0x01;         
19         }
20         I2C_SCL(0); 
21         I2C_DELAY(4); 
22     }
23     
24     I2C_SDA_OUT();
25     
26     if(ack != 0)
27     {
28         I2C_ACK();    
29     }
30     else
31     {
32         I2C_NOT_ACK();    
33     }   
34 
35     return dat;
36 }

  接收数据程序的第6~8行是将SDA数据线转换为输入状态。第15~19行是在SCL为高电平的时候读取SDA的电平状态,即接收到一个bit的数据。在接收完8个字节的数据后,第24行将SDA数据线转换为输出状态。第26~33行是根据函数输入的参数来决定接收到一个字节的数据之后反馈给从机的是应答信号还是非应答信号。最后一行是返回接收到的数据。

  IC2设备地址和数据方向:

   I2C总线上的每个从机设备都有一个属于自己的独立的设备地址,主机在发送起始信号之后,需要发送从机的设备地址来查找需要通讯的从机设备,因为总线上的I2C从机设备的地址是独立的,所以不会造成多个总线上的设备在传输数据时相互干扰。

  主机在发送设备地址的同时,还需要发送一个数据读写方向位,用来决定接下来的操作是向从机传输数据还是从从机读取数据。数据读写方向位一般包含在设备地址中发送给从机设备,如下图。

   bit7~bit1位是从机的设备地址,最低位bit0是数据读写方向位。

  当bit0位为0时,表示后面的操作是主机向从机发送数据。

  当bit0位为1时,表示后面的操作是主机从从机读取数据。

  关于I2C设备的地址需要注意的是,一般I2C设备在规格资料说明里给出的设备地址都是不包含数据读写方向位的,需要自己根据设备地址进行转换。比如说SHTC3温湿度传感器,在它的数据手册中给出的设备地址是0x70,在发送设备地址和读写方向位时,并不是发送0x70(最低位为0表示写)或0x71(最低位为1表示读),而是要经过以下变换得出的,首先将0x70左移一个bit,得到0xE0,再用0xE0的最低位来表示数据的读写方向位,即在给SHTC3发送写操作时应该发送0xE0(最低位为0表示写);给SHTC3发送读操作时应该发送0xE1(最低位为1表示读)。

   响应信号:

   在I2C总线通讯中,在传输完一个字节之后,接收数据的设备(主机和从机都有可能接收数据)会在第9个SCL时钟周期反馈一个应答信号或非应答信号给发送数据的设备,也就是说在I2C通讯中,数据的传输由8bit的数据和1bit的响应信号组成,即每一次传输会产生9个SCL时钟周期信号。如下图:

   响应信号包括应答信号(ACK)、非应答信号(NACK):

  应答信号(ACK):在第9个SCL时钟周期,数据发送端将SDA的控制权释放转换为输入状态,数据接收端控制SDA的输出,通过SDA数据线输出一个低电平来产生一个应答信号,数据发送端根据SDA数据线的电平状态来识别响应信号。

  非应答信号(NACK):在第9个SCL时钟周期,数据发送端将SDA的控制权释放转换为输入状态,数据接收端控制SDA的输出,通过SDA数据线输出一个高电平来产生一个非应答信号,数据发送端根据SDA数据的电平状态来识别响应信号。

  所以说应答信号和非应答信号是根据SDA数据在第9个SCL时钟周期时的电平状态来决定的,如果在第9个SCL时钟周期时SDA为低电平,那么就是应答信号;如果SDA是高电平,那么就是非应答信号。

  应答信号和非应答信号的作用如下:

    当主机向从机发送数据时:

  在正常的I2C通讯中,主机每发送一个字节的数据给从机后,从机都会反馈一个应答信号给主机,主机可以通过从机反馈的响应信号来判断与从机的通讯是否正常。如果发送完一个数据之后,从机反馈的是一个应答信号则说明数据传输成功,通讯正常;如果发送完一个数据之后,从机反馈的是一个非应道信号说明数据没有传输成功,通讯异常,可能是从机正在进行其它操作或异常,这时候主机应当发送一个停止信号来停止发送数据。

  一般主机在传输完一个数据给从机后,都会等待从机反馈应答信号,如果超时没有识别到应答信号,则说明收到的是非应答信号,程序如下:

 1 uint8_t I2C_WAIT_ACK(void)
 2 {   
 3     uint8_t Wait_cnt;
 4     
 5     I2C_SCL(0);      
 6     I2C_SDA_IN();
 7     I2C_SDA(0);    
 8     I2C_DELAY(2);
 9     I2C_SCL(1);    
10     
11     Wait_cnt = 255;
12     
13     while(RD_I2C_SDA != 0)
14     {
15         Wait_cnt--;
16         if(Wait_cnt == 0)
17         {
18             break;
19         }   
20     }
21 
22     I2C_SCL(0);     
23     I2C_SDA_OUT();   
24     
25     if(Wait_cnt == 0)
26     {
27         I2C_STOP();
28     }    
29 
30     return Wait_cnt;
31 }

  以上是主机等待从机应答信号的程序,第5~9行首先在SCL为低电平的时候将SDA数据线切换为输入状态,然后将SCL输出高电平产生第9个SCL时钟周期。第13~20行是等待应答信号,如果SDA位低电平,说明识别到了应答信号,直接通过break退出循环;如果一直识别不到应答信号,当Wait_cnt的值被自减到0后退出循环。第22~23行是将SDA数据线切换为输出状态。第25~28行是根据Wait_cnt来决定是否要产生停止信号,如果Wait_cnt等于0说明收到的是非应答信号,这时候通讯已经异常,所以产生一个停止信号来结束通讯;如果Wait_cnt不等于0说明收到的应答信号,通讯正常。函数通过返回Wait_cnt的值来指示通讯是否正常。

  当主机从从机读取数据时:

  当主机读取从机的数据时,每读取完一个字节的数据时,主机通过反馈一个应答信号或非应答信号给从机,用来通知从机是继续传输数据还是停止传输数据。

  如果主机在读取完一个从机发送的数据之后,产生一个应答信号给从机,那么从机会继续发送下一个数据给主机。

  如果主机在读取完一个从机发送的数据之后,产生一个非应答信号给从机,那么从机则会停止发送数据。

  也就是说,在主机读取从机的过程中,从机时根据主机反馈的响应信号来决定是否要传输下一个数据的。

  在主机读取从机数据时,主机产生的应答信号程序如下: 

1 void I2C_ACK(void)
2 {   
3     I2C_SCL(0); 
4     I2C_SDA(0);//发送低电平,响应从机
5     I2C_DELAY(2);         
6     I2C_SCL(1);    
7     I2C_DELAY(2);      
8     I2C_SCL(0); 
9 } 

  在SCL为高电平的时候,SDA输出一个低电平来表示应答信号。

  在主机读取从机数据时,主机产生的非应答信号程序如下:

1 void I2C_NOT_ACK(void)
2 {   
3     I2C_SCL(0); 
4     I2C_SDA(1);//发送低电平,响应从机
5     I2C_DELAY(2);         
6     I2C_SCL(1);    
7     I2C_DELAY(2);      
8     I2C_SCL(0); 
9 }

  在SCL为高电平的时候,SDA输出一个高电平来表示非应答信号。

  等待应答信号的程序在主机发送一个字节的函数I2C_WRITE_BYTE中用到;主机产生应答信号或非应答信号的程序在主机读取从机数据函数I2C_READ_BYTE中用到。

3、I2C通讯的读写过程

  主机写数据到从机的通讯过程如下图:

  图中灰色部分是主机产生的,白色部分是从机产生的。

  主机产生一个起始信号S,然后发送一个字节的从机设备地址SLAVE ADDRESS和写数据方向位R/W=0给从机,从机收到设备地址和R/W数据之后产生一个应答信号给主机,然后主机再发送一个字节的数据给从机,从机每收到一个数据都会产生一个应答信号给主机,当主机不需要再发送数据给从机或收到从机的非应答信号的时候产生一个停止信号P结束通讯。

  主机由从机中读取数据的通讯过程如下图:

  图中灰色部分是主机产生的,白色部分是从机产生的。

  主机产生一个起始信号S,然后发送一个字节的从机设备地址SLAVE ADDRESS和读数据方向位R/W=1给从机,从机收到设备地址和R/W数据之后产生一个应答信号给主机,然后主机开始读取从机发送过来的数据,主机每收到一个数据都会产生一个应答信号或非应答信号给从机,如果希望从机继续发送数据则反馈应答信号给从机;如果希望从机停止发送数据则反馈一个非应答信号给从机。当主机不需要再读取从机的数据时产生一个停止信号P结束通讯。

  I2C通讯的复合格式如下图:

   在I2C通讯过程中,可能需要先发送一个地址或命令给从机后再读取从机的数据,比如读取AT24C02的数据时,需要先发送需要读取的地址,然后再读取数据。主机需要先发送一个地址信息给从机,然后再读取数据,由于发送地址信息是一个写的过程,而读取数据是一个读的过程,这样就组成了复合通讯格式。

  主机首先发送一个起始信号S,然后发送一个字节的从机设备地址SLAVE ADDRESS和写数据方向位R/W=0给从机,等待从机反馈应答信号,再发送一个地址数据给从机后等待应答,然后再发送一个重复起始信号Sr,Sr信号之后发送一个字节的从机设备地址SLAVE ADDRESS和读数据方向位R/W=1给从机,以读取从机的数据,当读取完数据之后,主机产生一个结束信号P来停止通讯。 

原文地址:https://www.cnblogs.com/h1019384803/p/9569514.html