关于STM32的flash读写操作在RAM中实现方式

  • 时间:2025-11-07 14:26 作者: 来源: 阅读:0
  • 扫一扫,手机访问
摘要:第一章 关于STM32的flash读写操作在RAM中实现方式 文章目录 背景一、了解RAM和ROM?下面详细解释RAM和ROM的区别:1.RAM(Random Access Memory,随机存取存储器)2.ROM(Read Only Memory,只读存储器) 具体区别如下表:小结一下 二、CPU和flash功能定义介绍1. 基本功

第一章 关于STM32的flash读写操作在RAM中实现方式


文章目录

背景一、了解RAM和ROM?下面详细解释RAM和ROM的区别:1.RAM(Random Access Memory,随机存取存储器)2.ROM(Read Only Memory,只读存储器) 具体区别如下表:小结一下 二、CPU和flash功能定义介绍1. 基本功能2. 在嵌入式系统(如STM32)中的角色3. 访问速度4. 物理结构和制造工艺5. 寿命和可靠性6.协同关系 二、CPU和flash在程序执行过程中是如何执行的🔍 CPU执行函数的基本原理1. 程序计数器(PC)的作用2. 函数调用的具体过程普通函数调用: 调用过程: 三、理解STM32F4的指令缓存机制(I-Cache)概念 四、在RAM运行指定函数1.分散加载文件(.sct)2.编辑分散加载文件关于如何编写分散加载文件 3.对要在ram中运行函数进行定义4.编写拷贝文件 五、检验程序是否已经运行总结


背景

得先解释在Flash上擦写Flash为什么有问题:因为Flash存储器在某一时刻只能处于一种模式:要么被CPU读取(取指/取数),要么被擦写。所以,当CPU执行Flash上的指令时,如果同时擦写同一块存储区域,访问冲突会导致硬件错误。
用一个简单的类比来理解:
在Flash中运行擦写程序:就像你站在一块木板上,同时试图用锯子锯断这块木板。当你锯到脚下时,你必然会掉下去。
在RAM中运行擦写程序:就像你站在坚实的地面上(RAM),去锯旁边的一块木板(Flash)。你可以安全、完整地完成整个锯木过程。


一、了解RAM和ROM?

我们通常所说的RAM和ROM在计算机体系结构中指的是内存和存储器的类型,但在嵌入式系统中,特别是STM32中,我们通常将Flash称为ROM(用于存储程序代码和常量数据),将SRAM称为RAM(用于运行时的变量和堆栈)。

下面详细解释RAM和ROM的区别:

1.RAM(Random Access Memory,随机存取存储器)

特点:可读可写,但掉电后数据会丢失。

用途:用于临时存储数据,如变量、堆栈、动态分配的内存等。

类型:在嵌入式系统中,通常指的是SRAM(静态RAM)或者外部扩展的SDRAM(动态RAM)等。

2.ROM(Read Only Memory,只读存储器)

特点:通常用于存储固定数据,掉电后数据不丢失。但实际上,现在的ROM也可以写入,只是写入方式可能不如RAM方便(例如需要擦除和编程操作,且寿命有限)。

用途:用于存储程序代码、常量数据、固件等。

类型:在STM32中,ROM通常指Flash存储器,也有EEPROM(用于存储需要频繁修改的数据,但容量较小)。

在STM32中,我们通常将程序烧录到Flash(ROM)中,程序运行时,代码从Flash中被读取,变量和数据则在RAM中操作。

具体区别如下表:

小结一下

使用RAM存储:临时数据、变量、堆栈、动态分配的内存

使用ROM存储:程序代码、常量数据、配置参数、固件

二、CPU和flash功能定义介绍

1. 基本功能

CPU(Central Processing Unit)是计算机的“大脑”,负责执行程序指令,处理数据,控制其他硬件。它通过指令集架构(如ARM、x86)来定义可以执行的操作。

Flash是一种非易失性存储技术,用于长期保存数据,即使断电数据也不会丢失。它通常用于存储操作系统、应用程序、用户数据等。

2. 在嵌入式系统(如STM32)中的角色

在STM32这样的嵌入式系统中:
CPU:执行存储在Flash中的程序指令,处理来自外设的数据,控制整个系统的运行。
Flash:存储程序代码(固件)、常量数据等。当STM32启动时,CPU从Flash的特定地址(通常是0x08000000)开始读取指令并执行。

3. 访问速度

CPU:速度极快,现代CPU的时钟频率可达数GHz,每个时钟周期可以执行多条指令(流水线、超标量技术)。CPU内部还有多级缓存(L1、L2、L3)来加速数据访问。

Flash:访问速度相对较慢。从Flash读取数据需要微秒级时间,写和擦除操作更慢(毫秒级)。因此,CPU通常不会直接从Flash执行指令,而是将代码加载到RAM中执行,或者通过指令缓存来加速。

4. 物理结构和制造工艺

CPU:由数十亿个晶体管组成,包括算术逻辑单元(ALU)、控制单元、寄存器、缓存等。制造工艺极其复杂,目前已经达到纳米级别(如5nm、3nm)。

Flash:使用浮栅晶体管,通过 trapped charge 来存储数据。Flash内存被组织成块(Block)和页(Page),擦除操作以块为单位,写入以页为单位。

5. 寿命和可靠性

CPU:只要在规定的电压和温度范围内使用,寿命很长。但过度超频、高温会缩短其寿命。
Flash:每个存储单元有擦写次数限制(通常10万次左右)。超过次数后,存储单元可能损坏。因此,在嵌入式系统中,需要避免频繁写入同一Flash区域,通常采用磨损均衡技术。

6.协同关系

启动过程:

上电 → CPU从Flash固定地址读取初始栈指针CPU从Flash读取复位向量 → 跳转到Reset_HandlerCPU执行Flash中的启动代码 → 初始化系统CPU执行Flash中的main函数 → 应用程序运行

二、CPU和flash在程序执行过程中是如何执行的

🔍 CPU执行函数的基本原理

1. 程序计数器(PC)的作用

// 简化的CPU工作流程
while(1) {
    // 1. 取指令:从PC指向的地址读取指令
    instruction = memory[PC];
    
    // 2. 译码:理解指令含义
    decoded_instruction = decode(instruction);
    
    // 3. 执行:执行指令操作
    execute(decoded_instruction);
    
    // 4. 更新PC:指向下一条指令
    PC = next_instruction_address(PC);
}
2. 函数调用的具体过程
普通函数调用:

// 在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通过程序计数器指向的地址读取指令并执行,函数调用就是让程序计数器跳转到函数的起始地址。


三、理解STM32F4的指令缓存机制(I-Cache)

概念

指令缓存是CPU内部的一块高速内存,用于存储最近使用的指令。当CPU需要执行指令时,它首先检查指令缓存中是否已经存在该指令。如果存在(缓存命中),则直接从缓存中读取指令,避免了访问Flash的延迟。如果不存在(缓存未命中),则从Flash中读取指令,并将其存入缓存中,以便后续使用。。

注意事项:
I-Cache是透明的,对于程序员来说,通常不需要额外操作。

在极少数情况下,如果Flash内容被更改(例如通过程序自我更新),则需要无效化(Invalidate)I-Cache,以确保CPU不会读取到旧的指令。这可以通过SCB_InvalidateICache()函数实现。


四、在RAM运行指定函数

1.分散加载文件(.sct)

1.在项目文件夹下新建文件并自定义命名为“STM32F407ZGT6.sct”

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

项目选项 → Linker → 取消"Use Memory Layout from Target Dialog"点击scatter file 选择新建的文件点击"Edit"来编辑分散加载文件编辑后可进行编译即可,但此时会出错,没有关系因为还有的工作没有做完,接着往下走

2.编辑分散加载文件

先把结果放出来,下面进行解释


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中执行
  }
}

3.对要在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中运行的函数

}

4.编写拷贝文件

直接复制这个函数就行,然后在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中运行了,大功告成。

总结

到此就结束了,记得点赞关注哦,嘿嘿

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】20251031编辑(2025-11-07 15:34)
【系统环境|】看技术文档和手顺方法(2025-11-07 15:34)
【系统环境|】前端(Vue框架)实现主题切换(2025-11-07 15:33)
【系统环境|】c# OpenCV 基于成像色度计的汽车氛围灯亮度色度计算(2025-11-07 15:33)
【系统环境|】JAVA 接口文档优化 —— 用 Knife4j 让前后端对接 “零沟通”(参数、权限、示例全说清)(2025-11-07 15:32)
【系统环境|】BPF for HID drivers(2025-11-07 15:32)
【系统环境|】202506 CCF-GESP编程能力等级认证Scratch一级真题 建议答题时长:60min(2025-11-07 15:31)
【系统环境|】动态调整身份验证安全级别(2025-11-07 15:31)
【系统环境|】【AI辅助生成】QT 3D基础设施技术架构分析 为什么QT 3D技术栈如此复杂?(2025-11-07 15:30)
【系统环境|】HTML 事件(2025-11-07 15:30)
手机二维码手机访问领取大礼包
返回顶部