起因 云途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