MDK Scatter文件配置说明
📋 概述
本文档说明嵌入式项目中的MDK链接脚本(Scatter文件)配置,用于优化Flash内存布局,将代码和数据分别放置在零等待区和非零等待区,以提高系统性能。
🎯 优化目标
- 零等待区(192KB):存放关键代码,执行速度快
- 非零等待区(256KB):存放大数据模块(字体、图片等),访问频率低
- Bootloader区域(64KB):保留给Bootloader使用,不可变更
📊 Flash内存布局
┌─────────────────────────────────────────────────────────┐
│ Flash总容量: 512KB │
├─────────────────────────────────────────────────────────┤
│ 0x08000000 - 0x0800FFFF │ Bootloader区域 │ 64KB │
│ │ (不可变更) │ │
├─────────────────────────────────────────────────────────┤
│ 0x08010000 - 0x0803FFFF │ 零等待区 │ 192KB │
│ │ (代码区) │ │
├─────────────────────────────────────────────────────────┤
│ 0x08040000 - 0x0807FFFF │ 非零等待区 │ 256KB │
│ │ (数据区) │ │
└─────────────────────────────────────────────────────────┘
内存区域说明
| 区域 | 起始地址 | 结束地址 | 大小 | 用途 | 特点 |
|---|---|---|---|---|---|
| Bootloader | 0x08000000 | 0x0800FFFF | 64KB | Bootloader程序 | 不可变更,保留 |
| 零等待区 | 0x08010000 | 0x0803FFFF | 192KB | 关键代码 | 执行速度快 |
| 非零等待区 | 0x08040000 | 0x0807FFFF | 256KB | 大数据模块 | 访问频率低 |
🔧 链接脚本配置
加载区域(Load Region)
LR_IROM1 0x08010000 0x00070000 { ; 加载区域:从0x08010000开始,共448KB
- 起始地址:
0x08010000(跳过Bootloader区域) - 总大小:
0x00070000(448KB = 192KB + 256KB)
执行区域配置
1. ER_IROM1 - 零等待区(代码区)
ER_IROM1 0x08010000 0x00030000 { ; 零等待区:192KB (0x30000)
*.o (RESET, +First) ; 复位向量表(必须放在最前面)
*(InRoot$$Sections) ; 根段(初始化代码等)
; 关键应用代码 - 优先放在零等待区
lv_rt_thread_port.o (+RO) ; LVGL RT-Thread移植
ui_generic_main.o (+RO) ; UI通用主模块
; 其他关键代码(排除大数据模块)
.ANY (+RO) ; 其他模块(代码优先,数据次之)
}
特点:
- 执行地址:
0x08010000 - 0x0803FFFF(192KB) - 严格限制大小,超过会自动溢出到ER_IROM2
- 优先放置关键代码和频繁执行的代码
2. ER_IROM2 - 非零等待区(数据区-除字体外)
ER_IROM2 +0 0x00040000 { ; 执行地址:紧跟在ER_IROM1后,最大256KB
; UI相关数据(不包括字体,因为字体在ER_IROM3)
ui_*.o (+RO) ; UI相关数据
}
特点:
- 执行地址:紧跟在ER_IROM1后面(使用
+0) - 最大大小:256KB
- 放置除字体外的UI模块
3. ER_IROM3 - 非零等待区(字体数据)
ER_IROM3 +0 0x00040000 { ; 执行地址:紧跟在ER_IROM2后,最大256KB
; 字体数据放在最后(确保在ER_IROM2的末尾之后)
ui_font*.o (+RO) ; UI字体数据(放在最后)
}
特点:
- 执行地址:紧跟在ER_IROM2后面(使用
+0) - 最大大小:256KB
- 字体数据放在最后,确保在ER_IROM2的末尾之后
4. RW_IRAM1 - RAM区域
RW_IRAM1 0x20000000 0x00018000 { ; RAM:96KB (0x18000)
.ANY (+RW +ZI) ; 所有读写数据和零初始化数据
}
特点:
- 起始地址:
0x20000000 - 大小:96KB
- 存放所有读写数据和零初始化数据
📖 MDK Scatter文件语法规范
基本语法结构
MDK Scatter文件使用分号(;)作为注释符,基本结构如下:
; 注释:以分号开头
LR_name load_address max_size { ; 加载区域定义
ER_name exec_address max_size { ; 执行区域定义
module_pattern (attributes) ; 模块匹配规则
}
}
1. 加载区域(Load Region)语法
LR_name load_address max_size {
; 执行区域列表
}
参数说明:
LR_name: 加载区域名称(必须以LR_开头)load_address: 加载地址(十六进制,如0x08010000)max_size: 最大大小(十六进制,如0x00070000)
示例:
LR_IROM1 0x08010000 0x00070000 { ; 加载区域:从0x08010000开始,共448KB
; 执行区域...
}
2. 执行区域(Execution Region)语法
ER_name exec_address max_size {
; 模块匹配规则
}
参数说明:
ER_name: 执行区域名称(必须以ER_开头)exec_address: 执行地址,可以是:- 固定地址:
0x08040000(绝对地址) - 相对地址:
+0(紧跟在前面区域后面) - 对齐地址:
ALIGN 4(4字节对齐)
- 固定地址:
max_size: 最大大小(可选,十六进制)
示例:
ER_IROM1 0x08010000 0x00030000 { ; 固定地址,192KB
; 模块...
}
ER_IROM2 +0 0x00040000 { ; 相对地址,紧跟在ER_IROM1后,256KB
; 模块...
}
3. 地址表示方法
| 表示方法 | 语法 | 说明 | 示例 |
|---|---|---|---|
| 固定地址 | 0xXXXXXXXX |
绝对地址 | 0x08010000 |
| 相对地址 | +offset |
相对前一个区域 | +0(紧跟在后面) |
| 对齐地址 | ALIGN n |
n字节对齐 | ALIGN 4(4字节对齐) |
| 空地址 | EMPTY |
空区域 | EMPTY 0x1000(空1KB) |
地址计算示例:
ER_IROM1 0x08010000 0x00030000 { ; 从0x08010000开始,192KB
; 实际结束地址:0x08010000 + 0x00030000 = 0x08040000
}
ER_IROM2 +0 0x00040000 { ; 从0x08040000开始(+0表示紧跟在ER_IROM1后)
; 实际结束地址:0x08040000 + 0x00040000 = 0x08080000
}
4. 文件匹配规则
4.1 通配符匹配
| 通配符 | 说明 | 示例 | 匹配结果 |
|---|---|---|---|
* |
匹配任意字符 | *.o |
所有.o文件 |
? |
匹配单个字符 | ui_?.o |
ui_a.o, ui_b.o等 |
** |
递归匹配 | **/ui_*.o |
所有目录下的ui_*.o |
4.2 文件路径匹配
; 完整路径匹配
applications/lvgl/screens/ui_menu.o (+RO)
; 相对路径匹配(从项目根目录)
ui_*.o (+RO) ; 匹配所有ui_开头的.o文件
; 目录匹配
packages/LVGL-v8.3.11/src/font/*.o (+RO) ; 匹配指定目录下的所有.o文件
4.3 匹配优先级
规则:更具体的匹配规则优先于宽泛的规则
ER_IROM2 {
ui_*.o (+RO) ; 宽泛规则:匹配所有ui_开头的文件
}
ER_IROM3 {
ui_font*.o (+RO) ; 具体规则:只匹配ui_font开头的文件(优先级更高)
}
匹配顺序:
- 完全匹配的文件名(如
ui_menu.o) - 具体的通配符模式(如
ui_font*.o) - 宽泛的通配符模式(如
ui_*.o) .ANY(匹配所有未匹配的模块)
5. 特殊匹配规则
5.1 复位向量表
*.o (RESET, +First) ; 复位向量表,必须放在最前面
RESET: 复位向量表段+First: 强制放在区域的最前面
5.2 根段
*(InRoot$$Sections) ; 根段(初始化代码等)
InRoot$$Sections: 系统根段,包含初始化代码
5.3 任意匹配
.ANY (+RO) ; 匹配所有未匹配的模块
.ANY: 通配符,匹配所有未被其他规则匹配的模块- 通常放在最后,作为"兜底"规则
6. 属性说明
6.1 只读属性
| 属性 | 说明 | 包含内容 |
|---|---|---|
+RO |
只读(Read-Only) | 代码(.text)和只读数据(.rodata) |
+RO-CODE |
只读代码 | 仅代码段(.text) |
+RO-DATA |
只读数据 | 仅只读数据段(.rodata) |
注意:MDK中通常使用+RO,它包含代码和数据。
6.2 读写属性
| 属性 | 说明 | 包含内容 |
|---|---|---|
+RW |
读写(Read-Write) | 已初始化的读写数据(.data) |
+ZI |
零初始化(Zero-Initialized) | 未初始化的数据(.bss) |
+RW +ZI |
读写和零初始化 | 所有读写数据 |
6.3 特殊属性
| 属性 | 说明 | 示例 |
|---|---|---|
+First |
放在区域最前面 | *.o (RESET, +First) |
+Last |
放在区域最后面 | font_data.o (+RO, +Last) |
UNINIT |
未初始化数据 | backup.o (+RW, UNINIT) |
7. 注释语法
; 单行注释:以分号开头
; 这是注释内容
LR_IROM1 0x08010000 0x00070000 { ; 行尾注释
ER_IROM1 0x08010000 0x00030000 {
*.o (RESET, +First) ; 复位向量表
}
}
注意:
- 注释以分号(
;)开头 - 可以单独一行,也可以跟在代码后面
- 不支持多行注释(
/* */)
8. 完整语法示例
; ============================================================
; 加载区域定义
; ============================================================
LR_IROM1 0x08010000 0x00070000 { ; 加载区域:从0x08010000开始,共448KB
; ============================================================
; 执行区域1:零等待区(代码区)
; ============================================================
ER_IROM1 0x08010000 0x00030000 { ; 执行地址:0x08010000,大小:192KB
*.o (RESET, +First) ; 复位向量表(必须最前)
*(InRoot$$Sections) ; 根段(初始化代码)
; 明确指定的模块
lv_rt_thread_port.o (+RO) ; LVGL RT-Thread移植
ui_generic_main.o (+RO) ; UI通用主模块
; 通配符匹配
rt-thread\*.o (+RO) ; RT-Thread内核
; 任意匹配(放在最后)
.ANY (+RO) ; 其他所有模块
}
; ============================================================
; 执行区域2:非零等待区(数据区)
; ============================================================
ER_IROM2 +0 0x00040000 { ; 执行地址:紧跟在ER_IROM1后,大小:256KB
ui_*.o (+RO) ; UI相关数据
}
; ============================================================
; 执行区域3:字体数据(放在最后)
; ============================================================
ER_IROM3 +0 0x00040000 { ; 执行地址:紧跟在ER_IROM2后,大小:256KB
ui_font*.o (+RO) ; UI字体数据(更具体的规则,优先级更高)
}
; ============================================================
; RAM区域
; ============================================================
RW_IRAM1 0x20000000 0x00018000 { ; RAM:从0x20000000开始,96KB
.ANY (+RW +ZI) ; 所有读写数据和零初始化数据
}
}
9. 语法规则总结
✅ 必须遵守的规则
-
命名规则:
- 加载区域必须以
LR_开头 - 执行区域必须以
ER_开头 - RAM区域通常以
RW_开头
- 加载区域必须以
-
地址格式:
- 必须使用十六进制(
0x前缀) - 地址必须是4字节对齐
- 必须使用十六进制(
-
匹配顺序:
- 按出现顺序匹配
- 更具体的规则优先
-
区域嵌套:
- 执行区域必须在加载区域内
- 使用大括号
{}定义作用域
⚠️ 常见错误
-
地址冲突:
ER_IROM1 0x08010000 0x00030000 { ... } ; 占用到0x08040000 ER_IROM2 0x08030000 0x00040000 { ... } ; ❌ 错误:地址重叠 -
大小超限:
ER_IROM1 0x08010000 0x00030000 { ... } ; 限制192KB ; 如果实际代码超过192KB,会溢出或报错 -
匹配冲突:
ER_IROM2 { ui_*.o (+RO) ; 会匹配ui_font*.o } ER_IROM3 { ui_font*.o (+RO) ; ✅ 正确:更具体的规则优先 }
10. 调试技巧
查看Map文件
编译后查看rtthread.map文件,可以验证:
- 模块的实际放置地址
- 区域的实际大小
- 是否有地址冲突
常用Map文件搜索
# 搜索特定模块的地址
grep "ui_font" rtthread.map
# 搜索执行区域的边界
grep "ER_IROM" rtthread.map
# 查看区域大小
grep "Execution Region" rtthread.map
🔍 关键配置技巧
1. 执行地址使用 +0 的含义
+0表示执行地址紧跟在前面一个执行区域后面- 这样可以确保区域按顺序连续放置
- 例如:
ER_IROM2 +0会紧跟在ER_IROM1后面
2. 模块匹配优先级
在MDK scatter文件中,更具体的匹配规则会优先于宽泛的规则:
ui_*.o (+RO) ; 宽泛规则:匹配所有ui_开头的文件
ui_font*.o (+RO) ; 具体规则:只匹配ui_font开头的文件(优先级更高)
因此:
ui_font*.o会被匹配到ER_IROM3(更具体)- 其他
ui_*.o会被匹配到ER_IROM2(宽泛规则)
3. 确保字体在最后的策略
通过创建两个执行区域(ER_IROM2和ER_IROM3):
- ER_IROM2 放置其他UI模块(
ui_*.o) - ER_IROM3 放置字体模块(
ui_font*.o) - 由于ER_IROM3使用
+0,会紧跟在ER_IROM2后面 - 因此字体数据会放在最后
📝 配置要点总结
✅ 优点
- 性能优化:关键代码放在零等待区,执行速度快
- 空间优化:大数据模块放在非零等待区,节省零等待区空间
- 灵活分配:通过模块优先级控制,自动分配代码和数据
- 字体隔离:字体数据单独放在最后,便于管理
⚠️ 注意事项
- 大小限制:ER_IROM1严格限制192KB,超过会溢出
- 地址连续性:使用
+0确保区域连续放置 - 匹配顺序:更具体的规则会优先匹配
- 溢出处理:如果ER_IROM1放不下,会自动溢出到ER_IROM2
🛠️ 常见问题
Q1: 如果代码超过192KB怎么办?
A: 链接器会自动将超出的部分溢出到ER_IROM2。如果ER_IROM2也放不下,会报错,需要:
- 优化代码大小
- 将更多模块明确放到ER_IROM2
- 或者调整ER_IROM1的大小限制
Q2: 如何确保某个模块在特定区域?
A: 在对应的执行区域中明确指定该模块,例如:
ER_IROM1 {
specific_module.o (+RO) ; 明确指定模块
}
Q3: 字体为什么要在最后?
A: 字体数据通常很大,放在最后可以:
- 便于管理
- 如果空间不足,可以更容易调整
- 避免影响其他模块的布局
📚 参考资源
📅 更新记录
- 2025-12-24: 初始版本,配置零等待区和非零等待区分离