1、KeilC 单片机 C 语言应用研究系 别 计算机系 专 业 计算机科学与技术 姓 名 刘志远指导教师 张连生- 2 -Keil C 单片机 C 语言应用研究刘志远 (太原师 范学院计算机系 200202 班 山西太原 030012)指导老师:张连生【内容提要】 KeilC 是目前比较流行的单片机 C 语言调试和开发软件,多为当下单片机设计者所采用。但是, KeilC 和普通的 C 语言有很大的区别, KeilC 支持符合 ANSI 标准的 C 程序设计,它完全支持 C的标准指令和单片机优化 C 扩 展指令。特 别地,针对 8051 单片机的自身特点它作了一些特殊扩展。KeilC 生成的目标代
2、码效率非常高,生成的语句编码紧凑,易理解,在开发单片机软件时,能充分发挥单片机的有限资源,体现其高级语 言的优势。本文详细分析了 KeilC 语言的来源以及其与汇编语言、 Turbo C 的区别,重点论述了 KeilC 的特殊功能寄存器(SFR)、存储类型、指针和中断服务,反映了 KeilC 作为单片机开发软件的优势和高效率,并探讨了 KeilC 应用中的一些方法、技巧和注意事 项。【关键字】 单片机 KeilC 程序设计 C SFR引言:为什么要使用 KeilC?近年来随着计算机在社会领域的渗透, 单片机的应用正在不断地走向深入,随着单片机开发技术以及微控制技术(以软件代替硬件的高性能控制技
3、术)的日益完善和发展,单片机的应用必定导致传统控制技术发生巨大的变化,也就是说,单片机的应用是对传统控制技术的一场革命,因此,了解单片机的应用技术尤其是开发技术是非常有意义的。当设计一个小的嵌入式系统时,一般我们都用汇编语言,在很多工程中,这是一个很好的方法,因为代码一般都不超过 8K,而且都比较简单,如果硬件工程师要同时设计软件和硬件,经常会采用汇编语言来做程序,而硬件工程师一般不熟系像 C 一类的高级语言。与汇编语言相比 KeilC 它的可读性和可维护性都非常好,它是结构化的程序设计语言,编写效率很高,具有非常好的结构性和模块化,使得程序完全结构化,可移植性好而且更容易阅读和维护,代码的可
4、重用性也比较高,虽然 8051 芯片的派生门类特别多(达到了上百种之多) ,但 KeilC 语言对于它们的硬件资源大部分都可以进行操作。用 KeilC 编写程序比汇编更符合人们的思考习惯,开发者可以更专心的考虑算法而不是考虑一些细节问题,这样就减少了开发和调试的时间。尤其重要的是使用 KeilC这样的语言,程序员不必十分熟悉处理器的运算过程,也能够编出符合硬件实际的专业水平的程序,这意味着对新的处理器也能很快上手,不必知道处理器的具体内部结构,使得用 KeilC 编写的程序比汇编程序有更好的可移植性。KeilC 语言功能齐全,具有各种各样的数据类型,并引入了指针概念,可- 3 -使程序效率更高
5、。另外它也具有强大的图形功能,支持多种显示器和驱动器,而且计算功能、逻辑判断功能也比较强大,可以实现决策目的。而且它的适用范围广,适合多种操作系统,如DOS、Windows、UNIX,也适用于多种机型。用 KeilC 编写的程序具有坚固性:数据被破坏是导致程序运行异常的重要因素。KeilC 语言对数据进行了许多专业性的处理,避免了运行中间非异步的破坏;它对不同函数的数据实行覆盖,有效利用片上有限的 RAM 空间;它提供复杂的数据类型(数组、结构、联合、枚举、指针等) ,极大地增强了程序处理能力和灵活性;它还提供 auto、static、const 等存储类型和专门针对 8051 单片机的dat
6、a、idata、pdata 、xdata 、code 等存储类型,自动为变量合理地分配地址;提供small、 compact、large 等编译模式,以适应片上存储器的大小;KeilC 的中断服务程序的现场保护和恢复,中断向量表的填写,是直接与单片机相关的,都由 KeilC 编译器代办;它还提供了常用的标准函数库,以供用户直接使用;头文件中定义宏、说明复杂数据类型和函数原型,有利于程序的移植和支持单片机的系列化产品的开发;它有严格的句法检查,错误很少,可容易地在高级语言的水平上迅速地被排掉;并可方便地接受多种实用程序的服务:如片上资源的初始化有专门的实用程序自动生成;再如,有实时多任务操作系统
7、可调度多道任务,简化用户编程,提高运行的安全性等等。与 Turbo C 相比 KeilC 可以更有效地利用片上有限的 RAM 空间,利用其特有的特殊功能寄存器用sfr 可以更加有效地对硬件的 I/O 接口,提供了更有效率的内部 RAM 可寻址位或特殊功能寄存器中的可寻址位操作,KeilC 编译器支持 C 中大部分的函数但是一些不适用于嵌入式系统应用的库函数则没有包含,而是由 C51 扩充了一些非 C 标准函数使其更加适合单片机的操作 ,它同时针对 8051 单片机的自身特点作了一些特殊扩展。德国 keil 公司的单片机语言编译器 KeilC 支持九种基本数据类型,变量可存放在bit、data
8、、bdta 、idata、xdata、pdata 等不同类型的存储器空间;支持 AMD 和 DALLAS 公司的 80320等单片机的双数据指针和 PHILIPS 公司的 807651 等单片机的指令集以及 Infieon(Siemens )公司80c517 单片机中的算术单元和多重数据指针,支持数据覆盖。KeilC 是目前流行的单片机 C 语言调试和开发软件,Keil 提供了包括 C 编译器、宏汇编、连接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(Vision)将这些部份组合在一起。KeilC51 软件提供丰富的库函数和功能强大的 Windows 界面集成
9、开发调试工具。重要的一点是 KeilC51 生成的目标代码效率非常高,多数语句生成的汇编代码很紧凑,容易理解。在开发大型软件时,更能体现高级语言的优势。KeilC 编译器能产生可重入代码,而且用 KeilC 语言可以打开和关闭中断。KeilC 程序结构与一般 C 语言没有什么差别。一个 C51 源程序大体上是一个函数定义的集合,一个 C 源程序至少包括一个函数,在这个集合中有且仅有一个名为 main()的函数(主函数) ,也可能包含其它函数,因此函数是 C 程序的基本单位。主函数是程序的入口,一个 C 语言程序,总是从 main()函数开始执行的,而不管物理位置上这个 main()放在什么地方
10、,主函数中的所有程序执行完毕,则程序执行完毕。主程序通过直接书写语句和调用其它函数来实现有关功能,这些其它函数可以是由 C 语言本身提供给我们的,这样的函数称之为库函数;也可以是用户自己编写的,这样的函数称之为用户自定义函数。那么库函数和用户自定义函数有什么区别呢?库函数是 C在库文件中已定义的函数,其函数说明在相关的头文件中。对于这类函数用户在编程时只要用 include 预处理指令将头文件包含在用户文件中直接调用即可,简单地说,任何使用 KeilC 语言的人,都可以直接调用 C 的库函数而不需要为这个函数写任何代码,只需要包含具有该函数说明的相应的头文件即可;而自定义函数则是完全个性化-
11、4 -的,是用户根据自己需要而编写的。KeilC 提供了 100 多个库函数供我们直接使用。在 KeilC 中,函数定义由函数类型、函数名、参数表和函数体四部分组成。例如:void mDelay (unsigned int DelayTime)。函数名是一个标示符,标示符都是区分大小写的,最长为个字符。参数表是用圆括号括起来的若干参数,项与项之间用逗号隔开。函数体是用大括号括起来的若干 C 语句,语句间用分号隔开,即函数首部下面的大括号“” 内的部份,如果一个函数内有多个大括号,则最外层的一对“”为函数体的范围。最后一个语句一般是 return(在主函数中可以省略 )。每一个函数都返回一个值,
12、该值由return 语句中的表达式指定(省略时为零) 。函数体一般包括:声明部份:在这部份中定义所用到的变量;执行部份:由若干个语句组成。在某此情况下也可以没有声明部份,甚至即没有声明部份,也没有执行部份,如:void mDelay()这是一个空函数,什么也不干,但它是合法的。在编写程序时,可以利用空函数,比如主程序需要调用一个延时函数,可具体延时多少,怎么个延时法,暂时还不清楚,我们可以主程序的框架结构弄清,先编译通过,把架子搭起来再说,至于里面的细节,可以在以后慢慢地填,这时利用空函数,先写这么一个函数,这样在主程序中就可以调用它了。函数的类型就是返回值的类型,函数类型(除整形外)均需在函
13、数名前加以指定。C51 的一般格式如下:类型 函数名(参数表)参数说明:数据说明部分;执行语句部分;一个函数在程序中可以三种形态出现:函数定义、函数调用和函数说名。函数定义相当于汇编程序中的一般子程序。函数调用相当于调用子程序的 CALL 语句,在 C 中更普遍地规定函数调用可以出现在表达式中。函数定义和函数调用不分先后,但若调用在定义之前就必须先进行函数说明在调用前。函数说明是一个没有函数体的函数定义,而函数调用则要求有函数名和实参数表。主程序中的 mDelay 如果写成 mdelay 就会编译出错,即 C 语言区分大小写,这一点往往让初学者非常困惑,尤其是学过一门其它语言的人,有人喜欢,有
14、人不喜欢,但不管怎样,你得遵守这一规定。C 语言书写的格式自由,每个语句和资料定义的最后必须有一个分号,分号是 C 语句的必要组成部份。C 语言本身没有输入输出语句。输入和输出操作是由库函数 scanf 和 printf 等函数来完成的。C对输入输出实行“ 函数化” 。而 C51 编译器提供的输入输出库函数 scanf 和 printf 是通过单片机的串口实现的,在程序中使用这种输入输出库函数必须先对单片机的串口进行初始化。但对于单片机应用系统来说,由于具体要求不同,应用系统输入输出方式多种多样,不可能一律采用串行- 5 -口作输入输出而应根据实际需要,由应用系统的研制人员自己来编写满足特定需
15、求的输入输出函数。可以用/*.*/item1 的形式为 C 程序的任何一部份作注释,特别地, KeilC 也支持 C+风格的注释,就是用“/”引导的后面的语句是注释。KeilC 的主要特点和它与 C 的不同之处下面将介绍 KeilC 的主要特点和它与 C 的不同之处,并给你一些对 8051 使用 C 的启发。Keil 编译器除了少数一些关键地方外基本类似于 C,差异主要是 KeilC 可以让用户针对 8051 的结构进行程序设计,其它差异主要是由 8051 的一些局限引起的。KeilC51 编译器不支持 16 位宽的字符,而 C 提供对宽字符国际字符集的支持。KeilC 有 C 的所有标准数据
16、类型,除此之外,为了更加有利的利用 8051 的结构还加入了一些特殊的数据类型,C 语言中的基本数据类型有 char、int、short 、long 、float 和 double,但对于 C51 编译器来说,short 类型与 int 类型相同 double 类型与 float 类型相同。下表显示了标准数据类型在 8051 中占据的字节数,注意整型和长整型的符号位字节在最低的地址中。数据 类型 大小char/unsigned char 8 bitint/unsigned char 16 bitlong/unsigned long 32 bitfloat/double 32 bitgeneri
17、c pointer 24 bit除了这些标准数据类型外,编译器还支持一种位数据类型(bit :位类型) ,一个位变量存在于内部 RAM 的可位寻址区中,可像操作其它变量那样对位变量进行操作,而位数组和位指针是违法的。是 C51 编译器的一种扩充数据类型,利用它可定义一个变量,但不能定义位指针,也不能定义位数组。1. 特殊功能寄存器特殊功能寄存器用 sfr 来定义,而 sfr16 用来定义 16 位的特殊功能寄存器,如:DPTRsfr 是 C51 编译器的一种扩充数据类型,利用它可定义 8051 单片机的所有内部 8 位特殊功能寄存器。sfr 型数据占用一个内存单元变量,取值范围是 0-255。
18、sfr16 这是 C51 编译器的一种扩充数据类型,利用它可定义 8051 单片机内部 16 位特殊功能寄存器。sfr 型数据占用两个内存单元变量,取值范围是 0-65535。通过名字或地址来引用特殊功能寄存器,地址必须高于 80H 可位寻址的特殊功能寄存器的位变量定义用关键字 sbit,SFR 的定义如列表 0-1 所示对于大多数 8051 成员 Keil 提供了一个包含了所有特殊功能寄存器和他们的位的定义的头文件,通过包含头文件可以很容易的进行新的扩展,利用它可以定义 8051 单片机内部 RAM 中的可寻址位或特殊功能寄存器中的可寻址位。列表 0-1sfr SCON=0X98; /定义
19、SCON- 6 -sbit SM0=0X9F; /定义 SCON 的各位sbit SM1=0X9E; sbit SM2=0X9D; sbit REN=0x9C; sbit TB8=0X9B;sbit RB8=0X9A;sbit TI=0X99;sbit RI=0X98;2. 存储类型Keil 允许使用者指定程序变量的存储区,这使使用者可以控制存储区的使用编译器,可识别以下存储区:存储 区 描述DATA RAM 的 低1 28个 字节,可在 一 个周 期内 直接 寻址BDATA DATA 区的1 6个 字节 的可位 寻 址区IDATA RAM区 的高1 28个字 节,必 须 采用 间接 寻址PD
20、ATA 外部存 储区 的 256个 字节,通 过 P0口 的地 址对 其寻址使用指 令 MOVX Rn, 需要两 个 指令 周期XDATA 外部存 储区,使 用 DPTR寻址CODE 程序存 储区,使 用 DPTR寻址2.1 DATA 区对 DATA 区的寻址是最快的,所以应该把使用频率高的变量放在 DATA 区,由于空间有限,必须注意使用 DATA 区除了包含程序变量外,还包含了堆栈和寄存器组,DATA 区的声明如列表 0-2列表 0-2unsigned char data system_status=0;unsigned int data unit_id2;char data inp_st
21、ring16;float data outp_value;mytype data new_var;标准变量和用户自定义变量都可存储在 DATA 区中,只要不超过 DATA 区的范围。因为 C51 使用默认的寄存器组来传递参数,你至少失去了 8 个字节。另外,要定义足够大的堆栈空间,当你的内部堆栈溢出的时候,你的程序会莫名其妙的复位,实际原因是 8051 系列微处理器没有硬件报错机制,堆栈溢出只能以这种方式表示出来- 7 -2.2 BDATA 区你可以在 DATA 区的位寻址区定义变量,这个变量就可进行位寻址,并且声明位变量。这对状态寄存器来说是十分有用的,因为它需要单独的使用变量的每一位,不一
22、定要用位 变量名来引用位变量,下面是一些在 BDATA 段中声明变量和使用位变量的例子列表 0-3unsigned char bdata status_byte;unsigned int bdata status_word;unsigned long bdata status_dword;sbit stat_flag=status_byte4;if(status_word15)stat_flag=1;sfr SCON=0X98; /定义 SCON编译器不允许在 BDATA 段中定义 float 和 double 类型的变量,如果你想对浮点数的每位寻址,可以通过包含 float 和 long 的
23、联合来实现。列表 0-4typedef union /定义联合类型unsigned long lvalue; /长整型 32 位float fvalue; /浮点数 32 位bit_float; /联合名bit_float bdata myfloat; /在 BDATA 段中声名联合sbit float_ld=myfloat31 /定义位变量名 下面的代码访问状态寄存器的特定位。把访问定义在 DATA 段中的一个字节和通过位名和位号访问同样的可位寻址字节的位的代码对比。注意,对变量位进行寻址产生的汇编代码比检测定义在DATA 段的状态字节位所产生的汇编代码要好,如果你对定义在 BDATA 段中
24、的状态字节中的位采用偏移量进行寻址,而不是用先前定义的位变量名时,编译后的代码是错误的。下面的例子中 列表 0-51 /定义一个字节宽状态寄存器2 unsigned char data byte_status=0x43;3- 8 -4 /定义一个可位寻址状态寄存器5 unsigned char bdata bit_status=0x43;6 /把 bit_status 的第 3 位设为位变量7 sbit status_3=bit_status3;89 bit use_bit_status(void);1011 bit use_bitnum_status(void);12 13 bit use_
25、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 再加 121 temp+;22 23 if (use_bitnum_status() /如果第 3 位置位 temp 再加 124 temp+;25 26 2728 bit use_bit_status(void)29 return(bit)(status_3);30 3132
26、bit use_bitnum_status(void)- 9 -33 return(bit)(bit_status3);34 3536 bit use_byte_status(void)37 return byte _status38 2.3 IDATA 段IDATA 段也可存放使用比较频繁的变量,使用寄存器作为指针进行寻址,在寄存器中设置 8 位地址,进行间接寻址,和外部存储器寻址比较,它的指令执行周期和代码长度都比较短。unsigned char idata system_status=0;unsigned int idata unit_id2; char idata inp_string
27、16; float idata outp_value;2.4 PDATA 和 XDATA 段在这两个段声明变量和在其它段的语法是一样的,PDATA 段只有 256 个字节,而 XDATA 段可达 65536 个字节,下面是一些例子:unsigned char xdata system_status=0;unsigned int pdata unit_id2;char xdata inp_string16;float pdata outp_value;对 PDATA 和 XDATA 的操作是相似的,对 PDATA 段寻址比对 XDATA 段寻址要快,因为对PDATA 段寻址只需要装入 8 位地址
28、,而对 XDATA 段寻址需装入 16 位地址,所以尽量把外部数据存储在 PDATA 段中,对 PDATA 和 XDATA 寻址要使用 MOVX 指令,需要两个处理周期。列表 0-61 #include 23 unisgned char pdata inp_reg1;4 5 unsigned char xdata inp_reg2;6- 10 -7 void main(void)8 inp_reg1=P1;9 inp_reg2=P3;10 经常,外部地址段中除了包含存储器地址外还包含 I/O 器件的地址,对外部器件寻址可通过指针或 C51 提供的宏,我建议使用宏对外部器件进行寻址,因为这样更有
29、可读性宏定义使得存储段看上去像 char 和 int 类型的数组,下面是一些绝对寄存器寻址的例子。列表 0-7inp_byte=XBYTE0x8500; / 从地址 8500H 读一个字节inp_word=XWORD0x4000; / 从地址 4000H 读一个字和 2001Hc=*(char xdata *) 0x0000); / 从地址 0000 读一个字节XBYTE0x7500=out_val; / 写一个字节到 7500H可对除 BDATA 和 BIT 段之外的其它数据段采用以上方法寻址。通过包含头文件 absacc.h 来进行绝对地址访问。2.5 CODE 段代码段的数据是不可改变的
30、,8051 的代码段不可重写,一般代码段中可存放数据表,跳转向量和状态表,对 CODE 段的访问和对 XDATA 段的访问的时间是一样的,代码段中的对象在编译的时候初始化,否则,你就得不到你想要的值,下面是代码段的声明例子。unsigned int code unit_id2=1234;unsigned char0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x10,0x11,0x12,0x13,0x14,0x15, 3 指针C51 提供一个通用存储器指针,它本身是一变量,但其中存放的不是普通的数据,而是指向另一个数据的地址,指针变量也要占据一定的内存单元,在 C51 中指针变量的长度一般为 1-3 字节,指针变量也有具体类型,表示方法是在前面冠以数据类型符号,如 char * point1 表示 point1 是一个字符型的指针变量,指针变量的类型表示该指针所指向地址中数据的类型。使用指针变量可以方便的对 8051单片机各部分物理地址直接进行操作。3 字节的通用存储器指针,头一个字节表明指针所指的存储区空间,另外两个字节存储 16 位偏移量,对于 DATA,IDATA 和 PDATA 段,只需要 8 位偏移量。