stm32的產品都有內置Flash,而且不同系列的產品其內置Flash的大小不盡相同,結構上也有差異,本文將對stm32f07x,stm32f10x,stm32f40x的內置Flash結構,以及如何進行讀寫操作做一個介紹。
一、特性與構成
1.stm32f07x系列
2、stm32f10x系列
3、stm32f40x系列
二、對內部Flash的讀寫操作
2.1 stm32不同系列的產品,內部Flash的特性與構成確實存在一定的差異,但是對於讀寫操作,步驟一致,比如要往內部flash寫入數據,需要幾個步驟:
①、解鎖flash
②、擦除
③、寫入數據
④、鎖住flash
讀取Flash的內容,只需要直接讀取內存地址的數據即可。
注意:①如果要寫入的地址內保存了一些重要的數據,不想丟失的話,應該在擦除之前先把數據讀取到緩沖區里,再進行擦除和寫入新的數據。在擦除頁之后,如果寫入的數據不夠一頁,可以連續寫入。
②寫入數據之前,應該計算好MCU程序占用的內存,因為MCU的程序也是保存在Flash里,如果寫入的數據占用了MCU程序的內存,就會導致程序死掉。而計算MCU占用內存大小的方法可以將它生成為bin文件,從它的屬性里可以知道它的大小。具體可以參考:https://www.cnblogs.com/xingboy/p/10818813.html;這里提供簡單的操作介紹:
1、首先打開keil工程,點擊,出現以下界面,選擇User
2、最下方的Run #1打上勾,后面文本框里輸入:fromelf --bin --output ."SL@L.bin" "#L",然后點擊OK
3、編譯
4、到工程文件夾里找到這個bin文件,右擊該文件,點擊屬性就可以查到這個文件的大小
比如我這個bin文件是16KB+,那么我們從0x0800 0000開始算起,17KB的內存不要動,從17KB的位置開始寫入數據就不會影響MCU的程序。需要注意的是,不同的MCU,其每一頁的大小都不一樣,如開篇提供的三種MCU的Flash構成表格,stm32f07x每一頁是2KB,stm32f10x的每一頁是1KB,stm32f40x沒有頁的概念,只有扇區的概念,且扇區的大小也不一定相同,有的是16KB,有的是64KB,有的是128KB。所以雖然讀操作的步驟一樣,但是編程的時候有些區別。這里以HAL為例:
stm32f07x系列:
/************************************************************************ *函數名稱:void Flash_Write(uint32_t address,uint32_t *data,uint8_t size) *函數功能:往內部Flash里寫入數據 *函數形參:address:寫入數據的起始地址,data:要寫入的數據的源地址,size:大小 *函數返回值:無 *************************************************************************/ void Flash_Write(uint32_t address,uint32_t data) { uint32_t PageError = 0; if(HAL_FLASH_Unlock() != HAL_OK) /*解鎖Flash*/ { printf("Unlock Flash Fail!\r\n"); } else { printf("Unlock Flash succeed!\r\n"); } FLASH_EraseInitTypeDef FLASH_EraseInit; FLASH_EraseInit.TypeErase = FLASH_TYPEERASE_PAGES; FLASH_EraseInit.PageAddress = address; FLASH_EraseInit.NbPages = 1; if(HAL_FLASHEx_Erase(&FLASH_EraseInit,&PageError) != HAL_OK) { printf("Erase Flash Fail!\r\n"); } else { printf("Erase Flash Succeed!\r\n"); } HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,address,data); HAL_FLASH_Lock(); }
其中:
//擦除結構體:
typedef struct { uint32_t TypeErase; /*!< TypeErase: Mass erase or page erase. This parameter can be a value of @ref FLASHEx_Type_Erase */ uint32_t PageAddress; /*!< PageAdress: Initial FLASH page address to erase when mass erase is disabled This parameter must be a number between Min_Data = FLASH_BASE and Max_Data = FLASH_BANK1_END */ uint32_t NbPages; /*!< NbPages: Number of pagess to be erased. This parameter must be a value between Min_Data = 1 and Max_Data = (max number of pages - value of initial page)*/ } FLASH_EraseInitTypeDef;
//擦除函數:
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
//Flash編程函數
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
//鎖住Flash
HAL_StatusTypeDef HAL_FLASH_Lock(void);
TypeErase是擦除的類型,可以是FLASH_TYPEERASE_PAGES(頁擦出)也可以是FLASH_TYPEERASE_MASSERASE(塊擦除,可以把Main Flash主存儲塊全部擦除);
PageAddress是頁地址,就是數據要寫入的地址,因為我的MCU程序占用16KB內存,那么我從17KB開始寫入數據就是0x0800 0000 + (0x400 * 17) = 0x0800 4400;
NbPages擦除多少頁;
擦除函數的最后一個形參是一個指針,指向一個變量,這個變量包含了擦除錯誤的情況下的配置信息(0xFFFFFFFF表示所有的頁都被正確擦除)
Flash編程函數:第一個形參寫入的方式:FLASH_TYPEPROGRAM_HALFWORD(半字,一次寫入16bit),FLASH_TYPEPROGRAM_WORD(字,一次寫入32bit),FLASH_TYPEPROGRAM_DOUBLEWORD(雙字,一次寫入64bit).
第二個形參,寫入數據的起始地址,第三個形參是要寫入的數據。
stm32f40x系列
uint32_t GetSector(uint32_t Address) { uint32_t sector = 0; if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)) { sector = FLASH_SECTOR_0; } else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)) { sector = FLASH_SECTOR_1; } else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)) { sector = FLASH_SECTOR_2; } else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)) { sector = FLASH_SECTOR_3; } else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)) { sector = FLASH_SECTOR_4; } else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)) { sector = FLASH_SECTOR_5; } else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)) { sector = FLASH_SECTOR_6; } else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7)) { sector = FLASH_SECTOR_7; } else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8)) { sector = FLASH_SECTOR_8; } else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9)) { sector = FLASH_SECTOR_9; } else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10)) { sector = FLASH_SECTOR_10; } else { sector = FLASH_SECTOR_11; } return sector; } uint32_t start_Add = 0x080E0000; uint32_t end_Add = 0x080E0000; //FLASH寫入數據測試 uint32_t FlashWriteData(uint8_t *data,uint8_t len) { uint32_t UserStartSector; uint32_t SectorError; uint32_t i = 0; FLASH_EraseInitTypeDef pEraseInit; /*解鎖Flash*/ HAL_FLASH_Unlock(); /*清標志位*/ __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); UserStartSector = GetSector(start_Add); pEraseInit.TypeErase = TYPEERASE_SECTORS; pEraseInit.Sector = UserStartSector; pEraseInit.NbSectors = GetSector(end_Add)-UserStartSector+1 ; pEraseInit.VoltageRange = VOLTAGE_RANGE_3; if (HAL_FLASHEx_Erase(&pEraseInit, &SectorError) != HAL_OK) { /* Error occurred while page erase */ printf("Flash Erase failed!\r\n"); return (1); } printf("Flash Erase succeed!\r\n"); for(i = 0; i < len; i+= 4 ) { if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)(start_Add+i), *(uint32_t*)(data+i)) == HAL_OK) { /* Check the written value */ if(*(uint32_t *)(data + i) != *(uint32_t*)(start_Add+i)) { /* Flash content doesn't match SRAM content */ return 2; } } else { printf("src data != flash data\r\n"); return 3; } } HAL_FLASH_Lock(); //鎖住Flash return HAL_OK; }
2.2 讀操作
/*********************************************** *函數名稱:uint32_t Flash_Read(uint32_t address) *函數功能:從內部Flash里讀出數據 *函數形參:address:要讀出的數據的起始地址 *函數返回值:讀取到的數據 ************************************************/ uint32_t Flash_Read(uint32_t address) { uint32_t retval; retval = *(uint16_t *)(address); printf("retval = %x\r\n",retval); return retval; }
總結:如果需要存儲的數據並不是很多的時候,沒必要外掛一個Flash去存儲數據,完全可以充分利用內部Flash的。但是內部Flash對寫操作的次數有限制,網上找到的數據是1萬次,而讀操作的次數就沒有限制了。所以當數據量不大,不會頻繁修改的時候,完全可以寫到內部Flash。