单片机 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 中执行)
相关推荐
wuyk55512 小时前
防逆向基础:在量产固件中关闭调试接口并设置读保护(RDP)的正确姿势
stm32·单片机·嵌入式硬件·性能优化·重构
李某学编程15 小时前
基于ARM的微机原理学习笔记(一)
arm开发·笔记·学习
zhangrelay15 小时前
单片机原理与接口技术-2026融合版-从MEGA2560-ESP32与Wokwi到ROS2-Lyrical与具身智能栈
单片机·嵌入式硬件
sbjdhjd16 小时前
AI产业周度动态:从“更强模型”走向“可执行、可治理、可计价”的系统竞争 (2026年10月19日—10月25日)
人工智能·笔记·ai·开源·gemini·信息差·claude code
lhbweilai16 小时前
分析FreeRTOS内核对象API的源码实现(八)
c语言·开发语言·stm32·单片机
艾络迅Acceleronix17 小时前
环境监测终端:STM32+EG800Z AT+ 艾络迅平台实战|多传感器采集联动全流程
c语言·stm32·单片机·嵌入式硬件·物联网·1024程序员节
涂山苏苏⁠17 小时前
stm32复习总结
stm32·单片机·嵌入式硬件
ss27317 小时前
AI时代首先数据库-PostgreSQL实战笔记 27 PostgreSQL数据写入与读取流程解析
数据库·笔记·postgresql
Zwawa17 小时前
一只按键不只“按下”:短按、长按和连按如何共存
c语言·单片机·嵌入式
苏格拉真没有底18 小时前
ESP32 AI MCP调试器
stm32·单片机·嵌入式硬件