/* 如有不对的地方 请过路大佬指正 */

核心概念:内存映射

STM32 微控制器采用 统一编址 的方式。这意味着 CPU 访问的所有资源(程序存储器 Flash、数据存储器 RAM、外设寄存器等)都被分配到一个巨大的、连续的 32位地址空间 (0x0000 0000 到 0xFFFF FFFF) 中。CPU 只需要通过地址(和访问方式)来区分它是在读 Flash 的数据、RAM 的变量,还是在配置一个外设寄存器。这个地址空间的布局规划就是 **内存映射**。

STM32F103C8T6 的关键内存资源

Flash (ROM):

  • 物理本质:非易失性存储器 (NVM)。断电后数据不丢失。写入速度慢,擦写次数有限。
  • 作用:主要用于存储 程序代码 (代码段 .text)、**常量数据** (只读数据段 .rodata) 以及需要在芯片启动时复制到 RAM 中的 初始化的全局/静态变量的初始值 (数据段 .data 的初始值)。
  • 大小:64 KB (0x10000 字节)
  • 起始地址0x0800 0000 (这是用户 Flash 的标准起始地址)
  • 映射别名:系统启动时,可以通过 BOOT0/BOOT1 引脚配置,将 Flash (0x0800 0000) 或系统存储器 (Bootloader) 映射到 0x0000 0000。复位后,CPU 总是从 0x0000 0000 开始执行第一条指令。这种别名机制简化了启动流程。

SRAM (RAM):

  • 物理本质:易失性存储器。断电后数据丢失。读写速度快。
  • 作用:存储程序运行时的 **变量和数据**。具体包括:
  • 已初始化且非零的全局/静态变量 (数据段 .data)
  • 未初始化或初始化为零的全局/静态变量 (bss段 .bss)
  • 函数内部的局部变量 (通常存储在 栈 stack 上)
  • 动态分配的内存 (存储在 堆 heap)
  • 函数调用时的返回地址、参数等 (存储在 栈 stack)
  • 大小:20 KB (0x5000 字节)
  • 起始地址0x2000 0000 (这是 Cortex-M 内核为 SRAM 分配的固定地址范围起始)

关键区别:Flash (ROM) vs RAM (SRAM)

特性

Flash (ROM)

RAM (SRAM)

易失性

非易失性 (断电数据保留)

易失性 (断电数据丢失)

写入速度

 (需要擦除块再写入)

擦写次数

有限 (约 1万 - 10万次)

无限

主要用途

存储程序代码、常量、初始值

存储运行时变量、栈、堆、中间数据

起始地址

0x0800 0000 (用户)

0x2000 0000

访问方式

通过 Flash 接口控制器 (FSMC/FLITF)

直接通过总线矩阵

成本/密度

较低成本/较高密度

较高成本/较低密度

程序段 (Sections) 详解及其地址位置

当编译器编译你的 C/C++ 程序时,它会将不同类型的数据和代码归类到不同的“段”(Section) 中。链接器 (Linker) 则根据链接脚本 (*.ld 文件) 的指示,将这些段放置到内存映射中合适的地址范围。

以下是 STM32 程序中最核心的几个段及其在 STM32F103C8T6 内存映射中的典型位置:

代码段 (.text):

  • 内容:程序的可执行机器指令 (代码)。
  • 位置:Flash (ROM) 中。
  • 地址范围:通常从 0x0800 0000 开始连续存放。这是程序执行的起点。
  • 举例:你写的 main() 函数,各种中断处理函数 (IRQ_Handler),库函数代码等的编译结果都在这里。
  • 大小:取决于代码复杂度。

只读数据段 (.rodata):

  • 内容:程序中定义的常量 (const 修饰的全局或静态变量)、字符串字面量。
  • 位置:Flash (ROM) 中,通常紧跟在 .text 段后面。
  • 地址范围:例如 0x0800 XXXX (XXXX 取决于 .text 段大小)。
  • 举例

C
const float pi = 3.14159f; // 这个 pi 的值在 .rodata
char *message = "Hello, STM32!"; // 字符串 "Hello, STM32!" 本身在 .rodata (指针 message 在 .data 或 .bss)

已初始化数据段 (.data):

  • 内容已初始化且初始值不为零的全局变量和静态变量。
  • 位置
  • 初始值 存储在 Flash (ROM) 中 (通常在 .rodata 后面)。
  • 变量本身 在运行时位于 SRAM (RAM) 中。
  • 关键过程:芯片上电启动后,在 main() 函数执行之前,由启动文件 (startup_stm32f10x_xx.s) 中的初始化代码负责将 .data 段的初始值从 Flash 复制到 SRAM 中对应的位置。
  • SRAM 地址范围:通常从 SRAM 起始地址 0x2000 0000 开始存放 .data 段。
  • Flash 地址范围:例如 0x0800 YYYY (YYYY 取决于 .text + .rodata 的大小)。
  • 举例

C
int initialized_global = 42;        // 初始值 42 在 Flash, 变量在 SRAM
static int initialized_static = 100; // 初始值 100 在 Flash, 变量在 SRAM

未初始化数据段 (.bss):

  • 内容未初始化显式初始化为零的全局变量和静态变量。
  • 位置:SRAM (RAM) 中,紧跟在 .data 段后面**。
  • 关键过程:上电启动时,在 main() 执行之前,启动代码负责将整个 .bss 段在 SRAM 中的区域清零。
  • 地址范围:例如 0x2000 00AA (AA 取决于 .data 段在 SRAM 中的大小)。
  • 举例

C
int uninitialized_global;         // 在 .bss (启动时清零为0)
static int uninitialized_static;   // 在 .bss (启动时清零为0)
int zero_global = 0;               // 也在 .bss (启动时清零为0)

堆 (Heap):

  • 内容:用于 动态内存分配 (malloc(), calloc(), new 等)。
  • 位置:SRAM (RAM) 中,紧跟在 .bss 段后面。
  • 地址范围:例如 0x2000 0BBB 开始 (BBB 取决于 .data + .bss 的大小)。
  • 增长方向:向上增长 (向高地址增长)。
  • 管理:由内存管理库 (如 malloc 的实现,可能是标准库 newlib 的一部分或用户实现的) 管理。如果分配失败,malloc 返回 NULL
  • 大小:在链接脚本中定义 (_heap_end 或类似符号)。STM32CubeIDE 等工具生成的默认链接脚本通常将堆大小设置为一个较小的值 (如 0x200),需要根据应用动态分配需求手动调整链接脚本增大。

栈 (Stack):

  • 内容:存放函数调用时的 局部变量、函数参数、返回地址、函数调用的上下文 (寄存器保存) 等。
  • 位置:SRAM (RAM) 的 顶部。
  • 地址范围:栈顶指针 (SP) 初始值指向 SRAM 的末尾地址。例如,20KB SRAM 的地址范围是 0x2000 0000 - 0x2000 4FFF。栈顶初始值通常是 0x2000 5000 (虽然 0x2000 4FFF 是最后一个字节,但栈指针通常指向第一个可用字节或第一个不可用字节之上,由架构约定,CM 通常约定指向第一个可用字节,即初始 SP 为 0x2000 5000)。栈空间从这个高地址向下增长。
  • 增长方向:向下增长 (向低地址增长)。
  • 管理:由编译器在函数调用/返回时自动管理。如果栈溢出(增长到与堆或其他数据区域重叠),会导致程序崩溃(通常是难以调试的硬错误)。
  • 大小:在链接脚本和启动文件中定义。STM32CubeIDE 等工具生成的启动文件通常将栈大小设置为一个默认值 (如 0x400 = 1KB),需要根据函数调用深度和局部变量大小评估并手动调整启动文件增大。

STM32F103C8T6 内存映射图示 (简化版)

32-Bit Address Space 
+------------------------------------+ 0xFFFF FFFF
|          ... (Reserved) ...        |
| Peripherals (GPIO, USART, SPI, ...)| ---> 外设寄存器区域 (0x4000 0000 - 0x5FFF FFFF 等)
+------------------------------------+
|          ... (Reserved) ...        |
+------------------------------------+ 0x6000 0000
| External RAM (FSMC/FMC) [if used]  | ---> 外部 RAM (如果使用)
+------------------------------------+ 0xA000 0000
|          ... (Reserved) ...        |
+------------------------------------+ 0xDFFF FFFF
|          ... (Reserved) ...        |
+------------------------------------+ 0xE000 0000
|   Private Peripheral Bus (PPB)     | ---> Cortex-M 内核私有外设 (NVIC, SCB, SysTick @ 0xE000E000)
|  (NVIC, SysTick, SCB, Debug, ...)  |
+------------------------------------+ 0xE00F FFFF
|          ... (Reserved) ...        |
+====================================+ <--- 专注于 STM32F103C8T6 实际物理资源 --->
|                                    |
|                                    |
|           SRAM (20KB)              |
|  +------------------------------+  |
|  |      Stack (向下增长)         |  | 0x2000 5000 (初始 SP) ---> (栈顶)
|  |  (局部变量, 返回地址等)       |  |
|  |                              |  |
|  |  ... growing downward ...    |  |
|  +------------------------------+  |
|  |          ... (空闲) ...       |  |
|  +------------------------------+  |
|  |          Heap (向上增长)      |  | 0x2000 0BBB ---> (堆底)
|  |  (malloc, new, etc.)         |  |
|  |                              |  |
|  |  ... growing upward ...      |  |
|  +------------------------------+  |
|  |          .bss Section        |  | 0x2000 00AA
|  |  (未初始化/零初始化全局/静态) |  |
|  +------------------------------+  |
|  |          .data Section       |  | 0x2000 0000 (SRAM 起始)
|  |  (已初始化全局/静态变量)      |  |    (值从 Flash 复制而来)
|  +------------------------------+  |
|                                    |
+------------------------------------+ 0x2000 4FFF (SRAM 结束)
|          ... (Reserved) ...        |
+====================================+
|                                    |
|         Flash (64KB)               |
|  +------------------------------+  |
|  |          .rodata Section     |  | 0x0800 YYYY
|  |  (常量, 字符串字面量)         |  |
|  +------------------------------+  |
|  |          .data Initializers  |  | 0x0800 XXXX (初始值)
|  |  (.data段变量的初始值)        |  |    (复制到 SRAM .data)
|  +------------------------------+  |
|  |          .text Section       |  | 0x0800 0000 (Flash 起始)
|  |  (程序代码)                  |  |
|  +------------------------------+  |
|                                    |
+------------------------------------+ 0x0800 FFFF (Flash 结束)
|          ... (Reserved) ...        |
+------------------------------------+ 0x0000 0000 (复位向量起点)
      (别名:取决于 BOOT 引脚,通常是 0x0800 0000)

总结与关键点

内存映射

  • CPU 通过 32 位地址访问一切 (Flash, RAM, 外设)。不同资源占据地址空间的不同区块。

Flash vs RAM

  • Flash (0x0800 0000):存代码 (.text)、常量 (.rodata) 和 .data 段的初始值。只读、非易失。
  • SRAM (0x2000 0000):存运行时数据。包括变量 (.data, .bss)、动态内存 (堆 heap)、函数调用信息 (栈 stack)。可读写、易失。

程序段

  • .text:代码 -> Flash。
  • .rodata:常量 -> Flash。
  • .data:初始化的变量 -> 运行时在 RAM,初始值在 Flash (需复制)。
  • .bss:未初始化/零初始化变量 -> RAM (启动时清零)。
  • 堆 (heap):动态内存 -> RAM (在 .bss 后,向上增长)。
  • 栈 (stack):函数调用 -> RAM 顶部 (向下增长)。

启动过程

  • 芯片上电复位后,硬件从 0x0000 0000 (映射到 0x0800 0000) 取第一条指令执行。启动代码 (startup_stm32f10x_xx.s) 负责:

  • 设置初始栈指针 (SP = _estack, 指向 SRAM 末尾)。
  • .data 段的初始值从 Flash 复制到 SRAM 的 .data 区域。
  • .bss 段在 SRAM 中的区域清零。
  • 调用 SystemInit() (配置时钟等)。
  • 跳转到 main() 函数。

链接脚本 (.ld):

  • 这是定义内存布局的核心文件。它精确指定了:
  • 可用的物理内存区域 (Flash, RAM) 及其起始地址、大小。
  • 各个段 (.text, .rodata, .data, .bss) 分别放置在哪个内存区域。
  • 堆 (_heap_start, _heap_end) 和栈 (_estack) 的位置和大小。

堆栈溢出

  • 是嵌入式系统常见且危险的错误。栈向下增长撞到其他数据 (.bss, .data, 堆),或堆向上增长撞到栈,都会导致程序崩溃或行为异常。务必根据应用需求在链接脚本和启动文件中合理设置堆栈大小 (_Min_Heap_Size, _Min_Stack_Size 或 Stack_Size, Heap_Size)。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐