在 STM32 开发中,经常需要变量保持连续性(如 Bootloader 跳转、复位后保留关键参数),此时需让变量跳过系统初始化流程。本文基于 ST 官方 LAT1289 应用笔记(Rev 1.0),针对 Keil、IAR、CubeIDE 三大主流编译环境,详解变量不被初始化的配置方法与验证技巧,适用于 STM32G431 等全系列芯片,助力工程师快速落地需求。
1. 核心需求与原理
1.1 应用场景
- Bootloader 与 APP 跳转时,传递参数(如升级标志、设备状态);
- 系统复位后,保留关键数据(如校准参数、运行计数);
- 低功耗模式唤醒后,恢复变量原有值(避免重复初始化)。
1.2 实现原理
默认情况下,STM32 编译系统会将未显式初始化的变量放入 ZI 段(.bss),并在程序启动时自动初始化为 0。要让变量不被初始化,核心逻辑是:
- 将目标变量分配到独立的未初始化段(如.noinit、NO_INIT);
- 配置编译器 / 链接器,跳过该段的初始化流程,保留变量原始值。
2. 分环境实操方案
2.1 IAR 环境:最简单的关键字直接配置
IAR 无需修改链接文件,直接通过专用关键字修饰变量即可,步骤仅 2 步:
(1)变量声明:添加__no_init 关键字
在需要不被初始化的变量前,用
__no_init修饰,示例:// 声明16位无符号变量Test_NoInit,不被初始化
__no_init uint16_t Test_NoInit;
(2)功能验证
通过周期复位验证变量连续性,示例代码:
Test_NoInit += 10; // 每次运行增加10
HAL_Delay(2000); // 延时2秒
HAL_NVIC_SystemReset(); // 系统复位
- 预期效果:每次复位后,Test_NoInit 的值在上次基础上增加 10(而非重置为 0 后增加),证明配置生效。
2.2 Keil 环境:分编译器版本配置(AC5/AC6)
Keil 无专用关键字,需通过修改链接文件(.sct)划分独立区域,且需区分 Arm Compiler 5(AC5)和 Arm Compiler 6(AC6)版本。
核心前提
将变量放入带
UNINIT属性的 ZI 段(.bss),确保编译器不自动初始化该段数据。(1)Arm Compiler 5(AC5)配置步骤
-
修改链接文件(.sct):划分两个 RAM 区域,其中
RW_IRAM2为未初始化区域,属性设为UNINIT,示例:LR_IROM1 0x08000000 0x00020000 { // 代码区 ER_IROM1 0x08000000 0x00020000 { // 执行区 *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) .ANY (+XO) } RW_IRAM1 0x20000400 0x00007C00 { // 普通RAM区(初始化) .ANY (+RW +ZI) } // 未初始化区域:地址0x20000000,大小1KB(0x400),属性UNINIT RW_IRAM2 0x20000000 UNINIT 0x00000400 { .ANY (NO_INIT) // 绑定NO_INIT段 } } -
变量声明:
用
__attribute__指定变量归属NO_INIT段,并添加zero_init修饰,示例:// AC5专用声明:归属NO_INIT段,不被初始化 uint16_t Test_NoInit __attribute__((section("NO_INIT"), zero_init));
(2)Arm Compiler 6(AC6)配置步骤
AC6 不支持
zero_init修饰,且段命名需带.bss前缀,步骤如下:-
修改链接文件(.sct):未初始化区域绑定
.bss.NO_INIT段,示例:RW_IRAM2 0x20000000 UNINIT 0x00000400 { .ANY (.bss.NO_INIT) // AC6需带.bss前缀 } -
变量声明:仅指定
.bss.NO_INIT段,无需zero_init,示例:// AC6专用声明:归属.bss.NO_INIT段 uint16_t Test_NoInit __attribute__((section(".bss.NO_INIT")));
(3)Keil 环境配置注意
- 需在工程属性中确认编译器版本(Target → Code Generation → Arm Compiler);
- 链接文件修改后,需重新编译工程,确保配置生效。
3. CubeIDE 环境:修改 Linker 脚本(.ld)
CubeIDE 基于 GCC 编译器,需通过修改链接脚本(.ld)划分未初始化 RAM 区域,步骤分 3 步:
(1)修改 Linker 脚本:划分 RAM_NOINIT 区域
打开工程的
.ld文件(如STM32G431RBTX_FLASH.ld),在MEMORY节点中添加RAM_NOINIT区域,示例:MEMORY
{
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K // 代码区
RAM (xrw) : ORIGIN = 0x20000400, LENGTH = 31K // 普通RAM区(初始化)
RAM_NOINIT (xrw) : ORIGIN = 0x20000000, LENGTH = 1K // 未初始化RAM区(1KB)
}
(2)添加段描述:绑定.noinit_ram 段
在
.ld文件的SECTIONS节点中,添加.noinit_ram段描述,关联RAM_NOINIT区域: SECTIONS
{
// 其他段配置...
.noinit_ram : {
ALIGN(4); // 4字节对齐
_sbss_ram = .; // 段起始地址
*(.noinit_ram) // 匹配所有.noinit_ram段的变量
*(.noinit_ram*) // 匹配带后缀的.noinit_ram段
ALIGN(4);
_ebss_ram = .; // 段结束地址
} >RAM_NOINIT // 绑定到RAM_NOINIT区域
}
(3)变量声明:指定.section 属性
通过
__attribute__将变量归属.noinit_ram段,示例:// 声明不被初始化的变量,归属.noinit_ram段
__attribute__((section(".noinit_ram"))) uint16_t Test_NoInit;
(4)特殊芯片补充配置
部分 STM32 系列(如 STM32L4)需配置选项字节,确保 SRAM 复位后不被擦除:
- 配置
SRAM2_RST位:勾选 “SRAM2 is not erased when a system reset occurs”; - 配置路径:CubeIDE → Project → Properties → STM32Cube → Option Bytes。
3. 验证方法与关键注意事项
3.1 通用验证流程
- 声明目标变量并赋值(如
Test_NoInit = 0x1234); - 触发系统复位(如
HAL_NVIC_SystemReset())或低功耗唤醒; - 复位后读取变量值,若仍为
0x1234(或上次修改后的值),则配置生效。
3.2 避坑关键要点
- 变量类型限制:全局变量、静态局部变量支持该配置,普通局部变量(栈上)不支持;
- 地址冲突:划分未初始化区域时,需避免与栈(CSTACK)、堆(HEAP)地址重叠;
- 编译优化:若变量未被使用,可能被编译器优化,需在代码中添加实际操作(如赋值、打印);
- 多文件共享:若变量需跨文件使用,需在声明时添加
extern关键字(如extern __no_init uint16_t Test_NoInit;)。
4. 三大环境配置对比总结
| 编译环境 | 核心操作 | 关键差异 | 适用场景 |
|---|---|---|---|
| IAR | 关键字__no_init修饰变量 |
无需修改链接文件,配置最简单 | 快速开发、对链接脚本不熟悉的场景 |
| Keil AC5 | 修改.sct 文件 +section("NO_INIT")+zero_init |
需带zero_init修饰 |
旧项目兼容、使用 AC5 编译器的场景 |
| Keil AC6 | 修改.sct 文件 +section(".bss.NO_INIT") |
段名带.bss 前缀,无zero_init |
新项目、使用高版本编译器的场景 |
| CubeIDE | 修改.ld 文件 +section(".noinit_ram") |
需划分 RAM_NOINIT 区域 | 基于 GCC 编译器、STM32Cube 生态的场景 |
STM32 变量不被初始化的实现,本质是 “独立未初始化段 + 编译器跳过初始化” 的组合。IAR 环境配置最便捷,Keil 需区分编译器版本,CubeIDE 需修改 Linker 脚本,工程师可根据项目使用的编译环境选择对应方案。
阅读全文
368