在使用單片機的過程中,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