由于在暑假匆忙接收的嵌入式项目中需要使用特别大的数组,非分页RAM的内存不够用了,没办法,硬着头皮尝试使用分页RAM,但是完全没有单片机的基础,导致极其的困难。之前写程序都是按照纯软件的思维,主要考虑架构,不会考虑到每个变量具体存在哪个物理地址这么底层的问题,结果被飞思卡尔这分页地址、prm文件什么的搞得一头雾水,而网上的资料又少,讲的又大同小异的笼统,最后写出来的程序因为这分页地址的原因存在各种问题(还以为把变量放到分页RAM了,结果现在稍微懂了点回去看,发现其实很多根本还是分配在非分页区。晕倒~。但是居然还能相对正常运行也是很神奇)。这些天各种找相关的资料,结果发现在CodeWarrior的官方文档资料里其实把我想知道的都讲的很清楚了(还是官方文档给力,以后学什么东西直接找官方文档,不去到处找网上一堆零零散散的资源来学了)。本着学习的态度,将逐步把官方文档翻译一遍,供大家一起交流学习进步。
翻译的资料是公开的,我想应该不会有什么版权问题,如涉及版权问题,请联系我删除文章,原文档在这里(https://www.nxp.com/pages/codewarrior-development-studio-for-hcs12x-microcontrollers-classic-ide-v5.2:CW-HCS12X?&tab=Documentation_Tab&linkline=Users-Guides),另感谢NXP提供的学习资料。
理解S12(X)架构中的地址映射方案
by Christian Michel Sendis
译者注:译者博客(http://blog.csdn.net/lin_strong),转载请保留这条。此为官方文档AN3784,仅供学习交流使用,请勿用于商业用途。这篇文章中的memory(内存)和map,译者很多情况下直接翻译为“地址”,译者认为这篇文章中,很多情况下这几个词是同义词,都是指存放字节的一个地方。
目录
1. 介绍
2. CPU 本地地址
3. 分页窗口
4. 内存页
5. 控制各个对象在内存中放置的位置
介绍
在一个S12或S12X架构中,很有必要分清楚两种类型的内存地址:banked和non-banked。这篇文档描述了应该怎么样正确的访问某个内存地址,同时还较详细地描述了CodeWarrior的链接器是怎么把你的代码分配在这两种地址中的。理解你的应用是怎么使用内存的将有助于避免掉入常见的陷阱,还能帮助你发现哪里还有代码优化的空间。
HCS12(X)的地址总线位数导致不是所有的内存地址都是平等的。由于HCS12(X) CPU地址总线的位宽是16位,它可以直接访问的地址大小也就是16位可以访问的大小。16位地址可以访问的字节数为:2^16=65536字节,或者说64kB。当你有超过64kB的内存时,64kB外的地址就没办法用16位来编码了。
Non-banked内存指的是那些可以直接通过16位地址来访问的内存地址。
Banked内存指的是需要通过额外的行为来扩展HCS12(X) CPU寻址能力后才能访问的内存地址。
Banked和noe-banked分别是分页和非分页的同义词。分页和非分页这两个词源于内存页这个主意,内存页这个概念被用于扩展内存寻址能力。在Freescale的文档中,这些同义词常是可替换的。
为了理解一个应用是怎么访问banked内存的,你需要理解以下三个概念:
- CPU本地地址(CPU Local Map)
- 分页窗口(Page Window)
- 内存页(Memory Page)
CPU本地地址
CPU本地地址这个词指的是CPU可以通过它的指令集而直接访问的64kB地址空间。这64kB寻址空间包含了不同类型的内存资源:寄存器地址,RAM,EEPROM和Flash。你可以把CPU本地地址看做是访问这些物理地址的入口。
当读或写CPU本地地址的其中某个地址时,内存映射控制(MMC)模块会把这个本地地址翻译为另一个物理地址。MMC转换本地的16位地址为一个不同的物理地址,这个物理地址使用23位来编码(见图1)。我们之所以强调这个是因为,在HCS12(X)中,这23位全局地址空间也可以通过特殊的指令直接访问。MMC模块被集成在芯片上以把特定的本地地址与特定的芯片上内存资源联系起来。
图 1. MMC模块的地址翻译过程
HCS12家族的CPU本地地址
图2展示了对于一个HCS12设备,64kB空间内的地址是怎么联系到特定的内存资源的。寄存器以及其他内存资源有着专用的地址范围。这图是S12DP512的。
对于HCS12家族,不同设备可能有不同的本地地址映射图,然而它们却有两个共有特性:
- 首个共有特性是:虽然RAM、EEPROM和寄存器空间的访问可能在每个设备上不同,但是在上电重置后,由MMC映射的RAM、EEPROM和寄存器的默认地址总是在本地地址的头16kB(从0x0000到0x3FFF)内的。只有在HCS12家族中,你可以通过写MMC模块内特殊的INIT寄存器来修改EEPROM、RAM和寄存器空间的位置。欲知详情,请查阅你的设备的文档。
- 第二个共有特性是:低48kB(从地址0x4000到0xFFFF)存放着Flash内存。这个Flash区域被分为三个大小为16kB的块。中间那个16kB,从0x8000到0xBFFF,被称为Flash分页窗口(见 分页窗口)。
.
图 2.HCS12的CPU本地地址映射
HCS12X家族的CPU本地地址
在HCS12(X)家族中,所有设备的CPU本地地址映射是一致的。图3展示了64kB地址空间是怎么分配给特定内存资源的。所有HCS12(X)设备的CPU本地地址映射的分布是一致的。
图 3.HCS12(X)的默认CPU本地地址映射
分页窗口
在HCS12和HCS12X架构中,分页窗口的概念是一致的。在CPU本地地址内的大部分地址总指向定义好的固定物理位置。然而一些特定的地址不总指向同样的物理位置。这些特别的地址范围被称为分页窗口(page windows)。在一个分页窗口内的本地地址是16位的,这16位不足以让MMC模块确定其对应物理位置。
对于分页窗口范围内的本地地址,MMC模块需要额外的信息来翻译给定的本地地址为想要的物理地址,这个信息被存储在一个寄存器内。存储这个信息的寄存器被称作分页寄存器(page register)。
HCS12架构只包含一个用于Flash内存访问的分页窗口。这个分页窗口位于地址0x8000到0xBFFF。其对应的分页寄存器是PPAGE寄存器,这寄存器用于选择Flash分页窗口实际指向哪一部分物理内存。
改变PPAGE寄存器的值会改变映射在CPU本地地址的分页窗口内的内容。
HCS12X CPU本地地址包含三个分页窗口:一个用于EEPROM、一个用于RAM、还有一个用于Flash。每个分页窗口使用特定的分页寄存器来选择其实际指向的物理内存。EPAGE分页寄存器用于EEPROM的分页窗口;RPAGE用于RAM的分页窗口;PPAGE用于Flash的分页窗口。
改变一个分页寄存器的值将改变本地地址的对应分页窗口中映射的内容。
内存页
内存页是物理内存的一块有固定大小的连续部分。页大小与是什么内存资源有关:EEPROM是1kB,RAM是4kB,Flash是16kB。一种内存资源的内存页的大小与这内存资源在本地地址中对应资源的分页窗口大小有关。
这种对物理内存的分页只是一种概念上的划分。内存在物理上并不是真的分成了一页一页的。每个分页使用一个分页号来标识。为了让某个分页映射到分页窗口内,需要将其分页号写进对应的分页寄存器。分页号在芯片集成时就定义好了。定分页号的方案如下:
- 在HCS12家族中,分页被按顺序编号,最后一页内存的分页号固定为0x3F。比如:如果你的S12设备有32kB的Flash,那么Flash会被概念上分成两个16kB的页,分页号分别为0x3E和0x3F。如果它有48kB的Flash,就会有3个分页,分页号分别为0x3D、0x3E和0x3F。
- 在HCS12(X)家族中,分页被按顺序编号,最后一页内存的分页号固定为0xFF。比如:如果你的S12X设备有8kB的RAM,RAM将会被概念上分为两个4kB页,分页号为0xFE和0xFF。如果它有16kB的RAM,那么就会有4个RAM分页。
-
注意:
- 所有的分页和分页号的概念只在访问banked地址时才有用, 你必须要明白,这种概念上的划分为编号分页是对整个内存资源的划分。在某个内存资源内的任何地址都有对应的分页号,不管你用不用的到它。换句话说,不管某个内存地址会不会被通过页面切换机制访问或者直接地访问,它就在那里。
分页切换机制
为了在本地地址的分页窗口中访问一个特定的物理页,需要先把其分页号写入分页寄存器。比如,如果你是用高级语言写的代码,比如用C,CodeWarrior编译器会负责产生合适的指令,对分页寄存器的操作对用户是透明的(译者注:“透明”即你不需要管它)。这样,用户只需要确保编译器正确地理解了哪些变量或函数是放置在banked地址中的,以使编译器能产生所需的额外指令。这是通过在工程创建时选择个合适的地址模型或者通过在声明变量或函数时使用特殊的语法来实现的。
一旦需要的物理内存页被映射到了分页窗口内,CPU就可以使用16位寻址来访问其中的数据了。
-
注意:
- 你可以通过分页窗口来访问任意分页内存资源的任意内容。但是,每次你需要访问这样一个内存地址之前的那个写分页寄存器的操作会带来可观的开销。这就是为什么有些特定地址被直接映射到本地地址上,它们与任何分页寄存器的值无关,被称为non-banked,或者非分页地址。对于这些地址,通常不会使用分页访问,而是更喜欢使用直接访问。
HCS12设备的分页切换
图4展示了CPU本地映射,其中non-banked地址上标记了它们总是映射的地址的对应分页号。后面的例子示例了HCS12设备的页面切换。
图 4. CPU本地地址映射
HCS12下non-banked地址的例子
本地地址值0xC000对应于一个non-banked地址。根据图4 这个地址指向Flash分页0x3F的首个字节。读写地址0xC000总是会访问同一个物理地址,不管你怎么设置分页寄存器的值。
你也可以通过向PPAGE寄存器写入0x3F的方式来访问物理Flash分页0x3F的首个字节。这样Flash分页0x3F的所有内容就会出现在0x8000到0xBFFF间了。Flash分页0x3F的首个字节出现在本地地址0x8000处。
这两种方式都是正确的;但是直接访问0xC000的开销更小,因此更推荐。
HCS12下banked地址的例子
假设你想要读Flash分页0x3C的首个字节。这种情况下,Flash分页0x3C无法在CPU本地地址的non-banked地址找到(参照图4)。唯一的解决方案是使用Flash分页窗口来访问。应用必须向PPAGE寄存器中写入0x3C,然后访问位于本地地址0x8000的这个分页的首个字节。
HCS12X设备的分页切换
图5 在HCS12X的本地地址中的non-banked区域上标记了其对应指向的物理地址分页号。后面跟着的例子示例了HCS12X设备的页面切换。
图 5. HCS12X带有分页号的本地地址映射
HCS12X下non-banked地址的例子
本地地址0xC000对应于一个non-banked地址,它不属于任何分页窗口。根据图5,这个地址指向Flash分页号0xFF的首个字节。读写地址0xC000总是访问通过物理地址,跟任何分页寄存器的值无关。
你也可以通过向PPAGE寄存器写入0xFF的方式来访问物理Flash分页0xFF的首个字节。这样Flash分页0xFF的所有内容就会出现在0x8000到0xBFFF间了。Flash分页0xFF的首个字节出现在本地地址0x8000处。
这两种方式都是正确的;但是直接访问0xC000的开销更小,因此更推荐。
HCS12X下banked地址的例子
假设你想要读Flash分页0xFC的首个字节。这种情况下,Flash分页0xFC无法在CPU本地地址的non-banked地址找到(参照图5)。唯一的解决方案是使用Flash分页窗口来访问。应用必须向PPAGE寄存器中写入0xFC,然后访问位于本地地址0x8000的这个分页的首个字节。
在HCS12和HCS12X的例子中,CodeWarrior的C编译器都会负责自动地在访问一个分页地址之前自动地插入指令来写对应的分页寄存器。然而,为了确保这件事情会发生,程序员需要选择最合适应用的地址模型,并最终使用特别的标识符,如关键词__near或__far,或者#pragma声明来在需要的地方修改编译器行为。
HCS12X设备的全局访问
在S12架构中,一个对象可以有的最大大小是16kB。这是受限于CPU本地地址映射,在其中,同时可被CPU访问的最大连续内存空间是16kB。在S12架构中,企图分配一个大于16kB的对象会导致一个链接器错误。
为了减少这个限制,在S12X架构中,引入了另一种寻址方法:全局访问。
全局访问使得可以访问,在一个“新的”地址空间中的,最大64K的连续内存区域,这个空间被叫做全局地址空间。
在S12X CPU看来,有两种可以访问数据的64kB地址空间:
- 64kB CPU本地地址
- 64kB 全局地址
.
这64kB全局地址完全独立于64kB本地地址
为了指示CPU访问全局地址而不是更常使用的本地地址,程序员需要使用特殊的全局指令。这些全局指令有:GLDAA、GLDAB、GLDD、GLDS、GLDX、GLDY、GSTAA、GSTAB、GSTD、GSTS、GSTX 和 GSTY(详见CPU Block Guide)。
示例
- GLDAA $100 向累加器A中加载存储在全局地址0x100处的值
- LDAA $100 向累加器A中加载存储在本地地址0x100处的值
.
我们已经学习了分页窗口和分页寄存器的概念,对于64kB全局地址,我们可以把它理解为一个64kB的分页窗口,其中的内容取决于第四个分页寄存器—GPAGE。
全局地址是个覆盖了8Mb地址空间的23位地址,从地址0x000000到0x7FFFFF。在这个线性全局地址空间中,所有的内存资源都被分组,GPAGE寄存器可以被用于访问所有的RAM、EEPROM和FLASH地址,以及外部地址空间。
在向GPAGE中写入正确的值后,全局地址的内容就只能通过全局指令来访问了。
GPAGE的值的选取方式与其他分页寄存器的方式很相似:
- 写0xFF到GPAGE中会使全局地址映射总地址的最后64kB。
- 写0xFE会映射物理地址的的倒数第二个64kB。
什么时候使用全局寻址
使用全局寻址主要是由于两个原因:
- 当需要链接非常大的对象,大到由于本地地址中没有足够的连续地址空间而无法被链接时。这种情况下,使用全局寻址使得程序员能够通过全局指令来访问高达64kB的连续地址空间。可被链接的单个数据对象的最大大小是64kB。
- 当在运行被分页存储的代码的同时,试图访问同一种内存资源中的被分页存储的对象(变量或常量)。
.
比如,当应用需要访问分配在某个给定Flash分页中的常量,但当前执行的代码却跑在一个不同的Flash分页中。通常,当在S12架构中遇到这种情况时,会使用一个non-banked运行时例程来访问分页的对象。
在S12X中,当在分页Flash中运行时,可以使用这个新的全局访问方式来访问Flash中的任何地址,不需要碰PPAGE寄存器,也不需要跳到一个non-banked例程。
为了指示编译器使用全局访问来访问某个对象,可以把它声明在一个 #pragma DATA_SEG __GPAGE_SEG 块或#pragma CONST_SEG __GPAGE_SEG 块中,这取决于对象的特性。
S12X本地地址再映射能力
在新的S12X设备上,MMC模块可以由用户来配置0x4000到0x7FFF之间的CPU本地地址。这部分的本地地址默认是用来映射Flash的,但是它可以被配置来映射RAM或者外部空间,因此给用户提供了更大的灵活性,更灵活的配置哪些地址是non-banked的。请参考S12X编译器手册的编译器选项 -Map以及你的设备的datasheet,MMC模块,来获取更多关于这一特性的信息。
现在你已经见识到了访问内存地址的两种不同的方式。下一章会描述,怎么指示CodeWarrior链接器来放置我们的代码和变量到需要的内存地址中。我们还将看到,当使用C语言来开发时,怎么确保CodeWarrior的编译器知道某个对象应该放在banked还是non-banked内存地址中,以产生合适的代码。
控制对象在内存中的分配
这个部分描述了CodeWarrior连接器默认是怎么在内存中放置对象的,以及怎么改变这个默认行为以定制我们的应用。
名词 对象(objects) 指的是在内存中有固定地址的实体。可以是:
- 函数(代码)
- 变量(放置在RAM中的数据和数组)
- 常量(放置在Flash中并被标识为”const”的数据)
- 字符串(没有被预定义为数组的字符串字面值)
比如:printf( “Hello World”)将会产生字符串”Hello World”。相反地,声明一个变量为
unsigned char Message[] = “Hello World”;
的话,链接器会认为它是一个数组而不是一个字符串。
.
对象的位置由#pragma声明来控制。后面的内容并不是对#pragma声明的完全描述,只列出了那些常用的而已。如果想要详细描述的话,请参考放在你的CodeWarrior安装路径下编译器和搭建工具的手册。
我们将看到四种#pragma声明:
- #pragma CODE_SEG
- #pragma DATA_SEG
- #pragma CONST_SEG
- #pragma STRING_SEG
.
这四种声明都可以插入到C源文件中,并用于控制声明后面的对象的位置和特性。
怎么使用pragma声明来控制对象的位置
让我们先看一个例子。这里,一个变量、一个常量和一个函数被放在一个明确的placement section中。placement section是指向内存中特定区域的标签,被定义在项目的链接器参数文件(*.prm文件)的PLACEMENT块中。下一章中给出了链接器参数文件的结构,用于参考。
unsigned char variable1;
const unsigned char constant1;
void function1(void)
{
/* 代码 */
}
. #pragma声明并不是强制的。在上例中就没有#pragma声明。这种情况下,链接器使用默认行为并将放置对象到他们的默认位置。
- DEFAULT_ROM 是代码的默认位置
- DEFAULT_RAM 是变量和数组的默认位置
- ROM_VAR 是常量(ROM变量)的默认位置
.
DEFAULT_ROM、DEFAULT_RAM 和 ROM_VAR是定义在链接器参数文件的PLACEMENT块内标签。他们是CodeWarrior认识的特殊关键字。链接器认得它们,比如,ROM_VAR是常量在缺少#pragma声明时应该被放置的位置。
在这个例子中:
- variable1 将被放在 DEFAULT_RAM
- constant1 将被放在 ROM_VAR
- function1 将被放在 DEFAULT_ROM
.
链接器参数文件定义了每个placement section对应的地址范围。
下一个例子说明了#pragma声明的用法,以防万一用户想要修改连接器的默认行为
#pragma DATA_SEG MYVARIABLES (1)
unsigned char variable1;
#pragma DATA_SEG DEFAULT (2)
unsigned char variable2;
#pragma CONST_SEG MYCONSTANTS (3)
const unsigned char constant1;
#pragma CONST_SEG DEFAULT (4)
const unsigned char constant2;
#pragma CODE_SEG MYCODE (5)
void function1(void)
{
/* function1 内的代码 */
}
#pragma CODE_SEG DEFAULT (6)
void function2(void)
{
/* function2 内的代码 */
}
这里有一些定义#pragma声明的行为的规则:
- #pragma声明只对相关的对象起作用。比如,一条#pragma DATA_SEG声明不会影响常量的位置。只有#pragma CONST_SEG声明可以影响常量对象的位置。
- #pragma声明只对声明在#pragma后面的对象产生影响,并且一直影响到遇到另一个同特性的#pragma语句或者到编译单元(见以下注释)的结束。
.
在上例中,有六个#pragma声明,它们被编号以便于后文好引用它们。
在这个例子中,我们假设标签MYVARIABLES、MYCONSTANTS和MYCODE是由用户定义在项目链接器参数文件内的placement section的名字。
- #pragma声明(1) 会导致variable1被分配在placement section MYVARIABLES。
- #pragma声明(2) 会结束pragma声明(1)的影响。它会导致variable2被分配在它的默认位置,也就是DEFAULT_RAM。
- #pragma声明(3) 会导致constant1被分配在placement section MYCONSTANTS。
- #pragma声明(4) 会结束pragma声明(3)的影响。它会导致constant2被分配在它的默认位置,也就是ROM_VAR。
- #pragma声明(5) 会导致function1被分配在placement section MYCODE。
- #pragma声明(6) 会结束pragma声明(5)的影响。它会导致function2被分配在它的默认位置,也就是ROM_VAR。
-
注意:
- 我们之前提到过,一条#pragma声明的影响一直会持续到遇到下一个#pragma声明,或者到达编译单元的末尾。一个编译单元相当于源文件加上所有它包含进的头文件。这意味着在一个*.h头文件内的#pragma声明可以影响那些包含了它的源文件内的对象的位置。这可能对于开发者来说是使很难追踪的。这就是为什么总是像上例中那样在一个#pragma声明的后面明确写出一个#pragma DEFAULT 声明是一个好的编程实践。 注意:
- 在上例中,只有链接器的默认行为被修改了。链接器的工作在所有对象被给定地址后就结束了。在上例中,用到的placement section可以是banked或non-banked的。链接器在一定程度上不关心这些。生成访问地址的指令是编译器的活。开发者要负责确保编译器知道哪些对象放置在banked地址以及non-banked地址中,以使编译器能生成正确的访问指令。比如:是否需要操作分页寄存器。
许多开发者使用默认的编译器设置。这样就不需要为应用中的不同对象分别定制编译器行为了。默认情况下编译器的寻址行为被称为地址模型(memory model)。这在下一章中会讨论。
编译器和链接器的默认行为
当使用项目向导创建一个新的CodeWarrior项目时,用户被要求选择一个地址模型,选项有:Small、banked和large地址模型。选择的地址模型将决定CodeWarrior的链接器会默认地会把你的代码以及变量放在哪里,以及决定CodeWarrior的编译器会怎么产生访问你的对象的指令。
这里描述了每个地址模型:
- Small memory model:你的代码和变量都会默认放在non-banked位置。
- Banked memory model:你的代码默认会放在banked地址中,但是你的变量会默认放在non-banked地址中。
- Large memory model:你的代码和变量都会默认放在banked地址中。
.
选择地址模型将会影响你的项目中的三个元素:
- 编译器选项
- ANSI库
- 链接器参数文件
项目的编译器选项
编译器的行为受到选择的地址模型影响。CodeWarrior项目向导在你项目的编译器选项中插入一个 -M 选项。有三种参数,取决于模型:-Ms、-Mb或-Ml。这个选项指示编译器根据模型的假定来编译。
Small地址模型对应选项 -Ms。编译器不会插入任何指令来处理任何分页寄存器。变量将会被直接访问non-banked地址,并且你的代码会使用JSR/RTS指令来执行。
Banked地址模型对应选项 -Mb。当访问你的代码的时候,编译器会使用处理PPAGE寄存器的指令。在调用一个函数的时候会使用CALL指令。CALL指令会负责在运行你的函数前把它的分页号写到PPAGE寄存器中。变量则会默认的按照non-banked地址的方式访问。
Large地址模型对应选项 -Ml。编译器将使用CALL指令来访问你的代码,并且在访问RAM和EEPROM变量前也会插入分页处理指令,它们被默认放在分页地址。因此这个地址模型对代码大小和执行时间非常不友好,在大部分情况下不推荐。
如果你需要访问分页区变量,大部分情况下用这个方法就够了:选择banked地址模型,然后每次变量要放到分页区的时候都用特别标识符来告知编译器。文档的后面部分会讨论怎么访问分页区变量。这使得你可以只在需要访问分页区变量时才进行分页访问,而不是默认对所有变量都这么访问。
-
注意:
- JSR/RTS和CALL/RTC指令会在后面部分被简短的讨论
项目的ANSI库
在项目向导中选择一个地址模型还会添加对应的ANSI库。通过预编译的*.lib文件加进项目的ANSI库需要与你项目选择的编译器选项兼容。
项目的链接器参数文件
这是指示链接器要到哪里放置你的代码的文件。根据选择的地址模型,不同的参数文件会被加入你的项目中。
图6展示了由CodeWarrior项目向导为一个S12XEP100设备创建的参数文件,这个项目中没有使用XGATE。
我们就仅仅看下small和banked地址模型对应的prm文件的PLACEMENT块,。
图 6. small地址模型
图 7. banked地址模型
所有的蓝颜色的标签都是CodeWarrior认得的特别关键词。这些标签都有特别的用处:
- DEFAULT_ROM 是你的代码会被默认分配的地方,也就是当没有#pragma CODE_SEG声明时的。它在参数文件中是强制要有的。
- DEFAULT_RAM 是你的变量会被默认分配的地方,也就是当没有#pragma DATA_SEG声明时的。它是参数文件中强制要有的字段。
- __PRESTART 指示启动代码要放在哪里。
- STARTUP 是Startup数据结构被放置的地方
- ROM_VAR 是存储常量的默认位置(用“const”声明的变量)
- STRINGS是你的字符串字面值默认会被分配的地方,也就是当没有#pragma STRING_SEG声明时的。(字符串字面值是传递给函数的字面值,比如printf(“hello world”); 中使用的”hello world”)
- NON_BANKED 是一个由库使用的特殊标签,用于存放那些必须non-banked的对象。这个标签也可以被程序员在他的代码中使用。
- VIRTUAL_TABLE_SEGMENT是C++应用专用的。
- COPY是ram对象的初始化值会被分配的地方。
比如,当你这样声明一个变量:
unsigned char variable=0xAA;
0xAA这个值就被存放到Flash资源的COPY部分了。启动代码会在每次重置后复制这个值到相应“变量”的位置。 - SSTACK 是你的栈被放置的地方。SSTACK的大小是由命令STACKSIZE决定的,这命令也出现在prm文件中。
.
任何黑颜色的文本都不是特殊关键词。(见图6和图7)在这里,使用的黑颜色的标签只是为了示范的目的。在这里就是指上图中的标签PAGED_RAM和OTHER_ROM。CodeWarrior不特别地使用这些标签。它们是程序员用于在应用代码中使用#pragma声明使用的。
small和banked地址模型参数文件间最本质的区别是DEFAULT_ROM标签的位置。
small地址模型参数文件不使用分页Flash地址。标签OTHER_ROM指向分页地址,但是不被由向导创建的项目使用。OTHER_ROM放在那只是给程序员在需要的时候使用的。
你必须要明白,在任何地址模型中你都可以使用分页或者非分页对象。地址模型只是影响默认位置和默认编译器行为。
在你的代码中,你总是可以通过使用特殊的语法以本地地改变默认行为。
比如,为了在banked地址模型中使用banked变量,需要额外输入一些东西。后面的部分讲了些编码的注意事项。
改变编译器默认行为
改变对代码的默认访问方式
最高效地访问放置在non-banked地址内的函数的方式是通过JSR(跳转到子例程)/RTS(从子例程返回)指令对。JSR/RTS指令对不处理任何分页寄存器,并使用16位地址来跳转。
另一方面,为了访问放置在banked地址内的函数,必须使用CALL/RTC(从call返回)指令对。CALL/RTC指令对会处理PPAGE寄存器。
为了在调用一个函数的时候强制使用JSR/RTS指令对,那个函数必须使用“__near”声明。
示例:
void __near myfunction(void);
为了在调用一个函数的时候强制使用CALL/RTC指令对,那个函数必须使用 “__far”声明。
示例:
void __far myfunction(void);
改变对变量的默认访问方式
如果一个变量放置在non-banked地址中,不需要使用什么关键词来保证正确的访问。
在S12架构中,只有内部的Flash内存资源有分页,所以唯一的情景就是要访问的那个数据被放在分页Flash中,或者说,那是个分页区常量。见下例:
示例 1:在S12架构中访问分页区常量
清单 1:在S12架构中访问分页区常量
#pragma CONST_SEG PAGEDCONSTANTS
volatile const unsigned int __far constant1=0xAAAA;
#pragma CONST_SEG DEFAULT
unsigned int variable1;
void main(void) {
variable1=constant1;
for(;;) {}
}
清单 1 展示了把constant1的值读入variable1。constant1被放置在分页Flash地址。图8 展示了链接器参数文件的对应位置。
图 8. 链接器参数文件
注意,这里在constant1的声明中使用了 __far 标识符。这个__far标识符指示编译器在访问constant1的地址之前处理PPAGE寄存器。因为在S12架构中只有一个分页寄存器,就没必要讲更多了。
在S12X架构中,我们得说明哪一个分页寄存器与哪个变量相关。下例进行了说明。
示例 2:在S12X架构中访问分页区变量
当一个变量被分页存储,程序员需要告知编译器,这个变量的地址与哪个寄存器相关。这是通过使用#pragma声明来实现的。
使用关键词 __RPAGE_SEG、__EPAGE_SEG、__PPAGE_SEG 和__GPAGE_SEG来分别告知编译器去处理RPAGE、EPAGE、PPAGE 和 GPAGE寄存器。
在Banked RAM区的变量
在访问一个banked RAM地址之前,编译器需要插入 写RPAGE寄存器 的指令。对应的语法如下:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM
在Banked EEPROM区的变量
在访问一个banked EEPROM地址之前,编译器需要插入 写EPAGE寄存器 的指令。对应的语法如下:
#pragma DATA_SEG __EPAGE_SEG MY_EEPROM
在Banked Flash区的常量
在访问一个banked FLASH地址之前,编译器需要插入 写PPAGE寄存器 的指令。对应的语法如下:
#pragma CONST_SEG __PPAGE_SEG OTHER_ROM
我们看看怎么访问一个分页RAM区变量,和一个分页ROM区变量:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM (1)
unsigned int variable1;
#pragma DATA_SEG DEFAULT
#pragma CONST_SEG __GPAGE_SEG PAGED_ROM (2)
volatile const unsigned int constant1=0xAAAA;
#pragma CONST_SEG __GPAGE_SEG DEFAULT
void main(void) {
variable1 = constant1;
for(;;) {} /* wait forever */
/* please make sure that you never leave this function */
}
图 9 展示了这个例子的链接器参数文件。
图 9. 链接器参数文件
在这个例子中,#pragma声明(1) 被用来指示连接器把variable1放置在叫做PAGED_RAM的section中。同时,由于有__RPAGE_SEG标识符,我们告知编译器在访问这个变量之前需要先处理RPAGE寄存器。
. #pragma声明(2) 被用来指示连接器把常量constant1放置在叫做PAGED_ROM的section中。同时,告知编译器在访问受这个#pragma影响的常量之前需要先处理GPAGE寄存器。
这是个有意思的选择。我们故意选择GPAGE寄存器来访问分页Flash区数据。因为在这个例子中,我们的代码存放的DEFAULT_ROM的位置也在个分页Flash区。因此当执行main函数时,PPAGE寄存器需要被设置为0xFC(见图9),这样CPU才可以读取在Flash分页窗口内的主函数。当在Flash分页窗口内执行时,PPAGE值绝对不能改变,否则CPU就会跑飞。这就是为什么,为了访问另一个Flash页的地址,我们不再使用PPAGE寄存器,而是使用了GPAGE寄存器。
这是利用GPAGE寄存器的一个典型例子。
这篇文档到这里就结束了。想要获得关于链接器和编译器行为的更多信息的话,请参考位于CodeWarrior安装路径下的搭建工具与编译器手册。代码示例可以在CodeWarrior安装路径内的文件夹(CodeWarrior_Examples)里找到。