使用__attribute__((at(addr))) 固定变量到指定 Flash 地址
文章目录
- 一、代码示例:将变量固定到 Flash 0x08001000
- 二、__attribute__((at(addr))) 的作用
- 三、__attribute__((at(addr))) 可能导致的问题
- 四、运行时修改 Flash 存储的变量
- 五、在 GCC(STM32CubeIDE)中实现同样功能
在嵌入式开发中,有时我们需要在 Flash 指定地址存储特定的标志位或常量数据,例如 Bootloader 标志、固件版本信息、校验码等。这时,可以使用 __attribute__((at(addr)))
关键字,将变量固定存放在指定的 Flash 地址。
一、代码示例:将变量固定到 Flash 0x08001000
以下代码用于将 custom_data 变量固定到 Flash 地址 0x08001000,并赋值 0x12345678:
#define CUSTOM_FLAG_ADDR 0x08001000
#define SET_COMPILE_ADDR(addr) __attribute__((at(addr)))
SET_COMPILE_ADDR(CUSTOM_FLAG_ADDR) const uint32_t custom_data = 0x12345678;
代码解析:
#define CUSTOM_FLAG_ADDR 0x08001000
:定义 Flash 目标存储地址 0x08001000,该地址必须是空闲的,否则可能会覆盖已有的代码或数据。
#define SET_COMPILE_ADDR(addr) __attribute__((at(addr)))
:使用 attribute((at(addr))) 指定变量存放的物理地址(仅适用于 ARM Compiler 5)。
SET_COMPILE_ADDR(CUSTOM_FLAG_ADDR) const uint32_t custom_data = 0x12345678
:让 custom_data 变量存放在 0x08001000,并初始化值为 0x12345678。
二、attribute((at(addr))) 的作用
__attribute__((at(addr)))
是 Keil ARM Compiler 5(ARMCC5) 提供的编译器特性,允许开发者在编译时强制将变量存放到指定的 Flash 或 RAM 地址。例如:
-
用于 Bootloader 和应用程序通信
-
存储设备唯一 ID、固件版本等不可变数据
-
存储升级标志位
Tips:
-
Keil ARM Compiler 6(ARMCC6)和 GCC(STM32CubeIDE)不支持 attribute((at(addr)))
- ARMCC6 和 GCC 需要使用链接脚本(scatter 文件或 .ld 文件)指定存储区域,不能直接使用 attribute((at(addr)))。
-
变量存放地址必须是 Flash 空闲区
-
如果 0x08001000 处已有数据或程序代码,custom_data 可能会破坏已有内容,导致程序崩溃。
-
需要先查看 Keil 生成的 .map 文件,确保 0x08001000 是可用的。
-
-
Flash 不能直接修改
-
custom_data 存在于 Flash,而 Flash 不能像 RAM 一样直接赋值修改。
-
如果要修改该变量的值,必须使用 Flash 擦除 + 重新写入 操作(见后文)。
-
三、attribute((at(addr))) 可能导致的问题
直接使用 attribute((at(addr))) 可能遇到的问题:
-
问题 1:Flash 代码段冲突
如果 0x08001000 处有代码或数据,custom_data 可能会覆盖已有内容,导致程序崩溃。
✅解决方案:
-
查看 .map 文件 确保 0x08001000 是空闲的。
-
修改 scatter 文件 或 STM32CubeIDE 的 .ld 文件,手动指定存储区域。
-
-
问题 2:Flash 不能直接写入
由于 Flash 只能在擦除后写入,custom_data 变量的值 0x12345678 无法在运行时修改。
✅解决方案:
- 使用 Flash 读写 API 修改该地址的数据,而不是使用 attribute((at(addr)))。
四、运行时修改 Flash 存储的变量
如果 custom_data 需要在程序运行过程中修改,就不能直接用 attribute((at(addr))),而是应该使用 Flash 读写 API,如下所示:
#include "stm32f4xx_hal.h"
#define CUSTOM_FLAG_ADDR 0x08001000 // Flash 目标地址
void Write_CustomData(uint32_t new_value) {
HAL_FLASH_Unlock(); // 解锁 Flash 写权限
// 擦除 Flash 扇区(0x08001000 可能属于 SECTOR_1,需确认)
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t SectorError;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
EraseInitStruct.Sector = FLASH_SECTOR_1; // 需要根据具体 MCU 修改
EraseInitStruct.NbSectors = 1;
HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError);
// 写入新值
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, CUSTOM_FLAG_ADDR, new_value);
HAL_FLASH_Lock(); // 锁定 Flash
}
🔹 该函数的作用:
-
先解锁 Flash
-
擦除 Flash 对应扇区
-
重新写入新的值
-
锁定 Flash,防止误操作
📌 优点
-
可以在运行时修改 custom_data,不会破坏代码。
-
适用于需要存储状态标志、参数配置的场景。
五、在 GCC(STM32CubeIDE)中实现同样功能
由于 __attribute__((at(addr)))
不支持 GCC,我们可以在 STM32CubeIDE 或 ARM Compiler 6 中使用链接脚本(.ld 文件) 实现相同效果。
1. 修改 STM32F4xx.ld
在 MEMORY 段中添加:
FLASH_CUSTOM (rx) : ORIGIN = 0x08001000, LENGTH = 4
然后,在 SECTIONS 段中添加:
.custom_data_section :
{
*(.custom_data_section)
} > FLASH_CUSTOM
2. 在代码中存放变量
const uint32_t custom_data __attribute__((section(".custom_data_section"))) = 0x12345678;
📌 综上结论
如果数据不需要修改,可以用 attribute((at(addr)))(仅限 ARMCC5)。
如果数据需要修改,建议使用 Flash 读写 API。