起因 云途MCU内存有ECC(Error Correcting Code)功能,需要在startup.s中初始化所有内存(既赋值0),所以软复位后从startup.s中reset_handle运行,会重新初始化内存,原本内存的值会被清0,无法使用内存OTA升级程序,需要用到Flash来保存OTA信息。
这篇文章来讲下汇编启动程序做了什么,单片机启动过程,ld链接脚本中定义的变量在汇编程序中的引用,不同编译器汇编程序的区别。
参考文档 
正点原子《I.MX6U  嵌入式 x Linux  驱动开发指南 V1.6 6》——第七章 ARM  汇编基础 
ARM开发人员网站 ,可以直接搜索指令ARM资源图书馆 ,可以下载白皮书、ARM编程手册Documentation for binutils ,binutils工具链(ld, as…)的官方文档 
启动程序和启动过程 常用指令 
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