本帖最后由 luobeihai 于 2023-8-9 09:13 编辑
#申请原创# @21小跑堂
在有些情况下,我们想要把代码放到SDRAM运行。下面介绍在APM32的MCU中,如何把代码重定位到SDRAM运行。对于不同APM32系列的MCU,方法都是一样的。 1. APM32启动模式熟悉STM32的MCU都知道,可以通过配置 BOOT0/1 两个引脚的高低电平,选择不同的启动方式。对于APM32来说也是一样的,可以配置 BOOT0/1 引脚电平选择不同的启动模式,下表是我从APM32的用户手册截图的启动模式配置表: 有内置SRAM、Flash、系统存储区启动3种模式。对于让程序重定位到SDRAM运行,我们选择Flash启动即可。 不同的启动模式,本质其实就是MCU上电时,从哪个地址处读取出数据然后赋值给PC寄存器,以及SP寄存器。比如选择从Flash启动,MCU上电时,0x08000000处起始的4个字节的数据,赋值给SP寄存器,08000004处起始的4个字节的数据赋值给PC寄存器。设置了SP和PC寄存器,程序就可以正常运行了。 MCU上电,PC寄存器从哪里开始取值,和后面要讲的把代码搬运到SDRAM运行,是有关联的,所以这里提一下APM32的启动模式。 2. 程序段概念的引入一个程序的源码被编译之后,链接器会根据代码中的不同属性,把他们划分为一个个不同的段,比如 .text段、.rodata段、.data段、.bss/.zi段等等,还有用户也可以自定义一些段,比如把所有初始化的代码,自定义一个初始化段。 .text段:代码段或者文本段。我们编写的代码,链接器都是归类在这个段的。对于MCU来说就是存放在内部的Flash中。 .rodata段:只读数据段。比如我们使用const定义的变量,或者定义的字符串这些,都被链接到只读数据段。只读数据段和代码段,都是只能读不能写,所以都是存放在Flash中的。 .data段:可读可写的数据段。我们定义的初始化为非0的全局变量、非0的静态局部变量,都是存放在这个段的。 .bss/.zi段:.bss段或者.zi段,都是同一个段,只是叫法不一样。.bss段和.data段是一样的,都是可读可写的数据段的一种。.bss段存放的就是初始值为0的全局变量或者初始值为0的静态局部变量。 既然.data段和.bss段存放的内容基本一样,为什么要把这两个段分开存放?这是因为.bss段的初值是0,不需要烧录到Flash里面存放,在程序使用之前,我们把.bss段的对应区域给清0就行了,这样不需要浪费Flash空间。 堆:一段空闲的内存空间,可以给程序员自由使用。可以通过一些内存管理接口函数进行申请和释放。 栈:也是一块内存空间,不过程序自行管理。C语言的运行需要栈,MCU上电时就需要把SP(栈)寄存器指向一片正常可用的RAM作为栈来使用。
用户如果有需要,也可以自定义自己的段,然后链接器会根据用户的要求,把指定的代码编译到自定义的段。 我们要把代码重定位到SDRAM运行,本质就是复制这些段的数据,把这些数据复制到它们应该位于的地方(代码应该要位于链接地址处运行)。 3. keil散列文件语法分析前面提到,代码的重定位,本质就是数据的复制。数据的复制有3个要点要知道:从那里复制(源地址)、复制到哪里去(目的地址)、复制多长(长度)。 源地址:我们要从哪里开始复制数据呢?其实就是加载地址,我们程序烧录到哪个位置,那就是加载地址。比如程序烧写到内部的Flash中,那么加载地址就是0x08000000 。程序存放到外部的SPI Flash中,加载地址就是存放在外部SPI Flash的地址。 目的地址:要把程序复制到这里的地址,就是程序的链接地址,程序的运行就是要位于它的链接地址处运行(当然如果写的所有代码都是位置无关码,那么可以不在链接地址处运行)。 长度:复制多长。
上面提到的这些复制数据所需的信息,所有的这一切都可以从链接脚本中获取到,它是用来指导链接器如何进行链接的一种规则文件。对于Keil来说,链接脚本指的就是散列文件。 3.1 Keil默认的散列文件示例下面的代码是keil自动生成的APM32F407ZG型号的散列文件: - ; *************************************************************
- ; *** Scatter-Loading Description File generated by uVision ***
- ; *************************************************************
- LR_IROM1 0x08000000 0x00100000 { ; load region size_region
- ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
- *.o (RESET, +First)
- *(InRoot$Sections)
- .ANY (+RO)
- .ANY (+XO)
- }
- RW_IRAM1 0x20000000 0x00020000 { ; RW data
- .ANY (+RW +ZI)
- }
- }
一个散列文件,是由多个加载域、执行域和输入段所组成。可以通过Keil帮助文档获得这些内容的讲解。 3.2 散列文件语法上面的示例,每个数据、符号代表什么意义?这些内容我们可以通过Keil的帮助文档学习到,安装了Keil软件,就可以获取到帮助文档信息了。 打开链接相关的文档,找到散列文件的语法介绍。 下图就是帮助文档关于散列文件的组成结构: 根据上图:一个散列文件可以一个或多个加载域,每个加载域又可以包含多个可执行域,而可执行域是由各个段(如代码段、数据段等)组成的。 3.2.1 加载域语法- load_region_description ::=
- load_region_name ( base_address | ("+" offset )) [ attribute_list ] [ max_size ]
- "{"
- execution_region_description +
- "}"
load_region_name就是加载域的名称,base_address加载域的基地址,加载域的长度(就是整个程序烧录的大小)。然后加载域里面包含一个或多个可执行域。 以上面的示例为例: - LR_IROM1 0x08000000 0x00100000 { ; load region size_region
- }
加载域名称就是 LR_IROM1,起始地址0x08000000,长度0x00100000。 3.2.2 可执行域语法- execution_region_description ::=exec_region_name ( base_address | "+" offset ) [ attribute_list ] [ max_size | length ]"{"input_section_description *"}"
和加载域的描述也是类似的。然后可执行域里面就是由各个段组成的。 3.2.3 输入段输入段描述如下: 前面是选择代码的哪些区域空间链接进这个段,后面是段的名字。 比如:main.o(+RO) 就是说main.o文件链接到RO段。 常见段类型的解析: *.o (RESET, +First) :指的是所有的.o文件的RESET段,+First就是要求RESET段要链接到程序最开始的地方。 *(InRoot$$Sections):链接器去链接Keil自带的一部分代码。这部分代码的作用主要是数据段的重定位和清除bss段 .ANY (+RO):.ANY作用和 * 一样,指的是所有的意思,但是优先级比 * 低。这里说是把所有文件的RO段(Read Only段)放到这里。 .ANY (+XO):所有的 execute-only 段。
3.3 如何通过散列文件获取源、目的、长度我们学习散列文件的目的就是为了得到代码重定位的源(加载地址)、目的(链接地址)、和长度。那么我们如何通过散列文件获取这些信息? Keil的链接器定义了各种符号,通过这些符号我们可以获取到这些信息。 3.3.1 可执行域区域信息通过可执行域的这些符号,我们就知道把代码复制到哪里去了。 3.3.2 加载域区域信息通过加载域的这些信息,可以获取到各个段(代码段、数据段、bss段)的起始地址,和长度信息。 4. 代码重定位到SDRAM的方法我们为什么要把代码重定位到SDRAM运行? 一般有两种情况: 内部Flash空间不足,不能存放下所有的代码。这个时候我们就只能把编译得到的bin文件烧录到外部的存储设备了,比如SPI Flash等。但是SPI Flash根本就不能运行代码,所以MCU上电后就需要把SPI Flash的bin文件,搬运到SDRAM或者其他可运行代码的存储设备。 为了得到更快的执行速率,这个时候我们可以把代码搬运到SRAM或者SDRAM执行。(但是对于MCU来说,我不确定是内部的Flash执行代码更快还是SDRAM更快)
对于第一种情况,是很常用的。比如嵌入式Linux的设备,就是这种启动方式,内核镜像存储在外部EMMC、SD卡等这种大容量设备中,但是他们都无法执行代码,所以上电后会把内核镜像搬运到内存中运行。 根据这两种情况,我们可以有两种方法把代码搬运到SDRAM运行。 应用程序自己复制自己 通过Bootloader程序,复制应用程序
4.1 程序自己复制自己当整个应用程序都烧写在MCU的内部Flash时,这个时候我们可以使用这种方法,让应用程序自己复制自己到内存中,比如SDRAM运行。 当然在这种情况中,我好像想不到把程序复制到SDRAM运行的意义。是为了获得更快的代码运行速度?但是我也不确定程序在内部Flash运行更快还是SDRAM运行更快? 可能唯一的意义可以就是可以学习到代码重定位相关的知识了吧...... 废话少说,程序它为什么可以自己复制自己? 我们前面介绍过,程序运行应该位于它的链接地址上,但是当这个程序的所有代码都是位置无关码的时候,它就可以不在链接地址上运行。位置无关码就是这段代码可以在任何地址上正常运行,它与执行的位置无关。 位置无关码编写要求:
|