单片机 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 中执行)
相关推荐
weixin_452077647 小时前
STM32 烧录程序后上电不工作问题集合
stm32·单片机·嵌入式硬件
沸速存储7 小时前
内存技术的未来:DDR6、CAMM2与CXL将如何改变计算架构
科技·嵌入式硬件·架构·计算机外设·电脑
LCG元8 小时前
STM32多传感器数据采集系统设计:I2C+ADC+UART+低功耗工程实战
stm32·单片机·嵌入式硬件
国科安芯8 小时前
MCU芯片AS32X601如何使用 DMA 单次模式搬运 ADC 多通道采样数据?
单片机·嵌入式硬件
俊基科技8 小时前
1厘米的声学突围:A-29P如何在回音与噪声的双重围剿中“猎杀“纯净人声
嵌入式硬件·嵌入式开发·硬件开发·ai降噪·回声消除·语音模组·远扬拾音
一只小菜鸡..9 小时前
南京大学 操作系统 (JYY) 学习笔记:导论与历史的轮回 (OS Introduction)
笔记·学习
xqqxqxxq9 小时前
LeetCode Hot100 双指针专项题解笔记
笔记·算法·leetcode
懿路向前9 小时前
【HarmonyOS学习笔记】2026-07-24 | textProcessing 实体识别与踩坑实录
笔记·学习·边缘计算·harmonyos
春生野草10 小时前
C基础--八大排序(个人笔记)
c语言·笔记·排序算法
LCG元10 小时前
STM32 RS485 Modbus RTU通信系统设计:从协议解析到工程实战
stm32·单片机·嵌入式硬件