JNH官网

【ARM】MDK中分散加载功能的解析和一些应用

一、文档背景

掌握分散加载,如图1所示,可以使JNH官网方便地指定程序代码和变量的存储位置,达到优化性能和降低成本的优势,比如JNH官网想把时间关键代码存放在ITCM RAM里面运行,而占用空间超大又不需要快速运行的代码,JNH官网可以放到QSPI Flash里面,因为QSPI Flash的容量一般都比较大,又或者,将内部Flash和外部的QSPI Flash混合使用。所以,通过学习分散加载,JNH官网可以很方便地实现这些功能,获取到更好的性能和成本优势。

图1

二、分散加载

1、基础知识

(1)、其实在JNH官网建立工程的时候,肯定也是用过分散加载这个功能的,为什么这么说呢,JNH官网打开option选项里的target,如图2所示。图2里对ROM配置还有RAM配置部分的修改,其实本质上就是对分散加载文件的修改。

图2

(2)、分散加载文件在option选项里的Linker选项卡中可以找到,具体操作如图3左侧所示,点击下方的Edit,即可在keil中打开如图3右边所示的分散加载文件。也可以看到,文件中的配置,和图2中target内设置的地址、大小也是一 一对应的,其中每一行的代码含义JNH官网稍后也会一 一进行讲解。然后,如图4所示,改动一下target中的ROM分配,对应的,图4右边的分散加载文件就多出了对应的地址和大小。


图3

图4


(3)、如果用户想自行修改分散加载文件,则需要在option选项里的Linker选项卡中,去掉Use Memory Layout from Target Dialog的勾选,去掉之后,就可以任意指定一个分散加载文件了,但是要注意,指定文件以后,JNH官网Target选项卡中的设置就不再起作用了,具体操作如图5所示。

图5


2、代码解读

图6

(4)、接下来,对图6 中分散加载文件的代码进行逐行解读:

第5行:LR_IROM1 0x08000000 0x00200000 { ; load region size_region

LR是加载域(load region)的缩写,加载域是指编译时程序的实际存储位置。LR_IROM1是加载域的名称,开发者可以自定义。0x08000000 是加载域的起始地址,对应 Flash 的基地址(通常用于存储代码和常量数据)。0x00200000 是加载域的大小(2 MB)。


第6行: ER_IROM1 0x08000000 0x00200000 { ; load address = execution address

ER是执行域(execution region)的缩写,加载地址(Load Address)和执行地址(Execution Address)一致,表明该段数据无需从 Flash 移动到 RAM 才能运行。0x08000000 是执行域的起始地址,与 LR_IROM1 的地址相同,表示代码和常量数据在 Flash 中加载后直接在该地址执行。0x00200000 是执行域的大小(2 MB)。


第7行: *.o (RESET, +First)

*是通配符,*.o 表示所有的目标文件(.o),这行的意思就是说,把所有以.o结尾的目标文件的代码,都放置在这里,但是,有一个条件,就是要把标识为RESET段的代码放置在最前面,放置在最前面的操作,是由+First参数控制的。


这里其实要引申一个知识点,否则基本看不明白这里的操作含义,因为STM32的运行方式是这样的:CPU 要从地址 0x0000 0000 获取栈顶值,然后从始于 0x0000 0004 的自举存储器开始执行代码。前面已经把0x800 0000的FLASH地址配置为启动的存储器,因此就会从这里偏移位置0取得栈顶值,从偏移位置4取得第一行执行代码。根据这个要求,那么每个项目工程的启动文件,必须是这样设置才可以运行。如下图7所示:(DCD 表示分配 1 个 4 字节的空间)

图7

前面说到CPU会从偏移位置0处来加载栈顶值,可以从这里的图6看到,__initial_sp变量被放在第一个位置,意味着这个变量的值会放在这个区域的第一个位置,这样就确保了栈顶值是保存在这段代码布局的首位置了。但是编译器怎么知道这段代码一定会放在存储器的首位置,而不是放置在存储器的后面位置呢?

如果没有的特别的声明,编译器是可能把它放置在任何位置上的,并不是首位置。关键之处,就是sct文件中的*.o (RESET, +First)这一行代码,这里明确地说明,要把RESET的代码放在首位置,就是偏移0的位置。

然后JNH官网回头再看图6,就会发现,第55行早就已经说明,这一段代码区域被命名为RESET了,那么,当编译器看到这个名称时,就会把这段代码放置在存储器的首位置,这样CPU就会从这里获得栈顶值,就可以开始加载代码进行运行了,从偏移4的位置,也就是Reset_Handler的值,把它放到CPU的执行指令寄存器,就进入代码运行了。


第8行: *(InRoot$$Sections)

某些 Arm C 和 C++ 库部分必须放置在根区域中,例如 _main.o、_scatter*.o、_ dc*.o 和 *Region$$Table。此列表可能会因版本而异。链接器可以使用 InRoot$$Sections 自动放置所有这些段,以防未来发生变化。如图8所示:

图8

第9行: .ANY (+RO)

把只读数据(包括代码、常量等)放入执行区域中。(其实就是自动匹配未分配的只读段)


第10行: .ANY (+XO)

把仅执行数据(Executable Only)放入执行区域中。(其实就是自动匹配未分配的仅执行段)


第12行: RW_IRAM1 0x20000000 0x00050000 { ;

RW_IRAM1是读写区域的名称。0x20000000 是 RAM 的基地址,0x00050000 是读写区域的大小(320 KB)。这个区域,用于存放全局变量、堆栈等运行时需要读写的数据。


第13行: .ANY (+RW +ZI)

把所有需要读写的、已初始化全局变量和所有的零初始化变量放在 RW_IRAM1区域。(其实就是自动匹配未分配的读写和零初始化段)


(5)、然后.ANY和星号(*)在使用方面,其实是有一个比较关键的区别的,就是如果指定的区域满了,* 不会自动向下分配,而是报错,.ANY就可以连续向下分配,不报错。具体示例如下图9,JNH官网在sct文件中划分两个RAM区域,第一个RAM区域只分配128KB,并且使用星号来进行匹配。

图9


然后再在程序中,定义一个128KB的数组,如图10所示,数组后面的__attribute__((used))其实是一种:函数或变量属性修饰符,用于确保被标记的函数或变量在程序中不会被优化器错误地移除。



图10


然后,JNH官网观察一下是否可以分配成功,很明显,如图11所示,程序报错了。

报错信息表示,JNH官网的RW_IRAM1被限制为只有131072bytes,也就是128KB,但是程序实际使用了168848bytes,也就是164KB。显而易见,星号(*)无法自动向下分配。


图11


同样的,JNH官网更改星号(*)为.ANY后,同样的代码,就可以完成数据的向下自动分配,而不报错,如图12所示:



图12

3、SCT的应用

(6)、应用①:

在STM32里面,经常会有很多RAM空间,比如AXI SRAM 、SARM 、SDRAM,这三种RAM的访问速度都不太一样,三者的速度排序通常是:AXI SRAM > SRAM > SDRAM。根据他们的特性,工程师往往会有不同的需求,比如在AXI SRAM中存放一些需要高速执行的代码和数据;在SRAM中存放一些代码、堆栈或频繁访问的数据;在SDRAM中适合存放一些大容量数据,但不适合时间敏感的实时应用,比如一些字库数据等。

所以以上这些需求,JNH官网完全可以通过更改分散加载文件,来实现数据存储的指定。如图13所实现的样例,其实就是通过更改图14中的分散加载文件,来实现图13中内存的划分的。

注意,图13的代码中,__attribute__((section (".section_name"))),是用来指定变量或函数的存储位置的,告诉编译器,需要将特定的代码或数据放置到用户定义的内存区域(Section)中。然后SDRAM段那里多用了一个zeroinit参数,且对应的在sct文件中,SDRAM段也有一个UNINIT,这里的作用是,在不掉电复位的场景下,用UNINIT保住sdram段里复位之前的数据,复位后,需要被初始化为0的数据,就用zeroinit初始化为0,实现对 SDRAM 数据的精确控制,这是需要注意的一点。

然后需要注意的是,我这里自定义的区域,都是用的星号(*)作为前缀,而不是.ANY,如图14所示。这个意思其实也很明显,就是想让用户对指定的这些内存,拥有绝对的把控,指定哪些变量在哪,就必须在哪里,不可以自动往下分配,防止把变量存储位置给弄混了,不便于JNH官网后续给不同的代码分配不同的访问速度。

图13

图14


最后JNH官网点击编译一下工程,然后打开map文件,如图15可以看到,map文件中列出了对应的 .RAM_D1、.RAM_D2、.RAM_D3 和 .RAM_SDRAM这些段的定义及内存基地址。如图16可以看到,map文件中展示了变量(如 AXISRAMBuf、D2SRAMBuf、D3SRAMBuf 和 SDRAMSRAMBuf 等)及其所在的内存地址、类型、大小以及对应的段(如 .RAM_D1、.RAM_D2、.RAM_D3 和 .RAM_SDRAM)。在map文件中,这些内存分配结果,都是齐全且显而易见的。


图15



图16


(7)、应用②:可以把一些时间关键代码放置在ITCM RAM里,这个ITCM RAM可以被认为成一个更适合指令的运行RAM空间,速度更快一些,有这么一个优势。

在如下图17中,可以看到手册中规定了,ITCM的位置以及大小,所以JNH官网根据手册上的数据,在keil的Option选项中,把ITCM RAM的空间给定义到里面。(注意,此应用示例,勾选使用了Option选项中linker里的Use Memory Layout from Target Dialog,所以target选项里的RAM定义才会生效。)

图17


如果想要指定时间关键代码,将其放在ITCM RAM中,可以不需要像之前那样,在sct文件里手动添加导入,可以如图18所示,右键工程内部的文件夹,直接对该文件夹下的全部代码进行内存划分。比如直接单独把APP文件夹下的代码数据全部划分到IRAM2[0x0-0xFFFF]中。同理,也可以把BSP文件夹下的代码数据和SEGGER/HardFault文件夹下的代码数据都规划到这里,以提高程序的运行速度,但是要注意的是,这里的ITCM RAM只有64KB,别放超了。



图18

然后JNH官网可以打开对应的sct文件,看一下keil是否已经帮JNH官网把特定的数据放到了ITCM RAM里,如图19所示,可以看到,keil确实帮JNH官网把特定的目标文件的数据,给放入到了ITCM RAM中。



图19

那么接下来,JNH官网再进一步确认一下,当程序运行到特定文件时,他的运行地址是否会切换到ITCM RAM的地址范围呢,如图20所示,当JNH官网从main.c运行到MainRAM.c中时,运行地址确实成功切换了。所以,JNH官网想让时间关键代码运行在更快的RAM中的目标, 也确实实现了。


图20


且如图21所示,在map文件中,IRAM2执行域确实也变成了0x0000 0000开始的地址。


图21


(8)、应用③:JNH官网可以利用内部Flash和外部QSPI Flash,做出一个混合运行的程序。这个应用的优势在于,比如JNH官网的数据量很大,有一些字库、图库之类的,全部存在CPU内部那点Flash上绝对是不现实的,肯定是要放到外部的,比如QSPI Flash中。或者还有一些对执行速度要求不高的代码,也都可以全部放置到QSPI Flash里。因为目前128MB的QSPI Flash还是很常见的,所以对于大多数程序来说,在大小方面已经够了。

然后这个示例,JNH官网也不直接手动更改sct文件了,因为要演示放入到QSPI Flash中的文件会非常多,右键工程内部的文件夹,直接对该文件夹下的全部代码进行划分即可。

首先,JNH官网要把外部Flash定义在keil的target中,这个定义可以参考ST手册中对应内核的内存映射表,确定一下外部存储器应该被定义的地址范围,比如如下图22所示,可以看到QSPI Flash的范围应该是[0x9000 0000-0x9FFF FFFF]。然后由于JNH官网的QSPI Flash只有32MB,所以这里只填入0x9000 0000和0x2000000即可。



图22


然后JNH官网通过右键工程文件夹,来为文件进行批量的划分,把一些对执行速度要求不高的代码,或者字库数据,全部扔到JNH官网的外部存储器即可,如图23所示操作即可。



图23


接下来还是检查一下,打开对应的sct文件、map文件,看一下keil是否已经帮JNH官网把特定的数据放到了QSPI Flash里。如图24所示,没有问题,都已经被自动规划好了。


图24


然后JNH官网可以调试一下,看一下效果,这里需要注意,在调试前,需要在Flash Download里,选择上对应的烧录算法,如图25所示,内部Flash和外部QSPI Flash的两个算法都要加载到软件里来。



图25


进入到调试后可以看到,程序现象也是符合的,如图26,至此,JNH官网就完成了一个内部Flash和外部QSPI Flash混合运行的程序。



图26


三、讨论分析

问①:将时间关键代码放置在ITCM RAM里的优势是什么?

答①:ITCM RAM为指令缓存内存,通常具有极低的访问延迟和较高的带宽,适合存放时间关键代码,如实时操作系统的调度、ISR(中断服务程序)等。


四、结论

在嵌入式开发中,如何合理利用不同类型的内存存储代码是优化性能和降低成本的关键。通过合理配置sct文件,可以实现如本文所描述的:将时间关键代码放置在ITCM中,减小延迟并提高响应速度;而不常用的代码则可以存放在外部SDRAM或QSPI Flash中,既能节省内存空间,又能提高程序的扩展性。

理解这些内存和存储技术的优势和适用场景,可以帮助JNH官网开发者在实际项目中做出更高效的内存管理决策。


jnh官网 jnh官网 jnh官网 jnh官网 金年会 金年会 金年会 金年会