一、介紹
首先我們需要了解一個內存映射:
stm32的flash地址起始於0x0800 0000,結束地址是0x0800 0000加上芯片實際的flash大小,不同的芯片flash大小不同。
RAM起始地址是0x2000 0000,結束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。
Flash中的內容一般用來存儲代碼和一些定義為const的數據,斷電不丟失,
RAM可以理解為內存,用來存儲代碼運行時的數據,變量等等。掉電數據丟失。
STM32將外設等都映射為地址的形式,對地址的操作就是對外設的操作。
stm32的外設地址從0x4000 0000開始,可以看到在庫文件中,是通過基於0x4000 0000地址的偏移量來操作寄存器以及外設的。
一般情況下,程序文件是從 0x0800 0000 地址寫入,這個是STM32開始執行的地方,0x0800 0004是STM32的中斷向量表的起始地址。
在使用keil進行編寫程序時,其編程地址的設置一般是這樣的:
程序的寫入地址從0x08000000(數好零的個數)開始的,其大小為0x80000也就是512K的空間,換句話說就是告訴編譯器flash的空間是從0x08000000-0x08080000,RAM的地址從0x20000000開始,大小為0x10000也就是64K的RAM。這與STM32的內存地址映射關系是對應的。
M3復位后,從0x08000004取出復位中斷的地址,並且跳轉到復位中斷程序,中斷執行完之后會跳到我們的main函數,main函數里邊一般是一個死循環,進去后就不會再退出,當有中斷發生的時候,M3將PC指針強制跳轉回中斷向量表,然后根據中斷源進入對應的中斷函數,執行完中斷函數之后,再次返回main函數中。大致的流程就是這樣。
1.1、內部Flash的構成:
STM32F429 的內部 FLASH 包含主存儲器、系統存儲器、 OTP 區域以及選項字節區域,它們的地址分布及大小如下:
STM32F103的中容量內部 FLASH 包含主存儲器、系統存儲器、 OTP 區域以及選項字節區域,它們的地址分布及大小如下:
注意STM32F105VC的是有64K或128頁x2K=256k字節的內置閃存存儲器,用於存放程序和數據。
- 主存儲器:一般我們說 STM32 內部 FLASH 的時候,都是指這個主存儲器區域它是存儲用戶應用程序的空間,芯片型號說明中的 1M FLASH、 2M FLASH 都是指這個區域的大小。與其它 FLASH 一樣,在寫入數據前,要先按扇區擦除,
- 系統存儲區:系統存儲區是用戶不能訪問的區域,它在芯片出廠時已經固化了啟動代碼,它負責實現串口、 USB 以及 CAN 等 ISP 燒錄功能。
- OTP 區域:OTP(One Time Program),指的是只能寫入一次的存儲區域,容量為 512 字節,寫入后數據就無法再更改, OTP 常用於存儲應用程序的加密密鑰。
- 選項字節:選項字節用於配置 FLASH 的讀寫保護、電源管理中的 BOR 級別、軟件/硬件看門狗等功能,這部分共 32 字節。可以通過修改 FLASH 的選項控制寄存器修改。
1.2、對內部Flash的寫入過程:
1. 解鎖 (固定的KEY值)
(1) 往 Flash 密鑰寄存器 FLASH_KEYR 中寫入 KEY1 = 0x45670123
(2) 再往 Flash 密鑰寄存器 FLASH_KEYR 中寫入 KEY2 = 0xCDEF89AB
2. 數據操作位數
最大操作位數會影響擦除和寫入的速度,其中 64 位寬度的操作除了配置寄存器位外,還需要在 Vpp 引腳外加一個 8-9V 的電壓源,且其供電間不得超過一小時,否則 FLASH可能損壞,所以 64 位寬度的操作一般是在量產時對 FLASH 寫入應用程序時才使用,大部分應用場合都是用 32 位的寬度。
3. 擦除扇區
在寫入新的數據前,需要先擦除存儲區域, STM32 提供了扇區擦除指令和整個FLASH 擦除(批量擦除)的指令,批量擦除指令僅針對主存儲區。
扇區擦除的過程如下:
(1) 檢查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以確認當前未執行任何
Flash 操作;
(2) 在 FLASH_CR 寄存器中,將“激活扇區擦除寄存器位 SER ”置 1,並設置“扇
區編號寄存器位 SNB”,選擇要擦除的扇區;
(3) 將 FLASH_CR 寄存器中的“開始擦除寄存器位 STRT ”置 1,開始擦除;
(4) 等待 BSY 位被清零時,表示擦除完成。
4. 寫入數據
擦除完畢后即可寫入數據,寫入數據的過程並不是僅僅使用指針向地址賦值,賦值前還還需要配置一系列的寄存器,步驟如下:
(1) 檢查 FLASH_SR 中的 BSY 位,以確認當前未執行任何其它的內部 Flash 操作;
(2) 將 FLASH_CR 寄存器中的 “激活編程寄存器位 PG” 置 1;
(3) 針對所需存儲器地址(主存儲器塊或 OTP 區域內)執行數據寫入操作;
(4) 等待 BSY 位被清零時,表示寫入完成。
1.3、查看工程內存的分布:
由於內部 FLASH 本身存儲有程序數據,若不是有意刪除某段程序代碼,一般不應修改程序空間的內容,所以在使用內部 FLASH 存儲其它數據前需要了解哪一些空間已經寫入了程序代碼,存儲了程序代碼的扇區都不應作任何修改。通過查詢應用程序編譯時產生的“ *.map”后綴文件,
打開 map 文件后,查看文件最后部分的區域,可以看到一段以“ Memory Map of the
image”開頭的記錄(若找不到可用查找功能定位),
【注】ROM加載空間
這一段是某工程的 ROM 存儲器分布映像,在 STM32 芯片中, ROM 區域的內容就是 指存儲到內部 FLASH 的代碼。
在上面 map 文件的描述中,我們了解到加載及執行空間的基地址(Base)都是0x08000000,它正好是 STM32 內部 FLASH 的首地址,即 STM32 的程序存儲空間就直接是執行空間;它們的大小(Size)分別為 0x00000b50 及 0x00000b3c,執行空間的 ROM 比較小的原因就是因為部分 RW-data 類型的變量被拷貝到 RAM 空間了;它們的最大空間(Max)均為 0x00100000,即 1M 字節,它指的是內部 FLASH 的最大空間。
計算程序占用的空間時,需要使用加載區域的大小進行計算,本例子中應用程序使用
的內部 FLASH 是從 0x08000000 至(0x08000000+0x00000b50)地址的空間區域。
所以從扇區 1(地址 0x08004000)后的存儲空間都可以作其它用途,使用這些存儲空間時不會篡改應用程序空間的數據。
具體可參考原子的例程:實驗四十一:FLASH 模擬 EEPROM 實驗
文章引用地址:https://blog.csdn.net/qq_33559992/article/details/77676716
感謝原文作者
二、代碼拆分介紹(以STM32F105系列為例,如上圖表5所示)
2.1 讀/寫入數據流程
寫數據流程
2.1.1、Flash 解鎖,直接調用#include "stm32f10x_flash.h"中的void FLASH_Unlock(void)函數,這個函數是官方提供的,其內部代碼如下:

2.1.2、擦除扇區,也是直接調用固件庫官方的函數FLASH_Status FLASH_ErasePage(uint32_t Page_Address),這個官方函數代碼也貼出來看看,代碼如下:

注意這個擦除扇區函數是你提供一個STM32f105系列扇區的開始地址即可,擦除是按照頁擦除(每頁2KB=1024Byte)或者整個擦除(見STM32參考手冊的第二章2.3.3嵌入式閃存部分介紹)
比如我們要擦除互聯網型的127頁,我們只需要FLASH_ErasePage(0x0803f800);執行后,第127頁的0x0803f800-0x0803FFFF數據都將被擦除。
當然官方提供的也不知一個擦除函數,而是三個,具體如下,對於32位系統:一個是字節=4byte=32bite;一個是半字=2byte=16bite;一個是字節=1byte=8bite;進行擦除。
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
FLASH_Status FLASH_EraseAllPages(void);
FLASH_Status FLASH_EraseOptionBytes(void);
2.1.3、接下來是寫/讀數據函數,該函數也是官方給出的,我們只需要用就好了。但要注意,這個是個半字的寫操作,威少是uint16_t 的數據算半字呢,因為單片機是32的,對於32位單片機系統來說,一個字是4個字節的,8位的比如51單片機系統一個字就是2位的,64位單片機系統一個字就是8個字節,脫離單片機系統說字是多少個字節是沒意義的。所以這里寫入/讀出半字也就是一次寫入2個字節,寫完/讀出一次地址會加2。
寫數據操作:

當然官方給的不止是這一個函數寫數據,官方提供了3個
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);//一次寫一個字,對於32系統,一次寫的是4個字節,uint32_t 變量大小,32bit
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);//一次寫一個半字,對於32系統,一次寫的是2個字節,uint16_t 變量大小,16bit
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);//一次寫一個字節,對於32系統,一次寫的是1個字節,uint8_t 變量大小,8bit
讀數據操作:
讀數據的函數,官方並沒有給出:下面我們自己給出,具體的讀法代碼如下
1 //讀取指定地址的半字(16位數據) 2 //也是按照半字讀出,即每次讀2個字節數據返回 3 uint16_t FLASH_ReadHalfWord(uint32_t address) 4 { 5 return *(__IO uint16_t*)address; 6 }
如果要連續都區多個地址數據,可以進行如下代碼操作
1 //從指定地址開始讀取多個數據 2 void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead) 3 { 4 uint16_t dataIndex; 5 for(dataIndex=0;dataIndex<countToRead;dataIndex++) 6 { 7 readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2); 8 } 9 }
2.1.4、這步驟應該就是再次上鎖,保護存儲區不被重寫覆蓋了,直接使用官方的函數即可:FLASH_Lock();//上鎖寫保護
具體官方代碼貼出如下

三、簡單的小例程代碼實現
例子功能:
1、將數據存儲在stm32F105單片機的主存儲區0x08036000地址開始的扇區,(0x08036000應該是該單片機大約108個扇區的開始地址位置即頁108起始地址)。
2、將該單片機的頁108(page108=0x08036000)處的數據再讀出來;
具體實現代碼如下,作為例子,只進行了半字的讀寫操作,我們寫的數據buff為空,內容默認值為0
1 #include "stm32f10x_flash.h" 2 3 #define StartServerManageFlashAddress ((u32)0x08036000)//讀寫起始地址(內部flash的主存儲塊地址從0x08036000開始) 4 5 //從指定地址開始寫入多個數據 6 void FLASH_WriteMoreData(uint32_t startAddress,uint16_t *writeData,uint16_t countToWrite) 7 { 8 uint32_t offsetAddress=startAddress - FLASH_BASE; //計算去掉0X08000000后的實際偏移地址 9 uint32_t sectorPosition=offsetAddress/SECTOR_SIZE; //計算扇區地址,對於STM32F103VET6為0~255 10 uint32_t sectorStartAddress=sectorPosition*SECTOR_SIZE+FLASH_BASE; //對應扇區的首地址 11 uint16_t dataIndex; 12 13 if(startAddress<FLASH_BASE||((startAddress+countToWrite*2)>=(FLASH_BASE + SECTOR_SIZE * FLASH_SIZE))) 14 { 15 return;//非法地址 16 } 17 FLASH_Unlock(); //解鎖寫保護 18 19 FLASH_ErasePage(sectorStartAddress);//擦除這個扇區 20 21 for(dataIndex=0;dataIndex<countToWrite;dataIndex++) 22 { 23 FLASH_ProgramHalfWord(startAddress+dataIndex*2,writeData[dataIndex]); 24 } 25 26 FLASH_Lock();//上鎖寫保護 27 } 28 29 //讀取指定地址的半字(16位數據) 30 uint16_t FLASH_ReadHalfWord(uint32_t address) 31 { 32 return *(__IO uint16_t*)address; 33 } 34 35 //從指定地址開始讀取多個數據 36 void FLASH_ReadMoreData(uint32_t startAddress,uint16_t *readData,uint16_t countToRead) 37 { 38 uint16_t dataIndex; 39 for(dataIndex=0;dataIndex<countToRead;dataIndex++) 40 { 41 readData[dataIndex]=FLASH_ReadHalfWord(startAddress+dataIndex*2); 42 } 43 } 44 45 void write_to_flash(void) 46 { 47 u16 buff[1200]; 48 u16 count_len = 2272 / 2; 50 FLASH_WriteMoreData(StartServerManageFlashAddress,buff,count_len); 55 } 56 57 void read_from_flash(void) 58 { 59 u16 buff[1200]; 60 u16 count_len = 2272 / 2; 61 FLASH_WriteMoreData(StartServerManageFlashAddress,buff,count_len); 66 67 }
1 void mian(void) 2 { 3 .........//初始化其他外設 4 while(1) 5 { 6 ...........//其他外設執行函數 7 if(滿足條件真)//寫數據操作 8 { 9 write_to_flash(); 10 } 11 else //讀數據操作 12 { 13 read_from_flash(); 14 } 15 16 } 17 }
四、附言
值得的注意的是,我們讀寫的地址是0x08036000,讀寫方式是半字,這里地址空間對於stm32f105芯片來說是第108扇區,每個扇區2KB,stm32F105VC總共是256KB空間,128頁。所以地址能取到0x08036000,像小中容量stm32f103單片機,64KB和128KB的主存儲區地址都是到不了0x08036000,除非是stm32f103VE的256KB芯片的主存儲快,0x08036000才是有效的存儲地址,中小型這個地址都不是有效的主存儲開地址(超出了)
注:轉自https://www.cnblogs.com/pertor/p/9484663.html