STM32怎么说也用了好几年了,但是对于它的内存分布,启动过程,总是模棱两可;所以说决定写这篇文章做下梳理,水平有限,欢迎指正;
以下以F407为例
1. STM32地址空间分布
图片出自M3与M4权威指南
+-------------------------+----------------------------------+ | ||
Address Range | Description | |
+-------------------------+----------------------------------+ | ||
0x00000000 - 0x03FFFFFF | 内存别名映射区域 | |
+-------------------------+----------------------------------+ | ||
0x08000000 - 0x080FFFFF | 内部Flash存储器 | |
+-------------------------+----------------------------------+ | ||
0x10000000 - 0x1000FFFF | CCMRAM | |
+-------------------------+----------------------------------+ | ||
0x1FFF0000 - 0x1FFF77FF | 系统存储器 | |
+-------------------------+----------------------------------+ | ||
0x1FFF7800 - 0x1FFF7A0f | opt | |
+-------------------------+----------------------------------+ | ||
0x1FFFC000 - 0x1FFFC00F | 选项字节 | |
+-------------------------+----------------------------------+ | ||
0x20000000 - 0x2001BFFF | SRAM1 112KB | |
+-------------------------+----------------------------------+ | ||
0x20001C00 - 0x2001FFFF | SRAM2 16KB | |
+-------------------------+----------------------------------+ | ||
0x20020000 - 0x2003FFFF | SRAM3 64kB 407不存在 | |
+-------------------------+----------------------------------+ | ||
0x40000000 - 0x5FFFFFFF | 外设寄存器 | |
+-------------------------+----------------------------------+ | ||
0xE0000000 - 0xE00FFFFF | 系统控制空间 | |
+-------------------------+----------------------------------+ |
- [1] 内存别名映射区域
搜了一大堆,不太清楚,可以理解为,程序其实还是从地址0开始执行,如果选择flash启动那么就是将FLASH(0x0800 0000)重映射或者芯片出厂自带的Bootloader(0x1FFF 0000)重映射到地址0,故而代码是下载到 0x80000000 往后的存储空间中,却说运行又是从 0x00000000地址运行的 - [2] 内部Flash存储器
程序这就不用说了,开始时是中断向量表 - [3] CCMRAM
额,其实我也没用过,CCMRAM由内核直接控制,可以使用__attribute__((section(".ccmram")))
指定变量位置,总之就也是RAM,速度还更快 - [4] 系统存储器
这个主要用来存放 STM32F4 的 bootloader 代码用于下载代码;这个地址范围包含了一些特殊用途的存储器区域,主要用于存放一些系统配置信息、唯一设备标识号(Unique Device ID, UID)、Flash管理和保护设置等重要数据 - [5] opt
没用过加1 OTP 区域,即一次性可编程区域,共 528 字节,被分成两个部分,前面 512 字节(32 字节为 1 块,分成 16 块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面 16 字节,用于锁定对应块。 - [6] 选项字节
flash写保护读保护,flash编程用到 - [7] SRAM
内存区域,407ram大小为128KB,也就是上边的SRAM1和SRAM2,手册中把这里分为了两个区域,但是根据STM32407链接文件定义来看,是把这里SRAM1和SRAM2,还是统一作为整个RAM处理;直接作为128KB处理
其中22000000->23FFFFFF的32MB大小是20000000-20100000的位带映射
MEMORY
{CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64KRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128KFLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
}
- [8] 外设寄存器
各种外设寄存器区域
这还是看参考手册,存储器映射吧,其中42000000->43FFFFFF的32MB大小是40000000-40100000的位带映射,我们用来操控IO很方便;提醒一点并不是所有32都有位带操作,F7就不存在 - [9] 系统存储器
没了解过,以后有机会再去看吧
2.关于链接文件
点击查看代码
/* Entry Point */
ENTRY(Reset_Handler) /* 定义程序的入口点为 Reset_Handler *//* Highest address of the user mode stack */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* 设置用户模式栈的最高地址 这个数据在启动文件中会用到,正常情况下是在0x08000000存放,也就是复位中断的前一个字节,表示初始堆栈指针向下增长*//* 定义堆和栈的最小尺寸 */
_Min_Heap_Size = 0x200; /* 最小堆大小 (512 bytes) */
_Min_Stack_Size = 0x400; /* 最小栈大小 (1024 bytes) *//* 内存区域定义 */
MEMORY
{CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K /* 定义CCMRAM区域 */RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K /* 定义SRAM区域 */FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K /* 定义Flash区域 */
}/* 段的定义 */
SECTIONS
{/* 中断向量表存放在Flash中 */.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)) /* 保持中断向量表 */. = ALIGN(4);} >FLASH/* 程序代码和只读数据存放在Flash中 */.text :{. = ALIGN(4);*(.text) /* .text段 (代码) */*(.text*) /* .text*段 (代码) */*(.glue_7) /* glue arm到thumb代码 */*(.glue_7t) /* glue thumb到arm代码 */*(.eh_frame)KEEP (*(.init)) /* 保持初始化代码 */KEEP (*(.fini)) /* 保持结束代码 */. = ALIGN(4);_etext = .; /* 在代码段结束处定义全局符号 */} >FLASH/* 常量数据存放在Flash中 */.rodata :{. = ALIGN(4);*(.rodata) /* .rodata段 (常量, 字符串等) */*(.rodata*) /* .rodata*段 (常量, 字符串等) */. = ALIGN(4);} >FLASH/* ARM特定段 */.ARM.extab :{. = ALIGN(4);*(.ARM.extab* .gnu.linkonce.armextab.*). = ALIGN(4);} >FLASH.ARM :{. = ALIGN(4);__exidx_start = .;*(.ARM.exidx*)__exidx_end = .;. = ALIGN(4);} >FLASH/* 构造函数表 */.preinit_array :{. = ALIGN(4);PROVIDE_HIDDEN (__preinit_array_start = .);KEEP (*(.preinit_array*))PROVIDE_HIDDEN (__preinit_array_end = .);. = ALIGN(4);} >FLASH.init_array :{. = ALIGN(4);PROVIDE_HIDDEN (__init_array_start = .);KEEP (*(SORT(.init_array.*)))KEEP (*(.init_array*))PROVIDE_HIDDEN (__init_array_end = .);. = ALIGN(4);} >FLASH.fini_array :{. = ALIGN(4);PROVIDE_HIDDEN (__fini_array_start = .);KEEP (*(SORT(.fini_array.*)))KEEP (*(.fini_array*))PROVIDE_HIDDEN (__fini_array_end = .);. = ALIGN(4);} >FLASH/* 用于启动代码初始化数据 */_sidata = LOADADDR(.data);/* 初始化数据段存放在RAM中,其初始值在Flash中 */.data :{. = ALIGN(4);_sdata = .; /* 在数据段开始处定义全局符号 */*(.data) /* .data段 */*(.data*) /* .data*段 */*(.RamFunc) /* .RamFunc段 */*(.RamFunc*) /* .RamFunc*段 */. = ALIGN(4);_edata = .; /* 在数据段结束处定义全局符号 */} >RAM AT> FLASH/* CCMRAM段 */_siccmram = LOADADDR(.ccmram);.ccmram :{. = ALIGN(4);_sccmram = .; /* 在CCMRAM段开始处定义全局符号 */*(.ccmram)*(.ccmram*). = ALIGN(4);_eccmram = .; /* 在CCMRAM段结束处定义全局符号 */} >CCMRAM AT> FLASH/* 未初始化数据段存放在RAM中 */.bss :{_sbss = .; /* 在bss段开始处定义全局符号 */__bss_start__ = _sbss;*(.bss)*(.bss*)*(COMMON). = ALIGN(4);_ebss = .; /* 在bss段结束处定义全局符号 */__bss_end__ = _ebss;} >RAM/* 用户堆栈段,用于检查剩余的RAM */._user_heap_stack :{. = ALIGN(8);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;. = ALIGN(8);} >RAM/* 删除编译器库信息 *//DISCARD/ :{libc.a ( * )libm.a ( * )libgcc.a ( * )}/* ARM属性段 */.ARM.attributes 0 : { *(.ARM.attributes) }
}
栈 | ||
空闲空间 | ||
堆 | ||
.bss | ||
.data | ||
.text | ||
---- | ---- | ---- |
3. 启动文件
在启动文件的中断向量表中,开头如下所示,在我们程序地址默认0x08000000开始时,0x08000000地址的数据也就是_estack
,对应链接文件中的值_estack = ORIGIN(RAM) + LENGTH(RAM)
也就是ram最高地址;
0x08000004是复位中断,程序开始也就是进入这里;
点击查看代码
.word _estack.word Reset_Handler.word NMI_Handler.word HardFault_Handler.word MemManage_Handler.word BusFault_Handler.word UsageFault_Handler.word 0.word 0.word 0.word 0.word SVC_Handler.word DebugMon_Handler.word 0.word PendSV_Handler.word SysTick_Handler
点击查看代码
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %functionReset_Handler: ldr sp, =_estack /* 设置堆栈指针 *//* 将数据段的初始值从Flash复制到SRAM */movs r1, #0 /* 初始化偏移量 */b LoopCopyDataInit /*跳转*/
/*完成将有初始化数据的变量,复制到ram,也就是.data段*/
/*r1是偏移量,每次复制后偏移量加4*/
CopyDataInit:ldr r3, =_sidata /* 数据段初始值在Flash中的起始地址 */ldr r3, [r3, r1] /* 从Flash中读取数据 */str r3, [r0, r1] /* 将数据写入SRAM */adds r1, r1, #4 /* 更新偏移量 */b LoopCopyDataInit
/*判断是否复制结束*/
LoopCopyDataInit:ldr r0, =_sdata /* 数据段起始地址 */ldr r3, =_edata /* 数据段结束地址 */adds r2, r0, r1 /* 计算当前地址 */cmp r2, r3 /* 比较当前地址和数据段结束地址 */bcc CopyDataInit /* 如果当前地址小于结束地址,继续复制 */ldr r2, =_sbss /* .bss段起始地址 */b LoopFillZerobss
/*完成将有未初始化数据的变量,复制到ram,也就是.bss段*/
/* 清零.bss段 */
FillZerobss:movs r3, #0 /* 初始化值为0 */str r3, [r2], #4 /* 将0写入.bss段 */adds r2, r2, #4 /* 更新地址 */b LoopFillZerobssLoopFillZerobss:ldr r3, =_ebss /* .bss段结束地址 */cmp r2, r3 /* 比较当前地址和.bss段结束地址 */bcc FillZerobss /* 如果当前地址小于结束地址,继续清零 *//* 调用系统初始化函数 */bl SystemInit/*搜的解释:调用静态构造函数 一般不用C语言没有构造函数的概念,程序进入 main 函数之前执行一些初始化代码。GCC提供了constructor和destructor属性,允许你定义在main函数之前和程序退出时运行的函数。/*完成C库的初始化 确保在程序开始执行其主要逻辑之前正确初始化了 C/C++ 对象*/*/bl __libc_init_array/* 调用应用程序的入口点 */bl mainbx lr /* 返回 */
点击查看代码
pFunction Jump_To_Application;//定义一个函数指针uint32_t JumpAddress;__disable_irq(); //关闭中断JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 0x00000004);//得到复位程序地址Jump_To_Application = (pFunction) JumpAddress; //强制转换为函数__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); //设置栈顶地址,ram完全交给app使用Jump_To_Application();