In the previous article ICF link file detailed explanation and example analysis in IAR , I analyzed the syntax of the ICF link file in IAR through the memory mapping relationship in the I.MX RT1170 SDK. For IDEs used in MCU programming, IAR and Keil are used more frequently, so this article will analyze Keil’s scattered files .scf
( scatter file
).
Article directory
1 memory map
Like the previous article, I also use the link file in the SDK of I.MX RT1170 for analysis, and learn the grammar inside through the actual scattered file. It is the same routine as the previous section. In addition to the RAM that comes with the chip, there are NOR Flash and SDRAM. First look at the memory mapping table of the entire project:
type | name | initial address | size |
---|---|---|---|
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 |
For our project, there are the following memories:
- Two 256KB tightly coupled memories
DTCM
andITCM
- Two on-chip RAMs with ECC:
OC1
andOC2
. 0x30000000
A 16MB NOR Flash is connected to the FlexSPI1 interface whose starting address is0x80000000
A 64MB SDRAM is connected to the FlexSPI2 interface whose starting address is mapped . Among them, the first 48MB is used for the cacheable area, and the last 16MB (NCACHE_REGION
) is used for the uncacheable area. Generally, the buffer that directly interacts with the hardware needs to be set as uncacheable.
2 SCF syntax analysis
2.1 SCF file of the project
For the memory mapping above, the files provided in the official SDK SCF
are as follows:
#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
Let’s first analyze the first paragraph of scattered files. The syntax define
and #if defined
statements of KEIL’s scattered files are consistent with the C language, so the following paragraph is easy to understand:
#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
Let me explain first, when the I.MX series microcontroller is powered on, it will enter the L1 BootLoader, which is used to boot how to start the program, such as whether it is encrypted, the encryption key, whether it is XIP or non-XIP (it needs to be copied to RAM), and whether it needs to be initialized. clock. In the case of NOR Flash startup, the first 0x2000 bytes of the program image are used to provide some startup information to the L1 BootLoader. There is no need to pay too much attention to the meaning of these fields. If you want to understand in detail, you can refer to my article I.MX Detailed explanation of RT1170 startup: Boot configuration and composition of Bootable image header .
(1) __ram_vector_table__
is not defined elsewhere, so __ram_vector_table_size__
it is 0. This is also easy to understand, because there is NOR Flash here, the vector table is not placed in RAM, but placed at the front of NOR Flash.
(2) m_flash_config_start
and m_flash_config_size
: used to provide NOR Flash configuration information to L1 BootLoader, because after power-on, L1 BootLoader uses the slowest and safest configuration to initialize NOR Flash. If the user wants to configure some parameters by himself, such as making the clock faster, You can fill in the configuration information in this field, the starting address is 0x30000400
(the base address of NOR Flash is 0x30000000
), the length is 0xC00
(3) m_ivt_start
and m_ivt_size
: IVT ( Image Vector Table
) field, used to save parameters such as program entry address
(4) m_boot_data_start
and m_boot_data_size
: used Save the absolute starting address and size of the image
(5) m_dcd_data_start
and m_dcd_data_size
: DCD
fields, which are generally used to initialize SDRAM. Especially if you want the program to run in SDRAM, you need to configure this field
(6) m_xmcd_data_start
andm_xmcd_data_size
: You can see that the starting address and size here DCD
overlap with the fields above. In fact, the functions of the two are similar, except that DCD
the configuration is a register configuration instruction, which is more complicated, and XMCD simplifies these configuration operations. This The two fields are optional.
(7) m_interrupts_start
and m_interrupts_size
: As mentioned earlier, the size of the header information of the L1 BootLoader is 0x2000
, so 0x2000
it is the beginning of the program from the beginning, and the vector table is placed at the front, and the length is 0x400
.
(8) m_text_start
and m_text_size
: The code segment is immediately after the vector table, the starting address is 0x30002400
, here __use_flash64MB__
is false, we assume that 16MB ( ) NOR Flash is used 0x1000000
, and the remaining size is 0x1000000-0x2400=0x00FFDC00
.
(9) m_qacode_start
Sum : That is , (10) m_qacode_size
sum inside the chip in the previous memory map : If the vector table is not placed in NOR Flash, it is placed at the beginning of SDRAM. Since it is placed in NOR Flash, these two fields are not used ( 11) and : data data segment, where the data segment is placed in SDRAM. Here the size of SDRAM is 64M, and this field occupies the first 48M. (12) , , and : the same data segment, respectively and , that is, the on-chip RAM can be used as the data segment to place variablesSRAM_ITC_cm7
m_interrupts_ram_start
m_interrupts_ram_size
m_data_start
m_data_size
m_data2_start
m_data2_size
m_data3_start
m_data3_size
SRAM_ITC_cm7
SRAM_OC2
SRAM_OC1
If it is not used, we can declare it ourselves. Because L1 BootLoader uses this SRAM when running, you need to consider the time when using this SRAM.
(13) m_ncache_start
and m_ncache_size
: The last 16M of SDRAM is used as a non-cacheable area, such as the Buffer drawn by the GUI, the Buffer of the camera, and the DMA data. This kind of memory that directly interacts with the hardware needs to be defined in a non-cacheable area. This is related to the MPU configuration. You can refer to my MPU series articles MPU memory protection unit detailed explanation and examples and L1 Cache I-Cache and D-cache detailed explanation .
(14) Stack_Size
and Heap_Size
: the size of the stack and heap respectively. Since FreeRTOS is used in the program, it is only necessary to ensure that the size of the stack and heap here can successfully initialize FreeRTOS. There should be no memory allocation during the initialization of FreeRTOS, so it can be set to Heap Size
0 .
2.3 Loading area and execution area
Next, we will start to talk about the syntax of some scattered files. Please refer to the documentation: <DUI0377G_02_mdk_armlink_user_guide.pdf>
(can be found in the KEIL installation directory).
Compared with IAR, the syntax of KEIL's scatter file is much simpler, similar to that of Linux's ld file. The scatter file consists of one or more loading areas ( Load Region
), as shown in the figure below:
the syntax of the loading area is as follows:
load_region_name (base_address | ("+" offset)) [attribute_list] [max_size]
"{"
execution_region_description+
"}"
load_region_name
(name): A unique label used to identify different load areas by the linker, each load area must have a unique namebase_address
(Base address): The starting memory address where the code and data in the loading area are placed in memory.attribute_list
(property): Defines the characteristics and behavior of the load area, including read-only, read-write, execute-only, or other memory protection attributesmax_size
(maximum size): Optional, used to limit the size of the loading area to prevent memory overflowexecution_region_description
(Execution Region): The load region can contain one or more execution regions. The execution region represents a contiguous block of code and data loaded into memory as a single unit
It would be too time-consuming to summarize all the grammars into the article, so continue to analyze the scattered files, what grammar or keywords appear, and then we will find its meaning. Because there are too many macro definitions in the following scatter files, the reading is affected. Here, it is assumed that XIP_BOOT_HEADER_ENABLE=1
, XIP_BOOT_HEADER_DCD_ENABLE=1
, XIP_BOOT_HEADER_XMCD_ENABLE=0
and __heap_noncacheable__
(indicate that the heap is placed in a non-cacheable area to ensure that the heap memory will not be affected by the cache).
The rest of the scatter file actually defines a loading area LR_m_text
, its starting address is m_flash_config_start
( 0x30000400
), and its maximum size is m_text_start+m_text_size-m_flash_config_start
( 16M-0x400=0xFFFC00
), that is, 0x30000400
the link starts from there, and the size is 0xFFFC00
, this size only limits the size of the loading area (the following attribute for FIXED
the execution region). 0~0x400
Relevant to the encrypted startup of NXP RT series microcontrollers, these fields cannot be filled by the compiler, so they are not considered here.
LR_m_text m_flash_config_start m_text_start+m_text_size-m_flash_config_start { ; load region size_region
......
}
- In scatter files,
;
followed by comments
LR_m_text
There are many execution areas under the loading area . Let’s analyze them one by one:
1.RW_m_config_text
: Starting address 0x30000400
, size0xC00
RW_m_config_text m_flash_config_start FIXED m_flash_config_size { ; load address = execution address
* (.boot_hdr.conf, +FIRST)
}
FIXED
: Attributes of the execution area, indicating that the execution address and the load address of the execution area should be kept equal as much as possible. This means that the code and data allocated to this execution region will try to be placed at the specified execution address when loaded into memory. If it cannot be satisfied due to memory conflicts or insufficient space, the linker will report an error.+FIRST
: Indicatessection
placing the function at the beginning of the execution area
So here is to start placing the segment from 0x30000400 boot_hdr.conf
, because the placement position must be fixed to be correctly recognized by the L1 BootLoader, so the execution area needs to use FIXED
attributes.
2.RW_m_ivt_text
: Starting address 0x30001000
, size0x00000020
RW_m_ivt_text m_ivt_start FIXED m_ivt_size { ; load address = execution address
* (.boot_hdr.ivt, +FIRST)
}
Same as above, place the boot header of L1 BootLoader.
3.RW_m_boot_data_text
: start address 0x30001020
, size0x00000010
RW_m_boot_data_text m_boot_data_start FIXED m_boot_data_size { ; load address = execution address
* (.boot_hdr.boot_data, +FIRST)
}
Same as above, place the boot header of L1 BootLoader.
4.RW_m_dcd_data_text
: start address 0x30001030
, size0x000006E8
RW_m_dcd_data_text m_dcd_data_start FIXED m_dcd_data_size { ; load address = execution address
* (.boot_hdr.dcd_data, +FIRST)
}
Same as above, place the boot header of L1 BootLoader.
5.VECTOR_ROM
: start address 0x30002000
, size0x00000400
VECTOR_ROM m_interrupts_start FIXED m_interrupts_size { ; load address = execution address
* (.isr_vector,+FIRST)
}
Place the interrupt vector table. startup_MIMXRT1176_cm7.S
The section is defined in the startup file: .section .isr_vector, "a"
, where a
the section is marked as allocatable ( allocatable
), which means that it can be allocated to a certain location in memory when linking.
6.ER_m_text
: start address 0x30002400
, size0x00FFDC00
ER_m_text m_text_start FIXED m_text_size { ; load address = execution address
* (InRoot$$Sections)
.ANY (+RO)
}
InRoot$$Sections
It is a special mark used in the scatter file to place compressed data segments in the execution area to ensure that these data segments can be automatically decompressed and provided to the program at runtime. This feature is designed by ARM to reduce the storage space occupied. After power-on, the ARM library will decompress according to this section.- Reference text: Example of placing code in a root region
.ANY
: It can be understood as*
representing all segments, but.ANY
it can be used in multiple execution areas, but*
generally only used in one execution area, so.ANY
it will be more flexible. For details, refer to Chapter 7.4 of the manual<Placement of unassigned sections with the .ANY module selector>
(+RO)
: read-only data segment
This means placing all read-only data segments in this execution area.
7.VECTOR_RAM
: The interrupt vector table is placed in NOR Flash here. This execution area is not used.
VECTOR_RAM m_interrupts_start EMPTY 0 {
}
EMPTY
Indicates that an empty area is reserved, but the size of this area is set to 0, so this execution area has no effect.
8.RW_m_data2
: start address 0x20000000
, size0x00040000
RW_m_data2 m_data2_start m_data2_size {
* (RamFunction)
* (DataQuickAccess)
}
Two Sections are defined here: RamFunction
and DataQuickAccess
, which can __attribute__((section("")))
be used to SRAM_DTCM
, which can speed up the execution speed of functions and the access speed of data.
9.RW_m_data
: Starting address 0x80000000
, size0x03000000-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)
}
This execution area is the first 48M of SDRAM, where all read and write data segments and bss segments are placed, and two usb segments are declared at the same time, which are used to implement USB-related functions in the SDK. In fact, the USB segment non-cacheable
can definitely run if placed in the zone, but it also means that the cache is not used and the speed will be reduced a lot. Therefore, you can declare USB related variables to cacheable
the area, and then manually cache and update related functions in necessary places in the code, such as SCB_CleanInvalidateDCache
and SCB_CleanDCache
.
10.ARM_LIB_STACK
: Starting address 0x83000000
, size0x400
ARM_LIB_STACK m_data_start+m_data_size EMPTY -Stack_Size { ; Stack region growing down
}
ARM_LIB_STACK
: Fixed name of the execution area of the stack
Stack_Size
There is a in front of it -
, indicating that the stack is growing downward.
11.RW_m_ram_text
: Starting address 0x00000000
, size0x00040000
RW_m_ram_text m_qacode_start m_qacode_size { ;
* (CodeQuickAccess)
}
Similar to 7 above, declare a CodeQuickAccess
segment to link the function to the internal SRAM_ITCM
, because the internal SRAM is much faster than the access speed of NOR Flash or SDRAM.
12.RW_m_ncache
: start address 0x83000000
, size0x01000000-0x400
RW_m_ncache m_ncache_start m_ncache_size - Heap_Size { ; ncache data
* (NonCacheable.init)
* (*NonCacheable)
}
Define the sum non-cacheable
of the two segments of the area NonCacheable.init
and NonCacheable
reserve heap space, because here we assume that the heap space is also non-cacheable
.
13.ARM_LIB_HEAP
: Size0x400
ARM_LIB_HEAP +0 EMPTY Heap_Size { ; Heap region growing up
}
+0
: Represents the end address of the previous execution area.RW_m_ncache
This address is determined based on the number and size of variables placed by the user in the execution area.ARM_LIB_HEAP
: fixed name of the execution region of the heap
Defines the memory of the heap space.
14、RW_m_ncache_unused
RW_m_ncache_unused +0 EMPTY m_ncache_size-ImageLength(RW_m_ncache)-Heap_Size { ; Empty region added for MPU configuration
}
It is also placed at the end address of the previous execution area and is used to configure the MPU. The size is the m_ncache_size-ImageLength(RW_m_ncache)-Heap_Size
remaining non-cacheable
area except variables and heap.
ImageLength
You can take the size of an execution area