前言:
為了友善檢視部落格,特意申請了一個公衆号,附上二維碼,有興趣的朋友可以關注,和我一起讨論學習,一起享受技術,一起成長。

本文轉載自:第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 檔案。