IIC 協議原理

qq_27270029發表於2017-08-18

IIC匯流排

7位I2C匯流排可以掛接127個不同地址的I2C裝置,0號"裝置"作為群呼地址.

 第一個位元組(為slave address)由7位地址和一位R/W讀寫位組成的,這位元組是個器件地址。

常用IIC介面通用器件的器件地址是由種型別號,及定址碼組成的,共7位。
如格式如下: 
  D7 D6 D5 D4 D3 D2 D1 D0
1-器件型別由:D7-D4 共4位決定的。這是由半導公司生產時就已固定此型別的了,也就是說這4位已是固定的。
2-使用者自定義地址碼:D3-D1共3位。這是由使用者自己設定的,通常的作法如EEPROM這些器件是由外部IIC的3個引腳所組合電平決定的(用常用的名字如A0,A1,A2)。這也就是定址碼。
所以為什麼同一IIC匯流排上同一型號的IIC只能最多共掛8片同種類晶片的原因了。

3-最低一位就是R/W位。   

 I2C匯流排進行資料傳送時,時鐘訊號為高電平期間,資料線上的資料必須保持穩定,只有在時鐘線上的訊號為低電平期間,資料線上的高電平或低電平狀態才允許變化。

 

               

起始和終止訊號 :SCL線為高電平期間,SDA線由高電平向低電平的變化表示起始訊號;SCL線為高電平期間,SDA線由低電平向高電平的變化表示終止訊號。

資料傳送格式

(1)位元組傳送與應答

每一個位元組必須保證是8位長度。資料傳送時,先傳送最高位(MSB),每一個被傳送的位元組後面都必須跟隨一位應答位(即一幀共有9位)。如果一段時間內沒有收到從機的應答訊號,則自動認為從機已正確接收到資料。

最後一位R/W為告訴從機下一位元組資料是要讀還是寫,0為寫入,1為讀出。

 

 

 

 

 

任一地址讀取資料格式

硬體IIC與模擬IIC

硬體IIC中程式要等介面晶片的應答電平,等不到就死等,所以會卡死(估計未加超時處理所致),模擬IIC中的等待電平是cpu自己產生的,錯過了就錯過了,下次再來。如果說移植性,那完全在你的程式是否寫得好了,和介面形式沒關係。看IIC協議先:兩條線可以掛多個裝置。IIC裝置(稍微有點智慧的)裡有個固化的地址。只有在兩條線上傳輸的值等於我(IIC裝置)的地址時,我才作出響應。

開始訊號:處理器讓SCL時鐘保持高電平,然後讓SDA資料訊號由高變低就表示一個開始訊號。同時IIC匯流排上的裝置檢測到這個開始訊號它就知道處理器要傳送資料了。

停止訊號:處理器讓SCL時鐘保持高電平,然後讓SDA資料訊號由低變高就表示一個停止訊號。同時IIC匯流排上的裝置檢測到這個停止訊號它就知道處理器已經結束了資料傳輸,我們就可以各忙各個的了,如休眠等。

再看資料怎麼傳:SDA上傳輸的資料必須在SCL為高電平期間保持穩定:因為外接IIC裝置在SCL為高電平的期間採集資料方知SDA是高或低電平。SDA上的資料只能在SCL為低電平期間翻轉變化。

響應訊號(ACK):處理器把資料發給外接IIC裝置,如何知道IIC裝置資料已經收到呢?就需要外接IIC裝置迴應一個訊號給處理器。處理器發完8bit資料後就不再驅動匯流排了(SDA引腳變輸入),而SDA和SDL硬體設計時都有上拉電阻,所以這時候SDA變成高電平。那麼在第8個資料位,如果外接IIC裝置能收到訊號的話接著在第9個週期把SDA拉低,那麼處理器檢測到SDA拉低就能知道外接IIC裝置資料已經收到。

IIC資料從最高位開始傳輸。

再進一步說:IIC匯流排是允許掛載多個裝置的,如何訪問其中一個裝置而不影響其他裝置呢?

用7bit表示從地址,那麼可以掛載的從裝置數是2的7次方128個。處理器想寫的話:先傳送起始位,再發一個8bit資料:前7bit表示從地址,第8bit表示讀或者寫。0 write是處理器往IIC從裝置發,1read是IIC從裝置往處理器發。第9個時鐘週期回覆響應訊號。

下面就以AT24Cxx為例詳細說明一下:

首先發出一個start訊號,從裝置地址,R/W(0,寫),迴應ACK表示有這個從裝置存在。這時候是處理器從指定的從裝置讀資料的從裝置裡8bit儲存地址的指定。所以這裡R/W是0為寫。ACK迴應有這個裝置的話,處理器把要訪問的從裝置裡的8bit儲存地址寫好。ACK對方迴應。繼續一個start訊號+從裝置地址,最低位是高電平表示讀資料,迴應ACK表示有這個從裝置存在。在讀資料的時候,每發出一個時鐘,處理器會SDA上的資料存起來。那麼發出8個時鐘後處理器就能得到8位的資料。這時候若想連續讀就不斷迴應ACK訊號否則就發出停止訊號。

讀的過程:start訊號,從裝置地址,寫,待讀取儲存地址,再一個start訊號,從裝置地址,讀,8個時鐘,從裝置就把對應的資料反饋給處理器。

start訊號,哪一個裝置地址,寫,緊跟連續兩個位元組的資料:要寫的地址,對方收到8bit地址後迴應ACK,再8bit資料發給從裝置,對方收到8bit資料後迴應ACK,處理器寫完後傳送停止訊號。

 

1.感測器讀取

MPU6050感測器與控制器的通訊方式是I2C匯流排協議。該協議有兩根訊號線,分別是資料線和時鐘線,時鐘線是為資料的儲存位置做基準,資料線根據時鐘線的第幾個時鐘沿確定該位是第幾位資料,從而達到接收和傳送資料的目的。I2C匯流排時序圖4.4所示。

圖4.4 IIC匯流排時序圖

STM32系列控制器晶片對感測器MPU6050的資料讀取包括加速度和角速度兩部分,控制器內雖然已經有了硬體I2C介面,但是為了方便程式的可移植性和便於除錯,本設計使用普通I/O口的模擬I2C,即根據I2C的協議自己編寫軟體協議程式控制時序圖。具體的讀取資料指令如下:

IICreadBytes(MPU_ADDRESS,MPU6050_RA_ACCEL_XOUT_H,14,buffer)

MPU6050_Lastax=(((int16_t)buffer[0]) << 8) | buffer[1];

MPU6050_Lastay=(((int16_t)buffer[2]) << 8) | buffer[3];

MPU6050_Lastaz=(((int16_t)buffer[4]) << 8) | buffer[5];

//跳過溫度ADC          

MPU6050_Lastgx=(((int16_t)buffer[8]) << 8) | buffer[9];

MPU6050_Lastgy=(((int16_t)buffer[10]) << 8) | buffer[11];

MPU6050_Lastgz=(((int16_t)buffer[12]) << 8) | buffer[13];

其中MPU_ADDRESS為 MPU-6050 的 I2C 匯流排地址,MPU_RA_ACCEL_XOUT_H則為 X 軸加速度高位暫存器在 MPU-6050 中的地址,微控制器操作 I2C 匯流排從開始地址為MPU_RA_ACCEL_XOUT_H的地方讀取 14個位元組存入快取器buffer中,這14個位元組的前6個位元組是3個軸的加速度資料,後6個位元組是3個軸的角速度資料,中間的2個位元組是溫度感測器資料,我們用不到所以捨棄。將得到的資料根據高位在前,低位在後的原則進行資料合成,得到了三個軸的16bit的原始感測器測量值。


 


//產生IIC起始訊號

voidIIC_Start(void)

   SDA_OUT(); //sda線輸出

   delay_us(1);

   IIC_SDA=1;     

   IIC_SCL=1;

   delay_us(4);

 IIC_SDA=0;//START:when CLK is high,DATA changeform high to low

delay_us(4);

IIC_SCL=0;//鉗住I2C匯流排,準備傳送或接收資料

}   

//產生IIC停止訊號

voidIIC_Stop(void)

{

   SDA_OUT();//sda線輸出

   IIC_SCL=0;

   IIC_SDA=0;//STOP:when CLK is high DATA changeform low to high

   delay_us(4);

   IIC_SCL=1;

   IIC_SDA=1;//傳送I2C匯流排結束訊號

   delay_us(4);          

}

//等待應答訊號到來

//返回值:1,接收應答失敗

//        0,接收應答成功

u8IIC_Wait_Ack(void)

{

   u8 ucErrTime=0;

   SDA_IN();//SDA設定為輸入 

   IIC_SDA=1;

  delay_us(1);     

   IIC_SCL=1;

   delay_us(1);

   while(READ_SDA)

   {

      ucErrTime++;

      if(ucErrTime>250)

      {

        IIC_Stop();

        return 1;

      }

   }

   IIC_SCL=0;//時鐘輸出0

   return 0; 

}

//產生ACK應答

voidIIC_Ack(void)

{

   IIC_SCL=0;

   SDA_OUT();

   IIC_SDA=0;

   delay_us(2);

   IIC_SCL=1;

   delay_us(2);

   IIC_SCL=0;

}

 

//不產生ACK應答       

voidIIC_NAck(void)

{

   IIC_SCL=0;

   SDA_OUT();

   IIC_SDA=1;

   delay_us(2);

   IIC_SCL=1;

   delay_us(2);

   IIC_SCL=0;

}                            

//IIC傳送一個位元組

//返回從機有無應答

//1,有應答

//0,無應答       

voidIIC_Send_Byte(u8 txd)

{                       

    u8 t;  

    SDA_OUT();      

    IIC_SCL=0;//拉低時鐘開始資料傳輸

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

    {  IIC_SDA=(txd&0x80)>>7;

        txd<<=1;      

      delay_us(2);   //對TEA5767這三個延時都是必須的

      IIC_SCL=1;

      delay_us(2);

      IIC_SCL=0;

      delay_us(2);

    }

}     

//讀1個位元組,ack=1時,傳送ACK,ack=0,傳送nACK  

u8IIC_Read_Byte(unsigned char ack)

{

Unsignedchar i,receive=0;

   SDA_IN();//SDA設定為輸入

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

   {

        IIC_SCL=0;

        delay_us(2);

         IIC_SCL=1;

        receive<<=1;

   if(READ_SDA)receive++;  

      delay_us(1);

    }           

    if (!ack)

        IIC_NAck();//傳送nACK

    else

        IIC_Ack(); //傳送ACK  

    return receive;

}

 

/*功能:讀取指定裝置 指定暫存器的一個值輸入I2C_Addr  目標裝置地址addr暫存器地址返回讀出來的值*/

Unsignedchar

I2C_ReadOneByte(unsignedchar I2C_Addr,unsigned char addr)

{

   unsigned char res=0;

   IIC_Start(); 

   IIC_Send_Byte(I2C_Addr);    //傳送寫命令

   res++;

   IIC_Wait_Ack();

   IIC_Send_Byte(addr); res++;  //傳送地址

   IIC_Wait_Ack();   

   //IIC_Stop();//產生一個停止條件  

   IIC_Start();

   IIC_Send_Byte(I2C_Addr+1); res++;          //進入接收模式         

   IIC_Wait_Ack();

   res=IIC_Read_Byte(0);   

    IIC_Stop();//產生一個停止條件

 

   return res;

}

u8MPU_Read_Byte(u8 reg)

{

   u8 res;

    IIC_Start();

   IIC_Send_Byte((0x68<<1)|0);//傳送器件地址+寫命令 

   IIC_Wait_Ack();     //等待應答

    IIC_Send_Byte(reg);  //寫暫存器地址

    IIC_Wait_Ack();   //等待應答

    IIC_Start();

   IIC_Send_Byte((0x68<<1)|1);//傳送器件地址+讀命令 

    IIC_Wait_Ack();   //等待應答

   res=IIC_Read_Byte(0);//讀取資料,傳送nACK

    IIC_Stop();       //產生一個停止條件

   return res;  

}

 

/*

u8IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)

*功能:讀取指定裝置 指定暫存器的 length個值

輸入  dev  目標裝置地址

reg暫存器地址length 要讀的位元組數*data 讀出的資料將要存放的指標返回 讀出來的位元組數量*/

u8IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)

{

   u8 count = 0;

   IIC_Start();

   IIC_Send_Byte(dev);    //傳送寫命令IIC_Wait_Ack();

IIC_Send_Byte(reg);   //傳送地址

 IIC_Wait_Ack();  

 IIC_Start();

 IIC_Send_Byte(dev+1);  //進入接收模式

IIC_Wait_Ack();

for(count=0;count<length;count++)

{  if(count!=length-1)

data[count]=IIC_Read_Byte(1);//帶ACK的讀資料

else data[count]=IIC_Read_Byte(0);//最後一個位元組NACK}

 IIC_Stop();//產生一個停止條件

    return count;

}

 

/*將多個位元組寫入指定裝置 指定暫存器輸入dev目標裝置地址reg暫存器地址length 要寫的位元組數*data  將要寫的資料的首地址返回是否成功

*/

u8IICwriteBytes(u8 dev, u8 reg, u8 length, u8* data)

{

   u8count = 0;

   IIC_Start();

   IIC_Send_Byte(dev);    //傳送寫命令

   IIC_Wait_Ack();

   IIC_Send_Byte(reg);   //傳送地址

 IIC_Wait_Ack();  

for(count=0;count<length;count++)

{IIC_Send_Byte(data[count]);

IIC_Wait_Ack();

    }

   IIC_Stop();//產生一個停止條件

    return 1; //status == 0;

}

/*讀取指定裝置 指定暫存器的一個值輸入dev目標裝置地址reg  暫存器地址*data讀出的資料將要存放的地址返回1

*/

u8IICreadByte(u8 dev, u8 reg, u8 *data)

{*data=I2C_ReadOneByte(dev,reg);

    return 1;

}

Unsignedchar

IICwriteByte(unsignedchar dev, unsigned char reg, unsigned char data){

    return IICwriteBytes(dev, reg, 1,&data);

}

 

/**************************實現函式********************************************

*函式原型:    u8 IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8length,u8 data)

*功  能:     讀修改 寫 指定裝置 指定暫存器一個位元組 中的多個位

輸入  dev  目標裝置地址

      reg      暫存器地址

      bitStart 目標位元組的起始位

      length  位長度

      data   存放改變目標位元組位的值

返回   成功 為1

      失敗為0

*******************************************************************************/

u8IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8 length,u8 data)

{

 

    u8 b;

    if (IICreadByte(dev, reg, &b) != 0) {

        u8 mask = (0xFF << (bitStart +1)) | 0xFF >> ((8 - bitStart) + length - 1);

        data <<= (8 - length);

        data >>= (7 - bitStart);

        b &= mask;

        b |= data;

        return IICwriteByte(dev, reg, b);

    } else {

        return 0;

    }

}

 

/**************************實現函式********************************************

*函式原型:    u8 IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8data)

*功  能:     讀修改 寫 指定裝置 指定暫存器一個位元組 中的1個位

輸入  dev  目標裝置地址

      reg      暫存器地址

      bitNum 要修改目標位元組的bitNum位

      data 為0時,目標位將被清0 否則將被置位

返回   成功 為1

      失敗為0

*******************************************************************************/

u8IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8 data){

    u8 b;

    IICreadByte(dev, reg, &b);

    b = (data != 0) ? (b | (1 << bitNum)): (b & ~(1 << bitNum));

    return IICwriteByte(dev, reg, b);

}

 

//------------------Endof File----------------------------

相關文章