詳解STM32之EEPROM
一、I2C接口讀寫EEPROM(AT24C02)
?????? ——主模式,分別用作主發(fā)送器和主接收器。通過查詢事件的方式來確保正常通信。
1、I 2C接口初始化
? ? ? ? 與其他對(duì)GPIO 復(fù)用的外設(shè)一樣,它先調(diào)用了用戶函數(shù)I2C_GPIO_Confi g() 配置好 I 2 C 所用的 I/O端口,然后再調(diào)用用戶函數(shù) I2C_Mode_Confi gu() 設(shè)置 I 2 C 的工作模式,并使能相關(guān)外設(shè)的時(shí)鐘。
void?I2C_EE_Init(void) { ??I2C_GPIO_Config(); ??I2C_Mode_Config(); ??/*?根據(jù)頭文件?i2c_ee.??14?h?中的定義來選擇?EEPROM?要寫入的地址?*/ #ifdef?EEPROM_Block0_ADDRESS?/*?選擇?EEPROM?Block0?來寫入?*/ ??EEPROM_ADDRESS?=?EEPROM_Block0_ADDRESS; #endif #ifdef?EEPROM_Block1_ADDRESS??/*?選擇?EEPROM?Block1?來寫入?*/ ??EEPROM_ADDRESS?=?EEPROM_Block1_ADDRESS; #endif #ifdef?EEPROM_Block2_ADDRESS??/*?選擇?EEPROM?Block2?來寫入?*/ ??EEPROM_ADDRESS?=?EEPROM_Block2_ADDRESS; #endif #ifdef?EEPROM_Block3_ADDRESS??/*?選擇?EEPROM?Block3?來寫入?*/ ??EEPROM_ADDRESS?=?EEPROM_Block3_ADDRESS; #endif }
(1)EEPROM地址
? ? ? ? AT24C02:256字節(jié),高四位硬性規(guī)定,最低位是R/W(傳輸方向選擇位),在制作硬件時(shí),我們可以根據(jù)需要改變的是地址位中的 A2、A1、A0 位。原理圖上面全接地,所以它的地址為 :0xA0 或 0xA1。
2、GPIO端口初始化
static?void?I2C_GPIO_Config(void) { ??GPIO_InitTypeDef?GPIO_InitStructure; ??/*?使能與?I2C1?有關(guān)的時(shí)鐘?*/ ??RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,?ENABLE); ??RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,?ENABLE); ??/*??配置SCL?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); }
3、I2C模式初始化
typedef?struct { ??uint32_t?I2C_ClockSpeed; ??uint16_t?I2C_Mode; ??uint16_t?I2C_DutyCycle; ??uint16_t?I2C_OwnAddress1; ??uint16_t?I2C_Ack; ??uint16_t?I2C_AcknowledgedAddress; }?I2C_InitTypeDef;
(1)I2C_Mode:本成員是選擇 I 2 C 的使用方式,有 I 2 C 模式(I2C_Mode_I2C)和SMBus 模式。(I2C_Mode_SMBusDevice、I2C_Mode_SMBusHost)
(2)I2C_DutyCycle:設(shè)置的是 I 2 C 的 SCL 線時(shí)鐘的占空比。在 STM32 的 I 2 C 占空比配置中有兩個(gè)選擇,分別為高電平時(shí)間和低電平時(shí)間之比為16 :9 (I2C_DutyCycle_16_9)和 2 :1( I2C_DutyCycle_2)。
(3)I2C_OwnAddress1:本 成 員 配 置 的 是 STM32 的 I 2 C 設(shè) 備 自 己 的 地 址, 每個(gè) 連 接 到 I 2 C 總線上的設(shè)備都要有一個(gè)自己的地址,作為主機(jī)也不例外。這個(gè)地址可以被配置為 7 位和 10 位地址。我們把這個(gè)地址設(shè)置為 0x0A (自定義宏I2C1_OWN_ADDRESS7 的值)。
(4)I2C_Ack_Enable:本成員關(guān)于 I 2 C 應(yīng)答設(shè)置,設(shè)置為使能則每接收到一個(gè)字節(jié)就返回一個(gè)應(yīng)答信號(hào)。配置為允許應(yīng)答(I2C_Ack_Enable),這是絕大多數(shù)遵循 I 2 C標(biāo)準(zhǔn)的設(shè)備通信的要求,改為禁止應(yīng)答 (I2C_Ack_Disable)往往會(huì)導(dǎo)致通信錯(cuò)誤。
(5)I2C_AcknowledgeAddress:本成員選擇 I 2 C 的尋址模式是 7 位還是 10 位地址。這需要根據(jù)實(shí)際連接到 I 2C 總線上設(shè)備的地址進(jìn)行選擇。與 EEPROM 進(jìn)行通信,使用的為 7 位尋址模式(I2C_AcknowledgedAddress_7bit)。
(6)I2C_ClockSpeed:本成員設(shè)置的是 I 2 C 的傳輸速率,在調(diào)用初始化函數(shù)時(shí),函數(shù)會(huì)根據(jù)我們輸入的數(shù)值經(jīng)過運(yùn)算后把分頻值寫入到 I 2 C 的時(shí)鐘控制寄存器。而我們寫入的這個(gè)參數(shù)值不得高于 400 kHz。——400000
對(duì)結(jié)構(gòu)體成員賦值完成后,我們調(diào)用庫函數(shù) I2C_Init() 根據(jù)我們的配置對(duì) I 2 C 進(jìn)行初始化, 并調(diào)用庫函數(shù) I2C_Cmd() 使能I 2 C 外設(shè)。?
static?void?I2C_Mode_Configu(void) { ??I2C_InitTypeDef?I2C_InitStructure; ??I2C_InitStructure.I2C_Mode?=?I2C_Mode_I2C;??/*?I2C?配置?*/ ??I2C_InitStructure.I2C_DutyCycle?=?I2C_DutyCycle_2;??/*?高電平數(shù)據(jù)穩(wěn)定,低電平數(shù)據(jù)變化?SCL??時(shí)鐘線的占空比?*/ ??I2C_InitStructure.I2C_OwnAddress1?=?I2C1_OWN_ADDRESS7; ??I2C_InitStructure.I2C_Ack?=?I2C_Ack_Enable?; ??I2C_InitStructure.I2C_AcknowledgedAddress?=?I2C_AcknowledgedAddress_7bit;??/*?I2C?的尋址模式?*/ ??I2C_InitStructure.I2C_ClockSpeed?=?I2C_Speed;??/*?通信速率?*/ ??I2C_Init(I2C1,?&I2C_InitStructure);??/*?I2C1?初始化?*/ ??I2C_Cmd(I2C1,?ENABLE);??/*?使能?I2C1?*/ }
二、對(duì)EEPROM的讀寫操作
void?I2C_Test(void) { ??u16?i; ??printf("寫入的數(shù)據(jù)nr"); ??for?(?i?=?0;?i?<=?255;?i++?)?//填充緩沖 ??{ ????I2c_Buf_Write[i]?=?i; ????printf("0x%02X?",?I2c_Buf_Write[i]); ????if?(i?%?16?==?15) ????{ ??????printf("nr"); ????} ??} ??I2C_EE_BufferWrite(?I2c_Buf_Write,?EEP_Firstpage,?256);??//將?I2c_Buf_Write?中順序遞增的數(shù)據(jù)寫入?EERPOM?中 ??printf("nr?寫成功nr"); ??printf("nr?讀出的數(shù)據(jù)nr"); ??I2C_EE_BufferRead(I2c_Buf_Read,?EEP_Firstpage,?256);??//將?EEPROM?讀出數(shù)據(jù)順序保持到?I2c_Buf_Read?中 //將?I2c_Buf_Read?中的數(shù)據(jù)通過串口打印 ??for?(i?=?0;?i?<?256;?i++) ??{ ????if?(I2c_Buf_Read[i]?!=?I2c_Buf_Write[i]) ????{ ??????printf("0x%02X?",?I2c_Buf_Read[i]); ??????printf("錯(cuò)誤:I2C?EEPROM?寫入與讀出的數(shù)據(jù)不一致nr"); ??????return; ????} ????printf("0x%02X?",?I2c_Buf_Read[i]); ????if?(i?%?16?==?15) ????{ ??????printf("nr"); ????} ??} ??printf("I2C(AT24C02)讀寫測(cè)試成功nr"); }
? ? ? ? 功能是把數(shù)值 0 ~ 255 按順序填入緩沖區(qū)數(shù)組,并通過串口打印到終端,接著通過用戶函數(shù)I2C_EE_BufferWrite()把緩沖區(qū)的數(shù)據(jù)寫入EEPROM。寫入成功之后,利用用戶函數(shù) I2C_EE_BufferRead() 把數(shù)據(jù)讀取出來,進(jìn)行校驗(yàn),判斷數(shù)據(jù)是否被正確寫入。?
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; ??/*?If?WriteAddr?is?I2C_PageSize?aligned?*/ ??if?(Addr?==?0) ??{ ????/*?If?NumByteToWrite?<?I2C_PageSize?*/ ????if?(NumOfPage?==?0) ????{ ??????I2C_EE_PageWrite(pBuffer,?WriteAddr,?NumOfSingle); ??????I2C_EE_WaitEepromStandbyState(); ????} ????/*?If?NumByteToWrite?>?I2C_PageSize?*/ ????else ????{ ??????while?(NumOfPage--) ??????{ ????????I2C_EE_PageWrite(pBuffer,?WriteAddr,?I2C_PageSize); ????????I2C_EE_WaitEepromStandbyState(); ????????WriteAddr?+=?I2C_PageSize; ????????pBuffer?+=?I2C_PageSize; ??????} ??????if?(NumOfSingle?!=?0) ??????{ ????????I2C_EE_PageWrite(pBuffer,?WriteAddr,?NumOfSingle); ????????I2C_EE_WaitEepromStandbyState(); ??????} ????} ??} ??/*?If?WriteAddr?is?not?I2C_PageSize?aligned?*/ ??else ??{ ????/*?If?NumByteToWrite?<?I2C_PageSize?*/ ????if?(NumOfPage?==?0) ????{ ??????I2C_EE_PageWrite(pBuffer,?WriteAddr,?NumOfSingle); ??????I2C_EE_WaitEepromStandbyState(); ????} ????/*?If?NumByteToWrite?>?I2C_PageSize?*/ ????else ????{ ??????NumByteToWrite?-=?count; ??????NumOfPage?=?NumByteToWrite?/?I2C_PageSize; ??????NumOfSingle?=?NumByteToWrite?%?I2C_PageSize; ??????if?(count?!=?0) ??????{ ????????I2C_EE_PageWrite(pBuffer,?WriteAddr,?count); ????????I2C_EE_WaitEepromStandbyState(); ????????WriteAddr?+=?count; ????????pBuffer?+=?count; ??????} ??????while?(NumOfPage--) ??????{ ????????I2C_EE_PageWrite(pBuffer,?WriteAddr,?I2C_PageSize); ????????I2C_EE_WaitEepromStandbyState(); ????????WriteAddr?+=?I2C_PageSize; ????????pBuffer?+=?I2C_PageSize; ??????} ??????if?(NumOfSingle?!=?0) ??????{ ????????I2C_EE_PageWrite(pBuffer,?WriteAddr,?NumOfSingle); ????????I2C_EE_WaitEepromStandbyState(); ??????} ????} ??} }
? ? ? ? AT24C02 的 EEPROM 分為 32 頁,每頁可存儲(chǔ)8個(gè)字節(jié)的數(shù)據(jù),若在同一頁寫入超過 8 字節(jié),則超過的部分會(huì)被寫在該頁的起始地址,這樣部分?jǐn)?shù)據(jù)會(huì)被覆蓋。為了把連續(xù)的緩沖區(qū)數(shù)組按頁寫入 EEPROM,就需要對(duì)緩沖區(qū)進(jìn)入分頁處理。I2C_EE_BufferWrite() 函數(shù)根據(jù)我們輸入的緩沖區(qū)大小參數(shù) NumByteToWrite,計(jì)算出我們需要寫入多少頁,并計(jì)算寫入位置。分頁處理好之后,調(diào)用 I2C_EE_PageWrite() 函數(shù),這個(gè)函數(shù)是與 EEPROM 進(jìn)行 I 2 C通信的最底層函數(shù),它與 STM32 的 I 2 C 庫函數(shù)使用密切相關(guān)。
void?I2C_EE_PageWrite(u8*?pBuffer,?u8?WriteAddr,?u8?NumByteToWrite) { ??while?(I2C_GetFlagStatus(I2C1,?I2C_FLAG_BUSY)); ??I2C_GenerateSTART(I2C1,?ENABLE);??/*?Send?START?condition?*/ ??while?(!I2C_CheckEvent(I2C1,?I2C_EVENT_MASTER_MODE_SELECT));??/*?Test?on?EV5?and?clear?it?*/ ??I2C_Send7bitAddress(I2C1,?EEPROM_ADDRESS,?I2C_Direction_Transmitter);??/*?Send?EEPROM?address?for?write?*/ ??while?(!I2C_CheckEvent(I2C1,??I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));??/*?Test?on?EV6?and?clear?it?*/ ??I2C_SendData(I2C1,?WriteAddr);??/*?Send?the?EEPROM's?internal?address?to?write?to?*/ ??while?(!?I2C_CheckEvent(I2C1,?I2C_EVENT_MASTER_BYTE_TRANSMITTED));??/*?Test?on?EV8?and?clear?it?*/ ??while?(NumByteToWrite--)??/*?While?there?is?data?to?be?written?*/ ??{ ????I2C_SendData(I2C1,?*pBuffer);????/*?Send?the?current?byte?*/ ????pBuffer++;????/*?Point?to?the?next?byte?to?be?written?*/ ????while?(!I2C_CheckEvent(I2C1,?I2C_EVENT_MASTER_BYTE_TRANSMITTED)?);????/*?Test?on?EV8?and?clear?it?*/ ??} ??I2C_GenerateSTOP(I2C1,?ENABLE);??/*?Send?STOP?condition?*/ }
1、EEPROM頁寫入時(shí)序
這個(gè)頁寫入的函數(shù)是根據(jù) EEPROM 的頁寫入時(shí)序來編寫的。
調(diào)用庫函數(shù)I2C_Generate START() 產(chǎn)生 I 2 C 的通信起始信號(hào) S。
調(diào)用庫函數(shù)I2C_Send7bitAddress() 把前面條件編譯中賦值的變量EEPROM_ADDRESS 地 址 通 過 I 2 C1接口發(fā)送出去,數(shù)據(jù)傳輸方向?yàn)镾TM32的I2 C發(fā)送數(shù)據(jù)(I2C_Direction_Transmitter)。
調(diào) 用 庫 函 數(shù)I2C_SendData() , 請(qǐng) 注 意 這 個(gè) 庫 函 數(shù) 的 輸 入 參 數(shù) 為WriteAddr,根據(jù) EEPROM 的頁寫入時(shí)序,發(fā)送完 I 2 C 的地址后的第一個(gè)數(shù)據(jù)并不就是要寫入 EEPROM 的數(shù)據(jù), EEPROM 對(duì)這個(gè)數(shù)據(jù)解釋為將要對(duì)存儲(chǔ)矩陣寫入的地址,這個(gè)參數(shù) WriteAddr 是在我們調(diào)用 I2C_EE_PageWrite() 函數(shù)時(shí)作為參數(shù)輸入的。這個(gè)庫函數(shù)實(shí)際上是把數(shù)據(jù)傳輸?shù)綌?shù)據(jù)寄存器,再由 I 2 C 模塊根據(jù) I 2 C 協(xié)議發(fā)送出
去。
調(diào)用I2C_SendData() 函數(shù),向 EEPROM 發(fā)送要寫入的數(shù)據(jù),根據(jù)EEPROM 的頁寫入時(shí)序,這些數(shù)據(jù)將會(huì)被寫入到前面發(fā)送的頁地址中,若連續(xù)寫入超過一頁的最大字節(jié)數(shù)(8個(gè)),則多出來的數(shù)據(jù)會(huì)重新從該頁的起始地址連續(xù)寫入,覆蓋前面的數(shù)據(jù)。
調(diào)用庫函數(shù)I2C_Generate STOP() 產(chǎn)生 I 2 C 傳輸結(jié)束信號(hào),完成一次 I2 C 通信。
?
2、I2C事件檢測(cè)
? ? ? ? 在 I 2 C的通信過程中,會(huì)產(chǎn)生一系列的事件,出現(xiàn)事件后在相應(yīng)的寄存器中會(huì)產(chǎn)生標(biāo)志位。
? ? ? ? ?若發(fā)出了起始信號(hào),會(huì)產(chǎn)生事件 5(EV5),即 STM32 的 I 2 C成為主機(jī)模式;繼續(xù)發(fā)送完 I 2C 設(shè)備尋址并得到應(yīng)答后,會(huì)產(chǎn)生 EV6,即 STM32 的 I 2C 成為數(shù)據(jù)發(fā)送端;之后發(fā)送數(shù)據(jù)完成會(huì)產(chǎn)生 EV8 等。我們?cè)谧龀?I 2 C 通信操作時(shí),可以通過循環(huán)調(diào)用庫函數(shù)I2C_CheckEvent()進(jìn)行事件查詢,以確保上一操作完成后才進(jìn)行下一操作。
?
3、等到EEPROM內(nèi)部寫入完成
void?I2C_EE_WaitEepromStandbyState(void) { ??vu16?SR1_Tmp?=?0; ??do ??{ ????I2C_GenerateSTART(I2C1,?ENABLE);????/*?Send?START?condition?*/ ????SR1_Tmp?=?I2C_ReadRegister(I2C1,?I2C_Register_SR1);????/*?Read?I2C1?SR1?register?*/ ????I2C_Send7bitAddress(I2C1,?EEPROM_ADDRESS,??I2C_Direction_Transmitter);????/*?Send?EEPROM?address?for?write?*/ ??} ??while?(!(I2C_ReadRegister(I2C1,?I2C_Register_SR1)?&?0x0002)); ??I2C_ClearFlag(I2C1,?I2C_FLAG_AF);??/*?Clear?AF?flag?*/ ??I2C_GenerateSTOP(I2C1,?ENABLE);??/*?STOP?condition?*/ }
? ? ? ? 利用了 EEPROM 在接收完數(shù)據(jù)后,啟動(dòng)內(nèi)部周期寫入數(shù)據(jù)的時(shí)間內(nèi)不會(huì)對(duì)主機(jī)的請(qǐng)求做出應(yīng)答的特性。所以利用這個(gè)函數(shù)循環(huán)發(fā)送起始信號(hào),若檢測(cè)到 EEPROM 的應(yīng)答,則說明 EEPROM 已經(jīng)完成上一步的數(shù)據(jù)寫入,進(jìn)入 Standby 狀態(tài),可以進(jìn)行下一步的操作了。
?
?三、EEPROM讀
void?I2C_EE_BufferRead(u8*?pBuffer,?u8?ReadAddr,?u16?NumByteToRead) { //*((u8?*)0x4001080c)?|=0x80; ??while?(I2C_GetFlagStatus(I2C1,?I2C_FLAG_BUSY));?//?Added?by?Najoua ??/*?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); }
四、使用 I2 C讀寫EEPROM流程總結(jié)
(1)配置 I/O 端口,確定并配置 I 2 C 的模式,使能 GPIO 和 I 2 C 時(shí)鐘。
(2)寫 :
① 檢測(cè) SDA 是否空閑。
② 按 I 2 C 協(xié)議發(fā)出起始信號(hào)。
③ 發(fā)出 7 位器件地址和寫模式。
④ 要寫入的存儲(chǔ)區(qū)首地址。
⑤ 用頁寫入方式或字節(jié)寫入方式寫入數(shù)據(jù)。
⑥ 發(fā)送 I 2 C 通信結(jié)束信號(hào)。
每個(gè)操作之后要檢測(cè)“事件”是否成功。寫完后檢測(cè) EEPROM 是否進(jìn)入Standby狀態(tài)。
(3)讀 :
① 檢測(cè) SDA 是否空閑。
② 按 I 2 C 協(xié)議發(fā)出起始信號(hào)。
③ 發(fā)出 7 位器件地址和寫模式(偽寫)。
④ 發(fā)出要讀取的存儲(chǔ)區(qū)首地址。
⑤ 重發(fā)起始信號(hào)。
⑥ 發(fā)出 7 位器件地址和讀模式。
⑦ 接收數(shù)據(jù)。
類似寫操作,每個(gè)操作之后要檢測(cè)“事件”是否成功。
?