Skip to content

LV001-内部FLASH简介 ​

一、 FLASH 是什么 ​

1. 定义与存储原理 ​

FLASH(闪存)是一种电可擦除、可编程的非易失性存储器。它在 EEPROM 技术的基础上发展而来,本质区别在于擦除方式的电路结构不同,因此可以做到更高的集成度和更低的单位成本,代价是擦除粒度变粗。

它的存储单元是一个带浮栅(floating gate)的 MOS 管:

  • 浮栅被绝缘层包裹,注入其中的电荷在没有外部干预时不会泄漏,这就是“掉电不丢数据”的物理基础。
  • 写入(编程)时在控制栅加高压,电子穿过绝缘层进入浮栅,单元阈值电压改变;擦除时加反向高压,把浮栅里的电子移走。
  • 这两个动作需要的十几伏高压由芯片内部的电荷泵产生,外部只需要 3.3 V 供电,闪存编程手册(PM0042)明确写到“编程与擦除的高电压由内部产生”。

浮栅存储单元结构

2. 与 EEPROM 的差异 ​

STM32 的官方应用笔记 AN2594 用一张表对比了两者,摘录关键几行:

特性外部串行 EEPROM片内 FLASH
写入粒度字节半字(16 位)
写入时间随机字节 5 ~ 10 ms半字约 20 µs
擦除不需要必须,页擦除或整片擦除约 20 ms
读访问随机字约 92 µs每字几十个 CPU 周期,访问时间约 35 ns
擦写寿命1 万 ~ 100 万次每页 1 万次
写入过程不受 CPU 复位影响,只有掉电才会中断CPU 复位或掉电都会中断写入

最后一行是最容易被忽略、也最要命的一条:独立 EEPROM 一旦开始写,只要供电正常就一定会写完;而片内 FLASH 的写入可能被一次复位直接打断,写了一半的页处于不确定状态。这直接决定了用片内 FLASH 模拟 EEPROM 时必须带页头状态和上电恢复逻辑。

3. 主要特点 ​

  • 非易失:断电后数据保留,写入的电荷可以保持十年以上。
  • 读快写慢:读是普通内存访问,纳秒级;写和擦除是毫秒级,相差 4 ~ 5 个数量级。
  • 按页擦除:擦除的最小单位是页,STM32F103 大容量产品为 2 KB,小容量与中容量为 1 KB。
  • 只能单向改写:编程只能把位从 1 写成 0,要把 0 变回 1 必须擦除。
  • 擦写期间不能读:编程手册原文是“在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行”。也就是说擦写期间 CPU 会被硬件暂停,取指都拿不到。
  • 寿命有限:STM32F10xxx 的电气特性保证每页 1 万次编程/擦除循环。
  • 依赖 HSI:进行闪存编程操作时(写或擦除),必须打开内部的 RC 振荡器(HSI)。
  • 低功耗模式下停止:进入低功耗模式后所有闪存操作都被中止。

4. 为什么必须先擦除后才能写 ​

我们写入 FLASH 的时候,需要先擦除才能写,这是由其底层物理结构——NOR Flash 存储单元的工作原理决定的。

每个存储单元可以看作一个微小的、带有“陷阱”的晶体管。这个“陷阱”就是浮栅。状态“1”(已擦除状态):浮栅上没有电子。此时晶体管的阈值电压较低,容易导通。状态“0”(已写入状态):通过编程(写入)操作,向浮栅注入电子。这使得晶体管的阈值电压变高,难以导通。

写入(编程):是通过沟道热电子注入或 F-N 隧穿的方式,强行将电子“推”进浮栅。这个过程相对精确且缓慢。擦除是通过 F-N 隧穿的方式,施加一个强反向电场,将电子从浮栅上“拉”出来。这个过程是以整个扇区为单位同时进行的,非常“暴力”,且耗时较长。

核心原因:只能从“1”变为“0”,不能从“0”变为“1”。写入操作的本质是将比特位从 1 改变为 0。我们无法通过写入操作将 0 变回 1。擦除操作的本质是将整个扇区的所有比特位一次性重置为 1。

举个例子:

假设一个存储单元当前的值是 0(已写入,浮栅有电子)。现在想把它改成 1。

如果直接尝试“写入 1”,电路会尝试向浮栅注入电子,但浮栅上本来就有电子,这个操作是无效的,甚至可能损坏单元。最终,该单元的值仍然会是 0。无法实现从 0 到 1 的翻转。正确的流程是我们必须先执行擦除操作,将整个扇区的单元都强制重置为 1。然后,再执行写入操作,精确地将需要为 0 的位从 1 翻转为 0。

硬件层面还有一层保险:FPEC 在编程前会先读一次目标地址,若发现未被擦除就不执行编程并置 FLASH_SR 的 PGERR 位。唯一的例外是要写的值恰好是 0x0000——它本来就只需要“全部写成 0”,不涉及 0 变 1。

二、 STM32 内部 FLASH ​

这里了解一下内部 Flash 的一些内容,并不涉及对内部 Flash 读写等相关操作中的代码实现。

1. FLASH 简介 ​

flash 是存储芯片的一种,通过特定的程序可以修改里面的数据。FLASH 在电子以及半导体领域内往往表示 Flash Memory 的意思,即平时所说的“闪存”,全名叫 Flash EEPROM Memory。它结合了 ROM 和 RAM 的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM 的优势),使数据不会因为断电而丢失。

在 STM32 芯片内部有一个 FLASH 存储器,它主要用于存储代码,它是一种 nor flash。我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中,由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。

除了使用外部的工具(如下载器)读写 STM32 的内部 FLASH 外,STM32 芯片在运行的时候,也能对自身的内部 FLASH 进行读写,因此,若内部 FLASH 存储了应用程序后还有剩余的空间,我们可以把它像外部 SPI-FLASH 那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。

由于访问内部 FLASH 的速度要比外部的 SPI-FLASH 快得多,所以在紧急状态下常常会使用内部 FLASH 存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部 FLASH 中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部 FLASH 的操作。

2. 资料查找 ​

关于 STM32 的内部 Flash 的相关资料是没有在 STM32 中文参考手册 中有所体现的,关于 Flash 的相关资料,ST 官方有另外的文档,叫做 STM32F10xxx 闪存编程手册,英文版在这里:STM32F10xxx Flash memory microcontrollers,这个手册也有中文版:STM32F10xxx 闪存编程参考手册。

3. STM32 内部 Flash 框图 ​

我们可以看一下 STM32 中文参考手册 的 2.1 系统构架:

image-20230530174140633

除了使用外部的工具(如下载器)读写内部 FLASH 外,STM32 芯片在运行的时候,也能对自身的内部 FLASH 进行读写,因此,若内部 FLASH 存储了应用程序后还有剩余的空间,我们可以把它像外部 SPI-FLASH 那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。

由于访问内部 FLASH 的速度要比外部的 SPI-FLASH 快得多,所以在紧急状态下常常会使用内部 FLASH 存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部 FLASH 中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部 FLASH 的操作。

4. 内部 FLASH 构成 ​

4.1 组成 ​

STM32 的内部 FLASH(闪存模块)由:主存储器、信息块和闪存存储器接口寄存器等 3 部分组成。这一部分的相关说明我们需要查看 STM32F10xxx 闪存编程参考手册,这个中文版本的 1.2 闪存模块组织,也有英文版本的(STM32F10xxx Flash memory microcontrollers),其实在 STM32 中文参考手册 的 2.3.3 嵌入式闪存这一节也有这个表。在 ST 官网上可以找到相应的英文版本。如下图为 STM32F1 系列芯片大容量产品的闪存模块组织图:

image-20230530135945546

注意上表中的主存储器是本实验板使用的 STM32F103ZET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。

需要知道的是主存储器和信息块的写入,由内嵌的闪存编程/擦除控制器(FPEC)管理,编程与擦除的高电压由内部产生。在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行,即在进行写或擦除操作时,不能进行代码或数据的读取操作。

4.2 主存储器 ​

一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域(主闪存存储器),它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、512K FLASH 都是指这个区域的大小。

对于 STM32F103ZET6 型号芯片,主存储器分为256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。从上图可以看出主存储器的起始地址就是 0x08000000,当启动引脚 BOOT0 为低电平时,系统将从 0x08000000 地址开始读取代码,也就是从主存储器(Main Flash memory)启动。

【注意】

上表中的主存储器是 STM32F103ZET6 型号芯片的参数,芯片型号中的 E 就表示这是 STM32F1 系列的大容量产品,并且容量为 512KB。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。芯片型号各部分代表的含义其实可以查看芯片手册的《7 Part numbering》这一节,在芯片手册上有对芯片型号的详细说明。

image-20230530140426886

4.3 信息块 ​

该部分分为 2 个小部分,分别是启动程序代码和用户选择字节。

(1)系统存储器:这部分是用来存储 ST 芯片出厂时已经固化了的启动代码,用于实现串口、USB 以及 CAN 等 ISP 烧录功能。这个也可以叫系统存储器,都是一个意思,STM32F10xxx Flash memory microcontrollers 的 1.2 Flash module organization 一节中,这个地方是叫 system memory,个人觉得还是叫系统存储器更符合原版手册。当启动引脚 BOOT0 为高电平,BOOT1 为低电平时,系统将从 0x1FFFF000 地址开始读取代码,该代码将从 UART 收到的数据写到主存储器。也就是通过 UART 实现程序的下载。

(2)选项字节:用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16 字节。可以通过修改 FLASH 的选项控制寄存器修改。

4.4 接口寄存器 ​

闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制机构。这部分其实就包含了 STM32 的 FPEC(闪存编程和擦除控制器)模块,我们对 FLASH 的操作就是通过这个模块来实现的,这个模块包含了 7 个 32 位寄存器,分别是:

寄存器名称地址说明
FLASH_KEYR0x4002_2004 – 0x4002_2007FPEC 键寄存器
FLASH_OPTKEYR0x4002_2008 – 0x4002_200B选择字节键寄存器
FLASH_SR0x4002_200C – 0x4002_200F闪存状态寄存器
FLASH_CR0x4002_2010 – 0x4002_2013闪存控制寄存器
FLASH_AR0x4002_2014 – 0x4002_2017闪存地址寄存器
FLASH_OBR0x4002_201C – 0x4002_201F选择字节寄存器
FLASH_WRPR0x4002_2020 – 0x4002_2023写保护寄存器

【说明】其中 FPEC 总共有 3 个键值:RDPRT 键 = 0x000000A5;KEY1 = 0x45670123;KEY2 = 0xCDEF89AB。

三、 FLASH 相关寄存器 ​

这一部分我们就需要参考 STM32F10xxx 闪存编程参考手册 的 3 寄存器说明啦。这里只简单介绍几个,主要我们还是用库函数操作,这里简单了解下。

1. FPEC 键寄存器(FLASH_KEYR) ​

image-20230530174859848

该寄存器主要用来解锁 FPEC,必须在该寄存器写入特定的序列(KEY1 和 KEY2)解锁后,才能对 FLASH_CR 寄存器进行写操作。

2. 闪存控制寄存器(FLASH_CR) ​

image-20230530175141413

(1)LOCK 位,该位用于指示 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。

(2)STRT 位,该位用于开始一次擦除操作。在该位写入 1,将执行一次擦除操作。

(3)PER 位,该位用于选择页擦除操作,在页擦除的时候,需要将该位置 1。

(4)PG 位,该位用于选择编程操作,在往 FLASH 写数据的时候,该位需要置 1。

其他的位可以看参考手册。

3. 闪存状态寄存器(FLASH_SR) ​

image-20230530175015147

该寄存器主要用来指示当前 FPEC 的操作编程状态。各位描述如下:

image-20230530175249232

4. 闪存地址寄存器(FLASH_AR) ​

image-20230530175343778

这些位由硬件修改为当前/最后使用的地址。在页擦除操作中,软件必须修改这个寄存器以指定要擦除的页。

FAR:闪存地址 当进行编程时选择要编程的地址,当进行页擦除时选择要擦除的页。注意:当 FLASH_SR 中的 BSY 位为 1 时,不能写这个寄存器。

四、 选项字节与读写保护 ​

在实际发布的产品中,在 STM32 芯片的内部 FLASH 存储了控制程序,如果不作任何保护措施的话,可以使用下载器直接把内部 FLASH 的内容读取回来,得到 bin 或 hex 文件格式的代码拷贝,别有用心的厂商会利用该方法山寨产品。为此,STM32 芯片提供了多种方式保护内部 FLASH 的程序不被非法读取,但在默认情况下该保护功能是不开启的,若要开启该功能,需要改写内部 FLASH 选项字节(Option Bytes)中的配置。

1. 选项字节的内容 ​

1.1 选项字节的构成 ​

选项字节是一段特殊的 FLASH 空间,STM32 芯片会根据它的内容进行读写保护配置,选项字节的构成见下表,可以参考 STM32F10xxx 闪存编程参考手册 的 2.5 选择字节说明:

image-20230531095555140

STM32F103 系列芯片的选项字节有 8 个配置项,即上表中的 USER、RDP、DATA0/1 及 WRP0/1/2/3,而表中带 n 的同类项是该项的反码,即 nUSER 的值等于 (~USER)、nRDP 的值等于 (~RDP),STM32 利用反码来确保选项字节内容的正确性。选项字节的 8 个配置项具体的数据位配置说明如下:

image-20230531100044334

我们主要学习选项字节配置中的 RDP 位和 WRPx 位,它们分别用于配置读保护和写保护。

1.2 RDP 读保护 ​

1.2.1 读保护说明 ​

修改选项字节的 RDP 位的值可设置内部 FLASH 为以下保护级别:

(1)0xA5:无保护,这是 STM32 的默认保护级别,它没有任何读保护,读取内部 FLASH 的内容都没有任何限制。也就是说,第三方可以使用调试器等工具,获取该芯片 FLASH 中存储的程序,然后可以把获得的程序以 bin 和 hex 的格式下载到另一块 STM32 芯片中,加上 PCB 抄板技术,轻易复制出同样的产品。

(2)其它值:使能读保护,把 RDP 配置成除 0xA5 外的任意数值,都会使能读保护。在这种情况下,若使用调试功能(使用下载器、仿真器)或者从内部 SRAM 自举时都不能对内部 FLASH 作任何访问(读写、擦除都被禁止);而如果 STM32 是从内部 FLASH 自举时,它允许对内部 FLASH 的任意访问。也就是说,任何尝试从外部访问内部 FLASH 内容的操作都被禁止。例如,无法通过下载器读取它的内容,或编写一个从内部 SRAM 启动的程序,若该 SRAM 启动的程序读取内部 FLASH,会被禁止。而如果是芯片原本的内部 FLASH 程序自己访问内部 FLASH(即从 FLASH 自举的程序),是完全没有问题的,例如芯片本身的程序,若包含有指针对内部 FLASH 某个地址进行的读取操作,它能获取正常的数据。

另外,被设置成读保护后,FLASH 前 4K 字节的空间会强制加上写保护,也就是说,即使是从 FLASH 启动的程序,也无法擦写这 4K 字节空间的内容;而对于前 4K 字节以外的空间,读保护并不影响它对其它空间的擦除/写入操作。利用这个特性,可以编写 IAP 代码(In Application Program)更新 FLASH 中的程序,它的原理是通过某个通讯接口获取将要更新的程序内容,然后利用内部 FLASH 擦写操作把这些内容烧录到自己的内部 FLASH 中,实现应用程序的更新,该原理类似串口 ISP 程序下载功能,只不过 ISP 这个接收数据并更新的代码由 ST 提供,且存放在系统存储区域,而 IAP 是由用户自行编写的,存放在用户自定义的 FLASH 区域,且通讯方式可根据用户自身的需求定制,如 IIC、SPI 等,只要能接收到数据均可。

芯片被配置成读保护后根据不同的使用情况,访问权限不同,如下表:

image-20230531100517228
1.2.2 解除读保护 ​

当需要解除芯片的读保护时,要把选项字节的 RDP 位重新设置为 0xA5。在解除保护前,芯片会自动触发擦除主 FLASH 存储器的全部内容,即解除保护后原内部 FLASH 的代码会丢失,从而防止降级后原内容被读取到。

1.3 WRP 写保护 ​

1.3.1 写保护说明 ​

使用选项字节的 WRP0/1/2/3 可以设置主 FLASH 的写保护,防止它存储的程序内容被修改。写保护的配置一般以 4K 字节为单位,除 WRP3 的最后一位比较特殊外,每个 WRP 选项字节的一位用于控制 4K 字节的写访问权限,把对应 WRP 的位置 0 即可把它匹配的空间加入写保护。被设置成写保护后,主 FLASH 中的内容使用任何方式都不能被擦除和写入,写保护不会影响读访问权限,读访问权限完全由前面介绍的读保护设置限制。

对于大容量产品,选择字节 WRPx 中的每一个比特位用于保护主存储器中 2 个存储页(2K 字节/页),但是 WRP3 的位 7 用于保护第 62 ~ 255 页。WRP0:第 0 ~ 15 页的写保护,WRP1:第 16 ~ 31 页的写保护,WRP2:第 32 ~ 47 页的写保护,WRP3:位 0 ~ 6 提供第 48 ~ 61 页的写保护,位 7 提供第 62 ~ 255 页的写保护。

1.3.2 解除写保护 ​

解除写保护是逆过程,把对应 WRP 的位置 1 即可把它匹配的空间解除写保护。解除写保护后,主 FLASH 中的内容不会像解读保护那样丢失,它会被原样保留。

2. 修改选项字节的过程 ​

修改选项字节的内容可修改读写保护配置,不过选项字节复位后的默认状态是始终可以读但被写保护的,因此它具有类似前面提到的 FLASH_CR 寄存器的访问限制,要想修改,需要先对 FLASH_OPTKEYR 寄存器写入解锁编码。由于修改选项字节时也需要访问 FLASH_CR 寄存器,所以同样也要对 FLASH_KEYR 写入解锁编码。修改选项字节的整个过程总结如下:

  • 解除 FLASH_CR 寄存器的访问限制
    • 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
    • 再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB
  • 解除对选项字节的访问限制
    • 往 FLASH_OPTKEYR 中写入 KEY1 = 0x45670123
    • 再往 FLASH_OPTKEYR 中写入 KEY2 = 0xCDEF89AB
  • 配置 FLASH_CR 的 OPTPG 位,准备修改选项字节
  • 直接使用指针操作修改选项字节的内容,根据需要修改 RDP、WRP 等内容
  • 对于读保护的解除,由于它会擦除 FLASH 的内容,所以需要检测状态寄存器标志位以确认 FLASH 擦除操作完成
  • 若是设置读保护及其解除,需要给芯片重新上电复位,以使新配置的选项字节生效;对于设置写保护及其解除,需要给芯片进行系统复位,以使新配置的选项字节生效

五、 可读写空间计算 ​

其实我们读写 FLASH 的时候会有问题的,因为我们写好的代码也是保存在 FLASH 中的,万一我们不小心擦除了我们的代码存储区域,这不就完了,那怎么确保我们要进行读写的 FLASH 地址空间不会跟我们程序使用的 FLASH 区域产生冲突呢?下边就一起来了解一下吧。

1. 整个工程的空间分布 ​

在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产的 .map 后缀文件,可以了解程序存储到了哪些区域,我们可以到工程目录中的 Listing 文件夹中找到,也可以通过下图提示找到:

image-20230531075735423

打开 map 文件后,查看文件最后部分的区域,可以看到一段以 Memory Map of the image 开头的记录:

c
==============================================================================

Memory Map of the image  //存储分布映像

  Image Entry point : 0x08000131
  /* 程序 ROM 加载空间 */
  Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00000a38, Max: 0x00080000, ABSOLUTE, COMPRESSED[0x00000648])
    /* 程序 ROM 执行空间 */
    Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00000638, Max: 0x00080000, ABSOLUTE)
	/* 地址分布列表 */
    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x08000000   0x08000000   0x00000130   Data   RO            3    RESET               startup_stm32f10x_hd.o
    0x08000130   0x08000130   0x00000000   Code   RO         3210  * .ARM.Collect$$$$00000000  mc_w.l(entry.o)
// 中间部分省略......
      
    Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08000638, Size: 0x00000800, Max: 0x00010000, ABSOLUTE, COMPRESSED[0x00000010])

    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x20000000   COMPRESSED   0x00000400   Data   RW         3110    .data               main.o
    0x20000400        -       0x00000400   Zero   RW            1    STACK               startup_stm32f10x_hd.o


==============================================================================

这一段是就是之前分析 MDK 工程文件使用的 90-MDK 编译过程及文件全解/MDK 文件详解-GPIO 输出—多彩流水灯 的 ROM 存储器分布映像,在 STM32 芯片中,ROM 区域的内容就是指存储到内部 FLASH 的代码。其中 Memory Map of the image 表示这是存储分布映像部分。

2. 程序 ROM 的加载与执行空间 ​

前边我们打开的 map 文件中中有两段分别以“Load Region LR_ROM1”及“Execution Region ER_IROM1”开头的内容:

image-20230531080159961

Load Region LR_ROM1 描述的是程序的加载空间,Execution Region ER_IROM1 描述的是程序的执行空间。在芯片刚上电运行时,会加载程序及数据。例如,它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从 ROM 复制到 RAM 空间,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。

在上面 map 文件的描述中,我们了解到加载及执行空间的基地址(Base)都是 0x08000000,它正好是 STM32 内部 FLASH 的首地址,即 STM32 的程序存储空间就直接是执行空间;它们的大小(Size)分别为 0x00000638 及 0x00000a38,我们会发现执行空间的大小比加载空间的大小要小,执行空间的 ROM 比较小的原因就是因为部分 RW-data 类型的变量被拷贝到 RAM 空间了;它们的最大空间(Max)均为 0x00080000,即 512K 字节,它指的是内部 FLASH 的最大空间。

计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应用程序使用的内部 FLASH 是从 0x08000000 至(0x08000000 + 0x00000a38)地址的空间区域。

3. ROM 空间分布表 ​

在加载及执行空间总体描述之后,紧接着一个 ROM 详细地址分布表,它列出了工程中的各个段(如函数、常量数据)所在的地址 BaseAddr 及占用的空间 Size:

c
	/* 地址分布列表 */
    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x08000000   0x08000000   0x00000130   Data   RO            3    RESET               startup_stm32f10x_hd.o
    0x08000130   0x08000130   0x00000000   Code   RO         3210  * .ARM.Collect$$$$00000000  mc_w.l(entry.o)
// 中间部分省略......
    0x080004f6   0x080004f6   0x00000002   PAD
    0x080004f8   0x080004f8   0x00000120   Code   RO         3109    i.main              main.o
    0x08000618   0x08000618   0x00000020   Data   RO         3229    Region$$Table       anon$$obj.o

列表中的 Type 说明了该段的类型,CODE 表示代码,DATA 表示数据,而 PAD 表示段之间的填充区域,它是无效的内容,PAD 区域往往是为了解决地址对齐的问题。观察表中的最后一项,它的基地址是 0x08000618,大小为 0x00000020,可知它占用的最高的地址空间为:

shell
0x08000618  + 0x00000020 = 0x08000638

我们发现跟执行区域的最高地址 0x08000638 一样,但它们比加载区域说明中的最高地址 0x00000a38 要小,所以我们以加载区域的大小为准。

4. 我们可用空间 ​

经过上边的分析,我们知道了从 0x08000000 开始,0x00000a38 大小的区域存储了我们的应用程序,我们计算一下这大概就是 2616 字节的数据,一共就是 2.55 KB,而在我们的 FLASH 中,每一页是 2 KB,所以说,此应用程序将会占用 2 页(4KB)。对比 STM32 大容量产品内部 FLASH 的构成的内部 FLASH 页地址分布表:

image-20220924210401860

我们可以知道可知使用页 0 至页 3 就可以完全存储本应用程序,所以从页 4 后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。

5. 相关计算 ​

5.1 页起始地址计算 ​

比如说,页 4 地址怎么计算?

c
page_addr = 0x0800 0000 + 0x800 * num

比如页 2 的起始地址就等于 0x0800 0000 + 0x800 x 2 = 0x0800 1000。

5.2 页数怎么计算? ​

假设我们现在要写入 7000 个字节的数据,那么它需要占多少页呢?我们直接对 2048 取模就可以得到整数页,但是可能还会有余数,这些也是要占一整个页面的,所以只要余数不是 0,都需要再增加一个页,所以计算方式就是:

c
page_count = (len / sector_size) + ((len % sector_size)?1:0)

比如刚才的 7000 字节:

c
page_count = (7000 / 2048) + ((7000 % 2048)?1:0) = 3 + (0.42?1:0) = 3 + 1 = 4

5.3 从地址获取是第几页 ​

我们要是从指定地址开始写数据,那么这个地址是属于第几页?我们可以这样来计算:

c
page_num = (addr - 0x0800 0000)/sector_size // 取整数部分即可

比如 0x08001900 是第几页?我们带入上边的计算公式,可以得到:

c
page_num = (0x0800 1900 - 0x0800 0000)/2048 = 3

这样我们就得到是第三页啦。


本文档由 markdowncli 技能辅助生成