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