ARM-Cortex M核心的启动过程

乐活   2025-02-01 10:50   内蒙古  

所以这个东西到底是咋运行起来的,一给电源就启动了。

启动代码是 MCU 复位后执行的第一段代码,主要作用是 初始化堆栈、设置中断向量表、调用系统初始化函数,并最终跳转到 main()。该文件采用 ARM 汇编实现,并按照 Cortex-M 内核启动流程设计。

因为最终是要有一个稳定的执行指令的环境的,就是PC指针开始工作,CPU开始工作。

一开始的堆栈初始化就是:为 SP(栈指针)和 malloc() 分配 RAM。

接下来是:存储 MCU 的中断入口地址,就是NVIC

  1. 第一项:栈指针
  2. 第二项:Reset_Handler 作为复位后执行的代码

接下来是开始复位:

  1. 设置 SP,确保堆栈正确
  2. 调用 SystemInit(),初始化 MCU 时钟
  3. 跳转 __main(),最终执行 main()

在总结一下:

1️⃣ MCU 复位,CPU 读取向量表 0x00000000 

2️⃣ 加载 __initial_sp 到 SP 

3️⃣ 跳转 Reset_Handler 执行 

4️⃣ 调用 SystemInit() 初始化系统 

5️⃣ 跳转 __main() 进行 C 运行环境初始化 

6️⃣ 最终进入 main(),运行用户代码

这也是一样的流程

就在这里

引导加载程序是一段代码,可用于更新用户应用程序代码。可以使用其他下载渠道(USB或网络端口)获取新代码。引导 ROM 执行后,将执行引导加载程序,并在需要时进行更新,然后执行最终用户应用程序。
引导加载程序和用户应用程序应作为两个单独的 µVision 项目或目标进行编写和构建,从而产生两个单独的可执行映像/应用程序。
引导加载程序的主要任务是重新编程/替换用户应用程序,并跳转到用户应用程序执行它。
用户应用程序不一定需要知道引导加载程序的存在。
引导加载程序通常放置在芯片闪存基地址处,以便在复位后由 CPU 执行。下图演示了用户应用程序和引导加载程序的典型代码放置位置。

基址在这里,下面是引导的中断向量表。接着是引导代码,直接一段没使用,然后是用户的中断表,这个表还会拿出来继续映射。

这个一个栈指针,下一个就是程序指针

下一个就是中断源了

CPU 复位后,取出向量表中的 MSP(堆栈指针).

  1. 第 0 号地址:存储堆栈指针(MSP, Main Stack Pointer) 初始值。
  2. 第 1 号地址:存储复位向量(Reset Vector),指向 Reset_Handler。
  3. 第 2~N 号地址:存储 各种异常和中断的入口地址。

那就是APB,AHB和M0+往下了

从下往上走

在M3和M4架构中,中断向量表通常位于程序的起始位置。
中断向量表中的第一个条目是初始堆栈指针(SP)的值,第二个条目是复位向量(Reset Handler)的地址。
BootLoader程序的主要任务是初始化硬件(如串口),然后跳转到应用程序(APP)的起始地址执行。
  1. 初始化串口等外设,确保BootLoader可以与外部设备通信。
  2. 在跳转到应用程序之前,需要关闭所有中断,以避免在跳转过程中发生中断导致不可预知的行为。
  3. 从应用程序的起始地址(即中断向量表的第一个条目)读取初始堆栈指针的值,并将其设置为当前堆栈指针。
  4. 从应用程序的起始地址+4的位置读取复位向量的地址,并将其强制转换为函数指针,然后跳转到该地址执行。


大概就是这样

堆栈指针的设置:在跳转到应用程序之前,必须正确设置堆栈指针,否则应用程序可能无法正常运行。
中断的关闭:在跳转之前关闭中断是为了避免在跳转过程中发生中断,导致程序进入不可预知的状态。
应用程序的复位向量:应用程序的复位向量是应用程序的入口点,跳转到该地址相当于执行了一次复位操作。

__IO 是编译器特定的宏,通常表示“volatile”,用于告诉编译器该变量可能会被硬件或外部事件修改,避免编译器优化。
vu32 是一个易变的32位无符号整数类型,用于访问硬件寄存器或内存映射的外设。


这是一个用汇编编写的函数,用于设置主堆栈指针(MSP)。
  1. MSR MSP, r0:将传入的参数(addr,存储在寄存器 r0 中)赋值给主堆栈指针(MSP)。
  2. BX r14:跳转到链接寄存器(r14,即 LR)中存储的地址,用于返回到调用函数。

看看这个复杂的函数

检查栈顶地址是否合法:
(*(vu32*)appxaddr):读取应用程序起始地址的第一个字(栈顶地址)。
&0x2FFE0000:掩码操作,用于检查栈顶地址是否在RAM范围内(通常RAM的起始地址是 0x20000000)。
如果栈顶地址合法(即 (*(vu32*)appxaddr)&0x2FFE0000) == 0x20000000),则继续执行。
获取中断的地址:

jump2app = (void(*)())*(vu32*)(appxaddr+4):读取应用程序起始地址的第二个字(复位中断地址),并将其赋值给函数指针 jump2app。

设置堆栈指针:
MSR_MSP(*(vu32*)appxaddr):调用汇编函数 MSR_MSP,将应用程序的栈顶地址设置为当前堆栈指针。
关闭中断:
通过写 NVIC->ICER 和 NVIC->ICPR 寄存器,关闭所有中断并清除中断标志位,确保跳转过程中不会发生中断。

是这样

跳转到应用程序:
jump2app():通过函数指针跳转到应用程序的复位中断地址,开始执行应用程序。
  1. 栈顶地址检查:确保应用程序的栈顶地址在合法的RAM范围内。
  2. 复位中断地址:应用程序的第二个字是复位中断地址,BootLoader通过该地址跳转到应用程序。
  3. 关闭中断:在跳转之前关闭所有中断,确保跳转过程的安全。
  4. 堆栈指针设置:在跳转之前,正确设置应用程序的堆栈指针。

然后呢?怎么走?

堆栈已经好了

复制 .data 段(已初始化变量)
从 Flash 复制 .data(已初始化全局变量) 到 SRAM。.data 存在 Flash 中,运行时需要复制到 RAM。
清空 .bss 段(未初始化变量)
.bss(未初始化全局变量)在 RAM,需要全部清零。
调用 SystemInit()(初始化时钟等)
SystemInit() 由 CMSIS 提供,
通常用于:配置 系统时钟(HSE, HSI, PLL)
设置 Flash 加速
配置 总线时钟(AHB, APB)
STM32 HAL 库提供默认 SystemInit(),可在 system_stm32f4xx.c 修改。
调用 main()
main() 是用户程序的入口,至此启动完成。

当然这个还是写的不全面,下次再写!

云深之无迹
纵是相见,亦如不见,潇湘泪雨,执念何苦。
 最新文章