0%

MCU startup.s

起因

云途MCU内存有ECC(Error Correcting Code)功能,需要在startup.s中初始化所有内存(既赋值0),所以软复位后从startup.s中reset_handle运行,会重新初始化内存,原本内存的值会被清0,无法使用内存OTA升级程序,需要用到Flash来保存OTA信息。

这篇文章来讲下汇编启动程序做了什么,单片机启动过程,ld链接脚本中定义的变量在汇编程序中的引用,不同编译器汇编程序的区别。

参考文档

  1. 正点原子《I.MX6U 嵌入式 x Linux 驱动开发指南 V1.6 6》——第七章 ARM 汇编基础
  2. ARM开发人员网站,可以直接搜索指令
  3. ARM资源图书馆,可以下载白皮书、ARM编程手册
  4. Documentation for binutils,binutils工具链(ld, as…)的官方文档

启动程序和启动过程

常用指令

2020032320502673.png

startup_stm32f40_41xxx.s 代码分析

1
2
3
4
5
6
7
8
9
10
Stack_Size      EQU     0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp

Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit

第1行:EQU 是表示宏定义的伪指令,类似于 C 语言中的#define。伪指令的意思是指这个“指令”并不会生成二进制程序代码,也不会引起变量空间分配。0x00000400 表示栈大小,字节为单位。0x00000400 =1024字节=1KB。

第2行:开辟一段数据空间可读可写,段名 STACK,按照8字节对齐。ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER 后面的关键字表示这个段的属性。

  • STACK :表示这个段的名字,可以任意命名。
  • NOINIT:表示此数据段不需要填入初始数据。
  • READWRITE:表示此段可读可写。
  • ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于0)。

第3行:SPACE 这行指令告诉汇编器给STACK段分配 0x00000400 字节的连续内存空间。

第4行:__initial_sp 紧接着SPACE语句放置,表示了栈顶地址。__initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
                PRESERVE8   ; 指定当前文件保持堆栈8字节对齐
THUMB ; 表示后面的指令是THUMB指令集


; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors ; EXPORT申明标号为可被外部引用
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
...... 省略
DCD OTG_HS_IRQHandler ; USB OTG HS
DCD DCMI_IRQHandler ; DCMI
DCD CRYP_IRQHandler ; CRYP crypto
DCD HASH_RNG_IRQHandler ; Hash and Rng
DCD FPU_IRQHandler ; FPU
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors

上面这块代码初始化了中断向量表,第一个是SP指针初始化地址,后面是中断向量表,包含异常处理和外设中断,DCD会定义个4Bytes空间存储中断要跳转的地址。这块RESET数据段放在Flash开始,程序从Flash首地址开始运行,先初始化SP和PC(PC就是Reset_Handler),再跳转去Reset_Handler执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                AREA    |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ; 弱定义
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0 ; 跳转至SystemInit()函数初始化时钟
LDR R0, =__main ; 跳转至__main()初始化堆栈, __main()由MDK自动生成
BX R0
ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
...... 省略

上面这块定义了中断服务函数,都是弱定义,用户可以在别的文件中重定义。除了Reset_Handler有实现,其他都为死循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit

ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap

__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF

启动代码的最后一部分,简单的汇编语言实现 IF ELSE语句。如果定义了__MICROLIB,那么程序是不会执行ELSE分支的代码。MDK中MicroLIB的作用,参考:KeilMDK配置项中Use MicroLIB