MDK的编译过程及文件类型全解

前言:

为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。


本文转载自:第48章 MDK的编译过程及文件类型全解—零死角玩转STM32-F429系列


1. sct分散加载文件的格式与应用

1.1 sct分散加载文件简介

当工程按默认配置构建时,MDK 会根据我们选择的芯片型号,获知芯片的内部 FLASH 及内部 SRAM 存储器概况,生成一个以工程名命名的后缀为 .sct 的分散加载文件 (Linker Control File,scatter loading),链接器根据该文件的配置分配各个节区地址,生成分散加载代码,因此我们通过修改该文件可以定制具体节区的存储位置。

例如:

(1)可以设置源文件中定义的所有变量自动按地址分配到外部 SDRAM,这样就不需要再使用关键字 “__ attribut e__” 按具体地址来指定了;

(2)利用它还可以控制代码的加载区与执行区的位置,例如可以把程序代码存储到单位容量价格便宜的 NAND-FLASH 中,但在 NAND-FLASH 中的代码是不能像内部 FLASH 的代码那样直接提供给内核运行的,这时可通过修改分散加载文件,把代码加载区设定为 NAND-FLASH 的程序位置,而程序的执行区设定为 SDRAM 中的位置,这样链接器就会生成一个配套的分散加载代码,该代码会把 NAND-FLASH 中的代码加载到 SDRAM 中,内核再从 SDRAM 中运行主体代码,大部分运行 Linux 系统的代码都是这样加载的。

1.2 分散加载文件的格式

************************************************************ ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x08000000 0x00100000 { ; 注释:加载域,基地址空间大小 ER_IROM1 0x08000000 0x00100000 { ; 注释:加载地址 = 执行地址 *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00030000 { ; 注释:可读写数据 .ANY (+RW +ZI) } }

在默认的 sct 文件配置中仅分配了 Code、RO-data、RW-data 及 ZI-data 这些大区域的地址,链接时各个节区(函数、变量等)直接根据属性排列到具体的地址空间。

sct 文件中主要包含描述加载域及执行域的部分,一个文件中可包含有多个加载域,而一个加载域可由多个部分的执行域组成。同等级的域之间使用花括号 “{}” 分隔开,最外层的是加载域,第二层 “{}” 内的是执行域,其整体结构如下图:

1.2.1 加载域

//方括号中的为选填内容 加载域名 (基地址 | ("+"地址偏移)) [属性列表] [最大容量] "{" 执行区域描述+ "}"

配合前面分散加载文件内容,各部分介绍如下:

(1) 加载域名: 名称,在 map 文件中的描述会使用该名称来标识空间。如本例中只有一个加载域,该域名为 LR_IROM1。

(2) 基地址+地址偏移: 这部分说明了本加载域的基地址,可以使用+号连接一个地址偏移,算进基地址中,整个加载域以它们的结果为基地址。如本例中的加载域基地址为 0x08000000,刚好是 STM32 内部 FLASH 的基地址。

(3) 属性列表: 属性列表说明了加载域的是否为绝对地址、N 字节对齐等属性,该配置是可选的。本例中没有描述加载域的属性。

(4) 最大容量: 最大容量说明了这个加载域可使用的最大空间,该配置也是可选的,如果加上这个配置后,当链接器发现工程要分配到该区域的空间比容量还大,它会在工程构建过程给出提示。本例中的加载域最大容量为 0x00100000,即 1MB,正是本型号 STM32 内部 FLASH 的空间大小。

1.2.2 执行域

//方括号中的为选填内容 执行域名 (基地址 | "+"地址偏移) [属性列表] [最大容量 ] "{" 输入节区描述 "}"

执行域的格式与加载域是类似的,区别只是输入节区的描述有所不同,前面的例子中包含了 ER_IROM1 及 RW_IRAM 两个执行域,它们分别对应描述了 STM32 的内部 FLASH 及内部 SRAM 的基地址及空间大小。而它们内部的"输入节区描述"说明了哪些节区要存储到这些空间,链接器会根据它来处理编排这些节区。

1.2.3 输入节区描述

配合加载域及执行域的配置,在相应的域配置"输入节区描述",即可控制该节区存储到域中,其格式如下:

//除模块选择样式部分外,其余部分都可选选填 模块选择样式"("输入节区样式",""+"输入节区属性")" 模块选择样式"("输入节区样式",""+"节区特性")" 模块选择样式"("输入符号样式",""+"节区特性")" 模块选择样式"("输入符号样式",""+"输入节区属性")"

配合前面举例的分散加载文件内容,各部分介绍如下:

(1) 模块选择样式: 模块选择样式可用于选择 .o 及 .lib 目标文件作为输入节区,它可以直接使用目标文件名或 * 通配符,也可以使用 “.ANY”。

例如:使用语句 “bsp_led.o” 可以选择 bsp_led.o 文件,使用语句 “.o" 可以选择所有 .o 文件,使用 ".lib” 可以选择所有 .lib 文件,使用 * 或 “.ANY” 可以选择所有的 .o 文件及 .lib 文件。其中 “.ANY” 选择语句的优先级是最低的,所有其它选择语句选择完剩下的数据才会被 “.ANY” 语句选中。

(2) 输入节区样式: 我们知道在目标文件中会包含多个节区或符号,通过输入节区样式可以选择要控制的节区。

示例文件中 “(RESET,+First)” 语句的 RESET 就是输入节区样式,它选择了名为 RESET 的节区,并使用后面介绍的节区特性控制字 “+First” 表示它要存储到本区域的第一个地址。示例文件中的 “*(InRoot$$Sections)” 是一个链接器支持的特殊选择符号,它可以选择所有标准库里要求存储到 root 区域的节区,如 __main.o、__scatter.o 等内容。

(3) 输入符号样式: 同样地,使用输入符号样式可以选择要控制的符号,符号样式需要使用 “:gdef:” 来修饰。例如:可以使用 “*(:gdef:Value_Test)” 来控制选择符号 “Value_Test”。

(4) 输入节区属性: 通过在模块选择样式后面加入输入节区属性,可以选择样式中不同的内容,每个节区属性描述符前要写一个 “+” 号,使用空格或 “,” 号分隔开,可以使用的节区属性描述符见下表:

节区属性描述符 说明
RO-CODE & CODE 只读代码段
RO-DATA & CONST 只读数据段
RO & TEXT 包括 RO-CODE & RO-DATA
RW-DATA 可读写数据段
RW-CODE 可读写代码段
RW & DATA 包括RW-DATA & RW-CODE
ZI & BSS 初始化为 0 的可读写数据段
XO 只可执行的区域
ENTRY 节区入口点

eg:

示例文件中使用 “.ANY(+RO)” 选择剩余所有节区 RO 属性的内容都分配到执行域 ER_IROM1 中,使用 “.ANY(+RW +ZI)” 选择剩余所有节区 RW 及 ZI 属性的内容都分配到执行域 RW_IRAM1 中。

(5)节区特性: 节区特性可以使用 “+FIRST” 或 “+LAST” 选项配置它要存储到的位置,FIRST 存储到区域的头部,LAST 存储到尾部。通常重要的节区会放在头部,而 CheckSum (校验和)之类的数据会放在尾部。

示例文件中使用 “(RESET,+First)” 选择了 RESET 节区,并要求把它放置到本区域第一个位置,而 RESET 是工程启动代码中定义的向量表,该向量表中定义的堆栈顶和复位向量指针必须要存储在内部 FLASH 的前两个地址,这样 STM32 才能正常启动,所以必须使用 FIRST 控制它们存储到首地址。

; 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 DCD NMI_Handler ; NMI Handler

总的来说,我们的 sct 示例文件配置如下:

程序的加载域为内部 FLASH 的 0x08000000,最大空间为 0x00100000;程序的执行基地址与加载基地址相同,其中 RESET 节区定义的向量表要存储在内部 FLASH 的首地址,且所有 .o 文件及 .lib 文件的 RO 属性内容都存储在内部 FLASH 中;程序执行时 RW 及 ZI 区域都存储在以 0x20000000 为基地址,大小为 0x00030000 的空间(192KB),这部分正好是 STM32 内部主 SRAM 的大小。

链接器根据 sct 文件链接,链接后各个节区、符号的具体地址信息可以在 map 文件中查看。

2. 通过MDK配置选项来修改sct文件

了解 sct 文件的格式后,可以手动编辑该文件控制整个工程的分散加载配置,但sct 文件格式比较复杂,所以 MDK 提供了相应的配置选项可以方便地修改该文件,这些选项配置能满足基本的使用需求,以下将对这些选项进行说明。

2.1 选择sct文件的产生方式

首先需要选择 sct 文件产生的方式,选择使用 MDK 生成还是使用用户自定义的 sct 文件。在 MDK 的 “Options for Target->Linker->Use Memory Layout from Target Dialog” 选项即可配置该选择,见下图:

该选项的译文为 “是否使用 Target 对话框中的存储器分布配置”,勾选后,它会根据 “Options for Target” 对话框中的选项生成 sct 文件,这种情况下,即使我们手动打开它生成的 sct 文件编辑也是无效的,因为每次构建工程的时候,MDK 都会生成新的 sct 文件覆盖旧文件。该选项在 MDK 中是默认勾选的,若希望 MDK 使用我们手动编辑的 sct 文件构建工程,需要取消勾选,并通过 Scatter File 框中指定 sct 文件的路径,见下图:

2.2 通过Target对话框控制存储器分配

我们在 Linker 中勾选了 “使用 Target 对话框的存储器布局” 选项,那么 “Options for Target” 对话框中的存储器配置就生效了。主要配置是在 Device 标签页中选择芯片的类型,设定芯片基本的内部存储器信息以及在 Target 标签页中细化具体的存储器配置(包括外部存储器),见以下图片:

上图中 Device 标签页中选定了芯片的型号为 STM32F429IGTx,选中后,在 Target 标签页中的存储器信息会根据芯片更新。

在 Target 标签页中存储器信息分成只读存储器 (Read/Only Memory Areas) 和可读写存储器 (Read/Write Memory Areas) 两类,即 ROM 和 RAM,而且它们又细分成了片外存储器 (off-chip) 和片内存储器 (on-chip) 两类。

例如:

由于我们已经选定了芯片的型号,MDK 会自动根据芯片型号填充片内的 ROM 及 RAM 信息,其中的 IROM1 起始地址为 0x80000000,大小为 0x100000,正是该 STM32 型号的内部 FLASH 地址及大小;而 IRAM1 起始地址为 0x20000000,大小为 0x30000,正是该 STM32 内部主 SRAM 的地址及大小。图中的 IROM1 及 IRAM1 前面都打上了勾,表示这个配置信息会被采用,若取消勾选,则该存储配置信息是不会被使用的。

在标签页中的 IRAM2 一栏默认也填写了配置信息,它的地址为 0x10000000,大小为 0x10000,这是 STM32F4 系列特有的内部 64KB 高速 SRAM(被称为CCM)。当我们希望使用这部分存储空间的时候需要勾选该配置,另外要注意这部分高速 SRAM 仅支持 CPU 总线的访问,不能通过外设访问。

下面我们尝试修改 Target 标签页中的这些存储信息,例如:把 IRAM1 的基地址改为 0x20001000,然后编译工程,查看到工程的 sct 文件,和同时使用 IRAM1 和 IRAM2,然后编译工程,做一个比较。

//修改了IRAM1基地址后的sct文件内容 LR_IROM1 0x08000000 0x00100000 { ; load region size_region ER_IROM1 0x08000000 0x00100000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20001000 0x00030000 { ; RW data .ANY (+RW +ZI) } }//使用IRAM2时的sct文件内容 LR_IROM1 0x08000000 0x00100000 { ; load region size_region ER_IROM1 0x08000000 0x00100000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00030000 { ; RW data .ANY (+RW +ZI) } RW_IRAM2 0x10000000 0x00010000 { .ANY (+RW +ZI) } }

可以发现,sct 文件都根据 Target 标签页做出了相应的改变,除了这种修改外,在 Target 标签页上还控制同时使用 IRAM1 和 IRAM2、加入外部 RAM (如外接的 SDRAM),外部 FLASH 等。

2.3 控制文件分配到指定的存储空间

设定好存储器的信息后,可以控制各个源文件定制到哪个部分存储器,在 MDK 的工程文件栏中,选中要配置的文件,右键,并在弹出的菜单中选择 “Options for File xxxx” 即可弹出一个文件配置对话框,在该对话框中进行存储器定制,见下图:

在弹出的对话框中有一个 “Memory Assignment” 区域(存储器分配),在该区域中可以针对文件的各种属性内容进行分配,如 Code/Const 内容 (RO)、Zero Initialized Data 内容 (ZI-data) 以及 Other Data 内容 (RW-data),点击下拉菜单可以找到在前面 Target 页面配置的 IROM1、IRAM1、IRAM2 等存储器。

例如:图中我们把这个 bsp_led.c 文件的 Other Data 属性的内容分配到了 IRAM2 存储器(在 Target 标签页中我们勾选了 IRAM1 及 IRAM2),当在 bsp_led.c 文件定义了一些 RW-data 内容时(如初值非 0 的全局变量),该变量将会被分配到 IRAM2 空间,配置完成后点击 OK,然后编译工程,查看到的 sct 文件内容如下:

//修改bsp_led.c配置后的sct文件 LR_IROM1 0x08000000 0x00100000 { ; load region size_region ER_IROM1 0x08000000 0x00100000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00030000 { ; RW data .ANY (+RW +ZI) } RW_IRAM2 0x10000000 0x00010000 { bsp_led.o (+RW) .ANY (+RW +ZI) } }

可以看到在 sct 文件中的 RW_IRAM2 执行域中增加了一个选择 bsp_led.o 中 RW 内容的语句。

类似地,我们还可以设置某些文件的代码段被存储到特定的 ROM 中,或者设置某些文件使用的 ZI-data 或 RW-data 存储到外部 SDRAM 中(控制 ZI-data 到 SDRAM 时注意还需要修改启动文件设置堆栈对应的地址,原启动文件中的地址是指向内部 SRAM 的)。

虽然 MDK 的这些存储器配置选项很方便,但有很多高级的配置还是需要手动编写 sct 文件实现的,例如:MDK 选项中的内部 ROM 选项最多只可以填充两个选项位置,若想把内部 ROM 分成多片地址管理就无法实现了;另外 MDK 配置可控的最小粒度为文件,若想控制特定的节区也需要直接编辑 sct 文件。

(0)

相关推荐