59/70 I2C通訊詳解

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

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

I2C 主要用于電壓螟蒸、溫度監(jiān)控砚蓬,EEPROM數據的讀寫铃剔,光模塊的管理等懂拾。該總線只有兩根線捅儒,SCL 和 SDA液样,SCL 即 Serial Clock振亮,串行參考時鐘,SDA 即 Serial Data鞭莽,串行數據坊秸。

原理

IO口

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

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

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

對于MCU的開發(fā)者來講创淡,簡單的這樣理解就可以了痴晦。如果管腳設置成推挽輸出模式,輸出高時琳彩,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 總線了,否則會發(fā)生沖突复哆。直到檢測到一個 STOP 信號為止欣喧。STOP 的信號是在 SCL 口線為高時,SDA 產生一個上升沿梯找。STOP 信號之后唆阿,I2C總線恢復到初始狀態(tài)。

這里要分兩種情況锈锤,如果MCU的口線支持開漏輸出模式驯鳖,則可以直接把 SDA 和 SCL 設置成開漏輸出。例如 Silicon 的 C8051 系列 MCU牙咏,它的口線就支持開漏和推挽輸出臼隔。如果 MCU 不支持開漏輸出,例如 MSP430妄壶。當然如果軟件做的合理摔握,是可以避免這樣的事情的,但總線上的很多器件都不是由你能控制的丁寄,如何設計出更合理的軟件來避免這樣的問題發(fā)生呢氨淌?對于不支持開漏輸出MCU泊愧,我們最合理的做法是,當設置口線電平為高時盛正,我們把口線設置成輸入狀態(tài)删咱,然后利用口線上的上拉電阻來把口線拉高。這樣即使是兩邊電平不一致時豪筝,也不會造成IO口的損壞痰滋。

速度

常見的I2C總線依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s)续崖,但時鐘頻率可被允許下降至零敲街,這代表可以暫停通信。而新一代的I2C總線可以和更多的節(jié)點(支持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,可發(fā)送和接收數據览爵。I2C 總線在傳送數據過程中共有三種類型信號置鼻, 它們分別是:

  • 開始信號: SCL 為高電平時, SDA 由高電平向低電平跳變蜓竹,開始傳送數據箕母。
  • 結束信號: SCL 為高電平時, SDA 由低電平向高電平跳變俱济,結束傳送數據嘶是。
  • 應答信號:接收數據的 IC 在接收到 8bit 數據后,向發(fā)送數據的 IC 發(fā)出特定的低電平脈沖蛛碌,表示已收到數據聂喇。 CPU 向受控單元發(fā)出一個信號后,等待受控單元發(fā)出一個應答信號, CPU 接收到應答信號后希太,根據實際情況作出是否繼續(xù)傳遞信號的判斷克饶。若未收到應答信號,由判斷為受控單元出現故障誊辉。

具體時序如下圖所示:

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總線按字節(jié)傳輸棺滞,即每次傳輸8bits二進制數據裁蚁,傳輸完畢后等待接收端的應答信號ACK,收到應答信號后再傳輸下一字節(jié)继准。等不到ACK信號后枉证,傳輸終止∫票兀空閑情況下室谚,SCL和SDA都處于高電平狀態(tài)。

  • 判斷一次傳輸的開始:如上圖所示崔泵,I2C總線傳輸開始的標志是:SCL信號處于高電平期間秒赤,SDA信號出現一個由高電平向低電平的跳變。
  • 判斷一次傳輸的結束:如上圖所示憎瘸,I2C總線傳輸結束的標志是:SCL信號處于高電平期間入篮,SDA信號出現一個由低電平向高電平的跳變。跟開始標識正好相反幌甘。
  • 有效數據:在SCL處于高電平期間潮售,SDA保持狀態(tài)穩(wěn)定的數據才是有效數據,只有在SCL處于低電平狀態(tài)時锅风,SDA才允許狀態(tài)切換酥诽。前面已經講過了,SCL高電平期間皱埠,SDA狀態(tài)發(fā)生改變肮帐,是傳輸開始/.結束的標志。

讀寫

如上圖所示漱逸,I2C開始傳輸時泪姨,第一個字節(jié)的前7bit是地址信息(7位地址器件)游沿,第8bit是操作標識,為“0”時表示寫操作肮砾,為“1”時表示讀操作诀黍,第9個時鐘周期是應答信號ACK,低有效仗处,高電平表示無應答眯勾,傳輸終止。在上圖中還可以看出婆誓,正常情況下吃环,寫操作是I2C主設備方發(fā)起終止操作的,而讀操作時洋幻,I2C主控制器在接收完最后一個數據后郁轻,不對從設備進行應答,傳輸終止文留。

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

總線信號時序

再次總結下總線的各種時序狀態(tài):

  • 總線空閑狀態(tài):SDA 和 SCL 兩條信號線都處于高電平森书,即總線上所有的器件都釋放總線靶端,兩條信號線各自的上拉電阻把電平拉高;
  • 啟動信號 START:信號 SCL 保持高電平凛膏,數據信號SDA的電平被拉低(即負跳變)杨名。啟動信號必須是跳變信號,而且在建立該信號前必修保證總線處于空閑狀態(tài)译柏;
  • 停止信號 STOP:時鐘信號SCL保持高電平镣煮,數據線被釋放,使得SDA返回高電平(即正跳變)鄙麦,停止信號也必須是跳變信號典唇。
  • 數據傳送:SCL 線呈現高電平期間,SDA 線上的電平必須保持穩(wěn)定胯府,低電平表示 0 (此時的線電壓為地電壓)介衔,高電平表示1(此時的電壓由元器件的VDD決定)。只有在 SCL 線為低電平期間骂因,SDA 上的電平允許變化炎咖。
  • 應答信號 ACK:I2C 總線的數據都是以字節(jié)( 8 位)的方式傳送的,發(fā)送器件每發(fā)送一個字節(jié)之后,在時鐘的第9個脈沖期間釋放數據總線乘盼,由接收器發(fā)送一個ACK(把數據總線的電平拉低)來表示數據成功接收升熊。
  • 無應答信號 NACK:在時鐘的第 9 個脈沖期間發(fā)送器釋放數據總線,接收器不拉低數據總線表示一個 NACK绸栅,NACK 有兩種用途:
    a. 一般表示接收器未成功接收數據字節(jié)级野;
    b. 當接收器是主控器時,它收到最后一個字節(jié)后粹胯,應發(fā)送一個 NACK 信號蓖柔,以通知被控發(fā)送器結束數據發(fā)送,并釋放總線风纠,以便主控接收器發(fā)送一個停止信號 STOP况鸣。

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

模擬 I2C

目前大部分 MCU 都帶有 I2C 總線接口,但是芯片自帶的 I2C 也有兩個問題栈幸,一個是移植性較差不夠通用愤估,另外部分 MCU 不帶 I2C 還是得要模擬的方式,以及一些芯片設計的 I2C 據說是存在問題的速址,如: stm32 的 I2C 不夠穩(wěn)定,efm32 的 I2C 不夠節(jié)能等等由驹。這邊建議芍锚,在權衡 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;  
} 

//主機發(fā)送給從機應答信號
//產生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();
}

//主機發(fā)送給從機應答信號
//不產生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發(fā)送一個字節(jié)
//返回從機有無應答
//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個字節(jié)勉躺,ack=1時癌瘾,發(fā)送ACK,ack=0饵溅,發(fā)送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閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異逝变,居然都是意外死亡基茵,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門壳影,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拱层,“玉大人,你說我怎么就攤上這事宴咧「疲” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵掺栅,是天一觀的道長烙肺。 經常有香客問我,道長氧卧,這世上最難降的妖魔是什么桃笙? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮沙绝,結果婚禮上搏明,老公的妹妹穿的比我還像新娘。我一直安慰自己闪檬,他們只是感情好星著,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粗悯,像睡著了一般虚循。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上样傍,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天横缔,我揣著相機與錄音,去河邊找鬼铭乾。 笑死剪廉,一個胖子當著我的面吹牛,可吹牛的內容都是我干的炕檩。 我是一名探鬼主播斗蒋,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼捌斧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泉沾?” 一聲冷哼從身側響起捞蚂,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎跷究,沒想到半個月后姓迅,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡俊马,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年丁存,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柴我。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡解寝,死狀恐怖,靈堂內的尸體忽然破棺而出艘儒,到底是詐尸還是另有隱情聋伦,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布界睁,位于F島的核電站觉增,受9級特大地震影響,放射性物質發(fā)生泄漏翻斟。R本人自食惡果不足惜逾礁,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杨赤。 院中可真熱鬧敞斋,春花似錦、人聲如沸疾牲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阳柔。三九已至,卻和暖如春蚓峦,著一層夾襖步出監(jiān)牢的瞬間舌剂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工暑椰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留霍转,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓一汽,卻偏偏與公主長得像避消,于是被迫代替她去往敵國和親低滩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內容