大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是在MDK開發環境下將關鍵函數重定向到RAM中執行的幾種方法。
這個關鍵函數重定向到 RAM 中執行系列文章,痞子衡已經寫過 《IAR篇》、《MCUXpresso IDE篇》,今天一鼓作氣把 Keil MDK 篇也寫了,做個全家桶。
把 Keil MDK 放到最后來寫,其實痞子衡是有用意的。第一篇寫 IAR,我們基本上是要純手工改鏈接文件。第二篇寫 MCUXpresso IDE,我們除了手工改鏈接文件,也在利用它的鏈接文件配置自動生成功能。現在到了 Keil MDK,這個 IDE 其實跟 MCUXpresso IDE 一樣也支持鏈接文件配置自動生成,但是具體功能設計上有各有千秋,今天我們就來了解下:
- Note: 本文使用的 Keil uVision 軟件版本是 v5.31.0.0。
一、准備工作
為了便於描述后面的函數重定向方法實現,我們先做一些准備工作,選定的硬件平台是恩智浦 MIMXRT1170-EVK,主芯片內部有2MB RAM,外掛了 16MB Flash 和 2 片 32MB SDRAM。這些存儲設備在芯片系統中映射地址空間如下:
NOR Flash: 0x30000000 - 0x30FFFFFF (16MB)
ITCM RAM: 0x00000000 - 0x0003FFFF (256KB)
DTCM RAM: 0x20000000 - 0x2003FFFF (256KB)
OCRAM: 0x20200000 - 0x2037FFFF (1.5MB)
SDRAM: 0x80000000 - 0x83FFFFFF (64MB)
我們隨便選擇一個測試例程:\SDK_2.10.0_EVK-MIMXRT1170\boards\evkmimxrt1170\demo_apps\hello_world\cm7\mdk,其中 flexspi_nor 工程是最典型的代碼鏈接場景(見 MIMXRT1176xxxxx_cm7_flexspi_nor.scf 文件),全部的 readonly 段分配在 0x30000000 - 0x30FFFFFF 空間(在 Flash 中),全部的 readwrite 段分配在 0x20000000 - 0x2003FFFF 空間(在 DTCM 中)。鏈接文件精簡如下:
LR_m_text 0x30002000 0x00FFE000 {
VECTOR_ROM 0x30002000 FIXED 0x00000400 {
* (.isr_vector,+FIRST)
}
ER_m_text 0x30002400 FIXED 0x00FFDC00 {
* (InRoot$$Sections)
.ANY (+RO)
}
RW_m_data 0x20000000 0x0003F800 {
.ANY (+RW +ZI)
}
ARM_LIB_HEAP +0 EMPTY 0x00000400 {
}
ARM_LIB_STACK 0x20040000 EMPTY -0x00000400 {
}
}
現在我們再創建一個新源文件 critical_code.c 用於示例關鍵函數,將這個源文件添加進 hello_world_demo_cm7.uvprojx 工程里,critical_code.c 文件中只有如下三個測試函數(它們在 main 函數里會被調用):
void critical_func1(uint32_t n)
{
PRINTF("Arg = %d .\r\n", n);
}
void critical_func2(uint32_t n)
{
PRINTF("Arg * 2 = %d .\r\n", 2 * n);
}
void critical_func3(uint32_t n)
{
PRINTF("Arg * 3 = %d .\r\n", 3 * n);
}
編譯鏈接修改后的工程,然后查看其映射文件(hello_world_demo_cm7.map)找到跟 critical_code.c 文件相關的內容如下,顯然 critical_code.c 中的三個函數都會被鏈在 Flash 空間里(均在 .text 段里)。
===============================================================================
Image Symbol Table
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
critical_func1 0x30005429 Thumb Code 28 critical_code.o(.text.critical_func1)
critical_func2 0x30005449 Thumb Code 32 critical_code.o(.text.critical_func2)
critical_func3 0x30005469 Thumb Code 36 critical_code.o(.text.critical_func3)
===============================================================================
Memory Map of the image
Execution Region ER_m_text (Exec base: 0x30002400, Load base: 0x30002400, Size: 0x00003b68, Max: 0x00fbdc00, ABSOLUTE, FIXED)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x30005428 0x30005428 0x0000001c Code RO 17 .text.critical_func1 critical_code.o
0x30005444 0x30005444 0x00000004 PAD
0x30005448 0x30005448 0x00000020 Code RO 19 .text.critical_func2 critical_code.o
0x30005468 0x30005468 0x00000024 Code RO 21 .text.critical_func3 critical_code.o
0x3000548c 0x3000548c 0x00000004 PAD
===============================================================================
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
96 56 0 0 0 903 critical_code.o
二、重定向到RAM中方法
我們現在要做的事就是將 critical_code.c 文件中的函數重定向到 RAM 里執行,原鏈接文件 MIMXRT1176xxxxx_cm7_flexspi_nor.scf 中指定的是 DTCM 來存放 readwrite 段,那我們就嘗試將關鍵函數放到 DTCM 里(如需改到 ITCM、OCRAM、SDRAM,方法類似)。
2.1 自定義section指定函數 - 針對單個函數
第一種方法是用 __attribute__((section("UserSectionName"))) 語法來修飾函數定義,將其放到自定義程序段里。這種方法主要適用重定向單個關鍵函數,比如我們將 critical_func1() 函數放到名為 .criticalFunc 的自定義段里:
__attribute__((section(".criticalFunc"))) void critical_func1(uint32_t n)
{
PRINTF("Arg = %d .\r\n", n);
}
void critical_func2(uint32_t n)
{
PRINTF("Arg * 2 = %d .\r\n", 2 * n);
}
void critical_func3(uint32_t n)
{
PRINTF("Arg * 3 = %d .\r\n", 3 * n);
}
然后在工程鏈接文件 MIMXRT1176xxxxx_cm7_flexspi_nor.scf 里將這個自定義的 section .criticalFunc 也放進 RW_m_data 執行域中:
LR_m_text 0x30002000 0x00FFE000 {
; ...
RW_m_data 0x20000000 0x0003F800 {
.ANY (+RW +ZI)
* (.criticalFunc) ;添加 .criticalFunc 段
; 第二種寫法:*.o (.criticalFunc)
}
; ...
}
編譯鏈接修改后的工程,然后查看其映射文件(hello_world_demo_cm7.map)找到跟 critical_code.c 文件相關的內容如下,此時 critical_func1() 已經被放到自定義段 .criticalFunc 里,並且這個段被 MDK 底層鏈接器鏈接到了 RAM 里(RW_m_data 執行域空間)。
===============================================================================
Image Symbol Table
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
critical_func1 0x20000001 Thumb Code 28 critical_code.o(.criticalFunc)
critical_func2 0x30005429 Thumb Code 32 critical_code.o(.text.critical_func2)
critical_func3 0x30005449 Thumb Code 36 critical_code.o(.text.critical_func3)
===============================================================================
Memory Map of the image
Execution Region RW_m_data (Exec base: 0x20000000, Load base: 0x30005f60, Size: 0x00000078, Max: 0x0003f800, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x20000000 0x30005f60 0x0000001c Code RO 17 .criticalFunc critical_code.o
Execution Region ER_m_text (Exec base: 0x30002400, Load base: 0x30002400, Size: 0x00003b60, Max: 0x00fbdc00, ABSOLUTE, FIXED)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x30005428 0x30005428 0x00000020 Code RO 19 .text.critical_func2 critical_code.o
0x30005448 0x30005448 0x00000024 Code RO 21 .text.critical_func3 critical_code.o
0x3000546c 0x3000546c 0x00000004 PAD
===============================================================================
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
96 56 0 0 0 903 critical_code.o
2.2 自定義section指定函數 - 針對同一文件里的多個函數
第二種方法是利用 #pragma 語法來修飾函數定義(注意 AC5 編譯器 Armcc 和 AC6 編譯器 Armclang 語法不太一樣),將同一源文件里緊挨在一起的多個關鍵函數放到自定義段里。比如我們將 critical_func1() 和 critical_func2() 函數放到名為 .criticalFunc 的自定義段里:
- Note: 這種方法一般情況下不太推薦,代碼可移植性較差。
#pragma clang section text = ".criticalFunc" // 適用 AC6 編譯器(范圍開始)
//#pragma arm section code = ".criticalFunc" // 適用 AC5 編譯器(范圍開始)
void critical_func1(uint32_t n)
{
PRINTF("Arg = %d .\r\n", n);
}
void critical_func2(uint32_t n)
{
PRINTF("Arg * 2 = %d .\r\n", 2 * n);
}
#pragma clang section text = "" // 適用 AC6 編譯器(范圍結束)
//#pragma arm section code // 適用 AC5 編譯器(范圍結束)
void critical_func3(uint32_t n)
{
PRINTF("Arg * 3 = %d .\r\n", 3 * n);
}
然后也是同樣在工程鏈接文件 MIMXRT1176xxxxx_cm7_flexspi_nor.scf 里將這個自定義的 section .criticalFunc 也放進 RW_m_data 執行域中:
LR_m_text 0x30002000 0x00FFE000 {
; ...
RW_m_data 0x20000000 0x0003F800 {
.ANY (+RW +ZI)
* (.criticalFunc) ;添加 .criticalFunc 段
}
; ...
}
編譯鏈接修改后的工程,然后查看其映射文件(hello_world_demo_cm7.map)找到跟 critical_code.c 文件相關的內容如下,此時 critical_func1/2() 均已經被放到自定義段 .criticalFunc 里,並且這個段被 MDK 底層鏈接器鏈接到了 RAM 里(RW_m_data 執行域空間)。
===============================================================================
Image Symbol Table
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
critical_func1 0x20000001 Thumb Code 28 critical_code.o(.criticalFunc)
critical_func2 0x20000021 Thumb Code 32 critical_code.o(.criticalFunc)
critical_func3 0x30005429 Thumb Code 36 critical_code.o(.text.critical_func3)
===============================================================================
Memory Map of the image
Execution Region RW_m_data (Exec base: 0x20000000, Load base: 0x30005f50, Size: 0x0000009c, Max: 0x0003f800, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x20000000 0x30005f50 0x00000040 Code RO 17 .criticalFunc critical_code.o
Execution Region ER_m_text (Exec base: 0x30002400, Load base: 0x30002400, Size: 0x00003b4c, Max: 0x00fbdc00, ABSOLUTE, FIXED)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x30005428 0x30005428 0x00000024 Code RO 19 .text.critical_func3 critical_code.o
0x3000544c 0x3000544c 0x00000004 PAD
===============================================================================
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
100 60 0 0 0 887 critical_code.o
2.3 針對源文件中全部函數
前兩種重定向方法都是針對具體函數的(如果是多個關鍵函數分散在多個文件里,按方法逐一添加修飾當然也行),但如果某個庫源文件特別多,並且我們希望將這些源文件里函數全部重定向到 RAM 里,有沒有更便捷的方法呢?當然有!
我們現在將 critical_code.c 文件里全部函數都重定向,只需要在工程鏈接文件 MIMXRT1176xxxxx_cm7_flexspi_nor.scf 里做如下修改:
LR_m_text 0x30002000 0x00FFE000 {
; ...
RW_m_data 0x20000000 0x0003F800 {
.ANY (+RW +ZI)
critical_code.o (+RO +RW +ZI) ;添加 critical_code.o 全部目標
}
; ...
}
編譯鏈接修改后的工程,然后查看其映射文件(hello_world_demo_cm7.map)找到跟 critical_code.c 文件相關的內容如下,此時 critical_func1/2/3() 都鏈接在 RAM 里了。
===============================================================================
Image Symbol Table
Global Symbols
Symbol Name Value Ov Type Size Object(Section)
critical_func1 0x20000001 Thumb Code 28 critical_code.o(.text.critical_func1)
critical_func2 0x20000021 Thumb Code 32 critical_code.o(.text.critical_func2)
critical_func3 0x20000041 Thumb Code 36 critical_code.o(.text.critical_func3)
===============================================================================
Memory Map of the image
Execution Region RW_m_data (Exec base: 0x20000000, Load base: 0x30005f30, Size: 0x000000c0, Max: 0x0003f800, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x20000000 0x30005f30 0x0000001c Code RO 17 .text.critical_func1 critical_code.o
0x2000001c 0x30005f4c 0x00000004 PAD
0x20000020 0x30005f50 0x00000020 Code RO 19 .text.critical_func2 critical_code.o
0x20000040 0x30005f70 0x00000024 Code RO 21 .text.critical_func3 critical_code.o
===============================================================================
Image component sizes
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
96 56 0 0 0 903 critical_code.o
2.4 被淘汰的 __ram 關鍵字
IAR 和 MCUXpresso IDE 下都內置了 .ramfunc 段,使用關鍵字 __ramfunc 或者 __RAMFUNC() 來修飾函數就可以直接將該函數放進內置的 .ramfunc 段里,不需要用戶再去手工改鏈接文件。
早期的 Keil native compiler(CARM) 確實是支持類似的特性的,就是用 __ram 來修飾函數,但是到了 AC5/AC6 這個特性被拿掉了,用戶一律需要修改自己鏈接文件來完成。為了代碼兼容,在 MDK 下可以自己定義一個宏,然后鏈接文件里將 .ramfunc 放進 RW_m_data 執行域里。
#define __ramfunc __attribute__((section(".ramfunc")))
三、鏈接文件自動生成功能
第二節里介紹的方法都是基於用戶自己提供的鏈接文件,如果想啟動 MDK 的鏈接文件自動生成功能,需要在工程 Option / Linker 里將 Use Memory Layout from Target Dialog 選項勾選上,這時候用戶提供的 MIMXRT1176xxxxx_cm7_flexspi_nor.scf 文件就失效了,MDK 會自動生成一個名為 hello_world_demo_cm7.sct 的鏈接文件。
自動生成的 hello_world_demo_cm7.sct 鏈接文件配置非常簡單,在工程 Option / Target 里有 Read/Only Memory Areas 和 Read/Write Memory Areas 指定,這里僅簡單提供了 RO 和 RW 段空間指定設置,沒有關於用戶自定義段的單獨設置。
不過比較特色的是,在 MDK 里可以單獨為某個文件指定 Memory Assignment,這樣我們也能跟 2.3 節里的方法一樣實現整個文件里的函數重定向。
四、啟動文件中拷貝過程
三種函數重定向方法都介紹完了,不知道你是否曾有過這樣的疑問,這些關鍵函數機器碼到底是什么時候怎么從 Flash 中拷貝到 RAM 里的?這要從工程啟動文件 startup_MIMXRT1176_cm7.S 談起。在復位函數 Reset_Handler 的最后調用了 MDK 內置函數 __main,這個函數中隱藏着玄機,我們可以在 ARM CMSIS 庫中找到該函數原型,順着原型你應該可以發現其中的奧秘。
Reset_Handler:
cpsid i
.equ VTOR, 0xE000ED08
ldr r0, =VTOR
ldr r1, =__Vectors
str r1, [r0]
ldr r2, [r1]
msr msp, r2
ldr r0,=SystemInit
blx r0
cpsie i
ldr r0,=__main
bx r0
至此,在MDK開發環境下將關鍵函數重定向到RAM中執行的幾種方法痞子衡便介紹完畢了,掌聲在哪里~~~
歡迎訂閱
文章會同時發布到我的 博客園主頁、CSDN主頁、知乎主頁、微信公眾號 平台上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。