蒙蒙plus
蒙蒙plus
Published on 2025-12-24 / 19 Visits
0
0

MDK Scatter文件配置说明

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开头的文件(优先级更高)
}

匹配顺序

  1. 完全匹配的文件名(如ui_menu.o
  2. 具体的通配符模式(如ui_font*.o
  3. 宽泛的通配符模式(如ui_*.o
  4. .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. 语法规则总结

✅ 必须遵守的规则

  1. 命名规则

    • 加载区域必须以LR_开头
    • 执行区域必须以ER_开头
    • RAM区域通常以RW_开头
  2. 地址格式

    • 必须使用十六进制(0x前缀)
    • 地址必须是4字节对齐
  3. 匹配顺序

    • 按出现顺序匹配
    • 更具体的规则优先
  4. 区域嵌套

    • 执行区域必须在加载区域内
    • 使用大括号{}定义作用域

⚠️ 常见错误

  1. 地址冲突

    ER_IROM1 0x08010000 0x00030000 { ... }    ; 占用到0x08040000
    ER_IROM2 0x08030000 0x00040000 { ... }    ; ❌ 错误:地址重叠
    
  2. 大小超限

    ER_IROM1 0x08010000 0x00030000 { ... }    ; 限制192KB
    ; 如果实际代码超过192KB,会溢出或报错
    
  3. 匹配冲突

    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):

  1. ER_IROM2 放置其他UI模块(ui_*.o
  2. ER_IROM3 放置字体模块(ui_font*.o
  3. 由于ER_IROM3使用+0,会紧跟在ER_IROM2后面
  4. 因此字体数据会放在最后

📝 配置要点总结

✅ 优点

  1. 性能优化:关键代码放在零等待区,执行速度快
  2. 空间优化:大数据模块放在非零等待区,节省零等待区空间
  3. 灵活分配:通过模块优先级控制,自动分配代码和数据
  4. 字体隔离:字体数据单独放在最后,便于管理

⚠️ 注意事项

  1. 大小限制:ER_IROM1严格限制192KB,超过会溢出
  2. 地址连续性:使用+0确保区域连续放置
  3. 匹配顺序:更具体的规则会优先匹配
  4. 溢出处理:如果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: 初始版本,配置零等待区和非零等待区分离

Comment