S32K3学习笔记—链接文件及启动相关实操
一、常用操作
关于启动文件和链接文件一些基本知识,可以参考上一篇文章:S32K3学习笔记—链接文件及启动代码相关
1.关键字 attribute_
可以用_attribute__关键字将函数或者变量重新定义到某个section,这种比较简单,但是有时候也能解决很多的问题。例如:把一个函数放到 ramcode 段可以更快的执行
extern void __attribute__ ((section(".ramcode"))) XX_MainFunction(void);
void __attribute__ ((section(".ramcode"))) XX_MainFunction(void) //set XX_MainFunction to ramcode section
{
...
}
uint8 _attribute_ ((section(".mcal_data_no_cacheable"))) xx_status;//set xx_status to mcal_data_no_cacheable
2.关键字 #pragma GCC section
如果我们有大量的数据或者函数要定义到某个section,一行一行用_attribute__就会比较费劲。那么#pragma GCC section作用就体现了,我们只需要用一次就可以将某部分代码放到某个section。 data、test、bss、rodata都可以用以下方式
//set XX_MainFunction and XXX_MainFunction to ramcode section
#pragma GCC section text "ramcode"
extern void XX_MainFunction(void);
void XXX_MainFunction(void)
#pragma GCC section text "default"
#pragma GCC section text "ramcode"
void XX_MainFunction(void)
{
...
}
void XXX_MainFunction(void)
{
...
}
#pragma GCC section text "default"
//set xx_status and xxx_status to mcal_data_no_cacheable section
#pragma GCC section data "mcal_data_no_cacheable"
extern uint8 xx_status;
extern uint8 xxx_status;
#pragma GCC section text "default"
#pragma GCC section data "mcal_data_no_cacheable"
uint8 xx_status = 0;
uint8 xxx_status = 0;
#pragma GCC section data "default"
3.将中断向量表放到DTCM
为了更好的性能,有时堆栈可以从SRAM重定位到DTCM。为了更快的响应中断,我们可以把放到SRAM的中断向量表定义到DTCM中,只需要将之前的中断向量表从SRAM移动到DTCM中就行。需要注意的是中断向量表的起始地址需要对齐4096字节。
/*Divide 4K and use it for the interrupt vector table */
int_dtcm : ORIGIN = 0x20000000, LENGTH = 0x0010000 - 0x00001000 /* 60KB + int_stack_dtcm_c0*/
int_stack_dtcm_c0 : ORIGIN = 0x2000F000 - 0x00001000, LENGTH = 0x00001000 /* 4KB*/
/*change SRAM to DTCM,int_stack_dtcm_c0 will be assiggned to the MSP in the startup code*/
__Stack_dtcm_end = ORIGIN(int_stack_dtcm_c0);
__Stack_dtcm_start = ORIGIN(int_stack_dtcm_c0) + LENGTH(int_stack_dtcm_c0);
/*update the boundary for DTCM end address */
__INT_DTCM_START = ORIGIN(int_dtcm);
__INT_DTCM_END = ORIGIN(int_dtcm) + LENGTH(int_dtcm) + LENGTH(int_stack_dtcm_c0);
/*define the section*/
.int_vector :
{
. = ALIGN(4096);
__interrupts_ram_start = .;
. += __interrupts_init_end - __interrupts_init_start;
. = ALIGN(4);
__interrupts_ram_end = .;
} > int_dtcm
4.将代码放到ITCM
ITCM是零等待内存,CPU访问的时间比flash和SRAM更快,如果要使用ITCM,可以参考如下步骤:
在链接文件在定义ITCM section,其默认就定义了int_itcm,如下,因此我们关键字_attribute__ 将需要放的代码定义到ITCM中
__tcm_code_rom_start = __tcm_data_rom_end;
.itcm_text : AT(__tcm_code_rom_start)
{
. = ALIGN(4);
__itcm_start__ = .;
*(.itcm_text*)
. = ALIGN(4);
__itcm_end__ = .;
} > int_itcm
__tcm_code_rom_end = __tcm_code_rom_start + (__itcm_end__ - __itcm_start__);
默认的链接文件中没有定义ARM、EABI、GUN预留section位置,可以将他们添加到pflash中避免ITCM/DTCM或data section重叠
ARM.exidx:
{
*(.ARM.exidx*)
*(.gnu.linkonce.armexidx.*)
*(.glue*)
*(.vfp11*)
*(.v4*)
*(.iplt*)
*(.rel*)
) > int flash
链接文件在定义从flash加载到ITCM的地址
__RAM_ITCM_START = __itcm_start__;
__ROM_ITCM_START = __tcm_code_rom_start;
__ROM_ITCM_END = __tcm_code_rom_end;
在启动文件中将 _RAM_ITCM_START、_ROM_ITCM_START、_ROM_ITCM_END定义到init_table,将长度增加一个。
.section ".init_table", "a"
.long n+1
...
.long __RAM_ITCM_START
.long __ROM_ITCM_START
.long __ROM_ITCM_END
最后我们只需要用关键字_attribute__ 将需要放的代码定义到itcm_text中
void __attribute__ ((section(".itcm_text"))) XX_MainFunction(void) //set XXX to itcm_text section
{
...
}
5.将数据放到DTCM
DTCM和ITCM类似,但是专门用于数据。将data存放在DTCM,有以下操作
定义DTCM section,数据段有初始值和没有初始值两种类型,因此定义两个段 .data_tcm_data 和 .bss_tcm_data
__tcm_data_rom = __shareable_data_rom_end;
.data_tcm_data : AT(__tcm_data_rom)
{
. = ALIGN(4);
__dtcm_data_start__ = .;
*(.dtcm_data*)
. = ALIGN(4);
__dtcm_data_end__ = .;
} > int_dtcm
__tcm_data_rom_end = __tcm_data_rom + (__dtcm_data_end__ - __dtcm_data_start__);
.bss_tcm_data (NOLOAD) :
{
. = ALIGN(4);
__dtcm_bss_start__ = .;
*(.dtcm_bss*)
. = ALIGN(4);
__dtcm_bss_end__ = .;
} > int_dtcm
__tcm_code_rom_start = __tcm_data_rom_end;
在链接文件中定义加载到DTCM的地址
__RAM_DTCM_DATA_START = __dtcm_data_start__;
__ROM_DTCM_DATA_START = __tcm_data_rom;
__ROM_DTCM_END = __tcm_data_rom_end;
__BSS_DTCM_START = __dtcm_bss_start__;
__BSS_DTCM_SIZE = __dtcm_bss_end__ - __dtcm_bss_start__;
__BSS_DTCM_END = __dtcm_bss_end__;
在启动文件中将新增的添加到.init_table 和 .zero_table
.section ".init_table", "a"
.long n+1
...
.long __RAM_DTCM_DATA_START
.long __ROM_DTCM_DATA_START
.long __ROM_DTCM_END
.section ".zero_table", "a"
.long n+1
...
.long __BSS_DTCM_START
.long __BSS_DTCM_END
最后我们只需要用关键字_attribute__ 将需要放的代码定义到.dtcm_bss 和.dtcm_data中
uint8 _attribute__ ((section(".dtcm_bss"))) gPdxx;
uint8 _attribute__ ((section(".dtcm_data"))) gPdxx = 8;
6.链接二进制文件
某些情况下,我们可能会想把当前已有的二进制文件连接到当前的工程中,比如当我们在装HSE的核心固件的时候
int_pflash1 (R) : ORIGIN = 0x00600000, LENGTH = 0x00100000 /*Area for storage*/
TARGET(binary) /*指定二进制的格式*/
INPUT(路径...) /*文件的路径*/
OUTPUT_FORMAT(default)/*恢复文件输出格式*/
/*define the section*/
.int_pflash1 :
{
.= ALIGN(0X4);
_int_pflash1_start_ = .;
路径...(.data)
. = ALIGN(0X4);
_int_pflash1_end_ = .;
}
_int_pflash1_START_ = ORIGIN(int_pflash1);
_int_pflash1_SIZE_ = _int_pflash1_end_ - _int_pflash1_start_;
7.将数据储存在Dflash
S32K3x4 有128K的dflash,但HSE用最后的40KB,因此我们有88K可使用。
int_dflash : ORIGIN = 0x10000000, LENGTH = 0x00016000 /* 128KB - 40KB = 88K*/
/*define the section*/
.data_flash :
{
. = ALIGN(4);
*(.dflash)
} > int_dflash
uint8 __attribute__ ((section(".dflash"))) test_dflash[] = "dflash test" /*use the __attribute__ to relocate he variable in dflash*/
8.将数据放到 standby RAM
在standby模式下,只有standby RAM中的数据可以保留,S32K324有32KB的standby RAM。当前的链接文件将其作为普通的SRAM使用,如果我们需要使用,就需要新建一个单独的区域。此过程比较复杂,需要修改的地方比较多,链接文件和启动文件需要修改,此外,基于该段的特殊性,ECC初始化那块也需要保留数据也需要做一些修改,对于.bss和.data处理也会有差异,其初始化也需要重新做,工作量很大,等遇到实际需求再去研究这块。
二、实操
本文是基于S32K324官网demo修改一个双核双中断向量表单elf的工程,由于项目需要,要求每个核有自己的中断向量表且每个核的中断优先级是可以重复的,可能就会出现我们需要将S32K3双中断向量表的形式,以下基于RTD3.0做的简单修改。
1.链接文件的修改
根据上述的几种常用的操作,为了更快的响应中断,我们将中断向量表放在DTCM中。由于是想做两个中断向量表,将DTCM分出2个4K用于存放中断向量表,具体操作如下:
int_dtcm : ORIGIN = 0x20000000, LENGTH = 0x0000E000 /* 56KB */ /*64K include int_dtcm、int_stack_dtcm_c0、int_stack_dtcm_c1*/
int_stack_dtcm_c0 : ORIGIN = 0x2000E000, LENGTH = 0x00001000 /* 4KB */
int_stack_dtcm_c1 : ORIGIN = 0x2000F000, LENGTH = 0x00001000 /* 4KB */
相应的pflah section也需要做简单的修改
.pflash :
{
KEEP(*(.boot_header))
. = ALIGN(4096);
__interrupts_rom_start_c0 = .;
KEEP(*(.intc_vector_c0))
. = ALIGN(4);
__interrupts_rom_end_c0 = .;
. = ALIGN(4096);
__interrupts_rom_start_c1 = .;
KEEP(*(.intc_vector_c1))
. = ALIGN(4);
__interrupts_rom_end_c1 = .;
...
}
还需要定义一个section 加载中断向量表,需要注意的是中断向量表的起始地址需要对齐4096字节
.int_vector :
{
. = ALIGN(4096);
__interrupts_ram_start_c0 = .;
. += __interrupts_rom_end_c0 - __interrupts_rom_start_c0;
. = ALIGN(4);
__interrupts_ram_end_c0 = .;
. = ALIGN(4096);
__interrupts_ram_start_c1 = .;
. += __interrupts_rom_end_c1 - __interrupts_rom_start_c1;
. = ALIGN(4);
__interrupts_ram_end_c1 = .;
} > int_dtcm
将SRAM更改为DTCM,int_stack_dtcm_c0、int_stack_dtcm_c1将在启动代码中分配给MSP,且重新定义DTCM的边界
/*change SRAM to DTCM,int_stack_dtcm_c0 and int_stack_dtcm_c1 will be assiggned to the MSP in the startup code*/
__Stack_end_c0 = ORIGIN(int_stack_dtcm_c0);
__Stack_start_c0 = ORIGIN(int_stack_dtcm_c0) + LENGTH(int_stack_dtcm_c0);
__Stack_end_c1 = ORIGIN(int_stack_dtcm_c1);
__Stack_start_c1 = ORIGIN(int_stack_dtcm_c1) + LENGTH(int_stack_dtcm_c1);
/*update the boundary for DTCM end address for ECC initialization */
__INT_DTCM_START = ORIGIN(int_dtcm);
__INT_DTCM_END = ORIGIN(int_dtcm) + LENGTH(int_dtcm) + LENGTH(int_stack_dtcm_c0) + LENGTH(int_stack_dtcm_c1);
重新定义sections的地址,在内存初始化的启动代码所引用
__RAM_INTERRUPT_START_C0 = __interrupts_ram_start_c0;
__ROM_INTERRUPT_START_C0 = __interrupts_rom_start_c0;
__ROM_INTERRUPT_END_C0 = __interrupts_rom_end_c0;
__RAM_INTERRUPT_START_C1 = __interrupts_ram_start_c1;
__ROM_INTERRUPT_START_C1 = __interrupts_rom_start_c1;
__ROM_INTERRUPT_END_C1 = __interrupts_rom_end_c1;
__ENTRY_VTABLE_C0 = __ROM_INTERRUPT_START_C0;
__ENTRY_VTABLE_C1 = __ROM_INTERRUPT_START_C1;
__CORE0_VTOR = __interrupts_rom_start_c0;
__CORE1_VTOR = __interrupts_rom_start_c1;
__CORE2_VTOR = __interrupts_rom_start_c1;/*for s32K324, it's a pseudo value*/
2.中断向量表的修改
其实中断都是用的一套,按照正常的理解,多核每个都有一个自己的SysTick_Handler,但是ARM决定了只有一个 SysTick_Handler,为了区分不同核用不同的SysTick_Handler,因此只能用两个中断向量表修改如下:
/* Vector_Table_C0 */
.section ".intc_vector_c0","ax"
.align 2
.thumb
.globl undefined_handler
.globl undefined_handler
.globl VTABLE_C0
.globl _core_loop
.globl __Stack_start_c0 /* Top of Stack for Initial Stack Pointer */
.globl Reset_Handler /* Reset Handler */
.globl NMI_Handler /* NMI Handler */
.globl HardFault_Handler /* Hard Fault Handler */
.globl MemManage_Handler /* Reserved */
.globl BusFault_Handler /* Bus Fault Handler */
.globl UsageFault_Handler /* Usage Fault Handler */
.globl SVC_Handler /* SVCall Handler */
.globl DebugMon_Handler /* Debug Monitor Handler */
.globl PendSV_Handler /* PendSV Handler */
.globl SysTick_Handler /* SysTick Handler */ /* 15*/
VTABLE_C0:
.long __Stack_start_c0 /* Top of Stack for Initial Stack Pointer */
...
.size VTABLE_C0, . - VTABLE_C0
/* Vector_Table_C1 */
.section ".intc_vector_c1","ax"
.align 2
.thumb
.globl undefined_handler
.globl undefined_handler
.globl VTABLE_C1
.globl _core_loop
.globl __Stack_start_c1 /* Top of Stack for Initial Stack Pointer */
.globl Reset_Handler /* Reset Handler */
.globl NMI_Handler /* NMI Handler */
.globl HardFault_Handler /* Hard Fault Handler */
.globl MemManage_Handler /* Reserved */
.globl BusFault_Handler /* Bus Fault Handler */
.globl UsageFault_Handler /* Usage Fault Handler */
.globl SVC_Handler /* SVCall Handler */
.globl DebugMon_Handler /* Debug Monitor Handler */
.globl PendSV_Handler /* PendSV Handler */
.globl SysTick_Handler /* SysTick Handler */ /* 15*/
VTABLE_C1:
.long __Stack_start_c1 /* Top of Stack for Initial Stack Pointer */
...
.size VTABLE_C1, . - VTABLE_C1
通过观察发现两个向量表都是一样的,感觉不是特殊的要求一般也不会选择加一个向量表,虽然这种比较直观,能比较直观的区分两个核,但是比较浪费内存,当然这些都需要看需求
3.启动代码的修改
在启动文件中将变化的添加到.init_table
.section ".init_table", "a"
.long 7
...
.long __RAM_INTERRUPT_START_C0
.long __ROM_INTERRUPT_START_C0
.long __ROM_INTERRUPT_END_C0
.long __RAM_INTERRUPT_START_C1
.long __ROM_INTERRUPT_START_C1
.long __ROM_INTERRUPT_END_C1
...
加载中断向量表到RAM
.globl VTABLE_C0 /* interrupt vector table of Core0 */
.globl VTABLE_C1 /* interrupt vector table of Core1 */
SetVTOR:
/*GetCoreID*/
ldr r0, =0x40260004
ldr r1,[r0]
ldr r0, =MAIN_CORE
cmp r1,r0
beq SetVTOR_C0
b SetVTOR_C1
SetVTOR_C0:
/* relocate vector table to RAM */
ldr r0, =VTOR_REG
ldr r1, =__RAM_INTERRUPT_START_C0
str r1,[r0]
SetVTOR_C1:
/* relocate vector table to RAM */
ldr r0, =VTOR_REG
ldr r1, =__RAM_INTERRUPT_START_C1
str r1,[r0]
将两个中断向量表分别给MSP
/* Enable TCM and Disable RETEN bit */
LDR r1, =CM7_DTCMCR
LDR r0, [r1]
bic r0, r0, #0x4
orr r0, r0, #0x1
str r0, [r1]
/* Enable TCM and Disable RETEN bit */
LDR r1, =CM7_ITCMCR
LDR r0, [r1]
bic r0, r0, #0x4
orr r0, r0, #0x1
str r0, [r1]
/*GetCoreID*/
ldr r0, =0x40260004
ldr r1,[r0]
ldr r0, =MAIN_CORE
cmp r1,r0
beq SetCore0Stack
b SetCore1Stack
SetCore0Stack:
/* set up stack; r13 SP*/
ldr r0, =__Stack_start_c0
msr MSP, r0
b DisableSWT0
SetCore1Stack:
/* set up stack; r13 SP*/
ldr r0, =__Stack_start_c1
msr MSP, r0
#ifdef RAM_DATA_INIT_ON_ALL_CORES
b RamInit
#else
b DTCM_Init /* SWT1 clock is disabled at startup */
#endif
根据核ID进入不同的main_cx
_MAIN:
cpsie i
bl startup_go_to_user_mode
_GoMain:
/*GetCoreID*/
ldr r0, =0x40260004
ldr r1,[r0]
ldr r0, =MAIN_CORE
cmp r1,r0
beq main_c0
b main_c1
总结下,其实startup_cm7.s文件中修改的比较简单,都是根据ID来做区分,之后将中断向量表分别加载,且分别给到MSP,最后进入不同的main。上面一些常用操作支持我们干很多事情了,后续有需要再继续学习。