「STM32 Flash 操作全解析」擦除、寫入、讀取一網(wǎng)打盡!附完整源碼
在嵌入式開發(fā)中,MCU 內(nèi)部的 Flash 常用于存儲配置信息、日志數(shù)據(jù)或用于 OTA 升級。STM32F4 系列 MCU 提供了對 Flash 的靈活操作能力,包括按扇區(qū)擦除、字節(jié)或半字寫入等。本文將圍繞一段實際使用的 Flash 操作代碼進行講解,主要涉及 Flash 的擦除、寫入與讀取功能。
一、Flash 結(jié)構(gòu)及操作基本原理
STM32F4 MCU 的 Flash 存儲器按照扇區(qū)(Sector)劃分,每個扇區(qū)大小不一,例如在 STM32F407 中,前四個扇區(qū)大小為 16KB,第五個為 64KB,之后為若干個 128KB 的大扇區(qū)。片上 Flash 支持:
扇區(qū)級擦除(Sector Erase)
多種對齊方式的編程(如 Byte、Halfword、Word、Double Word)
擦寫需先解鎖并清除相關(guān)標志位
操作前需解鎖 Flash 控制器,完成后應及時鎖定以防意外寫入。
二、Flash 扇區(qū)映射及擦除操作
代碼中的扇區(qū)映射表 sec_map[] 采用結(jié)構(gòu)體 sec_info_t 維護每個扇區(qū)的起始地址、大小及編號:
typedef struct {
unsigned int start; unsigned int size; unsigned int secnum;
} sec_info_t;
這是一個結(jié)構(gòu)體類型,表示每個扇區(qū)的起始地址、扇區(qū)大小和扇區(qū)編號。接著通過一個常量數(shù)組 sec_map[] 列出 Flash 不同扇區(qū)的信息:
const sec_info_t sec_map[] = {
{0x08000000, 16*1024, FLASH_Sector_0},
{0x08004000, 16*1024, FLASH_Sector_1},
{0x08008000, 16*1024, FLASH_Sector_2},
{0x0800C000, 16*1024, FLASH_Sector_3},
{0x08010000, 64*1024, FLASH_Sector_4},
{0x08020000, 128*1024, FLASH_Sector_5},
{0x08040000, 128*1024, FLASH_Sector_6},
{0x08040000, 128*1024, FLASH_Sector_7}};
該映射表根據(jù) STM32F4 的 Flash 布局列出了常用的 8 個扇區(qū)。
二、Flash 擦除函數(shù)講解
函數(shù) mcu_flash_erase() 實現(xiàn)對 Flash 指定地址范圍的擦除。
int mcu_flash_erase(unsigned int addr, size_t size)
addr: 要擦除的起始地址
size: 要擦除的范圍(單位為字節(jié))
函數(shù)先計算扇區(qū)數(shù)量:
int len = sizeof(sec_map) / sizeof(sec_info_t);
然后依次遍歷扇區(qū),找出與 addr 和 size 匹配的扇區(qū)范圍,并執(zhí)行擦除:
status = FLASH_EraseSector(sec->secnum, VoltageRange_2);
在執(zhí)行擦除之前必須解鎖 Flash:
FLASH_Unlock();
擦除完成后鎖定 Flash:
FLASH_Lock();
最后返回 1 表示成功,返回 0 表示擦除失敗。
三、Flash 寫入函數(shù)講解
寫入函數(shù)為:
int mcu_flash_write(unsigned int addr ,const void *buf, size_t size)
addr: 寫入的起始地址
buf: 待寫入的數(shù)據(jù)緩沖區(qū)
size: 寫入數(shù)據(jù)的字節(jié)數(shù)
寫入之前,同樣要進行 Flash 解鎖,并清除標志位:
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_OPERR |
FLASH_FLAG_PGAERR);
然后進入一個循環(huán),按字節(jié)或半字方式逐步寫入 Flash:
if ((addr & 1) == 0 && size > 2) { status = FLASH_ProgramHalfWord(addr, *((uint16_t *)p)); wrlen = 2;
} else { status = FLASH_ProgramByte(addr, *((uint8_t *)p)); wrlen = 1;
}
寫入完成后更新地址和緩沖區(qū)指針:
size -= wrlen;addr += wrlen;p += wrlen;
如果寫入中某次操作返回錯誤,則提前跳出,最后執(zhí)行鎖定操作并返回是否寫入成功:
FLASH_Lock();return ret;
四、Flash 讀取函數(shù)講解
讀取函數(shù)的接口為:
int mcu_flash_read(unsigned int addr ,void *buf, size_t size)
該函數(shù)直接通過內(nèi)存拷貝讀取 Flash 數(shù)據(jù):
memcpy(buf, (void *)addr, size);
return 0;
其中 addr 是 Flash 的起始地址,buf 是目標緩存區(qū),size 是讀取字節(jié)數(shù)。由于 STM32 的 Flash 可以直接映射為內(nèi)存讀取,因此可以像訪問普通內(nèi)存一樣操作。
五、總結(jié)
本篇文章介紹了 STM32F4 MCU 內(nèi)部 Flash 的基礎(chǔ)操作實現(xiàn),包括:
使用結(jié)構(gòu)體映射 Flash 扇區(qū)信息
實現(xiàn) Flash 擦除函數(shù),通過匹配地址范圍擦除對應扇區(qū)
實現(xiàn) Flash 寫入函數(shù),按字節(jié)或半字逐步寫入 Flash
實現(xiàn) Flash 讀取函數(shù),通過 memcpy 方式直接讀取 Flash 內(nèi)容
以上代碼適用于裸機開發(fā),也可作為 STM32 Flash 操作的基礎(chǔ)模板,配合上層協(xié)議或文件系統(tǒng)進行擴展應用,如參數(shù)存儲、數(shù)據(jù)記錄、Bootloader 固件升級等功能。
開源源碼供參考:
#include "mcu_flash.h"#include "stm32f4xx.h"#include <string.h>typedef struct {
unsigned int start; unsigned int size; unsigned int secnum;
}sec_info_t;/*扇區(qū)地址映射 ---------------------------------------------------------------*/const sec_info_t sec_map[] =
{
{0x08000000, 16*1024, FLASH_Sector_0},
{0x08004000, 16*1024, FLASH_Sector_1},
{0x08008000, 16*1024, FLASH_Sector_2},
{0x0800C000, 16*1024, FLASH_Sector_3},
{0x08010000, 64*1024, FLASH_Sector_4},
{0x08020000, 128*1024, FLASH_Sector_5},
{0x08040000, 128*1024, FLASH_Sector_6},
{0x08040000, 128*1024, FLASH_Sector_7}
};/*
* @brief stm32 mcu 內(nèi)部flash擦除操作
* @param[in] addr - 地址
* @param[in] 探險大小 - size
* @return 0 - 失敗, 非0 - 成功
*/int mcu_flash_erase(unsigned int addr, size_t size){
int i; int len = sizeof(sec_map) / sizeof(sec_info_t); const sec_info_t *sec = &sec_map[len - 1];
FLASH_Status status;
/*越界處理*/
if (addr > sec->start + sec->size) return 0;
FLASH_Unlock(); for (i = 0; i < len; i++)
{
sec = &sec_map[i]; if ( (sec->start >= addr && sec->start < addr + size) ||
(sec->start + sec->size > addr && sec->start + sec->size <= addr + size))
{ //FLASH_OB_WRPConfig();
status = FLASH_EraseSector(sec->secnum, VoltageRange_2); if (status != FLASH_COMPLETE)
{
FLASH_Lock();
return 0;
}
}
}
FLASH_Lock();
return 1;
}/*
* @brief stm32 mcu 內(nèi)部flash寫操作
* @param[in] addr - 地址
* @param[in] buf - 數(shù)據(jù)緩沖區(qū)
* @param[in] 寫入大小 - size
* @return 0 - 失敗, 非0 - 成功
*/int mcu_flash_write(unsigned int addr ,const void *buf, size_t size){ unsigned char *p = (uint8_t *)buf;// unsigned int base = addr;// size_t tlen = size;
int wrlen;
FLASH_Status status = FLASH_COMPLETE; int ret = 0;
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_OPERR |
FLASH_FLAG_PGAERR);
while (size) {#if 0
/*根據(jù)對齊方式優(yōu)化寫入長度*/
if ((addr & 7) == 0 && size > 8) /*8字節(jié)對齊,按雙字寫入*/
{
status = FLASH_ProgramDoubleWord(addr, *((uint64_t *)p)); if (status != FLASH_COMPLETE) goto _quit;
wrlen = 8;
} else if ((addr & 3) == 0 && size > 4) /*4字節(jié)對齊,按字寫入*/
{
status = FLASH_ProgramWord(addr, *((uint32_t *)p)); if (status != FLASH_COMPLETE) goto _quit;
wrlen = 4;
} else if ((addr & 1) == 0 && size > 2) /*2字節(jié)對齊,按半字寫入*/
{
status = FLASH_ProgramHalfWord(addr, *((uint16_t *)p)); if (status != FLASH_COMPLETE) goto _quit;
wrlen = 2;
} else /*按字節(jié)寫入 --------*/
{
status = FLASH_ProgramByte(addr, *((uint8_t *)p)); if (status != FLASH_COMPLETE) goto _quit;
wrlen = 1;
}#endif
if ((addr & 1) == 0 && size > 2) /*2字節(jié)對齊,按半字寫入*/
{
status = FLASH_ProgramHalfWord(addr, *((uint16_t *)p)); if (status != FLASH_COMPLETE) goto _quit;
wrlen = 2;
} else /*按字節(jié)寫入 --------*/
{
status = FLASH_ProgramByte(addr, *((uint8_t *)p)); if (status != FLASH_COMPLETE) goto _quit;
wrlen = 1;
}
/*地址偏移 -------------------------------------------------------*/
size -= wrlen;
addr += wrlen;
p += wrlen;
}
_quit:
ret = status == FLASH_COMPLETE;// && memcmp(buf, (void *)base, tlen) ? 1 : 0;
FLASH_Lock(); return ret;
}/*
* @brief stm32 mcu 內(nèi)部flash讀操作
* @param[in] addr - 地址
* @param[in] buf - 數(shù)據(jù)緩沖區(qū)
* @param[in] 讀出長度 - size
* @return 0 - 失敗, 非0 - 成功
*/int mcu_flash_read(unsigned int addr ,void *buf, size_t size){ memcpy(buf, (void *)addr, size);
return 0;
}
評論