在嵌入式系统应用中,有时需要将常量或者函数放在代码闪存(Code Flash)的指定地址,以便后续的功能扩展或代码更新。
本文基于 EK-RA4M2,详细介绍如何在不同编译器环境下实现这一目标,涵盖 GNU、LLVM、IAR 和 Arm Compiler 6 共四种编译器。
由于瑞萨电子 RA 系列开发基于 FSP(Flexible Software Package),因此项目构建均基于 FSP 生成的代码。对于 IAR EW for Arm、Keil MDK 这类第三方集成开发环境(IDE),则需要依赖 RASC(RA Smart Configurator)创建项目。
对于所有支持的编译器来说,修改流程基本一致,核心步骤如下:
- 在 链接脚本文件(Linker Script File)中添加新的自定义段(section),并指定地址范围(通常包括起始地址和大小)。由于 FSP 工程会在每次重新 Generate Project Content 时重写相关的 Linker Script File,因此需要先备份原始文件,重命名后再进行修改。
- 使用特定关键字声明需要定位的函数或常量,将其放入新增 section 中。
- 在 IDE 中修改配置,使更新后的 Linker Script File 生效。
- 编译工程,通过生成的
*.map 文件确认相应标号已正确放置在目标地址。
假定目标是在 RA4M2 Code Flash 最高地址处新增两个 section:一个用于存放函数,另一个用于存放常量。由于高地址的 Block Size 为 32K(0x8000),因此两个 section 大小均设定为 0x8000。
实际使用时可以根据自己的需求调整大小,但一定要考虑改写内容时,最小的 Erase/Program 单元都是 Block Size。
一、GNU 编译器
首先介绍在 e²studio 中使用 GNU 编译器时的操作步骤。
在 e²studio 中找到以下 3 个文件,在原始路径下复制并重命名:
script\fsp.ld → script\fsp_new.ld
Debug\memory_regions.ld → Debug\memory_regions_new.ld
Debug\fsp_gen.ld → Debug\fsp_gen_new.ld
第一步,编辑 Debug\memory_regions_new.ld,在文件开头增加以下内容,对标号赋值:
FLASH_USER_DATA_LENGTH = 0x8000;
FLASH_USER_TEXT_LENGTH = 0x8000;
由于已经指定 2 个 Block 存储数据,用于其它内容链接的地址空间需要在此基础上去掉这 2 个 Block,因此更新 FLASH_LENGTH,并增加存放函数的 TEXT section 和存放常量的 DATA section:
FLASH_LENGTH = 0x00080000 - FLASH_USER_DATA_LENGTH - FLASH_USER_TEXT_LENGTH;
FLASH_USER_DATA_START = FLASH_START + FLASH_LENGTH;
FLASH_USER_TEXT_START = FLASH_USER_DATA_START + FLASH_USER_DATA_LENGTH;
memory_regions_new.ld 文件的变动参考下图:

第二步,修改 fsp_gen_new.ld 文件,在 MEMORY 中增加 FLASH_USER_DATA 和 FLASH_USER_TEXT;找到 __flash_readonly$$(READONLY)(注意:此处是两个下划线),在其后增加 .mydata 和 .mytext。
MEMORY
{
RAM (rwx) : ORIGIN = RAM_START, LENGTH = RAM_LENGTH
FLASH (rx) : ORIGIN = FLASH_START, LENGTH = FLASH_LENGTH
FLASH_USER_DATA (rx) : ORIGIN = FLASH_USER_DATA_START, LENGTH = FLASH_USER_DATA_LENGTH
FLASH_USER_TEXT (rx) : ORIGIN = FLASH_USER_TEXT_START, LENGTH = FLASH_USER_TEXT_LENGTH
DATA_FLASH (rx) : ORIGIN = DATA_FLASH_START, LENGTH = DATA_FLASH_LENGTH
QSPI_FLASH (rx) : ORIGIN = QSPI_FLASH_START, LENGTH = QSPI_FLASH_LENGTH
.mydata :
{
*(.rodata*)
}> FLASH_USER_DATA
.mytext :
{
*(.text*)
}> FLASH_USER_TEXT
}
fsp_gen_new.ld 文件的变动参考下图:

第三步,修改源码,将 const 类型变量和函数声明放在新增的 .mydata 和 .mytext 两个 section 中,同时增加函数定义。在 hal_entry() 中增加函数调用,避免被优化:
const uint8_t test_data[8] BSP_PLACE_IN_SECTION(".mydata") = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
};
uint32_t test_function_fibonacci(uint32_t num) BSP_PLACE_IN_SECTION(".mytext") __attribute__((noinline));

第四步,按照以下方式更新 fsp_new.ld 文件,使改动后的 Linker Script File(memory_regions_new.ld 和 fsp_gen_new.ld)生效:

最后一步,在 e²studio 中将 Linker Script File 设置为 fsp_new.ld:

完成上述所有步骤后编译工程,目标函数和常量就已经放在指定地址了。打开 *.map 文件,用常量和函数名作为关键字搜索,可以看到以下匹配:

二、LLVM 编译器
接下来介绍在 e²studio 中使用 LLVM 编译器时的操作步骤。
在 e²studio 中找到 3 个文件,在原始路径下复制并重命名:
script\fsp.lld → script\fsp_new.lld
Debug\memory_regions.lld → Debug\memory_regions_new.lld
Debug\fsp_gen.lld → Debug\fsp_gen_new.lld
第一步,编辑 Debug\memory_regions_new.lld,文件变动参考下图:

第二步,修改 fsp_gen_new.lld 文件,参考下图:

第三步,修改源码,将 const 类型变量和函数声明放在新增的 .mydata 和 .mytext 两个 section 中,同时增加函数定义。在 hal_entry() 中增加函数调用,避免被优化:
const uint8_t test_data_0[8] BSP_PLACE_IN_SECTION(".mydata") = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
};
const uint8_t test_data_1[8] BSP_PLACE_IN_SECTION(".mydata") = {
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
uint32_t test_function_fibonacci(uint32_t num) BSP_PLACE_IN_SECTION(".mytext") __attribute__((noinline));
int test_function_is_prime(int num) BSP_PLACE_IN_SECTION(".mytext") __attribute__((noinline));

第四步,按照以下方式更新 fsp_new.lld 文件,使改动后的 Linker Script File(memory_regions_new.lld 和 fsp_gen_new.lld)生效:

最后一步,在 e²studio 中将 Linker Script File 设置为 fsp_new.lld:

编译工程后,打开 *.map 文件,用常量和函数名作为关键字搜索,可以看到以下匹配:

三、IAR 编译器
前面介绍的是在瑞萨 e²studio 中使用 GNU 和 LLVM 的配置步骤。对于 IAR 和 Keil MDK 这类第三方 IDE,可以利用 RASC(Renesas RA Smart Configurator)创建相应工程后再修改。
对于 IAR Project,在 workspace 路径下找到 3 个文件,在原始路径下复制并重命名:
script\fsp.icf → script\fsp_new.icf
memory_regions.icf → memory_regions_new.icf
fsp_gen.icf → fsp_gen_new.icf
第一步,编辑 memory_regions_new.icf,文件变动参考下图:

第二步,修改 fsp_gen_new.icf 文件,参考下图:

第三步,修改源码,将 const 类型变量和函数声明放在新增的 .user_const 和 .user_text 两个 section 中,同时增加函数定义。在 hal_entry() 中增加函数调用,避免被优化:

第四步,按照以下方式更新 fsp_new.icf 文件,使改动后的 Linker Script File(memory_regions_new.icf 和 fsp_gen_new.icf)生效:

最后一步,在 IAR EW for Arm 中将 Linker Script File 设置为 fsp_new.icf:

特别提醒:在 IAR 中执行 clean 操作后,该配置会恢复为默认值(fsp.icf),需要重新进行修改。
编译工程后,打开 *.map 文件,用常量和函数名作为关键字搜索,可以看到以下匹配:

四、Arm Compiler 6
对于 Keil MDK 工程,在工程路径下找到 3 个文件,在原始路径下复制并重命名:
script\fsp.scat → script\fsp_new.scat
memory_regions.scat → memory_regions_new.scat
fsp_gen.scat → fsp_gen_new.scat
第一步,编辑 memory_regions_new.scat,文件变动参考下图:

第二步,修改 fsp_gen_new.scat 文件,参考下图。找到关键字 LOAD_REGION_OPTION_SETTING_OFS0,在该段落前增加 .my_data_section 和 .my_text_section 两个 section:

第三步,修改源码,将 const 类型变量和函数声明放在新增的 .my_data_section 和 .my_text_section 两个 section 中,同时增加函数定义。在 hal_entry() 中增加函数调用,避免被优化:

第四步,按照以下方式更新 fsp_new.scat 文件,使改动后的 Linker Script File(memory_regions_new.scat 和 fsp_gen_new.scat)生效:

最后一步,在 Keil MDK 中将 Linker > Scatter File 设置为 fsp_new.scat:

编译工程后,打开 *.map 文件,用常量和函数名作为关键字搜索,可以看到以下匹配:

小结
关于 const 变量、函数定位到 Code Flash 指定地址的方法,本文已经基于 GNU、LLVM、IAR 和 Arm Compiler 6 共四款主流编译器做了完整对比与说明。
理论讲清楚之后,接下来就是实操环节。对应的示例工程也已经准备好,方便直接验证和复用:
https://gitee.com/recn-mcu-ae/20260507rasectiongccllvmiarac6
如果需要进一步了解瑞萨 RA 系列 MCU 的更多配置细节,建议同步查阅 FSP 官方文档和对应的链接器手册。