人生境界:明智、中庸、诚信、谦逊
分类: 嵌入式
2013-11-12 13:42:46
(支持原创,如需转载,请注明地址:http://blog.sina.com.cn/litianping0709 作者:叶雨荫城(阿雨))
刚写完8051内存模型,今天我和大家一起来看看8051的代码分页机制(code banking)。如有疑问,请与我商榷。
Contiki支持的flash大小主要分为两种,一种是256KB(cc253x)或者128KB(snsinode)。如前篇文章所讲8051 memory spaces,flash主要映射到code存储空间,宽度为16bit,寻址范围可达到64kb。
首先我想问一个问题,为何要进行code banking??通俗点说一个标准的8051器件能寻址64KB的代码空间。对于超过64KB的代码,单片机系统通常采用代码分页(CODE BANKING)的方式来扩展程序空间。代码分页的机理就是将地址空间分成小于或等于64KB的不同的代码段,通过片选的方式实现程序在不同代码空间的跳转.
这篇文章当然描述的是一些基础性的东西,并且只针对contiki支持的soc。如果想详细参考,请看最后的reference list
How does it Work
什么叫做banks?flash被分解成的小于或者等于64KB的代码段称之为banks。不同设备的banks数量不一定相同,但是技术是一样的。每个bank的大小为32KB,因此cc2430有4个banks,而cc2530有8个banks。
由于flash的大小超过64KB,我们不得不利用三个字节来进行寻址。举个例子,cc2530的物理地址范围为ox000000到0x03ffff。
Common Segment
flash的低地址位的32KB(物理地址0x000000到0x007fff),一般称之为common segment(HOME 或者 BANKO),总是由code存储空间的低32KB进行寻址(0x0000-0x7fff)。
Switched Segments
而code的高32KB地址空间(0x8000-0xffff)主要用来寻址剩下的N-1代码段。在任何给定的时间内只能指向一个segment。通过一个特殊的寄存器来告诉芯片现在只想的bank是哪个,寄存器一般称之为PSBANK或者FMAP.
Physical and Virtual Addresses
下面的表格显示了物理地址(on flash),code存储空间的地址及相应FMAP寄存器之间的值。
Segment | Physical Address | Virtual Address | FMAP | Address in CODE |
---|---|---|---|---|
HOME | 0x000000 - 0x007FFF | 0x000000 - 0x007FFF | 0x00 | 0x0000 - 0x7FFF |
BANK1 | 0x008000 - 0x00FFFF | 0x018000 - 0x01FFFF | 0x01 | 0x8000 - 0xFFFF |
BANK2 | 0x010000 - 0x017FFF | 0x028000 - 0x02FFFF | 0x02 | 0x8000 - 0xFFFF |
BANK3 | 0x018000 - 0x01FFFF | 0x038000 - 0x03FFFF | 0x03 | 0x8000 - 0xFFFF |
... | ||||
BANK7 | 0x038000 - 0x03FFFF | 0x078000 - 0x07FFFF | 0x07 | 0x8000 - 0xFFFF |
The Problem
到目前为止,上述机制听起来比较简单,但是不知道大家有没有注意到一个问题,在x bank里的代码希望调用y bank里代码时该怎么办??如果在转换bank之前调用相应的代码将会出现错误。而如果在调用之前转到了相应的bank,那么调用者已经被转换,调用也是不可能的。
Writing Banked Software with SDCC
为了解决这个问题,SDCC使用了一项称之为trampoline的技术banked函数调用的实现通过位于common segment中的一小块intermediate代码段来实现。为了讲述简单,流程如下:
Normal vs Banked Calls
很明显,上述的调用对stack和调用的开销都会加大,但是我们知道banked调用相对是比较少的。
SDCC Memory Models
如之前文章所讨论到的8051 memory spaces,SDCC利用其中的模型之一来构建可运行程序。
Small, Medium and Large - Explicit Banking
对于small,medium或者large来说,开发者必须显式的调用banked函数。
例子:
void foo() __banked; void bar() __banked
{
foo();
}
Huge - Implicit Banking :
而对于huge模型来说,所有的函数调用/返回都会调用trampoline。看例子。
void foo();
void bar()
{
foo();
/ * This automatically becomes a banked return */
}
Here be Dragons
当然上述特性是SDCC不成文的特性,取决你自己怎么使用。
为了减少代码空间和提高性能,开发这可以利用__nonbanked关键字来阻止SDCC产生banked调用/返回。
为安全起见,最好遵循以下规则:
Bank Allocations
在利用sdcc开发程序时,开发者必须指定每个代码段存在于哪个segment。这个可以利用#pragma directive或者命令行指定。这里主要强调的是链接器不会检查bank溢出。如果你在一个相同的segment里分配了过多的文件并且超过32KB的大小,这样就会出现问题。
Banking in Contiki's 8051-based Ports
contiki的编译系统是banking-aware的,可以看看examples/cc2530dk或者examples/sensinode下的makefile文件,可以看到这一行:HAVE_BANKING=1;
HAVE_BANKING选项主要告诉编译器是否产生bankable的镜像
当编译结束,可以看到一些信息输出,这些信息可以帮助你检查分配情况,以防有错误发生。
Code Segment Sizes and memory footprint
最后输出的信息会类似于如下所示。
看看上面的信息可以判断出是否有bank溢出或者其他问题:
Automatic Bank Allocation
将利用sdcc banking进行编译时,分配器将会自动满足segment的大小,你只需要做一件事,就是在写新的中断服务程序时,告诉分配器这个程序必须位于HOME bank中。有两种方法:
但是如果你不想写新的ISRs,那么你不用担心任何东西。
在编译的最后阶段,当分配器运行后,会产生如下信息:
以Preallocations开始的一行后面列出了不能或者没有被移出的字节数目。例如,包含ISRs的标准库或者文件必须停留在HOME segment中。
Segment - max - alloc 相对来说比较重要,最后一列将会列出在编译结束后在每个segement中的字节数大小。这里列出的值的大小必须等需Decimal下列出的值的大小,
reference list