ARM7的Bootloader和分散加载文件笔记

ARM7的Bootloader和分散加载文件笔记
ARM7的Bootloader和分散加载文件笔记

Boot Loader概述

简单地说,在操作系统内核运行之前,通过一小程序,可以初始化硬件设备、建立内存空间的映射图等,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核配置好相应的环境,也可以下载文件到系统板上的SDRAM,对Flash进行擦除与编程,这个小程序一般称为Boot Loader。可以说,一个功能完善的Boot Loader已经相当于一个微型的操作系统了。

Boot Loader作为系统复位或上电后首先运行的代码,一般应写入Flash存储器并从起始物理地址0x0开始。Boot Loader是非常依赖于硬件而实现的,而且根据实现的功能不同,其复杂程度也各不相同。一个简单的Boot Loader可以只完成USB口的初始化,而功能完善的Boot Loader可以支持比较复杂的命令集,对系统的软硬件资源进行合理的配置与管理。因此,建立一个通用的Boot Loader 几乎是不可能的。

系统初始化代码直接对ARM微处理器内核及硬件控制器编程,多采用汇编语言编程,初始化代码一般应包括如下典型任务:

1.定义程序入口点;

2.设置异常和中断向量表;

3.初始化存储设备;

4.初始化堆栈指针寄存器;

5.初始化用户执行环境;

6.呼叫主应用程序。

1.1 定义程序入口

初始化代码必须定义整个程序的入口点。通过伪指令Entry指定编译器保留该段代码,同时配合链接器的设置,确定整个程序的入口点。

1.2 设置异常和中断向量表

1.3初始化存储设备

1. 存储器类型和时序的配置

2.存储器的地址分配与地址重映射

一种典型的存储器地址重映射过程描述如下:当系统上电或复位以后,PC指针指向0x0,程序从0x0地址开始执行,因此,为了能正确读取代码,要求此时Flash (或其它类型的ROM)的起始地址为0x0。但Flash(或其它类型的ROM)的访问速度大大低于RAM,每次产生异常后,都要从Flash(或其它类型的ROM)的异常向量表调转到相应的处理程序,会影响异常的响应速度,因此,系统便提供一种灵活的地址重映射方法,在系统完成必要地初始化以后,将RAM安排到0x0

地址处,而将原来位于0x0处的Flash(或其它类型的ROM)安排到其他的地方上去,加快异常的响应速度。

这个过程中最容易出错的地方是如何保证程序执行流程的连续性。因为PC 指针最初在Flash里取指令执行,在进行地址重映射以后,Flash(或其它类型的ROM)被安排到其他地址上去了,而当前地址被安排为RAM,如果事先没有对RAM的内容进行正确地设置,在往下取指令执行就会出错,即程序的连续性被存储器地址重映射这种变化所打断。

常用的处理方法是:先将Flash(或其它类型的ROM)的内容全部复制到RAM 中,然后再进行地址重映射。此时尽管Flash(或其它类型的ROM)和RAM的物理地址发生了变化,但由于RAM中的内容与原来的Flash(或其它类型的ROM)是一样的,PC指针就可以继续取得正确地指令执行,从而保证了程序流程的连续性。

ARM的存储器映射与存储器重映射

arm处理器本身所产生的地址为虚拟地址,每一个arm芯片内都有存储器,而这个芯片内的存储器的地址为物理地址。我们写程序的目的是为了利用芯片内的存储器,因此我们要知道存储器的地址,即物理地址,所以虚拟地址和物理地址之间必然存在一定的转换关系,这就是映射。把虚拟地址按照某种规则转换成物理地址的方法就为存储器映射。物理地址表示了被访问的存储器的位置。存储器映射有两种映射规则--大端映射和小端映射。

存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。

ARM7TDMI的存储器映射可以有0X00000000~0XFFFFFFFF的空间,即4G的映射空间,但所有器件加起来肯定是填不满的。

一般来说, 0X00000000依次开始存放FLASH——0X00000000,SRAM——0X40000000,BOOTBLOCK,外部存储器 0X80000000,VPB(低速外设地址,如GPIO,UART)——0XE0000000,AHB(高速外设:向量中断控制器,外部存储器控制器)——从0XFFFFFFFF回头。他们都是从固定位置开始编址的,而占用空间又不大,如AHB只占2MB,所以从中间有很大部分是空白区域,用户若使用这些空白区域,或者定义野指针,就可能出现取指令中止或者取数据中止。

由于系统在上电复位时要从0X00000000 开始运行,而第一要运行的就是厂家固化在片子里的BOOTBLOCK,这是判断运行哪个存储器上的程序,检查用户代码是否有效,判断芯片是否加密,芯片是否IAP(在应用编程),芯片是否ISP

(在系统编程),所以这个BOOTBLOCK要首先执行。而芯片中的BOOTBLOCK不能放在FLASH的头部,因为那要存放用户的异常向量表的,以便在运行、中断时跳到这来找入口,所以BOOTBLOCK只能放在FLSAH尾部才能好找到,呵呵。而ARM7的各芯片的FLASH大小又不一致,厂家为了BOOTBLOCK在芯片中的位置固定,就在编址的2G靠前编址的位置虚拟划分一个区域作为BOOTBLOCK 区域,这就是重映射,这样访问<2G即<0X80000000的位置时,就可以访问到在FLASH尾部的BOOTBLOCK区了。

BOOTBLOCK运行完就是要运行用户自己写的启动代码了,而启动代码中最重要的就是异常向量表,这个表是放在FLASH的头部首先执行的,而异常向量表中要处理多方面的事情,包括复位、未定义指令、软中断、预取指中止、数据中止、IRQ(中断) ,FIQ (快速中断),而这个异常向量表是总表,还包括许多分散的异常向量表,比如在外部存储器,BOOTBLOCK,SRAM中固化的,不可能都由用户直接定义,所以还是需要重映射把那些异常向量表的地址映到总表中。

为存储器分配地址的过程称为存储器映射,那么什么叫存储器重映射呢?为了增加系统的灵活性,系统中有部分地址可以同时出现在不同的地址上,这就叫做存储器重映射。重映射主要包括引导块“Boot Block”重映射和异常向量表的重映射。

1.引导块“Boot Block”及其重映射

Boot Block是芯片设计厂商在LPC2000系列ARM内部固化的一段代码,用户无法对其进行修改或者删除。这段代码在复位时被首先运行,主要用来判断运行哪个存储器上面的程序,检查用户代码是否有效,判断芯片是否被加密,系统的在应用编程(IAP)以及在系统编程功能(ISP)等。

Boot Block存在于内部Flash,LPC2200系列大小为8kb,它占用了用户的Flash空间,但也有其他的LPC系列不占用FLash空间的,而部分没有内部Flash 空间的ARM处理器仍然存在Boot Block。

重映射的原因:

Boot Block中有些程序可被用户调用,如擦写片内Flash的IAP代码。为了增加用户代码的可移植性,所以最好把Boot Block的代码固定的某个地址上。但由于各芯片的片内Flash大小不尽相同,如果把Boot Block的地址安排在内

部Flash结束的位置上,那就无法固定Boot Block的地址。

为了解决上面的问题,于是芯片厂家将Boot Block的地址重映射到片内存储器空间的最高端,即接近2Gb的地方,这样无论片内存储器的大小如何,都不会影响Boot Block的地址。因此当Boot Block中包含可被用户调用的IAP操作的代码时,不用修改IAP的操作地址就可以在不同的LPC系列的ARM上运行了。

2.异常向量表及其重映射

ARM内核在发生异常后,会使程序跳转到位于0x0000~0x001C的异常向量表处,再经过向量跳转到异常服务程序。但ARM单条指令的寻址范围有限,无法用一条指令实现4G范围的跳转,所以应在其后面的0x0020~0x003F地址上放置跳转目标,这样就可以实现4G范围内的任意跳转,因此一个异常向量表实际上占用了16个字的存储单元。以下为一张中断向量表:

LDR PC, ResetAddr

LDR PC, UndefinedAddr

LDR PC, SWI_Addr

LDR PC, PrefetchAddr

LDR PC, DataAbortAddr

DCD 0xb9205f80

LDR PC, [PC, #-0xff0]

LDR PC, FIQ_Addr

ResetAddr DCD ResetInit

UndefinedAddr DCD Undefined

SWI_Addr DCD SoftwareInterrupt

PrefetchAddr DCD PrefetchAbort

DataAbortAdd DCD DataAbort

Nouse DCD 0

IRQ_Addr DCD 0

FIQ_Addr DCD FIQ_Handler 重映射的原因:

由于ARM处理器的存储器结构比较复杂,可能同时存在片内存储器和片外存

储器等,他们在存储器映射上的起始地址都不一样,因此ARM内核要访问的中断向量表可能不在0x0000~0x003F地址上,因此采用了存储器重映射来实现将存在与不同地方的中断向量表都映射到0x0000~0x003F地址上。

注意:Boot Block 也存在中断向量表,而且复位后这段代码首先映射到

0x0000~0x003F地址上,也就是说复位后首先运行的是Boot Block程序。各个

存储区域的中断向量表也不尽相同。

1.4 初始化堆栈

因为ARM有7种执行状态,每一种状态的堆栈指针寄存器(SP)都是独立的(注意System 和User模式使用同一个SP)。所以,对程序中需要用到的每一种模式都要给SP定义一个堆栈地址。方法是改变状态寄存器内的状态位,使处理器切换到不同的状态,然后给SP赋值。注意:不要切换到User模式进行User模式的堆栈设置,因为进入User模式后就不能再操作CPSR回到别的模式了,可能会对接下去的程序执行造成影响。

1.5 初始化用户执行环境

主要包括初始化临界I/O设备,初始化应用程序执行环境,改变处理器的运行模式和状态,使能中断4部分。

初始化临界I/O设备。临界I/O设备是指哪些使能中断之前必须进行初始化的设备。如果不对这些设备进行必要的初始化,它们可能会在使能中断后产生一些没有意义的中断请求,从而影响程序的运行。

初始化应用程序执行环境。程序代码通过编译、链接后生成可执行映像文件,一个ARM 映像文件由RO,RW和ZI三个段组成,其中RO为代码段,RW是已初始化的全局变量,ZI 是未初始化的全局变量。映像一开始总是存储在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面执行,也可以转移到速度更快的RAM中执行;而RW和ZI这两部分是必须转移到可写的RAM里去。所谓应用程序执行环境的初始化,就是完成必要的从ROM到RAM 的数据传输和内容清零。

改变处理器的运行模式和状态。ARM微处理器在复位或上电状态下的默认模式为系统管理模式,而在初始化代码中可能需要切换到其它模式进行必要的操作,如初始化各个模式下的堆栈指针寄存器。因此,在系统的初始化过程中处理器模式一般会经历如图2所示的变化。同时,ARM微处理器在复位后总是处于ARM状态,对于兼容Thumb指令的处理器如果应用程序的入口点对应Thumb指令,则必须将微处理器切换到Thumb状态。

使能中断。如果系统需要使用中断,初始化代码应该使能中断。中断使能可以通过清除CPRS中的中断禁止位来完成。

1.6 呼叫主应用程序

当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序。如果主应用程

序是由C代码编写,可以通过以下两种方式进入C代码运行,最简单的情况如下:IMPORT C_Entry ;定义一个外部标号,最好不使用main

B C_Entry ;跳转到该处执行

在ARM的ADS编译环境中,还另外提供了一种进入C代码的机制:

IMPORT __main

B __main

__main()是编译器提供的一个函数,负责完成库函数的初始化和对C代码运行环境的初始化,最后自动调转到main()函数执行,此时应用程序的主函数名必须是main()。

用户可以根据需要选择是否使用main()函数,如果想让系统自动完成初始化过程,可以使用main()函数;如果所有的初始化过程都由用户自己完成,则不使用main()。

ADS下的分散加载文件应用实例

ADS下的分散加载文件应用实例

load_region_name start_address | "+"offset [attributes] [max_size]

{

execution_region_name start_address | "+"offset [attributes][max_size]

{

module_select_pattern ["("

("+" input_section_attr | input_section_pattern) ([","] "+" input_section_attr | ","

input_section_pattern)) *

")"]

}

}

加载区(load_region):指用来保存永久性数据(程序和只读变量)的区域;

执行区(execution_region):程序执行时所表现出来的区域;程序执行时,从加载区域将数据复制到执行区;

load_region_name(加载区域名): 用于Linker区别不同的加载区域,最多31个字符;

start_address:起始地址;

+offset:前一个加载区域尾地址+offset,做为当前的起始地址,且offset为0或4的倍数;attributes: PI 与地址无关

RELOC 重新部署

OVERLAY 覆盖,允许多个可执行区域在同一个地址,ADS不支持

ABSOLUTE 绝对地址(默认)

max_size:该加载区域的大小;

execution_region_name:执行区域名;

start_address:链接是目标存放的地址,必须字对齐;

+offset:同上;

attributes: PI 与地址无关

OVERLAY 覆盖

ABSOLUTE 绝对地址(默认)

FIXED 固定地址

UNINIT 不用初始化该区域的ZI段

module_select_pattern:目标文件滤波器,支持通配符“*”和“?”;*.o匹配所有目标,* (或“.ANY”)匹配所有目标文件和库。

input_section_attr:每个input_section_attr必须跟随在“+”后;且大小写不敏感;

RO-CODE或CODE

RO-DATA或CONST

RO或TEXT, selects both RO-CODE and RO-DATA

RW-DATA

RW-CODE

RW或DATA, selects both RW-CODE and RW-DATA

ZI或BSS

ENTRY, that is a section containing an ENTRY point.

FIRST,用于指定存放在一个执行区域的第一个或最后一个区域

LAST,同上

input_section_pattern:段名;

汇编中指定段:

AREA vectors, CODE, READONLY

C中指定段:

#pragma arm section [sort_type[[=]"name"]] [,sort_type="name"]*

sort_type: code

rwdata

rodata

zidata

如果“sort_type”指定了但没有指定“name”,那么之前的修改的段名将被恢复成默认值。

#pragma arm section将恢复所有段名为默认值。

一般应用:

#pragma arm section rodata = "sram", code ="sram"

.此间的“rodata”和“code”将定位在“sram”段中。

#pragma arm section

程序中对某区域的引用方法:

Load$$region_name$$Base Load address of the region.

Image$$region_name$$Base Execution address of the region.

Image$$region_name$$Length Execution region length in bytes (multiple of 4).

Image$$region_name$$Limit Address of the byte beyond the end of the execution region.

Image$$region_name$$ZI$$Base Execution address of the ZI output section in this region.

Image$$region_name$$ZI$$Length Length of the ZI output section in bytes (multiple of 4).

Image$$region_name$$ZI$$Limit Address of the byte beyond the end of the ZI output sectionin the execution region.

SectionName$$Base Input Address of the start of the consolidated section called SectionName.

SectionName$$Limit Input Address of the byte beyond the end of the consolidated section called SectionName.

Base:首地址;

Limit:尾地址;

region_name:RO、RW、ZI、load_region_name、execution_region_name;

例如:

RAM1区域的首地址:Image$$RAM1$$Base

sram段首地址:sram$$Base

注意:“sram$$Base”不一定等于“Image$$RAM2$$Base”;

实例:

起始地址大小

ROM: 0x00000000 256K 0x1fc 保留为加密字

RAM 0x40000000 16K

SRAM 0x80000000 512K

程序在ROM中运行;

RAM主要用于程序堆栈及优先用于存放部分变量;

SRAM速度慢,主要用于存放大的数据表。

LOAD_ROM1 0x00000000 ; 指定该加载区域首地址

{

EXEC_ROM1 +0 0x1f8 ; 没有前一加载区域,所以该执行区域首地址为加载去首地址

; 并指定该区域长度

{

Startup.o (vectors, +FIRST) ; 目标文件的vectors段放在该执行区域的第一段

irq.o (+RO) ; 目标文件的所有RO段放在该执行区域

swi.o (+RO)

}

}

LOAD_ROM2 0x00000200 ; 第二个加载区域

{

EXEC_ROM2 +0 0x3e600

{

* (+RO) ; 所有目标文件和库文件中的RO段存放在该区域}

RAM1 0x40000000 0x4000

{

* (+RW, +ZI) ; 所有目标文件和库文件的RW和ZI段存放在该区域

}

SRAM2 0x80000000 0x80000

{

* (sram) ; 所有目标文件中的sram段存放在该区域}

}

实例二:

Load_region1 0x00000000 0x1fc

{

EXEC_ROM1 +0

{

Startup.o (vectors, +FIRST)

irq.o (+RO)

}

}

Load_region2 0x00000200 0x3e600

{

EXEC_ROM2 +0

{

* (+RO)

}

Exec_RAM1 0x40000000 OVERLAY 0x4000 ; 覆盖,所以必须自己初始化RW 和ZI区域

{

* (+RW, +ZI)

}

Exec_IAP 0x40000000 OVERLAY 0x80000 ; 覆盖,所以执行前应该COPY到执行区

{

iap.o (+RO)

}

}

此例中iap.o定义在RAM中运行,在调用iap.c中函数之前应该将其从

Load$$region_name$$Base复制到Image$$region_name$$Base区域,而在系统启动前也应该将RW和ZI初始化好(__main不无效了)。

ARM处理器的分散加载及特殊应用研究

ARM处理器的分散加载及特殊应用研究

在一个采用ARM处理器的实时嵌入式系统中,目标硬件常常由Flash、SRAM、SDRAM和NVRA M(非易失性RAM)等存储器组成,并定位于不同的物理地址范围,那么,怎样通过软件更好地访问和利用这些不同的存储器并让系统高效地运行?分散加载(scatter loading)就提供了这样一种机制。它可以将内存变量定位于不同的物理地址上的存储器或端口,通过访问内存变量即可达到访问外部存储器或外设的目的;同时通过分散加载,让大多数程序代码在高速的内部RAM中运行,从而使得系统的实时性大大增强。

1 ARM ELF目标文件的主要构成

ARM ELF(Executable and Linking Format)目标文件主要由.Text段、.Data段、.BSS段构成,其他段如.debug段、.comment段等与本文关系不大,不作介绍。

Text段由可执行代码组成,段类型为Code,属性为RO;

Data段由已初始化数据组成,段类型为Data,属性为RO;

BSS段由未初始化数据组成,段类型为Zero,属性为RW,在应用程序启动时对该段的数据初始化为零。如果在分散加载文件中指定了UNINIT属性,则在应用程序启动时不初始化该段。

2 分散加载的基本原理

假设一个采用ARM处理器的实时嵌入式系统目标硬件的存储器由ROM存储器和RAM存储器组成。当一个嵌入式系统在仿真环境下调试完毕,需要脱机运行的时候,就需要将源程序编译连接成可执行目标代码并烧写到ROM存储器中。由于ROM存储器存取数据的速率比RAM存储器慢,因此,让程序在ROM存储器中运行。CPU每次取指令和取数据操作都要访问ROM存储器,这样需要在CPU的总线周期中插入等待周期,通过降低总线的速率来满足访问慢速的ROM存储器,这样势必会降低CPU的运行速率和效率,因此,分散加载就显得非常必要。

ARM的连接器提供了一种分散加载机制,在连接时可以根据分散加载文件(.scf文件)中指定的

存储器分配方案,将可执行镜像文件分成指定的分区并定位于指定的存储器物理地址。这样,当嵌入式系统在复位或重新上电时,在对CPU相应寄存器进行初始化后,首先执行ROM存储器的Bootloader(自举)代码,根据连接时的存储器分配方案,将相应代码和数据由加载地址拷贝到运行地址,这样,定位在RAM存储器的代码和数据就在RAM存储器中运行,而不再从ROM存储器中取数据或取指令,从而大大提高了CPU的运行速率和效率。分散加载的基本原理如图1所示。

3 分散加载文件语法

在一个实时嵌入式系统中,分散加载文件是对目标硬件中的多个存储器块的分块描述,它直接对应目标硬件存储器的起始地址和范围。同时,它在应用程序连接时用于告诉连接器用户程序代码和数据的加载地址和运行地址,在连接时由连接器产生相应的加载地址和运行地址符号,包括代码和数据的加载起始地址、运行地址和长度等。这些符号用于上电后执行启动代码的数据拷贝工作,启动代码根据这些符号,将指定代码和数据由ROM中的加载地址拷贝到RAM中的运行地址中,从而实现代码在高速RAM存储器中的脱机运行。其语法格式如下:

注意:

①每一个分散加载文件必须至少包含一个根区,每个根区的加载地址等于执行地址。

②每一个引导区必须至少包含一个执行区,每一个执行区必须至少包含一个代码段或数据段;一个引导区可以包含几个执行区,每一个执行区只能属于一个引导区。

4 分散加载时连接器生成的预定义符号

在编译连接时如果指定了分散加载文件(.scf文件),在连接后会自动生成如下变量:

5 重新实现_user_initial_stEickheap()函数

分散加载机制提供了一种指定代码和静态数据布局的方法。使用分散加载时,必须重新放置堆栈和堆。

应用程序的堆栈(stack)和堆(heap)是在C库函数初始化过程中建立起来的,在ADSl.2或更新版本中,在缺省状态下C库函数初始化代码会将连接器生成的符号Image$$ZI$$Limit地址作为堆的基地址。在分散加载时,连接器会将用户的__user_initidl_stackheap()函数代替C库函数默认的堆栈和堆初始化函数,并将其连接到用户的镜像文件中,用户可通过重新实现__user_initia l_stackheap()函数来改变堆栈和堆的位置,从而适合自己的目标硬件。

__user_initial_stackheap()可以用C或汇编语言来实现。它必须返回如下参数:

r0—堆基地址;

r1—堆栈基地址;

r2—堆长度限制值(需要的话);

r3—堆栈长度限制值(需要的话)。

当用户使用分散加载功能的时候,必须重新实现一user_initial_staacklaeap(),否则连接器会报错:

Error:L6218E:Undefined symbol Imager$$ZI$$一Limit(referred from sys_stackheap.o)。

注:Image$$ZI$$Limit变量为零初始化段(ZI段)的末地址。未使用分散加载时,堆默认就定位在ZI段的末地址,如图2所示。

__user_initial_stackheap()函数的实现有两种方法。

(1)共用一个存储区

汇编语言如下:

这种方式定义的堆栈和堆共用一个存储区,采用相向的增长方向,如图3所示。

(2)使用两个存储区

汇编语言如下:

这种方式定义的堆栈和堆分别采用两个不同存储区。堆栈采用向下增长,从地址Ox40000到地址Ox20000;堆采用向上增长,从地址0x28000000到地址0x28080000,如图4所示。

6 特殊应用

6. 1 定位目标外设

使用分散加载,可以将用户定义的结构体或代码定位到指定物理地址上的外设,这种外设可以是定时器、实时时钟、静态SRAM或者是两个处理器间用于数据和指令通信的双端口存储器等。在程序中不必直接访问相应外设,只需访问相应的内存变量即可实现对指定外设的操作,因为相应的内存变量定位在指定的外设上。这样,对外设的访问看不到相应的指针操作,对结构体成员的访问即可实现对外设相应存储单元的访问,让程序员感觉到仿佛没有外设,只有内存。

例如,一个带有两个32位寄存器的定时器外设,在系统中的物理地址为Ox04000000,其C语言结构描述如下:

要使用分散加载将上述结构体定位到Ox04000000的物理地址,可以将上述结构体放在一个文件名为timer_regs.c中,并在分散加载文件中指定即可,如下:

属性UNINIT是避免在应用程序启动时对该执行段的ZI数据段初始化为零。

在程序连接后,通过Image map文件可查看该ZI数据段的存储器分配情况:

Execution Region TIMER(Base:Ox04000000,Size:0x00000008,Max:0xffffffff,ABSOLUTE,UNINIT)Base Addr Size Type Attr Idx E Section Name 0bi ectOx04000000 0x00000008 Zero RW 32.bss tlmer_regs.o从Image map文件可以看出,该TIMER执行区定位在物理地址0x04000000,即结构体timer_regs定位在Ox04000000,因此,在程序中对结构体的操作即是对定时器的操作。

6.2 定义超大型结构体数组

分散加载机制在提供将指定代码和数据定位在指定物理地址的能力的同时,也提供了一种代码分割机制——可以将指定的零初始化段(ZI段)从可执行代码中分离出来。这样最终生成的烧入ROM 或Flash中的镜像文件就不包括那部分分割了的零初始化段,即使该零初始化段再大,也不影响最终生成的镜像文件的大小。但不采用分散加载机制,零初始化段在编译连接后是直接生成到镜像文件中的。它的大小直接影响最终要烧写的文件的大小,且零初始化段的大小还取决于内存的大小,它不能大到超过内存的大小;而采用分散加载机制,可以将某个零初始化段定位到非内存地址的一个存储器外设上,如NVRAM(非易失性随机存储器)。

笔者曾在一个实际工程中采用这种分散加载机制,将一个2MB的结构体数组定位到外部NVRAM 中,用于记录设备在工作过程中采集到的数据;而在本系统中,ARM处理器的内存只有256 KB,Flash存储器也只有2 MB。如果不采用分散加载,程序根本无法运行,也不能烧写到Flash中。

采用分散加载,把对复杂外设的访问变成对结构体数组的访问,使程序代码精简易懂。对程序员来说,对结构体数组的操作还是和内存变量的操作一样的。

编者注:本文为期刊缩略版,全文见本刊网站www.mesnet.com.cn。

结语

分散加载是嵌入式系统应用中不可或缺的一种加载方式,ARM、DSP、PowerPC和MIPS等嵌入式处理器,都离不开分散加载。这种分散加载的思想是通用的,只是不同处理器的实现方式不同。

本文详细阐述了基于ARM处理器的分散加载方法及其特殊应用,并以实际工程为例来说明怎样实现分散加载及使用分散加载的好处。它是笔者在实际工程应用中的心得体会,同时也是笔者工作经验的总结,希望本文对从事嵌入式系统设计和应用的工程技术人员能有所帮助。

分散加载文件的实现

很多朋友对分散加载不是很理解,其实它的原来很简单,这些加载的原理都源自生活。

由于现在的嵌入式技术发展比较快,各类存储器也层出不穷,但是它们在容量、成本和速度上有所差异,嵌入式系统又对成本比较敏感,那么合理的选择存储器和充分的利用存储器资源成为一个必要解决的问题。咋们工程师最喜欢的就是发掘问题,然后解决问题,基于嵌入式系统对存储器的敏感,那么要合理的利用存储器资源,就必须找到一种合理的方式。工程师们发现,可以把运行的程序放在不同成本的存储器中来寻找这个成本的支点,比如把没有运行的但是较为庞大的程序放在容量大、成本低、速度也较低的FLASH存储器中,要用的时候再去拿。但是,这里面又有一个问题,嵌入式本身就对信号的处理速度有较高的要求,这点在实时操作系统的定义上上有所体现。所以那些经常要用的程序段如果要保证其高速的运行那么就得放在一个在高速的存储器中,不过这是有代价的:较高成本,小容量。但是,相信由于技术的发展这个问题终将被解决,到时候寻找平衡点的问题也就不存在了。好了,说了多了点。切入正题。

程序总有两种状态:运行态和静止态。当系统掉电的时候程序需要被保存在非易失性的存储器中,且这个时候程序的排放是按照地址依次放的,换句话说:我才懒得管它怎么放,只要不掉就行。当系统上电后,CPU就要跑起来了,CPU属于高速器件,存储器总是不怎

么能跟得上,既然跟不上那么我们就尽量缩短它们之间的差距,那留下一条路,那就是尽量提高存储器的读取速度,存储器类型决定其速度的水平,那么尽量放在速度高的存储器就成为首选解决方案。那么我们就把要执行的程序暂时拿到速度较快的RAM中。那么拿的过程就牵涉到程序的加载了。这就是要解决的问题。

一个映像文件由域(region)、输出段(output sections)和输入段(input sections)组成。不要想得太复杂,其实他们之间就是包含与被包好的关系。具体关系是这样的:

映像文件> 域> 输出段> 输入段

输入段:

输入段就是我们写的代码+初始化的数据+应该被初始化为0的数据+没有初始化的数据,用英文表示一下就是:RO(ReadOnly),RW(ReadWrite),ZI(ZeroInitialized),NOINIT (Not Initialized)。ARM连接器根据各个输入段不同的属性把相同的拿再一起组合一下

就成为了输出段。

请看看平时写的东东:

AREA RESET, CODE, READONLY

AREA DSEG1, DATA, READWRITE

AREA HEAP, NOINIT, READWRITE

看出其属性没?

输出段:

为了简化编译过程和更容易取得各种段的地址,那么把多个同属性的输入段按照一定的规律组合在一起,当然这个输出段的属性就和它包含的输入段的属性一样咯。输入段的排放规律就是:最先排放RO属性的输入段,然后是RW属性段,最后是ZI或NOINIT段。

域:

为什么还要加一层域,我的理解是由于代码的功能不同,那么我们有必要把不同功能的代码分类放。我们可以把需要高速执行的代码放在一起、把对速度要求不高的放在一起、把执行频率高的放在一起,把执行频率低的放在一起...那么按照这种方式放的代码就可以根据其具体需要放在不同的存储器中了。这样可以提高程序执行速度。一个域中包含1~3个

输出段。

映像文件:

我暂时把映像文件理解成烧到存储器中的文件,由N个域组成。这些域其实可以看做是独立的模块,只是他们按照一定的顺序(这个顺序还是:RO+RW+ZI)被捆绑在一起,这

样才方便烧写到非易失存储器中去。

好了,了解了映像文件的组成,那么来看看映像文件是怎么跑起来的。

映像文件就是有N节车厢的火车,车厢(域)里装着要送到不同站(不同类型的存储器)的货物。到相应的站了,那么就把相应的车厢拿下来。指挥拿这个的就是scatter文件。拿下货物车厢后,我们就解开它,把里面的品牌为RO的货物提取出来,按照scatter的指示发给某个地址,然后再先后把品牌为RW和ZI的货物发到scatter指定的地址。

看看这个加深理解:

LOAD_ROM1 0X00000000 ; 从火车上取出来时的地址(如:成都站)

{

EXEC_ROM1 0x40000000

{

PROGRAM.O(+RO)? ;把品牌RO的货物发给0x40000000去

RAM1 0x80000000

{

PROGRAM.O(+RW,+ZI);把品牌RW,ZI的货物依次发给0x80000000

}

}

......

}

其他的段也可以这样依葫芦画瓢。

scatter的原理就介绍这样,其中的语法和规则要多写多把代码的地址拖出来看才能体会。不过都是很简单的,生活中的小常识就能解决这些问题。为什么?因为设计这些规则的工程师的灵感就是源自生活。嘿嘿...享受把代码随处放的乐趣吧,...enjoy...

分散加载

ARM处理器的分散加载及特殊应用研究 摘要从ARM ELF目标文件主要构成出发,详细介绍了分散加载的基本原理、分散加载文件的语法、分散加载时连接器生成的预定义符号及要重新实现的函数等;以定位目标外设和定义超大型结构体数组两项应用来加以说明,并给出完整的工程实例和Bootloader代码。这些都已经在实际工程中多次应用和验证,是笔者实际工程项目的萃取。 关键词分散加载嵌入式系统 Scatter Loading Bootloader ARM ELF 引言 在当今的嵌入式系统设计中,ARM处理器以价格便宜、功耗低、集成度高、外设资源丰富和易于使用的特点而得到广泛的应用;在速度和性能方面已达到或超过部分PC104嵌入式计算机的性能,而成本却比相应的PC104计算机低很多,广泛应用于手机、GPS接收机、地图导航、路由器、以太网交换机及其他民用和工业电子设备。 在一个采用ARM处理器的实时嵌入式系统中,目标硬件常常由Flash、SRAM、SDRAM和NVRAM(非易失性RAM)等存储器组成,并定位于不同的物理地址范围,那么,怎样通过软件更好地访问和利用这些不同的存储器并让系统高效地运行?分散加载(scatter loading)就提供了这样一种机制。它可以将内存变量定位于不同的物理地址上的存储器或端口,通过访问内存变量即可达到访问外部存储器或外设的目的;同时通过分散加载,让大多数程序代码在高速的内部RAM中运行,从而使得系统的实时性大大增强。 1 ARM ELF目标文件的主要构成 ARM ELF(Executable and Linking Format)目标文件主要由.Text段、.Data 段、.BSS段构成,其他段如.debug段、.comment段等与本文关系不大,不作介绍。 .Text段由可执行代码组成,段类型为Code,属性为RO; .Data段由已初始化数据组成,段类型为Data,属性为RO; .BSS段由未初始化数据组成,段类型为Zero,属性为RW,在应用程序启动时对该段的数据初始化为零。如果在分散加载文件中指定了UNINIT属性,则在应用程序启动时不初始化该段。 2 分散加载的基本原理 假设一个采用ARM处理器的实时嵌入式系统目标硬件的存储器由ROM存储器和RAM存储器组成。当一个嵌入式系统在仿真环境下调试完毕,需要脱机运行的时候,就需要将源程序编译连接成可执行目标代码并烧写到ROM存储器中。由于ROM存储器存取数据的速率比RAM存储器慢,因此,让程序在ROM存储器中运行。CPU每次取指令和取数据操作都要访问ROM存储器,这样需要在CPU的总线周期中插入等待周期,通过降低总线的速率来满足访问慢速的ROM存储器,这样势必会降低CPU的运行速率和效率,因此,分散加载就显得非常必要。

在RealView MDK中使用J-Link对STM32F130ZET6外部Nor Flash烧写的方法

在RealView MDK中使用J-Link对STM32F130ZET6外部Nor Flash烧写的方法by 314forever 文章中以STM32F103ZET6外接SST39VF6401B为例,在红牛实验板上操作成功。图例中用到的地址需要根据你自己的情况作相应调整。 新建一个工作目录。将x:\Keil\ARM\Flash\STM32F10x\下的所有文件拷进来,将x:\Keil\ARM\Flash\SST39x640x\下的FlashPrg.c文件拷进来并覆盖同名文件,将x:\Keil\ARM\Flash\下的FlashOS.h文件拷进来。如果你使用的Nor Flash 不在MDK自带驱动的范围内,那么需要根据FlashPrg.c文件自行编写相关函数 打开工程STM32Fx,在FlashDev.c中添加如下代码,有关这段代码请参考x:\Keil\ARM\Flash\SST39x640x\FlashDev.c 修改FlashDev.c和FlashPrg.c中的头文件引用 在目标属性中修改宏定义和输出文件

编译此工程,将得到的STM32F10x_NOR.FLM文件拷入x:\Keil\ARM\Flash\文件夹中。然后在MDK的Flash下载设置中就出现了NOR Flash的选项 这样我们就得到了MDK对Nor Flash的接口文件,接下来是如何在工程中将数据弄到Nor Flash上。 新建工程,将要烧入Nor Flash中的数据单独存放在一个文件中

然后自定义分散加载文件 在这里把数据定义到Nor Flash中,注意地址要根据你自己的情况来设置 在程序中要对数据进行使用,否则编译后未用到的数据可能会被优化掉 编译结果 添加Flash烧写算法,MDK会根据不同的地址采用不同的算法

ARM伪指令详述

1、AREA 语法格式: AREA 段名属性1,属性2,…… AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用―|‖括起来,如|1_test|。 属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:— CODE属性:用于定义代码段,默认为READONL Y。 — DATA属性:用于定义数据段,默认为READWRITE。 — READONL Y属性:指定本段为只读,代码段默认为READONL Y。 — READWRITE属性:指定本段为可读可写,数据段的默认属性为READWRITE。—ALIGN属性:使用方式为ALIGN 表达式。在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为0~31,相应的对齐方式为2表达式次方。—COMMON属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元。 一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。 使用示例: AREA Init,CODE,READONL Y 指令序列 ;该伪指令定义了一个代码段,段名为Init,属性为只读 2、ALIGN 语法格式: ALIGN {表达式{,偏移量}} ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式|。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如1、2、4、8、16等。若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。 使用示例: AREA Init,CODE,READONL Y,ALIEN=3 ;指定后面的指令为8字节对齐。 指令序列 END 3、CODE16、CODE32 语法格式: CODE16(或CODE32) CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。 CODE32伪指令通知编译器,其后的指令序列为32位的ARM指令。 若在汇编源程序中同时包含ARM指令和Thumb指令时,可用CODE16伪指令通知编译器其后的指令序列为16位的Thumb指令,CODE32 伪指令通知编译器其后的指令序列为32位的ARM指令。因此,在使用ARM指令和Thumb指令混合编程的代码里,可用这两条伪指令进行切换,但注意他们只通知编译器其后指令的类型,并不能对处理器进行状态的切换。使用示例: AREA Init,CODE,READONL Y …… CODE32 ;通知编译器其后的指令为32位的ARM指令

M0系列ARM芯片的启动程序分解

周立功NXP LPC21xx/22xx 系列ARM 芯片的启动程序分解 作者:钟常慰 关于LPC2200 启动程序分散加载描述文件的叙述 在ADS LPC2200 的启动模板中有一个scf 文件夹,其中有mem_a.scf、mem_b.scf、mem_c.scf 这3 个文件,这3 个文件是ADS 的 分散加载机制,其目的是将代码段和数据段分别定位到指定地址上。可以在Arm Linker 中选择加载路径。分散装载技术概述: 分散装载技术可以把用户的应用程序分割成多个RO(只读)运行域和RW(可读写)运行域(一个存储区域块),并且给它们制定不同的 地址。一个嵌入式系统中,Flash、16 位RAM、32 位RAM 都可以存在于系统中,所以,将不同功能的代码定位在特定的位置会大大地提 高系统的运行效率。下面是最为常用的2 种情况: 1、32 位的RAM 运行速度很快,因此就把中断服务程序作为一个单独的运行域,放在32 位的RAM,使它的响应时间达到最快。 2、程序在RAM 中运行,其效率要远远高于在ROM 中运行,所以将启动代码(Boot loader)以外的所有代码都复制在RAM 中运行, 可以提高运行效率。 分散装载技术主要完成了2 个基本的功能: 如何分散。就是如何将输入段组成输出段和域。 如何装载。就是确定装载域和运行域在存储空间里的地址是多少。 域可以分为装载域和运行域 装载域描述运行前输出段和域在ROM/RAM 里的分布状态,运行域描述了运行时输出段和域在ROM/RAM 里的分布状态。大多数情况下, 映像文件在执行前把它装载到ROM 里,而当运行时,域里的有些输出段(比如RW 类型的输出段)必须复制到RAM 里,程序才能正常运行, 所以,在装载和运行时,RW 类的输出段处在不同的位置(地址空间)。 Scatterfile 分散加载文件: 在scatterfile 中可以为每一个代码或数据区在装载和执行时指定不同的存储区域地址,Scatlertoading 的存储区块可以分成二种类 型: 装载区:当系统启动或加载时应用程序的存放区。 执行区:系统启动后,应用程序进行执行和数据访问的存储器区域,系统在实时运行时可以有一个或多个执行块。 映像中所有的代码和数据都有一个装载地址和运行地址(二者可能相同也可能不同,视具体情况而定)。scatter 文件语法 scatter 文件是一个简单的文本文件,包含一些简单的语法。 My Region 0x0000 0x1000 ;我的名字My Region 起始地址0x0000 属性0x1000 { ;the context of region 这个域的范围 } 标题

ARM——分散加载描述文件

ARM——分散加载描述文件 分散加载的实现(scatter) 很多朋友对分散加载不是很理解,其实它的原来很简单,这些加载的原理都源自生活。 由于现在的嵌入式技术发展比较快,各类存储器也层出不穷,但是它们在容量、成本和速度上有所差异,嵌入式系统又对成本比较敏感,那么合理的选择存储器和充分的利用存储器资源成为一个必要解决的问题。咋们工程师最喜欢的就是发掘问题,然后解决问题,基于嵌入式系统对存储器的敏感,那么要合理的利用存储器资源,就必须找到一种合理的方式。工程师们发现,可以把运行的程序放在不同成本的存储器中来寻找这个成本的支点,比如把没有运行的但是较为庞大的程序放在容量大、成本低、速度也较低的FLASH存储器中,要用的时候再去拿。但是,这里面又有一个问题,嵌入式本身就对信号的处理速度有较高的要求,这点在实时操作系统的定义上上有所体现。所以那些经常要用的程序段如果要保证其高速的运行那么就得放在一个在高速的存储器中,不过这是有代价的:较高成本,小容量。但是,相信由于技术的发展这个问题终将被解决,到时候寻找平衡点的问题也就不存在了。好了,说了多了点。切入正题。 程序总有两种状态:运行态和静止态。当系统掉电的时候程序需要被保存在非易失性的存储器中,且这个时候程序的排放是按照地址依次放的,换句话说:我才懒得管它怎么放,只要不掉就行。当系统上电后,CPU就要跑起来了,CPU 属于高速器件,存储器总是不怎么能跟得上,既然跟不上那么我们就尽量缩短它们之间的差距,那留下一条路,那就是尽量提高存储器的读取速度,存储器类型决定其速度的水平,那么尽量放在速度高的存储器就成为首选解决方案。那么我们就把要执行的程序暂时拿到速度较快的RAM中。那么拿的过程就牵涉到程序的加载了。这就是要解决的问题。 一个映像文件由域(region)、输出段(output sections)和输入段(input sections)组成。不要想得太复杂,其实他们之间就是包含与被包好的关系。具体关系是这样的: 映像文件>域>输出段>输入段 输入段: 输入段就是我们写的代码+初始化的数据+应该被初始化为0的数据+没有初始化的数据,用英文表示一下就是:RO(ReadOnly),RW (ReadWrite),ZI (ZeroInitialized),NOINIT(Not Initialized)。ARM连接器根据各个输入段不同的属性把相同的拿再一起组合一下就成为了输出段。 请看看平时写的东东: AREA RESET, CODE, READONLY AREA DSEG1, DATA, READWRITE AREA HEAP, NOINIT, READWRITE 看出其属性没? 输出段: 为了简化编译过程和更容易取得各种段的地址,那么把多个同属性的输入段按照一定的规律组合在一起,当然这个输出段的属性就和它包含的输入段的属性

ScatterFile分散加载文件及其应用

分散加载文件及其应用 分散加载能够将加载和运行时存储器中的代码和数据描述在被称为分散加载描述文件的一个文本描述文件中,以供连接时使用。 (1)分散加载区 分散加载区域分为两类: ? 加载区,包含应用程序复位和加载时的代码和数据。 ? 执行区,包含应用程序执行时的代码和数据。应用程序启动过程中,从每个加载区可创建一个或多个执行区。 映象中所有的代码和数据准确地分为一个加载区和一个执行区。 (2)分散加载文件示例 ROM_LOAD 0x0000 0x4000 { ROM_EXEC 0x0000 0x4000; Root region { * (+RO); All code and constant data } RAM 0x10000 0x8000 { * (+RW, +ZI); All non-constant data } }

(3)分散加载文件语法 load_region_name start_address | "+"offset [attributes] [max_size] { execution_region_name start_address | "+"offset [attributes][max_size] { module_select_pattern ["(" ("+" input_section_attr | input_section_pattern) ([","] "+" input_section_attr | "," input_section_pattern)) * ")"] } } load_region:加载区,用来保存永久性数据(程序和只读变量)的区域;execution_region:执行区,程序执行时,从加载区域将数据复制到相应执行区后才能被正确执行; load_region_name:加载区域名,用于“Linker”区别不同的加载区域,最多31个字符;start_address:起始地址,指示区域的首地址; +offset:前一个加载区域尾地址+offset 做为当前的起始地址,且“offset”应为“0”或“4”的倍数; attributes:区域属性,可设置如下属性: PI 与地址无关方式存放; RELOC 重新部署,保留定位信息,以便重新定位该段到新的执行区; OVERLAY 覆盖,允许多个可执行区域在同一个地址,ADS不支持; ABSOLUTE 绝对地址(默认); max_size:该区域的大小; execution_region_name:执行区域名; start_address:该执行区的首地址,必须字对齐; +offset:同上; attributes:同上; PI 与地址无关,该区域的代码可任意移动后执行; OVERLAY 覆盖; ABSOLUTE 绝对地址(默认); FIXED 固定地址; UNINIT 不用初始化该区域的ZI段; module_select_pattern:目标文件滤波器,支持通配符“*”和“?”; *.o匹配所有目标,* (或“.ANY”)匹配所有目标文件和库。 input_section_attr:每个input_section_attr必须跟随在“+”后;且大小写不敏感; RO-CODE 或CODE RO-DATA 或CONST RO或TEXT, selects both RO-CODE and RO-DATA RW-DATA RW-CODE RW 或DATA, selects both RW-CODE and RW-DATA ZI 或BSS

分散加载描述文件

7.5 分散加载描述文件 在7.3节中已经简单介绍了映像的组成,也介绍了如何用命令选项来构建简单结构的映像。要构建映像的存储器映射,链接器必须有:描述节如何分组成区的分组信息、描述映像区在存储器映射中的放置地址的放置信息。 分散加载机制允许为链接器指定映像的存储器映射信息,可实现对映像组件分组和布局的全面控制。分散加载通常仅用于具有复杂存储器映射的映像(尽管也可用于简单映像),也就是适合加载和执行时内存映射中的多个区是分散的情况。本节将对armlink所使用的分散加载描述文件作详细介绍。 7.5.1 分散加载机制 7.5.1.1 何时使用分散加载机制 链接命令行选项提供了一些对数据和代码布局的控制,但如果要对布局进行全面控制则需要比命令行选项更详细的指令。对于以下一些情况,就需要或最好使用分散加载描述文件: 复杂存储器映射:代码和数据需要放在多个不同存储器区域,必须详细指明哪个节放在哪个存储器空间。 不同存储器类型:许多系统包含FLASH、ROM、SDRAM和快速SRAM。利用分散加载可将代码和数据放置在最适合的存储器类型中。例如,中断代码可能放在快 速SRAM中,以改进中断响应时间,而将不频繁使用的配置信息可能放在较慢的 FLASH中。 存储器映射I/O:分散加载机制可将数据节精确放在存储器的某个地址,便于访问外设映射内存。 固定位置的函数:可以将函数放在存储器中的一个固定位置,即使周围的应用程序已经被修改并重新编译。 使用符号识别堆和栈:链接程序时,分散加载机制可为堆和栈的位置定义符号。 在实现嵌入式系统时,通常会需要使用分散加载机制,因为这些系统一般都会使用ROM、RAM和存储器映射I/O。 注意,如果为Cortex-M3结构的处理器编译程序,此处理器结构有着一个固定的内存映射,可以使用分散加载文件来定义栈和堆。 链接时如要使用分散加载文件,则需使用链接命令选项--scatter description_file,详细内容参考7.2节。 7.5.1.2 为分散加载所定义的符号 当armlink使用分散加载描述文件创建映像时,它将创建一些区相关符号,在7.4节中已作详细介绍。仅当代码引用这些特殊符号时,链接器才创建它们。 当分散加载描述文件被使用时,7.4节中的符号Image$$RW$$Base、 Image$$RW$$Limit、Image$$RO$$Base、Image$$RO$$Limit、 Image$$ZI$$Base和Image$$ZI$$Limit不被定义。 若使用分散加载文件,但不指定任何区名并且不使用__user_initial_stackheap(),则库将生成一个错误信息。

M3启动文件.tmp

目录 第1章CMSIS标准 (1) 1.1 CMSIS标准简介 (1) 1.2 CMSIS标准的软件架构 (1) 1.3 CMSIS标准的文件结构 (2) 第2章注解startup.s文件(以startup_LPC17xx.s为例) (3) 2.1 堆栈以及堆的初始化 (3) 2.1.1 堆栈的初始化 (3) 2.1.2 堆的初始化 (3) 2.2 中断向量表的初始化 (4) 2.3 调用Reset Handler (5) 2.4 其他的代码 (5) 第3章ARM芯片的启动过程详解 (7) 3.1 ARM芯片的启动过程概述 (7) 3.2 结合代码来看ARM芯片的启动过程 (8) 3.2.1 调试环境的搭建及测试代码 (8) 3.2.2 跟踪启动代码 (8) 3.2.3 详细的启动过程 (9) 3.2.4 __main (10) 3.2.5 __rt_entry (10) 3.2.6 __rt_lib_init (11) 3.3 关于microlib (11) 3.4 x.map (11) 3.4.1 关于链接器 (12) 3.4.2 RW段在RAM的存放 (12) 3.5 关于ARM程序的Memory管理 (12) 3.5.1 ARM镜像文件的组成(image) (12) 3.5.2 关于image文件(镜像文件) (13) 3.5.3 RO段 (13) 3.5.4 RW段 (14) 3.5.5 ZI段(初始化为0或未初始化的变量) (15) 3.6 缺省内存映射 (16) 3.7 内存模型 (17) 第4章关于分散加载文件 (18) 4.1 什么时候使用分散加载文件 (18) 4.2 适用范围 (18) 4.3 再谈ARM Image(镜像文件) (18) 4.4 节放置 (19) 4.5 一个简单的加载过程 (20) 4.6 分散加载文件语法 (21) 4.6.1 加载时域(区)描述 (21) 4.6.2 运行时域(区)描述 (22) 4.6.3 输入段描述 (23)

STM32 分散加载文件

STM32 分散加载文件 IAP —MDK (2011-07-25 14:42:30) ; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x08000000 0x00004000 ; load region size_region 第一个加载域,起始地址0x08000000,{ 大小0x00004000 ER_IROM1 0x08000000 0x00004000 ; load address = execution address 第一个运行时域, { 起始0x08000000,大小0x00004000 *.o (RESET, +First) IAP第一阶段还是在FLASH中运行 *(InRoot$$Sections) startup_stm32f10x_md.o } ER_IROM2 0x20008000 0x00004000 ; load address = execution address第二个运行时域, { 起始0x20008000,大小0x00004000 .ANY (+RO) IAP第二阶段加载到SDRAM中运行 } RW_IRAM1 0x20000000 0x00008000 ; RW data 把可读写的数据和初始化为0的数据放在内存SDRAM的开头 { .ANY (+RW +ZI) } } 做个比喻:就像一列火车在起始地址0x08000000装上大小0x00004000的货物,然后把特定的货物送到指定的地方拿下来运行或者存放。上面这辆火车就停了3个地方。 编译时出现一下警告: warning: L6314W: No section matches pattern address(RO). 在Target中的Linker中有一栏Misc controls,键入 --diag_suppress=L6314 即可,如下

STM32库开发实战指南-M4:49-在SRAM中调试代码

第49章在SRAM中调试代码 本章参考资料:《STM32F4xx中文参考手册》、《STM32F4xx规格书》、《Cortex-M3权威指南》、《Cortex-M4Technical Reference Manual》(跟M3大部分是相同的,读英文不习惯可先参考《Cortex-M3权威指南》)。 学习本章时,配合《STM32F4xx中文参考手册》“存储器和总线结构”及“嵌入式FLASH接口”章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。 49.1在RAM中调试代码 一般情况下,我们在MDK中编写工程应用后,调试时都是把程序下载到芯片的内部FLASH运行测试的,代码的CODE及RW-data的内容被写入到内部FLASH中存储。但在某些应用场合下却不希望或不能修改内部FLASH的内容,这时就可以使用RAM调试功能了,它的本质是把原来存储在内部FLASH的代码(CODE及RW-data的内容)改为存储到 SRAM中(内部SRAM或外部SDRAM均可),芯片复位后从SRAM中加载代码并运行。 把代码下载到RAM中调试有如下优点: ?下载程序非常快。RAM存储器的写入速度比在内部FLASH中要快得多,且没有擦除过程,因此在RAM上调试程序时程序几乎是秒下的,对于需要频繁改动代 码的调试过程,能节约很多时间,省去了烦人的擦除与写入FLASH过程。另外, STM32的内部FLASH可擦除次数为1万次,虽然一般的调试过程都不会擦除这 么多次导致FLASH失效,但这确实也是一个考虑使用RAM的因素。 ?不改写内部FLASH的原有程序。 ?对于内部FLASH被锁定的芯片,可以把解锁程序下载到RAM上,进行解锁。 相对地,把代码下载到RAM中调试有如下缺点: ?存储在RAM上的程序掉电后会丢失,不能像FLASH那样保存。 ?若使用STM32的内部SRAM存储程序,程序的执行速度与在FLASH上执行速度无异,但SRAM空间较小。 ?若使用外部扩展的SDRAM存储程序,程序空间非常大,但STM32读取SDRAM的速度比读取内部FLASH慢,这会导致程序总执行时间增加,因此在 SDRAM中调试的程序无法完美仿真在内部FLASH运行时的环境。另外,由于 STM32无法直接从SDRAM中启动且应用程序复制到SDRAM的过程比较复杂 (下载程序前需要使STM32能正常控制SDRAM),所以在很少会在STM32的 SDRAM中调试程序。 49.2STM32的启动方式 在前面讲解的STM32启动代码章节了解到CM-4内核在离开复位状态后的工作过程如下,见图49-1:

ARM映像文件及其地址映射.

ARM映像文件及其地址映射 2010-06-24 14:16 1、什么是arm的映像文件, arm映像文件其实就是可执行文件,包括bin或hex两种格式,可以直接烧到ROM里执行。在axd调试过程中,我们调试的是axf文件,其实这也是一种映像文件,它只是在bin文件中加了一个文件头和一些调试信息。 映像文件一般由域组成,域最多由三个输出段组成(RO,RW,ZI),输出段又由输入段组成。所谓域,指的就是整个bin映像文件所处在的区域,它又分为加载域和运行域。对于嵌入式系统而言,程序映象都是存储在Flash存储器等一些非易失性器件中的,而在运行时,程序中的RW段必须重新装载到可读写的RAM中。简单来说,程序的加载时域就是指程序烧入Flash中的状态,运行时域是指程序执行时的状态。一般来说flash里的整个bin文件所在的地址空间就是加载域,当然在程序一般都不会放在 flash里执行,一般都会搬到sdram里运行工作,它们在被搬到sdram里工作所处的地址空间就是运行域。我们输入的代码,一般有代码部分和数据部分,这就是所谓的输入段,经过编译后就变成了bin文件中ro段和rw段,还有所谓的zi段,这就是输出段。在ARM的集成开发环境中,只读的代码段和常量被称作RO段(ReadOnly);可读写的全局变量和静态变量被称作RW 段(ReadWrite);RW段中要被初始化为零的变量被称为ZI段(ZeroInit)。对于加载域中的输出段,一般来说RO段后面紧跟着RW段,RW段后面紧跟着ZI段。在运行域中这些输出段并不连续,但RW和ZI一定是连着的。ZI段和RW段中的数据其实可以是RW属性。 2、简单地址映射 对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项output中指定RO Base和RW Base,即在simple模式下,告知连接器RO和RW的连接基地址。 这种模式下,ARM Linker会输出以下符号,它们指示了在运行域中各个输出段所处的地址空间,在使用的时候可以用IMPORT引入: | Image$$RO$$Base|:表示RO段在运行域中的起始地址 |Image$$RO$$Limit|:表示RO区末地址后面的地址,即RW数据源的起始地址 |Image$$RW$$Base|:RW区在RAM里的执行区起始地址,也就是编译器选项 RW_Base指定的地址 |Image$$ZI$$Base|:ZI区在RAM里面的起始地址 |Image$$ZI$$Limit|:ZI区在RAM里面的结束地址后面的一个地址 RO Base对应的就是| Image$$RO$$Base|,RW Base 对应的是

为C++程序添加文件保存加载功能

为C++程序添加文件保存加载功能 一、引子 为什么要浪费时间去设计一个算法来实现数据的文件存储还要费劲地调试代码呢?Boost库可以为你做这些事情。借助于串行化模板,你可以容易地把数据存储到你自己定制格式的文件中。本文将教给你如何轻松地存储数据并回读数据。 二、概述 当你开发一个软件包时,你总是想集中精力于软件的功能。而最让你担心的是,花费大量的时间写代码,而该代码有可能会应用在另外大量的其他程序中。那正是重用的含义所在,你会希望另外某人已经为你编写出这样现成的代码。 这类问题的一个很好的例子是给予你的程序存档的能力。例如,你可能在写最伟大的天文学程序-在该程序中,你的用户可以轻易地输入时间和坐标,你的程序负责绘制当前天空的地图。但是,假定你赋予你的用户能够高亮某些星星,这样以来它们可以容易地突出在地图上。最后,你让用户能够保存他们的配置以备后用。 你的程序集中于天文学编程。你并不是在写一个通用库来保存文档,所以你不必把大量的时间花在存储功能上,因为你要专注于程序的天文学特性。如果你是用C++编程,你可以从Boost重用库得到帮助。为了保存文件,Boost库包括一个串行化类,正是你需要的。 如果你成功地创建了你的程序工程,很可能有一个类来包含用户信息或文档。例如,你可能有一个类,该类列举用户们最喜欢的星星的名字和位置。(请原谅这里的简化)。这就是你希望用户能够保存到磁盘上的数据。毕竟,几乎所有的程序都有文件保存功能。微软的Word保存文本和格式化数据,而Excel保存工作单数据。一个优秀的地图程序可以用户保存喜欢的位置,GPS路线,旅程,等等。 借助于Boost串行化库的帮助,实现保存很容易-所要做是仅仅是设置好你的类,而由库来负责其它一切-使你专注于真正的工作。 其思想是很简单的:你创建了一个包含用户数据的对象。当准备保存信息时,用户选择File|Save As,然后从文件对话框中选择一个文件名即可。借助于Boost,你的程序就把数据保存到选定的文件中。以后,当用户重新启动该程序时,选择File|Open,选定已保存的文件,你的程序再一次使用Boost-但是这一次重新装入数据,因此,重新产生了该对象。

stm32固件库函数使用keil时的常见错误分析

error: A1355U: A Label was found which was in no AREA 终级解决办法 分类:深入C语言2009-12-30 11:26208人阅读评论(0)收藏举报error: A1355U: A Label was found which was in no AREA 在KEIL工程中,我使用另一个工程中正确的分散加载文件到一个新工程中,竟然就出现这样的提示。 到网上搜索,有很多内容是关于这个问题的,但是却没有几个很好解决问题的。 其实出现这个问题有两种情况,一种是出现在.s的汇编文件中,另一种是出现在.scf (或者.scat)等的分散加载文件中。 原来很多人在汇编文件(多数为.s的启动代码)中出现这个问题的,基本上是因为使用汇编的格式不对, 关于这个错误,ARM官网有相关的说明: A1355U: A Label was found which was in no AREA Example: This can occur where no white-space precedes an assembler directive. Assembler directives must be indented with white-space, for example: use: IF :DEF: FOO ; code ENDIF not: IF :DEF: FOO ; code ENDIF Symbols in the left hand column 1 are assumed to be labels, hence the error message. 意思是在编写汇编文件时,标号要顶格写,而其他的代码都要用空格或者TAB键来使代码进行缩进,这样,就不会出现编译的问题了。 如果问题是出现在分散加载文件中,那么很可能你是把分散加载文件一起加入到了KEIL的工程中,类似这样: 这时候,就可能会出现 error: A1355U: A Label was found which was in no AREA这样的报错,不管你用多么正确的SCATTER文件格式,它始终都会报错。为什么呢? 原来我们通常加到KEIL工程中的.c ,.s, .h等文件是供ARMCC,ARMASM等编译器处理的,而分散加载文件 .scf ,.scat等文件是供ARMLINKER等连接器处理的,当我们把分散加载文件加入到工程中后,KEIL会调用ARMASM编译器将分散加载文件做为汇编文件去处理,因为分散加载文件本来就不是合法的汇编文件,所以在编译的时候就出现了编译错误的提示。 解决方法:在工程中删除分散加载文件,在option->linker->scatter file路径中指定即可,如图:

试图搞懂MDK程序下载到Flash(四)--生成bin文件下载到Nor Flash

试图搞懂MDK程序下载到Flash(四)--生成bin文件下载到Nor Flash 喜讯啊!!下载到flash中的一种实现了啊!!!鸡冻了!终于可以脱机运行了,尽管是下载到了Nor Flash中运行,还没有下载到Nand Flash中运行,但是这也是一个进步了吧,下面详细介绍一下MDK编译的程序下载到Nor Flash运行的步骤。 1、编写程序 我的主程序功能是实现UART的输出功能,工程文档结构图如下: 需要自己编写的文件有三个main.c uart.c uart.h,下面贴出这三个文件的实验代码:main.c文件 #include #include"uart.h" int main() { unsigned int a=10; //系统时钟初始化, FCLK=400MHz,HCLK=100MHz,PCLK=50MHz Uart0_Init(115200); //初始化并设置波特率为115 200 while(1) { Uart0_Printf("Uart0_Printf test output is:%d\n",a); } } uart.c文件

#include #include #include"uart.h" #define PCLK 50000000 #define UART_BRD (int)((PCLK/(baudrate*16))-1) /*********************************************** *函数名称:void Uart0_Init(unsigned int baudrate) *参数说明:baudrate:波特率 *返回值:无 *全局变量: 无 *功能:对UART0进行初始化 ************************************************/ void Uart0_Init(unsigned int baudrate) { GPHCON&=~((3<<4)|(3<<6)); //GPH2--TXD0;GPH3--RXD0 GPHCON|=((2<<4)|(2<<6)); //设置GPH2、GPH3为TXD0、RXD0功能 GPHUP=0x00; //上拉电阻使能 ULCON0|=0x03; //设置数据发送格式:8个数据位,1个停止位,无校验位 UCON0=0x05; //发送模式和接收模式都使用查询模式 UBRDIV0=UART_BRD; //设置波特率,其中波特率作为一个参数传递到该初始化函数 URXH0=0; //将URXH0清零 } /*********************************************** *函数名称:void putc(unsigned char c) *参数说明:c:通过串口接收到的字符,注意这里是8位数据 *返回值:无 *全局变量: 无 *功能:将通过串口接收到的字符发送给PC机并显示在 * 串口调试工具。 ************************************************/ void putc(unsigned char c) { UTXH0=c;

ARM7的Bootloader和分散加载文件笔记

Boot Loader概述 简单地说,在操作系统内核运行之前,通过一小程序,可以初始化硬件设备、建立内存空间的映射图等,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核配置好相应的环境,也可以下载文件到系统板上的SDRAM,对Flash进行擦除与编程,这个小程序一般称为Boot Loader。可以说,一个功能完善的Boot Loader已经相当于一个微型的操作系统了。 Boot Loader作为系统复位或上电后首先运行的代码,一般应写入Flash存储器并从起始物理地址0x0开始。Boot Loader是非常依赖于硬件而实现的,而且根据实现的功能不同,其复杂程度也各不相同。一个简单的Boot Loader可以只完成USB口的初始化,而功能完善的Boot Loader可以支持比较复杂的命令集,对系统的软硬件资源进行合理的配置与管理。因此,建立一个通用的Boot Loader 几乎是不可能的。 系统初始化代码直接对ARM微处理器内核及硬件控制器编程,多采用汇编语言编程,初始化代码一般应包括如下典型任务: 1.定义程序入口点; 2.设置异常和中断向量表; 3.初始化存储设备; 4.初始化堆栈指针寄存器; 5.初始化用户执行环境; 6.呼叫主应用程序。 1.1 定义程序入口 初始化代码必须定义整个程序的入口点。通过伪指令Entry指定编译器保留该段代码,同时配合链接器的设置,确定整个程序的入口点。 1.2 设置异常和中断向量表 1.3初始化存储设备 1. 存储器类型和时序的配置 2.存储器的地址分配与地址重映射 一种典型的存储器地址重映射过程描述如下:当系统上电或复位以后,PC指针指向0x0,程序从0x0地址开始执行,因此,为了能正确读取代码,要求此时Flash (或其它类型的ROM)的起始地址为0x0。但Flash(或其它类型的ROM)的访问速度大大低于RAM,每次产生异常后,都要从Flash(或其它类型的ROM)的异常向量表调转到相应的处理程序,会影响异常的响应速度,因此,系统便提供一种灵活的地址重映射方法,在系统完成必要地初始化以后,将RAM安排到0x0 地址处,而将原来位于0x0处的Flash(或其它类型的ROM)安排到其他的地方上去,加快异常的响应速度。

Cortex-M3在MDK下汇编程序分散加载文件出错的解决方法

Cortex-M3在MDK下汇编程序分散加载文件出错的解决方法 1.对于汇编调试,不需要添加启动,仅设置堆栈即可 2.默认分散加载文件如下(LM3S615,其实其他也差不多,都是自动生成):LR_IROM1 0x00000000 0x00008000 { ; load region size_region ER_IROM1 0x00000000 0x00008000 { ; load address = execution address *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00002000 { ; RW data .ANY (+RW +ZI) } } 需要注意的是,自己写的程序的入口必须是RESET,如下程序所示:STACK_TOP EQU 0x20002000 AREA RESET,CODE,READWRITE DCD STACK_TOP DCD START ENTRY START MOV R0, #10 MOV R1, #0 loop ADD R1, R0 SUBS R0, #1 BNE loop deadloop B deadloop END 然后在编译的时候linker选项下如下图所示:

编译之后输出结果如下所示: Build target 'Target 1' assembling test.s... linking... test.sct(8): warning: L6314W: No section matches pattern *(InRoot$$Sections). Program Size: Code=24 RO-data=0 RW-data=0 ZI-data=0 "test.axf" - 0 Error(s), 1 Warning(s). 此时,会出现这个警告。据网友们说是无关紧要的。虽然如此,但是看着不爽。后来查看了帮助文档,找到了消除这个警告的方法,在linker选项中加上如下一行: 虽然现在警告已经消除了,但是仍然不明白为什么。也就是没有搞懂这个分散加载文件中的 *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) 这几行是什么意思,有待高人指点!

如何在Keil4下通过J-Link下载文件数据到STM32外部的Nor Flash

如何在Keil4下通过J-Link下载文件数据到STM32外部的Nor Flash 实验目的: 把一张320*240像素的图片文件,通过J-Link下载到STM32外部的Nor Flash里去,然后再读到LCD显示屏显示出来。用LCD转换图片工具把图片搞成一个C文件。 准备烧写算法: 声明:图例中用到的地址需要根据你自己的情况作相应调整。 Keil单片机开发平台是根据预先设定好的Flash烧写算法将用户程序烧到单片机的Flash内部的,那么由于这个算法是固定的,我们往往不关心,所以我们对其原理不是很了解,实际上,我们都知道,要将程序烧进去,需要在工程选项中选择对应的单片机型号,如下图所示。

这个过程就是准备Flash的烧写算法,选中了某个单片机型号,就确定了其使用的Flash特征,那么这些算法藏在什么地方呢。这些算法就藏在Keil安装的根目录下X:\Keil\ARM\Flash目下,该目录下有很多的文件夹,每个文件夹里有对应的工程,每个工程都是某种型号单片机的Flash烧写算法,他们具体的内容不同,但是具有统一的接口,以便被Keil调用。 现在我们要烧写STM32的外部Nor Flash,所以我们要新建一个算法: 新建一个文件夹,如Test。将X:\Keil\ARM\Flash\STM32F10x\下的所有文件拷进Test文件夹来,将X:\Keil\ARM\Flash\SST39x160x\下的FlashPrg.c文件也拷进Test文件夹来并覆盖同名文件,将X:\Keil\ARM\Flash\下的FlashOS.h 文件也拷进Test文件夹来。如果你使用的Nor Flash不在MDK自带驱动的范围内,那么需要根据FlashPrg.c文件自行编写相关函数。 打开工程文件STM32Fx.uvproj,此时会报一个错误,提示没有选择STM32的Device型号。 这时你根据你的STM32的型号选择相应的就OK了,如下图所示。

相关主题
相关文档
最新文档