找回密码
 立即注册
首页 业界区 安全 MCU启动流程、文件与keil配置

MCU启动流程、文件与keil配置

梦霉 前天 22:37
文件

map文件

Section Cross References
这部分内容描述了各个文件之间函数的调用关系,如下图main.o(i.main) refers to sys.o(i.sys_stm32_clock_init) for sys_stm32_clock_init,表示main.c文件中的main函数调用了sys.c中的sys_stm32_clock_init函数。i.main表示main函数的入口地址,i.sys_stm32_clock_init表示sys_stm32_clock_init的入口地址。
1.png

Removing Unused input sections from the image
这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据)。
2.png

Image Symbol Table
映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器中的存储地址、类型、 大小等信息。分为本地符号(Local Symbols)和全局符号(Global Symbols)。
Local Symbols
本地符号(Local Symbols)记录了用static声明的全局变量地址和大小, c文件中函数的地址和用static声明的函数代码大小,汇编文件中的标号地址(作用域:本文件)。
.text 段
内容:代码(程序指令)、常量(const)、中断向量表。
位置:通常放在Flash(只读存储器)。
特点:程序不会在运行时修改.text段内容。
map文件里显示从某个地址(如 0x08000000)开始,大小等于编译出的代码量。
.data 段
内容:已初始化的全局变量/静态变量。
位置:运行时在 RAM,但初始化值存放在Flash。
启动时操作:Reset_Handler会把Flash中的初值拷贝到 RAM。
map文件里会看到 .data 在RAM中的地址范围和大小,也会有对应的Load Region指向Flash地址。
.bss 段
内容:未初始化的全局变量/静态变量,编译器会自动填0。
位置:RAM中。
启动时操作:Reset_Handler会把.bss段区域清零。
map文件里会显示起始地址和大小,初始值全0。
Global Symbols
全局符号(Global Symbols)记录了全局变量的地址和大小, C文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域:全工程)。
Memory Map of the image
映像文件分为加载域(Load Region)和运行域(Execution Region),一个程序可以有多个加载域,一个加载域有至少一个运行域。加载域为映像程序的实际存储区域,运行域是MCU上电后的运行状态。加载域和运行域的简化关系如图所示,在执行main函数之前, RW(有初值且不为0的变量)数据会被拷贝到RAM区,同时还会在RAM里面创建ZI区(初始化为0的变量)。
3.png

映像的入口地址,也就是整个程序运行的起始地址,为0x08000131。
LR_IROM1加载域,其内部包含两个运行域:ER_IROM1和RW_IRAM1。
4.png

5.png

Image component sizes
映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇总信息。
6.png

表示.c/.s 文件生成对象所占空间大小(单位:字节,下同),即.c/.s 文件编
译后所占代码空间的大小。
7.png

表示被提取的库成员( .lib)添加到映像中的部分所占空间大小。
8.png

表示本工程全部程序汇总后的占用情况。
.S文件

栈空间配置
9.png

EQU是宏定义的伪指令, 类似C中的define。定义栈大小为0x00000400字节,1024B(1KB)。
AREA命令汇编一个新的代码段或者数据段。段名为STACK;NOINIT表示不初始化;READWRITE表示可读写;ALIGN=3,表示按照2^3即8字节对齐。
SPACE 分配内存指令, 分配大小为 Stack_Size 字节连续的存储单元给栈空间。
__initial_sp挨着SPACE放置,表示栈的结束地址,栈是从高往低生长,所以结束地址就是栈顶地址。栈用于存放局部变量,函数形参等,属于编译器自动分配和释放的内存,大小不能超过内部SRAM的大小。

查看map文件可以看出,栈大小与.s文件配置相同。
这里只定义了栈大小,栈的起始位置在keil的“option for target”的target里配置。
堆空间配置
11.png

如果不使用C库的malloc和free等函数,就用不到堆空间,可以设置Heap_Size的大小为0。
中断向量表
AREA    RESET, DATA, READONLY
定义一个只读的段名字为RESET,放在数据段里。
.s文件里的中断向量表(.vectors或者叫中断向量表)就是一张函数指针表,本质上是一段连续的存储单元,里面存放的是地址(函数入口地址或者初始栈指针地址),MCU硬件在复位或发生中断时会根据这张表找到要执行的函数。
  1. __Vectors     DCD     __initial_sp               ; Top of Stack
  2.                 DCD     Reset_Handler              ; Reset Handler
  3.                 DCD     NMI_Handler                ; NMI Handler
  4.                 DCD     HardFault_Handler          ; Hard Fault Handler
  5.                 ...
  6. __Vectors_End
复制代码
DCD: 分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。
__initial_sp:初始栈顶指针值(MCU 上电后,SP=这个值)。
Reset_Handler的地址:复位时 CPU 会跳转执行这个函数。
后面依次是NMI、中断服务函数的入口地址。
中断发生时,NVIC硬件会去查这张表,取出对应的函数入口地址,CPU 跳转到这个函数执行。这些函数就是在startup_xxx.s里定义的xxx_Handler。
中断向量表被放置在代码段的最前面。程序在FLASH运行时,向量表的起始地址是0x0800 0000。地址0x0800 0000存放的是栈顶地址。DCD是以四字节对齐分配内存,也就是下个地址是0x0800 0004,存放的是Reset_Handler中断函数入口地址。向量表中存放的都是中断服务函数的函数名, 所以C语言中的函数名对芯片来说实际上就是一个地址。
复位程序
AREA    |.text|, CODE, READONLY
定义一个名为 .text 的只读代码段,存放的是指令(代码)。|...| 表示名字是字符串,不需要符合符号命名规则。
12.png

利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。
PROC表示子程序开始。
EXPORT  Reset_Handler             [WEAK]
声明复位中断向量Reset_Handler为全局属性,这样外部文件就可以调用此复位中断服务。WEAK表示弱定义,如果外部文件优先定义了该标号则首先引用外部定义的标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现。
IMPORT  __main
IMPORT表示该标号来自外部文件。这里表示__main这个函数来自外部的文件。
LDR     R0, =__main
把符号__main的地址加载到寄存器R0。这里的__main不是程序员写的main(),而是C运行时库的入口函数(C runtime,CRT)。作用是初始化C环境,比如:
初始化.data段(把Flash中的初始值拷贝到RAM)。
清零.bss段。
调用SystemInit()配置时钟、外设。
最后才调用真正的main()。_main和main是两个完全不同的函数。_main代码是编译器自动创建的。
BX      R0
跳转到R0里保存的地址,也就是执行__main函数。
ENDP 表示子程序结束。
中断服务程序
13.png

14.png

中断函数分为系统异常中断和外部中断,外部中断根据不同芯片有所变化。B指令是跳转到一个‘.’,表示无限循环。
这些中断服务函数都被[WEAK]声明为弱定义函数,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。
用户堆栈配置
15.png

第一行判断是否定义了__MICROLIB。__MICROLIB这个宏定义在KEIL的“option for target”的target里配置。如果定义__MICROLIB,则__initial_sp、__heap_base和__heap_limit这三个标号具有全局属性,可被外部的文件使用。__initial_sp表示栈顶地址,__heap_base表示堆起始地址,__heap_limit表示堆结束地址。
如果没有定义__MICROLIB,则使用默认的 C 库运行。堆栈的初始化由C库函数__main完成。
IMPORT __use_two_region_memory
告诉汇编器本文件里要用到外部符号__use_two_region_memory,符号的作用是选择内存分配模型:
two-region model(常见):堆(heap)从RAM的低地址向上长,栈(stack)从RAM的高地址向下长,二者之间逐渐逼近。
one-region model:堆和栈放在一起,由用户自己管理。
大多数 Cortex-M 项目默认就是 two-region 模型。
EXPORT __user_initial_stackheap
对外导出一个函数符号,ARM C库在启动时会调用它,用来得到堆和栈的初始位置。
LDR     R0, =  Heap_Mem
保存堆起始地址。
LDR     R1, =(Stack_Mem + Stack_Size)
保存栈大小。
LDR     R2, = (Heap_Mem +  Heap_Size)
保存堆大小。
LDR     R3, = Stack_Mem
保存栈顶指针。
BX      LR
跳转到 LR 标号给出的地址,不用返回。
.sct文件

是Keil/ARM编译器(ARMCC)或Arm Compiler 6 (armclang)用来描述链接过程和内存布局的文件,叫做scatter file(分散加载文件)。新建一个工程时Keil会根据芯片的Flash和RAM大小,生成一个默认的.sct文件。如果需要更复杂的内存分布(多段RAM、外部SDRAM、双区Flash),开发者需要手动写.sct。
MCU厂商(如 ST、NXP、瑞萨)提供的Keil示例工程里通常已经写好了.sct文件,配合启动文件(startup_xxx.s)使用。
  1. LR_IROM1 0x08000000 0x00040000  {    ; load region size_region
  2.   ER_IROM1 0x08000000 0x00040000  {  ; load address = execution address
  3.    *.o (RESET, +First)
  4.    *(InRoot$$Sections)
  5.    .ANY (+RO)
  6.    .ANY (+XO)
  7.   }
  8.   RW_IRAM1 0x20000000 0x0000C000  {  ; RW data
  9.    .ANY (+RW +ZI)
  10.   }
  11. }
复制代码
LR_IROM1表示整个工程的 Load Region(装载位置)——对应 Flash。
ER_IROM1是Execution Region(执行位置)——程序实际运行时的代码位置。
.ANY (+RO) 把所有只读段(.text、.rodata)放到 Flash。
.ANY (+RW +ZI)把.data(RW,带初值的全局变量)和.bss(ZI,Zero Init,未初始化变量)放到RAM。
系统启动流程

MCU上下电会被复位,上电后执行的程序实际上就是复位时执行的程序。.S类的启动文件由芯片厂商提供。启动文件用汇编编写,是系统上电复位后第一个执行的程序。启动文件主要做了以下工作:
1、初始化堆栈指针SP = _initial_sp
2、初始化程序计数器指针PC = Reset_Handler
3、配置堆栈起始地址、大小
4、初始化中断向量表
7、调用C库中的_main函数初始化用户堆栈,最终调用main函数
示例

打开一个keil工程,打开debug并点击reset,查看0x0800 0000处的值,可以看到和SP、PC寄存器的值相同(差一是ARM和thumb指令的规则)。同时查看map文件可以看到,和__initial_sp以及startup_stm32f103xe.o(.text)的值相同。
16.png

图中左边还能看到,Cortex‐M3处理器拥有R0‐R15的寄存器组。其中R13作为堆栈指针SP。SP有两个,但在同一时刻只能有一个可以看到。
17.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册