IAR Embedded Workbench中的MCU启动过程

Technical Note

Architectures:

All

Component:

general

Updated:

2023/8/3 5:17

在嵌入式系统中,当MCU复位之后,需要运行对应的启动代码来对系统进行初始化,然后才会调用main函数,开始运行用户的代码。通常情况下,对应的启动代码一般是工具厂商或者芯片厂商提供,嵌入式软件开发工程师不需要特别关注。不过掌握对应的MCU启动过程对于了解和掌握嵌入式系统还是非常有帮助的,特别是在启动过程中出现问题的时候。

本文主要以Arm Cortex-M为例,介绍IAR Embedded Workbench中的MCU启动过程。

MCU启动过程

MCU启动过程指的是从MCU复位到main函数之前的过程。

当MCU复位之后,MCU会从对应的复位向量开始运行,初始化Stack pointer指向指定Stack区域的末尾,然后调用__low_level_init函数进行相关的初始化。

接下来是全局和静态变量的初始化:初始值为0的变量对应的RAM区域会清零,初始值为非0的变量,会从ROM拷贝到RAM(注意:如果__low_level_init函数返回0,这一步将会跳过)。

然后是C++动态初始化:构造静态 C++ 对象,最后会调用main函数。

更具体一点:

当MCU复位之后,PC指针会指向对应的复位向量,然后运行对应的启动代码(startup code),启动代码首先会初始化Stack pointer指向指定Stack区域的末尾。

然后初始化初始值为0的存储在RAM中的全局和静态变量(比如 int i = 0;):

初始化初始值为非0的存储在RAM中的全局和静态变量(比如 int i = 1;),对应的初始值从相应的ROM拷贝到对应的RAM:

最后,调用main函数:

启动代码

通常情况下,如果ICF文件中添加了initialize by copy 命令,linker会自动选择并添加对应的启动代码来完成对应的启动过程。对应的启动代码通过库文件的方式进行link。对应的启动代码在安装目录arm\src\lib下面:

\arm\src\lib\thumb\cstartup_M.s                  (__iar_program_start)

\arm\src\lib\thumb\cmain.s                     (__cmain,__call_main)

\arm\src\lib\runtime\low_level_init.c               (__low_level_init)

\arm\src\lib\runtime\data_init.c                  (__iar_data_init3)

\arm\src\lib\runtime\zero_init3.c                 (__iar_zero_init3)

\arm\src\lib\runtime\copy_init3.c                 (__iar_copy_init3)

对应的启动代码和相关文件信息会在map文件里面列出来:

同时map文件里面INIT TABLE章节会列出对应的全局和静态变量的初始化信息:初始值为0的会使用__iar_zero_init3进行初始化,初始值为非0的会使用__iar_copy_init3进行初始化:

调试

为了能够调试查看对应的启动代码和启动过程,需要配置Debugger选项里面的Run to,即不要勾选Run to,这样调试的时候复位之后PC会停在复位向量而不是main函数,然后就可以调试对应的启动代码和启动过程。

复位之后,PC会停在复位向量Reset_Handler,Reset_Handler首先会调用SystemInit函数进行相关的配置和初始化(这个是Cortex-M CMSIS的标准),然后会调用__iar_program_start:

如果对应的MCU有FPU,__iar_program_start首先会调用__iar_init_vfp对FPU进行初始化:

然后__iar_program_start会调用__cmain:

__cmain首先会调用__low_level_init(默认实现为空,仅返回 1):

__cmain然后会调用__iar_data_init3进行全局和静态变量的初始化:

__iar_data_init3首先会调用__iar_zero_init3进行初始值为0的全局和静态变量的初始化:

__iar_data_init3然后会调用__iar_copy_init3进行初始值为非0的全局和静态变量的初始化:

最后__call_main会调用main函数跳转到main函数:

至此MCU从复位向量开始,运行启动代码之后就跳转到main函数,然后开始运行用户的代码:

注意事项

  • Cortex-M的MSP赋值是硬件自动操作的(复位之后从中断向量表0地址偏移取值然后赋给MSP),所以在上面的启动代码和启动过程没有体现。如果需要手动对MSP进行赋值(比如bootloader跳转到application,需要手动对application的MSP进行赋值),那么需要在启动代码最开始进行操作。

  • IAR默认的启动代码是linker在link的时候自动添加的,需要在ICF文件添加initialize by copy命令。

  • __low_level_init函数给用户提供了一个初始化的接口,用户可以实现对应的__low_level_init函数,如果MCU的RAM有ECC机制,需要在__low_level_init函数里面按照ECC的位宽对RAM区域进行一次写操作,避免后续RAM操作产生ECC错误。需要注意的是,__low_level_init函数在全局和静态变量初始化之前,所以里面不能使用全局和静态变量。同时,__low_level_init函数的返回值决定全局和静态变量是否需要初始化(返回值为1代表全局和静态变量需要初始化,返回值为0代表全局和静态变量不需要初始化)。

  • __iar_program_start是IAR里面默认的程序开始标签,如果代码里面使用了其它的程序开始标签,需要使用linker选项--entry指定对应的程序开始标签:

总结

本文主要以Arm Cortex-M为例,介绍IAR Embedded Workbench中的MCU启动过程:当MCU复位之后,PC指针会指向对应的复位向量,然后运行对应的启动代码(startup code) :如果MCU有FPU,__iar_program_start首先会调用__iar_init_vfp对FPU进行初始化,然后__iar_program_start会调用__cmain。__cmain首先会调用__low_level_init,然后会调用__iar_data_init3进行全局和静态变量的初始化。__iar_data_init3首先会调用__iar_zero_init3进行初始值为0的全局和静态变量的初始化,然后会调用__iar_copy_init3进行初始值为非0的全局和静态变量的初始化。最后__call_main会调用main函数跳转到main函数。

参考文献:

  1. IAR C/C++ Development Guide (The initialization phase,System startup and termination)

  2. https://www.beningo.com/understanding-the-microcontroller-boot-process/

  3. Michael Barr, Anthony Massa Programming Embedded Systems, Second Edition (Initialize the Hardware)

We do no longer support Internet Explorer. To get the best experience of iar.com, we recommend upgrading to a modern browser such as Chrome or Edge.