STM32L051测试 (四、Flash和EEPROM的读写)
目錄
- 前言
- 1、STM32L051內部存儲模塊地址范圍
- 2、讀寫函數的設計
- 2.1 讀取函數
- 2.2 EEPROM寫函數
- 寫入問題說明修改
- 2.3 Flash寫函數
- 2.4 讀寫EEPROM的后續問題
- 2.4.1 問題的出現和解決
- 2.4.2 問題的分析(大小端模式數據格式)
- <font color=#FF0033>2.4.3 STM32L071RBT6 EEPROM讀寫全字問題
- 讀問題的出現
- 寫問題的出現
- <font color=#FF0033>最后問題的解決
- 總結
前言
今天開始測試L051 flash的讀寫。
本系列博文目錄:
STM32L051測試 (一、使用CubeMX生成工程文件 — ST系列芯片通用)
STM32L051測試 (二、開始添加需要的代碼)
STM32L051測試 (三、I2C協議設備的添加測試)
1、STM32L051內部存儲模塊地址范圍
開始找到F103的FLASH圖復習了一遍,然后L051C8T6,64KB的flash, 然后我驚奇的發現還有2KB的EEPROM。
發現L051系列的地址與F103完全不同,F103的flash每頁的長度有1KB(小容量<=64KB)和2KB(大容量128KB起)查看各種資料,
查了2個小時,
還是不知道L051的flash 每頁長度是 128Byte 還是256Byte????????????????????????
還是請教了一下大佬,發現直接在J-Flash中可以找到答案,先上個F103的圖:
然后來看個L051的圖:
圖中64KB 的flash 和2KB的EEPROM都能都明顯的看出地址,flash 512頁,每頁128bytes,EEPROM只有4頁,每頁512bytes.知道了基本的地址,就可以操作起來了。
最后還需要確定的一點事,最小擦除單元是128bytes,還是256bytes,按以前的認知,刪除是按照一個Sector擦除的,也就是128bytes,但是我查看了一些網上的例子和資料,有的是說256bytes,所以后面需要自己確定一下。
其實在HAL庫的 stm32l0xx_hal_flash.h 文件中有過 FLASH_PAGE_SIZE 的定義,就是128bytes :
#define FLASH_SIZE (uint32_t)((*((uint32_t *)FLASHSIZE_BASE)&0xFFFF) * 1024U) #define FLASH_PAGE_SIZE ((uint32_t)128U) /*!< FLASH Page Size in bytes */對于flash的操作,有一些基礎知識補充一下:
Read interface organized by word, half-word or byte in every area
? Programming in the Flash memory performed by word or half-page
? Programming in the Option bytes area performed by word
? Programming in the data EEPROM performed by word, half-word or byte
? Erase operation performed by page (in Flash memory, data EEPROM and Option
bytes)
STM32L051寫Flash必須字,讀 字節、半字、字都支持。(這句話也是錯誤的,這是以前哪里看到的,實際測試寫可以根據字,半字,字節來寫)。
一些基本概念:
定義字是根據處理器的特性決定的。
首先ARM是32bit處理器,所以它的字是32bit的。半字自然就是16bit;
字節不論在哪個CPU上都是8bit。
1 Byte = 8 bits(即 1B=8b)
1 KB = 1024 Bytes
Bit意為“位”或“比特”,是計算機運算的基礎,屬于二進制的范疇;
Byte意為“字節”,是計算機文件大小的基本計算單位;
2、讀寫函數的設計
HAL庫中肯定是有對flash和EEPROM進行操作的函數,我們這里新建一個stml0_flash.c 和stml0_flash.h 函數分別放在對應位置,進行自己的函數設計。
庫中Flash與EEPROM的函數看樣子是分別放在
stm32l0xx_hal_flash.c 和 stm32l0xx_hal_flash_ex.c 中的,我們先使用EEPROM,因為提供EEPROM,就是讓用戶可以保存一些掉電后的數據的嘛。
測試完EEPROM,再去測試下flash,因為怕有時候數據不夠放……
2.1 讀取函數
//讀取指定地址的半字(16位數據) uint16_t FLASH_ReadHalfWord(uint32_t address) {return *(__IO uint16_t*)address; }//讀取指定地址的全字(32位數據) uint32_t FLASH_ReadWord(uint32_t address) {return *(__IO uint32_t*)address; }簡單測試一下:
u32 read_data1=0XFFFFFFFF; u32 read_data2=0XFFFFFFFF; ... read_data1 = FLASH_ReadWord(DATA_EEPROM_START_ADDR); printf("the DATA_EEPROM_START_ADDR is: 0x %x \r\n",read_data1); read_data2 = FLASH_ReadWord(DATA_EEPROM_START_ADDR + EEPROM_PAGE_SIZE); printf("the EEPROM sceond page test data is: 0x %x \r\n",read_data2);沒有寫入數據讀取的值應該都是0。
2.2 EEPROM寫函數
對EEPROM的寫函數:
stm32l0xx_hal_flash_ex.h中函數如下:
通過函數看來,可以直接用,但是這里有一個問題
需要測試一下,擦除是否會擦除整個扇區,有待驗證!!
答:EEPROM的擦除可以直接擦除某個地址的字,不會擦除整個片區
EEPROM的操作相對Flash,比較簡單,直接使用HAL庫中的函數即可完成
HAL_FLASHEx_DATAEEPROM_Unlock(); HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, DATA_EEPROM_START_ADDR, write_data1); HAL_FLASHEx_DATAEEPROM_Lock(); ... if(btn_getState(&K1_BUTTON_150mS) == BTN_EDGE2){printf(" K1 150ms button!,EEPROM_Erase test\r\n");HAL_FLASHEx_DATAEEPROM_Unlock();HAL_FLASHEx_DATAEEPROM_Erase(DATA_EEPROM_START_ADDR+4);HAL_FLASHEx_DATAEEPROM_Lock();HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);} ... if(btn_getState(&K2_BUTTON_150mS) == BTN_EDGE2){printf(" K2 150ms button!EEPROM_read test\r\n");read_data1 = FLASH_ReadWord(DATA_EEPROM_START_ADDR);printf("the DATA_EEPROM_START_ADDR is: 0x %x \r\n",read_data1);}按照上面的例子,擦除DATA_EEPROM_START_ADDR+4的地址不會影響DATA_EEPROM_START_ADDR地址的開始寫入的數據
寫入一樣,如果不在意以前的數據,直接寫入就可以。
總結來說EEPROM的使用還是很好用而且簡單的。
而且EEPROM是可以按照字節,半字,全字寫入的,測試了一下,是右對齊
右對齊什么意思呢,打個比方就是如果在地址 addr 中,本來寫入全字 0x12345678 然后直接在EEPROM 的 addr 這個地址,寫入半字 0xFFFF, 再讀取全字的話,addr 地址的全字就變成了 0x1234FFFF ,這個具體的為什么在地址寫入半字,不會直接從地址開始占用2個字節,是因為地址的類型為 uint32_t ,所以該地址就是 4個字節類型的數。
寫入問題說明修改
2021/11/23 修改說明 上面一段的解釋有誤,這里修改一下,不是因為地址類型為uint32_t,地址類型永遠是這個,是因為定義的數據類型為uint32_t ,然后STM32又是小端模式,所以保存的方式是從地址的最后開始保存,4個字節的全字,第一個字節放在地址開始+4 的位置,第二個字節放在地址開始+3的位置,所以如果調用HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, DATA_EEPROM_START_ADDR, write_data1);
關鍵在于FLASH_TYPEPROGRAMDATA_WORD以全字方式寫入半字,那么內核會自動分配4個字節的寬度,半字的第一個字節放在寫入地址+4 的位置,第二個字節放在寫入地址+3的位置,所以導致了上面的結果
HAL_FLASHEx_DATAEEPROM_Unlock();HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, DATA_EEPROM_START_ADDR, 0X88);HAL_FLASHEx_DATAEEPROM_Lock();read_data1 = FLASH_ReadWord(DATA_EEPROM_START_ADDR);printf("the DATA_EEPROM_START_ADDR is: 0x %x \r\n",read_data1);最后在 stml0_flash.h 中把函數完善聲明一下,使得主函數中的程序更加簡潔。
void MY_DATAEEPROM_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data) {HAL_FLASHEx_DATAEEPROM_Unlock(); HAL_FLASHEx_DATAEEPROM_Program(TypeProgram, Address, Data);HAL_FLASHEx_DATAEEPROM_Lock(); }那么L051 的EEPROM的測試就到這里,其實有EEPROM,在項目中的保存數據的功能就問題不大了,但是我們既然開始了,就把L051 Flash的讀寫也測試一下。
2.3 Flash寫函數
Flash的讀取其實和EEPROM一樣,主要是寫函數,來看一下stm32l0xx_hal_flash.h 中有哪些函數
/* IO operation functions *****************************************************/ HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data); HAL_StatusTypeDef HAL_FLASH_Program_IT(uint32_t TypeProgram, uint32_t Address, uint32_t Data);/* FLASH IRQ handler function */ void HAL_FLASH_IRQHandler(void); /* Callbacks in non blocking modes */ void HAL_FLASH_EndOfOperationCallback(uint32_t ReturnValue); void HAL_FLASH_OperationErrorCallback(uint32_t ReturnValue);/*** @}*//** @addtogroup FLASH_Exported_Functions_Group2* @{*/ /* Peripheral Control functions ***********************************************/ HAL_StatusTypeDef HAL_FLASH_Unlock(void); HAL_StatusTypeDef HAL_FLASH_Lock(void); HAL_StatusTypeDef HAL_FLASH_OB_Unlock(void); HAL_StatusTypeDef HAL_FLASH_OB_Lock(void); HAL_StatusTypeDef HAL_FLASH_OB_Launch(void);有點忙,Flash的后面再也,看了幾個demo,只需要做幾個測試就可以;
2021.8.5 今天有空來接著測試一下L051 Flash的讀寫,看了下HAL_FLASH_Program函數:
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint32_t Data) {HAL_StatusTypeDef status = HAL_ERROR;/* Process Locked */__HAL_LOCK(&pFlash);/* Check the parameters */assert_param(IS_FLASH_TYPEPROGRAM(TypeProgram));assert_param(IS_FLASH_PROGRAM_ADDRESS(Address));/* Wait for last operation to be completed */status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);if(status == HAL_OK){/* Clean the error context */pFlash.ErrorCode = HAL_FLASH_ERROR_NONE;/*Program word (32-bit) at a specified address.*/*(__IO uint32_t *)Address = Data;/* Wait for last operation to be completed */status = FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);}/* Process Unlocked */__HAL_UNLOCK(&pFlash);return status; }這里也再次說明了,L051的寫必須以字的方式寫入。
不管了,先測試一下,不擦除直接寫入,這里先定義一下寫入的地址,前面我們已經知道了L051 flash一共 512頁,每頁128bytes,所以我們直接拿最后面的幾頁來測試
#define ADDR_FLASH_PAGE_505 0X08000000 + 128*504 // #define ADDR_FLASH_PAGE_506 0X08000000 + 128*505 // #define ADDR_FLASH_PAGE_507 0X08000000 + 128*506 // #define ADDR_FLASH_PAGE_508 0X08000000 + 128*507 // #define ADDR_FLASH_PAGE_509 0X08000000 + 128*508 // #define ADDR_FLASH_PAGE_510 0X08000000 + 128*509 // #define ADDR_FLASH_PAGE_511 0X08000000 + 128*510 // #define ADDR_FLASH_PAGE_512 0X08000000 + 128*511 //最后一頁開始先不擦除,直接在最后一頁寫入一個字讀取一下試試,整理一下寫入函數:
void MY_DATAFLASH_Program(uint32_t Address, uint32_t Data) {HAL_FLASH_Unlock(); if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK){printf("write data 0x%x OK\n", Data);}else{printf("failed!!!\n");}HAL_FLASH_Lock(); }測試一下;
MY_DATAFLASH_Program(ADDR_FLASH_PAGE_512,write_data2);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_512);printf("the ADDR_FLASH_PAGE_512 is: 0x %x \r\n",read_data1);在沒有寫入flash之前,該地址讀出來的數據為0,寫入后讀出來是正常寫入的數據
這里有個疑問,按理來說,flash存儲器有個特點,就是只能寫0,不能寫1,所以flash的寫入,比需先擦除,或者至少檢查一下該數據區是否可以寫入,但是L051 怎么初始的時候讀出來是0? 難道L051的有區別,需要測試一下
先在一個地址寫0XFFFFFFFF ,然后寫完了再寫一次別的數據看看能否直接寫入,結果是寫入了0XFFFFFFFF ,不能繼續直接寫數據,說明,估計L051是相反的,這里具體是不是我只看測試結果,結論的話我自己知道就可以應用,希望有權威大神指正。
這里我們得用到一個關鍵的函數 ,這個函數是在stm32l0xx_hal_flash_ex.c 這個文件中的,是flash的擦除函數:
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)所以這里我們知道了以后,可以優化一下寫入函數,我們項目中用到的是可以直接對某個地址的寫入,然后也不需要保存,此頁其他的數據,所以我們把函數改成如下:
void MY_DATAFLASH_Program(uint32_t Address, uint32_t Data) {FLASH_EraseInitTypeDef EraseInitStruct;uint32_t checkdata;uint32_t PAGEError = 0;checkdata = FLASH_ReadWord(Address);HAL_FLASH_Unlock(); /*如果是0,直接寫*/if(checkdata == 0){ if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK){printf("write data 0x%x OK\n", Data);}else{printf("failed!!!\n");}}/*否則擦除再寫*/else{__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; // 刷除方式EraseInitStruct.PageAddress = Address; // 起始地址EraseInitStruct.NbPages = 1;if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK){// 如果刷除錯誤printf("\r\n FLASH Erase Fail\r\n");printf("Fail Code:%d\r\n",HAL_FLASH_GetError());printf("Fail Page:%d\r\n",PAGEError);}if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK){printf("write data 0x%x OK\n", Data);}else{printf("failed!!!\n");}}HAL_FLASH_Lock(); }自己修改了一個函數, 改成這樣以后,就能直接在想寫入的地方寫入數據了,到這里,flash足夠我項目中的使用了。但是還有最后一個疑問,就是擦除的一頁到底是不是128bytes我來驗證一下。
u32 write_data1 = 0X12345678; u32 write_data2 = 0X87654321; u32 write_data3 = 0XFFFFFFFF;MY_DATAFLASH_Program(ADDR_FLASH_PAGE_508 + 124,0X508508FF); MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509,write_data3); MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509+4,write_data2); MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509+8,write_data1); MY_DATAFLASH_Program(ADDR_FLASH_PAGE_510,0X510510FF); read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_508 + 124);printf("the ADDR_FLASH_PAGE_508 last is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509);printf("the ADDR_FLASH_PAGE_509 1 is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 4);printf("the ADDR_FLASH_PAGE_509 2 is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 8);printf("the ADDR_FLASH_PAGE_509 3 is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_510);printf("the ADDR_FLASH_PAGE_510 first is: 0x %x \r\n",read_data1);if(btn_getState(&K2_BUTTON_150mS) == BTN_EDGE2){printf(" K2 150ms button!EEPROM_read test\r\n");read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_508 + 124);printf("the ADDR_FLASH_PAGE_508 last is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509);printf("the ADDR_FLASH_PAGE_509 1 is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 4);printf("the ADDR_FLASH_PAGE_509 2 is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_509 + 8);printf("the ADDR_FLASH_PAGE_509 3 is: 0x %x \r\n",read_data1);read_data1 = FLASH_ReadWord(ADDR_FLASH_PAGE_510);printf("the ADDR_FLASH_PAGE_510 first is: 0x %x \r\n",read_data1);} if(btn_getState(&K1_BUTTON_150mS) == BTN_EDGE2){// printf(" K1 150ms button!,EEPROM_Erase test\r\n");// MY_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, DATA_EEPROM_START_ADDR, write_data1);printf(" K1 150ms button!,flash write test\r\n");MY_DATAFLASH_Program(ADDR_FLASH_PAGE_509,0X33333333);HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);}因為地址位置如果有數據的話會擦除一頁再寫入,所以我們看了一下508頁最后一個地址,和510頁的第一個地址數據,對509頁的數據進行了操作,結果發現不會改變508 和510的數據,509頁的數據會全部清除!
所以得出的結論顯而易見,flash擦除按照一頁一頁擦除,
在L051上面一頁為128bytes,而且擦除后數據全部為 0;
2.4 讀寫EEPROM的后續問題
最近有一個項目,因為缺貨 STM32L051R系列缺貨,買了 STM32L071RBT6 替代:
看了一下地址,其實我們上面所有的測試代碼基本都是可以直接用的,代碼直接用STM32L051的代碼也是可以的,實際項目中,還是使用EEPROM比較方便,所以在使用EEPROM的時候,發現了一個問題(并不是換了芯片的問題),還是數據讀取和寫入的問題。
2.4.1 問題的出現和解決
下面程序的硬件平臺是 STM32L071RBT6,首先在程序中,有一個寫ID的函數:
寫入IO是通過字節的方式 byte 寫入的,寫了6個字節(藍牙設備的ID)。
然后最初讀取的函數用的是:
使用這個讀ID的函數,問題就出來了。
上圖代碼中,我讀取ID使用的是半字讀取,處理方式是把讀到的半字前面8位給ID1, 后面8位給ID2, 但是測試中發現數據讀出來與想要的相反,什么意思呢,看下面的測試說明:
上電打印出EEPROM中讀取的一個地址的,每個ID(每個ID是uint8_t類型)的數據,在代碼開始定義了測試數據:
BlueID_STRUCT test; ... /* CHBlueID_STRUCT Flash_PowerOn_BlueCheck() {CHBlueID_STRUCT PowerOn_ID;PowerOn_ID.CH1ID = FLASH_blueIDRead(CH1_ID_ADDR);PowerOn_ID.CH2ID = FLASH_blueIDRead(CH2_ID_ADDR);PowerOn_ID.CH3ID = FLASH_blueIDRead(CH3_ID_ADDR);PowerOn_ID.CH4ID = FLASH_blueIDRead(CH4_ID_ADDR);PowerOn_ID.CH5ID = FLASH_blueIDRead(CH5_ID_ADDR);PowerOn_ID.CH6ID = FLASH_blueIDRead(CH6_ID_ADDR);PowerOn_ID.CH7ID = FLASH_blueIDRead(CH7_ID_ADDR);PowerOn_ID.CH8ID = FLASH_blueIDRead(CH8_ID_ADDR);PowerOn_ID.CH9ID = FLASH_blueIDRead(CH9_ID_ADDR);PowerOn_ID.CH10ID = FLASH_blueIDRead(CH10_ID_ADDR);return PowerOn_ID; } */ BlueChipID = Flash_PowerOn_BlueCheck(); //上電先把ID讀出來做比較 //打印一個出來測試,看結果 printf("BlueChipID.CH1ID is 0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\r\n",BlueChipID.CH1ID.ID1,BlueChipID.CH1ID.ID2,BlueChipID.CH1ID.ID3,BlueChipID.CH1ID.ID4,BlueChipID.CH1ID.ID5,BlueChipID.CH1ID.ID6);test.ID1= 0XFF; //結構體每個元素是 uint8_t 類型 test.ID2= 0XEE; test.ID3= 0XDD; test.ID4= 0XCC; test.ID5= 0XBB; test.ID6= 0XAA;while (1){...}在程序中通過操作,寫入測試數據,調用上面提到過的寫ID的函數,寫ID的函數是每個字節每個字節寫的:
按我們希望的結果來說,讀出來按照順序打印,應該是:0xff,0xee,0xdd,0xcc,0xbb,0xaa 。測試實際上是:
為了確實是讀的問題還是寫的問題,添加了讀字節的函數:
打印的結果:
說明確實是ID的讀取函數出的問題,是因為使用的半字讀取,問題處理不麻煩,我們把讀取函數修改一下:
BlueID_STRUCT FLASH_blueIDRead(uint32_t address) {BlueID_STRUCT u48id;// uint16_t temp1,temp2,temp3; /**(__IO uint16_t*)address; */// temp1=*(__IO uint16_t*)address; // u48id.ID1 = (uint8_t)(temp1>>8);// u48id.ID2 = (uint8_t)(temp1&0X00FF);// temp2=*(__IO uint16_t*)(address+2);// u48id.ID3 = (uint8_t)(temp2>>8);// u48id.ID4 = (uint8_t)(temp2&0X00FF);// temp3=*(__IO uint16_t*)(address+4);// u48id.ID5 = (uint8_t)(temp3>>8);// u48id.ID6 = (uint8_t)(temp3&0X00FF); u48id.ID1 = FLASH_Readbyte(address);u48id.ID2 = FLASH_Readbyte(address+1);u48id.ID3 = FLASH_Readbyte(address+2);u48id.ID4 = FLASH_Readbyte(address+3);u48id.ID5 = FLASH_Readbyte(address+4);u48id.ID6 = FLASH_Readbyte(address+5);return u48id; }測試結果才正常了,如下圖:
2.4.2 問題的分析(大小端模式數據格式)
出現上面的問題,其實是和我們經常說的大端模式和小端模式有關的。
STM32使用的是小端模式,簡單介紹一下大端模式小端模式數據存放的方式,如下圖:
知道上面的知識,我們在開始的讀取函數中是直接讀取的半字(__IO uint16_t*)address; ,但是我們寫入的時候是一個字節一個字節寫入,上面的例子所以我們內存中的數據實際上如下圖所示:
使用(__IO uint16_t*)address; 去讀取,讀取出來的數值一個uint16_t類型的數值:
假設是 a,a=*(__IO uint16_t*)addr; 會有 a = 0xEEFF; 所以高8位變成了 0xEE。 OK! 解釋清楚!問題解決!
至此,我們基本上把STM32L051 的EEPROM 和Flash 功能都測試過了,把工程中需要用到的功能做了測試,也學到了一些新的知識,還是實踐出結論啊,當初沒有自己測試之前看了網上的有關類似的介紹,還是很多誤解,這下全部清晰了。
2.4.3 STM32L071RBT6 EEPROM讀寫全字問題
讀問題的出現
前面其實測試過,讀取全字是可以的,直接使用return *(__IO uint32_t*)address;:便可以讀到該地址的全字:
uint32_t FLASH_ReadWord(uint32_t address) {return *(__IO uint32_t*)address; }所以在后面使用過程中,有這么一個函數:
一開始還真的不知道哪里出了問題?折騰了好一陣才發現調用函數讀取ID時就會卡死。
問題的解決:
因為還有另外一個藍牙版本的產品,如果是藍牙設備的ID,因為藍牙設備的ID是6位的,所以當時讀取藍牙的ID的時候使用的是(藍牙版本是沒問題的):
其實折騰了好一會兒,后來想著藍牙是讀一個字節,要不要試著把 全字 分為 4個字節,單獨讀取試一試?
于是學藍牙把程序分為4個字節讀取:
代碼也放一下,方便復制:
u32 FLASH_ReadEnoceanID(uint32_t address) {u32 keepid;u8 i;keepid = FLASH_Readbyte(address);i = FLASH_Readbyte(address + 1);keepid = (i<<8)|keepid;i = FLASH_Readbyte(address + 2);keepid = (i<<16)|keepid;i = FLASH_Readbyte(address + 3);keepid = (i<<24)|keepid;return keepid; }CHID_STRUCT Flash_PowerOn_Check() {CHID_STRUCT PowerOn_ID;PowerOn_ID.CH1ID = FLASH_ReadEnoceanID(CH1_ID_ADDR);PowerOn_ID.CH2ID = FLASH_ReadEnoceanID(CH2_ID_ADDR);PowerOn_ID.CH3ID = FLASH_ReadEnoceanID(CH3_ID_ADDR);PowerOn_ID.CH4ID = FLASH_ReadEnoceanID(CH4_ID_ADDR);PowerOn_ID.CH5ID = FLASH_ReadEnoceanID(CH5_ID_ADDR);PowerOn_ID.CH6ID = FLASH_ReadEnoceanID(CH6_ID_ADDR);PowerOn_ID.CH7ID = FLASH_ReadEnoceanID(CH7_ID_ADDR);PowerOn_ID.CH8ID = FLASH_ReadEnoceanID(CH8_ID_ADDR);PowerOn_ID.CH9ID = FLASH_ReadEnoceanID(CH9_ID_ADDR);PowerOn_ID.CH10ID = FLASH_ReadEnoceanID(CH10_ID_ADDR);return PowerOn_ID; }測試一下,發現就好了,至于原因,目前還不知道為什么……(最后問題解決有說明,內存字節對齊問題)
寫問題的出現
本來以為解決了上面問題OK了,可是后面測試的時候發現寫的時候也有問題:
一直用的寫全字函數為:
FLASH_Status FLASH_WriteWord(uint32_t Address, uint32_t Data) {FLASH_Status i = FLASH_COMPLETE;HAL_FLASHEx_DATAEEPROM_Unlock(); while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_WORD, Address, Data) != HAL_OK);HAL_FLASHEx_DATAEEPROM_Lock();return i; }在程序中會調用此函數進行 ID 保存:
但是使用時候發現:
問題的解決:
其實這個問題也莫名其妙,真是說不出來為什么,估計是得詳細的查看數據手冊,但是還是 因為 在藍牙的版本上面沒有此類問題:
所以這里還是嘗試改成 以 字節 方式寫入:
FLASH_Status FLASH_WriteWord(uint32_t Address, uint32_t Data) {FLASH_Status state = FLASH_COMPLETE;u8 i = 0;u8 writedata[6]={0};writedata[0] = (u8)Data;writedata[1] = (u8)(Data>>8);writedata[2] = (u8)(Data>>16);writedata[3] = (u8)(Data>>24);HAL_FLASHEx_DATAEEPROM_Unlock(); for(i=0; i<4; i++){while(HAL_FLASHEx_DATAEEPROM_Program(FLASH_TYPEPROGRAMDATA_BYTE, Address + i, writedata[i]) != HAL_OK);}HAL_FLASHEx_DATAEEPROM_Lock();return state; }使用此種方式寫入,就不會出現問題!
其實也可以嘗試修改地址,使得成為 4 的倍數,可能也不會出問題,這里就不測試了(最后問題解決有還是測試了,內存字節對齊問題)
2.4.3小結使用的芯片為 STM32L071RBT6
最后問題的解決
先直接說結論,就是EEPROM地址定義的問題,應該是4字節對齊(4的整數倍),讀取全字的操作才能正常!
上面 STM32L071RBT6 EEPROM 讀寫全字問題的關鍵在于,存儲地址的定義上,如上面一張圖所示:
(我在EEPROM區域定義了10個地址,用來存放無線設備的ID數據,如果是藍牙芯片,那么ID為6個字節,如果是Enocean芯片,那么ID為4個字節,為了保持代碼的統一,我在使用保存4個字節的ID數據的地址定義時候沿用的是藍牙的 EEPROM區域定義)
那么正如我圖中猜想的一樣,藍牙的 ID 6個字節,我是都是通過一個字節一個字節操作,組合起來進行的,所以一切正常。
但是對于 4個字節的 ID ,期初是用的 全字的方式,就出問題了,換成一個字節一個字節的操作,看上去是解決問題了。
但是實際上多了一些隱藏問題,暫時也說不清楚,在產品使用的時候,讀寫ID還是會有莫名其妙的問題,最終還是對當初的這個猜想,地址是不是也需要4字節對齊?進行了修改測試,于是乎,對于 4 個字節 ID的處理,地址改成:
把地址修改成 4 的倍數以后,上面的讀取全字的兩個函數便可以正常使用,而不會出上面莫名其妙的問題。
總結
在STM32L0 系列讀取 EEPROM 的時候,需要注意:
字節操作,傳入合理范圍內的任何地址參數都可以;
半字操作,地址需要 2 字節對齊,就是 2的倍數;
全字操作,地址需要 4 字節對齊,就是 4 的倍數;
總結
以上是生活随笔為你收集整理的STM32L051测试 (四、Flash和EEPROM的读写)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java jre 配置_JRE的安装及环
- 下一篇: OpenGL采样贴图显示不出来