PN学堂GD32教程第14篇——Arm单片机启动流程

14.1 ARM启动流程-复位函数

基于keil工程编译出来的AXF,单片机进入Reset_Handler()之后,会调用SystemInit初始化时钟,然后进入_main。

_main的具体实现在keil里面是闭源的,外网有大神通过反汇编破译了主要流程出来,_main并不等价于我们写的用户主函数,而是在流程的最后调用main。

CPU寻址图:

32位单片机的寻址区间都是0x0000 0000~0xFFFF FFFF,CODE区域存储BootRom(若有)、BootLoader以及User Application(我们自己烧录的);SRAM是片内的运存,我们在堆栈上开辟的存储空间最终都会映射到这个模块上;片上外设就是我们最熟悉的GPIO、ADC、Usart等等;EXMC外部存储器控制器,用来访问片外扩展的Nand Flash、SRAM或者LCD彩屏等;最后Cortex-Mx内核寄存器读写操作可以进行中断管理、休眠管理、debug、滴答计时、浮点数硬件运算等等。

下面先看code区,

最底下Aliased...叫做**"别名区"(Alias Region)或"启动空间"(Boot Space),** 这个区域是 Code(代码区)的起始段(0x0000 0000到0x002F FFFF)。

ARM Cortex-M 内核的设计规定:CPU 上电复位后,必须从0x0000 0000这个地址开始获取第一条指令(复位中断向量)和初始堆栈指针。但是,实际的代码物理地址并不一定在0x0000 0000例如 Main Flash 在0x0800 0000,系统存储区在0x1FFFF B000)。

为了解决这个问题,MCU 内部设计了一个硬件电路(地址映射器)。根据外部的 BOOT0和 BOOT1 引脚的电平状态,决定把哪一块物理存储器的内容"影射"(Alias)到 0x0000 0000 这个地址:

只要BOOT0=0,那么Main Flash(0x0800 0000 区域)就会映射到 0x0000 0000。此时 CPU 访问 0x0就是访问主 Flash,程序从用户代码启动。

如果 BOOT 1=0,BOOT0**=1** :把System Memory(即 Bootloader/BootROM**)**(0x1FFFF B000区域)映射到 0x0000 0000。此时 CPU 访问0x0就是访问系统存储区,程序进入厂家预置的 Bootloader 模式(STM32为首的厂商主流做法是使用串口0接受镜像文件,并且固化到Main Flash中)。

如果BOOT0=BOOT1,部分芯片还支持把 SRAM 映射到这里,从内存启动。

回到启动流程上面:

中断服务函数,中断向量表里面存储着中断服务函数的地址:

14.2 ARM启动流程-内存栈

ram区域的分布:

注:只有用const修饰的常量和初始化了的变量会被存储在Flash区域,并且后者在程序运行的时候搬移到bss段。

以最简单的点灯程序为例,绿色区域是栈,灰色区域是堆、.bss集合、.data段的集合。

栈指针(Stack Pointer, SP)是 CPU 内部的一个特殊寄存器。它的作用就像一个游标,始终指向当前栈的"栈顶"位置。

压栈(PSUH):当需要给函数分配空间时,SP 的数值会减小(向低地址移动),为函数预留出一块内存空间。

弹栈(POP):当函数执行完毕退出时,SP 的数值会增大(向高地址移动),这块预留的内存就被释放了。

  1. 程序刚启动时:进入 main() 函数
  • 动作 :程序启动,进入 main() 函数。

  • SP 移动 :SP 从初始的栈底 0x20000428 处向下移动(地址变小) ,留出 main 函数的栈帧空间。

  • 当前状态 :main 函数栈帧建立完毕。

  1. 顺序执行初始化:DelayInit()
  • 动作 :执行到 DelayInit();。

  • SP 移动 :SP 继续向下移动 ,为 DelayInit 函数建立栈帧。

  • 当前状态 :栈中从底向上依次是 main 帧 -> DelayInit 帧。

  1. 顺序执行初始化:LedDrvInit()
  • 动作 :DelayInit 执行完毕,它的栈帧被销毁(SP 向上退回原位)。紧接着执行 LedDrvInit();。

  • SP 移动 :SP 再次向下移动 ,建立 LedDrvInit 的栈帧。

  • 当前状态 :栈中从底向上依次是 main 帧 -> LedDrvInit 帧。

  1. 嵌套调用(关键点):TurnOnLed(LED1)
  • 动作 :LedDrvInit 执行完并销毁(SP退回)。接着进入 while(1) 循环,执行到 TurnOnLed(LED1);。

  • SP 移动:

    1. TurnOnLed 被调用,SP 向下移动 ,建立 TurnOnLed 的栈帧。

    2. 假设 TurnOnLed 内部调用了其他函数(如 DelayNms),SP 会继续向下移动 ,在 TurnOnLed 的栈帧下方再建立新的栈帧。

  • 当前状态 :此时栈中的深度达到最大,从底到顶依次是 main 帧 -> TurnOnLed 帧 -> 其他子函数帧。这对应了图中灰色区域最上方的一小块。

  1. 函数返回与空间复用
  • 动作 :当 DelayNms(1000) 执行完毕,程序返回到 TurnOnLed。

  • SP 移动 :DelayNms 的栈帧销毁,SP 向上退回 。接着 TurnOnLed 执行完毕,它的栈帧也被销毁,SP 继续向上退回 ,恢复到执行 TurnOnLed 之前的位置。

  • 当前状态 :TurnOnLed 和 DelayNms 的栈帧虽然被释放了,但它们占据的内存地址(即栈顶空间)变成了"可用状态"。

  1. 空间复用的发生
  • 动作 :接着程序执行 DelayNms(1000);。

  • SP 移动 :SP 再次向下移动 ,为 DelayNms 建立栈帧。

  • 核心点 :此时 DelayNms 所占据的栈空间,正是刚刚由 TurnOnLed 释放出来的空间。

  • 这就实现了栈空间复用 :CPU 不需要给每个函数都准备一块永久独占的内存,而是所有被调用的函数共享同一块栈空间。谁当前正在运行,谁就"租用"栈顶的空间,运行完就"退租",下一个被调用的函数可以无缝衔接使用这块空间。

程序员划分堆栈时需要考虑的因素:

①时否需要在函数体内部开辟大数组,注释的写法就是开辟栈空间,不过后来我改成分配堆了。

②函数调用的层级嵌套关系。

Flash空间布局

相关推荐
lhh_qrsly3 小时前
一、电机控制怎么学
驱动开发·嵌入式硬件
女神下凡4 小时前
芯参谋(49):eMCP_软件设计规范
arm开发·嵌入式硬件·设计规范
嵌入式学习菌5 小时前
Modbus 03H 最多读多少寄存器?10H 最多写多少寄存器?
单片机
北纬的小熊5 小时前
Matlab Simulink电力电子仿真-全桥 LLC 谐振变换器调频控制(PFM)仿真研究
嵌入式硬件·matlab
尼喃5 小时前
贴片三极管清单:A1SHB(PW2301A)与A2SHB(PW2302A)平芯微
嵌入式硬件
小李不想当小白5 小时前
USART串口数据包(STM32标准库学习笔记)
经验分享·笔记·stm32·单片机·嵌入式硬件·学习
HQB20266 小时前
EG2181|屹晶微电子 600V 单相半桥 MOS/IGBT 栅极驱动芯片
嵌入式硬件·硬件工程
硅基手札6 小时前
【Linux 内核专栏 18】进程间通信 IPC(下)— Socket·Netlink·fd 三件套·futex·线程同步
linux·运维·驱动开发·单片机·嵌入式硬件·mcu·架构
尼喃15 小时前
OVP/OCP过压过流保护芯片选型表:2A、3A、5A与10A方案怎么选
嵌入式硬件