第一章 关于STM32的flash读写操作在RAM中实现方式
得先解释在Flash上擦写Flash为什么有问题:因为Flash存储器在某一时刻只能处于一种模式:要么被CPU读取(取指/取数),要么被擦写。所以,当CPU执行Flash上的指令时,如果同时擦写同一块存储区域,访问冲突会导致硬件错误。
用一个简单的类比来理解:
在Flash中运行擦写程序:就像你站在一块木板上,同时试图用锯子锯断这块木板。当你锯到脚下时,你必然会掉下去。
在RAM中运行擦写程序:就像你站在坚实的地面上(RAM),去锯旁边的一块木板(Flash)。你可以安全、完整地完成整个锯木过程。
我们通常所说的RAM和ROM在计算机体系结构中指的是内存和存储器的类型,但在嵌入式系统中,特别是STM32中,我们通常将Flash称为ROM(用于存储程序代码和常量数据),将SRAM称为RAM(用于运行时的变量和堆栈)。
特点:可读可写,但掉电后数据会丢失。
用途:用于临时存储数据,如变量、堆栈、动态分配的内存等。
类型:在嵌入式系统中,通常指的是SRAM(静态RAM)或者外部扩展的SDRAM(动态RAM)等。
特点:通常用于存储固定数据,掉电后数据不丢失。但实际上,现在的ROM也可以写入,只是写入方式可能不如RAM方便(例如需要擦除和编程操作,且寿命有限)。
用途:用于存储程序代码、常量数据、固件等。
类型:在STM32中,ROM通常指Flash存储器,也有EEPROM(用于存储需要频繁修改的数据,但容量较小)。
在STM32中,我们通常将程序烧录到Flash(ROM)中,程序运行时,代码从Flash中被读取,变量和数据则在RAM中操作。

使用RAM存储:临时数据、变量、堆栈、动态分配的内存
使用ROM存储:程序代码、常量数据、配置参数、固件
CPU(Central Processing Unit)是计算机的“大脑”,负责执行程序指令,处理数据,控制其他硬件。它通过指令集架构(如ARM、x86)来定义可以执行的操作。
Flash是一种非易失性存储技术,用于长期保存数据,即使断电数据也不会丢失。它通常用于存储操作系统、应用程序、用户数据等。
在STM32这样的嵌入式系统中:
CPU:执行存储在Flash中的程序指令,处理来自外设的数据,控制整个系统的运行。
Flash:存储程序代码(固件)、常量数据等。当STM32启动时,CPU从Flash的特定地址(通常是0x08000000)开始读取指令并执行。

CPU:速度极快,现代CPU的时钟频率可达数GHz,每个时钟周期可以执行多条指令(流水线、超标量技术)。CPU内部还有多级缓存(L1、L2、L3)来加速数据访问。
Flash:访问速度相对较慢。从Flash读取数据需要微秒级时间,写和擦除操作更慢(毫秒级)。因此,CPU通常不会直接从Flash执行指令,而是将代码加载到RAM中执行,或者通过指令缓存来加速。
CPU:由数十亿个晶体管组成,包括算术逻辑单元(ALU)、控制单元、寄存器、缓存等。制造工艺极其复杂,目前已经达到纳米级别(如5nm、3nm)。
Flash:使用浮栅晶体管,通过 trapped charge 来存储数据。Flash内存被组织成块(Block)和页(Page),擦除操作以块为单位,写入以页为单位。
CPU:只要在规定的电压和温度范围内使用,寿命很长。但过度超频、高温会缩短其寿命。
Flash:每个存储单元有擦写次数限制(通常10万次左右)。超过次数后,存储单元可能损坏。因此,在嵌入式系统中,需要避免频繁写入同一Flash区域,通常采用磨损均衡技术。
启动过程:
上电 → CPU从Flash固定地址读取初始栈指针CPU从Flash读取复位向量 → 跳转到Reset_HandlerCPU执行Flash中的启动代码 → 初始化系统CPU执行Flash中的main函数 → 应用程序运行
// 简化的CPU工作流程
while(1) {
// 1. 取指令:从PC指向的地址读取指令
instruction = memory[PC];
// 2. 译码:理解指令含义
decoded_instruction = decode(instruction);
// 3. 执行:执行指令操作
execute(decoded_instruction);
// 4. 更新PC:指向下一条指令
PC = next_instruction_address(PC);
}
// 在Flash中的代码
0x08000100: void function_A(void) {
0x08000102: int x = 10;
0x08000106: function_B(x);
0x0800010A: }
0x08000120: void function_B(int param) {
0x08000122: printf("Value: %d", param);
0x08000130: }
; 调用function_B时的汇编代码
0x08000106: MOV R0, #10 ; 准备参数
0x08000108: BL 0x08000120 ; 分支链接到function_B地址
; BL指令会:
; 1. 将返回地址(0x0800010A)保存到LR寄存器
; 2. 将PC设置为0x08000120
; 在function_B中
0x08000120: PUSH {R0, LR} ; 保存寄存器和返回地址
0x08000122: ... ; 执行函数体
0x0800012E: POP {R0, LR} ; 恢复寄存器
0x08000130: BX LR ; 跳转回返回地址
小结一下
CPU和flash之间交互的方式为:CPU读取地址,然后执行该地址处的代码(函数)。但更准确的说法是:CPU通过程序计数器指向的地址读取指令并执行,函数调用就是让程序计数器跳转到函数的起始地址。
指令缓存是CPU内部的一块高速内存,用于存储最近使用的指令。当CPU需要执行指令时,它首先检查指令缓存中是否已经存在该指令。如果存在(缓存命中),则直接从缓存中读取指令,避免了访问Flash的延迟。如果不存在(缓存未命中),则从Flash中读取指令,并将其存入缓存中,以便后续使用。。

注意事项:
I-Cache是透明的,对于程序员来说,通常不需要额外操作。
在极少数情况下,如果Flash内容被更改(例如通过程序自我更新),则需要无效化(Invalidate)I-Cache,以确保CPU不会读取到旧的指令。这可以通过SCB_InvalidateICache()函数实现。
1.在项目文件夹下新建文件并自定义命名为“STM32F407ZGT6.sct”

2.在Keil中查看/创建分散加载文件:

先把结果放出来,下面进行解释
LR_IROM1 0x08000000 0x00050000 { ; 加载区域: 320KB Flash
; Flash执行区域 - 大部分代码在这里执行
ER_IROM1 0x08000000 0x00050000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO) ; 普通只读代码和常量
.ANY (+XO) ; 执行代码
}
; RAM数据区域 - 变量和数据
RW_IRAM1 0x20000000 0x00018000 { ; 减少到96KB,为RAM代码留空间
.ANY (+RW +ZI)
}
; RAM代码执行区域 - Flash操作函数在这里运行
ER_IRAM2 0x20018000 0x00004000 { ; 16KB用于RAM中执行的代码
*.o (RAM_FUNC) ; RAM中运行的函数
}
; 剩余的RAM作为数据区域
RW_IRAM3 0x2001C000 0x00004000 { ; 最后16KB
.ANY (+RW +ZI)
}
}
stm32默认的分散加载文件解读
LR_IROM1 0x08000000 0x00100000 { ; 加载区域,起始地址0x08000000,大小1MB
; 第一个执行区域:Flash区域,用于存放只读代码和数据
ER_IROM1 0x08000000 0x00100000 {
*.o (RESET, +First) ; 中断向量表
*(InRoot$$Sections) ; 库代码
.ANY (+RO) ; 所有只读代码和常量,包括RAM_FUNC的加载地址
}
; 第二个执行区域:RAM区域,用于存放读写数据,以及运行RAM_FUNC
RW_IRAM1 0x20000000 0x00020000 {
.ANY (+RW +ZI) ; 所有读写数据和未初始化数据
}
; 新增一个执行区域,用于在RAM中运行代码,但代码在Flash中加载,运行时拷贝到RAM
ER_IRAM1 0x20000000 0x00020000 {
.ANY (RAM_FUNC) ; 将RAM_FUNC段放在RAM中执行
}
}
对于单个运行函数的定义
// 定义一个在RAM中运行的函数
__attribute__((section("RAM_FUNC"))) void Flash_Write(uint32_t address, uint32_t data)
{
// 在这里实现Flash写操作
}
也可以对多个函数进行定义,如下
#define RAM_FUNC __attribute__((section("RAM_FUNC")))
RAM_FUNC void func1(void) {
// 函数体
}
RAM_FUNC void func2(void) {
// 函数体
}
这里section中的字段“RAM_FUNC”和分散加载文件中的字段要对齐,如:
ER_IRAM2 0x20018000 0x00004000 { ; 16KB用于RAM中执行的代码
*.o (RAM_FUNC) ; RAM中运行的函数
}
直接复制这个函数就行,然后在main函数下执行,主要功能是将flash中存储的代码地址和执行区域地址对应起来
extern uint32_t Load$$ER_IRAM2$$Base; //获取数据存储时的位置
extern uint32_t Image$$ER_IRAM2$$Base; // 获取数据执行时的位置
extern uint32_t Image$$ER_IRAM2$$Length; //表示ER_IRAM2区域的大小(字节数)
void Copy_RAM_Functions(void)
{
uint32_t *src = &Load$$ER_IRAM2$$Base;
uint32_t *dst = &Image$$ER_IRAM2$$Base;
uint32_t len = (uint32_t)&Image$$ER_IRAM2$$Length;
// 按字拷贝(假设长度是4的倍数)
for (uint32_t i = 0; i < len; i += 4) {
*dst++ = *src++;
}
}

编译后路径下会生成一个.map的文件,用记事本打开,并搜索RAM下运行的函数名称,就可以看到你的函数名称了,通过核对其执行地址和你在分散文件中设置的地址区域是否符合就可以确定是否成功了。



执行地址在设定的定制区域内,说明你的函数已经在RAM中运行了,大功告成。
到此就结束了,记得点赞关注哦,嘿嘿