TKStudio内置51编译器SDCC对C语言的扩展(1)

来源:互联网 发布:002174游族网络雪球 编辑:程序博客网 时间:2024/06/06 02:08

SDCC支持符合ANSI标准C语言程序设计,同时针对8051单片机自身的特点做了一些扩展。特别是在存储区,存储类型,指针和函数方面做了一些扩充。现在将就与ANSI C中不同的部分来进行讲述,相同之处可查看有关C程序设计的书籍,这里就不多讲了。

SDCC支持ANSI C中的所有关键字,同时它们又扩充了如下的关键字:

at      far   sbit       idata        sfr   interrupt   sfr16  bit   code   pdata  using

data   reentrant   xdata    critical  naked

 

1. 存储区
8051结构支持几个物理分开的程序和数据的存储区或存储空间。每个存储区都有有利的和不利的方面。这些存储空间可能:
 可读不可写
 可读写
 读写比别的存储空间快
可以分为:内部存储区和外部存储区。相对来说内部存储区的访问速度要快,但是空间一般都比较小。外部存储区访问起来相对要花费时间,因为外部数据区是通过一个数据指针加载一个地址来间接访问的,但空间可能大了一点。这就需要根据具体的开发过程中的需要,进行考虑,合理安排存储空间。
8051家族的微控制器有一个最低的128字节的内部存储器,其中结构如下:
- 字节 00-1F – 4组寄存器组R0到R7, 占用32字节,
- 字节 20-2F – 128个位可寻址单元,占用16字节,
- 字节 30-7F – 其他普通存储空间,占用80字节。
此外,有些MCS51家族的处理器有128字节的存储扩展,这些存储器都是间接可寻址的内部RAM存储器(idata)。此外,一些芯片可能拥有一些内建的扩展存储器,这些存储器是直接可寻址的RAM,不会与内部存储器相冲突。通常SDCC只使用第一组寄存器(寄存器0组),但你可以指定其它寄存器组(使用关键字),这将被使用在中断程序的例子里。默认地,编译器将分配堆栈的起始地址为紧跟在分配变量存储器后的最后一个字节处。举例来说,如果前两个寄存器组被使用和仅仅4个字节大小被用作数据变量,那么内部堆栈的起始地址被设置为20(0x14)。这意味着当堆栈增长的时候,它会覆盖剩余的寄存器组跟128位可位寻址的存储空间(16字节)和其它80字节的普通存储空间。如果任何位变量被使用,那么数据变量将被分配到没被使用到的寄存器组跟在最后一个位变量的下一个字节处。举例来说:如果寄存器组0,跟寄存器组1被使用,而且有9个位变量被使用(2个字节被使用),数据变量将被分配到地址0x10到0x20的地址空间,然后继续在地址0x22开始,你能够使用--dataloc去指定数据分配的开始地址和--iram-size 去指定总共的内部RAM(data+idata)的大小。
默认情况下,8051连接器分配栈的地址为紧跟内部数据变量的最后一个字节处,选项--stack-loc允许你去指定栈的起始地址,也就是说, 你可以设定它为普通存储空间的任何数据变量后。如果你的微型控制器有附加的间接可寻址的内部RAM (idata),你可以把栈的地址设置到上面去。你可以使用--xdata-loc去设置外部RAM (xdata)起始地址和 --xram-size选项去指定它的大小。同样的方法也可以适用于代码存储器设置,使用选项--code-loc 跟 --code-size。若不明白,不要指定任何选项,并看看由此产生的内存布局是否恰当,然后你就可以自己调整它。
连接器会产生两个关于存储器分配信息的文件。第一个是带扩展名的.map文件,这文件列出了所有的变量跟段信息。第二个是带扩展名.mem文件,这文件列出了最终的存储配置信息。
连接器将发出警告如果存储器段被覆盖,或者没足够的存储空间,或者没足够的空间分配给栈使用。如果你看到任何连接警告和(或)错误关于栈或段的分配问题,那么你就要来看看.map或.mem文件去查出问题所在。可能.mem文件能解决这个问题。


存储类型
SDCC除了支持多数的ANSI-C存储类型之外,也支持多种扩展存储类型。
2.1 data / near
对于小模式(下一节将讲述)来说,这是默认的存储类型,这种存储类型变量的声明将被分配到8051内部存储器RAM的可直接寻址的地址空间里。51系列一般具有128 字节直接寻址存储器,这是8051 能够访问的速度最快的存储器,生成的汇编代码只需一个MOV 指令即可读写该RAM 中的数据。
例如:
data unsigned char test_data;
写入 0x01 到这变量产生的汇编代码
75*00 01 mov _test_data,#0x01
2.2 xdata / far
这种存储类型声明的变量被分配到外部存储器RAM中去。这是大模式(下一节将讲述)的默认存储类型。这样开发人员能够访问更大的RAM 空间,但生成的汇编代码需要使用MOVX 指令来读写该存储器,这要求将外部存储器地址装入数据指针。
例如:
xdata unsigned char test_xdata;
写入 0x01 到这变量产生的汇编代码如下:
90s00r00 mov dptr,#_test_xdata
74 01 mov a,#0x01
F0 movx @dptr,a
2.3 idata
这种存储类型的声明被分配到8051内部存储器RAM的间接可寻址空间。间接可寻址存储器与直接寻址存储器类似,在8051 内核中共有128 字节(不包括特殊功能寄存器)。但是,访问idata 需要额外的MOV 命令将RAM 地址移至工作寄存器中。
例如:
idata unsigned char test_idata;
写入 0x01 到这变量产生的汇编代码如下:
78r00 mov r0,#_test_idata
76 01 mov @r0,#0x01
2.4 pdata
xdata页访问正像使用8051其他寻址模式一样简单,它总是被定位到xdata的起始位置并拥有最大为256字节大小的空间,用于访问分页的外部数据存储器。注意:pdata本身访问的是xdata空间。这是中模式(下一节将讲述)默认的存储类型。
下面的例子是写0x01到pdata变量中。
例如:
pdata unsigned char test_pdata;
写入 0x01 到这变量产生的汇编代码如下:
78r00 mov r0,#_test_pdata
74 01 mov a,#0x01
F2 movx @r0,a

注意:如果--xstack 选项被使用,pdata 存储区域将跟在xstack 存储区域后面并且总的大小限制为256个字节
2.5 code
这种存储类型的变量声明被分配到代码存储区。对于SDCC 来说,这类变量只读,因此常使用code 来声明常量(如:查找表)。
code unsigned char test_code;
访问这个变量产生的汇编代码如下:
90s00r6F mov dptr,#_test_code
E4 clr a
93 movc a,@a+dptr
用索引访问code存储区的字符数组非常的高效:
code char test_array[] = {’c’,’h’,’e’,’a’,’p’};
使用8位无符号数作为索引访问这个数组的汇编代码如下:
E5*00 mov a,_index
90s00r41 mov dptr,#_test_array
93 movc a,@a+dptr
2.6 bit
这是一种数据类型和一种专有的存储类型。当声明一个为bit的变量时,它被分配到8051存储器的位可寻址区域。8051 内核的16 字节直接寻址RAM 可用作位寻址存储器(字节0x20 至0x2F),提供128 个可寻址位。使用该类变量作为标志位可高效利用存储空间。
例如:
bit test_bit;
写入 1 到这个变量产生的汇编代码:
D2*00 setb _test_bit
除了8051专有的存储类型外,大多数体系结构支持ANSI-C位域。
根据ISO/IEC 9899,位跟位域除了明确有符号标识指出,否则被实现为无符号。
注意的是bit不能声明为指针。如bit *bitpt; 同时也不支持bit数组。
2.7 sfr / sfr16 / sfr32 / sbit
如bit关键字那样,sfr / sfr16 / sfr32 / sbit也是一种数据类型和存储类型,它们通常描述一种特殊的功能寄存器和8051的专门的位变量。用在程序中的SFR可控制计时器,计数器,串口,并口和外围设备。SFR的地址从0x80到0xFF,可以以位,字节和字访问。
例如:
sfr at (0x80) P0;

sfr16 at (0x8C8A) TMR0;
sbit at (0xd7) CY;
特殊的功能寄存器中能被8整除的地址,这些地址都是位可寻址的,sbit能声明这些sfr里面的指定的位地址。16位和32位特殊功能寄存器如果要求一定存储顺序的联合,最好不要使用sfr16或sfr32来声明。尽管SDCC通常最先访问最低字节,请注意,这是不能保证的。
注意:sdcc的sfr定义语法可能与其它编译器不兼容,比如定义0x80的sfr为P0,其它编译器可能要写成:
sfr P0=0x80;
但是sdcc要写成:
sfr at (0x80) P0;
并且sdcc会把第一种写法解释为将0x80赋值给一个叫做P0的变量,而不会产生任何警告。在这里必须小心,特别是在把其它编译器下的代码移植到SDCC更加需要注意这点。
最后需要指出的是:这些存储类型关键字前面加上两个下划线(_)其含义跟不加两个下划线一样。即data,也可以使用__data关键,sfr与__sfr含义相同。在编写程序过程中,开发者可以根据个人爱好和习惯使用这些关键字,但要注意不要与标识符混淆。


存储模式
存储模式定义缺省的存储类型,用在函数参数,自动变量,和没有直接声明存储类型的变量上。在SDCC编译器命令行中用small ,medium和large控制命令指定存储模式。默认使用small模式。如:
sdcc --model-small sdcctest.c
或者
sdcc --model-large sdcctest.c
 SDCC允许MCS51代码有三种存储器模式,小模式,中模式和大模式。在不同存储器模式下编译的模块不应该组合到一起,否则结果将是不可预知的。在编译器的支持下,库例程分别被编译成了小模式,中模式以及大模式的代码。被编译的库模块分别被包含在小模式,中模式和大模式的目录下,这样就可以根据需要连接合适的库。
3.1 small模式
在本模式中,缺省的情况下所有的变量位于8051系统的内部RAM数据区。这和用data存储类型标识符明确声明的一样。在本模式中变量访问非常快。建议一般情况下都使用该模式。
3.2 medium模式
medium模式,缺省情况下的所有变量都放在外部数据区的一页中,这就象用pdata声明的一样。该存储模式可提供最多256字节的变量。限制是由于它通过寄存器R0和R1间接寻址。本存储模式不如SMALL模式有效,因此变量访问不是很快,然而比large模式快。
当用medium模式,SDCC编译器用以@R0和@R1为操作数的指令,访问外部存储区。R0和R1是字节寄存器只提供地址的低字节。如果medium模式使用多于256字节的外部存储区,高字节地址或页由8051的PORT2提供。这种情况下必须初始化PORT2,以使用正确的外部存储页。这可在启动代码中实现,同时必须指定PDATA的起始地址。
3.3 large模式
在large模式,所有变量缺省的放在外部数据存储区可到64K字节。这和用xdata存储类型标识符明确声明的一样。数据指针DPTR 用做寻址,通过该指针访问存储区是低效的,特别是对两个或多个字节的变量。这种访问机制比前两种模式产生更多的代码。
比起使用大模式来,明智的使用处理器专门的存储类型,“可重入”类型函数将产生更有效的代码。在大模式情况下编译程序,一些优化器将被关闭,因此除非特殊的要求,一般都推荐使用小模式。
3.4 外部堆栈
另外这里还将提及有关外部堆栈方面的内容。外部堆栈(--xstack 选项)被定位在pdata存储空间中(通常在外部RAM的开始段)并且使用pdata中所有没有被使用的空间。当使用--xstack选项来编译程序,所有的可重入函数中的参数和局部变量将被分配在该区域。该选项在当程序需要大量堆栈空间时被使用。当使用了--stack-auto选项时,所有的参数变量和局部变量将被分配在外部堆栈里。编译器输出外部RAM段的高字节地址到端口P2中,因此当使用外部堆栈选项,这端口可能没被应用程序使用。 


绝对地址
数据项可能要被指派一个绝对地址,使用at<address>关键字,再加上存储类型。
声明语法为:
  [Memory_space] at [address] type variable_name
Memory_space:为上面讲述的存储类型
Address      : 为指定的地址
Type         :数据类型
variable_name :变量名
例如:
xdata at 0x7ffe unsigned int chksum;
或者,下面更好的符合ISO/IEC 9899 C标准:
__xdata __at (0x7ffe) unsigned int chksum;
如上面描述的例子,变量chksum将被定位为外部RAM的0x7ffe跟0x7fff地址。编译器不会跟踪声明的绝对寻址变量,而且可能在其地址声明其它变量,造成相互覆盖。因此它是留给程序员去确保跟其它的变量声明的绝对地址没有覆盖情况,汇编器列表文件(.lst)跟连接器输出文件(.rst)和(.map)能很好的去查看覆盖情况。例如以下程序清单 2 1显示了有趣的潜在错误:
程序清单 2 1绝对地址的覆盖
#include <8051.h>
unsigned char a = 0x44;
unsigned int b = 0x0000;
unsigned char c[64] = {0x00};
unsigned char at 0x0010 y;
unsigned char at 0x0010 z;
void main(void)
{
for(b=0; b<64; b++)
c[b] = 0x55;
y = 0x81;
z = 0x82;
a = c[5];
while (1);
}
使用SDCC 时,尽管变量"y"和"z"分配同一个位置,也可进行无错误或警告的编译。如果要运行该程序,我们认为程序(a = c[5])中"a"最终将被设置为0x55。但情况并非如此。"a"最终被分配的值为0x82。
如果查看SDCC 生成的.map 文件中以下几行语句(显示每个变量的实际地址),便会明白这种情况的原因。
Area Addr Size Decimal Bytes
(Attributes)
-------------------------------- ---- ---- ------- -----
------------
. .ABS. 0000 0000 = 0. bytes
(ABS,OVR)
Value Global
-------- --------------------------------
...
0010 _y
0010 _z
...
Area Addr Size Decimal Bytes
(Attributes)
-------------------------------- ---- ---- ------- -----
------------
DSEG 0008 0043 = 67. bytes
(REL,CON)
Value Global
-------- --------------------------------
0008 _a
0009 _b
000B _c
注意,变量名称前的下划线是由编译器添加的。如果"c"位于地址0x000B,长度为64 字节,那么它将覆盖位于地址0x0010 处的变量"y"和"z"。
然而,如果在提供一个初始化的实际内存分配时发生重叠,将被连接器检测出来。
例如:
__code __at (0x7ff0) char Id[5] = ”SDCC”;
如上例子,变量Id将被分配到代码存储区0x7ff0到0x7ff4之间。
如果I/O设备映射到了存储空间,应使用volatile 关键词,以确保C 编译器不进行优化以便能访问到要访问的存储区
volatile __xdata __at (0x8000) unsigned char PORTA_8255;
对于一些体系结构(MCS51),如果对一些以块(256字节)边界为起始位置的(xdata/far)数组进行访问是非常高效。
所有存储类型的变量都可以被指定绝对地址,例如:
__bit __at (0x02) bvar;
上面的例子将分配一个变量在位可寻址空间的偏移值为0x02处。没有必要照这样去为变量分配绝对地址,除非你想精确地去控制所有已经分配的变量,还有一种使用这情况的可能,就是去写硬件可移植代码。举例来说,如果有个使用到一个或以上的微型控制器I/O脚的程序,同时这些脚在不同的硬件中也是有差异的,你可能在你的程序中声明I/O脚,如下程序清单 2 2所示:
程序清单 2 2 I/O脚的使用
extern volatile __bit MOSI;
extern volatile __bit MISO;
extern volatile __bit MCLK;

unsigned char spi_io(unsigned char out_byte)
{
unsigned char i=8;
do {
MOSI = out_byte & 0x80;
out_byte < <= 1;
MCLK = 1;

if(MISO)
out_byte += 1;
MCLK = 0;
} while(--i);
return out_byte;
}
那么,在第一个硬件中的某处代码,你会使用如下声明:
__bit __at (0x80) MOSI;
__bit __at (0x81) MISO;
__bit __at (0x82) MCLK;
同样地, 在第二个硬件中,你会使用:
__bit __at (0x83) MOSI;
__bit __at (0x91) MISO;
__bit __at (0x92) MCLK;
当遇到同样的硬件时,就可以使用独立不变的程序,例如调用库。这是有点类似sbit ,但在整项工程只有一个绝对地址已被指定。
绝对地址也有可能用于仿真位寻址变量。在下面的例子中,在位寻址存储器的第一个字节处定义变量n_byte。然后,在8051 内核位寻址存储器的最前8 位定义n_bit0 至n_bit7。由于这种重叠,可采用变量n_bit0至n_bit7 对变量n_byte 进行位寻址。如程序清单 2 3所示。
程序清单 2 3仿真位寻址变量
#include<8051.h>
data unsigned char at 0x0020 n_byte;
bit at 0x00 n_bit0;
bit at 0x01 n_bit1;
bit at 0x02 n_bit2;
bit at 0x03 n_bit3;
bit at 0x04 n_bit4;
bit at 0x05 n_bit5;
bit at 0x06 n_bit6;
bit at 0x07 n_bit7;
void main(void)
{
n_byte = 0x00;
n_bit7 = 1;
P0 = n_byte; // P0 = 0x80
while (1);
}
不过一般情况下这种特性不需要使用,只是在必要时才使用。


指针
SDCC编译器用*字符支持变量指针的声明。SDCC指针可用在所有标准C中可用的操作中。但是,因为8051 和派生系列的独特结构,SDCC编译器提供两个类型的指针:通用指针和存储器指针。
SDCC允许(通过语言扩展)指针去精确的指向8051任何的存储空间。除了指定修饰的指针之外,编译器使用(默认)通用指针去指向任何的存储空间。
指针声明例子:

__xdata unsigned char * __data p;

__data unsigned char * __xdata p;

__xdata unsigned char * __code p;

__code unsigned char * __code p;

unsigned char * __xdata p;

unsigned char * p;

char (* __data fp)(void);
所有没修饰的指针都被看作为3字节(ds390为4字节)通用指针。
通用指针的最高次序字节包含数据空间信息。汇编器支持被调用的程序无论何时都可以使用通用指针去读取数据或写数据。这样对于可再重用库程序非常有用。指定存储指针类型将能产生高效的代码。

5.1 通用指针
通用指针和标准C指针的声明相同。例如:
char *str;
char* xdata p;
int *pnum;
long *pcount;
通用指针用三个字节保存(ds390为4字节)。最高次序字节包含数据空间信息,低两字节用来保存地址本身。最高次序字节可能的值如下:
 0x00—外部数据存储器RAM(xdata/far),
 0x40—内部数据存储器RAM(idata/near),
 0x60—外部数据存储器页RAM(pdata),
 0x80—代码存储器ROM(code)。
通用指针可访问8051存储空间内的任何变量。通用指针可以用来访问所有类型的变量,而不管变量存储在哪个存储空间中,因而许多库函数都使用通用指针。通过使用通用指针,一个函数可以访问数据,而不用考虑它存储在什么存储器中。
通用指针很方便,但是也很慢。在所指向目标的存储空间不明确的情况下,它们用的最多。
5.2 存储器指针
存储器指针或类型确定的指针在定义时包括一个存储器类型说明,并且总是指向此说明的特定存储器空间。例如
char data *str;
int xdata *numtab;
long code *powtab;
正是由于存储器类型在编译时已经确定,通用指针中用来表示存储器类型的字节就不再需要了。
指向idata,data,bdata和pdata的存储器指针用一个字节保存,指向code和xdata的存储器指针用两个字节保存。使用存储器指针比通用指针效率要,高速度要快。当然,存储器指针的使用不是很方便。在所指向目标的存储空间明确并不会变化的情况下,它们用的最多。
5.3 存储器指针和通用指针的比较
使用存储器指针可以显著的提高8051 C程序的运行速度
下面的示例程序说明了使用不同的指针在代码长度,占用数据空间和运行时间上的不同。
Description    idata Pointer       xdata Pointer      Generic Pointer
C源程序      idata *ip;          char xdata *xp;     char *p;
char val;          char val;           char val;
val = *ip;         val = *xp;          val = *xp;
编译后的代码
MOV R0,ip         MOV DPL,xp +1        MOV DPL,p
MOV val,@R0      MOV DPH,xp          MOV DPH,p + 1
MOV A,@DPTR        MOV B,p+2
MOV val,A             LCALL __gptrget
指针大小   1 byte             2 byte                  3 byte
代码长度   4 bytes            9 bytes                 11 bytes + library call
执行时间   4 cycles           7 cycles                 13 cycles


转自:http://blog.sina.com.cn/s/blog_5f235c790100duon.html

0 0