在上一篇文章IAR中ICF鏈接文件詳解和實例分析中,我通過I.MX RT1170的SDK中的內(nèi)存映射關(guān)系,分析了IAR中的ICF鏈接文件的語法。對于MCU編程所使用的IDE來說,IAR和Keil用得比較多,所以這一篇文章就來分析一下Keil的分散文件.scf
(scatter file
)。
1 內(nèi)存映射
和上一篇文章一樣,同樣使用I.MX RT1170的SDK中的鏈接文件進行分析,通過實際的分散文件來學(xué)習(xí)里面的語法。和上一節(jié)也是同一個例程,除了芯片自帶的RAM外,還有NOR Flash和SDRAM。首先來看一下整個工程的內(nèi)存映射表格:
類型 | 名稱 | 起始地址 | 大小 |
---|---|---|---|
Flash | NOR Flash | 0x30000000 | 0x1000000 |
RAM | SDRAM | 0x80000000 | 0x3000000 |
RAM | NCACHE_REGION | 0x83000000 | 0x1000000 |
RAM | SRAM_DTC_cm7 | 0x20000000 | 0x40000 |
RAM | SRAM_ITC_cm7 | 0x0 | 0x40000 |
RAM | SRAM_OC1 | 0x20240000 | 0x80000 |
RAM | SRAM_OC2 | 0x202c0000 | 0x80000 |
RAM | SRAM_OC_ECC1 | 0x20340000 | 0x10000 |
RAM | SRAM_OC_ECC2 | 0x20350000 | 0x10000 |
對于我們的工程來說,有以下幾個內(nèi)存:
- 兩個256KB的緊耦合內(nèi)存
DTCM
和ITCM
- 兩個帶ECC的片內(nèi)RAM:
OC1
和OC2
。 - 在映射的起始地址為
0x30000000
的FlexSPI1接口上接了一個16MB的NOR Flash - 在映射的起始地址為
0x80000000
的FlexSPI2接口上接了一個64MB的SDRAM。其中,前48MB用于可緩存的區(qū)域,后16MB(NCACHE_REGION
)用于不可緩存區(qū)域,通常直接與硬件進行交互的buffer需要設(shè)置為不可緩存。
2 SCF語法分析
2.1 工程的SCF文件
針對上面的內(nèi)存映射,官方的SDK中提供的SCF
文件如下:
#if (defined(__ram_vector_table__))
#define __ram_vector_table_size__ 0x00000400
#else
#define __ram_vector_table_size__ 0x00000000
#endif
#define m_flash_config_start 0x30000400
#define m_flash_config_size 0x00000C00
#define m_ivt_start 0x30001000
#define m_ivt_size 0x00000020
#define m_boot_data_start 0x30001020
#define m_boot_data_size 0x00000010
#define m_dcd_data_start 0x30001030
#define m_dcd_data_size 0x000006E8
#define m_xmcd_data_start 0x30001040
#define m_xmcd_data_size 0x00000204
#define m_interrupts_start 0x30002000
#define m_interrupts_size 0x00000400
#define m_text_start 0x30002400
#if (defined(__use_flash64MB__))
#define m_text_size 0x03FFDC00
#else
#define m_text_size 0x00FFDC00
#endif
#define m_qacode_start 0x00000000
#define m_qacode_size 0x00040000
#define m_interrupts_ram_start 0x80000000
#define m_interrupts_ram_size __ram_vector_table_size__
#define m_data_start (m_interrupts_ram_start + m_interrupts_ram_size)
#define m_data_size (0x03000000 - m_interrupts_ram_size)
#define m_data2_start 0x20000000
#define m_data2_size 0x00040000
#define m_data3_start 0x202C0000
#define m_data3_size 0x00080000
#define m_ncache_start 0x83000000
#define m_ncache_size 0x01000000
/* Sizes */
#if (defined(__stack_size__))
#define Stack_Size __stack_size__
#else
#define Stack_Size 0x0400
#endif
#if (defined(__heap_size__))
#define Heap_Size __heap_size__
#else
#define Heap_Size 0x0400
#endif
#if defined(XIP_BOOT_HEADER_ENABLE) && (XIP_BOOT_HEADER_ENABLE == 1)
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start { ; load region size_region
RW_m_config_text m_flash_config_start FIXED m_flash_config_size { ; load address = execution address
* (.boot_hdr.conf, +FIRST)
}
RW_m_ivt_text m_ivt_start FIXED m_ivt_size { ; load address = execution address
* (.boot_hdr.ivt, +FIRST)
}
RW_m_boot_data_text m_boot_data_start FIXED m_boot_data_size { ; load address = execution address
* (.boot_hdr.boot_data, +FIRST)
}
#if defined(XIP_BOOT_HEADER_DCD_ENABLE) && (XIP_BOOT_HEADER_DCD_ENABLE == 1)
RW_m_dcd_data_text m_dcd_data_start FIXED m_dcd_data_size { ; load address = execution address
* (.boot_hdr.dcd_data, +FIRST)
}
#elif defined(XIP_BOOT_HEADER_XMCD_ENABLE) && (XIP_BOOT_HEADER_XMCD_ENABLE == 1)
RW_m_xmcd_data_text m_xmcd_data_start FIXED m_xmcd_data_size { ; load address = execution address
* (.boot_hdr.xmcd_data, +FIRST)
}
#endif
#else
LR_m_text m_interrupts_start m_text_start+m_text_size-m_interrupts_start { ; load region size_region
#endif
VECTOR_ROM m_interrupts_start FIXED m_interrupts_size { ; load address = execution address
* (.isr_vector,+FIRST)
}
ER_m_text m_text_start FIXED m_text_size { ; load address = execution address
* (InRoot$$Sections)
.ANY (+RO)
}
#if (defined(__ram_vector_table__))
VECTOR_RAM m_interrupts_ram_start EMPTY m_interrupts_ram_size {
}
#else
VECTOR_RAM m_interrupts_start EMPTY 0 {
}
#endif
RW_m_data2 m_data2_start m_data2_size {
* (RamFunction)
* (DataQuickAccess)
}
#if (defined(__heap_noncacheable__))
RW_m_data m_data_start m_data_size-Stack_Size { ; RW data
#else
RW_m_data m_data_start m_data_size-Stack_Size-Heap_Size { ; RW data
#endif
.ANY (+RW +ZI)
*(*m_usb_dma_init_data)
*(*m_usb_dma_noninit_data)
}
#if (!defined(__heap_noncacheable__))
ARM_LIB_HEAP +0 EMPTY Heap_Size { ; Heap region growing up
}
#endif
ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size { ; Stack region growing down
}
RW_m_ram_text m_qacode_start m_qacode_size { ;
* (CodeQuickAccess)
}
#if (defined(__heap_noncacheable__))
RW_m_ncache m_ncache_start m_ncache_size - Heap_Size { ; ncache data
#else
RW_m_ncache m_ncache_start m_ncache_size { ; ncache data
#endif
* (NonCacheable.init)
* (*NonCacheable)
}
#if (defined(__heap_noncacheable__))
ARM_LIB_HEAP +0 EMPTY Heap_Size { ; Heap region growing up
}
RW_m_ncache_unused +0 EMPTY m_ncache_size-ImageLength(RW_m_ncache)-Heap_Size { ; Empty region added for MPU configuration
#else
RW_m_ncache_unused +0 EMPTY m_ncache_size-ImageLength(RW_m_ncache) { ; Empty region added for MPU configuration
#endif
}
}
2.2 define
先來分析第一段分散文件,KEIL的分散文件的語法define
和#if defined
語句與C語言一致,所以下面這一段還是很好理解的:
#if (defined(__ram_vector_table__))
#define __ram_vector_table_size__ 0x00000400
#else
#define __ram_vector_table_size__ 0x00000000
#endif
#define m_flash_config_start 0x30000400
#define m_flash_config_size 0x00000C00
#define m_ivt_start 0x30001000
#define m_ivt_size 0x00000020
#define m_boot_data_start 0x30001020
#define m_boot_data_size 0x00000010
#define m_dcd_data_start 0x30001030
#define m_dcd_data_size 0x000006E8
#define m_xmcd_data_start 0x30001040
#define m_xmcd_data_size 0x00000204
#define m_interrupts_start 0x30002000
#define m_interrupts_size 0x00000400
#define m_text_start 0x30002400
#if (defined(__use_flash64MB__))
#define m_text_size 0x03FFDC00
#else
#define m_text_size 0x00FFDC00
#endif
#define m_qacode_start 0x00000000
#define m_qacode_size 0x00040000
#define m_interrupts_ram_start 0x80000000
#define m_interrupts_ram_size __ram_vector_table_size__
#define m_data_start (m_interrupts_ram_start + m_interrupts_ram_size)
#define m_data_size (0x03000000 - m_interrupts_ram_size)
#define m_data2_start 0x20000000
#define m_data2_size 0x00040000
#define m_data3_start 0x202C0000
#define m_data3_size 0x00080000
#define m_ncache_start 0x83000000
#define m_ncache_size 0x01000000
/* Sizes */
#if (defined(__stack_size__))
#define Stack_Size __stack_size__
#else
#define Stack_Size 0x0400
#endif
#if (defined(__heap_size__))
#define Heap_Size __heap_size__
#else
#define Heap_Size 0x0400
#endif
先說明一下,I.MX系列單片機上電會進入L1 BootLoader,它用來引導(dǎo)程序如何啟動,比如說是否加密、加密密鑰、是XIP還是non-XIP(就要拷貝到RAM)、是否要初始化時鐘。在NOR Flash啟動的情況下,程序鏡像的前0x2000字節(jié)就是用來給L1 BootLoader提供一些啟動信息的,這里不必過分關(guān)注這些字段的意義,若想詳細(xì)理解可以參考我的這篇文章I.MX RT1170啟動詳解:Boot配置、Bootable image頭的組成。
(1)__ram_vector_table__
沒有在別的地方定義,所以__ram_vector_table_size__
為0。這也很好理解,因為這里有NOR Flash,向量表就不放到RAM中了,而是放在NOR Flash的最前面。
(2)m_flash_config_start
和m_flash_config_size
:用來給L1 BootLoader提供NOR Flash的配置信息,因為上電后L1 BootLoader用最慢的最保險的配置來初始化NOR Flash,如果用戶希望自行配置一些參數(shù),比如時鐘變快一些,就可以在這個字段填充配置信息,起始地址為0x30000400
(NOR Flash的基地址為0x30000000
),長度為0xC00
(3)m_ivt_start
和m_ivt_size
:IVT(Image Vector Table
)字段,用來保存程序入口地址等參數(shù)
(4)m_boot_data_start
和m_boot_data_size
:用來保存鏡像的絕對起始地址和大小
(5)m_dcd_data_start
和m_dcd_data_size
:DCD
字段,一般用來初始化SDRAM,特別是希望程序在SDRAM運行的時候需要配置此字段
(6)m_xmcd_data_start
和m_xmcd_data_size
:可以看到這里的起始地址和大小與上面的DCD
字段重合了,實際上二者的功能類似,只不過DCD
的配置是一個個寄存器配置的指令,比較復(fù)雜,而XMCD簡化了這些配置操作,這兩個字段是二選一的。
(7)m_interrupts_start
和m_interrupts_size
:前面說了,L1 BootLoader的頭信息的大小為0x2000
,所以從0x2000
開始就是程序的開始,最前面放置向量表,長度為0x400
。
(8)m_text_start
和m_text_size
:代碼段緊接著向量表后面,起始地址為0x30002400
,這里__use_flash64MB__
為假,我們假設(shè)用的是16MB(0x1000000
)的NOR Flash,剩下的大小就是0x1000000-0x2400=0x00FFDC00
。
(9)m_qacode_start
和m_qacode_size
:即前面內(nèi)存映射中芯片內(nèi)部的SRAM_ITC_cm7
(10)m_interrupts_ram_start
和m_interrupts_ram_size
:如果向量表沒有放在NOR Flash,就放在SDRAM的起始,這里由于放在NOR Flash,這兩個字段沒有用到
(11)m_data_start
和m_data_size
:data數(shù)據(jù)段,這里將data段放在了SDRAM。這里SDRAM的大小為64M,這個字段占了前48M。
(12)m_data2_start
、m_data2_size
、m_data3_start
和m_data3_size
:同樣是data數(shù)據(jù)段,分別為SRAM_ITC_cm7
和SRAM_OC2
,即片內(nèi)的RAM都可以作為data段放置變量
-
SRAM_OC1
沒有用到,我們可以自行聲明。因為L1 BootLoader運行時用到了這塊SRAM,所以使用時需要考慮使用這塊SRAM的時間。
(13)m_ncache_start
和m_ncache_size
:即SDRAM最后的16M用來做non-cacheable區(qū)域,比如GUI繪制的Buffer、攝像頭的Buffer和DMA的數(shù)據(jù),這種直接與硬件交互的內(nèi)存,需要定義在不可緩存的區(qū)域。這與MPU配置有關(guān),可以參考我的MPU系列的文章MPU內(nèi)存保護單元詳解及例子和L1 Cache之I-Cache和D-cache詳解。
(14)Stack_Size
和Heap_Size
:分別為棧和堆的大小,由于程序中使用了FreeRTOS,所以只要保證這里的棧和堆的大小能夠成功初始化FreeRTOS即可,初始化FreeRTOS過程應(yīng)該沒有內(nèi)存分配,所以Heap Size
可以設(shè)置為0。
2.3 加載區(qū)域和執(zhí)行區(qū)域
接下來開始涉及到一些分散文件的語法,參考文檔:<DUI0377G_02_mdk_armlink_user_guide.pdf>
(可以在KEIL安裝目錄下找到)。
相比IAR,KEIL的分散文件的語法簡單地多,和Linux的ld文件差不多,分散文件就由一個或多個加載區(qū)域(Load Region
)構(gòu)成,如下圖所示:
加載區(qū)域的語法如下:
load_region_name (base_address | ("+" offset)) [attribute_list] [max_size]
"{"
execution_region_description+
"}"
-
load_region_name
(名稱): 用于由鏈接器識別不同加載區(qū)域的獨特標(biāo)簽,每個加載區(qū)域必須具有唯一的名稱 -
base_address
(基地址):加載區(qū)域內(nèi)的代碼和數(shù)據(jù)在內(nèi)存中放置的起始內(nèi)存地址 -
attribute_list
(屬性): 定義加載區(qū)域的特性和行為,包括只讀、讀寫、僅執(zhí)行或其他內(nèi)存保護屬性 -
max_size
(最大大小): 可選,用于限制加載區(qū)域的大小,防止內(nèi)存溢出 -
execution_region_description
(執(zhí)行區(qū)域): 加載區(qū)域可以包含一個或多個執(zhí)行區(qū)域。執(zhí)行區(qū)域表示連續(xù)的代碼和數(shù)據(jù)塊,作為一個單獨的單元加載到內(nèi)存中
如果要把所有語法都總結(jié)到文章中就太耗時了,所以還是繼續(xù)分析分散文件,出現(xiàn)了什么語法或關(guān)鍵字,我們再來去找它的意思。由于后面的分散文件中的宏定義太多而影響閱讀,這里假設(shè)XIP_BOOT_HEADER_ENABLE=1
、XIP_BOOT_HEADER_DCD_ENABLE=1
、XIP_BOOT_HEADER_XMCD_ENABLE=0
和__heap_noncacheable__
(表示將堆放置在non-cacheable區(qū)域,保證堆內(nèi)存不會收到緩存的影響)。
剩下的分散文件實際上就是定義了一個加載區(qū)域LR_m_text
,它的起始地址為m_flash_config_start
(0x30000400
),最大的大小為m_text_start+m_text_size-m_flash_config_start
(16M-0x400=0xFFFC00
),即從0x30000400
處開始鏈接,大小為0xFFFC00
,這個大小僅限制加載區(qū)域的大小(下面屬性為FIXED
的執(zhí)行區(qū)域)。0~0x400
與NXP RT系列單片機的加密啟動有關(guān),這些字段編譯器無法進行填充,所以這里就沒有考慮。
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start { ; load region size_region
......
}
- 在分散文件中,
;
后面為注釋
在加載區(qū)域LR_m_text
下有非常多個執(zhí)行區(qū)域,下面來一個個分析一下:
1、RW_m_config_text
:起始地址0x30000400
,大小0xC00
RW_m_config_text m_flash_config_start FIXED m_flash_config_size { ; load address = execution address
* (.boot_hdr.conf, +FIRST)
}
-
FIXED
:執(zhí)行區(qū)域的屬性,表示讓執(zhí)行區(qū)域的執(zhí)行地址與加載地址盡量保持相等。這意味著,分配給這個執(zhí)行區(qū)域的代碼和數(shù)據(jù)在加載到內(nèi)存時會盡量放置在指定的執(zhí)行地址上。如果因為內(nèi)存沖突或空間不足等原因無法滿足,則鏈接器會報錯。 -
+FIRST
:表示把該section
放在該執(zhí)行區(qū)域的最開始的地方
所以這里就是從0x30000400開始處開始放置boot_hdr.conf
段,因為放置的位置必須固定才能被L1 BootLoader正確識別,所以執(zhí)行區(qū)域需要用FIXED
屬性。
2、RW_m_ivt_text
:起始地址0x30001000
,大小0x00000020
RW_m_ivt_text m_ivt_start FIXED m_ivt_size { ; load address = execution address
* (.boot_hdr.ivt, +FIRST)
}
同上,放置L1 BootLoader的引導(dǎo)頭。
3、RW_m_boot_data_text
:起始地址0x30001020
,大小0x00000010
RW_m_boot_data_text m_boot_data_start FIXED m_boot_data_size { ; load address = execution address
* (.boot_hdr.boot_data, +FIRST)
}
同上,放置L1 BootLoader的引導(dǎo)頭。
4、RW_m_dcd_data_text
:起始地址0x30001030
,大小0x000006E8
RW_m_dcd_data_text m_dcd_data_start FIXED m_dcd_data_size { ; load address = execution address
* (.boot_hdr.dcd_data, +FIRST)
}
同上,放置L1 BootLoader的引導(dǎo)頭。
5、VECTOR_ROM
:起始地址0x30002000
,大小0x00000400
VECTOR_ROM m_interrupts_start FIXED m_interrupts_size { ; load address = execution address
* (.isr_vector,+FIRST)
}
放置中斷向量表。在啟動文件startup_MIMXRT1176_cm7.S
文件中定義了該段:.section .isr_vector, "a"
,這里的a
表示將該段標(biāo)記為可分配(allocatable
)的,意味著它在鏈接時可以被分配到內(nèi)存中的某個位置。
6、ER_m_text
:起始地址0x30002400
,大小0x00FFDC00
ER_m_text m_text_start FIXED m_text_size { ; load address = execution address
* (InRoot$$Sections)
.ANY (+RO)
}
-
InRoot$$Sections
是在分散文件中使用的特殊標(biāo)記,用來將壓縮數(shù)據(jù)段放置該執(zhí)行區(qū)域中,以確保這些數(shù)據(jù)段在運行時能夠被自動解壓縮并提供給程序使用。該特性是ARM為了減少存儲空間占用設(shè)計的,上電后ARM庫會根據(jù)此段來進行解壓。- 參考文章:Example of placing code in a root region
-
.ANY
:可以理解為*
,表示所有段,但.ANY
可以用在多個執(zhí)行區(qū)域中,而*
一般只用在一個執(zhí)行區(qū)域中,所以.ANY
會更靈活一些。具體參考手冊7.4章節(jié)<Placement of unassigned sections with the .ANY module selector>
-
(+RO)
:只讀數(shù)據(jù)段
這里表示將所有的只讀數(shù)據(jù)段放置在這個執(zhí)行區(qū)域。
7、VECTOR_RAM
:這里將中斷向量表放置在NOR Flash了,這個執(zhí)行區(qū)域沒有用到
VECTOR_RAM m_interrupts_start EMPTY 0 {
}
-
EMPTY
表示保留一個空區(qū)域,但這里區(qū)域的大小設(shè)為0,所以這段執(zhí)行區(qū)域沒有任何作用
8、RW_m_data2
:起始地址0x20000000
,大小0x00040000
RW_m_data2 m_data2_start m_data2_size {
* (RamFunction)
* (DataQuickAccess)
}
這里定義了兩個Section:RamFunction
和DataQuickAccess
,在程序中都可以用__attribute__((section("")))
來定義函數(shù)或變量到內(nèi)部的SRAM_DTCM
中,可以加快函數(shù)的執(zhí)行速度和數(shù)據(jù)的訪問速度。
9、RW_m_data
:起始地址0x80000000
,大小0x03000000-0x400
RW_m_data m_data_start m_data_size-Stack_Size { ; RW data
.ANY (+RW +ZI)
*(*m_usb_dma_init_data)
*(*m_usb_dma_noninit_data)
}
這個執(zhí)行區(qū)域就是SDRAM的前48M,將所有的讀寫數(shù)據(jù)段和bss段放置在此,同時聲明兩個usb段,用于SDK中對于USB相關(guān)功能的實現(xiàn)。實際上USB段放在non-cacheable
區(qū)域肯定是可以運行的,但是同時也意味著沒有用到緩存,速度就會降低很多。所以就可以將USB相關(guān)變量聲明到cacheable
的區(qū)域,然后在代碼中必要的地方手動緩存更新相關(guān)函數(shù),如SCB_CleanInvalidateDCache
和SCB_CleanDCache
。
10、ARM_LIB_STACK
:起始地址0x83000000
,大小0x400
ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size { ; Stack region growing down
}
-
ARM_LIB_STACK
:棧的執(zhí)行區(qū)域的固定名稱
這里的Stack_Size
的前面有一個-
,表示棧是向下生長的。
11、RW_m_ram_text
:起始地址0x00000000
,大小0x00040000
RW_m_ram_text m_qacode_start m_qacode_size { ;
* (CodeQuickAccess)
}
與上面的7類似,聲明一個CodeQuickAccess
段,用于將函數(shù)鏈接到內(nèi)部的SRAM_ITCM
中,因為內(nèi)部的SRAM的速度比NOR Flash或SDRAM的訪問速度都快得多。
12、RW_m_ncache
:起始地址0x83000000
,大小0x01000000-0x400
RW_m_ncache m_ncache_start m_ncache_size - Heap_Size { ; ncache data
* (NonCacheable.init)
* (*NonCacheable)
}
定義non-cacheable
區(qū)域的兩個段NonCacheable.init
和NonCacheable
,同時預(yù)留堆的空間,因為這里我們假設(shè)堆空間也為non-cacheable
。
13、ARM_LIB_HEAP
:大小0x400
ARM_LIB_HEAP +0 EMPTY Heap_Size { ; Heap region growing up
}
-
+0
:表示為上一個執(zhí)行區(qū)域的結(jié)束地址,根據(jù)用戶放置到RW_m_ncache
執(zhí)行區(qū)域的變量的多少和大小來決定這個地址 -
ARM_LIB_HEAP
:堆的執(zhí)行區(qū)域的固定名稱
定義堆空間的內(nèi)存。
14、RW_m_ncache_unused
文章來源:http://www.zghlxwxcb.cn/news/detail-702241.html
RW_m_ncache_unused +0 EMPTY m_ncache_size-ImageLength(RW_m_ncache)-Heap_Size { ; Empty region added for MPU configuration
}
同樣放置在上一個執(zhí)行區(qū)域的結(jié)束地址處,用來給MPU進行配置,大小m_ncache_size-ImageLength(RW_m_ncache)-Heap_Size
即除去變量和堆外的剩下的non-cacheable
區(qū)域。文章來源地址http://www.zghlxwxcb.cn/news/detail-702241.html
-
ImageLength
可以取某個執(zhí)行區(qū)域占的大小
到了這里,關(guān)于嵌入式IDE(2):KEIL中SCF分散加載鏈接文件詳解和實例分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!