前言:
本文将对云途启动文件(startup.s)进行详细介绍,
不同IDE的启动文件大同小异,本文以IAR为例。
介绍云途不同型号芯片的启动时间(从复位到进入main函数,以及时钟初始化完成)。
针对特殊场景需要尽量减少启动时间,本文介绍几种优化方式。
版本:
Config Tool Version:2.7.6
SDK version:1.3.1
IDE:IAR
1. 声明与定义
MODULE ?cstartup
EXTERN main
EXTERN SystemInit
EXTERN RamInit0
EXTERN RamInit1
EXTERN RamInit2
EXTERN VectorTableCopy
EXTERN STACK_end
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER:NOROOT(2)
THUMB
MODULE ?cstartup:声明当前汇编模块名为?cstartup,用于链接器识别模块。
EXTERN 指令:声明外部符号(函数),表示这些函数在其他文件(通常是 C 文件)中定义,此处需要调用。包括:
main:C 程序入口函数;
SystemInit:系统初始化;
RamInit0/1/2:RAM 分阶段初始化函数;
VectorTableCopy:中断向量表复制函数;
STACK_end:栈顶地址(链接脚本中定义)。
PUBWEAK Reset_Handler:定义一个公共弱符号Reset_Handler(复位中断处理函数)。PUB表示可被外部访问,WEAK表示若其他地方有同名强符号,会被覆盖(通常复位向量固定指向此处)。
SECTION .text:CODE:REORDER:NOROOT(2):定义代码段.text,属性为可执行代码(CODE),允许链接器重排(REORDER),非必需段(NOROOT,未被引用时可被链接器丢弃),优先级为 2。
THUMB:指定使用 ARM 的 THUMB 指令集(16 位指令,更紧凑,适合嵌入式场景)。
2. 复位启动流程
芯片复位后,硬件会自动跳转到复位向量(即Reset_Handler),开始执行以下步骤:
2.1 关闭中断,初始化通用寄存器
CPSID I ;关闭IRQ中断(防止初始化被中断干扰)
LDR R1,=0 ;初始化通用寄存器
...(R2到R7均设为0)
MOV R8,R7 ;R8到R12均设为0(R7已为0)
...
MOV R12,R7 ;R1-R12全为0
CPSID I:通过修改 CPSR(程序状态寄存器)关闭 IRQ 中断,确保初始化过程不被打断。
因为复位后的通用寄存器R1-R12的值UnKnown,所以要重新初始化,清除寄存器残留值,保证初始状态一致。
509cab12-ec71-4a79-94fa-46ba72a33aec-image.png
2.2 RAM 第一阶段初始化(RamInit0)
/* RamInit 0 Stage, focus on ecc init, asm code*/
BL RamInit0
SRAM存储器中的内容在上电之后内容是随机的,其中的
有效数据和ECC数据并未建立起关联。此时,如果读取SRAM的内容并进行ECC校验,大概率上是会出现
ECC错误的。
在使用支持ECC的SRAM之前,需要手动对SRAM进行初始化操作(循环赋值0x5A,方便辨认)。
另外YT_LINK中的RAM段的POR_ONLY属性就是在RamInit0和RamInit1中完成。读取RCU上电标志位,以判断是否执行这段RAM的初始化。(
例如开辟一段空间存放Bootloader和APP的交互信息)
3f3feff0-4279-44d2-b615-cc75694a58e9-image.png
2.3 设置栈顶指针(SP)
/* Initialize the stack pointer */
LDR r0,=STACK_end
MOV r13,r0
栈是 C 语言运行的基础(用于函数调用、局部变量存储等)。STACK_end是栈的栈顶地址(由链接脚本定义,栈通常向下生长,即高地址向低地址生长)。
此步骤为后续 C 函数调用(如RamInit1)准备栈环境。
2.4 RAM 第二阶段初始化(RamInit1)
/* RamInit 1 Stage, focus on copy data,clear bss, c code*/
LDR r0,=RamInit1
BLX r0
注释:RamInit1,用于复制数据段(.data)、清除 BSS 段(未初始化全局变量),由 C 语言实现。
RamInit0是将RAM初始化为统一固定值0x5A,而RamInit1则是将定义在RAM中的全局变量初始化赋值。
有初始值的定义在(.data段),初始化是从FLASH将初始值copy到RAM。另外如果设置了POR_ONLY属性的RAM段在无上电标志位时则不会copy;设置INIT_NULL属性的RAM也不会copy。
0c6e1538-f689-4e24-bcf1-8f7973b19b15-image.png
定义了但未赋值的全局变量则会定义在(.bss)段,会被清零。
e59e886d-f82b-4d7b-965c-5389a005b219-image.png
2.5 复制中断向量表
/* Copy Vector Table for interrupt, c code */
#ifndef __NO_VECTOR_TABLE_COPY
/* Call the to copy vector table from flash to ram */
ldr r0,=VectorTableCopy
blx r0
#endif
19e38303-1188-4187-a36d-0da6f0eecb6a-image.png
作用:中断向量表(存储各中断处理函数地址)默认存储在 Flash 程序的起始地址,复制到 RAM 可
提高中断响应速度,或支持动态修改向量表。
将中断向量表基地址(SCB->VTOR)偏移到 IVT_RAM_start(由链接脚本定义);
中断向量表在链接脚本中默认1024Byte空间,在程序的起始地址,第一个字是栈顶指针,第二个字就是Reset_Handle的地址,后面是所有中断的地址入口。中断向量表在Vector.s中定义。
可通过宏 __NO_VECTOR_TABLE_COPY 配置是否需要 VectorTableCopy。
07653add-7bcd-4fce-8fb8-82782edfe7b4-image.png
fae1ce37-c753-4d4d-9797-b2a120308a74-image.png
2.6 系统初始化
#ifndef __NO_SYSTEM_INIT
LDR r0,=SystemInit
BLX r0 ; 调用SystemInit
#endif
云途不同芯片的系统初始化内容有些许差异,以HA0为例,会使能FPU(浮点运算单元),Flash的Deep PowerDown Enable,关闭WDG。
可通过宏 __NO_SYSTEM_INIT 配置是否需要 SystemInit。
bb181722-3454-48b7-8e24-3095e246aca2-image.png
2.7 RAM 第三阶段初始化(RamInit2)
/* RamInit 2 Stage, focus on others ram init, c code */
LDR r0,=RamInit2
BLX r0
作用:处理前两阶段未包含的 RAM 初始化需求(如特殊用途 RAM),由C语言实现。
如图RamInit2为一个弱函数,用户可以重写(覆盖) 这个函数。
3354cdbc-5dbd-4b62-8aaa-328bdc5ab0b1-image.png
2.8 开启中断,跳转至main
/* Unmask interrupts */
CPSIE I
/* Call the main routine */
BL main
开启 IRQ 中断,为进入main函数做准备
BL main:跳转到 C 语言的main函数,启动应用程序。
2.9 死循环(main返回后)
JumpToSelf:
B JumpToSelf
END
若main函数意外返回(正常情况下main不会返回),程序会进入死循环,防止跑飞(执行未知地址指令)。
2.10 总结
该启动文件的核心流程是:复位后关闭中断 → 初始化通用寄存器组 → 分阶段初始化 RAM(含 data段 / BSS、扩展 RAM) → 设置栈 → 复制向量表 → 系统初始化 → 跳转至main。整个过程为 C 程序运行准备了硬件环境(RAM、WDG、Flash、中断向量、FPU等等)和软件环境(栈、全局变量),是从硬件复位到应用程序启动的桥梁。
3. 不同型号芯片启动时间
优化等级:Low
基于Helloword demo程序测试:
db770b96-8d52-46d7-a687-6da907646f07-image.png
LE0:
d4010816-923a-4fee-86ef-85d6e9446732-image.png
LE1:
45acf8f4-1e35-4274-8972-6db1273a9fed-image.png
MC0:
06bf7614-b73c-44a2-871f-e978af156664-image.png
MD1:
96ef18f9-c380-4b3a-a730-eea2f646dc70-image.png
MD2:
0a3beceb-2038-49e0-80d2-3a539c68c3c4-image.png
ME0:
0bef586e-e34b-47c6-8b51-e9e30ef121b9-image.png
HA0:
40cd4029-aa9c-451e-a100-ecf1c622455a-image.png
tips:
上述时间仅供参考,因为不同优化等级,不同IDE,启动文件编辑都有可能造成启动时间差异。
HA的RAM是256K,相比于ME0(128K)初始化时间反而更短:
因为 HA0 的内核是M7的并且是按照
64bit去赋值RAM的,时间更短。
HA复位后到启动文件执行需要1ms左右,原因是HA有硬件安全启动(MC也有硬件安全启动,但HA流程相较于MC更复杂),增加了耗时。
4. 启动时间优化
在启动时RAM循环赋值可能出现多次从Flash取指令赋值时间较长(指令未对齐),造成启动时间变长的问题,而且具有随机性(低优化等级编译)。
在一些特殊应用中,例如Powerdown周期性唤醒(唤醒即复位),因为要控制功耗,所以需尽可能减少启动时间,下面介绍启动时间优化的方式。
4.1 RamInit0优化
d188e5e7-1584-4454-a934-6e747ee5c2a7-image.png
如上图在每一段RAM赋初值0x5A的前面加上ALIGNROM 4,确保后续代码 / 数据地址满足 4 字节对齐,提高访问正确性和效率。
4.2 RamInit1优化
如下图,使用C语言标准库函数memcopy,和memset代替RAMInit1的循环赋值操作,汇编优化更好:
3adeb124-7ac4-4598-bcc3-b25706d6b0b1-image.png
d8063910-647c-4b06-b593-9e6fb59ffa8b-image.png
4.3 减少RAM初始化
可通过YCT去配置RAM段的Init_Policy属性为NULL,这样就不会初始化这段RAM,当然为了防止RAM_ECC,也不能访问这段RAM,可在正常启动后重新以32bit为单位(ECC机制)向这段RAM写入初始值,就可以正常访问了。
这种方法适用于对RAM需求量小的工况,例如powerdown周期性唤醒,唤醒复位后只需执行少量代码就继续进入powerdown。
1bc02a7f-7f29-4295-970b-e35b74909e56-image.png
4.4 打开I-Cache(HA,MD2)
b84ed9fc-2c7f-4d3f-a2e6-fb50a25110f9-image.png
HA(M7内核)和MD2系列,也可以打开I-Cache,对于反复用到的指令,比如循环赋值指令,打开I-Cache可提高指令命中率,避免反复访问Flash取指令降低效率。
在启动文件打开I-Cache参考下图代码(HA),可在进入main函数后关闭I-Cache。
a20cd4e7-8823-48bf-aee5-c948b2a972f0-image.png
03d570eb-894e-434b-af2c-207b353f316f-image.png