天天看點

通信協定:IIC協定的原理和模拟IIC的實作

1、IIC協定的由來

IIC協定最早是在1982年由飛利浦公司設計開發的,它是一種兩線制(SDL + SCL)的串行通行方式,它也是主從機之間通信的方式,在今天也是被廣泛的應用在很多的産品裝置上。

使用IIC協定進行資料通信的裝置,它既可以作為主機又可以作為從機(支援多主多從),并且它是一種半雙工的通信方式。

另外,IIC協定還是帶有總線仲裁功能的一種通信協定!

2、IIC 協定的一些參數

IIC 作為一種通信的協定,它是包含了幾個相關的特征參數的,如下所示:

通信協定:IIC協定的原理和模拟IIC的實作

注:IIC協定是一種半雙工、同步的通信方式!

3、IIC 協定的通信速率

IIC可以支援的通信速率範圍較大,可以很好的滿足多種裝置對于不同的通信速度的要求,常見的IIC支援的速率有以下幾個:

1)普通模式(100kHz即100kbps)

2)快速模式(Fm)(400kHz)

3)快速模式+(Fs+)(1MHz)

4)高速模式(Hs)(3.4MHz)

5)超高速模式(UFm)(5MHz)

當然,以上标明的速率一般指的是硬體IIC的速率,對于通過軟體模拟實作的IIC,它的速率是受到所使用的CPU的處理速度和性能影響的,不可以一概而論!

4、IIC 協定的接口

IIC 協定的接口有兩個:一個是用于時鐘同步的時鐘線 SCL,另一個是用于資料收發的資料線 SDL。一般我們也稱為串行資料線 SDA 和串行時鐘線 SCL。

連接配接到IIC總線上的裝置通過這兩根線互相傳遞資訊,SDA 和 SCL 都是雙向線,可以互相之間進行資訊的互動,但是這樣的互動是半雙工的,同一時刻隻能有一個方向進行資料的操作,不能同時進行。

IIC裝置上的兩根通信線一般示意如下:

通信協定:IIC協定的原理和模拟IIC的實作

重要:一般IIC總線上都會接兩個上拉電阻(如上圖的R27、R28),阻值一般選擇為4.7K,這兩個上拉電阻有很大的用處:

1)提高總線的驅動能力

2)讓總線在空閑時處于高電平,保證下次能夠快速啟動傳輸

5、IIC 協定的時序圖

IIC 協定的一次通信過程如下圖所示:

通信協定:IIC協定的原理和模拟IIC的實作

IIC 協定的每部分分解介紹如下:

(1)IIC總線的起始信号

IIC總線發送啟動信号的時序如圖:

通信協定:IIC協定的原理和模拟IIC的實作

使用IIC協定進行通信是,首先要發送起始信号。

起始信号是需要時鐘線SCL穩定的保持在高電位,SDA由高電位變化為低電位。啟動信号發送完成之後就可以進行資料的發送了。

如果在一次通信過程中,有兩台以上的裝置同時發出了起始信号,都希望獲得總線控制權的話,那麼第一個發出起始信号的裝置将獲得總線控制權,作為主裝置開始傳輸資料。這就是IIC的總線仲裁!

注意:起始信号由主機負責産生。

(2)停止信号

IIC總線發送停止信号的時序如圖:

通信協定:IIC協定的原理和模拟IIC的實作

停止信号是在時鐘線SCL為高電位的時候,資料線SDA由低電平變化為高電平。停止信号一般是在通信完成之後或者通信失敗退出之後發送的。

注意:停止信号由主機負責産生。

(3)資料傳輸的有效性

資料的傳輸是在發送完成了啟動信号之後便可以進行資料的傳輸了。

IIC資料傳輸的協定如下圖:

通信協定:IIC協定的原理和模拟IIC的實作

IIC 協定要求在時鐘信号SCL為高電位的期間,資料線SDA上的資料要保持穩定,不能發生變化(上圖中1的位置)。隻有在時鐘信号SCL電位變低的時候,資料線SDA上的電平狀态才能發生跳變。

每一個資料的bit位傳輸需要一個時鐘脈沖,一次傳輸最多是8bit。

一個完整的傳輸過程的通信時序如圖:

通信協定:IIC協定的原理和模拟IIC的實作

通信開始時,最開始發送的都是位址幀。比如,一個7Bit的位址,首先發出的是最高位,即讀寫位(1-讀,0-寫),用于訓示目前的通信是讀操作還是寫操作。

(4)應答(ACK / NACK)

IIC 協定幀的第9位是應答位(ACK/NACK)。所有幀(資料或位址)都是一樣的。一旦發送幀的前8位,接收裝置就可以控制資料線SDA進行應答。

如果接收裝置在第9個時鐘脈沖沒有将SDA線拉低進行應答,則可能是接收裝置沒有接收到資料,或者出現錯誤。在這種情況下,主機需要決定該做什麼樣的處理(一般考慮重發或者退出)。

注意:SCL時鐘信号由主機負責産生。資料的發送是高位先發的!

6、IIC 總線的仲裁

IIC 總線仲裁指的是什麼呢?

IIC總線支援多個主機同時在總線上發送資料,但是同一時刻隻能有一個主機傳送資料。是以必須要通過某些手段來決定哪個主機獲得總線的控制權,其它的沒有獲得主機控制權的裝置就隻能進行等待,直到獲得總線控制權才能進行資料的傳輸。

IIC總線仲裁的方式有兩種:時鐘同步、仲裁。它們分别如下:

(1)時鐘同步

時鐘同步是通過時鐘線SCL來實作的。在時鐘信号SCL由高到低的切換過程中,IIC器件會開始數自身的低電平周期。當主器件的時鐘信号變為低電平的時候,它會使SCL線保持這個電平狀态直到達到高電平。假如這個時候有另外一個器件的時鐘依然是處于低電平的周期,這個時鐘的低到高的變化不會改變SCL線的狀态。

是以,SCL線被有着最長的低電平周期的器件占有總線的控制權,而這個時候低電平周期短的器件會進入高電平的等待狀态,直到目前的主器件釋放總線控制權,自身能夠獲得總線控制權才會改變這些狀态。

時鐘同步的時序示意圖如下:

通信協定:IIC協定的原理和模拟IIC的實作

(2)仲裁

仲裁和同步一樣,都是為了解決多主機情況下的總線控制沖突。仲裁的過程與從機無關。

隻有在總線空閑的時候主機才可以啟動傳輸。兩個主機可能在比較短的時間内在總線上同時産生一個有效的起始信号,這種情況下需要仲裁來決定由哪個主機占有總線控制權來完成資料傳輸。

仲裁是逐位進行,在每一位資料的仲裁期間,當時鐘線SCL為高電平時,每個主機都檢查資料總線SDA上的電平是否和自己要發送的相同。

這個過程需要持續很多位。理論上講,如果兩個主機所傳輸的内容完全相同,那麼他們能夠成功傳輸而不出現錯誤。但是,如果一個主機發送高電平但檢測到SDA總線上的電平為低時,則認為自己仲裁失敗并關閉自己的SDA資料線上的資料傳輸,而另一個主機則繼續完成自己的傳輸。

IIC總線仲裁的時序示意圖如下:

通信協定:IIC協定的原理和模拟IIC的實作

7、IIC通信的流程

每個IIC裝置都通過唯一的器件位址進行識别,根據裝置功能,他們既可以是發送器也可作為接收器。通信的流程如下:

1)IIC從機檢測到IIC總線上的起始信号之後,就開始從總線上接收位址,之後會把從總線接收到的位址和自身的器件位址(通過軟體程式設計)進行比較,一旦兩個位址相同,IIC從機将發送一個确認應答(ACK),并響應總線的後續指令;

2)發送或接收所資料;

3)發送或接收完成之後,在收到應答信号ACK之後結束資料的傳輸。

此外,如果軟體開啟了廣播呼叫,則IIC從機始終對一個廣播位址 (0x00)發送确認應答。I2C子產品始終支援7位和10位的位址。

(1)有關位址幀的發送

1)7 位位址的 IIC 通訊流程

通信協定:IIC協定的原理和模拟IIC的實作

7Bit位址的通信中,開始信号之後的第一幀是位址幀+讀寫位,剛好是8Bit的資料,直接發送,等待從裝置應答之後便可以進行資料的通信。

2)10 位位址的 IIC 通訊流程(主機發送)

通信協定:IIC協定的原理和模拟IIC的實作

對于10-bit地的址裝置,需要使用兩個幀來傳輸10Bit的slave位址。

第一個幀的前5個bit固定為b11110xx,後接slave位址的高2位,第8位仍然是讀寫(R/W)位,接着是一個ACK位,由于總線上可能有多個10 Bit 從裝置位址的高2bit相同,是以這個ACK可能由多有slave裝置傳回。

第二個幀緊接着第一幀發送,包含slave位址的低8位(7:0),接着該位址的slave回複一個ACK(或NACK)。

注意:10-bit位址的裝置和7-bit位址的裝置在總線中是可以并存的,因為7-bit位址的高5位不可能是b11110。

3)10 位位址的 I2C 通訊流程(主機接收)

通信協定:IIC協定的原理和模拟IIC的實作

8、模拟IIC的實作

注意:本文的代碼僅用于個人學習使用,請勿擅自用于商業用途!

模拟IIC的實作是使用單片機的IO口模拟IIC的協定時序,實作IIC的通信。既然要使用單片機的IO口進行模拟,是以需要先進行一些定義,如下:

// 頭條:嵌入式之入坑筆記

#define IIC_WRITE 0x00    // 從機寫入
#define IIC_READ  0X01    // 從機讀取
#define IIC_ACK   0           // I2C器件應答,拉低總線

// 設定資料線 SDA -- PB.6
#define IIC_SDA_INPUT()  { GPIOB->MODER&=~(3<<(6*2));GPIOB->MODER|=0<<(6*2); delay_us(2);}
#define IIC_SDA_OUTPUT() { GPIOB->MODER&=~(3<<(6*2));GPIOB->MODER|=1<<(6*2); delay_us(2);}
#define IIC_SDA_HIGH()   { GPIOB->BSRRL |= 1<<6 ;  delay_us(2);}
#define IIC_SDA_LOW()    { GPIOB->BSRRH |= 1<<6;   delay_us(2);}
#define IIC_SDA_IO()     ( GPIOB->IDR  & (1<<6) )

// 設定時鐘線 SCL -- PB.7
#define IIC_SCL_INPUT()  { GPIOB->MODER&=~(3<<(7*2));GPIOB->MODER|=0<<(7*2); delay_us(2);}
#define IIC_SCL_OUTPUT() { GPIOB->MODER&=~(3<<(7*2));GPIOB->MODER|=1<<(7*2); delay_us(2);}
#define IIC_SCL_HIGH()   { GPIOB->BSRRL |= 1<<7 ;  delay_us(2);}
#define IIC_SCL_LOW()    { GPIOB->BSRRH |= 1<<7;   delay_us(2);}
#define IIC_SCL_IO()     ( GPIOB->IDR  & (1<<7) )      

注:SDA、SCL修改為自己所使用的單片機IO進行設定即可。

(1)發送IIC起始信号

IIC 起始信号的代碼實作如下:

// 頭條:嵌入式之入坑筆記

void I2C_Start(void)
{
      uint32_t num;
      IIC_SDA_OUTPUT();
      IIC_SCL_OUTPUT();
      IIC_SDA_HIGH();
      IIC_SCL_HIGH();
      Delay();
      num=2000;              /* 用于判斷IIC從機是否空閑          */
      while(num--)
      {
          if(IIC_SCL_IO())   /* 根據IIC協定,時鐘線拉高空閑 */
          {
              break;
          }
      }
      Delay();
      IIC_SDA_LOW();
      Delay();
      Delay();
      IIC_SCL_LOW();
      Delay();
}      

(2)發送IIC停止信号

IIC 停止信号的代碼實作如下:

// 頭條:嵌入式之入坑筆記

void I2C_Stop()
{
      uint32_t num;
      IIC_SDA_LOW();
      Delay();
      IIC_SCL_LOW();
      Delay();
      IIC_SCL_HIGH();
      Delay();
      num=2000;
      while(num--)
      {
          if(IIC_SCL_IO())
          {
              break;
          }
      }
      IIC_SDA_HIGH();
      Delay();
}      

(3)IIC的應答信号

// 頭條:嵌入式之入坑筆記

// IIC發送成功應答
uint32_t I2C_SetACK(void)
{
        uint32_t ack;
        uint32_t num;
        IIC_SCL_LOW();
        Delay();
        IIC_SDA_HIGH();
        IIC_SDA_OUTPUT();
        Delay();
        IIC_SDA_LOW();
        Delay();
        IIC_SCL_HIGH();
        num=2000;
        while(num--)                                                        /* 需要判斷IIC時鐘是否拉高      */
        {
            if(IIC_SCL_IO())
            {
                break;
            }
        }
        Delay();
        IIC_SCL_LOW();
        Delay();
        return ack;
}

// IIC發送失敗應答
uint32_t I2C_SetNoACK(void)
{
      uint32_t ack,num;
      IIC_SCL_LOW();
      Delay();
      IIC_SDA_HIGH();
      IIC_SDA_OUTPUT();
      Delay();
      IIC_SCL_HIGH();
      num = 2000;
      while(num--)
      {
          if(IIC_SCL_IO())   /* 需要判斷IIC時鐘是否拉高 */
          {
              break;
          }
      }
      Delay();
      IIC_SCL_LOW();
      Delay();
      return ack;
}

// IIC 擷取應答信号
uint32_t I2C_GetACK(void)
{
    uint32_t ack;
    IIC_SCL_LOW();
    Delay();
    IIC_SDA_INPUT();
    Delay();
    IIC_SCL_HIGH();
    Delay();
    if(IIC_SDA_IO()) //讀取SDA的電平狀态
        ack = 1;    //不響應
    else
        ack = 0;    //響應

    Delay();
    IIC_SCL_LOW();
    IIC_SDA_LOW();
    IIC_SDA_OUTPUT();
    Delay();
    return ack;
}      

(4)IIC 讀取一個位元組

// 頭條:嵌入式之入坑筆記

// IIC 讀取一個位元組
uint8_t I2C_Read(void)
{
        uint8_t t = 8;
        uint8_t dat = 0;
        uint32_t num;
        IIC_SCL_LOW();
        IIC_SDA_INPUT();
        while(t--)
        {
            Delay();
            Delay();
            IIC_SCL_HIGH();
            num = 2000;
            while(num--)  /* 需要判斷IIC時鐘是否拉高 */
            {
                if(IIC_SCL_IO())
                    break;
            }
            IIC_SCL_HIGH();
            Delay();
            dat <<= 1;

            if( IIC_SDA_IO() )
                dat++;
            IIC_SCL_LOW();
        }
        Delay();
        return dat;
}      

(5)IIC 寫一個位元組

// 頭條:嵌入式之入坑筆記

// IIC寫一個位元組
void I2C_Write(uint8_t dat)
{
        uint8_t t = 8;
        uint32_t num;
        IIC_SDA_LOW();
        IIC_SDA_OUTPUT();
        Delay();
        while(t--)
        {
            if(dat & 0x80)  //高位先發 MSB
            {
                IIC_SDA_HIGH();
            }
            else
            {
                IIC_SDA_LOW();
            }
            dat <<= 1;
            Delay();
            IIC_SCL_HIGH();
            Delay();
            num=2000;     
            while(num--)    /* 需要判斷IIC時鐘是否拉高      */
            {
                if(IIC_SCL_IO() )
                {
                     break;
                }
            }
            Delay();
            IIC_SCL_LOW();
            Delay();
    }
    Delay();
}      

繼續閱讀