ARM Cortex-M3能否从SRAM启动?如何用startup.s实现代码搬移?
从SRAM(0x20000000-0x3fffffff)启动的实现方案
是的,你必须把代码从Flash/ROM(0x00000000-0x1fffffff)复制到目标SRAM区域——因为MCU的硬件启动逻辑默认从0x00000000地址取指令,而SRAM是易失性存储,上电后为空,必须先把代码从非易失的Flash/ROM复制过去,再跳转到SRAM执行。下面是两种具体实现方式,以及关键配置要点:
一、通过Startup.s完成代码复制与跳转
这是最常用的方式,因为启动代码是系统执行的第一段代码,能确保在任何全局变量初始化前完成复制操作。
汇编代码示例(修改startup.s)
; 定义地址与代码长度,根据实际项目调整 FLASH_START EQU 0x00000000 SRAM_START EQU 0x20000000 CODE_SIZE EQU 0x10000 ; 假设代码总长度为64KB,需从编译后的.map文件获取实际值 Reset_Handler PROC EXPORT Reset_Handler [WEAK] ; 保留原有栈初始化代码 LDR R0, =__initial_sp MOV SP, R0 ; 开始复制代码到SRAM LDR R1, =FLASH_START LDR R2, =SRAM_START LDR R3, =CODE_SIZE copy_loop: CMP R3, #0 BEQ copy_done LDR R4, [R1], #4 STR R4, [R2], #4 SUB R3, R3, #4 B copy_loop copy_done: ; 跳转到SRAM中的复位向量(向量表第2项为PC初始值) LDR R0, =SRAM_START + 0x4 LDR R0, [R0] BX R0 ENDP
配套链接脚本修改
必须调整链接脚本,指定代码段的加载地址(LMA,Flash中的位置)和运行地址(VMA,SRAM中的位置),否则代码中的地址引用会指向Flash,导致跳转后执行错误:
MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256K ; 根据实际Flash大小调整 SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512K ; 根据实际SRAM大小调整 } SECTIONS { .text : { . = ALIGN(4); *(.isr_vector) /* 中断向量表 */ *(.text) /* 主代码段 */ *(.text*) /* 其他代码段 */ *(.rodata) /* 只读数据 */ *(.rodata*) . = ALIGN(4); _etext = .; /* 代码段结束标记 */ } >SRAM AT>FLASH ; 运行在SRAM,加载在FLASH .data : AT = _etext { . = ALIGN(4); _sdata = .; /* 初始化数据段起始 */ *(.data) *(.data*) . = ALIGN(4); _edata = .; /* 初始化数据段结束 */ } >SRAM .bss : { . = ALIGN(4); _sbss = .; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); _ebss = .; } >SRAM }
二、通过C代码实现(适用于不想修改汇编的场景)
如果不想改动startup.s,可以在main函数最开始执行代码复制,但要注意:此时全局变量还未初始化,复制函数不能依赖任何全局数据,必须用硬编码地址和寄存器操作。
C代码示例
#include <stdint.h> // 硬编码地址与代码长度,需匹配实际项目 #define FLASH_START 0x00000000UL #define SRAM_START 0x20000000UL #define CODE_SIZE 0x10000UL // 从.map文件获取实际代码长度 // 纯寄存器操作的复制函数,不依赖全局变量 static void copy_code_to_sram(void) { uint32_t *src = (uint32_t*)FLASH_START; uint32_t *dest = (uint32_t*)SRAM_START; uint32_t count = CODE_SIZE / 4; // 按4字节对齐复制 while (count--) { *dest++ = *src++; } } // 标记该函数存放在SRAM区域 __attribute__((section(".sram_main"))) void sram_main(void) { // 这里编写实际业务代码,比如LED闪烁、外设初始化等 while (1) { // 示例操作 } } int main(void) { // 先完成代码复制 copy_code_to_sram(); // 跳转到SRAM中的业务函数 void (*sram_main_ptr)(void) = (void(*)())&sram_main; sram_main_ptr(); // 永远不会执行到此处 while (1); }
配套链接脚本调整
需要在链接脚本中添加.sram_main段的配置,确保它被分配到SRAM区域:
// 在SECTIONS中添加: .sram_main : { . = ALIGN(4); *(.sram_main) . = ALIGN(4); } >SRAM
关键注意事项
- 代码长度必须准确:CODE_SIZE不能小于编译后的实际代码总长度(可从.map文件的
_etext - FLASH_START计算),否则会复制不完整;也不要过大,避免覆盖SRAM中的栈、堆区域。 - 栈指针配置:确保startup.s中的栈指针指向SRAM的空闲区域,不要和代码段重叠。
- 中断向量表处理:如果需要在SRAM中响应中断,必须把中断向量表复制到SRAM,然后配置MCU的向量表偏移寄存器(如STM32的
SCB->VTOR)指向SRAM中的向量表起始地址。 - 调试注意事项:调试时要注意选择"从SRAM运行"的调试配置,避免调试器仍从Flash加载代码。
内容的提问来源于stack exchange,提问作者ADemonevil
相关产品推荐
相关产品推荐

