Keil MDK(ARM编译器)分散加载特性(下):使用分散加载文件(.sct)控制

引子

在上篇文章中我们使用Keil MDK提供的GUI控制分散加载,实际上,工程会根据GUI中的这些设置生成一个分散加载文件(.sct),而这个文件才直接控制链接器分散加载,有时GUI提供的设置并不能满足我们的要求,就需要直接编写这个分散加载文件。

查看根据GUI设置生成的分散加载文件(.sct)

若使用GUI控制分散加载(Project – Options for Target ‘xxx’ > Linker 中已勾选Use Memory Layout From Target Dialog),会在Objects文件夹(默认情况,或在Project – Options for Target ‘xxx’ > Output中Select Folder for Objects中设置的其他文件夹)生成一个工程名.sct文件,这个文件就是根据GUI中的设置生成的,并真正用于控制链接器的分散加载。

同时,我们如果想使用分散加载文件实现某个GUI存在的功能,但是不清楚怎么写时,可以参考这个由Keil MDK生成的分散加载文件。

认识分散加载文件(.sct)

这里以Keil MDK生成的默认分散加载文件为例,解读其意义。

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
;LR即加载域,这个加载域的名字是LR_IROM1,随后为基地址(0x08000000)和最大大小(0x00020000)
LR_IROM1 0x08000000 0x00020000  {    ; load region size_region
;ER即运行域,这个运行域的名字是ER_IROM1,随后为基地址(0x08000000)和最大大小(0x00020000)
  ER_IROM1 0x08000000 0x00020000  {  ; load address = execution address
;将RESET放在最前面,也就是将中断向量表放在最前面
   *.o (RESET, +First)
;将 MDK 的一些库文件全部放在根域
   *(InRoot$$Sections)
;所有未指定位置的RO数据和XO数据放在此运行域中
;模块名称为.ANY,代表没有指定位置的节,输入节属性为(+RO),代表RO数据
   .ANY (+RO)
   .ANY (+XO)
  }
;这个运行域的名字是RW_IRAM1,随后为基地址(0x20000000)和最大大小(0x00020000)
  RW_IRAM1 0x20000000 0x00020000  {  ; RW data
;所有未指定位置的RW数据和ZI数据放在此运行域中
   .ANY (+RW +ZI)
  }
}

分散文件包含一/多个加载域,每个加载区域包含一/多个运行域。每个加载域/运行域范围使用大括号标识。注释使用分号;标识。

一个加载域包含以下部分:

  • 名称
  • 基地址
  • 属性(可选属性见Load region attributes
  • 最大大小(可选)
  • 一/多个运行域

一个运行域包含以下部分:

  • 名称
  • 基地址(绝对/相对)
  • 属性(可选属性见Execution region attributes
  • 最大大小(可选)
  • 一/多个输入节(input section)描述

一个输入节描述占一行,包含以下部分:

  • 模块名称(object文件(.o)、库文件(.lib)成员名或文件名,可以使用通配符)
  • 输入节名称或属性(可选属性见Syntax of an input section description,输入节名称可以使用通配符),可选的属性有RO、XO、RW、ZI等,代表这个模块中的RO、XO、RW、ZI数据
  • 符号名称

一个加载域通常至少包含一个ROM和0或多个RAM。因为加载域要保证掉电后也不会丢失,所以加载域必须包含一个ROM并且基地址和最大大小与这块ROM相同。通常不同ROM之间的地址并不连续,因此每个ROM都需要在不同的加载域中,每个加载域中也只有一个ROM。

在工程中使用自定义的分散加载文件(.sct)

在Project – Options for Target ‘xxx’ > Linker 中取消勾选Use Memory Layout From Target Dialog,并在Scatter File中设置自定义的分散加载文件路径即可。

同一加载域下使用多个RAM(=目标(Target)面板中使用额外的RAM)

有时我们需要使用多块内存或同块内存分割,就需要在同一加载域下作为运行域存在。例如STM32H7提供了DTCM(128KiB)和AXI-SRAM(512iKB)内存,分散加载文件编写如下(但此时没有任何数据在AXI-SRAM中):

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   .ANY (+RW +ZI)
  }
  ; AXI SRAM 512KB
  RW_IRAM_AXI_SRAM 0x24000000 0x00080000  {

  }
}

使用.ANY模块名称(=目标(Target)面板中勾选default)

对于没有指定位置的部分可以使用.ANY模块名称表示,并且它可以出现在多个执行域中。例如,数据没有办法在一个RAM中装下,需要装在额外不连续的RAM中,此时就可以在RAM加载域中使用多个.ANY模块名称。例如,让未指定地址的RW和ZI数据同时可以存在于DTCM和AXI-SRAM中:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   .ANY (+RW +ZI)
  }
  ; AXI SRAM 512KB
  RW_IRAM_AXI_SRAM 0x24000000 0x00080000  {
    .ANY (+RW +ZI)
  }
}

在有多.ANY时,链接器会优先选择放置在最大的运行域,若放不下才会将放不下的部分放到更小的运行域中。当然我们也可以通过指定.ANY的优先级的方式指定放置的顺序,具体方法请看下节。

.ANY的优先级

在有多.ANY时,链接器会优先选择放置在最大的运行域,我们也可以通过指定.ANY的优先级的方式指定放置的顺序。具体方法为在.ANY后加上一个数字,这个数字就代表了这个.ANY的优先级,优先级是从零开始的正整数,数字越大优先级就越高。例如,我们想让未指定地址的RW和ZI数据优先放在DTCM中,因此我们将DTCM的.ANY优先级设为2,SRAM的.ANY优先级设为1。

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   .ANY2 (+RW +ZI)
  }
  ; AXI SRAM 512KB
  RW_IRAM_AXI_SRAM 0x24000000 0x00080000  {
    .ANY1 (+RW +ZI)
  }
}

加载域中使用UNINIT属性(=目标(Target)面板中勾选No Init)

假如不想让某个段内存中的ZI数据不被初始化,可以使用UNINIT属性(并不会影响RW数据的初始化)。但是有时这个选项并不能完全生效,有时ZI数据仍然会被初始化,具体请见ARM: Uninialized Variables Get Initialized。使用方法为在加载域基地址后加上UNINIT属性。

例如,我们想让DTCM不进行ZI数据的初始化:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 UNINIT 0x00020000  {
   .ANY (+RW +ZI)
  }
}

指定某个文件中的数据的位置(=文件选项(Options for File面板中的存储分配(Memory Assginment)

有时我们想将某个文件中的数据放置在特定的位置中,可以显式指定它的位置。使用方法为输入节描述的模块名称使用文件名.o(对象文件)并加上属性(例如RO、XO、ZI、RW等)

例如,我们想将fast.c文件中的RO数据加载到ITCM中,将fast.c的RW和ZI数据加载到DTCM中运行,并将其他RW和ZI数据加载到AXI-SRAM中,其他RO、XO数据的运行域为ROM:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; ITCM 128KB
  RW_IRAM_ITCM 0x00000000 0x00020000  {
   fast.o (+RO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   fast.o (+RW +ZI)
  }
  ; AXI SRAM 512KB
  RW_IRAM_AXI_SRAM 0x24000000 0x00080000  {
    .ANY (+RW +ZI)
  }
}

使用多个ROM(多个加载域)(=目标(Target)面板中使用额外的ROM)

由于多个ROM的地址一般并不连续,而加载域的地址空间必须是掉电后不丢失的ROM,因此一般将一个ROM放在一个加载域中,多个ROM就需要多个加载域。这个操作与目标(Target)面板的只读存储区域(Read/Only Memory Areas)中填写额外的ROM相同,填写的基地址和大小将影响生成的加载域和运行域中ROM的基地址和大小。

例如,使用QSPI外接Flash时:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   .ANY (+RW +ZI)
  }
}
LR_ROM_QSPI 0x90000000 0x00800000  {
  ER_ROM_QSPI 0x90000000 0x00800000  {

  }
}

例子:将数据下载到片外 SPI Flash 中

这个问题与Keil MDK(ARM编译器)分散加载特性(上):使用GUI控制中相同,只是使用分散加载文件来实现。在分散加载文件添加一下内容:

LR_ROM_SPI 0xC0000000 0x00800000  {
  ER_ROM_SPI 0xC0000000 0x00800000  {
    gb2312.o (+RO)
  }
}

指定了一个名为LR_ROM_SPI的加载域和名为ER_ROM_SPI的运行域,他们实际都是相同的虚拟的ROM,并将字库(编译后名为gb2312.o)的RO数据(实际只包含常量不包含函数)放在这个运行域中。注意我们使用的虚拟的地址,因此不能使用这个地址直接访问SPI Flash中的数据。

例子:使用片外 QSPI Flash 运行代码

这个问题与Keil MDK(ARM编译器)分散加载特性(上):使用GUI控制中相同,只是使用分散加载文件来实现。在分散加载文件添加一下内容:

LR_ROM_QSPI 0x90000000 0x00800000  {
  ER_ROM_QSPI 0x90000000 0x00800000  {
    extern_function.o (+RO)
  }
}

指定了一个名为LR_ROM_QSPI的加载域和名为ER_ROM_QSPI的运行域,他们实际都是相同的虚拟的ROM,并将字库(编译后名为extern_function.o)的RO数据(包含常量和函数)放在这个运行域中。在调用这部分函数/常量之前必须先初始化QSPI。

输入节描述属性可选项

除了前面提到的RO、XO、ZI、RW,还有一些更细分的属性或者同义词可以使用

属性别名说明
RO-CODECODERO数据中的代码部分
RO-DATACONSTRO数据中的常量部分
ROTEXTRO数据,同时包含RO-CODE和RO-DATA
RW-DATARO数据中的数据部分
RW-CODERO数据中的代码部分
RWDATARW数据,同时包含RW-CODE和RW-DATA
XOXO数据
ZIBSSZI数据
ENTRY包含一个ENTRY点的部分

例如,上一节的例子可以等价地写为:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+TEXT)
    .ANY (+XO)
  }
  ; ITCM 128KB
  RW_IRAM_DTCM 0x00000000 0x00020000  {
   fast.o (+RO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   fast.o (+DATA +BSS)
  }
  ; AXI SRAM 512KB
  RW_IRAM_AXI_SRAM 0x24000000 0x00080000  {
    .ANY (+RW +ZI)
  }
}

输入节描述属性伪属性FIRST、LAST

有时我们必须制定放置的顺序,例如我们必须将中断向量表放在地址开始处(一般默认映射到片内Flash的起始地址),此时我们可以使用FIRST伪属性将某个部分放置到最开始的位置。例如,默认生成的分散加载文件就将RESET放在ROM最开始的位置:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   .ANY (+RW +ZI)
  }
}

而RESET就代表了中断向量表(在启动文件中,RESET这个名字是由AREA后的RESET确定的):

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp                      ; Top of Stack
                DCD     Reset_Handler                     ; Reset Handler

需要注意,一个运行域只能有一个FIRST和LAST伪属性。

修改启动位置(=目标(Target)面板中选择Startup的ROM)

修改启动位置即是调整中断向量表的位置,也是利用输入节描述属性伪属性FIRST,将RESET放置在特定固定位置,具体请见上一节。

在输入节描述的模块选择模式中使用通配符

使用星号*来指代匹配任何内容,例如

模块选择模式描述
*匹配任何模块或库
*.o匹配任何对象模块
*armlib*匹配 ARM 提供的所有 C 库
*cpplib*匹配 ARM 提供的所有 C++ 库
*math.lib匹配任何以 结尾的库路径
math.lib,例如 C:\apps\lib\math\satmath.lib

需要注意,使用*后则不允许在其他地方使用*(同一属性),若要匹配任意模块且允许在不同运行域中出现请使用.ANY。

例如,将ARM提供的C库加载到ITCM中:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; ITCM 128KB
  RW_IRAM_ITCM 0x00000000 0x00020000  {
   *armlib* (+RO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   .ANY (+RW +ZI)
  }
}

使用自定义的名称来指定位置

我们希望在我们的代码中使用一个名称来标识数据,方便在分散加载文件中拥有同一名称数据的位置。为了达到这个目的可以在代码中使用__attribute__((section("name"))),并在分散加载文件中指定该名称的数据放置的位置。例如,我们希望将一个变量一定放置在DTCM中,在源文件中:

int variable __attribute__((section(".DTCM")));

在分散加载文件中:

LR_IROM 0x08000000 0x00020000  {    ; load region size_region
  ; on-chip flash 128KB
  ER_IROM 0x08000000 0x00020000  {  ; load address = execution address
    *.o (RESET, +First)
    *(InRoot$$Sections)
    .ANY (+RO)
    .ANY (+XO)
  }
  ; DTCM 128KB
  RW_IRAM_DTCM 0x20000000 0x00020000  {
   * (.DTCM)
  }
  ; AXI SRAM 512KB
  RW_IRAM_AXI_SRAM 0x24000000 0x00080000  {
    .ANY (+RW +ZI)
  }
}

将数据放置在特定地址

我们也可以直接使用地址直接指定位置,可以使用__attribute__((section(“”.ARM.__at_address”)))或者__attribute__((at(address)))。例如将某个函数放置在0x20000处,在源文件中:

int sqr(int n1) __attribute__((at(0x20000)));
\\ 或者
int sqr(int n1) __attribute__((section(".ARM.__at_0x20000")));
int sqr(int n1)
{
    return n1*n1;
}

将一个变量放置在0x8000处:

int variable __attribute__((at(0x8000)));
\\ 或者
int variable __attribute__((section(".ARM.__at_0x8000")));

关于InRoot$$Sections

某些 ARM C 和 C++ 库部分必须放置在根区域中,例如__main.o__scatter*.o__dc*.o*Region$$Table,这些可以通过InRoot$$Sections标识。因此需要将InRoot$$Sections放在根域中。

更多资料

请参考:

ARM Compiler armlink User Guide Version 5.06 – Scatter-loading Features

ARM Compiler armlink User Guide Version 5.06 – Scatter File Syntax

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇