一、BDATA 区
下面的代码访问状态寄存器的特定位,把访问定义在DATA 段中的一个字节和通过位名和位号访问同样的可位寻址字节的位的代码对比。注意:对变量位进行寻址产生的汇编代码比检测定义在DATA 段的状态字节位所产生的汇编代码要好,如果你对定义在 BDATA 段中的状态字节中的位采用偏移量进行寻址,而不是用先前定义的位变量名时,编译后的代码是错误的
下面的例子中 use_bitnum_status 的汇编代码比 use_byte_status 的代码要大
1 //定义一个字节宽状态寄存器
2 unsigned char data byte_status=0x43;
3
4 //定义一个可位寻址状态寄存器
5 unsigned char bdata bit_status=0x43;
6 //把 bit_status 的第 3位设为位变量
7 sbit status_3=bit_status^3;
8
9 bit use_bit_status(void);
10
11 bit use_bitnum_status(void);
12
13 bit use_byte_status(void);
14
15 void main(void)
{
16 unsigned char temp=0;
17 if (use_bit_status())
{ //如果第 3位置位 temp 加1
18 temp++;
19 }
20 if (use_byte_status())
{ //如果第 3 位置位 temp 再加 1
21 temp++;
22 }
23 if (use_bitnum_status())
{ //如果第 3 位置位 temp 再加 1
24 temp++;
25 }
26 }
27
28 bit use_bit_status(void)
{
29 return(bit)(status_3);
30 }
31
32 bit use_bitnum_status(void)
{
33 return(bit)(bit_status^3);
34 }
35
36 bit use_byte_status(void)
{
37 return byte _status&0x04;
38 }
目标代码列表
; FUNCTION main (BEGIN)
; SOURCE LINE # 15
; SOURCE LINE # 16
0000 E4 CLR A
0001 F500 R MOV temp,A
; SOURCE LINE # 17
0003 120000 R LCALL use_bit_status
0006 5002 JNC ?C0001
; SOURCE LINE # 18
0008 0500 R INC temp
; SOURCE LINE # 19
000A ?C0001:
; SOURCE LINE # 20
000A 120000 R LCALL use_byte_status
000D 5002 JNC ?C0002
; SOURCE LINE # 21
000F 0500 R INC temp
; SOURCE LINE # 22
0011 ?C0002:
; SOURCE LINE # 23
0011 120000 R LCALL use_bitnum_status
0014 5002 JNC ?C0004
; SOURCE LINE # 24
0016 0500 R INC temp
; SOURCE LINE # 25
; SOURCE LINE # 26
0018 ?C0004:
0018 22 RET
; FUNCTION main (END)
; FUNCTION use_bit_status (BEGIN)
; SOURCE LINE # 28
; SOURCE LINE # 29
0000 A200 R MOV C,status_3
; SOURCE LINE # 30
0002 ?C0005:
0002 22 RET
; FUNCTION use_bit_status (END)
; FUNCTION use_bitnum_status (BEGIN)
The compiler obtains the desired bit by using the entire byte instead of using
a bit address.
; SOURCE LINE # 32
; SOURCE LINE # 33
0000 E500 R MOV A,bit_status
0002 6403 XRL A,#03H
0004 24FF ADD A,#0FFH
; SOURCE LINE # 34
0006 ?C0006:
0006 22 RET
; FUNCTION use_bitnum_status (END)
; FUNCTION use_byte_status (BEGIN)
; SOURCE LINE # 36
; SOURCE LINE # 37
0000 E500 R MOV A,byte_status
0002 A2E2 MOV C,ACC.2
; SOURCE LINE # 38
0004 ?C0007:
0004 22 RET
; FUNCTION use_byte_status (END)
二、PDATA和XDATA段
在这两个段声明变量和在其它段的语法是一样的 PDATA 段只有256 个字节,而XDATA段可达65536 个字节 下面是一些例子
unsigned char xdata system_status=0;
unsigned int pdata unit_id[2];
char xdata inp_string[16];
float pdata outp_value;
对 PDATA 和 XDATA 的操作是相似的 对 PDATA 段寻址比对 XDATA 段寻址要快 因为对PDATA 段寻址只需要装入 8 位地址 而对 XDATA 段寻址需装入 16 位地址 所以尽量把外部数据存储在PDATA段中对 DATA 和XDATA 寻址要使用 MOVX 指令需要两个处理周期
1 #include <reg51.h>
2
3 unisgned char pdata inp_reg1;
4
5 unsigned char xdata inp_reg2;
6
7 void main(void)
{
8 inp_reg1=P1;
9 inp_reg2=P3;
10 }
产生的目标代码列表
; FUNCTION main (BEGIN)
; SOURCE LINE # 7
; SOURCE LINE # 8
注意 'inp_reg1=P1' 需要4个指令周期
0000 7800 R MOV R0,#inp_reg1
0002 E590 MOV A,P1
0004 F2 MOVX @R0,A
; SOURCE LINE # 9
注意 'inp_reg2=P3' 需要5个指令周期
0005 900000 R MOV DPTR,#inp_reg2
0008 E5B0 MOV A,P3
000A F0 MOVX @DPTR,A
; SOURCE LINE # 10
000B 22 RET
; FUNCTION main (END)
经常 外部地址段中除了包含存储器地址外还包含 I/O 器件的地址 对外部器件寻址可通过指针或 C51 提供的宏 我建议使用宏对外部器件进行寻址 因为这样更有可读性宏定义使得存储段看上去像 char 和 int 类型的数组 下面是一些绝对寄存器寻址的例子
inp_byte=XBYTE[0x8500]; // 从地址8500H读一个字节
inp_word=XWORD[0x4000]; // 从地址4000H读一个字和2001H
c=*((char xdata *) 0x0000); // 从地址0000读一个字节
XBYTE[0x7500]=out_val; // 写一个字节到 7500H可对除BDATA和BIT段之外的其它数据段采用以上方法寻址 通过包含头文件 absacc.h来进行绝对地址访问
三、指针
C51 提供一个 3 字节的通用存储器指针,通用指针的头一个字节表明指针所指的存储区空间,另外两个字节存储 16位偏移量 对于DATA IDATA 和PDATA 段 只需要 8 位偏移量Keil允许使用者规定指针指向的存储段这种指针叫具体指针,使用具体指针的好处是节省了存储空间,编译器不用为存储器选择和决定正确的存储器操作指令产生代码。这样就使代码更加简短,但你必须保证指针不指向你所声明的存储区以外的地方,否则会产生错误,而且很难调试。
下面的例子反映出使用具体指针比使用通用指针更加高效,使用通用指针的第一个循环需要378个处理周期,使用具体指针只需要 151 个处理周期
指针类型 大小
通用指针 3 字节
XDATA指针 2 字节
CODE 指针 2 字节
IDATA指针 1 字节
DATA指针 1 字节
PDATA指针 1 字节
1 #include <absacc.h>
2
3 char *generic_ptr;
4
5 char data *xd_ptr;
6
7 char mystring[]="Test output";
8
9 main()
{
10 1 generic_ptr=mystring;
11 1 while (*generic_ptr)
{
12 2 XBYTE[0x0000]=*generic_ptr;
13 2 generic_ptr++;
14 2 }
15 1
16 1 xd_ptr=mystring;
17 1 while (*xd_ptr)
{
18 2 XBYTE[0x0000]=*xd_ptr;
19 2 xd_ptr++;
20 2 }
21 1 }
; FUNCTION main (BEGIN)
; SOURCE LINE # 9
; SOURCE LINE # 10
0000 750004 R MOV generic_ptr,#04H
0003 750000 R MOV generic_ptr+01H,#HIGH mystring
0006 750000 R MOV generic_ptr+02H,#LOW mystring
0009 ?C0001:
; SOURCE LINE # 11
0009 AB00 R MOV R3,generic_ptr
000B AA00 R MOV R2,generic_ptr+01H
000D A900 R MOV R1,generic_ptr+02H
000F 120000 E LCALL ?C_CLDPTR
0012 FF MOV R7,A
0013 6011 JZ ?C0002
; SOURCE LINE # 12
0015 900000 MOV DPTR,#00H
0018 F0 MOVX @DPTR,A
; SOURCE LINE # 13
0019 7401 MOV A,#01H
001B 2500 R ADD A,generic_ptr+02H
001D F500 R MOV generic_ptr+02H,A
001F E4 CLR A
0020 3500 R ADDC A,generic_ptr+01H
0022 F500 R MOV generic_ptr+01H,A
; SOURCE LINE # 14
0024 80E3 SJMP ?C0001
0026 ?C0002:
; SOURCE LINE # 16
0026 750000 R MOV xd_ptr,#LOW mystring
0029 ?C0003:
; SOURCE LINE # 17
0029 A800 R MOV R0,xd_ptr
002B E6 MOV A,@R0
002C FF MOV R7,A
002D 6008 JZ ?C0005
; SOURCE LINE # 18
002F 900000 MOV DPTR,#00H
0032 F0 MOVX @DPTR,A
; SOURCE LINE # 19
0033 0500 R INC xd_ptr
; SOURCE LINE # 20
0035 80F2 SJMP ?C0003
; SOURCE LINE # 21
0037 ?C0005:
0037 22 RET
; FUNCTION main (END)
由于使用具体指针能够节省不少时间,所以我们一般都不使用通用指针。
尽量使用指定存储类型的指针(memory-specific pointer)不使用一般指针(generic pointer)
如果程序移植的时候不做修改,所有的指针将都是“一般指针”,我们的建议是尽量修改为“指定存储类型”的指针,因为它的效率要高很多。
首先一般指针使用三个字节,第一个字节指示是什么存储类型,后两个字节是指针指向的地址。“指定存储类型”的指针则只用一个或者两个字节。可见“一般指针”占用内存多。
另外,为了取得“一般指针”指向的数据,程序必须调用?C?CLDPTR函数,在?C?CLDPTR中根据指针第一字节指示的存储类型采取不同的读取RAM的方式。而使用“指定存储类型”的指针时,采取哪种读取RAM的方式在编译时已经确定,不用在运行时动态判断。可见“一般指针”运行效率低。
“指定存储类型”的指针指向的变量必须要有明确的存储类型。一般情况下程序中使用指针是为了指向大块内存,而KeilC中大块内存一般定义为外部变量。依照第一点移植建议,所有的外部变量都定义为xdata或者pdata类型了,有明确的存储类型,这说明程序中的指针基本都可以改为“指定存储类型”的指针。