单片机 Flash 指定地址存储常量字符串调试笔记

一、基本信息

单片机型号:华大 HC32F460

开发环境:Keil

Flash规格: 512K (0x00000000 ~ 0x0007FFFF),扇区大小8K

目标功能:将常量字符串编译保存到Flash的指定绝对地址

二、问题现象

使用 attribute((at())) 直接指定字符串地址:

复制代码
const char myFixedString[] __attribute__((at(0x00078000))) = "Hello, HC32F460!";

将上述代码编译后Map文件异常。原代码 RO Data 约 66KB, 加入上述代码后,Total ROM Size 膨胀至 505KB,生成的烧录文件体积巨大,烧录缓慢且浪费 Flash 空间。

Map 文件对比:

项目 修改前 修改后
RO Size 66372 (64.82kB) 516116 (504.02kB)
ROM Size 68344 (66.74kB) 518088 (505.95kB)

三、原因分析

__attribute__((at(0x00078000))) 要求链接器将变量放置到该绝对地址。代码原有结束地址约 0x00010800,而目标地址 0x0007F000 相距约 442KB。Keil 链接器为了保证地址连续性,从代码结束处到目标地址之间的所有空白区域都被填充为 0,导致生成的映像文件包含大量无用数据,ROM Size 急剧增大。

四、解决方法

使用分散加载文件 + 独立加载域

原理

为固定数据创建一个独立的加载域(Load Region),起始地址直接设为所需地址。链接器不会在两个加载域之间插入填充数据。

步骤

打开 Keil 工程选项 → Linker → 取消勾选 "Use Memory Layout from Target Dialog"

点击 Edit 编辑分散加载.sct 文件,内容如下:

复制代码
; 主加载域:代码区,占用 Flash 前半部分,结束于数据区之前
LR_IROM1 0x00000000 0x00078000  {    ; 480KB,结束于 0x77FFF
  ER_IROM1 0x00000000 0x00078000  {
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x1FFF8000 0x0002F000  {
   .ANY (+RW +ZI)
  }
}

; 独立数据加载域:固定字符串,放在最后 8KB 扇区(0x78000 ~ 0x79FFF)
LR_ROM_DATA 0x00078000 0x00002000  {
  ER_ROM_DATA 0x00078000 0x00002000  {
    *(.my_fixed_data)    ; 收集所有标记为 .my_fixed_data 的段
  }
}

注意

主加载域大小(0x78000)必须保证实际代码+RO Data 不超过该值,否则会与数据域重叠。当前代码仅 66KB,安全。

使用独立加载域时,主加载域必须缩小到数据区起始地址之前,否则地址重叠会导致链接失败。

在 C 代码中定义变量到自定义段并编译。

复制代码
const char myFixedString[] __attribute__((section(".my_fixed_data"))) = "Hello, HC32F460!";

编译后下载代码到单片机,通过调试模式查看,Flash(0x00078000)地址并无指定数据。

查看Map文件发现以下链接报告,字符串段被链接器优化移除,最终 Flash 中并无数据。

复制代码
Removing apl_application.o(.my_fixed_data), (17 bytes).

原因分析

链接器默认开启"死代码消除"(Dead Code Elimination),虽然通过 .sct 文件为 .my_fixed_data 段分配了空间,但 C 代码中定义的字符串变量没有被任何地方引用(未读取其地址或内容),链接器认为该段是未使用的输入段,因此在最终链接阶段将其移除。

解决方法:

使用 __attribute__((used))强制保留自定义段

复制代码
// 字符串将被放置在 Flash 地址 0x00078000
const char myFixedString[] __attribute__((section(".my_fixed_data"), used)) = "Hello, HC32F460!";

五、验证步骤

  • 重新编译工程 ,观察编译输出无 Removing ... 警告。

  • 查看 Map 文件

    • 搜索 myFixedString,确认其地址为 0x00078000

    • 检查 Total ROM Size代码实际大小 + 字符串长度(约 66KB + 几十字节)。

  • 烧录并运行

    • 在代码中通过指针读取该地址的数据,打印确认内容正确。

六、关键注意事项

注意点 说明
地址冲突 指定的 Flash 地址必须在芯片有效范围内,且不能与代码、中断向量表重叠。建议放在 Flash 尾部扇区。
主加载域大小 使用独立加载域时,主加载域必须缩小到数据区起始地址之前,否则地址重叠会导致链接失败。
const 修饰 只读字符串建议定义为 const,数据直接放在 Flash 中,不占用 RAM。
运行时修改 如需运行时修改 Flash 中的字符串,不能使用 const,且必须调用 Flash 擦写函数(需在 RAM 中执行)
相关推荐
提灯春秋2 小时前
基于定时器中断的多任务轮询架构
单片机·嵌入式硬件·架构
飞鸟真人2 小时前
关于能所合一豆包问答笔记
笔记
jllllyuz2 小时前
ESP32开发-迷你掌上平衡车miniBot完整开发指南
单片机·嵌入式硬件
wb1892 小时前
docker-ce容器技术重习
运维·笔记·docker·容器·云计算
咖啡忍者2 小时前
【SAP CO】4.COPC产品成本控制-5.生产订单
笔记
_李小白2 小时前
【OSG学习笔记】Day 40: EventCallback(事件回调)
笔记·学习
不爱吃大饼2 小时前
WeMos开发板
单片机·嵌入式硬件
三佛科技-134163842122 小时前
LP3799FAC/LP3799FBC--非标60W(24V2.5A)电源芯片恒压恒流方案分析(电路图,PCB设计)
嵌入式硬件·物联网·智能家居·pcb工艺
雅斯驰2 小时前
BMS、电机控制、医疗设备:ISO1540DR的I²C隔离应用版图
单片机·嵌入式硬件