[STM32 - 野火] - - - 固件库学习笔记 - - - 十四.读写内部FLASH
存储程序代码:在 STM32 芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中。由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。存储关键信息除了通过外部工具(如下载器)进行读写操作外,STM32芯片在运行时也能直接对内部FLASH进行读写操作
一、FLASH介绍
-
1、概念:非易失性存储:内部FLASH用于存储程序代码和常量数据,断电后数据不丢失。
-
2、结构与容量
-
型号相关:不同STM32系列(如F1/F4/H7)容量差异大,从16KB到2MB不等。
-
分块管理:
-
F1系列:分页结构(1KB或2KB每页)。
-
F4系列:扇区结构(16KB、64KB、128KB等)。
-
H7系列:双Bank设计,支持并行操作。
-
-
-
3、访问特性
-
读写速度:零等待周期时可达CPU主频速度,高频需插入等待周期(如F1@72MHz需2周期)。
-
操作单位:
-
擦除:按页/扇区或整片(时间较长,需数毫秒到秒级)
-
写入:按半字(16位)或字(32位),不支持按字节写入。
-
-
解锁机制:通过FLASH_KEYR写入密钥解锁写保护。
-
-
4、功能特性
-
启动配置:通过BOOT引脚选择启动源(如FLASH、系统存储器)。
-
保护机制:
-
读保护(RDP):防止调试接口读取,触发后需全片擦除解除。
-
写保护:可选扇区保护,避免误擦写。
-
-
选项字节(Option Bytes):配置保护、复位策略等,需单独操作。
-
-
5、编程方式
-
ICP(在线编程):通过SWD/JTAG接口烧录,使用ST-Link等工具。
-
IAP(应用内编程):
-
自更新:程序运行时修改自身FLASH,需注意中断管理和代码重定位。
-
双Bank应用:H7系列可在双Bank间切换,实现无中断升级。
-
-
对于存储器的其他介绍,可以详见这一篇。常见存储器介绍
二、STM32的内部FLASH简介

- 存储程序代码:
在 STM32 芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中。
由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。
- 存储关键信息
除了通过外部工具(如下载器)进行读写操作外,STM32芯片在运行时也能直接对内部FLASH进行读写操作。
如果内部FLASH在存储应用程序后仍有剩余空间,我们可以将其当作一个类似于外部SPI-FLASH的存储设备,用于保存程序运行时产生的数据。
这些数据在掉电后依然可以被保留,因此非常适合用于存储需要长期保存的关键信息。
- 访问速度
由于内部FLASH的访问速度远高于外部SPI-FLASH,它在紧急状态下可以作为存储关键记录的首选区域。例如,在系统出现异常或需要快速保存重要数据时,内部FLASH能够快速响应并完成数据存储。
- 安全性
为了保护程序代码和存储数据的安全性,STM32还提供了一系列内部FLASH的保护措施。例如,可以通过设置选项字节或使用加密算法来禁止读写内部FLASH中的内容。一些应用会在首次运行时计算加密信息并将其存储在特定区域,随后删除部分加密代码,从而防止程序被抄袭。
2.1 内部FLASH的构成
STM32 的内部 FLASH 包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小如下:
2.1.1 主存储器
一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、 512K FLASH 都是指这个区域的大小。
主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。
注意上表中的主存储器是本实验板使用的 STM32VET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。
关于 STM32 内部 FLASH 的容量类型可根据它的型号名获知,见下表:
2.1.2 系统存储区
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB以及 CAN 等 ISP 烧录功能。
STM32出厂前就已经使用了的空间。
2.1.3 选项字节
选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16字节。可以通过修改 FLASH 的选项控制寄存器修改。
相当于(FLASH类型的)寄存器。
三、对内部FLASH的写入过程
3.1 解锁
由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。
所以对 FLASH 写入数据前,需要先给它解锁。
解锁步骤:
-
1、 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123;
-
2、再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB;

/* FLASH Keys */
#define RDP_Key ((uint16_t)0x00A5)
#define FLASH_KEY1 ((uint32_t)0x45670123)
#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)
/**
* @brief Unlocks the FLASH Program Erase Controller.
* @note This function can be used for all STM32F10x devices.
* - For STM32F10X_XL devices this function unlocks Bank1 and Bank2.
* - For all other devices it unlocks Bank1 and it is equivalent
* to FLASH_UnlockBank1 function..
* @param None
* @retval None
*/
void FLASH_Unlock(void)
{
/* Authorize the FPEC of Bank1 Access */
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
3.2 擦除扇区
在写入新的数据前,需要先擦除存储区域, STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除 (批量擦除) 的指令,批量擦除指令仅针对主存储区。
擦除步骤:
-
1、检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
BSY 0:不忙碌 1:忙碌;
-
2、在 FLASH_CR 寄存器中,将“激活页擦除寄存器位 PER ”置 1;
-
3、用 FLASH_AR 寄存器选择要擦除的页;
-
4、将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
-
5、 等待 BSY 位被清零时,表示擦除完成;
-
6、将“激活页擦除寄存器位 PER ”清 0;
/**
* @brief Erases a specified FLASH page.
* @note This function can be used for all STM32F10x devices.
* @param Page_Address: The page address to be erased.
* @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
* FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
{
FLASH_Status status = FLASH_COMPLETE;
/* Check the parameters */
assert_param(IS_FLASH_ADDRESS(Page_Address));
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(EraseTimeout); // 1、检测BSY位
if(status == FLASH_COMPLETE)
{
/* if the previous operation is completed, proceed to erase the page */
FLASH->CR|= CR_PER_Set; // 2、PER置1
FLASH->AR = Page_Address; // 3、选择要擦除的页
FLASH->CR|= CR_STRT_Set; // 4、STRT置1
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(EraseTimeout); // 5、等待清除完成
/* Disable the PER Bit */
FLASH->CR &= CR_PER_Reset; // 6、PER清0
}
/* Return the Erase Status */
return status;
}

3.3 写入数据
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器。
写入步骤:
-
1、检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
-
2、将 FLASH_CR 寄存器中的“激活编程寄存器位 PG”置 1;
-
3、向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;
-
4、等待 BSY 位被清零时,表示写入完成;
-
5、将 FLASH_CR 寄存器中的“激活编程寄存器位 PG”清0;
/**
* @brief Programs a word at a specified address.
* @note This function can be used for all STM32F10x devices.
* @param Address: specifies the address to be programmed.
* @param Data: specifies the data to be programmed.
* @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
* FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
FLASH_Status status = FLASH_COMPLETE;
__IO uint32_t tmp = 0;
/* Check the parameters */
assert_param(IS_FLASH_ADDRESS(Address));
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(ProgramTimeout); // 1、检测BSY位
if(status == FLASH_COMPLETE)
{
/* if the previous operation is completed, proceed to program the new first
half word */
FLASH->CR |= CR_PG_Set; // 2、PG置1
*(__IO uint16_t*)Address = (uint16_t)Data; // 3、写入数据
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(ProgramTimeout); // 4、等待写入完成
if(status == FLASH_COMPLETE)
{
/* if the previous operation is completed, proceed to program the new second
half word */
tmp = Address + 2;
*(__IO uint16_t*) tmp = Data >> 16;
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(ProgramTimeout);
/* Disable the PG Bit */
FLASH->CR &= CR_PG_Reset; // 5.PG清0
}
else
{
/* Disable the PG Bit */
FLASH->CR &= CR_PG_Reset;
}
}
/* Return the Program Status */
return status;
}

四、查看工程的空间分布
由于内部FLASH本身存储了程序代码,若非有意删除某段代码,一般不应修改程序空间的内容。因此,在使用内部FLASH存储其他数据之前,需先了解哪些空间已写入程序代码。已存储程序代码的扇区不应进行任何修改。
通过查询应用程序编译时产生的“*.map”后缀文件,可以了解程序存储到了哪些区域。

用Total ROM Size(十进制转十六进制+Base)算出整个程序占用空间。
五、实验:读写内部FLASH
// flash.c文件
#include "flash.h"
#define FLASH_BASE_ADDR 0x08000000
#define FLASH_SIZE (1024*512)
// 1、解锁
// 2、擦除指定的页(查看工程分部,以防擦除程序本身)
// 3、写入数据
// 4、读出数据校验
void Flash_Write(uint32_t Addr, uint32_t data)
{
FLASH_Unlock();
FLASH_ErasePage(Addr); // FLASH_ErasePage(0x80000000 + 2*1024*page); 擦除第page页
FLASH_ProgramWord(Addr, data);
FLASH_Lock();
}
uint32_t Flash_Read(uint32_t Addr)
{
if(Addr < FLASH_BASE_ADDR || Addr >= FLASH_BASE_ADDR + FLASH_SIZE)
{
// 地址超出范围,返回错误值
return 0x5aa5a5a5;
}
uint32_t *p = (uint32_t *)Addr;
return *p;
}
// flash.h文件
#ifndef __FLASH_H
#define __FLASH_H
#include "stm32f10x.h"
void Flash_Write(uint32_t Addr, uint32_t data);
uint32_t Flash_Read(uint32_t Addr);
#endif /* __FLASH_H */
// main.c文件
#include "stm32f10x.h"
#include "usart.h"
#include "flash.h"
int main(void)
{
uint32_t temp = 0;
USART_Config();
Flash_Write(0x08000000+2*1024*5, 0x12345678);
printf("\r\n 写入完成\r\n");
temp = Flash_Read(0x08000000+2*1024*5);
printf("\r\n 写入的值为 0x%x :\r\n" , temp);
while(1)
{
}
}

- 在 Flash_Read 函数中,对地址进行检测是十分必要的。如果地址传错,而函数中没有这种检测机制,那么就需要花费大量精力去排查这种失误。
更多推荐



所有评论(0)