國(guó)科安芯推出的AS32X601系列MCU芯片內(nèi)置的I2C模塊提供了符合工業(yè)標(biāo)準(zhǔn)的兩線串行制接口,可用于MCU和外部I2C設(shè)備的通訊。I2C總線使用兩條串行線:串行數(shù)據(jù)線SDA和串行時(shí)鐘線SCL。 I2C接口模塊實(shí)現(xiàn)了I2C協(xié)議的標(biāo)準(zhǔn)模式和快速模式,支持多主機(jī)I2C總線架構(gòu)。其標(biāo)準(zhǔn)模式為100K,快速模式400K。而EEPROM,作為一種支持字節(jié)級(jí)單獨(dú)擦寫、數(shù)據(jù)掉電不丟失的存儲(chǔ)器,其存儲(chǔ)容量(從幾字節(jié)到數(shù)百千字節(jié))恰好滿足了大量嵌入式應(yīng)用對(duì)中小規(guī)模非易失性數(shù)據(jù)存儲(chǔ)的需求。將EEPROM與并行地址/數(shù)據(jù)總線相連的傳統(tǒng)方式會(huì)占用大量I/O口,在引腳資源緊張的微控制器(如眾多8位、32位MCU)上顯得笨重且不經(jīng)濟(jì),因此,AS32X601系列開發(fā)板搭載了一塊24C02 eeprom。本文旨在系統(tǒng)闡述I2C EEPROM的工作原理與核心操作流程。內(nèi)容將涵蓋I2C通信的基本框架,EEPROM的器件尋址方式,以及針對(duì)字節(jié)寫入等關(guān)鍵流程
一、硬件設(shè)計(jì)

二、I2C時(shí)序
①Start開始信號(hào)、Stop停止信號(hào):
這兩個(gè)信號(hào)由主機(jī)產(chǎn)生,不屬于數(shù)據(jù)域交互:
在SCL的高電平時(shí),主機(jī)將SDA的電平由 高–>低是Start信號(hào)(下降沿);
在SCL的高電平時(shí),主機(jī)將SDA的電平由 低–>高是Stop信號(hào)(上升沿);
②7位尋址
AS32X601的I2C只支持7位尋址模式,配置過(guò)程中從機(jī)地址需要左移1位才為實(shí)際地址。
③數(shù)據(jù)方向
0寫/1讀
④應(yīng)答ACK、非應(yīng)答NACK
在SCL的一個(gè)時(shí)鐘周期內(nèi),從機(jī)在SCL的高電平時(shí),將SDA的電平由高拉低(或者繼續(xù)保持低電平狀態(tài)) 則是ACK信號(hào);
從機(jī)在SCL的高電平時(shí),如果SDA的電平一直是 高電平 則是NACK信號(hào);

三、時(shí)鐘
I2C0、I2C1時(shí)鐘來(lái)自APB0,I2C2、I2C3時(shí)鐘來(lái)自ABP1。具體配置可見I2C_CTLR寄存器。

四、I2C初始化
1.配置I2Cx需要的GPIO為復(fù)用功能。
2.通過(guò)配置I2C_INITSTRUCT初始化I2Cx,包括時(shí)鐘分頻,從機(jī)地址,ACK,高低電平時(shí)間等

3.按需求配置中斷,并配置IRQ_HANDLER;
4.調(diào)用收發(fā)接口,并處理數(shù)據(jù)
五、如何操作EEPROM
5.1按字節(jié)寫入函數(shù)
FlagStatus I2C_MEEPROMWriteByte(I2C_TypeDef* I2Cx, uint8_t addr, uint16_t reg, uint16_t data, uint32_t timeout)
{
unsigned int num;
/ 等待總線釋放 /
while (!I2C_CheckStatus(I2Cx, I2C_BUS_IDLE))
{
I2C_StartClear(I2Cx);
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
if ((timeout--) == 0)
{
return RESET;
}
delay_ms(1);
}
I2C_GenerateStart(I2Cx);
/ 等待啟動(dòng)信號(hào)完成 /
while (!I2C_CheckStatus(I2Cx, MASTER_START_READY))
{
if ((timeout--) == 0)
{
return RESET;
}
delay_ms(1);
}
I2C_Send7bitAddress(I2Cx, addr, I2C_WRITE);
I2C_StartClear(I2Cx);
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成地址并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_WADDR_ACK))
{
if ((timeout--) == RESET)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_SendData(I2Cx, (uint8_t)(reg >> 0));
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成數(shù)據(jù)并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))
{
if ((timeout--) == 0)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_SendData(I2Cx, data);
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成數(shù)據(jù)并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))
{
if ((timeout--) == 0)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return 1;
}
代碼執(zhí)行流程詳細(xì)解釋如下:
等待總線空閑:函數(shù)首先進(jìn)入一個(gè)循環(huán),反復(fù)檢查I2C總線是否處于空閑(I2C_BUS_IDLE)狀態(tài)。如果總線被占用(忙狀態(tài)),它會(huì)嘗試通過(guò)調(diào)用I2C_StartClear和I2C_GenerateStop來(lái)清除可能的異常狀態(tài)并發(fā)送停止信號(hào),試圖釋放總線。每次循環(huán)都會(huì)遞減超時(shí)計(jì)數(shù)器timeout并延遲1毫秒。如果timeout減到0,函數(shù)會(huì)返回RESET。這個(gè)步驟確保了本次傳輸開始時(shí)總線是可用的。
發(fā)起起始條件:確認(rèn)總線空閑后,函數(shù)調(diào)用I2C_GenerateStart在I2C總線上產(chǎn)生一個(gè)起始條件(Start Condition),這標(biāo)志著一次傳輸序列的開始。
等待起始條件完成:緊接著,函數(shù)進(jìn)入另一個(gè)循環(huán),等待起始條件成功發(fā)出的狀態(tài)(MASTER_START_READY)。同樣,這里也有超時(shí)檢查和1ms延遲,防止程序死鎖。超時(shí)則返回失敗。
發(fā)送從機(jī)地址(寫模式):起始條件成功后,函數(shù)調(diào)用I2C_Send7bitAddress,將參數(shù)addr(EEPROM的7位設(shè)備地址)和寫操作位(I2C_WRITE,通常值為0)組合成一個(gè)8位字節(jié)發(fā)送出去。隨后清除相關(guān)狀態(tài)和中斷標(biāo)志。
等待從機(jī)地址應(yīng)答:函數(shù)循環(huán)等待從設(shè)備(EEPROM)對(duì)收到地址的應(yīng)答信號(hào)(MSEND_WADDR_ACK)。如果EEPROM存在于總線上并識(shí)別出自己的地址,它會(huì)拉低SDA線作為應(yīng)答(ACK)。函數(shù)檢測(cè)到這個(gè)狀態(tài)才能繼續(xù)。此處有一個(gè)代碼瑕疵:超時(shí)判斷寫成了(timeout--) == RESET,雖然RESET很可能定義為0,但不如其他地方的== 0直觀統(tǒng)一。超時(shí)或失敗會(huì)發(fā)送停止條件并返回失敗。
發(fā)送EEPROM內(nèi)部存儲(chǔ)地址(存在嚴(yán)重錯(cuò)誤):地址應(yīng)答后,函數(shù)準(zhǔn)備發(fā)送要寫入的EEPROM內(nèi)部單元地址reg。這是一個(gè)關(guān)鍵錯(cuò)誤。對(duì)于16位地址的EEPROM(如reg是uint16_t),需要發(fā)送兩個(gè)字節(jié):先發(fā)送高8位,再發(fā)送低8位。但代碼中I2C_SendData(I2Cx, (uint8_t)(reg >> 0))的reg >> 0等于reg本身,所以它只發(fā)送了reg的低8位,完全遺漏了高8位。這會(huì)導(dǎo)致寫入到錯(cuò)誤的EEPROM位置。
等待內(nèi)部地址字節(jié)應(yīng)答:發(fā)送(不完整的)地址字節(jié)后,循環(huán)等待EEPROM對(duì)此數(shù)據(jù)字節(jié)的應(yīng)答(MSEND_DATA_ACK)。有超時(shí)處理。
發(fā)送要寫入的數(shù)據(jù):收到地址字節(jié)應(yīng)答后,調(diào)用I2C_SendData(I2Cx, data)發(fā)送數(shù)據(jù)。這里有一個(gè)潛在問題:參數(shù)data是uint16_t類型,但函數(shù)被命名為WriteByte,且I2C_SendData通常發(fā)送一個(gè)字節(jié)。這里發(fā)生了隱式截?cái)啵挥衐ata的低8位被發(fā)送出去。函數(shù)意圖和參數(shù)類型不匹配。
等待數(shù)據(jù)字節(jié)應(yīng)答:再次循環(huán)等待EEPROM對(duì)收到數(shù)據(jù)字節(jié)的應(yīng)答。有超時(shí)處理。
結(jié)束傳輸:數(shù)據(jù)成功發(fā)送并得到應(yīng)答后,函數(shù)調(diào)用I2C_GenerateStop產(chǎn)生停止條件(Stop Condition),結(jié)束本次I2C通信。然后清除中斷標(biāo)志。
5.2讀函數(shù)
FlagStatus I2C_MEEPROMRead(I2C_TypeDef* I2Cx, uint8_t addr, uint16_t reg, uint8_t* pData, uint32_t Size, uint32_t timeout)
{
uint32_t num = 0x00;
/ 等待總線釋放 /
while (!I2C_CheckStatus(I2Cx, I2C_BUS_IDLE))
{
I2C_StartClear(I2Cx);
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
if ((timeout--) == 0)
{
return RESET;
}
delay_ms(1);
}
I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_ACK);
I2C_GenerateStart(I2Cx);
/ 等待啟動(dòng)信號(hào)完成 /
while (!I2C_CheckStatus(I2Cx, MASTER_START_READY))
{
if ((timeout--) == 0)
{
I2C_StartClear(I2Cx);
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_Send7bitAddress(I2Cx, addr, I2C_WRITE);
I2C_StartClear(I2Cx);
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成地址并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_WADDR_ACK))
{
if ((timeout--) == 0)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_SendData(I2Cx, (uint8_t)(reg >> 8));
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成數(shù)據(jù)并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))
{
if ((timeout--) == 0)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return 0;
}
delay_ms(1);
}
I2C_SendData(I2Cx, (uint8_t)(reg >> 0));
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成數(shù)據(jù)并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_DATA_ACK))
{
if ((timeout--) == 0)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_GenerateStart(I2Cx);
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成數(shù)據(jù)并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MASTER_START_REPEAT))
{
if ((timeout--) == 0)
{
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
I2C_Send7bitAddress(I2Cx, addr, I2C_READ);
I2C_ClearITPendingBit(I2Cx);
/ 等待從機(jī)接收完成地址并發(fā)送ack /
while (!I2C_CheckStatus(I2Cx, MSEND_RADDR_ACK))
{
if ((timeout--) == 0)
{
I2C_StartClear(I2Cx);
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
for (num = 0; num < Size; num++)
{
if (num == (Size - 1))
{
/* IIC sends NACK */
I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_NACK);
}
else
{
I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_ACK);
}
I2C_StartClear(I2Cx);
I2C_ClearITPendingBit(I2Cx);
/* Wait for the slave to send the completed data, and the host will send an ack */
while (!(I2C_CheckStatus(I2Cx, MREAD_DATA_ACK) || I2C_CheckStatus(I2Cx, MREAD_DATA_NACK)))
{
if ((Timeout--) == 0)
{
I2C_StartClear(I2Cx);
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return RESET;
}
delay_ms(1);
}
*pData++ = I2C_ReceiveData(I2Cx);
}
I2C_StartClear(I2Cx);
I2C_GenerateStop(I2Cx);
I2C_ClearITPendingBit(I2Cx);
return SET;
}
代碼執(zhí)行流程詳細(xì)解釋如下:
函數(shù)參數(shù)說(shuō)明:
I2Cx: I2C外設(shè)指針
addr: EEPROM設(shè)備地址(7位)
reg: EEPROM內(nèi)部起始地址(16位)
pData: 指向接收數(shù)據(jù)緩沖區(qū)的指針
Size: 要讀取的字節(jié)數(shù)
timeout: 超時(shí)計(jì)數(shù)值(注意:函數(shù)內(nèi)部有一處拼寫錯(cuò)誤寫成了Timeout)
代碼執(zhí)行流程詳細(xì)解釋:
等待總線空閑 :函數(shù)首先檢查I2C總線是否空閑(I2C_BUS_IDLE)。如果總線忙,執(zhí)行清理操作(I2C_StartClear)并發(fā)送停止信號(hào)(I2C_GenerateStop),嘗試釋放總線。每次循環(huán)都遞減超時(shí)計(jì)數(shù)器并延遲1ms,超時(shí)則返回RESET。
配置應(yīng)答 :調(diào)用I2C_AcknowledgeConfig(I2Cx, I2C_IICAA_ACK)使能主設(shè)備的數(shù)據(jù)應(yīng)答功能,這是為后續(xù)接收數(shù)據(jù)做準(zhǔn)備。
發(fā)起起始條件 :生成起始條件(I2C_GenerateStart)開始傳輸,并等待起始條件成功(MASTER_START_READY)。超時(shí)則清理總線并返回失敗。
發(fā)送設(shè)備地址(寫模式) :發(fā)送EEPROM的7位地址和寫方向位(I2C_WRITE),因?yàn)镋EPROM讀取操作需要先發(fā)送要讀取的內(nèi)部地址,這相當(dāng)于一個(gè)"偽寫"操作。清除相關(guān)狀態(tài)后,等待EEPROM應(yīng)答地址(MSEND_WADDR_ACK)。
發(fā)送重復(fù)起始條件 :為了從寫操作切換到讀操作,需要發(fā)送一個(gè)重復(fù)起始條件(Repeated Start)。調(diào)用I2C_GenerateStart,然后等待重復(fù)起始條件完成(MASTER_START_REPEAT)。這是I2C協(xié)議中在不釋放總線的情況下改變數(shù)據(jù)傳輸方向的標(biāo)準(zhǔn)做法。
發(fā)送設(shè)備地址(讀模式) :再次發(fā)送EEPROM的7位地址,但這次帶讀方向位(I2C_READ)。等待EEPROM對(duì)此讀地址的應(yīng)答(MSEND_RADDR_ACK)。
循環(huán)接收數(shù)據(jù) :這是函數(shù)的核心部分,循環(huán)接收Size個(gè)字節(jié)的數(shù)據(jù):
在接收倒數(shù)第二個(gè)字節(jié)時(shí)(num == (Size - 1)),將主設(shè)備的應(yīng)答配置為不應(yīng)答(I2C_IICAA_NACK),這是I2C協(xié)議規(guī)定的:主設(shè)備在接收最后一個(gè)字節(jié)前發(fā)送不應(yīng)答信號(hào),通知從設(shè)備停止發(fā)送。
對(duì)于其他字節(jié),使能應(yīng)答(I2C_IICAA_ACK)。
等待從設(shè)備發(fā)送數(shù)據(jù)完成的狀態(tài)(MREAD_DATA_ACK或MREAD_DATA_NACK)。這里使用了邏輯或||,表示等待任意一種接收完成狀態(tài)。
從I2C數(shù)據(jù)寄存器讀取數(shù)據(jù)(I2C_ReceiveData(I2Cx))并存儲(chǔ)到pData指向的緩沖區(qū),然后指針遞增。
結(jié)束傳輸 :所有數(shù)據(jù)接收完成后,生成停止條件(I2C_GenerateStop)結(jié)束本次I2C通信,清除相關(guān)狀態(tài)。
六、下板驗(yàn)證
我們操作I2C寫入0~0x3f數(shù)據(jù),結(jié)果如下:

操作波形如圖:

讀取完最后一個(gè)數(shù)據(jù)后發(fā)送NACK:

審核編輯 黃宇
-
mcu
+關(guān)注
關(guān)注
147文章
18914瀏覽量
397777 -
EEPROM
+關(guān)注
關(guān)注
9文章
1137瀏覽量
86014 -
I2C
+關(guān)注
關(guān)注
28文章
1556瀏覽量
131202
發(fā)布評(píng)論請(qǐng)先 登錄
CW32單片機(jī)I2C接口來(lái)讀寫EEPROM芯片
基于CW32 MCU的I2C接口優(yōu)化穩(wěn)定讀寫EEPROM關(guān)鍵技術(shù)
深入剖析I2C協(xié)議
【沁恒CH585開發(fā)板免費(fèi)試用體驗(yàn)】I2C 讀寫EEPROM (三)
【沁恒CH585開發(fā)板免費(fèi)試用體驗(yàn)】I2C 讀寫EEPROM (二)
AS32X601芯片F(xiàn)lash擦寫調(diào)試技術(shù)解析
AS32系列MCU芯片I2C模塊性能解析與調(diào)試
AS32X601驅(qū)動(dòng)系列教程 GPIO_點(diǎn)亮LED詳解
AS32X601驅(qū)動(dòng)系列教程 SMU_系統(tǒng)時(shí)鐘詳解
I2C EEPROM編程失敗的原因?
【RA-Eco-RA4M2開發(fā)板評(píng)測(cè)】I2C讀取EEPROM(二)
基于APM32F407如何制作I2C EEPROM(AT24C02型號(hào))的MDK-Keil下載算法
AS32X601的I2C模塊操作EEPROM詳解
評(píng)論