ARM Cortex-M嵌入式C基础编程(下)

ARM Cortex-M嵌入式C基础编程(下)

ARM Cortex-M Embedded C Fundamentals/Tutorial
-Aviral Mittal

Load Region Vs Execution Region:

现在这意味着代码或代码的某些部分可能有不同的地址用于将它们加载到存储器中,例如,当它们加载到ROM中时,以及当它们被执行时。加载程序的地址称为其“加载地址”,执行程序的地址称为其执行地址。             

现在很明显,如果程序的加载地址或程序的某一部分的加载地址与它们各自的执行地址不同,则必须将代码或代码的某一部分“重新定位”到不同的地址,即从其“加载地址”到“执行地址”             

函数\uuu scatterload正是这样做的。             

除了将代码或部分代码从加载区域重新定位到执行区域之外,此函数还将对某些区域(如堆栈区域)进行初始化。堆栈区域通常初始化为零,因为用于为堆栈内存保留空间的编译器指令“SPACE”也会导致其初始化为零。             

虽然Keil通常会自动为用户插入所有的后台代码,以便重新定位代码,但它也允许用户定义称为散点加载文件的特定文件来定义不同的内存区域。但是,这超出了本文的范围。这里显示了散点加载文件的示例。使用自定义散点加载文件,用户可以有效地将代码和数据“散点”到内存中的多个区域。             

这现在提出了一个有趣的观点:如果系统RAM不可用怎么办?在系统通电时,系统RAM很可能是可用的。XIP是这个场景的答案。             

用户可以使用自己的“入口点”绕过默认执行流。这可以通过设置链接器选项“--entry My_Reset_Handler”来完成。             

另一种方法是使用汇编指令'ENTRY'。

随机信息:             

----------------             

实际上,程序的完整执行顺序如下:             

1. 堆栈指针从0x0000的内存内容加载              

2. 处理器的程序计数器加载到Reset_Handler的位置,该位置将出现在内存位置0x0000_0004             

3. Reset_Handler只不过是一个跳转到__main的程序(这是因为在startup.s文件中,Reset_Handler已被编码为这样做)
4.   __main calls __scatterload; Program jumps to __scatterload
5.   __scatterload -> Initialization, Zero Initialization regions to 0, load region relocation to execution addresses
6.   __scatterload_null
7.   __scatterload_zero_init
8.   __rt_entry
9.   __user_setup_stackheap (optional)
10. __user_libspace
11. __rt_entry_li 
12. __rt_lib_init
11. main() -> User Code
12. __rt_lib_shutdown

上面编写的C程序包含应用程序代码和数据常量。当应用程序代码和数据的编译版本被放入微控制器的存储器中时,它可以被放入存储器的“根区域”或“非根区域”。根区域具有相同的加载时间和执行时间地址。非根区域具有不同的加载时间和执行时间地址。根区域包含ARM输出的区域表链接器region表包含需要初始化的非根代码和数据区域的地址。             

但是根区域是什么?             

根区域是内存空间中的一个区域,其中程序的“加载地址”只是其“执行地址”。             

但是什么是“加载地址”和什么是“执行地址”?             

加载地址只是用户代码将驻留在内存中的位置。例如,闪存或ROM或RAM。             

但是,有时程序不能从它在内存中的位置“执行”,但在执行之前,必须将它重新定位到执行它的位置的内存区域中。这是“执行地址”。

Load Address Vs Execution Address: Why these are different at all?

原因可能有很多,其中一个解释如下:             

考虑一个例子,其中用户代码驻留在闪存中,并且由于闪存访问速度慢,因此可能需要从更靠近处理器的RAM执行代码以进行快速处理。存储程序的闪速存储器位置将是其“加载地址”,而复制并最终执行程序的RAM存储器位置将是其“执行地址”。             

在任何嵌入式产品中,软件都很常见地驻留在非易失性存储器(如闪存)上,并在执行前将代码从闪存复制到RAM。             

区域表还包含一个函数指针,该指针指示区域需要什么样的初始化,例如复制、归零或解压缩函数。             

__scatterload遍历region表并初始化各个执行时间区域。功能:             

将零初始化零,初始化(ZI)区域为零             

将非根代码和数据区域从其加载时间位置复制或解压缩到执行时间区域。             

__main总是在启动时调用此函数,然后再调用__rt_entry。

What is Scatter-Loading?

散点加载是使用户能够使用文本描述指定已编译二进制文件到链接器的内存映射的过程。             

这是一种控制二进制文件的组件在内存映射中的位置的方法。             

单词“scatter”的来源是这样一个事实:如果一个程序相当复杂,需要将编译后的代码的几个区域放在内存的几个区域中,那么它实际上是“scatter”在内存中。因此,这种机制通常适用于复杂程序,但也同样适用于简单程序。

As per Joseph Yui:

不同的开发工具有不同的方式来指定微控制器系统中程序和数据存储器的布局。在ARM工具链中,可以使用名为散点加载文件的文件类型,或者在Keil MDK-ARM的情况下,散点加载文件可以由mVision开发环境自动生成。

When to use Scatter-Loding?

根据ARM文档:             

实现嵌入式系统通常需要分散加载,因为它们使用ROM、RAM和内存映射外设。             

需要或非常有用分散加载的情况:

Complex memory maps 

必须放在许多不同内存区域中的代码和数据需要详细说明将数据段放在内存空间的何处。

不同类型的存储             

许多系统包含各种物理内存设备,如flash、ROM、SDRAM和fast SRAM。散点加载描述可以将代码和数据与最合适的内存类型相匹配。例如,可以将中断代码放入快速SRAM中以提高中断响应时间,但可能会将不经常使用的配置信息放入较慢的闪存中。             

内存映射外设             

散点加载描述可以将数据部分放在内存映射中的精确地址,以便可以访问内存映射的外围设备。             

在固定位置的函数             

即使修改并重新编译了周围的应用程序,也可以将函数放在内存中的同一位置。这对于跳转表的实现很有用。             

使用符号标识堆和堆栈             

当应用程序链接时,可以为堆和堆栈位置定义符号。

What is a debug-adaptor: 

为了将程序代码下载到微控制器,并执行诸如暂停和单步执行等调试操作,您可能需要调试适配器将PC上的USB连接转换为微控制器使用的调试通信协议。大多数C编译器供应商都有自己的调试适配器产品。例如,Keil有ULINK产品系列,IAR提供I-Jet产品。             

一些开发包已经在板上内置了USB调试适配器。

软件设备驱动程序:             

用于读/写外设寄存器的软件。             

目标对象文件:             

在大多数情况下,项目包含许多单独编译的文件。编译过程完成后,每个源文件都会有一个对应的目标文件。为了生成最终组合的可执行映像,需要单独的链接过程。在链接阶段之后,IDE还可以生成其他文件格式的程序图像,以便将图像编程到设备。             

闪存编程:             

几乎所有的Cortex -M微控制器都使用闪存来存储程序。创建程序图像后,我们需要将程序下载到微控制器的闪存中。要做到这一点,你需要一个调试适配器,如果你使用的微控制器板没有一个内置。实际的flash编程过程可能相当复杂,但这些通常由IDE完全处理,您只需单击鼠标即可执行整个编程过程。注意,如果您愿意,也可以将应用程序下载到SRAM并从中执行它们             

执行程序并调试:             

编译好的程序下载到微控制器后,您可以运行该程序并查看它是否工作。您可以使用IDE中的调试环境来停止处理器(通常称为halt),并检查系统的状态以确保其正常工作。如果它不能正常工作,您可以使用各种调试功能(如单步执行)详细检查程序操作。所有这些操作都需要一个调试适配器(或者一个内置在开发工具包中的适配器,如果有的话)来连接正在测试的IDE和微控制器。如果发现一个软件错误,那么您可以编辑程序代码,重新编译项目,将代码下载到微控制器,然后再次测试             

在编译程序的执行过程中,可以通过各种I/O机制(如UART接口或LCD模块)输出信息来检查程序的执行状态和结果。本书中的一些例子将展示如何实现其中一些方法。

上面的流程与基于GNU'gcc'的流程不同,它看起来如下:

主要区别在于连接过程。基于GNU的过程是单步的,而第一步有单独的链接过程。

posted @ 2020-06-24 16:03  吴建明wujianming  阅读(652)  评论(0编辑  收藏  举报