ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

简单理解STM32内存分配与堆栈(上)

简单理解STM32内存分配与堆栈(上) 文章目录前言一、Memory mapping1.1 Memory mapping各部分介绍1.2 SRAM静态存储器—— 单片机内部的高速内存1.3 System memory系统存储器—— ST官方Bootloader救砖神器1.4 Flash闪存—— 存放用户程序的地方二、Flash内部结构2.1 isr_vector 段中断向量表2.1.1 什么是 isr_vector 段2.1.2 isr_vector 段的结构2.1.3 了解MCU上电启动流程2.2 .text 段程序代码2.2.1 什么是 .text 段2.2.2 .text 段的特点2.2.3 .text 段包含的内容2.2.4 .text 段的典型使用场景2.3 .rodata 段详解2.3.1 什么是 .rodata 段2.3.2 .rodata 段的特点2.3.3 .rodata 段包含的内容2.3.4 .rodata 段的典型应用2.4 .data 初始化镜像2.4.1 什么是.data 初始化镜像2.4.2 .data 初始化镜像的特点2.4.3 .data 初始化镜像包含的内容2.4.4.data 初始化镜像的典型使用场景前言本篇文章上下两篇以STM32F407为例进行讲解其他STM32芯片的结构其实都大差不差可以参考本文进行理解。一、Memory mapping下面这张图是STM32F40x 存储器映射图Memory map出自 STM32F4 的数据手册 整个 Cortex‑M4 拥有4GB 全局寻址空间被切分成 8 个 Block每个 Block 固定 512MB。1.1 Memory mapping各部分介绍先大体了解一下每一部分是做什么如下如所示Block地址范围名称用途Block00x00000000 ~ 0x1FFFFFFFCode 代码区Flash、系统存储器、OTP 选项字节BOOT 引脚决定重映射到哪个存储器Block10x20000000 ~ 0x3FFFFFFFSRAM内部 RAM主 SRAM、CCM‑RAMBlock20x40000000 ~ 0x5FFFFFFFPeripherals 外设APB1/APB2/AHB 外设寄存器GPIO、USART、SPI 等Block30x60000000 ~ 0x7FFFFFFFFSMC bank12外部 SRAM/NOR FlashBlock40x80000000 ~ 0x9FFFFFFFFSMC bank34外部 NAND、PC 卡Block50xA0000000 ~ 0xBFFFFFFFFSMC 寄存器FSMC 控制寄存器Block60xC0000000 ~ 0xDFFFFFFF未使用 Not used保留Block70xE0000000 ~ 0xFFFFFFFFCortex‑M4 内部外设NVIC、SysTick、内核外设接下来我们重点来了解”Code 代码区“以及”SRAM“内存中相对重要的内容都在这两部分中。⭐ 首先要明确Cortex-M内核的MCU运行时的一条铁律包括STM32在内的所有Cortex-M内核的MCU如GD32、NXP、TI、瑞萨、各种国产M核芯片上电复位后都从0x0000 0000开始读取指令。从0x0000 0000开始看第一个部分是Aliased to Flash, system memory or SRAM depending on the BOOT pins直译 根据 BOOT 引脚的状态别名映射到 Flash、系统存储器 或 SRAM。这块区域本身是不能存放数据的在真实的物理地址上是不存在的可以理解成虚拟的逻辑空间。其中 “Aliased” 中文翻译为 “别名”可以直接理解成地址映射 简单理解就是系统可以根据 BOOT 引脚BOOT0、BOOT1的电平组合通过控制 AHB Advanced High-performance Bus总线的映射把 “System memory OTP” 、 “SRAM” 或 “Flash” 三块存储器中的某一块指向 0x0000 0000 这个位置。为什么要这样做这要从 Cortex-M 内核的规定说起CPU 上电后固定从 0x0000 0000 地址读取内容并开始运行。之所以要固定是因为 CPU 刚上电时没有任何配置必须有一个雷打不动的启动入口否则它不知道该去哪里取第一条指令。但问题是0x0000 0000 本身是虚拟的逻辑地址物理上并没有真实存储器放不了程序于是就需要映射把真正存有程序的存储器指向0 地址这样 CPU 访问 0 地址时实际读到的就是那块真实存储器的内容。这个“访问0地址、实际读取的却是另一块存储器” 的过程就叫别名(Aliasing)也就是地址映射。为什么可选的偏偏是 Flash、System memory、SRAM 这三块是因为它们分别对应嵌入式开发的三种核心场景Flash(0x0800 0000)存正式程序掉电不丢用于产品正常运行System memory(0x1FFF 0000)存厂商固化的 BootLoader用于串口下载程序或救砖SRAM(0x2000 0000)速度快但掉电丢失用于调试阶段快速测试。这三个场景下一节会重点解释至于具体映射哪一块则由 BOOT0、BOOT1 两个引脚的电平组合决定。为什么用硬件引脚选是因为这个选择必须在 CPU 启动之前完成此时软件还没运行只能靠硬件引脚电平来决定。可以结合图片理解下程序运行时是怎么映射的接下来我们将逐个分析一下每一部分都有什么特点。补充简单理解总线概念 总线Bus 芯片内部各个模块之间传输数据的公共通道。AHB 是 ARM 公司制定的 AMBA 总线标准中的一种AMBA 芯片内部通信的规范。打个比方总线就像一条【高速公路】↓CPU、内存、外设 就像路边的【各个城市】↓数据 就像在公路上跑的【车辆】↓大家通过这条公路互相传输数据1.2 SRAM静态存储器—— 单片机内部的高速内存SRAM 是一种静态存储器Static RAM静态随机存取存储器。可以类比我们平时组装电脑时买的内存条比如 32G 内存只不过电脑上安装的其实是 DRAM动态存储器而不是 SRAM。 两者的关键区别SRAM只要有电数据就稳定保持不需要刷新DRAM不仅要有电还要每隔一段时间刷新一遍否则数据会丢失。 SRAM vs DRAM 对比表对比项SRAMDRAM全称Static Random Access Memory静态随机存取存储器Dynamic Random Access Memory动态随机存取存储器是否需要刷新❌ 不需要周期性刷新只要持续供电数据即可保持。✅ 需要周期性刷新存储单元中的电荷会逐渐泄漏。访问延迟通常较低响应较快。⚡通常高于 SRAM访问延迟相对较大。带宽特点单次访问快但通常容量较小。可通过 DDR、高频率和多通道获得很高的整体带宽。成本高单位容量成本较高。低单位容量成本较低。容量较小适合做高速缓存或片上 RAM。较大适合作为系统主内存。常见应用MCU 内部 SRAM、CPU Cache、FPGA 片上存储等。电脑内存DDR、手机内存LPDDR、嵌入式系统外部 SDRAM/DDR 等。 提示1、SRAM 和 DRAM 都属于易失性存储器断电后数据都会丢失。2、STM32 内部常见的运行内存通常是 SRAM如果系统需要更大的运行内存可能会外挂 SDRAM 或 DDR SDRAM。1.3 System memory系统存储器—— ST官方Bootloader救砖神器System memory 里存放的是 ST 公司出厂时固化的一段 Bootloader 程序。这段程序由 ST 官方编写非常稳定且用户无法修改出厂固化。核心用途当单片机通过 J-Link/ST-Link 下载不进去时用它来救砖。使用方法操作步骤① 把 BOOT0 引脚拉【高电平】(接3.3V)↓② 复位单片机重新上电或按复位键↓③ System memory (0x1FFF 0000) 被映射到 0 地址↓④ CPU 上电从 0 地址读取执行 ST 官方 Bootloader↓⑤ 此时可以通过【串口(UART)】发送固件重新下载程序↓⑥ 下载完成后把 BOOT0 拉回【低电平】恢复正常启动原理说明这段 Bootloader 里面就是 ISP在系统编程串口升级代码。当 CPU 运行这段代码时它会监听串口你在串口中发送固件就能把程序烧录进 Flash从而让变砖的单片机复活。STM32 芯片 BOOT0 电平对照表BOOT0 电平典型启动区域常见用途低电平0Main Flash用户程序 Flash正常运行用户程序 ⭐高电平1System MemoryST 内置 Bootloader下载、烧录、恢复程序具体接口依芯片而定 ️ 提示1、上表是许多 STM32 芯片的典型启动行为并非所有型号都完全相同。2、实际启动方式还可能受到 Option Bytes、BOOT1/nBOOT1、nBOOT0、Boot Lock 等配置影响。3、System Memory Bootloader支持 UART、USB DFU、CAN、I2C、SPI 等何种下载方式取决于具体 STM32 型号建议查阅数据手册及 ST 官方文档 AN2606。1.4 Flash闪存—— 存放用户程序的地方Flash 是单片机中存放用户程序的存储器起始地址为0x0800 0000。我们平时用 J-Link / ST-Link 下载的代码默认都会烧录到0x0800 0000这个地址这里存放的就是我们自己编写的用户程序。 Flash 的关键特性特性说明断电不丢失Main Flash 属于非易失性存储器断电后程序和数据仍会保留。下次上电后CPU 通常会重新从 Flash 中的复位向量开始执行程序。可反复擦写Flash 可以擦除后重新烧录程序通常以页、扇区或 Bank 为单位擦除具体擦除单位取决于 STM32 型号。常用启动区域在绝大多数 STM32 的正常应用场景中芯片从 Main Flash 启动并运行用户程序。Flash 与启动的关系① 平时 BOOT0 低电平(默认接地)↓② 上电后Flash(0x0800 0000) 被映射到 0 地址↓③ CPU 从 0 地址读取实际读到的就是Flash里的用户程序↓④ 开始运行你写的程序 ✅ 下载方式对比下载工具/方式程序最终写入位置说明J-LinkMain Flash通常从0x08000000开始通过 SWD/JTAG 接口下载和调试使用广泛通常支持断点调试、擦除、烧录等功能。⭐ST-LinkMain Flash通常从0x08000000开始ST 官方调试下载器通过 SWD/JTAG 接口连接 STM32可用于烧录、调试和擦除 Flash。串口 ISPUART BootloaderMain Flash通常从0x08000000开始芯片先进入 System Memory 中的 ST 内置 Bootloader再通过 UART 接收程序数据并由 Bootloader 写入 Main Flash。传统型号通常需要通过 BOOT0 选择 System Memory 启动。 提示1、0x08000000是大多数 STM32 的 Main Flash 起始地址。2、 J-Link 和 ST-Link 通常不需要将 BOOT0 拉高因为它们通过 SWD/JTAG 直接访问芯片的调试接口。3、串口 ISP 是否需要拉高 BOOT0以及 Bootloader 支持 UART、USB DFU、CAN、I2C、SPI 中的哪些接口取决于具体 STM32 型号及其 Option Bytes 配置。4、大部分下载到0x0800 0000指的是程序的起始地址通常从这里开始。完整的用户代码会从0x0800 0000往后依次存放。二、Flash内部结构上面把SRAM、System memory、Flash这三块内容解释了一下接下来再研究研究程序运行时内存里的数据是怎么存放的博主刚入行时觉得MCU内存就是一个神神秘秘的黑盒子不知道里面的数据是怎么存储和处理的现在咱们就研究研究黑盒子里面到底是什么东西拨开迷雾见晴天。先说主要的部分也就是截图中FLASH我们往芯片中下载的程序就是存储在这一部分目前博主使用的STM32F407的Flash大小是1M不同 STM32 的 Flash 容量是不同的可能是 16 KB、64 KB、128 KB、256 KB、512 KB、1 MB、2 MB 等用户代码会从0x0800 0000一直往上存如下图所示假设用户代码的大小是蓝色的部分,橙色部分是没有被用户程序占用的剩余空闲的 Flash。在 STM32 的 ELF 可执行文件中编译器和链接器会把程序内容划分为多个段Section。其中.text和.rodata是最常见的两个 Flash 只读区域。图中蓝色的部分用户代码并不是一坨没有结构的二进制编译器Keil/GCC会把它整理成若干个段Section按地址从低到高依次排列。实际地址和排列顺序由链接脚本决定。不同芯片、不同 IDE、不同编译器优化等级下段的位置可能略有区别。.isr_vector中断向量表保存的是初始 MSP 和各处理函数入口地址.text保存Reset_Handler、main()、中断服务函数等的实际机器指令.rodata保存字符串、const 常量、查找表等只读数据。.data 初始化镜像存放已初始化全局 / 静态变量的初始值仅存于 Flash上电后启动代码会把这部分内容拷贝到 RAM 的.data区域使用。2.1 isr_vector 段中断向量表2.1.1 什么是 isr_vector 段isr_vector 段就是中断向量表位于整个程序的最开头起始地址为0x0800 0000Flash 的起始地址。它可以理解成一张跳转地址表——里面按 Cortex-M 内核规定的固定顺序依次存放着各种异常和中断的处理函数入口地址。当某个中断或异常发生时内核就会去这张表里查到对应的函数地址然后跳过去执行相应的服务函数ISR。isr_vector 段的特点特性说明内容各异常/中断服务函数的入口地址不是函数本体访问权限只读运行时不修改存储位置Flash中0x0800 0000起整个程序的最开头排列顺序由Cortex‑M内核固定规定不能随意改生命周期程序运行期间一直存在2.1.2 isr_vector 段的结构向量表开头两项是最关键的CPU 复位后硬件会自动读取它们地址内容作用0x0800 0000初始 MSP 值主栈指针/栈顶指针复位后自动装入 SP 寄存器存放的是栈内存顶部的物理地址0x0800 0004复位处理函数Reset_Handler的入口地址复位后自动装入 PC 寄存器CPU 从这里开始执行 PC 寄存器指向下一条要执行的指令地址 0x0000_0000初始 MSP主栈指针CPU 干活时会产生大量临时数据——局部变量、函数参数、返回地址等。这些数据需要一个专门的存放区域这就是内存中的 栈Stack。地址 0x0000_0000 存储的就是这块栈空间的 起始位置。打个比方把 CPU 想象成产线上的组装员工栈就是它临时放工具、零件和记录工作进度的工作台。函数调用、局部变量保存以及中断现场保护都会使用这张工作台。对 Cortex-M 单片机而言芯片复位后硬件首先从向量表的第一个表项读取初始主栈指针 MSP通常该表位于映射后的 0x00000000如果 MSP 没有被正确初始化后续函数压栈或中断自动保存现场时就可能向错误地址写数据导致内存破坏、Fault甚至 HardFault。所以芯片硬件做的第一件事就是自动去 0x0000_0000 读取栈指针把工作台搭好。地址 0x0000_0004Reset_Handler 入口地址工作台准备好了但 CPU 还是动不了——它不知道第一条指令在哪不知道第一步做什么。地址 0x0000_0004 存储的就是 Reset_Handler 函数在 Flash 中的地址。这是芯片出生后的第一份工作清单。硬件会自动把 0x0000_0004 里的值加载到 PC 寄存器程序计数器 中。PC 指向哪CPU 就去哪取指令执行——于是芯片开始跑起来了。再往后依次是各种异常和中断的入口地址有固定顺序地址内容0x0800 0008NMI_Handler0x0800 000CHardFault_Handler0x0800 0010 ~ 0038MemManage、BusFault、UsageFault、SVCall、PendSV 等系统异常0x0800 003CSysTick_Handler0x0800 0040起各个外设中断USART、TIM、EXTI等⚠️ 关键区别向量表里存的是中断函数的入口地址而函数本体机器码在 .text 段。isr_vector 告诉 CPU这个中断的处理函数在哪里text 提供 CPU这个函数具体要执行什么。2.1.3 了解MCU上电启动流程认识了向量表的结构后我们再来看上电后 MCU 是怎么利用这张表启动起来的。整个过程分为硬件自动和软件执行两个阶段。阶段一硬件自动完成取 SP 和 PCCortex-M 内核复位后硬件自动做两件事1、从 0x0800 0000 取出 MSP 初值装入 SP 寄存器——先把栈顶确定好STM32 的栈向下增长SP 一开始指向栈的最高地址2、从 0x0800 0004 取出 Reset_Handler 的地址装入 PC 寄存器——让程序跳到复位函数。 顺序是先定栈顶SP再定入口PC。有了栈后面的函数调用、压栈才能正常工作。阶段二软件执行Reset_Handler → mainPC 指向 Reset_Handler 后CPU 开始执行复位处理函数在进入 main() 之前还会依次完成这些初始化初始化系统时钟SystemInit把 .data 段的初值从 Flash 拷贝到 SRAM把 .bss 段在 SRAM 里清零最后跳转到 main()用户程序正式开始运行。完整启动流程图上电 / 复位↓硬件从 0x0800 0000 取 MSP → SP 寄存器定栈顶↓硬件从 0x0800 0004 取 Reset_Handler 地址 → PC 寄存器定入口↓执行 Reset_Handler初始化时钟 → 搬 .data → 清 .bss↓跳转到 main()用户程序开始运行⚠️注意1、MCU 上电启动流程中isr_vector 段里的 HandlerNMI、HardFault、SysTick、外设中断……一个都不会被执行因为它们不是按启动流程顺序执行的而是哪个中断/异常发生了才跳去执行哪个 Handler——完全由事件触发跟启动顺序无关。2、Reset_Handler 是STM32微控制器复位后执行的第一个函数也是整个程序的入口点。当系统上电、按下复位按键、看门狗超时或软件复位时CPU会从中断向量表的第二个位置0x08000004取出Reset_Handler 的地址并跳转执行。这个函数主要完成以下工作步骤操作说明①设置栈指针MSP将栈顶地址加载到主栈指针为函数调用做准备②拷贝.data段将Flash中保存的已初始化变量的初始值拷贝到SRAM中③清零.bss段将未初始化变量所在的SRAM区域全部清零④调用SystemInit()配置系统时钟PLL、Flash等待周期、总线分频等⑤调用main()跳转到C语言主函数开始执行用户代码⑥死循环如果main函数意外返回进入无限循环防止程序跑飞2.2 .text 段程序代码2.2.1 什么是 .text 段.text 段是程序代码段存放的是编译后的机器指令。这是程序执行的主体部分包含了所有函数如 main、SystemBlockInit、中断服务函数等的二进制机器码。2.2.2 .text 段的特点特性说明内容机器指令汇编代码访问权限只读程序运行时不会修改自身代码存储位置Flash 中STM32F407 从0x08000000起始的 Flash 区域执行方式可以直接在 Flash 中执行XIP生命周期程序运行期间一直存在2.2.3 .text 段包含的内容// 示例代码intadd(inta,intb){returnab;}voidmain(void){intresultadd(1,2);while(1);}编译后这段代码的 .text 段会包含add 函数的机器指令main 函数的机器指令此外一个完整的可执行程序.text 段通常还包含启动代码Reset_Handler中断服务函数ISRC 库函数如 memcpy 等 提示虽然在 .isr_vector 和 .text 中都能看到 Reset_Handler但两者保存的内容完全不同。.isr_vector 中保存 Reset_Handler 的入口地址。.text 中保存 Reset_Handler 的实际机器代码。也就是说.isr_vector 告诉 CPUReset_Handler 在哪里.text 提供 CPUReset_Handler 具体要执行什么。2.2.4 .text 段的典型使用场景.text 段存放的是可执行指令机器码通常位于 FlashROM中具有只读、可执行属性。程序运行时CPU 直接从这里取指令执行。常见的内容包括场景说明启动代码复位向量、中断向量表主函数main函数库函数标准库函数如memcpy、printf中断服务函数ISR中断服务例程RTOS 内核代码FreeRTOS 等内核代码2.3 .rodata 段详解2.3.1 什么是 .rodata 段.rodata 是只读数据段Read-Only Data存放的是程序运行过程中不会修改的数据如字符串常量、全局常量、const 修饰的变量等。2.3.2 .rodata 段的特点特性说明内容常量、字符串、const变量访问权限只读写入会触发 HardFault存储位置Flash 中.text段之后访问方式直接从 Flash 读取生命周期程序运行期间一直存在2.3.3 .rodata 段包含的内容// 字符串常量constchar*messageHello, World!;// const 修饰的变量constintMAX_VALUE100;// 静态常量staticconstintARRAY_SIZE10;// 字符串字面量voidprint_message(){printf(This is a string literal);}// 查找表constuint8_tsin_table[256]{0,1,2,...};编译后.rodata 段中会包含“Hello, World!” 字符串MAX_VALUE 的值100ARRAY_SIZE 的值10“This is a string literal” 字符串 sin_table 查找表2.3.4 .rodata 段的典型应用应用场景示例字符串常量Error: Invalid input查表数据正弦表、CRC 表、查找表配置参数默认配置、校准数据常量数组const int table[] {1, 2, 3, 4}函数指针表中断向量表、回调函数表2.4 .data 初始化镜像2.4.1 什么是.data 初始化镜像.data 初始化镜像是存储在 Flash 中的一组数据它包含了所有已初始化全局变量和静态变量的初始值。由于 SRAM 是易失性存储器断电后数据会丢失因此这些初始值必须保存在非易失性的 Flash 中。在系统启动时启动代码会将这些初始值从 Flash 拷贝到 SRAM 中对应的 .data 段。2.4.2 .data 初始化镜像的特点特性说明内容.data段中所有变量的初始值访问权限只读仅在启动时被读取存储位置Flash 中.rodata段之后作用启动时拷贝到 SRAM 中的.data段生命周期仅在启动阶段使用运行时不访问2.4.3 .data 初始化镜像包含的内容// 已初始化的全局变量intglobal_var100;// 已初始化的静态变量staticintstatic_var200;// 已初始化的全局数组uint8_tbuffer[4]{1,2,3,4};// 已初始化的结构体structconfig{uint16_tbaud_rate;uint8_tdata_bits;}uart_config{115200,8};编译后.data 初始化镜像中会包含global_var 的初始值100 static_var 的初始值200buffer 数组的初始值1, 2, 3, 4uart_config 结构体的初始值115200, 8.data 初始化镜像的启动过程系统上电启动流程系统上电↓① 硬件自动加载初始 MSP 和 Reset_Handler↓② Reset_Handler 执行↓③ 拷贝 .data 初始化镜像从 Flash 到 SRAM↓④ 清零 .bss 段↓⑤ 初始化堆栈↓⑥ 调用 main 函数.data 初始化镜像 与 .data段 的区别对比项.data初始化镜像.data段存储位置Flash非易失SRAM易失内容变量的初始值变量的运行时值访问权限只读可读可写生命周期仅在启动时使用程序运行期间一直存在修改方式不可修改可以修改2.4.4.data 初始化镜像的典型使用场景场景说明全局变量初始化int count 0;静态变量初始化static int flag 1;数组初始化uint8_t buffer[10] {0};结构体初始化struct config cfg {115200, 8};指针初始化char *ptr Hello;任何过程中由多数矛盾存在的话其中必定有一种是主要的起者领导的决定的作用。————《毛选》
返回列表