第一节 Keil C51扩展关键字
深入理解并应用C51对标准ANSIC的扩展是学习C51的关键之一。因为大多数扩展功能都是直接针对8051系列CPU硬件的。大致有以下8类:
8051存储类型及存储区域
* 存储模式
* 存储器类型声明
* 变量类型声明
* 位变量与位寻址
* 特殊功能寄存器(SFR)
* C51指针
* 函数属性
具体说明如下(8031为缺省CPU)。
C51 V4.0版本有以下扩展关键字(共19个):
_at_ idata sfr16 alien interrupt small
bdata large _task_ Code bit pdata
using reentrant xdata compact sbit data sfr
第二节 存储模式
存储模式决定了没有明确指定存储类型的变量,函数参数等的缺省存储区域,共三种:
1. Small模式
所有缺省变量参数均装入内部RAM,优点是访问速度快,缺点是空间有限,只适用于小程序。
2. Compact模式
所有缺省变量均位于外部RAM区的一页(256Bytes),具体哪一页可由P2口指定,在STARTUP.A51文件中说明,也可用pdata指定,优点是空间较Small为宽裕速度较Small慢,较large要快,是一种中间状态。
3. large模式
所有缺省变量可放在多达64KB的外部RAM区,优点是空间大,可存变量多,缺点是速度较慢。
提示:存储模式在C51编译器选项中选择
第三节 指针
C51支持一般指针(Generic Pointer)和存储器指针(Memory_Specific Pointer).
1. 一般指针
一般指针的声明和使用均与标准C相同,不过同时还可以说明指针的存储类型,例如:
long * state;为一个指向long型整数的指针,而state本身则依存储模式存放。
char * xdata ptr;ptr为一个指向char数据的指针,而ptr本身放于外部RAM区,以上的long,char等指针指向的数据可存放于任何存储器中。
一般指针本身用3个字节存放,分别为存储器类型,高位偏移,低位偏移量。
2. 存储器指针
基于存储器的指针说明时即指定了存贮类型,例如:
char data * str;str指向data区中char型数据
int xdata * pow; pow指向外部RAM的int型整数。
这种指针存放时,只需一个字节或2个字节就够了,因为只需存放偏移量。
3. 指针转换
即指针在上两种类型之间转化:
* 当基于存储器的指针作为一个实参传递给需要一般指针的函数时,指针自动转化。
* 如果不说明外部函数原形,基于存储器的指针自动转化为一般指针,导致错误,因而请用“#include”说明所有函数原形。
* 可以强行改变指针类型。
C51函数声明对ANSI C作了扩展,具体包括:
1. 中断函数声明:
中断声明方法如下:
void serial_ISR () interrupt 4 [using 1]
{
}
为提高代码的容错能力,在没用到的中断入口处生成iret语句,定义没用到的中断。
void extern0_ISR() interrupt 0{}
void timer0_ISR () interrupt 1{}
void extern1_ISR() interrupt 2{}
void timer1_ISR () interrupt 3{}
void serial_ISR () interrupt 4{}
2. 通用存储工作区
3. 选通用存储工作区由using x声明,见上例。
4. 指定存储模式
由small compact 及large说明,例如:
void fun1(void) small { }
提示:small说明的函数内部变量全部使用内部RAM。关键的经常性的耗时的地方可以这样声明,以提高运行速度。
5. #pragma disable
在函数前声明,只对一个函数有效。该函数调用过程中将不可被中断。
6. 递归或可重入函数指定
在主程序和中断中都可调用的函数,容易产生问题。因为51和PC不同,PC使用堆栈传递参数,且静态变量以外的内部变量都在堆栈中;而51一般使用寄存器传递参数,内部变量一般在RAM中,函数重入时会破坏上次调用的数据。可以用以下两种方法解决函数重入:
a、在相应的函数前使用前述“#pragma disable”声明,即只允许主程序或中断之一调用该函数;
b、将该函数说明为可重入的。如下:
void func(param...) reentrant;
KeilC51编译后将生成一个可重入变量堆栈,然后就可以模拟通过堆栈传递变量的方法。
由于一般可重入函数由主程序和中断调用,所以通常中断使用与主程序不同的R寄存器组。
另外,对可重入函数,在相应的函数前面加上开关“#pragma noaregs”,以禁止编译器使用绝对寄存器寻址,可生成不依赖于寄存器组的代码。
7. 指定PL/M-51函数由alien指定。
第一节 绝对地址访问
C51提供了三种访问绝对地址的方法:
1. 绝对宏:
在程序中,用“#include<absacc.h>”即可使用其中定义的宏来访问绝对地址,包括:
CBYTE、XBYTE、PWORD、DBYTE、CWORD、XWORD、PBYTE、DWORD
具体使用可看一看absacc.h便知
例如:
rval=CBYTE[0x0002];指向程序存贮器的0002h地址
rval=XWORD [0x0002];指向外RAM的0004h地址
2. _at_关键字
直接在数据定义后加上_at_ const即可,但是注意:
(1)绝对变量不能被初使化;
(2)bit型函数及变量不能用_at_指定。
例如:
idata struct link list _at_ 0x40;指定list结构从40h开始。
xdata char text[25b] _at_0xE000;指定text数组从0E000H开始
提示:如果外部绝对变量是I/O端口等可自行变化数据,需要使用volatile关键字进行描述,请参考absacc.h。
3. 连接定位控制
此法是利用连接控制指令code xdata pdata \data bdata对“段”地址进行,如要指定某具体变量地址,则很有局限性,不作详细讨论。
第二节 Keil C51与汇编的接口
1. 模块内接口
方法是用#pragma语句具体结构是:
#pragma asm
汇编行
#pragma endasm
这种方法实质是通过asm与ndasm告诉C51编译器中间行不用编译为汇编行,因而在编译控制指令中有SRC以控制将这些不用编译的行存入其中。
2. 模块间接口
C模块与汇编模块的接口较简单,分别用C51与A51对源文件进行编译,然后用L51将obj文件连接即可,关键问题在于C函数与汇编函数之间的参数传递问题,C51中有两种参数传递方法。
(1) 通过寄存器传递函数参数
最多只能有3个参数通过寄存器传递,规律如下表:
参数数目
char
int
long,float
一般指针
1
R7
R6& R7
R4~R7
R1~R3
2
R5
R4& R5
R4~R7
R1~R3
3
R3
R2& R3
R1~R3
2) 通过固定存储区传递(fixed memory)
这种方法将bit型参数传给一个存储段中:
?function_name?BIT
将其它类型参数均传给下面的段:?function_name?BYTE,且按照预选顺序存放。
至于这个固定存储区本身在何处,则由存储模式默认。
(3) 函数的返回值
函数返回值一律放于寄存器中,有如下规律:
return type
Registev
说明
bit
标志位
由具体标志位返回
char/unsigned char 1_byte指针
R7
单字节由R7返回
int/unsigned int 2_byte指针
R6 & R7
双字节由R6和R7返回,MSB在R6
long&unsigned long
R4~R7
MSB在R4, LSB在R7
float
R4~R7
32Bit IEEE格式
一般指针
R1~R3
存储类型在R3 高位R2 低R1
(4) SRC控制
该控制指令将C文件编译生成汇编文件(.SRC),该汇编文件可改名后,生成汇编.ASM文件,再用A51进行编译。
第三节 C51软件包中的通用文件
在C51\LiB目录下有几个C源文件,这几个C源文件有非常重要的作用,对它们稍事修改,就可以用在自己的专用系统中。
1. 动态内存分配
init_mem.C:此文件是初始化动态内存区的程序源代码。它可以指定动态内存的位置及大小,只有使用了init_mem( )才可以调回其它函数,诸如malloc calloc,realloc等。
calloc.c:此文件是给数组分配内存的源代码,它可以指定单位数据类型及该单元数目。
malloc.c:此文件是malloc的源代码,分配一段固定大小的内存。
realloc.c:此文件是realloc.c源代码,其功能是调整当前分配动态内存的大小。
2. C51启动文件STARTUP.A51
启动文件STARTUP.A51中包含目标板启动代码,可在每个project中加入这个文件,只要复位,则该文件立即执行,其功能包括:
* 定义内部RAM大小、外部RAM大小、可重入堆栈位置
* 清除内部、外部或者以此页为单元的外部存储器
* 按存储模式初使化重入堆栈及堆栈指针
* 初始化8051硬件堆栈指针
* 向main( )函数交权
开发人员可修改以下数据从而对系统初始化
常数名 意义
IDATALEN 待清内部RAM长度
XDATA START 指定待清外部RAM起始地址
XDATALEN 待清外部RAM长度
IBPSTACK 是否小模式重入堆栈指针需初始化标志,1为需要。缺省为0
IBPSTACKTOP 指定小模式重入堆栈顶部地址
XBPSTACK 是否大模式重入堆栈指针需初始化标志,缺省为0
XBPSTACKTOP 指定大模式重入堆栈顶部地址
PBPSTACK 是否Compact重入堆栈指针,需初始化标志,缺省为0
PBPSTACKTOP 指定Compact模式重入堆栈顶部地址
PPAGEENABLE P2初始化允许开关
PPAGE 指定P2值
PDATASTART 待清外部RAM页首址
PDATALEN 待清外部RAM页长度
提示:如果要初始化P2作为紧凑模式高端地址,必须:PPAGEENAGLE=1,PPAGE为P2值,例如指定某页1000H-10FFH,则PPAGE=10H,而且连接时必须如下:
L51<input modules> PDATA(1080H),其中1080H是1000H-10FFH中的任一个值。
以下是STARTUP.A51代码片断,红色是经常可能需要修改的地方:
;------------------------------------------------------------------
; This file is part of the C51 Compiler package
; Copyright KEIL ELEKTRONIK GmbH 1990
;------------------------------------------------------------------
; STARTUP.A51: This code is executed after processor reset.
;
; To translate this file use A51 with the following invocation:
;
; A51 STARTUP.A51
;
; To link the modified STARTUP.OBJ file to your application use the following
; L51 invocation:
;
; L51 <your object file list>, STARTUP.OBJ <controls>
;
;------------------------------------------------------------------
;
; User-defined Power-On Initialization of Memory
;
; With the following EQU statements the initialization of memory
; at processor reset can be defined:
;
; ; the absolute start-address of IDATA memory is always 0
IDATALEN EQU 80H ; the length of IDATA memory in bytes.
;
XDATASTART EQU 0H ; the absolute start-address of XDATA memory
XDATALEN EQU 0H ; the length of XDATA memory in bytes.
;
PDATASTART EQU 0H ; the absolute start-address of PDATA memory
PDATALEN EQU 0H ; the length of PDATA memory in bytes.
;
; Notes: The IDATA space overlaps physically the DATA and BIT areas of the
; 8051 CPU. At minimum the memory space occupied from the C51
; run-time routines must be set to zero.
;-------------------------------------------------------------------
;
; Reentrant Stack Initilization
;
; The following EQU statements define the stack pointer for reentrant
; functions and initialized it:
;
; Stack Space for reentrant functions in the SMALL model.
IBPSTACK EQU 0 ; set to 1 if small reentrant is used.
IBPSTACKTOP EQU 0FFH+1 ; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the LARGE model.
XBPSTACK EQU 0 ; set to 1 if large reentrant is used.
XBPSTACKTOP EQU 0FFFFH+1; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the COMPACT model.
PBPSTACK EQU 0 ; set to 1 if compact reentrant is used.
PBPSTACKTOP EQU 0FFFFH+1; set top of stack to highest location+1.
;
;-----------------------------------------------------------------
;
; Page Definition for Using the Compact Model with 64 KByte xdata RAM
;
; The following EQU statements define the xdata page used for pdata
; variables. The EQU PPAGE must conform with the PPAGE control used
; in the linker invocation.
;
PPAGEENABLE EQU 0 ; set to 1 if pdata object are used.
PPAGE EQU 0 ; define PPAGE number.
;
;-----------------------------------------------------------------
3. 标准输入输出文件
putchar.c
putchar.c是一个低级字符输出子程,开发人员可修改后应用到自己的硬件系统上,例如向CLD或LEN输出字符。
缺省:putchar.c是向串口输出一个字符XON|XOFF是流控标志,换行符“\*n”自动转化为回车/换行“\r\n”。
getkey.c
getkey函数是一个低级字符输入子程,该程序可用到自己硬件系统,如矩阵键盘输入中,缺省时通过串口输入字符。
4. 其它文件
还包括对Watch-Dog有独特功能的INIT.A51函数以及对8×C751适用的函数,可参考源代码。
第四节 段名转换与程序优化
1. 段名协定(Segment Naming Conventions)
C51编译器生成的目标文件存放于许多段中,这些段是代码空间或数据空间的一些单元,一个段可以是可重定位的,也可以是绝对段,每一个可重定位的段都有一个类型和名字,C51段名有以下规定:
每个段名包括前缀与模块名两部分,前缀表示存储类型,模块名则是被编译的模块的名字,例如:
?CO?main1 :表示main1模块中的代码段中的常数部分
?PR?function1?module 表module模块中函数function1的可执行段,具体规定参阅手册。
2. 程序优化
C51编译器是一个具有优化功能的编译器,它共提供六级优化功能。确保生成目标代码的最高效率(代码最少,运行速度最快)。具体六级优化的内容可参考帮助。
在C51中提供以下编译控制指令控制代码优化:
OPTIMIZE(SJXE):尽量采用子程序,使程序代码减少。
NOAREGS:不使用绝对寄存器访问,程序代码与寄存器段独立。
NOREGPARMS:参数传递总是在局部数据段实现,程序代码与低版本C51兼容。
OPTIMIZE(SIZE)AK OPTIMIZE(speed)提供6级优化功能,缺省为: OPTIMIZE(6,SPEED)。
一节 本征库函数(intrinsic routines)和非本征证库函数
C51强大功能及其高效率的重要体现之一在于其丰富的可直接调用的库函数,多使用库函数使程序代码简单,结构清晰,易于调试和维护,下面介绍C51的库函数系统。
C51提供的本征函数是指