第50章 讀寫內部FLASH
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊》、《STM32F4xx規格書》、庫說明文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
50.1 STM32的內部FLASH簡介
在STM32芯片內部有一個FLASH存儲器,它主要用於存儲代碼,我們在電腦上編寫好應用程序后,使用下載器把編譯后的代碼文件燒錄到該內部FLASH中,由於FLASH存儲器的內容在掉電后不會丟失,芯片重新上電復位后,內核可從內部FLASH中加載代碼並運行,見圖 501。
圖 501 STM32的內部框架圖
除了使用外部的工具(如下載器)讀寫內部FLASH外,STM32芯片在運行的時候,也能對自身的內部FLASH進行讀寫,因此,若內部FLASH存儲了應用程序后還有剩余的空間,我們可以把它像外部SPI-FLASH那樣利用起來,存儲一些程序運行時產生的需要掉電保存的數據。
由於訪問內部FLASH的速度要比外部的SPI-FLASH快得多,所以在緊急狀態下常常會使用內部FLASH存儲關鍵記錄;為了防止應用程序被抄襲,有的應用會禁止讀寫內部FLASH中的內容,或者在第一次運行時計算加密信息並記錄到某些區域,然后刪除自身的部分加密代碼,這些應用都涉及到內部FLASH的操作。
1. 內部FLASH的構成
STM32的內部FLASH包含主存儲器、系統存儲器、OTP區域以及選項字節區域,它們的地址分布及大小見表 501。
表 501 STM32內部FLASH的構成
區域 |
塊 |
名稱 |
塊地址 |
大小 |
主存儲器 |
塊1 |
扇區0 |
0x0800 0000 - 0x0800 3FFF |
16 Kbytes |
扇區1 |
0x0800 4000 - 0x0800 7FFF |
16 Kbytes |
||
扇區2 |
0x0800 8000 - 0x0800 BFFF |
16 Kbytes |
||
扇區3 |
0x0800 C000 - 0x0800 FFFF |
16 Kbyte |
||
扇區4 |
0x0801 0000 - 0x0801 FFFF |
64 Kbytes |
||
扇區5 |
0x0802 0000 - 0x0803 FFFF |
128 Kbytes |
||
扇區6 |
0x0804 0000 - 0x0805 FFFF |
128 Kbytes |
||
扇區7 |
0x0806 0000 - 0x0807 FFFF |
128 Kbytes |
||
扇區8 |
0x0808 0000 - 0x0809 FFFF |
128 Kbytes |
||
扇區9 |
0x080A 0000 - 0x080B FFFF |
128 Kbytes |
||
扇區10 |
0x080C 0000 - 0x080D FFFF |
128 Kbytes |
||
扇區11 |
0x080E 0000 - 0x080F FFFF |
128 Kbytes |
||
塊2 |
扇區12 |
0x0810 0000 - 0x0810 3FFF |
16 Kbytes |
|
扇區13 |
0x0810 4000 - 0x0810 7FFF |
16 Kbytes |
||
扇區14 |
0x0810 8000 - 0x0810 BFFF |
16 Kbytes |
||
扇區15 |
0x0810 C000 - 0x0810 FFFF |
16 Kbyte |
||
扇區16 |
0x0811 0000 - 0x0811 FFFF |
64 Kbytes |
||
扇區17 |
0x0812 0000 - 0x0813 FFFF |
128 Kbytes |
||
扇區18 |
0x0814 0000 - 0x0815 FFFF |
128 Kbytes |
||
扇區19 |
0x0816 0000 - 0x0817 FFFF |
128 Kbytes |
||
扇區20 |
0x0818 0000 - 0x0819 FFFF |
128 Kbytes |
||
扇區21 |
0x081A 0000 - 0x081B FFFF |
128 Kbytes |
||
扇區22 |
0x081C 0000 - 0x081D FFFF |
128 Kbytes |
||
扇區23 |
0x081E 0000 - 0x081F FFFF |
128 Kbytes |
||
系統存儲區 |
0x1FFF 0000 - 0x1FFF 77FF |
30 Kbytes |
||
OTP區域 |
0x1FFF 7800 - 0x1FFF 7A0F |
528 bytes |
||
選項字節 |
塊1 |
0x1FFF C000 - 0x1FFF C00F |
16 bytes |
|
塊2 |
0x1FFE C000 - 0x1FFE C00F |
16 bytes |
各個存儲區域的說明如下:
主存儲器
一般我們說STM32內部FLASH的時候,都是指這個主存儲器區域,它是存儲用戶應用程序的空間,芯片型號說明中的1M FLASH、2M FLASH都是指這個區域的大小。主存儲器分為兩塊,共2MB,每塊內分12個扇區,其中包含4個16KB扇區、1個64KB扇區和7個128KB的扇區。如我們實驗板中使用的STM32F429IGT6型號芯片,它的主存儲區域大小為1MB,所以它只包含有表中的扇區0-扇區11。
與其它FLASH一樣,在寫入數據前,要先按扇區擦除,而有的時候我們希望能以小規格操縱存儲單元,所以STM32針對1MB FLASH的產品還提供了一種雙塊的存儲格式,見表 502。(2M的產品按表 501的格式)
表 502 1MB產品的雙塊存儲格式
1M字節單塊存儲器的扇區分配(默認) |
1M字節雙塊存儲器的扇區分配 |
||||
DB1M=0 |
DB1M=1 |
||||
主存儲器 |
扇區號 |
扇區大小 |
主存儲器 |
扇區號 |
扇區大小 |
1MB |
扇區0 |
16 Kbytes |
Bank 1 512KB |
扇區0 |
16 Kbytes |
扇區1 |
16 Kbytes |
扇區1 |
16 Kbytes |
||
扇區2 |
16 Kbytes |
扇區2 |
16 Kbytes |
||
扇區3 |
16 Kbytes |
扇區3 |
16 Kbytes |
||
扇區4 |
64 Kbytes |
扇區4 |
64 Kbytes |
||
扇區5 |
128 Kbytes |
扇區5 |
128 Kbytes |
||
扇區6 |
128 Kbytes |
扇區6 |
128 Kbytes |
||
扇區7 |
128 Kbytes |
扇區7 |
128 Kbytes |
||
扇區8 |
128 Kbytes |
Bank 2 512KB |
扇區12 |
16 Kbytes |
|
扇區9 |
128 Kbytes |
扇區13 |
16 Kbytes |
||
扇區10 |
128 Kbytes |
扇區14 |
16 Kbytes |
||
扇區11 |
128 Kbytes |
扇區15 |
16 Kbytes |
||
- |
- |
扇區16 |
64 Kbytes |
||
- |
- |
扇區17 |
128 Kbytes |
||
- |
- |
扇區18 |
128 Kbytes |
||
- |
- |
扇區19 |
128 Kbytes |
通過配置FLASH選項控制寄存器FLASH_OPTCR的DB1M位,可以切換這兩種格式,切換成雙塊模式后,扇區8-11的空間被轉移到扇區12-19中,扇區細分了,總容量不變。
注意如果您使用的是STM32F40x系列的芯片,它沒有雙塊存儲格式,也不存在扇區12-23,僅STM32F42x/43x系列產品才支持扇區12-23。
系統存儲區
系統存儲區是用戶不能訪問的區域,它在芯片出廠時已經固化了啟動代碼,它負責實現串口、USB以及CAN等ISP燒錄功能。
OTP區域
OTP(One Time Program),指的是只能寫入一次的存儲區域,容量為512字節,寫入后數據就無法再更改,OTP常用於存儲應用程序的加密密鑰。
選項字節
選項字節用於配置FLASH的讀寫保護、電源管理中的BOR級別、軟件/硬件看門狗等功能,這部分共32字節。可以通過修改FLASH的選項控制寄存器修改。
50.2 對內部FLASH的寫入過程
1. 解鎖
由於內部FLASH空間主要存儲的是應用程序,是非常關鍵的數據,為了防止誤操作修改了這些內容,芯片復位后默認會結FLASH上鎖,這個時候不允許設置FLASH的控制寄存器,並且不能對修改FLASH中的內容。
所以對FLASH寫入數據前,需要先給它解鎖。解鎖的操作步驟如下:
(1) 往Flash 密鑰寄存器 FLASH_KEYR中寫入 KEY1 = 0x45670123
(2) 再往Flash 密鑰寄存器 FLASH_KEYR中寫入 KEY2 = 0xCDEF89AB
2. 數據操作位數
在內部FLASH進行擦除及寫入操作時,電源電壓會影響數據的最大操作位數,該電源電壓可通過配置FLASH_CR 寄存器中的 PSIZE位改變,見表 503。
表 503 數據操作位數
電壓范圍 |
2.7 - 3.6 V (使用外部Vpp) |
2.7 - 3.6 V |
2.1 – 2.7 V |
1.8 – 2.1 V |
位數 |
64 |
32 |
16 |
8 |
PSIZE(1:0)配置 |
11b |
10b |
01b |
00b |
最大操作位數會影響擦除和寫入的速度,其中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 位被清零時,表示寫入完成。
50.3 查看工程的空間分布
由於內部FLASH本身存儲有程序數據,若不是有意刪除某段程序代碼,一般不應修改程序空間的內容,所以在使用內部FLASH存儲其它數據前需要了解哪一些空間已經寫入了程序代碼,存儲了程序代碼的扇區都不應作任何修改。通過查詢應用程序編譯時產生的"*.map"后綴文件,可以了解程序存儲到了哪些區域,它在工程中的打開方式見圖 502,也可以到工程目錄中的"Listing"文件夾中找到。
圖 502 打開工程的.map文件
打開map文件后,查看文件最后部分的區域,可以看到一段以"Memory Map of the image"開頭的記錄(若找不到可用查找功能定位),見代碼清單 501。
代碼清單 501 map文件中的存儲映像分布說明
1 =======================================================================
2 Memory Map of the image //存儲分布映像
3
4 Image Entry point : 0x080001ad
5
6 /*程序ROM加載空間*/
7 Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00000b50, Max: 0x00100000, ABSOLUTE)
8
9 /*程序ROM執行空間*/
10 Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00000b3c, Max: 0x00100000, ABSOLUTE)
11
12 /*地址分布列表*/
13 Base Addr Size Type Attr Idx E Section Name Object
14
15 0x08000000 0x000001ac Data RO 3 RESET startup_stm32f429_439xx.o
16 0x080001ac 0x00000000 Code RO 5359 * .ARM.Collect$$$$00000000 mc_w.l(entry.o)
17 0x080001ac 0x00000004 Code RO 5622 .ARM.Collect$$$$00000001 mc_w.l(entry2.o)
18 0x080001b0 0x00000004 Code RO 5625 .ARM.Collect$$$$00000004 mc_w.l(entry5.o)
19 0x080001b4 0x00000000 Code RO 5627 .ARM.Collect$$$$00000008 mc_w.l(entry7b.o)
20 0x080001b4 0x00000000 Code RO 5629 .ARM.Collect$$$$0000000A mc_w.l(entry8b.o)
21 /*...此處省略大部分內容*/
22 0x08000948 0x0000000e Code RO 4910 i.USART_GetFlagStatus stm32f4xx_usart.o
23 0x08000956 0x00000002 PAD
24 0x08000958 0x000000bc Code RO 4914 i.USART_Init stm32f4xx_usart.o
25 0x08000a14 0x00000008 Code RO 4924 i.USART_SendData stm32f4xx_usart.o
26 0x08000a1c 0x00000002 Code RO 5206 i.UsageFault_Handler stm32f4xx_it.o
27 0x08000a1e 0x00000002 PAD
28 0x08000a20 0x00000010 Code RO 5363 i.__0printf$bare mc_w.l(printfb.o)
29 0x08000a30 0x0000000e Code RO 5664 i.__scatterload_copy mc_w.l(handlers.o)
30 0x08000a3e 0x00000002 Code RO 5665 i.__scatterload_null mc_w.l(handlers.o)
31 0x08000a40 0x0000000e Code RO 5666 i.__scatterload_zeroinit mc_w.l(handlers.o)
32 0x08000a4e 0x00000022 Code RO 5370 i._printf_core mc_w.l(printfb.o)
33 0x08000a70 0x00000024 Code RO 5275 i.fputc bsp_debug_usart.o
34 0x08000a94 0x00000088 Code RO 5161 i.main main.o
35 0x08000b1c 0x00000020 Data RO 5662 Region$$Table anon$$obj.o
36
這一段是某工程的ROM存儲器分布映像,在STM32芯片中,ROM區域的內容就是指存儲到內部FLASH的代碼。
1. 程序ROM的加載與執行空間
上述說明中有兩段分別以"Load Region LR_ROM1"及"Execution Region ER_IROM1"開頭的內容,它們分別描述程序的加載及執行空間。在芯片剛上電運行時,會加載程序及數據,例如它會從程序的存儲區域加載到程序的執行區域,還把一些已初始化的全局變量從ROM復制到RAM空間,以便程序運行時可以修改變量的內容。加載完成后,程序開始從執行區域開始執行。
在上面map文件的描述中,我們了解到加載及執行空間的基地址(Base)都是0x08000000,它正好是STM32內部FLASH的首地址,即STM32的程序存儲空間就直接是執行空間;它們的大小(Size)分別為0x00000b50及0x00000b3c,執行空間的ROM比較小的原因就是因為部分RW-data類型的變量被拷貝到RAM空間了;它們的最大空間(Max)均為0x00100000,即1M字節,它指的是內部FLASH的最大空間。
計算程序占用的空間時,需要使用加載區域的大小進行計算,本例子中應用程序使用的內部FLASH是從0x08000000至(0x08000000+0x00000b50)地址的空間區域。
2. ROM空間分布表
在加載及執行空間總體描述之后,緊接着一個ROM詳細地址分布表,它列出了工程中的各個段(如函數、常量數據)所在的地址Base Addr及占用的空間Size,列表中的Type說明了該段的類型,CODE表示代碼,DATA表示數據,而PAD表示段之間的填充區域,它是無效的內容,PAD區域往往是為了解決地址對齊的問題。
觀察表中的最后一項,它的基地址是0x08000b1c,大小為0x00000020,可知它占用的最高的地址空間為0x08000b3c,跟執行區域的最高地址0x00000b3c一樣,但它們比加載區域說明中的最高地址0x8000b50要小,所以我們以加載區域的大小為准。對比表 501的內部FLASH扇區地址分布表,可知僅使用扇區0就可以完全存儲本應用程序,所以從扇區1(地址0x08004000)后的存儲空間都可以作其它用途,使用這些存儲空間時不會篡改應用程序空間的數據。
50.4 操作內部FLASH的庫函數
為簡化編程,STM32標准庫提供了一些庫函數,它們封裝了對內部FLASH寫入數據操作寄存器的過程。
1. FLASH解鎖、上鎖函數
對內部FLASH解鎖、上鎖的函數見代碼清單 502。
代碼清單 502 FLASH解鎖、上鎖
1
2 #define FLASH_KEY1 ((uint32_t)0x45670123)
3 #define FLASH_KEY2 ((uint32_t)0xCDEF89AB)
4 /**
5 * @brief Unlocks the FLASH control register access
6 * @param None
7 * @retval None
8 */
9 void FLASH_Unlock(void)
10 {
11 if ((FLASH->CR & FLASH_CR_LOCK) != RESET) {
12 /* Authorize the FLASH Registers access */
13 FLASH->KEYR = FLASH_KEY1;
14 FLASH->KEYR = FLASH_KEY2;
15 }
16 }
17
18 /**
19 * @brief Locks the FLASH control register access
20 * @param None
21 * @retval None
22 */
23 void FLASH_Lock(void)
24 {
25 /* Set the LOCK Bit to lock the FLASH Registers access */
26 FLASH->CR |= FLASH_CR_LOCK;
27 }
解鎖的時候,它對FLASH_KEYR寄存器寫入兩個解鎖參數,上鎖的時候,對FLASH_CR寄存器的FLASH_CR_LOCK位置1。
2. 設置操作位數及擦除扇區
解鎖后擦除扇區時可調用FLASH_EraseSector完成,見代碼清單 503。
代碼清單 503 擦除扇區
1 /**
2 * @brief Erases a specified FLASH Sector.
3 *
4 * @note If an erase and a program operations are requested simultaneously,
5 * the erase operation is performed before the program one.
6 *
7 * @param FLASH_Sector: The Sector number to be erased.
8 *
9 * @note For STM32F42xxx/43xxx devices this parameter can be a value between
10 * FLASH_Sector_0 and FLASH_Sector_23.
11 *
12 * @param VoltageRange: The device voltage range which defines the erase parallelism.
13 * This parameter can be one of the following values:
14 * @arg VoltageRange_1: when the device voltage range is 1.8V to 2.1V,
15 * the operation will be done by byte (8-bit)
16 * @arg VoltageRange_2: when the device voltage range is 2.1V to 2.7V,
17 * the operation will be done by half word (16-bit)
18 * @arg VoltageRange_3: when the device voltage range is 2.7V to 3.6V,
19 * the operation will be done by word (32-bit)
20 * @arg VoltageRange_4: when the device voltage range is 2.7V to 3.6V + External Vpp,
21 * the operation will be done by double word (64-bit)
22 *
23 * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PROGRAM,
24 * FLASH_ERROR_WRP, FLASH_ERROR_OPERATION or FLASH_COMPLETE.
25 */
26 FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange)
27 {
28 uint32_t tmp_psize = 0x0;
29 FLASH_Status status = FLASH_COMPLETE;
30
31 /* Check the parameters */
32 assert_param(IS_FLASH_SECTOR(FLASH_Sector));
33 assert_param(IS_VOLTAGERANGE(VoltageRange));
34
35 if (VoltageRange == VoltageRange_1) {
36 tmp_psize = FLASH_PSIZE_BYTE;
37 } else if (VoltageRange == VoltageRange_2) {
38 tmp_psize = FLASH_PSIZE_HALF_WORD;
39 } else if (VoltageRange == VoltageRange_3) {
40 tmp_psize = FLASH_PSIZE_WORD;
41 } else {
42 tmp_psize = FLASH_PSIZE_DOUBLE_WORD;
43 }
44 /* Wait for last operation to be completed */
45 status = FLASH_WaitForLastOperation();
46
47 if (status == FLASH_COMPLETE) {
48 /* if the previous operation is completed, proceed to erase the sector */
49 FLASH->CR &= CR_PSIZE_MASK;
50 FLASH->CR |= tmp_psize;
51 FLASH->CR &= SECTOR_MASK;
52 FLASH->CR |= FLASH_CR_SER | FLASH_Sector;
53 FLASH->CR |= FLASH_CR_STRT;
54
55 /* Wait for last operation to be completed */
56 status = FLASH_WaitForLastOperation();
57
58 /* if the erase operation is completed, disable the SER Bit */
59 FLASH->CR &= (~FLASH_CR_SER);
60 FLASH->CR &= SECTOR_MASK;
61 }
62 /* Return the Erase Status */
63 return status;
64 }
本函數包含兩個輸入參數,分別是要擦除的扇區號和工作電壓范圍,選擇不同電壓時實質是選擇不同的數據操作位數,參數中可輸入的宏在注釋里已經給出。函數根據輸入參數配置PSIZE位,然后擦除扇區,擦除扇區的時候需要等待一段時間,它使用FLASH_WaitForLastOperation等待,擦除完成的時候才會退出FLASH_EraseSector函數。
3. 寫入數據
對內部FLASH寫入數據不像對SDRAM操作那樣直接指針操作就完成了,還要設置一系列的寄存器,利用FLASH_ProgramWord、FLASH_ProgramHalfWord和FLASH_ProgramByte函數可按字、半字及字節單位寫入數據,見代碼清單 504。
代碼清單 504 寫入數據
1
2 /**
3 * @brief Programs a word (32-bit) at a specified address.
4 *
5 * @note This function must be used when the device voltage range is from 2.7V to 3.6V.
6 *
7 * @note If an erase and a program operations are requested simultaneously,
8 * the erase operation is performed before the program one.
9 *
10 * @param Address: specifies the address to be programmed.
11 * This parameter can be any address in Program memory zone or in OTP zone.
12 * @param Data: specifies the data to be programmed.
13 * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PROGRAM,
14 * FLASH_ERROR_WRP, FLASH_ERROR_OPERATION or FLASH_COMPLETE.
15 */
16 FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
17 {
18 FLASH_Status status = FLASH_COMPLETE;
19
20 /* Check the parameters */
21 assert_param(IS_FLASH_ADDRESS(Address));
22
23 /* Wait for last operation to be completed */
24 status = FLASH_WaitForLastOperation();
25
26 if (status == FLASH_COMPLETE) {
27/* if the previous operation is completed, proceed to program the new data */
28 FLASH->CR &= CR_PSIZE_MASK;
29 FLASH->CR |= FLASH_PSIZE_WORD;
30 FLASH->CR |= FLASH_CR_PG;
31
32 *(__IO uint32_t*)Address = Data;
33
34 /* Wait for last operation to be completed */
35 status = FLASH_WaitForLastOperation();
36
37 /* if the program operation is completed, disable the PG Bit */
38 FLASH->CR &= (~FLASH_CR_PG);
39 }
40 /* Return the Program Status */
41 return status;
42 }
看函數代碼可了解到,使用指針進行賦值操作前設置了數據操作寬度,並設置了PG寄存器位,在賦值操作后,調用了FLASH_WaitForLastOperation函數等待寫操作完畢。HalfWord和Byte操作寬度的函數執行過程類似。
50.5 實驗:讀寫內部FLASH
在本小節中我們以實例講解如何使用內部FLASH存儲數據。
50.5.1 硬件設計
本實驗僅操作了STM32芯片內部的FLASH空間,無需額外的硬件。
50.5.2 軟件設計
本小節講解的是"內部FLASH編程"實驗,請打開配套的代碼工程閱讀理解。為了方便展示及移植,我們把操作內部FLASH相關的代碼都編寫到"bsp_internalFlash.c"及"bsp_internalFlash.h"文件中,這些文件是我們自己編寫的,不屬於標准庫的內容,可根據您的喜好命名文件。
1. 程序設計要點
(7) 對內部FLASH解鎖;
(8) 找出空閑扇區,擦除目標扇區;
(9) 進行讀寫測試。
2. 代碼分析
硬件定義
讀寫內部FLASH不需要用到任何外部硬件,不過在擦寫時常常需要知道各個扇區的基地址,我們把這些基地址定義到bsp_internalFlash.h文件中,見代碼清單 441。
代碼清單 505 各個扇區的基地址(bsp_internalFlash.h文件)
1
2 /* 各個扇區的基地址 */
3 #define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000)
4 #define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000)
5 #define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000)
6 #define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000)
7 #define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000)
8 #define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000)
9 #define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000)
10 #define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000)
11 #define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000)
12 #define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000)
13 #define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000)
14 #define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000)
15
16 #define ADDR_FLASH_SECTOR_12 ((uint32_t)0x08100000)
17 #define ADDR_FLASH_SECTOR_13 ((uint32_t)0x08104000)
18 #define ADDR_FLASH_SECTOR_14 ((uint32_t)0x08108000)
19 #define ADDR_FLASH_SECTOR_15 ((uint32_t)0x0810C000)
20 #define ADDR_FLASH_SECTOR_16 ((uint32_t)0x08110000)
21 #define ADDR_FLASH_SECTOR_17 ((uint32_t)0x08120000)
22 #define ADDR_FLASH_SECTOR_18 ((uint32_t)0x08140000)
23 #define ADDR_FLASH_SECTOR_19 ((uint32_t)0x08160000)
24 #define ADDR_FLASH_SECTOR_20 ((uint32_t)0x08180000)
25 #define ADDR_FLASH_SECTOR_21 ((uint32_t)0x081A0000)
26 #define ADDR_FLASH_SECTOR_22 ((uint32_t)0x081C0000)
27 #define ADDR_FLASH_SECTOR_23 ((uint32_t)0x081E0000)
這些宏跟表 501中的地址說明一致。
根據扇區地址計算SNB寄存器的值
在擦除操作時,需要向FLASH控制寄存器FLASH_CR的SNB位寫入要擦除的扇區號,固件庫把各個扇區對應的寄存器值使用宏定義到了stm32f4xx_flash.h文件。為了便於使用,我們自定義了一個GetSector函數,根據輸入的內部FLASH地址,找出其所在的扇區,並返回該扇區對應的SNB位寄存器值,見代碼清單 442。
代碼清單 506 寫入到SNB寄存器位的值(stm32f4xx_flash.h及bsp_internalFlash.c文件)
1 /*固件庫定義的用於扇區寫入到SNB寄存器位的宏(stm32f4xx_flash.h文件)*/
2 #define FLASH_Sector_0 ((uint16_t)0x0000)
3 #define FLASH_Sector_1 ((uint16_t)0x0008)
4 #define FLASH_Sector_2 ((uint16_t)0x0010)
5 #define FLASH_Sector_3 ((uint16_t)0x0018)
6 #define FLASH_Sector_4 ((uint16_t)0x0020)
7 #define FLASH_Sector_5 ((uint16_t)0x0028)
8 #define FLASH_Sector_6 ((uint16_t)0x0030)
9 #define FLASH_Sector_7 ((uint16_t)0x0038)
10 #define FLASH_Sector_8 ((uint16_t)0x0040)
11 #define FLASH_Sector_9 ((uint16_t)0x0048)
12 #define FLASH_Sector_10 ((uint16_t)0x0050)
13 #define FLASH_Sector_11 ((uint16_t)0x0058)
14 #define FLASH_Sector_12 ((uint16_t)0x0080)
15 #define FLASH_Sector_13 ((uint16_t)0x0088)
16 #define FLASH_Sector_14 ((uint16_t)0x0090)
17 #define FLASH_Sector_15 ((uint16_t)0x0098)
18 #define FLASH_Sector_16 ((uint16_t)0x00A0)
19 #define FLASH_Sector_17 ((uint16_t)0x00A8)
20 #define FLASH_Sector_18 ((uint16_t)0x00B0)
21 #define FLASH_Sector_19 ((uint16_t)0x00B8)
22 #define FLASH_Sector_20 ((uint16_t)0x00C0)
23 #define FLASH_Sector_21 ((uint16_t)0x00C8)
24 #define FLASH_Sector_22 ((uint16_t)0x00D0)
25 #define FLASH_Sector_23 ((uint16_t)0x00D8)
26
27 /*定義在bsp_internalFlash.c文件中的函數*/
28 /**
29 * @brief 根據輸入的地址給出它所在的sector
30 * 例如:
31 uwStartSector = GetSector(FLASH_USER_START_ADDR);
32 uwEndSector = GetSector(FLASH_USER_END_ADDR);
33 * @param Address:地址
34 * @retval 地址所在的sector
35 */
36 static uint32_t GetSector(uint32_t Address)
37 {
38 uint32_t sector = 0;
39
40 if ((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)) {
41 sector = FLASH_Sector_0;
42 } else if ((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)) {
43 sector = FLASH_Sector_1;
44 }
45
46 /*此處省略扇區2-扇區21的內容*/
47
48 else if ((Address < ADDR_FLASH_SECTOR_23) && (Address >= ADDR_FLASH_SECTOR_22)) {
49 sector = FLASH_Sector_22;
50 } else { /*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_23))*/
51 sector = FLASH_Sector_23;
52 }
53 return sector;
54 }
代碼中固件庫定義的宏FLASH_Sector_0-23對應的值是跟寄存器說明一致的,見圖 503。
圖 503 FLASH_CR寄存器的SNB位的值
GetSector函數根據輸入的地址與各個扇區的基地址進行比較,找出它所在的扇區,並使用固件庫中的宏,返回扇區對應的SNB值。
讀寫內部FLASH
一切准備就緒,可以開始對內部FLASH進行擦寫,這個過程不需要初始化任何外設,只要按解鎖、擦除及寫入的流程走就可以了,見代碼清單 443。
代碼清單 507 對內部地FLASH進行讀寫測試(bsp_internalFlash.c文件)
1
2 /*准備寫入的測試數據*/
3 #define DATA_32 ((uint32_t)0x00000000)
4 /* 要擦除內部FLASH的起始地址 */
5 #define FLASH_USER_START_ADDR ADDR_FLASH_SECTOR_8
6 /* 要擦除內部FLASH的結束地址 */
7 #define FLASH_USER_END_ADDR ADDR_FLASH_SECTOR_12
8
9 /**
10 * @brief InternalFlash_Test,對內部FLASH進行讀寫測試
11 * @param None
12 * @retval None
13 */
14 int InternalFlash_Test(void)
15 {
16 /*要擦除的起始扇區(包含)及結束扇區(不包含),如8-12,表示擦除8、9、10、11扇區*/
17 uint32_t uwStartSector = 0;
18 uint32_t uwEndSector = 0;
19
20 uint32_t uwAddress = 0;
21 uint32_t uwSectorCounter = 0;
22
23 __IO uint32_t uwData32 = 0;
24 __IO uint32_t uwMemoryProgramStatus = 0;
25
26 /* FLASH 解鎖 ********************************/
27 /* 使能訪問FLASH控制寄存器 */
28 FLASH_Unlock();
29
30 /* 擦除用戶區域 (用戶區域指程序本身沒有使用的空間,可以自定義)**/
31 /* 清除各種FLASH的標志位 */
32 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
33 FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
34
35
36 uwStartSector = GetSector(FLASH_USER_START_ADDR);
37 uwEndSector = GetSector(FLASH_USER_END_ADDR);
38
39 /* 開始擦除操作 */
40 uwSectorCounter = uwStartSector;
41 while (uwSectorCounter <= uwEndSector) {
42 /* VoltageRange_3 以"字"的大小進行操作 */
43 if (FLASH_EraseSector(uwSectorCounter, VoltageRange_3) != FLASH_COMPLETE) {
44 /*擦除出錯,返回,實際應用中可加入處理 */
45 return -1;
46 }
47 /* 計數器指向下一個扇區 */
48 if (uwSectorCounter == FLASH_Sector_11) {
49 uwSectorCounter += 40;
50 } else {
51 uwSectorCounter += 8;
52 }
53 }
54
55 /* 以"字"的大小為單位寫入數據 ********************************/
56 uwAddress = FLASH_USER_START_ADDR;
57
58 while (uwAddress < FLASH_USER_END_ADDR) {
59 if (FLASH_ProgramWord(uwAddress, DATA_32) == FLASH_COMPLETE) {
60 uwAddress = uwAddress + 4;
61 } else {
62 /*寫入出錯,返回,實際應用中可加入處理 */
63 return -1;
64 }
65 }
66
67
68 /* 給FLASH上鎖,防止內容被篡改*/
69 FLASH_Lock();
70
71
72 /* 從FLASH中讀取出數據進行校驗***************************************/
73 /* MemoryProgramStatus = 0: 寫入的數據正確
74 MemoryProgramStatus != 0: 寫入的數據錯誤,其值為錯誤的個數 */
75 uwAddress = FLASH_USER_START_ADDR;
76 uwMemoryProgramStatus = 0;
77
78 while (uwAddress < FLASH_USER_END_ADDR) {
79 uwData32 = *(__IO uint32_t*)uwAddress;
80
81 if (uwData32 != DATA_32) {
82 uwMemoryProgramStatus++;
83 }
84
85 uwAddress = uwAddress + 4;
86 }
87 /* 數據校驗不正確 */
88 if (uwMemoryProgramStatus) {
89 return -1;
90 } else { /*數據校驗正確*/
91 return 0;
92 }
93 }
94
該函數的執行過程如下:
(1) 調用FLASH_Unlock解鎖;
(2) 調用FLASH_ClearFlag清除各種標志位;
(3) 調用GetSector根據起始地址及結束地址計算要擦除的扇區;
(4) 調用FLASH_EraseSector擦除扇區,擦除時按字為單位進行操作;
(5) 調用FLASH_ProgramWord函數向起始地址至結束地址的存儲區域都寫入數值"DATA_32";
(6) 調用FLASH_Lock上鎖;
(7) 使用指針讀取數據內容並校驗。
main函數
最后我們來看看main函數的執行流程,見代碼清單 444。
代碼清單 508 main函數(main.c文件)
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8 /*初始化USART,配置模式為 115200 8-N-1*/
9 Debug_USART_Config();
10 LED_GPIO_Config();
11
12 LED_BLUE;
13 /*調用printf函數,因為重定向了fputc,printf的內容會輸出到串口*/
14 printf("this is a usart printf demo. \r\n");
15 printf("\r\n歡迎使用秉火 STM32 F429 開發板。\r\n");
16 printf("正在進行讀寫內部FLASH實驗,請耐心等待\r\n");
17
18 if (InternalFlash_Test()==0) {
19 LED_GREEN;
20 printf("讀寫內部FLASH測試成功\r\n");
21
22 } else {
23 printf("讀寫內部FLASH測試失敗\r\n");
24 LED_RED;
25 }
26 }
main函數中初始化了用於指示調試信息的LED及串口后,直接調用了InternalFlash_Test函數,進行讀寫測試並根據測試結果輸出調試信息。
50.5.3 下載驗證
用USB線連接開發板"USB TO UART"接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到擦寫內部FLASH的調試信息。
50.6 每課一問
5. 嘗試擦除應用程序所在的內部FLASH扇區,觀察實驗現象。
6. 使用C語言的"const uint8_t value;"和"volatile const uint8_t value"語句定義的變量value有什么區別?若定義后使用內部FLASH操作擦除value的存儲空間,再讀取value的值,哪種定義能正常讀取?