一篇文章講清楚I2C通信及軟件編程--附開源軟件I2C驅動程序

1、導讀

        如果你想深入瞭解和學習I2C通信,請閱讀全篇文章,如果你只是要臨時快速的完成I2C通信外設的驅動,可以直接看代碼,複製到你的工程中去,編譯,調試很快就解決問題。本文重點還是想教你真正的理解了I2C通信的原理與編程,I2C通信一要掌握原理,二要自己真正的去編程實踐,如果你看完本篇文章,你能自己編寫一個軟件模擬I2C驅動程序,你就真正的掌握了I2C通信原理。

2、I2C通信協議簡介

    I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接於總線上的器件之間傳送信息。I2C總線上分成主機和從機兩種設備

     主機用於啓動總線傳送數據併產生時鐘以同步從機,此時任何被尋址的器件均被認爲是從器件.在總線上主機和從機、發送和接收的關係不是恆定的,而取決於此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然後主動發送數據至從器件,最後由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件.然後主機接收從器件發送的數據,最後由主機終止接收過程。在這種情況下.主機負責產生定時時鐘和終止數據傳送。

     

2.1 工作原理 

 

      SDA(串行數據線)和SCL(串行時鐘線)都是雙向I/O線,接口電路爲開漏輸出,需通過上拉電阻接電源VCC。當總線空閒時.兩根線都是高電平,連接總線的外同器件都是CMOS器件,輸出級也是開漏電路.在總線上消耗的電流很小,因此,總線上擴展的器件數量主要由電容負載來決定,因爲每個器件的總線接口都有一定的等效電容.而線路中電容會影響總線傳輸速度.當電容過大時,有可能造成傳輸錯誤.所以,其負載能力爲400pF,因此可以估算出總線允許長度和所接器件數量。

2.2  I2C總線特點

(1)在硬件上,I2C總線只需要一根數據線和一根時鐘線兩根線,總線接口已經集成在芯片內部,不需要特殊的接口電路,而且片上接口電路的濾波器可以濾去總線數據上的毛刺.因此I2C總線簡化了硬件電路PCB佈線,降低了系統成本,提高了系統可靠性。因爲I2C芯片除了這兩根線和少量中斷線,與系統再沒有連接的線,常用的IC外設可以很容易形成標準化和模塊化,便於重複利用。

(2)I2C總線是一個真正的多主機總線,如果兩個或多個主機同時初始化數據傳輸,可以通過沖突檢測和仲裁防止數據破壞,每個連接到總線上的器件都有唯一的地址,任何器件既可以作爲主機也可以作爲從機,但同一時刻只允許有一個主機。數據傳輸和地址設定由軟件設定,非常靈活。總線上的器件增加和刪除不影響其他器件正常工作。

(3)I2C總線可以通過外部連線進行在線檢測,便於系統故障診斷和調試,故障可以立即被尋址,軟件也利於標準化和模塊化,縮短開發時間。

(4)連接到相同總線上的IC數量只受總線最大電容的限制,串行的8位雙向數據傳輸位速率在標準模式下可達100Kbit/s,快速模式下可達400Kbit/s,高速模式下可達3.4Mbit/s。

(5)總線具有極低的電流消耗.抗高噪聲干擾,增加總線驅動器可以使總線電容擴大10倍,傳輸距離達到15m;兼容不同電壓等級的器件,工作溫度範圍寬

    

2.3  字節格式

     I2C總線上傳輸數據前要先發送一個起始位(起始條件 START),之後SDA 線上發送數據,數據的每個字節必須爲8 位,每次傳輸可以發送的字節數量不受限制。每個字節後必須跟一個應答位(ACK)。首先傳輸的是數據的最高位(MSB),如果從機要完成一些其他功能後(例如一個內部中斷服務程序)才能接收或發送下一個完整的數據字節,可以使時鐘線SCL 保持低電平,迫使主機進入等待狀態,當從機準備好接收下一個數據字節並釋放時鐘線SCL 後數據傳輸繼續, 數據傳輸完成後再發送一個停止位(STOP)表示一次通信完成。

 

 

2.4   應答響應

   數據傳輸必須帶響應位,相關的響應時鐘脈衝由主機產生。在響應的時鐘脈衝期間發送器釋放SDA 線(高)。

   在響應的時鐘脈衝期間,接收器必須將SDA 線拉低,使它在這個時鐘脈衝的高電平期間保持穩定的低電平。

   通常被尋址的接收器在接收到的每個字節後,

 

必須產生一個響應。當從機不能響應從機地址時(例如它正在執行一些實時函數不能接收或發送),從機必須使數據線保持高電平,主機然後產生一個停止條件終止傳輸或者產生重複起始條件開始新的傳輸。

      如果從機接收器響應了從機地址,但是在傳輸了一段時間後不能接收更多數據字節,主機必須再一次終止傳輸。這個情況用從機在第一個字節後沒有產生響應來表示。從機使數據線保持高電平,主機產生一個停止或重複起始條件。

     如果傳輸中有主機接收器,它必須通過在從機發出的最後一個字節時產生一個響應,向從機發送器通知數據結束。從機發送器必須釋放數據線,允許主機產生一個停止或重複起始條件。

2.5 時鐘同步

       所有主機在SCL線上產生它們自己的時鐘來傳輸I2C總線上的報文。數據只在時鐘的高電平週期有效,因此需要一個確定的時鐘進行逐位仲裁。

       時鐘同步通過線與連接I2C 接口到SCL 線來執行。這就是說SCL 線的高到低切換會使器件開始數它們的低電平週期,而且一旦器件的時鐘變低電平,它會使SCL 線保持這種狀態直到到達時鐘的高電平。但是如果另一個時鐘仍處於低電平週期,這個時鐘的低到高切換不會改變SCL 線的狀態。因此SCL 線被有最長低電平週期的器件保持低電平。此時低電平週期短的器件會進入高電平的等待狀態。

     當所有有關的器件數完了它們的低電平週期後,時鐘線被釋放並變成高電平。之後器件時鐘SCL線的狀態沒有差別,而且所有器件會開始數它們的高電平週期。首先完成高電平週期的器件會再次將SCL線拉低。

這樣產生的同步SCL 時鐘的低電平週期由低電平時鐘週期最長的器件決定,而高電平週期由高電平時鐘週期最短的器件決定。

3、軟件模擬I2C通信驅動程序編寫

        根據上面I2C通信協議的介紹,一個段I2C的通信時序分成“起始位”,“數據位”,“停止位”,“應答位”,軟件模擬I2C通信就是實現這幾個數據位的函數。

         本文的開源的軟件I2C驅動以STM32F103爲硬件,底層的IO調用使用了STM32F103的驅動,如果你使用的CPU與此不同,需要進行底層IO驅動部分的修改。

3.1  初始化函數

        軟件模擬I2C通信驅動程序要編寫的第一個函數i2c_init, 就是I2C通信接口使用的SCL, SDA兩個IO的初始化,根據I2C協議,這兩個IO要初始化成開漏模式。i2c_init函數首先打開SCL, SDA  IO口的時鐘,設置SCL, SDA爲輸出開漏模式,輸出爲高電平。

I2C_NUM是一個宏定義,用於定義軟件驅動支持的軟件模擬的I2C的通道的數量,可以設置爲1或2。後面的i2c_start, i2c_stop,i2c_ack等函數均有一個輸入參數取值爲0或1分別表示第1路,第2路I2C驅動。

        使用軟件模擬I2C驅動程序,需要首先調用這個函數進行一次初始化。

/****************************************************************************************
** Function name:      i2c_init()
** Descriptions:        I2C使用的IO管腳初始化函數
** input parameters:   無
** output parameters:  無
** Returned value:      無
****************************************************************************************/
OTP_VOID i2c_init(void)
{

#if(2 == I2C_NUM)
    /*打開I2C0使用的端口時鐘*/
    RCC_APB2PeriphClockCmd(I2C0_SOFT_SCL_PORT_PERIPH |  I2C0_SOFT_SDA_PORT_PERIPH, Enable);
    RCC_APB2PeriphClockCmd(I2C1_SOFT_SCL_PORT_PERIPH |  I2C1_SOFT_SDA_PORT_PERIPH , Enable);
    
    SDA_OUT(0);
    SDA_OUT(1);
    SDAH(0);
    SDAH(1);
    
    SCL_OUT(0);
    SCL_OUT(1);
    SCLH(0);
    SCLH(1);
     
#else
    
    RCC_APB2PeriphClockCmd(I2C0_SOFT_SCL_PORT_PERIPH |  I2C0_SOFT_SDA_PORT_PERIPH , Enable);
    
    SDA_OUT(0);
    SDAH(0);
    
    SCL_OUT(0);
    SCLH(0);
#endif    

}

     

3.2  起始位,停止位函數

           i2c_start函數用於產生起始位條件,即保持SCL高電平時, SDA產生一個下降沿。

           i2c_stop函數用於產生停止條件,即保持SCL高電平時,SDA產生一個上升沿。

/*********************************************************************************************************
** Function name:      i2c_delay()
** Descriptions:        I2C延時函數
** input parameters:   延時值
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
OTP_VOID i2c_delay(OTP_UINT32 time)
{
    OTP_UINT32 i;
    for( i = 0; i < time; i++)
    {
        /*do nothing*/
    }
}


/*********************************************************************************************************
** Function name:      i2c_start()
** Descriptions:        產生I2C起始條件
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL OTP_VOID i2c_start(OTP_UINT8 id)
{   
    SDA_OUT(id);  
    SDAH(id);
    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);
    SDAL(id); 
    i2c_delay(I2C_DELAY_TIME);
    SCLL(id);
    SDAH(id);
    
}

/*********************************************************************************************************
** Function name:      i2c_stop()
** Descriptions:        產生I2C停止條件
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL OTP_VOID i2c_stop(OTP_UINT8 id)
{
    SDA_OUT(id);
    
    SCLL(id);
    SDAL(id);
    i2c_delay(I2C_DELAY_TIME);
    
    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);

    SDAH(id);
    i2c_delay(I2C_DELAY_TIME);
}

3.3 應答

      應答分成應答位ACK, 非應答NACK兩個種。當主機進行寫數據給從機時,需要調用i2c_wait_ack等待從機應答獲取從機已經接收到寫入的數據。當主機進行讀取數據時,需要調用i2c_send_ack給從機進行應答,告訴從機傳輸下一下字節,讀取到最後一個字節後調用i2c_sens_no_ack給從機產生非應答,告訴從機讀取完成,不要再向總線上輸出數據了。

/*********************************************************************************************************
** Function name:      i2c_send_ack()
** Descriptions:        產生I2C應答條件
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL OTP_VOID i2c_send_ack(OTP_UINT8 id)   
{
    /*發送0,在scl爲高電平時使sda信號爲低*/
    SDA_OUT(id);
    
    SCLL(id);                  /*數據線可以輸出 */

    SDAL(id);                  /*在此發出應答或非應答信號 */    
    i2c_delay(I2C_DELAY_TIME); 

    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);  /*時鐘低電平週期大於4.7μs*/

    SCLL(id);
    SDAH(id);
    i2c_delay(I2C_DELAY_TIME);
}

/*********************************************************************************************************
** Function name:      i2c_send_no_ack()
** Descriptions:        產生I2C非應答條件
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL OTP_VOID i2c_send_no_ack(OTP_UINT8 id)
{
    /*發送1,在scl爲高電平時使sda信號爲高*/
    SDA_OUT(id);
    SCLL(id);           /*數據線可以輸出 */
    
    SDAH(id);
    i2c_delay(I2C_DELAY_TIME);

    SCLH(id);
    i2c_delay(I2C_DELAY_TIME);  

    SCLL(id);
    SDAH(id);
}

/*********************************************************************************************************
** Function name:      i2c_wait_ack()
** Descriptions:        等待I2C應答信號
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL STATUS i2c_wait_ack(OTP_UINT8 id)
{
    OTP_UINT32 flag = 0; 
    SCLL(id);   
    SDA_IN(id);
                        
    i2c_delay(I2C_DELAY_TIME);
    
    SCLH(id);       /*讀取應答位 */
    i2c_delay(I2C_DELAY_TIME);
    
    flag = SDA_R(id);
    SCLL(id);
    i2c_delay(I2C_DELAY_TIME);
    
    if(flag)
    {
        return ERROR;
    }
    return OK;
}

3.4 寫字節和讀字節

         I2C傳輸數據是一個字節爲單位來進行傳輸,讀寫數據首先要實現讀寫一個字節的函數,即讀字節i2c_read_byte和寫字節i2c_write_byte兩個。

/*********************************************************************************************************
** Function name:      i2c_write_byte()
** Descriptions:        向I2C寫一個字節
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL STATUS i2c_write_byte(OTP_UINT8 id, OTP_UINT8 data)
{
    /*向I2C總線寫一個字節*/
    OTP_UINT8 i = 0;

    SDA_OUT(id);
        
    for (i = 0; i < 8; i++ )    
    {   
        SCLL(id);
        i2c_delay(I2C_DELAY_TIME / 2);
        if (data & 0x80 )   
        {
            SDAH(id);
        }
        else
        {   
            SDAL(id);
        }
        data <<= 1;
        i2c_delay(I2C_DELAY_TIME / 2);

        SCLH(id);
        i2c_delay(I2C_DELAY_TIME);      
    }
    return i2c_wait_ack(id );
}

/*********************************************************************************************************
** Function name:      i2c_read_byte()
** Descriptions:        從I2C讀取一個字節
** input parameters:   操作的I2C端口
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
OTP_UINT8 i2c_read_byte(OTP_UINT8 id)
{
    OTP_UINT8 i = 0, data=0;
    
    SDA_IN(id);
    i2c_delay(I2C_DELAY_TIME);

    for(i = 0; i < 8; i++)
    {   
        SCLL(id);                       /*置時鐘線爲低,準備接收數據位*/
        i2c_delay(I2C_DELAY_TIME);  

        SCLH(id);                       /*置時鐘線爲高使數據線上數據有效*/
        i2c_delay(I2C_DELAY_TIME / 2);

        if(SDA_R(id))
        {
            data |= 1 << (7 - i) ;        /*讀數據位,接收的數據位放入retc中 */
        }
           
        i2c_delay(I2C_DELAY_TIME / 2);
    }
    SCLL(id);
    return(data);
    
}

3.5  總線數據讀寫

    I2C總線一般通信都是讀寫多個字節,I2C從器件如EEPROM,  IO擴展芯片,傳感器芯片(這芯片在I2C總線上做從器件)等,芯片內部設置有寄存器,對於這些芯片的訪問就是讀寫寄存器。

     當寫從器件寄存器時,I2C總線上需要先寫入器件的從機地址,再寫入寄存器的地址,最後寫入寄存器的數據,具體的寫入時序如下圖所示。根據時序圖,編寫一個向I2C從器件寫寄存器的函數i2c_send_data,這個函數調用上面介紹的函數,來實現一次對從器件的數據寫入。

/*********************************************************************************************************
** Function name:      i2c_send_data()
** Descriptions:        向i2c總線發送數據
** input parameters:   id--操作的I2C端口
**                      i2cAddr--器件地址
**                      addr--器件內部地址
**                      addr_len--內部地址長度
**                      pSendData--緩衝區地址
**                      sendLen--數據長度
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
STATUS i2c_send_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,    OTP_UINT16 addr,  
                     OTP_UINT8 addr_len, OTP_UINT8 *pSendData, OTP_UINT8 sendLen)
{
    OTP_UINT8 i =0;
    OTP_UINT8 temp[3];
    
    temp[0] = i2cAddr & I2C_WRITE;
    if(2 == addr_len)
    {    
        temp[1] = addr >> 8;
    }
    else
    {
        temp[1] = addr;
    }    
    temp[2] = addr;
    
    i2c_stop(id);
    i2c_start(id);
    
    for(i = 0; i < addr_len + 1; i++)
    {
        if( OK != i2c_write_byte(id, temp[i]))
        {
            ASSERT(0);
            i2c_stop(id);  
            return ERROR;
        }    
    }    
    
        
    while (sendLen--)
    {   
        if( OK != i2c_write_byte(id, *pSendData++) )
        {
            ASSERT(0);
            i2c_stop(id); 
            return ERROR;
        }
    }
    
    i2c_stop(id);
    
    return(OK);

}

     當讀從器件寄存器時,I2C總線上需要先發乘客器件的從機寫地址,再寫入寄存器的地址,產生重複超始條件,發送器件從機讀地址,最後讀取寄存器的數據,根據這種應用,編寫一個向I2C從器件寫寄存器的函數i2c_recv_data,這個函數調用上面介紹的函數來實現,時序圖如下圖所示,注意這個函數實現的I2C操作時序要複雜一些,當讀取到最後一個字節後,還要額外再讀取一個字節來給從機產生非應答位來通信從機讀取完成, 不用再向總線上傳輸數據。


/*********************************************************************************************************
** Function name:      i2c_rcv_data()
** Descriptions:        從i2c總線讀取數據
** input parameters:   id--操作的I2C端口
**                      Addr--地址
**                      pSendData--緩衝區地址
**                      sendLen--數據長度
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
STATUS i2c_rcv_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,   OTP_UINT16 addr,  
                    OTP_UINT8 addr_len, OTP_UINT8 *pRcvData, OTP_UINT8 rcvLen)
{
    OTP_UINT8 buf[3];
    OTP_UINT8 i = 0;
    
    buf[0] = i2cAddr & I2C_WRITE;
    if(2 == addr_len)
    {    
        buf[1] = addr >> 8;
    }
    else
    {
        buf[1] = addr;
    } 
    buf[2] = addr;
    
     
    i2c_start(id);
    
    /*發送讀地址*/
    for(i = 0; i < addr_len + 1; i++)
    {
        if( OK != i2c_write_byte(id, buf[i]))
        {
            ASSERT(0);
            i2c_stop(id);  
            return ERROR;
        }      
    
    }    
    
    
    i2c_start(id);
    /*發送讀取命令*/
    if(OK != i2c_write_byte (id, i2cAddr | I2C_READ))
    {
        /*ASSERT(0);*/
        i2c_stop(id); 
        return ERROR;
    }

    while (rcvLen--)
    {
        *pRcvData++ = i2c_read_byte(id);
        i2c_send_ack(id);
    }
    /*多讀取一個數據,用於產生非應答信號*/
    (OTP_VOID)i2c_read_byte(id);
    i2c_send_no_ack(id);
    
    i2c_stop(id);
    
    return OK;
}

3.6  頭文件及功能配置

        到此,已經講解完了I2C軟件模擬的全部驅動程序了,下面貼出驅動程序中所使用的相關宏定義的頭文件。

#ifndef __INCLUDE_I2C_H
#define __INCLUDE_I2C_H
#include "stm32f10x.h"

/*********************************************************************************************************
*                定義軟件I2C接口數目
*********************************************************************************************************/
#define     I2C_NUM  2 

/*********************************************************************************************************
*                定義I2C驅動管腳
*********************************************************************************************************/
#define I2C0_SOFT_SCL_PORT_PERIPH  RCC_APB2Periph_GPIOA
#define I2C0_SOFT_SCL_PORT         GPIOA
#define I2C0_SOFT_SCL              GPIO_Pin_11 

#define I2C0_SOFT_SDA_PORT_PERIPH  RCC_APB2Periph_GPIOA
#define I2C0_SOFT_SDA_PORT         GPIOA
#define I2C0_SOFT_SDA              GPIO_Pin_12

#define I2C1_SOFT_SCL_PORT_PERIPH  RCC_APB2Periph_GPIOB
#define I2C1_SOFT_SCL_PORT         GPIOB
#define I2C1_SOFT_SCL              GPIO_Pin_6 

#define I2C1_SOFT_SDA_PORT_PERIPH  RCC_APB2Periph_GPIOB
#define I2C1_SOFT_SDA_PORT         GPIOB
#define I2C1_SOFT_SDA              GPIO_Pin_7



/*********************************************************************************************************
*                定義軟件I2C接口速率
*********************************************************************************************************/
#define    I2C_SPEED         50000                                   /*定義I2C速率爲100K                */
#define    I2C_DELAY_TIME    (CPU_FREQ/I2C_SPEED/20)                 /*定義I2C時鐘延時時間,半個時鐘週期 */

/*********************************************************************************************************
*                定義軟件I2C接口讀寫命令
*********************************************************************************************************/
#define    I2C_WRITE    0xFE
#define    I2C_READ     0x01



typedef struct S_I2C_IO_STRUCT
{
    GPIO_TypeDef   *sclPort;        /*SCL的port口*/
    OTP_UINT16      sclPin;         /*SCL的pin*/
    GPIO_TypeDef   *sdaPort;        /*SDA的port口*/
    OTP_UINT16      sdaPin;        /*SDA的pin*/
}S_I2C_IO;


/*********************************************************************************************************
** Function name:      i2c_init()
** Descriptions:        I2C使用的IO管腳初始化函數
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
OTP_VOID i2c_init(void);


/*********************************************************************************************************
** Function name:      i2c_rcv_data()
** Descriptions:        從i2c總線讀取數據
** input parameters:   id--操作的I2C端口
**                      Addr--地址
**                      pSendData--緩衝區地址
**                      sendLen--數據長度
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
STATUS i2c_rcv_data(OTP_UINT8 id,       OTP_UINT8 i2cAddr,    OTP_UINT16 addr,  
                    OTP_UINT8 addr_len, OTP_UINT8 *pRcvData, OTP_UINT8 rcvLen);

/*********************************************************************************************************
** Function name:      i2c_send_data()
** Descriptions:        向i2c總線發送數據
** input parameters:   id--操作的I2C端口
**                      i2cAddr--器件地址
**                      addr--器件內部地址
**                      addr_len--內部地址長度
**                      pSendData--緩衝區地址
**                      sendLen--數據長度
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
STATUS i2c_send_data(OTP_UINT8 id, OTP_UINT8 i2cAddr, OTP_UINT16 addr,  
                     OTP_UINT8 addr_len, OTP_UINT8 *pSendData, OTP_UINT8 sendLen);

          上面的頭文件還有一些宏定義是引用軟件模擬I2C驅動程序之外的頭文件,在這裏做一個說明。

/*定義CPU的工作頻率, STM32主頻工作頻率設置爲72MHz*/
#define    CPU_FREQ                  72000000


/*數據結構重定義*/

typedef char            OTP_CHAR;   /* 不保證是否有符號*/
typedef void            OTP_VOID;                                       
typedef signed char     OTP_INT8;   /* 有符號8位 */
typedef unsigned char   OTP_UINT8;  /* 無符號8位 */
typedef signed short    OTP_INT16;  /* 有符號16位 */
typedef unsigned short  OTP_UINT16; /* 無符號16位 */
typedef signed int      OTP_INT32;  /* 有符號32位 */
typedef unsigned int    OTP_UINT32; /* 無符號32位 */

/*與定義成了long的移植代碼對接,顯式說明長度爲32位*/
typedef signed long     OTP_LONG32; /* 有符號32位 */
typedef unsigned long   OTP_ULONG32;    /* 無符號32位 */
typedef signed long long    OTP_INT64;  /* 有符號64位 */
typedef unsigned long long OTP_UINT64;  /* 無符號64位 */

/* 8位寄存器類型 */
typedef volatile OTP_UINT8 OTP_REG8;

/* 指令 */
typedef unsigned int    OTP_INSTRUCT;


/* 局部變量或局部函數修飾符 */
#ifndef LOCAL
#define LOCAL           static
#endif

#ifndef IMPORT
#define IMPORT          extern
#endif


/* OK ERROR 與 STATUS 一起使用*/
#ifndef STATUS
#define STATUS int 
#endif
#ifndef OK
#define OK              0
#endif
#ifndef ERROR
#define ERROR           (-1)
#endif

4、使用軟件模擬I2C驅動程序讀寫EEPROM

         上面我們已經自己編寫了一個軟件模擬I2C驅動程序,下面就來實現一個EEPROM  AT24C64的讀寫函數。



/*AT24Cxx 使用第幾個I2C id*/
#define AT24CXX_I2C_ID		0
#define AT24C08_I2C_ADDR	0xA0
#define AT24C64_I2C_ADDR	0xA0


/*定義頁大小*/
#define AT24C08_PAGE_SIZE   16
#define AT24C64_PAGE_SIZE   32

/*********************************************************************************************************
** Function name:      at24cxx_delay()
** Descriptions:        AT24Cxx操作延時函數
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
LOCAL OTP_VOID at24cxx_delay(OTP_UINT8 time)
{
    OTP_UINT16 i = 0;
    OTP_UINT16 j = 0;

    for( i = 0; i < time; i++ )
    {
        /*主頻33M,延時1ms要執行4800次循環*/
        for(j = 0; j < 4800; j++)
        {
        }
    }
}


/*********************************************************************************************************
** Function name:      at24c64_read()
** Descriptions:        AT24C64讀操作函數
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
STATUS  at24c64_read(OTP_UINT16 addr, OTP_UINT8 *pBuf, OTP_UINT16 len)
{
    OTP_UINT16  pageleft = 0;   

    /*ASSERT(NULL != pBuf);*/
    if(NULL==pBuf)
    {
        ASSERT(0);
        return ERROR;
    }
   
    while(len)
    {
        /*  當前頁還有多少空間可以讀   */
        pageleft = AT24C64_PAGE_SIZE - (addr & (AT24C64_PAGE_SIZE - 1));

        /*  當前頁可以讀出多少字節的數據*/
        pageleft = (pageleft >= len) ? len : pageleft;

       
        /*讀出數據*/
        if( OK != i2c_rcv_data(AT24CXX_I2C_ID,
                               AT24C64_I2C_ADDR, addr, sizeof(addr),
                               pBuf, pageleft)) 

        
        {
            ASSERT(0);
            return ERROR;
        }

        len = len - pageleft;
        addr = addr + pageleft;
        pBuf = pBuf + pageleft;
        
     }

     return OK;
     
}

/*********************************************************************************************************
** Function name:      at24c64_write()
** Descriptions:        AT24C64讀操作函數
** input parameters:   無
** output parameters:  無
** Returned value:      無
*********************************************************************************************************/
STATUS at24c64_write(OTP_UINT16 addr, OTP_UINT8 *pBuf, OTP_UINT16 len)
{
    OTP_UINT16   pageleft = 0;  


    if(NULL == pBuf)
    {
        return ERROR;
    }
    
    
    while(len)
    {
        /*  當前頁還有多少空間可以讀   */
        pageleft = AT24C64_PAGE_SIZE - (addr & (AT24C64_PAGE_SIZE - 1));

        /*  當前頁可以讀出多少字節的數據*/
        pageleft = (pageleft >= len) ? len : pageleft;


        /*寫入數據*/
        if(OK != i2c_send_data(AT24CXX_I2C_ID, AT24C64_I2C_ADDR, addr, sizeof(addr), pBuf, pageleft))
        {
            return ERROR;
        }



        len = len - pageleft;
        addr = addr + pageleft;
        pBuf = pBuf + pageleft;
        /*寫完一頁數據延時10ms,等待其寫入*/
        at24cxx_delay(10);
     }

     
     return OK;

}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章