STM32內部Flash讀寫操作
硬件平台:以STM32F103C8T6為例
固件庫SDK版本:HAL V1.8.3
1、內存映射介紹
(1)stm32的flash地址起始於0x0800 0000,結束地址是0x0800 0000加上芯片實際的flash大小,不同的芯片flash大小不同。
(2)RAM起始地址是0x2000 0000,結束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。
Flash中的內容一般用來存儲代碼和一些定義為const的數據,斷電不丟失, RAM可以理解為內存,用來存儲代碼運行時的數據,變量等等。掉電數據丟失。STM32將外設等都映射為地址的形式,對地址的操作就是對外設的操作。
stm32的外設地址從0x4000 0000開始,可以看到在庫文件中,是通過基於0x4000 0000地址的偏移量來操作寄存器以及外設的。

一般情況下,程序文件是從 0x0800 0000 地址寫入,這個是STM32開始執行的地方,0x0800 0004是STM32的中斷向量表的起始地址。
在使用keil進行編寫程序時,其編程地址的設置如下圖:
程序的寫入地址從0x08000000開始的,其大小為0x10000也就是64KB的空間,換句話說就是告訴編譯器flash的空間是從0x08000000-0x08010000,RAM的地址從0x20000000開始,大小為0x05000也就是20KB的RAM。這與STM32的內存地址映射關系是對應的。
M3復位后,從0x08000004取出復位中斷的地址,並且跳轉到復位中斷程序,中斷執行完之后會跳到我們的main函數,main函數里邊一般是一個死循環,進去后就不會再退出,當有中斷發生的時候,M3將PC指針強制跳轉回中斷向量表,然后根據中斷源進入對應的中斷函數,執行完中斷函數之后,再次返回main函數中。大致的流程就是這樣。

2、Flash分布介紹
flash由主存儲區、系統存儲區、選項字節區域,部分型號還有OTP(one time program)區域組成,地址分布如下圖:


主存儲器:一般我們說 STM32 內部 FLASH 的時候,都是指這個主存儲器區域它是存儲用戶應用程序的空間,芯片型號說明中的 1M FLASH、 2M FLASH 都是指這個區域的大小。與其它 FLASH 一樣,在寫入數據前,要先按扇區擦除。
系統存儲區:系統存儲區是用戶不能訪問的區域,它在芯片出廠時已經固化了啟動代碼,它負責實現串口、 USB 以及 CAN 等 ISP 燒錄功能。
OTP 區域:OTP(One Time Program),指的是只能寫入一次的存儲區域,容量為 512 字節,寫入后數據就無法再更改, OTP 常用於存儲應用程序的加密密鑰。
選項字節區域:選項字節用於配置 FLASH 的讀寫保護、電源管理中的 BOR 級別、軟件/硬件看門狗等功能,這部分共 32 字節。可以通過修改 FLASH 的選項控制寄存器修改。
3、讀寫flash操作流程
- 解鎖 (固定的KEY值)
(1) 往 Flash 密鑰寄存器 FLASH_KEYR 中寫入 KEY1 = 0x45670123
(2) 再往 Flash 密鑰寄存器 FLASH_KEYR 中寫入 KEY2 = 0xCDEF89AB

-
數據操作位數
最大操作位數會影響擦除和寫入的速度,其中 64 位寬度的操作除了配置寄存器位外,還需要在 Vpp 引腳外加一個電壓源,且其供電間不得超過一小時,否則 FLASH可能損壞,所以 64 位寬度的操作一般是在量產時對 FLASH 寫入應用程序時才使用,大部分應用場合都是用 32 位的寬度。 -
擦除扇區
在寫入新的數據前,需要先擦除存儲區域, STM32 提供了扇區擦除指令和整個FLASH 擦除(批量擦除)的指令,批量擦除指令僅針對主存儲區。
扇區擦除的過程如下:
(1) 檢查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以確認當前未執行任何 Flash 操作;
(2) 在 FLASH_CR 寄存器中,將“激活扇區擦除寄存器位 SER ”置 1,並設置“扇 區編號寄存器位 SNB”,選擇要擦除的扇區;
(3) 將 FLASH_CR 寄存器中的“開始擦除寄存器位 STRT ”置 1,開始擦除;
(4) 等待 BSY 位被清零時,表示擦除完成。 -
寫入數據
擦除完畢后即可寫入數據,寫入數據的過程並不是僅僅使用指針向地址賦值,賦值前還還需要配置一系列的寄存器,步驟如下:
(1) 檢查 FLASH_SR 中的 BSY 位,以確認當前未執行任何其它的內部 Flash 操作;
(2) 將 FLASH_CR 寄存器中的 “激活編程寄存器位 PG” 置 1;
(3) 針對所需存儲器地址(主存儲器塊或 OTP 區域內)執行數據寫入操作;
(4) 等待 BSY 位被清零時,表示寫入完成。 -
寫flash操作流程圖

讀flash操作流程

4、代碼實現
官方庫函數提供了幾個接口函數,可對flash進行解鎖、上鎖、讀、寫、擦除等操作
stm32f1xx_hal_flash.h
stm32f1xx_hal_flash.c
stm32f1xx_hal_flash_ex.h
stm32f1xx_hal_flash_ex.c
- 接口函數介紹
// 解鎖操作函數
HAL_StatusTypeDef HAL_FLASH_Unlock(void);
HAL_StatusTypeDef HAL_FLASH_Lock(void);
// 寫操作函數
/**
* @brief Program halfword, word or double word at a specified address
* @note The function HAL_FLASH_Unlock() should be called before to unlock the FLASH interface
* The function HAL_FLASH_Lock() should be called after to lock the FLASH interface
*
* @note If an erase and a program operations are requested simultaneously,
* the erase operation is performed before the program one.
*
* @note FLASH should be previously erased before new programmation (only exception to this
* is when 0x0000 is programmed)
*
* @param TypeProgram: Indicate the way to program at a specified address.
* This parameter can be a value of @ref FLASH_Type_Program
* @param Address: Specifies the address to be programmed.
* @param Data: Specifies the data to be programmed
*
* @retval HAL_StatusTypeDef HAL Status
*/
HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
/**
* @brief Program halfword, word or double word at a specified address with interrupt enabled.
* @note The function HAL_FLASH_Unlock() should be called before to unlock the FLASH interface
* The function HAL_FLASH_Lock() should be called after to lock the FLASH interface
*
* @note If an erase and a program operations are requested simultaneously,
* the erase operation is performed before the program one.
*
* @param TypeProgram: Indicate the way to program at a specified address.
* This parameter can be a value of @ref FLASH_Type_Program
* @param Address: Specifies the address to be programmed.
* @param Data: Specifies the data to be programmed
*
* @retval HAL_StatusTypeDef HAL Status
*/
HAL_StatusTypeDef HAL_FLASH_Program_IT(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
// 擦除操作函數
/**
* @brief Perform a mass erase or erase the specified FLASH memory pages
* @note To correctly run this function, the @ref HAL_FLASH_Unlock() function
* must be called before.
* Call the @ref HAL_FLASH_Lock() to disable the flash memory access
* (recommended to protect the FLASH memory against possible unwanted operation)
* @param[in] pEraseInit pointer to an FLASH_EraseInitTypeDef structure that
* contains the configuration information for the erasing.
*
* @param[out] PageError pointer to variable that
* contains the configuration information on faulty page in case of error
* (0xFFFFFFFF means that all the pages have been correctly erased)
*
* @retval HAL_StatusTypeDef HAL Status
*/
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError);
HAL_StatusTypeDef HAL_FLASHEx_Erase_IT(FLASH_EraseInitTypeDef *pEraseInit);
- 用戶接口實現
flash.h
#ifndef __FLASH_H__
#define __FLASH_H__
#include "main.h"
#define FLASH_BASE_ADDR 0x08000000UL /* Flash基地址 */
#define SECTOR_SIZE 1024 /* 塊大小 */
#define FLASH_SIZE (1*64*1024) /* Flash 容量 */
#define UserFlashAddress ((uint32_t)(FLASH_BASE_ADDR | 0xFC00)) // 用戶讀寫起始地址(內部flash的主存儲塊地址從0x080FC000開始)
#endif /* __FLASH_H__ */
flash.c
#include "flash.h"
//從指定地址開始寫入多個數據(16位)
void FLASH_WriteHalfWordData(uint32_t startAddress, uint16_t *writeData, uint16_t countToWrite)
{
uint32_t offsetAddress = startAddress - FLASH_BASE_ADDR; // 計算去掉0X08000000后的實際偏移地址
uint32_t sectorPosition = offsetAddress / SECTOR_SIZE; // 計算扇區地址,對於STM32F103VET6為0~255
uint32_t sectorStartAddress = sectorPosition * SECTOR_SIZE + FLASH_BASE_ADDR; // 對應扇區的首地址
uint16_t dataIndex;
if (startAddress < FLASH_BASE_ADDR || ((startAddress + countToWrite * 2) >= (FLASH_BASE_ADDR + SECTOR_SIZE * FLASH_SIZE)))
{
return; // 非法地址
}
FLASH_Unlock(); // 解鎖寫保護
FLASH_ErasePage(sectorStartAddress); // 擦除這個扇區
for (dataIndex = 0; dataIndex < countToWrite; dataIndex++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, startAddress + dataIndex * 2, writeData[dataIndex]);
}
FLASH_Lock(); // 上鎖寫保護
}
//從指定地址開始寫入多個數據(32位)
void FLASH_WriteWordData(uint32_t startAddress, uint32_t *writeData, uint16_t countToWrite)
{
uint32_t offsetAddress = startAddress - FLASH_BASE_ADDR; // 計算去掉0X08000000后的實際偏移地址
uint32_t sectorPosition = offsetAddress / SECTOR_SIZE; // 計算扇區地址,對於STM32F103VET6為0~255
uint32_t sectorStartAddress = sectorPosition * SECTOR_SIZE + FLASH_BASE_ADDR; // 對應扇區的首地址
uint16_t dataIndex;
if (startAddress < FLASH_BASE_ADDR || ((startAddress + countToWrite * 4) >= (FLASH_BASE_ADDR + SECTOR_SIZE * FLASH_SIZE)))
{
return; // 非法地址
}
FLASH_Unlock(); // 解鎖寫保護
FLASH_ErasePage(sectorStartAddress); // 擦除這個扇區
for (dataIndex = 0; dataIndex < countToWrite; dataIndex++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, startAddress + dataIndex * 4, writeData[dataIndex]);
}
FLASH_Lock(); // 上鎖寫保護
}
//從指定地址開始寫入多個數據(64位)
void FLASH_WriteDoubleWordData(uint32_t startAddress, uint64_t *writeData, uint16_t countToWrite)
{
uint32_t offsetAddress = startAddress - FLASH_BASE_ADDR; // 計算去掉0X08000000后的實際偏移地址
uint32_t sectorPosition = offsetAddress / SECTOR_SIZE; // 計算扇區地址,對於STM32F103VET6為0~255
uint32_t sectorStartAddress = sectorPosition * SECTOR_SIZE + FLASH_BASE_ADDR; // 對應扇區的首地址
uint16_t dataIndex;
if (startAddress < FLASH_BASE_ADDR || ((startAddress + countToWrite * 8) >= (FLASH_BASE_ADDR + SECTOR_SIZE * FLASH_SIZE)))
{
return; // 非法地址
}
FLASH_Unlock(); // 解鎖寫保護
FLASH_ErasePage(sectorStartAddress); // 擦除這個扇區
for (dataIndex = 0; dataIndex < countToWrite; dataIndex++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, startAddress + dataIndex * 8, writeData[dataIndex]);
}
FLASH_Lock(); // 上鎖寫保護
}
// 讀取指定地址的半字(16位數據)
uint16_t FLASH_ReadHalfWord(uint32_t address)
{
return *(__IO uint16_t*)address;
}
// 讀取指定地址的單字(32位數據)
uint16_t FLASH_ReadWord(uint32_t address)
{
return *(__IO uint32_t*)address;
}
// 讀取指定地址的雙字(64位數據)
uint16_t FLASH_ReadDoubleWord(uint32_t address)
{
return *(__IO uint64_t*)address;
}
// 從指定地址開始讀取多個數據(16位數據)
void FLASH_ReadHalfWordData(uint32_t startAddress, uint16_t *readData, uint16_t countToRead)
{
uint16_t dataIndex;
for (dataIndex = 0; dataIndex < countToRead; dataIndex++)
{
readData[dataIndex] = FLASH_ReadHalfWord(startAddress + dataIndex * 2);
}
}
// 從指定地址開始讀取多個數據(32位數據)
void FLASH_ReadWordData(uint32_t startAddress, uint32_t *readData, uint16_t countToRead)
{
uint16_t dataIndex;
for (dataIndex = 0; dataIndex < countToRead; dataIndex++)
{
readData[dataIndex] = FLASH_ReadWord(startAddress + dataIndex * 4);
}
}
// 從指定地址開始讀取多個數據(64位數據)
void FLASH_ReadDoubleWordData(uint32_t startAddress, uint64_t *readData, uint16_t countToRead)
{
uint16_t dataIndex;
for (dataIndex = 0; dataIndex < countToRead; dataIndex++)
{
readData[dataIndex] = FLASH_ReadDoubleWord(startAddress + dataIndex * 8);
}
}
test.c
#include "main.h"
#include "flash.h"
void write_to_flash(void)
{
uint16_t buff[64];
uint16_t count_len = sizeof(buff) / sizeof(buff[0]);
printf("\r\nWriteData successful!\r\n");
for(uint16_t i = 0; i < count_len; ++i)
{
if(i != 0 && i % 16 == 0)
printf("\r\n");
buff[i] = rand() % 64 + 1;
printf("0x%02x,", buff[i]);
}
FLASH_WriteHalfWordData(UserFlashAddress,buff,count_len);
printf("\r\n");
}
void read_from_flash(void)
{
uint16_t buff[64];
uint16_t count_len = sizeof(buff) / sizeof(buff[0]);
FLASH_ReadHalfWordData(UserFlashAddress, buff,count_len);
printf("\r\nReadData successful!\r\n");
for(uint16_t i = 0; i < count_len; ++i)
{
if(i != 0 && i % 16 == 0)
printf("\r\n");
printf("0x%02x,", buff[i]);
}
printf("\r\n");
}
void main(void)
{
// 省略掉了部分初始化代碼,只保留調用代碼
write_to_flash();
read_from_flash();
while(1);
}
測試結果截圖:

