你不能不知的I2C总线

 I2C总线

I2C总线(Inter-Integrated Circuit Bus)是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。某些书籍或者文档中也写作IIC,读作“I方C”。

I2C是嵌入式中最常见的,也是最重要的总线通信协议之一。很多传感器、外围芯片都使用I2C协议。它具有如下特点:

(1)硬件线路简单:I2C总线只需要一根数据线和一根时钟线两根线,

(2)灵活:数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。

(3)可以连接设备数量多:连接到相同总线上的IC数量只受总线最大电容的限制。

4.8.1 I2C器件地址

I2C总线是一个主从结构的总线,所有的数据传输都必须由主机发起,通常单片机做主机,其他连接在I2C总线的设备称之为从机或者器件。

I2C还有一个重要的概念:器件地址。连接在I2C总线上的设备,除了主机之外,每个器件都有自己的地址。主机想要和某个器件通信时,先往I2C总线发送器件地址。

I2C器件地址一般为8位,最后一位是读写标志位。0表示主机要读取器件的数据;1表示主机要往器件写数据。

4.8.2 I2C时序

I2C总线只需要两根线,分别是时钟线(SCL)、数据线(SDA)。其中时钟线提供时间周期,时间周期越短则数据传输速率越快。数据线用来传输起始位、应答位,数据等。时序图如4.37所示。

图4.37 I2C时序图

数据格式主要有起始位、停止位、数据位、应答位(ACK)、NACK。

1. 起始位

当主机想要启动I2C数据传输时,需要要先往I2C总线发送起始位。起始位的条件是SCL线为高电平时,SDA线从高电平向低电平切换。

2. 停止位

当主机想要终止I2C数据传输时,需要往I2C总线发送停止位,释放I2C总线的占用。停止位的条件是SCL线为高电平时,SDA线从低电平向高电平切换。

3. 数据位

SDA数据线上的每个字节必须是8位,每次传输的字节数量没有限制。每个字节后必须跟一个响应位(ACK)。首先传输的数据是最高位(MSB),SDA上的数据必须在SCL高电平周期时保持稳定,数据的高低电平翻转变化发生在SCL低电平时期。

4. 应答位

每个字节传输必须带响应位,相关的响应时钟也由主机产生,在响应的时钟脉冲期间(第9个时钟周期),发送端释放SDA线,接收端把SDA拉低。

5. NACK位

以下情况会导致出现NACK位:

(1)接收机没有发送机响应的地址,接收端没有任何ACK发送给发射机

(2)由于接收机正在忙碌处理实时程序导致接无法接收或者发送

(3)传输过程中,接收机识别不了发送机的数据或命令

(4)接收机无法接收

(5)主机接收完成读取数据后,要发送NACK结束告知从机

4.8.3 模拟I2C

I2C属于比较简单的总线,完全可以根据I2C的时序,使用I/O模拟I2C。本文将使用STM32的GPIO口实现模拟I2C的功能,帮助读者理解I2C的时序控制。

打开Chapter4\05_I2C_24c02\mdk\IIC24c02.uvproj工程文件,接着打开24c02.c文件,模拟IIC的代码都在这个文件中。

1. I2C初始化

I2C的初始化部分代码主要是对STM32的GPIO进行初始化。GPIOB_9作为数据引脚(SDA),GPIOB_8作为时钟引脚(SCL),代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c     5行//I2C初始化void IIC_Init(void){                    

  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);  //打开GPIOB时钟  //GPIOB8,B9  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;          //输出模式  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;         //开漏输出  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;     //100MHz  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;           //上拉  GPIO_Init(GPIOB, &GPIO_InitStructure);                 //初始化    

  IIC_Stop();   //先给停止信号,复位I2C总线上所有的设备}


2. 起始信号

当SCL为高电平时,SDA出现一个下降沿表示I2C总线启动信号,代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c     23行//I2C启动信号void IIC_Start(void){

     //先把SDA输出引脚置高     IIC_SDAOUT=1;   

     //SCL引脚置高            IIC_SCL=1;

     //等待4us     delay_us(4);

     //SDA引脚拉低     IIC_SDAOUT=0;

     //等待4us     delay_us(4);

     //SCL引脚拉低     IIC_SCL=0;     //准备发送数据或者接收数据}


IIC_SCL指I2C的SCL引脚,IIC_SDAOUT 指I2C的SDA引脚输出,在24c02.h文件中分别被定义成PBout(8)和PBout(9),代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.h     8行#define IIC_SCL      PBout(8)  //SCL#define IIC_SDAOUT   PBout(9) // SDA


IIC_SDAOUT=1表示SDA引脚输出高电平。这里是GPIO输出高低电平的另外一种写法,等价于之前的GPIO_WriteBit(GPIOB, GPIO_Pin_9, Bit_SET)。

IIC_Start函数使用SDA、SCL引脚,通过输出高低电平和延时的操作,模拟了I2C启动信号。其时序如图4.38所示。

图4.38 I2C启动信号时序

3. 停止信号

当SCL高电平时,SDA出现一个上升沿表示I2C总线停止信号,代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c     34行void IIC_Stop(void){

     //SDA先低电平,这样才能出现上升沿     IIC_SDAOUT=0;

     delay_us(4);

     //SCL高电平     IIC_SCL=1;

     delay_us(4);

     //SDA由低电平变高电平,此时出现一个上升沿。                                 IIC_SDAOUT=1;                                     

}


4.应答信号

I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,在响应的时钟脉冲期间(第9个时钟周期),由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK)。主机等待从机应答信号的相关代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c     50行//返回值:1表示NACK, 0表示 ACKu8 MCU_Wait_Ack(void){

     u8 ack;

 

     IIC_SDAOUT=1;

     delay_us(1);       

     IIC_SCL=1;

     delay_us(1);

     //读取SDA总线电平     if (IIC_SDAIN)          

     {

             ack = 1;         //高电平则表示NACK应答     }

     else     {

             ack = 0;         //低电平则表示NACK应答     }

     IIC_SCL=0;

     delay_us(1);

     return ack; 

}


5.数据位发送

在I2C总线上传送的每一位数据都有一个时钟脉冲相对应。在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1则为高电平。时序如图4.39所示。

图4.39 数据位发送时序

6.发送一个字节

I2C写一个字节相当于往I2C总线发送了8个数据位,根据图4.39数据位发送时序,我们可以用I/O模拟,代码如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c  113行//参数:Senddata 要发送的数据void IIC_write_OneByte(u8 Senddata){                       

     u8 t;  

            

     IIC_SCL=0;   

     for(t=0;t<8;t++)

     {

             //先发送高位             IIC_SDAOUT=(Senddata&0x80)>>7;

             //左移1位             Senddata=(Senddata<<1);     

             delay_us(2);  

             IIC_SCL=1;

             delay_us(2);

             IIC_SCL=0;      

             delay_us(2);

     }       

}


其中比较关键的代码是Senddata的移位操作。

根据 & 和 >> 的特性,(Senddata&0x80)>>7相当于保留Senddata的最高位,其它位清零,同时再把最高位右移到最低位。相当于把Senddata最高位的数值赋给IIC_SDAOUT,从而实现SDA引脚根据Senddata的最高位输出响应的高低电平。

之后Senddata=(Senddata<<1),把Senddata的第2高位通过左移1位的方式,使Senddata的第2高位变成最高位。

再通过for循环,重复这两步操作,把Senddata的每一位都发送出去。为了方便直观理解,我们假设Senddata等于170,十六进制为:0xAA,二进制为:10101010。整个for循环的移位操作可以用图4.40直观的表示出来。

图4.40 移位操作流程图


7.读一个字节

读时序和发送时序相同,不同的是发送时需要在SCL低电平的时候更改SDA数据位,而读时需要在SCL高电平的时候读取SDA数据位。同时,每读取一位数据,都需要左移1位,保证高位在前。读完数据后需要发送ACK或者NACK应答信号。代入如下:

//Chapter4\05_I2C_24c02\USER\24C02\24c02.c  137行u8 IIC_Read_OneByte(u8 ack){

   u8 i,receivedata=0;



    for(i=0;i<8;i++ )

   {

        IIC_SCL=0;

        delay_us(2);

      IIC_SCL=1;

        receivedata<<=1;

        if(IIC_SDAIN)

      {

             receivedata++;  

      }

      delay_us(1);

    }                                         

    if (!ack)

        MCU_NOAck();

    else        MCU_Send_Ack();

    return receivedata;

}


4.8.4 小结

I2C是嵌入式中最常见的总线通信协议,读者需要熟练掌握,了解IIC的时序,并能使用I/O模拟I2C操作。


猜你喜欢

转载自blog.51cto.com/14640655/2476932