1 前言
在前面一章我们具体介绍了IAR icf链接文件的功能以及相关语法,接下来我们来进行实际操作来证明一下这些操作是确实可用的。下面的例子都是将一些readonly性质的code和data定义到RAM中的操作。
2 相关例子
现在以i.MX RT1050的IAR工程为例来做说明。
在这里我们先贴上RT的存储映射地址,等下会用到。
我们提取出我们接下来用到的地址空间:
起始地址 | 描述 |
---|---|
0x2000 0000 | DTCM (相当于内部SRAM) |
0x6000 0000 | FlexSPI(相当于FLASH) |
0x8000 0000 | SDRAM |
2.1 定义数据不指定位置
在函数中定义一个数组如下:
编译后通过map文件查看其地址:
可以看到my_code的链接地址在0x8000 0000,查看其地址处于外部SDRAM,为什么IAR会把my_code放到了外部SDRAM,这中间发生了什么,让我们看到该项目的链接文件就可以找到答案:
/*定义SDRAM的起始地址和终止地址*/
define symbol m_data3_start = 0x80000000;
define symbol m_data3_end = 0x81DFFFFF;
/*定义SDRAM的region空间,名字为DATA3_region*/
define region DATA3_region = mem:[from m_data3_start to m_data3_end-__size_cstack__];
/*定义ZI block,并将ZI性质的数据放入该block*/
define block ZI with alignment = 32 { first zi, section m_usb_dma_noninit_data };
/*将ZI block放入SDRAM的region空间*/
place in DATA3_region { block ZI };
定义的my_code数组属于zi性质的数据,经过以上的操作,最终my_code被链接到了SDRAM中。
接下来我们加入const关键字改变其性质为readonly,如下:
编译后通过map文件查看其地址:
可以看到my_code的链接地址在0x60000 4074,查看其地址处于FLASH,这中间又发生了什么:
/*定义FLASH区的一段空间的起始地址和终止地址*/
define symbol m_text_start = 0x60002400;
define symbol m_text_end = 0x63FFFFFF;
/*定义位于FLASH的region空间,名字为TEXT_region */
define region TEXT_region = mem:[from m_interrupts_start to m_interrupts_end]
| mem:[from m_text_start to m_text_end];
/*将readonly性质的数据放入TEXT_region区*/
place in TEXT_region { readonly };
定义的my_code数组属于readonly性质的数据,经过以上的操作,最终my_code被链接到了FLASH中。
2.2 定义数组指定位置
接下来我们自定义section的方式来指定数据的链接地址。
使用#pragma关键词自定义Section,如下:
#pragma location = ".my_code_section"
uint8_t my_code[] = "my code\r\n";
在链接文件中使用place at语法放置my_code_section的位置,链接文件代码如下:
/*将.my_code_section section的数据链接到地址0x20200100 */
place at address mem:0x20210000 { section .my_code_section };
编译后通过map文件查看其地址:
可以看到成功的将其链接到了我们想要的地址。
我们再使用建立region的方式来实现,链接文件代码如下:
define region DATA2_region = mem:[from m_data2_start to 0x20210000];
define region my_code_region= mem:[from 0x20210000 to m_data2_end];
place in my_code_region { section .my_code_section };
编译后通过map文件查看其地址:
可以看到这样操作也可以成功的将其链接到了我们想要的地址。
对于函数链接地址的操作同样可以通过以上方法去实现,在这里则不再展开细说,大家可以照着这种方法试试手。(对于函数,如果想指定其在RAM中运行且不指定地址的情况,还有一种更加方便的方法,就是使用__ramfunc关键字对函数进行限定,这样则将该函数归纳在section .textrw中,这样在IAR的初始化函数中则可直接将其拷贝至RAM。)
2.3 批量操作
上面介绍的方法(即使用#pragma的方式)只能一个一个的去定义,这样略显麻烦,如果我想批量操作,该如何去操作呢?
首先将目标文件和数据全部放在一个C文件中,在链接文件中我们则可以使用place in或者place at将这个文件编译后生成的全部section放入指定链接地址。示例链接文件代码如下:
place in my_code_region { readonly object my_code.o };
上面代码的意思就是将my_code.c文件中的readonly性质的section放入my_code_region中,这里我就不举例子说明了。
我们也可以使用initialize by copy,通过IAR的初始化函数在APP运行前将相关code和data拷贝至RAM,所以这种方法适用于将代码链接到RAM,但是不能指定明确的地址,示例链接文件代码如下(强烈推荐使用这种方法,非常简单,好用,还不容易出错):
initialize by copy {readwrite, section .textrw, readonly object my_code.o};
上面代码的意思就是IAR初始化函数会将所有readwrite性质的data,.textrw section的数据以及my_code.c文件中的readonly性质的section拷贝至RAM区。还可以使用这个函数将整个代码链接至RAM中运行。
3 实例操作之链接代码至RAM
接下来我以RT1050的项目来具体实验一下(FLASH启动地址0x60000000, RAM起始地址为0x20000000)
3.1 链接单个section至RAM
首先自定义section:
#pragma location = ".my_code_section"
void my_code_fun(void)
{
printf("hello world.\r\n");
}
再使用initialize by copy将这个section链接至RAM:
initialize by copy { readwrite, section .textrw ,section .my_code_section};
编译后查看map文件:
运行代码查看跳转地址:
3.2 链接单个.o文件至RAM
使用initialize by copy将fsl_gpio.o链接至RAM:
initialize by copy { readwrite, section .textrw, readonly object fsl_gpio.o};
编译后查看map文件发现fsl_gpio.c中的文件全部链接至了RAM中:
运行代码查看跳转地址:
3.3 链接整个代码至RAM
使用以下代码将除了与启动相关的代码(也就是进入main函数之前的代码)之外的所有代码链接至RAM:
initialize by copy { rw, ro}
except {
ro section .boot_hdr.conf,
ro section .boot_hdr.ivt,
ro section .boot_hdr.boot_data,
ro section .boot_hdr.dcd_data,
section .intvec,
section .init_array,
ro object ABImemcpy.o,
ro object startup_MIMXRT1024.o,
ro object system_MIMXRT1024.o
};
编译后查看map文件发现除了没有except里面的部分全部链接至了RAM中:
运行代码查看跳转地址:
(注意1:我们不能将所有的代码都链接至RAM中,换句话说就是有些代码必须得链接至FLASH,因为必须运行完IAR的程序初始化函数__iar_program_start,而这个函数执行了代码拷贝至RAM的工作,所以需要在FLASH中执行完__iar_program_start后,RAM中才会有代码,之后再跳转进入RAM中运行,所有至少需要将__iar_program_start之前包括它的函数链接至FLASH才能保证正常进入main函数。如果想完全将代码放入RAM中运行,则是可以直接将代码下载至RAM中,或者像RT1050这种支持non-XIP启动的MCU,则需要修改头信息,让BootROM实现所有代码的拷贝,类似于non-XIP启动。)
(注意2:如果想在正常启动过程中将中断向量表链接至RAM,则需要注意两个点,第一点就是将中断向量链接至RAM的同时还要保证芯片的正常启动,我们可以通过将与启动相关的部分中断向量表拷贝副本链接至FLASH保证正常启动,再将完整的中断向量链接至RAM。第二点则是需要通过修改SCB->VTOR寄存器改变中断向量映射地址。)
END
番外篇(initialize by copy的实现者)
关于链接文件initialize by copy这个函数,我们可以对其进行深挖一下。
IAR给芯片运行main函数设计了一个入口函数__iar_program_start,该函数在Reset_Handler中被调用,下面是该函数的内部组成:
可以看到其内部调用了__iar_copy_init3 ,该函数就是initialize by copy实现者,目的就是将相关数据拷贝至RAM。
个人猜想:
第一点:
请大家妥善使用关键词ro和rw,因为这两个属性会涵盖一些别的属性的数据,也就是说它是一个大的集合,包含很多section的数据,举个例子:
现使用以下代码将我自定义的section拷贝至RAM中运行:
/*初始化函数中拷贝readwrite, .textrw段和.my_code_section至RAM*/
initialize by copy { readwrite, section .textrw ,section .my_code_section};
但是当同时拥有SDRAM和SRAM的情况下,所以数据将被拷贝在哪里?
答案:是拷贝到你指定rw属性数据所在的RAM区。
再举个例子:
如果使用以下语句将ro属性的数据放入了RAM区,那么IAR就会认为这个区域默认是FLASH。所产生的bin文件的排布也会偏移你的预期。
/*将ro属性的数据放入XXXXX_region*/
place in RAM_region { ro };
总之如果使用place in语句放置ro段和rw段,ro一定要放在FLASH区域, rw一定要放在RAM区。