第50章 讀寫內部FLASH—零死角玩轉STM32-F429系列


 

第50章     讀寫內部FLASH

全套200集視頻教程和1000PDF教程請到秉火論壇下載: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 FLASH2M FLASH都是指這個區域的大小。主存儲器分為兩塊,共2MB,每塊內分12個扇區,其中包含416KB扇區、164KB扇區和7128KB的扇區。如我們實驗板中使用的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_OPTCRDB1M位,可以切換這兩種格式,切換成雙塊模式后,扇區8-11的空間被轉移到扇區12-19中,扇區細分了,總容量不變。

注意如果您使用的是STM32F40x系列的芯片,它沒有雙塊存儲格式,也不存在扇區12-23,僅STM32F42x/43x系列產品才支持扇區12-23

    系統存儲區

系統存儲區是用戶不能訪問的區域,它在芯片出廠時已經固化了啟動代碼,它負責實現串口、USB以及CANISP燒錄功能。

    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)分別為0x00000b500x00000b3c,執行空間的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_ProgramWordFLASH_ProgramHalfWordFLASH_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函數等待寫操作完畢。HalfWordByte操作寬度的函數執行過程類似。

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_CRSNB位寫入要擦除的扇區號,固件庫把各個扇區對應的寄存器值使用宏定義到了stm32f4xx_flash.h文件。為了便於使用,我們自定義了一個GetSector函數,根據輸入的內部FLASH地址,找出其所在的扇區,並返回該扇區對應的SNB寄存器值,見代碼清單 442

代碼清單 506 寫入到SNB寄存器位的值(stm32f4xx_flash.hbsp_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,表示擦除891011扇區*/

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函數,因為重定向了fputcprintf的內容會輸出到串口*/ 
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的值,哪種定義能正常讀取?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM