单片机 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 中执行)
相关推荐
xuhaoyu_cpp_java3 小时前
项目学习(三)分页查询
java·经验分享·笔记·学习
Szime4 小时前
高速 ADC 国产替代选型:通信、雷达、仪器仪表项目要看哪些参数?
单片机·嵌入式硬件·fpga开发
菜鸟的学习日记、6 小时前
GPIO的几种模式——以STM32为例
stm32·单片机·嵌入式硬件·gpio
Cloud_Shy6186 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第五章 Item 33 - 35)
开发语言·人工智能·笔记·python·学习方法
做cv的小昊6 小时前
计算机图形学:【Games101】学习笔记08——光线追踪(辐射度量学、渲染方程与全局光照、蒙特卡洛积分与路径追踪)
图像处理·笔记·学习·计算机视觉·游戏引擎·图形渲染·概率论
星恒随风6 小时前
C++ 类和对象入门(五):初始化列表、explicit 和 static 成员详解
开发语言·c++·笔记·学习·状态模式
辰哥单片机设计6 小时前
STM32智能睡眠检测系统
stm32·单片机·嵌入式硬件
隔窗听雨眠8 小时前
在STM32上跑通TinyML:从模型训练到推理优化的完整实战指南
stm32·单片机·嵌入式硬件
ryanuo710 小时前
Mac(M芯片)上进行嵌入式开发遇到的问题
嵌入式硬件·macos·开发板
伊布拉西莫10 小时前
【流畅的Python】第20章:并发执行器 — 学习笔记
笔记·python·学习