59/70 I2C通訊詳解

在使用單片機的過程中,I2C 通信可以說是最被廣泛使用和采納的協議之一,采用 I2C 協議可以占用更少的資源,鏈接多臺設備,因此它和 SPI 一樣,在數字傳感器中備受偏愛。

I2C(Inter-Integrated Circuit)字面上的意思是集成電路之間,它其實是 I2C Bus 簡稱,所以中文應該叫集成電路總線,它是一種串行通信總線,使用多主從架構,由飛利浦公司在1980年代為了讓主板、嵌入式系統或手機用以連接低速周邊設備而發展。I2C 的正確讀法為“I平方C”("I-squared-C"),而“I二C”("I-two-C")則是另一種錯誤但被廣泛使用的讀法。自2006年11月1日起,使用I2C協議已經不需要支付專利費,但制造商仍然需要付費以獲取I2C從屬設備地址。

I2C 主要用于電壓、溫度監控,EEPROM數據的讀寫,光模塊的管理等。該總線只有兩根線,SCL 和 SDA,SCL 即 Serial Clock,串行參考時鐘,SDA 即 Serial Data,串行數據。

原理

IO口

注意使用其他通訊協議時,你可能不需要特別注意 IO 的模式,但如果你使用的是模擬 I2C 的話,則最好關注下 IO 口模式設置。在 I2C 總線中有 2 個口線,SDA 和 SCL。這兩個口線對為 OC 輸出。

OC就是開漏輸出(Open Collector)的簡稱,有時候也叫OD輸出(Open-Drain),OD是對mos管而言,OC是對雙極型管而言,在用法上沒啥區別。相對于OC輸出,另一種輸出叫推挽輸出(Push-Pull),一般的MCU管腳輸出可以設置這兩種模式。這里分別介紹下這兩種輸出的不同點。

  • 推挽輸出 : 可以輸出高、低電平連接數字器件,推挽結構一般是指兩個三極管分別受兩互補信號的控制,總是在一個三極管導通的時候另一個截止.
  • 開漏輸出 : 輸出端相當于三極管的集電極未接任何電平, 要得到高電平狀態需要上拉電阻才行,適合于做電流型的驅動,其吸收電流的能力相對強(一般20ma以內)。

對于MCU的開發者來講,簡單的這樣理解就可以了。如果管腳設置成推挽輸出模式,輸出高時,IO口相當于VCC, 輸出低時IO口相當于接地。如果管腳設置成開漏輸出模式,輸出高時,IO口的電平會和與其相連的口線進行與操作,如果都為高,才會被上拉拉成高電平,輸出為低時,也相當于接地。

在網上我們看到很多的例程代碼都是直接設置IO口的高低電平,這樣做其實是不太合理的。因為我們只滿足了 I2C 總線在自己這端的時序要求。而沒有考慮到連接在總線上的其他器件。如果總線上其他器件的電平和MCU輸出的電平一致,這樣做是沒問題的,如果兩邊的電平不一致時,這樣做就有一定風險造成IO的損壞。當你輸出高時,相當于IO口連接到VCC,如果對方這是恰好輸出的是低電平,那就相當于短路了。因為 I2C 總線要實現線與的功能,所以 SDA 和 SCL 口線都必須設置為開漏輸出模式,這種方式也是最安全的模式。我們使用 MCU 的硬件 I2C 接口時,口線會被自動設置成開漏。但有時候我們會使用 IO 口來模擬 I2C 總線,這個時候我們如何設置口線呢?

因為 I2C 的總線是開漏輸出的,總線上接上上拉電阻后,SCL 和 SDA 就變成了高電平,這個時候掛接在總線上的任意一個 I2C 主機口可以把 SDA 拉低,即產生了一個 START 信號,掛接在總線上的其他 I2C 主機檢測到這個信號后就不能去操作 I2C 總線了,否則會發生沖突。直到檢測到一個 STOP 信號為止。STOP 的信號是在 SCL 口線為高時,SDA 產生一個上升沿。STOP 信號之后,I2C總線恢復到初始狀態。

這里要分兩種情況,如果MCU的口線支持開漏輸出模式,則可以直接把 SDA 和 SCL 設置成開漏輸出。例如 Silicon 的 C8051 系列 MCU,它的口線就支持開漏和推挽輸出。如果 MCU 不支持開漏輸出,例如 MSP430。當然如果軟件做的合理,是可以避免這樣的事情的,但總線上的很多器件都不是由你能控制的,如何設計出更合理的軟件來避免這樣的問題發生呢?對于不支持開漏輸出MCU,我們最合理的做法是,當設置口線電平為高時,我們把口線設置成輸入狀態,然后利用口線上的上拉電阻來把口線拉高。這樣即使是兩邊電平不一致時,也不會造成IO口的損壞。

速度

常見的I2C總線依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s),但時鐘頻率可被允許下降至零,這代表可以暫停通信。而新一代的I2C總線可以和更多的節點(支持10比特長度的地址空間)以更快的速率通信:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。

連線

如上圖所示,I2C 是 OC 或 OD 輸出結構,使用時必須在芯片外部進行上拉,上拉電阻R的取值根據 I2C 總線上所掛器件數量及 I2C 總線的速率有關,一般是標準模式下 R 選擇 10kohm,快速模式下 R 選取 1kohm,I2C 總線上掛的 I2C 器件越多,就要求 I2C 的驅動能力越強,R 的取值就要越小,實際設計中,一般是先選取 4.7kohm 上拉電阻,然后在調試的時候根據實測的 I2C 波形再調整 R 的值。

I2C 只有兩根通訊線:數據線 SDA 和時鐘 SCL,可發送和接收數據。I2C 總線在傳送數據過程中共有三種類型信號, 它們分別是:

  • 開始信號: SCL 為高電平時, SDA 由高電平向低電平跳變,開始傳送數據。
  • 結束信號: SCL 為高電平時, SDA 由低電平向高電平跳變,結束傳送數據。
  • 應答信號:接收數據的 IC 在接收到 8bit 數據后,向發送數據的 IC 發出特定的低電平脈沖,表示已收到數據。 CPU 向受控單元發出一個信號后,等待受控單元發出一個應答信號, CPU 接收到應答信號后,根據實際情況作出是否繼續傳遞信號的判斷。若未收到應答信號,由判斷為受控單元出現故障。

具體時序如下圖所示:

I2C總線的主要時序參數有:開始建立時間 t(SUSTA),開始保持時間 t(HDSTA),數據建立時間 t(SUDAT),數據保持時間 t(SUDAT) ,結束建立時間 t(SUSTO) 。

  • 開始建立時間:SCL 上升至幅度的90%與SDA下降至幅度的90%之間的時間間隔;

  • 開始保持時間:SDA下降至幅度的10%與SCL下降至幅度的10%之間的時間間隔;

  • 數據建立時間:SDA上升至幅度的90%或SDA下降至幅度的10%與SCL上升至幅度的10%之間的時間間隔;

  • 數據保持時間:SCL下降至幅度的10%與SDA上升至幅度的10%或SDA下降至幅度的90%之間的時間間隔;

  • 結束建立時間:SCL上升至幅度的90%與SDA上升至幅度的90%之間的時間間隔;

  • I2C總線傳輸的特點:I2C總線按字節傳輸,即每次傳輸8bits二進制數據,傳輸完畢后等待接收端的應答信號ACK,收到應答信號后再傳輸下一字節。等不到ACK信號后,傳輸終止。空閑情況下,SCL和SDA都處于高電平狀態。

  • 判斷一次傳輸的開始:如上圖所示,I2C總線傳輸開始的標志是:SCL信號處于高電平期間,SDA信號出現一個由高電平向低電平的跳變。
  • 判斷一次傳輸的結束:如上圖所示,I2C總線傳輸結束的標志是:SCL信號處于高電平期間,SDA信號出現一個由低電平向高電平的跳變。跟開始標識正好相反。
  • 有效數據:在SCL處于高電平期間,SDA保持狀態穩定的數據才是有效數據,只有在SCL處于低電平狀態時,SDA才允許狀態切換。前面已經講過了,SCL高電平期間,SDA狀態發生改變,是傳輸開始/.結束的標志。

讀寫

如上圖所示,I2C開始傳輸時,第一個字節的前7bit是地址信息(7位地址器件),第8bit是操作標識,為“0”時表示寫操作,為“1”時表示讀操作,第9個時鐘周期是應答信號ACK,低有效,高電平表示無應答,傳輸終止。在上圖中還可以看出,正常情況下,寫操作是I2C主設備方發起終止操作的,而讀操作時,I2C主控制器在接收完最后一個數據后,不對從設備進行應答,傳輸終止。

I2C數據總線SDA是在時鐘為高時有效,在時鐘SCL為高期間,SDA如果發生了電平變化就會終止或重啟I2C中線,所以我們在數據傳輸過程中,要在SCL為低的時候去更改SDA的電平。

總線信號時序

再次總結下總線的各種時序狀態:

  • 總線空閑狀態:SDA 和 SCL 兩條信號線都處于高電平,即總線上所有的器件都釋放總線,兩條信號線各自的上拉電阻把電平拉高;
  • 啟動信號 START:信號 SCL 保持高電平,數據信號SDA的電平被拉低(即負跳變)。啟動信號必須是跳變信號,而且在建立該信號前必修保證總線處于空閑狀態;
  • 停止信號 STOP:時鐘信號SCL保持高電平,數據線被釋放,使得SDA返回高電平(即正跳變),停止信號也必須是跳變信號。
  • 數據傳送:SCL 線呈現高電平期間,SDA 線上的電平必須保持穩定,低電平表示 0 (此時的線電壓為地電壓),高電平表示1(此時的電壓由元器件的VDD決定)。只有在 SCL 線為低電平期間,SDA 上的電平允許變化。
  • 應答信號 ACK:I2C 總線的數據都是以字節( 8 位)的方式傳送的,發送器件每發送一個字節之后,在時鐘的第9個脈沖期間釋放數據總線,由接收器發送一個ACK(把數據總線的電平拉低)來表示數據成功接收。
  • 無應答信號 NACK:在時鐘的第 9 個脈沖期間發送器釋放數據總線,接收器不拉低數據總線表示一個 NACK,NACK 有兩種用途:
    a. 一般表示接收器未成功接收數據字節;
    b. 當接收器是主控器時,它收到最后一個字節后,應發送一個 NACK 信號,以通知被控發送器結束數據發送,并釋放總線,以便主控接收器發送一個停止信號 STOP。

起始信號是必需的,結束信號和應答信號,都可以不要。

模擬 I2C

目前大部分 MCU 都帶有 I2C 總線接口,但是芯片自帶的 I2C 也有兩個問題,一個是移植性較差不夠通用,另外部分 MCU 不帶 I2C 還是得要模擬的方式,以及一些芯片設計的 I2C 據說是存在問題的,如: stm32 的 I2C 不夠穩定,efm32 的 I2C 不夠節能等等。這邊建議,在權衡 I2C 占用的 CPU 資源是否可以承受后,再做選擇。

以下以 EFM32 為例,給出參考代碼,這邊的代碼只需要修改部分宏定義,就可以直接移植到 STM32 等其他單片機上。

/* iic相關引腳定義 */
#define IIC_SDA_PORT      gpioPortA  
#define IIC_SDA_PIN        0  

#define IIC_SCL_PORT      gpioPortA  
#define IIC_SCL_PIN        1

/* iic相關功能函數定義 */
//I2C_SDA PC0 --> IIC_SDA=1;
#define IIC_SDA_HIGH()      GPIO_PinOutSet(IIC_SDA_PORT, IIC_SDA_PIN)  
//I2C_SCL PC1 --> IIC_SCL=1;
#define IIC_SCL_HIGH()      GPIO_PinOutSet(IIC_SCL_PORT, IIC_SCL_PIN)
//I2C_SDA PC0 --> IIC_SDA=0;
#define IIC_SDA_LOW()      GPIO_PinOutClear(IIC_SDA_PORT, IIC_SDA_PIN)
//I2C_SCL PC1 --> IIC_SCL=0;
#define IIC_SCL_LOW()      GPIO_PinOutClear(IIC_SCL_PORT, IIC_SCL_PIN)
//設置SDA為輸入
#define IIC_SDA_DISABLE()  GPIO_PinModeSet(IIC_SDA_PORT, IIC_SDA_PIN, gpioModeDisabled, 0)
#define IIC_SCL_DISABLE()  GPIO_PinModeSet(IIC_SCL_PORT, IIC_SCL_PIN, gpioModeDisabled, 0)

#define IIC_SCL_SET_OUT()    GPIO_PinModeSet(IIC_SCL_PORT, IIC_SCL_PIN, gpioModePushPull, 0)

//設置為推挽輸出模式
//#define IIC_SDA_SET_OUT()    GPIO_PinModeSet(IIC_SDA_PORT, IIC_SDA_PIN, gpioModePushPull, 0)
//設置為推開漏出模式
#define IIC_SDA_SET_OUT()    GPIO_PinModeSet(IIC_SDA_PORT, IIC_SDA_PIN, gpioModeWiredAndDrive, 0)
#define IIC_SDA_SET_IN()    GPIO_PinModeSet(IIC_SDA_PORT, IIC_SDA_PIN, gpioModeInput, 0)
#define IIC_SDA_INPUT()      GPIO_PinInGet(IIC_SDA_PORT, IIC_SDA_PIN)

/* 初始化IIC */
void IIC_Init(void)
{
  //設置為推挽輸出模式
  IIC_SDA_SET_OUT();  
  IIC_SCL_SET_OUT();    

  //I2C_SDA PC0 --> IIC_SDA=1;
  IIC_SDA_HIGH();
  //I2C_SCL PC1 --> IIC_SCL=1;
  IIC_SCL_HIGH();
}


/* 產生IIC起始信號 */
void IIC_Start(void)
{
  u8 i;
  //IIC_SDA設置為推挽輸出
  IIC_SDA_SET_OUT();
  
  //I2C_SDA PC0 --> IIC_SDA=1;
  IIC_SDA_HIGH();
  //I2C_SCL PC1 --> IIC_SCL=1;
  IIC_SCL_HIGH();

  i = 5;
  while(i--);                               //setup time for stop 4us
  
  //I2C_SDA PC0 --> IIC_SDA=0;
  IIC_SDA_LOW();
  
  i = 5;
  while(i--);                               //setup time for stop 4us
  
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();

}
//產生IIC停止信號
void IIC_Stop(void)
{
  u8 i;
  //IIC_SDA設置為推挽輸出
  IIC_SDA_SET_OUT();
  
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();
  //I2C_SDA PC0 --> IIC_SDA=0;
  IIC_SDA_LOW();
    
  i = 5;
  while(i--);                               //setup time for stop 4us
  
  //I2C_SCL PC1 --> IIC_SCL=1;
  IIC_SCL_HIGH();
  //I2C_SDA PC0 --> IIC_SDA=1;
  IIC_SDA_HIGH();

  i = 5;
  while(i--);                               //setup time for stop 4us                 
}
//主機接收從機應答信號
//等待應答信號到來
//返回值:1,接收應答失敗
//        0,接收應答成功
u8 IIC_Wait_Ack(void)
{
  u8 ucErrTime=0;
  
  //SDA設置為輸入
  IIC_SDA_SET_IN();

  //I2C_SDA PC0 --> IIC_SDA=1;
  IIC_SDA_HIGH();
  
  __nop();
  
  //I2C_SCL PC1 --> IIC_SCL=1;
  IIC_SCL_HIGH();
  
  __nop();
  while(IIC_SDA_INPUT())
  {
    ucErrTime++;
    if(ucErrTime>250)
    {
      IIC_Stop();
      return 1;
    }
  }
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();  
  return 0;  
} 

//主機發送給從機應答信號
//產生ACK應答
void IIC_Ack(void)
{
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();  
  //IIC_SDA設置為推挽輸出
  IIC_SDA_SET_OUT();
  
  //I2C_SDA PC0 --> IIC_SDA=0;
  IIC_SDA_LOW();
  __nop();
  
  //I2C_SCL PC1 --> IIC_SCL=1;
  IIC_SCL_HIGH();
  __nop();
  
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();
}

//主機發送給從機應答信號
//不產生ACK應答        
void IIC_NAck(void)
{
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();
  //IIC_SDA設置為推挽輸出
  IIC_SDA_SET_OUT();  
  
  //I2C_SDA PC0 --> IIC_SDA=1;
  IIC_SDA_HIGH();
  __nop();
  
  //I2C_SCL PC1 --> IIC_SCL=1;
  IIC_SCL_HIGH();
  
  __nop();
  
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();
}                        
//IIC發送一個字節
//返回從機有無應答
//1,有應答
//0,無應答        
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
  
  //IIC_SDA設置為推挽輸出
  IIC_SDA_SET_OUT();  
     
  //I2C_SCL PC1 --> IIC_SCL=0;
  IIC_SCL_LOW();
  
    for(t=0;t<8;t++)
    {              
    //I2C_SDA PC0 --> IIC_SDA輸出數據
        if (((txd&0x80)>>7) & 1)
    {
      IIC_SDA_HIGH();
    }
    else 
    {
      IIC_SDA_LOW();  
    }
    
        txd<<=1;     
    __nop();   //對TEA5767這三個延時都是必須的
    
    //I2C_SCL PC1 --> IIC_SCL=1;
    IIC_SCL_HIGH();
    __nop(); 
    
    //I2C_SCL PC1 --> IIC_SCL=0;
    IIC_SCL_LOW();
    __nop();
    }
}       


//讀1個字節,ack=1時,發送ACK,ack=0,發送nACK   
u8 IIC_Read_Byte(u8 ack)
{
  u8 i, receive=0;
  
  //SDA設置為輸入
  IIC_SDA_SET_IN();

  
  
    for(i = 0; i < 8; i++)
  {
    //I2C_SCL PC1 --> IIC_SCL=0;
    IIC_SCL_LOW();
    __nop();
    //I2C_SCL PC1 --> IIC_SCL=1;
    IIC_SCL_HIGH();
        receive <<= 1;
    
        if(IIC_SDA_INPUT())
      receive++;   
      
    __nop(); 
    }
  
  __nop(); 

  ack ? IIC_Ack() : IIC_NAck();
  
    return receive;
}

自帶 I2C

這邊僅以 stm32 平臺通過 I2C 總線讀取 EEPROM 為例,說明下如何通過芯片自帶 I2C 功能來通信。

#define  EEP_Firstpage      0x00

u8 I2c_Buf_Write[256];
u8 I2c_Buf_Read[256];

static void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 

  /* 使能與 I2C1 有關的時鐘 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);  
    
  /* PB6-I2C1_SCL、PB7-I2C1_SDA*/
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;          // 開漏輸出
  GPIO_Init(GPIOB, &GPIO_InitStructure);
}

static void I2C_Mode_Configu(void)
{
  I2C_InitTypeDef  I2C_InitStructure; 

  /* I2C 配置 */
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
  I2C_InitStructure.I2C_OwnAddress1 =I2C1_OWN_ADDRESS7; 
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
  
  /* 使能 I2C1 */
  I2C_Cmd(I2C1, ENABLE);

  /* I2C1 初始化 */
  I2C_Init(I2C1, &I2C_InitStructure);   
}

void I2C_EE_Init(void)
{

  I2C_GPIO_Config(); 
 
  I2C_Mode_Configu();

  /* 選擇 EEPROM Block0 來寫入 */
  EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
}

void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

  Addr = WriteAddr % I2C_PageSize;
  count = I2C_PageSize - Addr;
  NumOfPage =  NumByteToWrite / I2C_PageSize;
  NumOfSingle = NumByteToWrite % I2C_PageSize;

  I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  I2C_EE_WaitEepromStandbyState();

}

void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // Added by Najoua 27/08/2008
    
  /* Send START condition */
  I2C_GenerateSTART(I2C1, ENABLE);
  
  /* Test on EV5 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); 
  
  /* Send EEPROM address for write */
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  
  /* Test on EV6 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));  

  /* Send the EEPROM's internal address to write to */    
  I2C_SendData(I2C1, WriteAddr);  

  /* Test on EV8 and clear it */
  while(! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

  /* While there is data to be written */
  while(NumByteToWrite--)  
  {
    /* Send the current byte */
    I2C_SendData(I2C1, *pBuffer); 

    /* Point to the next byte to be written */
    pBuffer++; 
  
    /* Test on EV8 and clear it */
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  }

  /* Send STOP condition */
  I2C_GenerateSTOP(I2C1, ENABLE);
}

void I2C_EE_WaitEepromStandbyState(void)      
{
  vu16 SR1_Tmp = 0;

  do
  {
    /* Send START condition */
    I2C_GenerateSTART(I2C1, ENABLE);
    /* Read I2C1 SR1 register */
    SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);
    /* Send EEPROM address for write */
    I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  }while(!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));
  
  /* Clear AF flag */
  I2C_ClearFlag(I2C1, I2C_FLAG_AF);
  /* STOP condition */    
  I2C_GenerateSTOP(I2C1, ENABLE); 
}

void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{  
  //*((u8 *)0x4001080c) |=0x80; 
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // Added by Najoua 27/08/2008
    
    
  /* Send START condition */
  I2C_GenerateSTART(I2C1, ENABLE);
  //*((u8 *)0x4001080c) &=~0x80;
  
  /* Test on EV5 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

  /* Send EEPROM address for write */
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);

  /* Test on EV6 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
  
  /* Clear EV6 by setting again the PE bit */
  I2C_Cmd(I2C1, ENABLE);

  /* Send the EEPROM's internal address to write to */
  I2C_SendData(I2C1, ReadAddr);  

  /* Test on EV8 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  
  /* Send STRAT condition a second time */  
  I2C_GenerateSTART(I2C1, ENABLE);
  
  /* Test on EV5 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  
  /* Send EEPROM address for read */
  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);
  
  /* Test on EV6 and clear it */
  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
  
  /* While there is data to be read */
  while(NumByteToRead)  
  {
    if(NumByteToRead == 1)
    {
      /* Disable Acknowledgement */
      I2C_AcknowledgeConfig(I2C1, DISABLE);
      
      /* Send STOP Condition */
      I2C_GenerateSTOP(I2C1, ENABLE);
    }

    /* Test on EV7 and clear it */
    if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))  
    {      
      /* Read a byte from the EEPROM */
      *pBuffer = I2C_ReceiveData(I2C1);

      /* Point to the next location where the byte read will be saved */
      pBuffer++; 
      
      /* Decrement the read bytes counter */
      NumByteToRead--;        
    }   
  }

  /* Enable Acknowledgement to be ready for another reception */
  I2C_AcknowledgeConfig(I2C1, ENABLE);
}

void I2C_Test(void)
{
  u16 i;

  printf("寫入的數據\n\r");

  for ( i=0; i<=255; i++ ) //填充緩沖
  {   
    I2c_Buf_Write[i] = i;  
  }

  //將I2c_Buf_Write中順序遞增的數據寫入EERPOM中 
  I2C_EE_BufferWrite(I2c_Buf_Write, EEP_Firstpage, 256);     

  //將EEPROM讀出數據順序保持到I2c_Buf_Read中 
  I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256); 
}

參考鏈接:
http://wenku.baidu.com/view/2a0a7f9869dc5022abea001d.html
http://blog.csdn.net/zmq5411/article/details/6085740
https://zh.wikipedia.org/wiki/I%C2%B2C
http://blog.sina.com.cn/s/blog_626998030102vfjx.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容