云途启动文件(startup.s)详解
-
前言:
- 本文将对云途启动文件(startup.s)进行详细介绍,不同IDE的启动文件大同小异,本文以IAR为例。
- 介绍云途不同型号芯片的启动时间(从复位到进入main函数,以及时钟初始化完成)。
- 针对特殊场景需要尽量减少启动时间,本文介绍几种优化方式。
版本:
Config Tool Version:2.7.6
SDK version:1.3.1
IDE:IAR1. 声明与定义
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,所以要重新初始化,清除寄存器残留值,保证初始状态一致。

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的交互信息)

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。

- 定义了但未赋值的全局变量则会定义在(.bss)段,会被清零。

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
- 作用:中断向量表(存储各中断处理函数地址)默认存储在 Flash 程序的起始地址,复制到 RAM 可提高中断响应速度,或支持动态修改向量表。
- 将中断向量表基地址(SCB->VTOR)偏移到 IVT_RAM_start(由链接脚本定义);
- 中断向量表在链接脚本中默认1024Byte空间,在程序的起始地址,第一个字是栈顶指针,第二个字就是Reset_Handle的地址,后面是所有中断的地址入口。中断向量表在Vector.s中定义。
- 可通过宏 __NO_VECTOR_TABLE_COPY 配置是否需要 VectorTableCopy。


2.6 系统初始化
#ifndef __NO_SYSTEM_INIT LDR r0,=SystemInit BLX r0 ; 调用SystemInit #endif- 云途不同芯片的系统初始化内容有些许差异,以HA0为例,会使能FPU(浮点运算单元),Flash的Deep PowerDown Enable,关闭WDG。
- 可通过宏 __NO_SYSTEM_INIT 配置是否需要 SystemInit。

2.7 RAM 第三阶段初始化(RamInit2)
/* RamInit 2 Stage, focus on others ram init, c code */ LDR r0,=RamInit2 BLX r0- 作用:处理前两阶段未包含的 RAM 初始化需求(如特殊用途 RAM),由C语言实现。
- 如图RamInit2为一个弱函数,用户可以重写(覆盖) 这个函数。

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程序测试:

LE0:

LE1:

MC0:

MD1:

MD2:

ME0:

HA0:

tips:- 上述时间仅供参考,因为不同优化等级,不同IDE,启动文件编辑都有可能造成启动时间差异。
- HA的RAM是256K,相比于ME0(128K)初始化时间反而更短:
- 因为 HA0 的内核是M7的并且是按照64bit去赋值RAM的,时间更短。
- HA复位后到启动文件执行需要1ms左右,原因是HA有硬件安全启动(MC也有硬件安全启动,但HA流程相较于MC更复杂),增加了耗时。
4. 启动时间优化
- 在启动时RAM循环赋值可能出现多次从Flash取指令赋值时间较长(指令未对齐),造成启动时间变长的问题,而且具有随机性(低优化等级编译)。
- 在一些特殊应用中,例如Powerdown周期性唤醒(唤醒即复位),因为要控制功耗,所以需尽可能减少启动时间,下面介绍启动时间优化的方式。
4.1 RamInit0优化

- 如上图在每一段RAM赋初值0x5A的前面加上ALIGNROM 4,确保后续代码 / 数据地址满足 4 字节对齐,提高访问正确性和效率。
4.2 RamInit1优化
-
如下图,使用C语言标准库函数memcopy,和memset代替RAMInit1的循环赋值操作,汇编优化更好:


4.3 减少RAM初始化
- 可通过YCT去配置RAM段的Init_Policy属性为NULL,这样就不会初始化这段RAM,当然为了防止RAM_ECC,也不能访问这段RAM,可在正常启动后重新以32bit为单位(ECC机制)向这段RAM写入初始值,就可以正常访问了。
- 这种方法适用于对RAM需求量小的工况,例如powerdown周期性唤醒,唤醒复位后只需执行少量代码就继续进入powerdown。

4.4 打开I-Cache(HA,MD2)

-
HA(M7内核)和MD2系列,也可以打开I-Cache,对于反复用到的指令,比如循环赋值指令,打开I-Cache可提高指令命中率,避免反复访问Flash取指令降低效率。
-
在启动文件打开I-Cache参考下图代码(HA),可在进入main函数后关闭I-Cache。


快速上手云途开发生态
发帖前请查看
帮助改进和优化YT CONFIG TOOL,有机会抽取YTM32B1ME0 EVB哦...