STM32F10X闪存编程(1)基础知识
前言
编程手册下载地址:STM32F10xxx闪存编程手册中文:基于 STM32F10xxx 的闪存编程中文手册项目 - AtomGit
闪存存储器即Flash,在 STM32 微控制器中,Flash 存储器承担着以下核心作用:
- 存储程序代码:保存编译后的固件,确保每次复位后程序能够正常运行。
- 保存配置数据:用于持久化存储系统参数。
- 支持固件更新:用于空中固件更新(FOTA)以及存储引导加载程序(Bootloader)。
手册里也有提到STM32F10xxx内嵌的闪存存储器可以用于在线编程(ICP)或在程序中编程(IAP)烧写。当然,对于容量不大的芯片,通常使用外部存储器来实现IAP升级功能。
从底层物理结构来看,STM32 的 Flash 模块通常基于 NOR Flash 技术,这种结构支持快速的随机读取操作,非常适合程序的直接执行。然而,它的写入机制比较特殊:每次向 Flash 写入数据时,只能将数据位从 1 变为 0。如果希望将某位恢复为 1,就必须先执行擦除操作,且擦除操作必须以“页(Page)”或“扇区(Sector)”为单位进行。
这种按扇区擦除的机制决定了 Flash 的读写粒度。例如,STM32F1 系列通常以 1KB 或 2KB 的页面为单位,而 STM32F4 系列则以 16KB、64KB 甚至 128KB 的扇区为单位。此外,Flash 存储器的擦写寿命是有限的(通常在 10万次左右),过度擦写会导致扇区失效。
闪存模块
判断mcu类型
以F1为例,按照不同容量,存储器组织成32个1K字节/页(小容量)、128个1K字节/页(中容量)、128个2K字 节/页(互联型)、256个2K字节/页(大容量)的主存储器块和一个信息块。



以STM32F103RCT6为例,根据上述图片,其为引脚为64,256K闪存,48KRAM的大容量产品。

启动方式
手册中提到:
这句话怎么理解呢?我们结合以下图片。stm32的启动模式取决于boot0和boot1的引脚电平,不管你是哪种启动模式,STM32 上电或复位后,CPU 的 PC(程序计数器)指针永远是从 0x00000000 开始取指令的。这是 ARM Cortex-M 内核的硬件规定。

既然 CPU 总是从 0x00000000 开始跑,那这个地址里装的是什么代码呢?这就取决于你板子上 BOOT0 和 BOOT1 引脚的电平状态(即启动模式)了。STM32 会在复位瞬间锁存这两个引脚的电平,进行内存重映射(Memory Remap):
-
模式一:主闪存存储器启动(Main Flash)
- BOOT0 = 0
- 映射机制:芯片将主闪存(物理地址
0x08000000)映射到0x00000000。 - 结果:这就是我们平时最常用的模式。CPU 从
0x00000000取指令,实际上读取的是0x08000000里的用户代码。
-
模式二:系统存储器启动(System Memory)
- BOOT0 = 1,BOOT1 = 0
- 映射机制:芯片将系统存储器(物理地址
0x1FFFF000)映射到0x00000000。 - 结果:这块区域是 ST 官方在出厂时固化在芯片里的 Bootloader(ISP 程序),出厂后不可修改。此时 CPU 从
0x00000000取指令,实际上运行的是官方的 Bootloader,这就是为什么我们可以通过串口下载程序的原因。
-
模式三:内置 SRAM 启动
- BOOT0 = 1,BOOT1 = 1
- 映射机制:芯片将内部 SRAM(物理地址
0x20000000)映射到0x00000000。 - 结果:SRAM 是易失性存储器,掉电数据就没了。这种模式一般用于快速调试代码,不需要反复擦写 Flash。
读操作

从系统架构图可以看到Flash接口通过ICode总线和DCode总线与MCU相连,而Flash只与Flash接口交换数据,这样做有什么好处呢?CPU 的运行速度(比如 72MHz)远快于 Flash 的物理读取速度。如果 CPU 直接去 Flash 拿数据,每次都要等很久(插入等待周期)。这个接口充当了 CPU 和 Flash 之间的“缓冲翻译官”,负责发出底层物理控制信号,并管理预取缓冲器。
ICode总线负责取指令,CPU 运行程序时,读控制器会提前把接下来要执行的指令块“预取”到缓存里,这样 CPU 就不需要停下来等 Flash,从而实现流水线的高效执行。DCode总线负责数据,当你的代码里定义了 const 常量(如 const int arr[] = {...};),或者在调试时读取 Flash 里的数据,都会走这条总线。
并且Flash 接口内置了仲裁机制,数据访问(D-Code)的优先级高于指令预取(I-Code)。
取指令
预取缓冲器
预取缓冲器包含两个数据块,每个数据块8个字节,STM32F103 的内部 Flash 物理位宽是 64 位(即 8 个字节),因此读取预取指令块可以在一个周期内完成。
CPU 正在执行当前这 8 字节里的指令时,Flash 控制器并没有闲着,它已经提前把接下来的 8 字节(下一个数据块)从 Flash 里搬到了缓冲器里。当 CPU 执行完当前指令,需要下一条时,直接从缓冲器里拿,不需要等待 Flash 的物理读取时间。这就完美掩盖了 Flash 读取慢的缺陷。
预取控制器
单片机每次复位后,预取缓冲器处于开启状态。预取控制器根据预取缓冲器中可用的空间决定是否访问闪存,预取缓冲器中有至少一块的空余 空间时,预取控制器则启动一次读操作。
DCode接口
闪存访问控制器
闪存编程和擦除控制器(FPEC)
- FPEC键寄存器(FLASH_KEYR)
- 选项字节键寄存器(FLASH_OPTKEYR)
- 闪存控制寄存器(FLASH_CR)
- 闪存状态寄存器(FLASH_SR)
- 闪存地址寄存器(FLASH_AR)
- 选项字节寄存器(FLASH_OBR)
- 写保护寄存器(FLASH_WRPR)
解除闪存锁
需要注意以下三点:
- 默认上锁,两步解锁:复位后闪存是锁死的。必须严格按照顺序,先向密钥寄存器(FLASH_KEYR)写入KEY1,再写入KEY2,才能成功解锁。
- 写错直接“死机”:如果解锁时没按顺序写,或者写错了密钥,系统会立刻报总线错误,并且在下次重启前彻底锁死,再也无法写入。
- 支持手动上锁:解锁后,程序可以主动把闪存重新锁上(设置LOCK位)。如果后续还需要写入,只要再次输入正确的“KEY1+KEY2”就能重新解锁。
三个键值分别为:RDPRT键 = 0x000000A5, KEY1 = 0x45670123, KEY2 = 0xCDEF89AB

以HAL库为例,首先判断一下是否上锁,如果是就按顺序写入KEY1和KEY2,如果不是直接返回HAL_OK。写完键值之后再判断一下是否还是上锁状态,如果是的话就返回HAL_ERROR。
主闪存编程

- 检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的编程操作;
- 设置FLASH_CR寄存器的PG位为’1’;
- 在指定的地址写入要编程的半字;
- 等待BSY位变为’0’;
- 读出写入的地址并验证数据。
注意:这种编程模式必须以半字形式写入,否则会报总线错误;FPEC先读出指定地址的内容并检查它是否被擦除,如未被擦除则不执行编程并在FLASH_SR寄存器的PGERR位提出警告(唯一的例外是当要烧写的数值是0x0000时,0x0000可被正确烧入且 PGERR位不被置位);如果指定的地址在FLASH_WRPR中设定为写保护,则不执行编程并在 FLASH_SR寄存器的WRPRTERR位置’1’提出警告。FLASH_SR寄存器的EOP为’1’时表示编程结束。
当FLASH_SR寄存器的BSY位为’1’时,不能对任何寄存器执行写操作。
闪存擦除
页擦除
- 检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的闪存操作;
- 设置FLASH_CR寄存器的PER位为’1’;
- 用FLASH_AR寄存器选择要擦除的页;
- 设置FLASH_CR寄存器的STRT位为’1’;
- 等待BSY位变为’0’;
- 读出被擦除的页并做验证。
HAL库代码如下:

整片擦除
- 检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的闪存操作;
- 设置FLASH_CR寄存器的MER位为’1’;
- 设置FLASH_CR寄存器的STRT位为’1’;
- 等待BSY位变为’0’;
- 读出所有页并做验证。

HAL库代码如下:

选项字节编程
之后补充
保护
- 对于小容量和中容量的产品为4页;
- 对于大容量和互联型的产品为2页。
HAL库中没有针对保护的特定库函数,可以参考:
49. 设置FLASH的读写保护及解除 — [野火]STM32 HAL库开发实战指南——基于野火F4系列开发板 文档
[STM32 - 野火] - - - 固件库学习笔记 - - - 十五.设置FLASH的读写保护及解除 - 技术栈
读保护
读保护只防“外人”,不防“自己”。芯片正常上电,从内部 Flash 启动运行你的程序时,你的程序可以正常读取 Flash,也可以通过 IAP(在应用编程)去擦写其他区域的 Flash。
为了防止恶意代码修改中断向量表跳到 RAM 里执行,Flash 的第 0~3 页会被硬件强制加上写保护。这意味着哪怕是你自己的程序,也无法擦写这几页(通常用来放 Bootloader)。
一旦开启读保护,外部调试器(JTAG/SWD)就彻底变成了“瞎子”。
- 调试器不能读 Flash,不能写 Flash,不能擦除 Flash。
- 黑客常用的手段——把破解代码下载到 RAM 里,然后让 CPU 从 RAM 启动去读 Flash,这条路也被堵死了。
- 甚至连 DMA(直接内存访问)也被限制了,防止有人用 DMA 绕过 CPU 偷偷把 Flash 里的数据搬运出来。
虽然调试器不能读 Flash 了,但 STM32 还是留了一个后门:你依然可以通过调试器把代码下载到 RAM 里并运行。如果你以后想更新程序或关闭读保护,你可以写一段只有几行的极简代码(里面只包含解锁选项字节、关闭读保护的指令),下载到 RAM 里运行。
为了防止有人用这种方法窃取数据,STM32 规定:只要你把读保护级别降回去(比如从 Level 1 降到 Level 0),芯片会立刻自动执行“全片擦除”。也就是说,你原来的程序和数据会瞬间灰飞烟灭,必须重新烧录。


写保护
不同容量的芯片,写保护打包的规则不同:
- 小/中容量产品:以 4页 为一组。比如你开启了第0页的写保护,那么第0、1、2、3页会同时被锁死。
- 大容量产品:前62页以 2页 为一组(比如第0、1页绑定,第2、3页绑定);但第62页到第255页这最后的一大块区域,是绑定在一起的,只要开启保护,这最后的一大片就全部被锁死。
- 互联型产品:前62页同样以 2页 为一组;第62页到第127页是绑定在一起的。
如果试图在一个受保护的页面进行编程或擦除操作,在闪存状态寄存器(FLASH_SR)中会返回一个保护错误标志。
写保护(WRP)是存储在“选项字节(Option Bytes)”里的。你在代码里修改了 WRP 寄存器的值,写保护并不会立刻生效。你必须让芯片执行一次系统复位(Reset),芯片在启动时才会去读取新的选项字节,把写保护规则真正应用到硬件上。


选项字节写保护
后续补充
选项字节说明
后续补充
寄存器
暂不额外说明
更多推荐

所有评论(0)