【STM32H7教程】第70章 STM32H7的內部Flash基礎知識和HAL庫API
- 2020 年 3 月 11 日
- 筆記
完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第70章 STM32H7的內部Flash基礎知識和HAL庫API
本章節為大家講解內部Flash的基礎知識和對應的HAL庫API。
70.1 初學者重要提示
70.2 內部Flash基礎知識
70.3 內部Flash的HAL庫用法
70.4 源文件stm32h7xx_hal_flash.c
70.5 總結
70.1 初學者重要提示
- 本章2.5小節裡面的Flash三級讀保護是重點,務必要掌握明白。
- STM32H743XI有兩個獨立的BANK,一個BANK的編程和擦除操作對另一個BANK沒有任何影響。但是如果用戶應用程式和要擦寫的Flash扇區在同一個BANK,在執行擦寫操作時,用戶應用程式將停止運行,包括中斷服務程式。
- STM32H7的兩個Flash BANK是256bit頻寬,CPU訪問是採用的兩個64bit AXI匯流排。
- HAL庫的內部Flash編程函數HAL_FLASH_Program固定編寫32位元組數據。
70.2 內部Flash基礎知識
70.2.1 內部Flash的硬體框圖
認識一個外設,最好的方式就是看它的框圖,方便我們快速的了解內部Flash的基本功能,然後再看手冊了解細節。

通過這個框圖,我們可以得到如下資訊:
- sys_ck時鐘輸入
D1域匯流排時鐘。
- po_rst輸入
Power on reset 上電複位。
- d1_rst輸入
D1域系統複位。
- flash_it輸出
flash中斷請求輸出。
STM32H7的兩個Flash BANK是獨立的,讀寫和擦除互補影響,256bit頻寬,CPU訪問是採用的兩個64bit AXI匯流排。
70.2.2 內部Flash框架
關於內部Flash的框架,了解以下幾個知識點即可:
- 256bit為單位,即32位元組,並且每個單位配10bit的ECC校驗位。正是這個原因要求大家對Flash進行編程時,必須以32位元組為單位。
- 兩個獨立的BANK,每個BANK有1MB容量。並且每個BANK的扇區大小固定為128KB,即8個扇區。

BANK1的地址範圍:0x0800 0000到0x080F FFFF。
BANK2的地址範圍:0x0810 0000到0x081F FFFF。
70.2.3 內部Flash讀操作
STM32H7的內部Flash讀操作跟內部RAM的讀操作是一樣的,支援64bit,32bit,16bt和8bit,使用比較簡單。這裡我們重點普及一個知識點,H7的內部Flash在不同主頻下需要做的延遲參數:

對於上面的表格,大家可以看到,當延遲等待設置為0的時候,即無等待,單周期訪問,速度可以做到70MHz。增加1個Flash周期後,訪問速度可以做到140MHz。當增加到3個或4個Flash周期後,最高速度可以做到225MHz。
了解了這個知識點後,再來看下面的時序,非常具有參考意義:
註:ACLK、ARADDR、ARVALID、RDATA、RVALID 和RLAST是AXI匯流排訊號。Flash讀和Flash數據是 Flash 介面訊號。

關於這個時序要要認識到以下幾點:
- AXI匯流排發起讀取訊號後,Flash端等待了3個時鐘周期(注意延遲三個周期,支援的Flash速度),之後連續讀取了4個64bit數據。
- 由於AXI匯流排是64bit的,所以1次讀取就可以讀出64bit數據,連續讀取4次後,就是256bit,即Flash介面的一組數據,因為H7的Flash介面頻寬是256bit的。
- 如果不開Flash Cache的情況下,連續讀可以提升性能。
下面是連續讀取8個64bit數據的時序圖,跟連續讀取4個64bit數據基本是一樣的,只是多讀取了4組數據。

70.2.4 內部Flash寫入和擦除操作
最重要的知識點放在開頭說:STM32H7內部Flash的寫操作地址必須是32位元組對齊(此地址對32求餘數為0),寫入的數據量也必須是32位元組整數倍,不足32位元組整數倍,補0也要是整數倍。
這裡我們重點了解Flash的寫入和擦除流程。Flash的寫入扇區流程如下:
- 先保證這塊扇區空間之前已經擦除過了。
- 解鎖Flash,通過HAL庫的函數HAL_FLASH_Unlock實現。
- 檢查是否防寫,使能Flash可以編程,然後對其進行編程操作,編程完畢後,等待編程完成,然後禁止Flash編程位。具體操作可以通過HAL庫的函數HAL_FLASH_Program實現。
Flash的擦除流程如下:
- 解鎖Flash,通過HAL庫的函數HAL_FLASH_Unlock實現。
- 如果是BANK1或者BANK2需要擦除,調用函數FLASH_MassErase,然後等待擦除完成,完成之後關閉BANK1和BANK2的擦除請求位BER1/BER2
- 如果是扇區擦除,調用函數FLASH_Erase_Sector,然後等待擦除完成,完成之後關閉扇區的擦除請求位SER。
70.2.5 內部Flash讀保護
內部Flash支援三級讀保護RDP(read out protection)。
- Level 0(無保護)
默認設置,所有讀寫和擦除操作都可以正常支援。
- Level 1 (Flash連接保護)
- 可以防止連接調試器時讀取Flash內容,或者RAM中存有惡意獲取程式碼,也是禁止的。因此只要調試器連接晶片,或者從內部RAM啟動運行程式碼,都是禁止訪問內部Flash的。
- 如果沒有檢測到從內部RAM啟動和系統bootloader啟動且沒有連接調試器,對用戶Flash的讀寫和擦除操作都是允許的,並且其它安全存儲區也是可以訪問的。否則是禁止訪問的,一旦檢測到對Flash的讀請求,將產生匯流排錯誤。
- 如果將Level 1切換到Level 0時,用戶Flash區和安全區域將被擦除。
- Level 2(設備保護和自舉保護)
- 所有調試特性被關閉。
- 禁止從RAM啟動。
- 除了選項位元組裡面的SWAP位可以配置,其它位都無法更改。
- 禁止了調試功能,且禁止了從RAM和系統bootloader啟動,用戶Flash區是可以執行讀寫和擦除操作的,訪問其它安全存儲區也是可以的。
特別注意:Level2修改是永久性的,一旦配置為Level2將不再支援被修改。
如果大家要設置讀保護的話,使用HAL的API可以設置,也可以使用STM32CubeProg設置:

70.2.6 內部Flash選項位元組
Flash選項位元組主要用於boot地址設置,安全保護,Flash扇區保護等,涉及到的選項比較多。如果大家打算了解這一部分的話,使用STM32CubeProg進行設置即可,也比較方便。

70.2.7 內部Flash的ECC校驗
這裡先說下為什麼內部Flash要帶ECC校驗,因為隨著晶片的製造製程水平越高,帶電粒子產生的位翻轉就越多,此時的ECC是必須要有的,一般可以糾正1-2個bit,安全等級高的Flash類存儲器和RAM類都是必須要帶ECC的。
對於STM32H7帶的ECC校驗,一般不需要用戶去管理。
- ECC相關知識
關於ECC方面的知識,專門整理了一個帖子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86777
70.3 內部Flash的HAL庫用法
70.3.1 內部Flash結構體FLASH_TypeDef
內部Flash相關的暫存器是通過HAL庫中的結構體FLASH_TypeDef定義的,在stm32h743xx.h中可以找到這個類型定義:
typedef struct { __IO uint32_t ACR; __IO uint32_t KEYR1; __IO uint32_t OPTKEYR; __IO uint32_t CR1; __IO uint32_t SR1; __IO uint32_t CCR1; __IO uint32_t OPTCR; __IO uint32_t OPTSR_CUR; __IO uint32_t OPTSR_PRG; __IO uint32_t OPTCCR; __IO uint32_t PRAR_CUR1; __IO uint32_t PRAR_PRG1; __IO uint32_t SCAR_CUR1; __IO uint32_t SCAR_PRG1; __IO uint32_t WPSN_CUR1; __IO uint32_t WPSN_PRG1; __IO uint32_t BOOT_CUR; __IO uint32_t BOOT_PRG; uint32_t RESERVED0[2]; __IO uint32_t CRCCR1; __IO uint32_t CRCSADD1; __IO uint32_t CRCEADD1; __IO uint32_t CRCDATA; __IO uint32_t ECC_FA1; uint32_t RESERVED1[40]; __IO uint32_t KEYR2; uint32_t RESERVED2; __IO uint32_t CR2; __IO uint32_t SR2; __IO uint32_t CCR2; uint32_t RESERVED3[4]; __IO uint32_t PRAR_CUR2; __IO uint32_t PRAR_PRG2; __IO uint32_t SCAR_CUR2; __IO uint32_t SCAR_PRG2; __IO uint32_t WPSN_CUR2; __IO uint32_t WPSN_PRG2; uint32_t RESERVED4[4]; __IO uint32_t CRCCR2; __IO uint32_t CRCSADD2; __IO uint32_t CRCEADD2; __IO uint32_t CRCDATA2; __IO uint32_t ECC_FA2; } FLASH_TypeDef;
這個結構體的成員名稱和排列次序和CPU的暫存器是一 一對應的。
__IO表示volatile, 這是標準C語言中的一個修飾字,表示這個變數是非易失性的,編譯器不要將其優化掉。core_m7.h 文件定義了這個宏:
#define __O volatile /*!< Defines 'write only' permissions */ #define __IO volatile /*!< Defines 'read / write' permissions */
下面我們看下Flash的定義,在stm32h743xx.h文件。
#define PERIPH_BASE (0x40000000UL) #define D1_AHB1PERIPH_BASE (PERIPH_BASE + 0x12000000UL) #define FLASH_R_BASE (D1_AHB1PERIPH_BASE + 0x2000UL) #define FLASH ((FLASH_TypeDef *) FLASH_R_BASE) <----- 展開這個宏,(FLASH_TypeDef *)0x52002000
我們訪問Flash的CR1暫存器可以採用這種形式:FLASH->CR1 = 0。
70.3.2 內部Flash擦除結構體FLASH_EraseInitTypeDef
下面是做內部Flash擦除的結構體,用到的地方比較多:
typedef struct { uint32_t TypeErase; uint32_t Banks; uint32_t Sector; uint32_t NbSectors; uint32_t VoltageRange; } FLASH_EraseInitTypeDef;
下面將結構體成員逐一做個說明:
- TypeErase
用於選擇BANK擦除還是扇區擦除,H743有兩個BANK,每個BANK有個8個扇區,每個扇區128KB。具體支援的參數如下:
#define FLASH_TYPEERASE_SECTORS 0x00U /* 扇區方式擦除 */ #define FLASH_TYPEERASE_MASSERASE 0x01U /* BANK方式擦除 */
- Banks
用於選擇要擦除的BANK,或者兩個BANK都選擇:
#define FLASH_BANK_1 0x01U /* Bank 1 */ #define FLASH_BANK_2 0x02U /* Bank 2 */ #define FLASH_BANK_BOTH (FLASH_BANK_1 | FLASH_BANK_2) /* Bank1 和 Bank2 */
- Sector
用於選擇要擦除的扇區:
#define FLASH_SECTOR_0 0U /* Sector Number 0 */ #define FLASH_SECTOR_1 1U /* Sector Number 1 */ #define FLASH_SECTOR_2 2U /* Sector Number 2 */ #define FLASH_SECTOR_3 3U /* Sector Number 3 */ #define FLASH_SECTOR_4 4U /* Sector Number 4 */ #define FLASH_SECTOR_5 5U /* Sector Number 5 */ #define FLASH_SECTOR_6 6U /* Sector Number 6 */ #define FLASH_SECTOR_7 7U /* Sector Number 7 */
- NbSectors
用於設置要擦除的扇區個數,對於STM32H743來說,範圍1到8。
- VoltageRange
用於設置編程的並行位數,電壓不同,位數不同:
#define FLASH_VOLTAGE_RANGE_1 0x00000000U /* Flash program/erase by 8 bits */ #define FLASH_VOLTAGE_RANGE_2 FLASH_CR_PSIZE_0 /* Flash program/erase by 16 bits */ #define FLASH_VOLTAGE_RANGE_3 FLASH_CR_PSIZE_1 /* Flash program/erase by 32 bits */ #define FLASH_VOLTAGE_RANGE_4 FLASH_CR_PSIZE /* Flash program/erase by 64 bits */
70.3.3 內部Flash的操作總結
使用方法由HAL庫提供:
- Flash編程函數操作流程
- Flash解鎖函數HAL_FLASH_Unlock。
- Flash查詢方式編程HAL_FLASH_Program。
- Flash中斷方式編程HAL_FLASH_Program_IT。
- Flash上鎖函數HAL_FLASH_Lock。
- 選項位元組編程流程
- 選項位元組解鎖函數HAL_FLASH_OB_Unlock。
- 選項位元組載入函數HAL_FLASH_OB_Launch。
- 選項位元組編程函數HAL_FLASHEx_OBProgram。
- 選項位元組加鎖函數HAL_FLASH_OB_Lock。
- Flash擦除流程
- Flash解鎖函數HAL_FLASH_Unlock。
- Flash查詢方式擦除HAL_FLASHEx_Erase。
- Flash中斷方式擦除HAL_FLASHEx_Erase_IT。
- Flash上鎖函數HAL_FLASH_Lock。
70.4 內部Flash源文件stm32h7xx_hal_flash.c
此文件涉及到的函數較多,這裡把幾個常用的函數做個說明:
- HAL_FLASH_Unlock
- HAL_FLASH_Lock
- HAL_FLASHEx_Erase
- HAL_FLASH_Program
70.4.1 函數HAL_FLASH_Lock
函數原型:
HAL_StatusTypeDef HAL_FLASH_Lock(void) { /* 設置FLASH Bank1控制暫存器Lock位,即禁止訪問 */ SET_BIT(FLASH->CR1, FLASH_CR_LOCK); /* 驗證Flash Bank1是否已經被鎖住 */ if (READ_BIT(FLASH->CR1, FLASH_CR_LOCK) == 0U) { return HAL_ERROR; } /* 設置FLASH Bank2控制暫存器Lock位,即禁止訪問 */ SET_BIT(FLASH->CR2, FLASH_CR_LOCK); /* 驗證Flash Bank2是否已經被鎖住 */ if (READ_BIT(FLASH->CR2, FLASH_CR_LOCK) == 0U) { return HAL_ERROR; } return HAL_OK; }
函數描述:
用於Flash加鎖,加鎖後將不能對Flash進行編程和擦除。
70.4.2 函數HAL_FLASH_Unlock
函數原型:
HAL_StatusTypeDef HAL_FLASH_Unlock(void) { if(READ_BIT(FLASH->CR1, FLASH_CR_LOCK) != 0U) { /* 允許訪問Flash Bank1 */ WRITE_REG(FLASH->KEYR1, FLASH_KEY1); WRITE_REG(FLASH->KEYR1, FLASH_KEY2); /* 驗證是否已經解鎖 */ if (READ_BIT(FLASH->CR1, FLASH_CR_LOCK) != 0U) { return HAL_ERROR; } } if(READ_BIT(FLASH->CR2, FLASH_CR_LOCK) != 0U) { /* 允許訪問Flash Bank2 */ WRITE_REG(FLASH->KEYR2, FLASH_KEY1); WRITE_REG(FLASH->KEYR2, FLASH_KEY2); /* 驗證是否已經解鎖 */ if (READ_BIT(FLASH->CR2, FLASH_CR_LOCK) != 0U) { return HAL_ERROR; } } return HAL_OK; }
函數描述:
此函數用於Flash解鎖,解鎖後可以對Flash進行擦除和編程。
70.4.3 函數HAL_FLASH_Program
函數原型:
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t FlashAddress, uint32_t DataAddress) { HAL_StatusTypeDef status; __IO uint32_t *dest_addr = (__IO uint32_t *)FlashAddress; __IO uint32_t *src_addr = (__IO uint32_t*)DataAddress; uint32_t bank; uint8_t row_index = FLASH_NB_32BITWORD_IN_FLASHWORD; /* 檢測參數 */ assert_param(IS_FLASH_TYPEPROGRAM(TypeProgram)); assert_param(IS_FLASH_PROGRAM_ADDRESS(FlashAddress)); /* 上鎖 */ __HAL_LOCK(&pFlash); #if defined (FLASH_OPTCR_PG_OTP) if((IS_FLASH_PROGRAM_ADDRESS_BANK1(FlashAddress)) || (IS_FLASH_PROGRAM_ADDRESS_OTP(FlashAddress))) #else if(IS_FLASH_PROGRAM_ADDRESS_BANK1(FlashAddress)) #endif { bank = FLASH_BANK_1; } else { bank = FLASH_BANK_2; } /* 錯誤標識,無錯誤 */ pFlash.ErrorCode = HAL_FLASH_ERROR_NONE; /* 等待操作完成 */ status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, bank); if(status == HAL_OK) { if(bank == FLASH_BANK_1) { #if defined (FLASH_OPTCR_PG_OTP) if (TypeProgram == FLASH_TYPEPROGRAM_OTPWORD) { /* 設置OTP暫存器的PG位,使能可以編程 */ SET_BIT(FLASH->OPTCR, FLASH_OPTCR_PG_OTP); } else #endif { /* 設置PG位,使能可編程 */ SET_BIT(FLASH->CR1, FLASH_CR_PG); } } else { /* 設置PG位 */ SET_BIT(FLASH->CR2, FLASH_CR_PG); } __ISB(); __DSB(); #if defined (FLASH_OPTCR_PG_OTP) if (TypeProgram == FLASH_TYPEPROGRAM_OTPWORD) { /* 編程OTP(16 bits) */ *(__IO uint16_t *)FlashAddress = *(__IO uint16_t*)DataAddress; } else #endif { /* 對Flash進行編程 */ do { *dest_addr = *src_addr; dest_addr++; src_addr++; row_index--; } while (row_index != 0U); } __ISB(); __DSB(); /* 等待最後一次操作完成 */ status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, bank); #if defined (FLASH_OPTCR_PG_OTP) if (TypeProgram == FLASH_TYPEPROGRAM_OTPWORD) { /* 如果編程操作完成,關閉OTP PG位 */ CLEAR_BIT(FLASH->OPTCR, FLASH_OPTCR_PG_OTP); } else #endif { if(bank == FLASH_BANK_1) { /* 如果操作完成,關閉PG位 */ CLEAR_BIT(FLASH->CR1, FLASH_CR_PG); } else { /* 如果操作完成,關閉PG位 */ CLEAR_BIT(FLASH->CR2, FLASH_CR_PG); } } } /* 解鎖 */ __HAL_UNLOCK(&pFlash); return status; }
函數描述:
此函數主要用於Flash編程,固定編程32個位元組數據。
函數參數:
- 第1個參數是要編程的Flash類型,支援兩種參數:
- FLASH_TYPEPROGRAM_FLASHWORD,用於晶片內部Flash編程。
- FLASH_TYPEPROGRAM_OTPWORD,用於晶片內部OTP存儲區編程,當前的H743並沒有這個區域,所以可以忽略。
- 第2個參數是要編程的Flash地址。
- 第3個參數是要編程到Flash的數據地址。
- 返回值,返回HAL_TIMEOUT表示超時,HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示串口忙,正在使用中。
注意事項:
- 第2個參數的Flash地址要是32位元組對齊的,即此地址對32求余等於0。
- 第3個參數務必要是32位元組的整數倍。
70.4.4 函數HAL_FLASHEx_Erase
函數原型:
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError) { HAL_StatusTypeDef status = HAL_OK; uint32_t sector_index; /* 檢查參數 */ assert_param(IS_FLASH_TYPEERASE(pEraseInit->TypeErase)); assert_param(IS_FLASH_BANK(pEraseInit->Banks)); /* 上鎖 */ __HAL_LOCK(&pFlash); /* 無錯誤 */ pFlash.ErrorCode = HAL_FLASH_ERROR_NONE; /* 等待BANK1的操作完成 */ if((pEraseInit->Banks & FLASH_BANK_1) == FLASH_BANK_1) { if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_1) != HAL_OK) { status = HAL_ERROR; } } /* 等待BANK2的操作完成 */ if((pEraseInit->Banks & FLASH_BANK_2) == FLASH_BANK_2) { if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_2) != HAL_OK) { status = HAL_ERROR; } } if(status == HAL_OK) { if(pEraseInit->TypeErase == FLASH_TYPEERASE_MASSERASE) { /* 整個BANK1或者BANK2擦除 */ FLASH_MassErase(pEraseInit->VoltageRange, pEraseInit->Banks); /* 等待操作完成 */ if((pEraseInit->Banks & FLASH_BANK_1) == FLASH_BANK_1) { if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_1) != HAL_OK) { status = HAL_ERROR; } /* 如果擦除完成,關閉BANK1的BER位 */ FLASH->CR1 &= (~FLASH_CR_BER); } if((pEraseInit->Banks & FLASH_BANK_2) == FLASH_BANK_2) { if(FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_2) != HAL_OK) { status = HAL_ERROR; } /* 如果擦除操作完成,關閉BANK2的BER位 */ FLASH->CR2 &= (~FLASH_CR_BER); } } else { /* 初始化扇區錯誤碼 */ *SectorError = 0xFFFFFFFFU; /* 扇區方式擦除 */ for(sector_index = pEraseInit->Sector; sector_index < (pEraseInit->NbSectors + pEraseInit->Sector); sector_index++) { FLASH_Erase_Sector(sector_index, pEraseInit->Banks, pEraseInit->VoltageRange); if((pEraseInit->Banks & FLASH_BANK_1) == FLASH_BANK_1) { /* 等待BANK1操作完成 */ status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_1); /* 如果擦除操作完成,禁止SER位 */ FLASH->CR1 &= (~(FLASH_CR_SER | FLASH_CR_SNB)); } if((pEraseInit->Banks & FLASH_BANK_2) == FLASH_BANK_2) { /* 等待BANK2操作完成 */ status = FLASH_WaitForLastOperation((uint32_t)FLASH_TIMEOUT_VALUE, FLASH_BANK_2); /* 如果擦除操作完成,禁止SER位 */ FLASH->CR2 &= (~(FLASH_CR_SER | FLASH_CR_SNB)); } if(status != HAL_OK) { /* 如果擦除出錯,停止後續擦除,返回擦除異常的扇區號 */ *SectorError = sector_index; break; } } } } /* 解鎖 */ __HAL_UNLOCK(&pFlash); return status; }
函數描述:
用於內部Flash的批量擦除(BANK擦除)和扇區方式擦除。
函數參數:
- 第1個參數是FLASH_EraseInitTypeDef類型結構體指針變數。
- 第2個參數是錯誤碼返回,返回0xFFFFFFFF表示全部正確,返回其它值是擦除過程中的錯誤扇區。
- 返回值,返回HAL_ERROR表示參數錯誤,HAL_OK表示發送成功,HAL_BUSY表示忙,正在使用中。
70.5 總結
本章節就為大家講解這麼多,對於內部Flash編程來說,掌握本章節的這些知識點就夠用了,更多的知識點可以看STM32H7的參考手冊學習。