1. 程式人生 > >使用GPIO實現IIC Slave的方法討論【1】

使用GPIO實現IIC Slave的方法討論【1】

在本階段的工作中,需要實現一個由GPIO模擬的I2C從機工程設計,以前只使用GPIO模擬I2C設計過主機,對於從機的設計,還是首次。下面就講本次工作中從機設計思想做詳細記錄。


IIC的簡單總結



對於I2C訊號,需要有START,STOP,ACK,NACK,以及接收DATA。接收DATA是在SCL的低電平可能發生跳變,START和STOP是在高電平跳變。當SCL保持高電平的時候,SDA從H跳變到L,即為START;當SCL保持高電平的時候,SDA從L跳變到H,即為STOP。


  • START訊號:

 

圖1



  • STOP訊號:


圖2



  • ACK訊號:


圖3



  • 主機下發地址以及讀寫訊號:


圖4



程式設計以及分析



  • 為所使用的硬體平臺的暫存器配置
#define WAIT_IIC_SCL_HIGH   while ( !GET_SCL_DAT )
#define WAIT_IIC_SCL_LOW    while ( GET_SCL_DAT )
#define WAIT_IIC_SDA_HIGH   while ( !GET_SDA_DAT )
#define WAIT_IIC_SDA_LOW    while ( GET_SDA_DAT )


#define IIC_WAIT_START      WAIT_IIC_SCL_HIGH;  WAIT_IIC_SDA_LOW    
#define IIC_WAIT_STOP       WAIT_IIC_SCL_LOW; SDA_IN; WAIT_IIC_SCL_HIGH; WAIT_IIC_SDA_HIGH 


#define IIC_SLAVE_SEND_LOW  WAIT_IIC_SCL_LOW; SDA_OUT; SET_SDA_LOW; WAIT_IIC_SCL_HIGH      
#define IIC_SLAVE_SEND_HIGH WAIT_IIC_SCL_LOW; SDA_OUT; SET_SDA_HIGH; WAIT_IIC_SCL_HIGH


#define IIC_SLAVE_SEND_ACK  IIC_SLAVE_SEND_LOW
#define IIC_SLAVE_SEND_NAK  IIC_SLAVE_SEND_HIGH


  • 初始化
void iic_init(void)  // 完成GPIO作為I2C的初始化

完成GPIO時鐘寄存機配置等功能。



  • 接收地址以及讀寫命令模組
for(bitcount = 0; bitcount < 7; bitcount ++)
{
    WAIT_IIC_SCL_LOW;                   
    WAIT_IIC_SCL_HIGH;
    iic_slv_addr <<= 1;  //先移位,再讀數
    if(GET_SDA_DAT)
        iic_slv_addr |= 0x01;
    else
        iic_slv_addr |= 0x00;
}
iic_slv_addr <<= 1;
// 讀取7位地址


WAIT_IIC_SCL_LOW;
WAIT_IIC_SCL_HIGH;
if(GET_SDA_DAT)
    iic_master_rw = 1;
else
    iic_master_rw = 0;
// 讀寫標誌位


if (iic_slv_addr == SLAVE_ADDR)
    IIC_SLAVE_SEND_ACK;    // 地址正確,從機發送ACK訊號
    
    從機接收介面定義以及說明
    uint8_t L_i2c_rx( uint8_t xdata *pDestBuf, uint16_t *wRecLen);
    
    pDestBuf:接收資料儲存的目的地址 wRecLen:實際接收到的資料的長度

    
  • 接收核心程式碼分析
while(!recFinish)
{
    for(bitcount=0; bitcount<8; bitcount++)
    {
        while(GET_SCL_DAT);
        SDA_IN;
        while(!GET_SCL_DAT);


    r0 = GET_SDA_DAT;
    while(GET_SCL_DAT)
    {
        r1 = GET_SDA_DAT;
        if((r0 == 0) && (r1 == 1))
        {
            recFinish = 1;
            return 0;
        }
    }


    rxbyte <<= 1;
    if(r1)
        rxbyte |= 0x01;
    else
        rxbyte |= 0x00; 
}
buf[len] = rxbyte;
len++;
IIC_SLAVE_SEND_ACK;



在從機接收完地址以後,如果讀寫標誌位是寫(L),接下來,從機就會接收資料,接收資料完成的標誌為接收到了STOP訊號,就必須在從機發完ACK後的第一個SCL高電平時檢測是否有SDA從H跳轉到L,如果發生了,接收程式結束,如果沒有發生跳變,繼續接收資料的bit7,bit6,……bit0.


圖5



根據圖5,在時鐘節拍①中從機發送ACK訊號以後,因為GPIO的取樣頻率遠大於I2C的始終頻率(設計中使用100K),所以需要在傳送ACK後,要等待SCL變為L,在上面的程式碼中體現在c點,在時鐘節拍②中,SDA可能會發生變化。最重要的時需要在時鐘節拍③內取樣SDA,判斷是否有變化(即從f到g中連續取樣SDA),如果發生了SDA從H到L,那麼就認為接收到了STOP訊號,跳出接收資料的函式;如果發生SDA從L到H,會被認為是一個異常的訊號(START訊號,但不應該在此時出現,上段程式碼中沒有處理此異常情況,請注意),同樣也需要跳出接收資料的函式;如果SDA沒有發生任何變化,同時等待到SCL發生由H到L的變化,則意味著接收到了下一位元組的bit7……


這樣最核心的問題就變為怎樣判斷在時鐘節拍③中SDA是否發生了變化,並且發生了怎樣的變化。設計的思想為:在f點(上升沿)後取第一個SDA的取樣值r0,遍歷時鐘節拍③直到g點(下降沿),連續取樣SDA的下一個取樣值r1,當在時鐘節拍③內,只要發生了r1 != r0的時候,馬上跳出接收資料程式。但如果r1 === r0,在檢測到h點的時候,才把r1賦值給接收位元組rxbyte.


以上取樣能夠成功的一個前提是:SDA在SCL每一時鐘節拍的變化能夠被取樣到。


在此假設GPIO的時鐘為2MHz,SCL的傳輸速度為100KHz,時序關係如下圖所示:


圖6



上圖是比較理想的SCL的時鐘週期訊號,在每半個SCL的時鐘週期中,有10個取樣點,這樣確保了SCL上升沿後的第一個GPIO取樣到了r0=0,也能在後5個取樣點中採到了r1=1,在這種情況下,不會發生任何錯誤。


但是,實際情況並非如此,在GPIO模擬I2C的SCL訊號中,佔空比並不是50%,如果此時SDA的變化在GPIO第一個取樣沿之前就發生了變化,那麼就無法取樣到正確的電平變化訊號。


所以,以上設計方法能夠成功的前提為:SDA並不會在緊靠著SCL的上升沿或者下降沿而變化,也就是說SDA的任何變化,都能被GPIO的時鐘取樣到。


  • 從機發送介面定義以及說明
uint8_t L_i2c_tx(const uint8_t xdata * pSendBuf, uint16_t wSendLen);

pSendBuf:傳送資料快取地址 wSendLen:傳送資料的長度


  • 傳送核心程式碼分析


for(bytecount = 0; bytecount < len; bytecount ++)
{        
    txmask = 0x80;
    txbyte = buf[bytecount];
    for(bitcount = 0; bitcount < 8; bitcount ++)
    {
        WAIT_IIC_SCL_LOW;
        SDA_OUT;
        if ( txbyte & txmask )
            SET_SDA_HIGH;   
        else    
            SET_SDA_LOW;  
        WAIT_IIC_SCL_HIGH;
        txmask = txmask >> 1;       
    }


    WAIT_IIC_SCL_LOW;                                               
    SDA_IN;
    WAIT_IIC_SCL_HIGH;
    if ( GET_SDA_DAT )  
        break;
}
return (bytecount);



在傳送資料的時候需要注意,資料要在SCL的低電平時更新(SDA跳變),在SCL為高電平的時候保持不變。一個Byte傳送完成後,需要等待主機發送的ACK訊號,從機接收到ACK訊號後根據需要傳送的位元組數,判斷是否繼續傳送,還是要等待主機發送的STOP命令。

主從機通訊流程圖


另:本篇博文原發表於另一部落格賬號,特遷移至此