1. 程式人生 > >模擬IIC(轉載只為查閱方便,若侵權,立刪)

模擬IIC(轉載只為查閱方便,若侵權,立刪)

模擬IIC

IIC 即Inter-Integrated Circuit(積體電路匯流排),這種匯流排型別是由飛利浦半導體公司在八十年代初設計出來的一種簡單、雙向、二線制、同步序列匯流排,主要是用來連線整體電路(ICS) ,IIC是一種多向控制匯流排,也就是說多個晶片可以連線到同一匯流排結構下,同時每個晶片都可以作為實時資料傳輸的控制源。這種方式簡化了訊號傳輸匯流排介面。 
(來自百度百科)

簡單講IIC匯流排通訊就像你在和你物件打電話,講道理,步驟幾乎完全一致。回憶一下自己打電話的過程。 
你:撥號 
你物件:喂? 
你:巴拉巴拉– 
你物件:哦 
你:巴拉巴拉– 
你物件:哦 
你:掛啦,麼麼噠 
你物件:哦 
你:掛電話 
通話結束 
IIC匯流排時序


那現在來看一下IIC匯流排通訊過程

對於你來說,你是打電話的,屬於主動方,首先該你寫入資料(以寫入EEPROM為例) 
1,起始訊號(撥號動作 讀寫方向為 寫 因為你要填號碼) 
2,傳送要寫入的資料地址(填入你物件手機號) 
3,傳送要儲存資料(就是你巴拉巴拉–一堆的話) 
4,每寫入一個位元組EEPROM會迴應一個應答位0說明寫入成功(就是你物件回了個“哦”),未迴應則未成功寫入(就像你物件沒聽明白,就不會回答“哦”) 
5,停止訊號(掛電話)

讀資料流程,以EEPROM為例(就像打電話的過程中你聽你物件說話) 
1,起始訊號(依舊是撥號動作 讀寫方向為寫 因為你要填號碼) 
2,寫入地址(你物件手機號) 
3,重新進行起始訊號(此時讀寫方向為 讀)傳送資料地址(比如你說了一句:明天去哪吃飯) 
4,讀取器件返回的地址(你物件說:去你家吧) 
5,繼續讀資料則寫應答,不讀則寫入非應答(你接著問:吃點啥,你物件回答:隨便。你不想問了,說 掛了啊(非應答)) 
6,停止訊號(掛電話) 
(讀寫操作可以連續進行)

以上是讀寫過程,怎麼使用微控制器模擬出該過程,分別介紹各個步驟的函式實現 
IIC匯流排有兩條線,一條資料線SDA(記憶:DATA資料),一條時鐘線SCL(記憶:CLOCK時鐘)

(1),起始訊號 
由時序圖可見SCL高電平期間,SDA出現一個下降沿表示起始訊號。

程式碼實現

void IIC_Start()
{
    //資料線先保持為高,起始訊號要該口的下降沿
    SDA  = 1; 
    //時鐘線保持為高
    SCL = 1;
    //有一個大概4us的延時具體以器件而定
    IIC_Delay();
    //資料線拉低出現下降沿
    SDA = 0;
    //延時一小會,保證可靠的下降沿
    IIC_Delay();
    //拉低時鐘線,保證接下來資料線允許改變
    SCL = 0;
}

(2)資料傳輸 
當SCL為低電平的時候SDA允許變化,即傳送方必須先保證SCL為低電平再進行資料的讀寫操作,當SCL為高電平時,寫資料一方不可以變化,此時讀資料方讀取該位資料的0/1,8位資料後跟應答位,來決定是否繼續讀寫

寫一個位元組資料程式碼實現

void IIC_Send_Byte(uint8_t txd)
{
    //定義一個計數變數
    uint8_t i;
    //將時鐘線拉低允許資料改變
    SCL = 0;
    //按位傳送資料
    for(i = 0;i < 8; i ++)
    {
        //按位給SDA賦值 先發高位再發低位
        SDA = (txd&0x80 >> 7);
        //保證將要傳送的位一直是最高位
        txd <<= 1;
        //延時,保證一段可靠的電平
        IIC_Delay();
        //時鐘線拉高,此時資料線不得改變,用於對方讀取資料
        SCL = 1;
        //延時,得到可靠的電平
        IIC_Delay();
        //時鐘線拉低用於下一次資料改變
        SCL = 0;
        //延時 得到一段可靠的電平
        IIC_Delay();
    }

}

讀一個位元組資料程式碼實現

//返回值為收到的資料
//引數為是否應答1應答0不應答
uint8_t IIC_Read_Byte(uint8_t ack)
{
    //定義計數變數
    uint8_t i = 0;
    //定義接收變數
    uint8_t receive = 0;
    //此時要把資料線的模式切換為輸入模式 本程式中不予體現
    for(i = 0;i < 8; i ++)
    {
        //時鐘線拉高 讀資料保證對方資料不改變
        IIC_SCL = 1;
        //來一個延時保證電平可靠
        IIC_Delay();
        //先左移接收變數,防止迴圈結束時改變該變數
        receive <<= 1;
        //判斷資料線電平
        if(SDA)
        {
            //高電平的話接收變數自加,低電平不變化只左移,即保證了該位為0
            receive ++;
        }
        //延時一小會 保證一個可靠的電平
        IIC_Delay()
        //時鐘線拉低,允許下一位資料改變
        SCL = 0;
    }
    if(!ack)
    {
        //不需要應答 則給出非應答訊號,不再繼續
        IIC_NAck(); 
    }else{
        //需要應答 則給應答
        IIC_Ack();
    }
}

(3)停止訊號 
SCL高電平期間,SDA產生一個上升沿 表示停止

程式碼實現

void IIC_Stop()
{
    //先保證時鐘線為高電平
    SCL = 1;
    //保證資料線為低電平
    SDA = 0;
    //延時 以得到一個可靠的電平訊號
    IIC_Delay();
    //資料線出現上升沿
    SDA = 1;
    //延時保證一個可靠的高電平
    IIC_Delay();
}

(4)應答與非應答,等待應答 
根據時序圖可以看出,第九位資料持續為低電平的時候即為應答,這部分比較簡單

程式碼實現

//應答函式
void IIC_Ack()
{
    //資料線一直保持為低電平,時鐘線出現上升沿即為應答
    SCL = 0;
    SDA = 0;
    IIC_Delay();
    SCL = 1;
    IIC_Delay();
    //應答完成後 將時鐘線拉低 允許資料修改
    SCL = 0;
}
//非應答
void IIC_NAck()
{
    //非應答即相反 與應答區別即為資料線保持高電平即可
    SCL = 0;
    SDA = 1;
    IIC_Delay();
    SCL = 1;
    IIC_Delay();
    //最後要將時鐘線拉低 允許資料變化
    SCL = 0;
}

//等待應答
uint8_t IIC_Wait_Ack()
{
    //應答等待計數
    uint8_t ackTime;
    //先將資料線要設定成輸入模式本程式未體現,有應答則會出現下降沿
    //時鐘線拉高
    SCL = 1;
    IIC_Delay();
    //等待資料線拉低應答
    while(SDA){
        //如果在該時間內仍未拉低
        ackTime ++;
        if(ackTime > 250)
        {
            //認為非應答 停止訊號
            IIC_Stop();
            return 1;
        }
    }
    SCL = 0;
    return 0 ;
}

(4)讀寫資料過程 
寫資料過程 
起始訊號-》傳送器件地址-》等待應答-》傳送寫資料地址-》等待應答-》傳送寫入資料-》等待應答-》停止訊號 
(給你女票打電話-》等她接-》你說話-》等她哦-》你告訴他什麼事-》等她哦-》你掛了)

讀資料過程 
起始訊號 -》傳送器件地址-》等待應答-》 傳送讀資料地址-》等待應答-》重新起始訊號-》傳送器件地址+1(設定為讀模式)-》等待應答-》讀資料-》-》繼續讀 給應答-》不繼續給停止應答-》停止訊號

(給你女票打電話-》等他接-》你說夫人有何指示-》她說你閉嘴聽我說-》你們重新來了一遍撥號(可能女票脾氣比較大加強迫症)-》等你接-》她說事-》你聽著-》她說掛-》你掛)

程式碼實現

寫資料

void  Device_WriteData(uint8_t DeciveAddr,uint8_t DataAddr,uint8_t Data)
{
    //起始訊號
    IIC_Start();    
    //傳送器件地址                         
    IIC_Send_Byte(DeciveAddr);       
    //等待應答
    IIC_Wait_Ack();                          
    //傳送資料地址
    IIC_Send_Byte(DataAddr);                     
    //等待應答
    IIC_Wait_Ack();                          
    //傳送資料
    IIC_Send_Byte(Data);                     
    //等待應答
    IIC_Wait_Ack();                          
    //結束訊號
    IIC_Stop();     
}

//讀資料
//引數一 器件地址
//引數二 資料地址
//引數三 接收資料儲存
//引數四 接收長度
//
void Decive_ReadData(uint8_t DeciveAddr,uint8_t DataAddr,uint8_t *ReciveData,uint8_t num)
{
    //定義計數變數
    uint8_t i;
    //起始訊號
    IIC_Start();
    //傳送器件地址
    IIC_Send_Byte(DeciveAddr);
    //等待應答
    IIC_Wait_Ack();
    //傳送資料地址
    IIC_Send_Byte(DataAddr);                     
    //等待應答
    IIC_Wait_Ack();     

    //起始訊號
    IIC_Start()
    //傳送器件地址讀模式
    IIC_Send_Byte(DeciveAddr + 1);
    //等待應答
    IIC_Wait_Ack();
    //讀資料
    for(i = 0;i < (num-1);i ++)
    {
        //前num-1位資料時需要給應答的因為要繼續讀
        *ReciveData= IIC_Read_Byte(1);
        ReciveData++;
    }
    //最後一位資料不需要給應答 因為不用讀了
    *ReciveData = IIC_Read_Byte(0);
    //停止訊號
    IIC_Stop();
}

IIC匯流排流程基本結束,有些地方仍有不清晰之處,望提示待修繕。 
對於STM32而言,IO的輸入輸出模式是需要人工切換的,這就使得原本速率就比較低的IIC匯流排通訊變得更慢,現在主流的做法是在SDA使用前切換IO模式,但是對於STM32本身而言。它的開漏模式是可以同時兼任輸入輸出的,應當可以稍微的提高速率。