1、第三章 Keil C 语言及其程序设计C51 是在标准 C 的基础上,根据单片机存储器硬件结构及内部资源,扩展了相应的数据类型和变量,而 C51 在语法规定、程序结构与设计方法上,都与标准 C 基本相同。Keil C 语言的编译器及编译过程如图 3-1 所示。图 3-1 Keil C 语言的编译器及编译过程C 语言是美国国家标准协会(ANSI)制定的编程语言标准, 1987 年 ANSI公布 87 ANSI C,即标准 C 语言。Keil C51 语言是在 ANSI C 的基础上针对 51 单片机的硬件特点进行的扩展,并向 51 单片机上移植,经过多年努力,C51 语言已经成为公认的高效、简洁
2、而又贴近 51 单片机硬件的实用高级编程语言。目前大多数的 51 单片机用户都在使用 C51 语言来进行程序设计。用 C51 进行单片机软件开发,有如下优点:(1)可读性好。C51 语言程序比汇编语言程序的可读性好,因而编程效率高,程序便于修改。(2)模块化开发与资源共享。用 C51 开发出来的程序模块可以不经修改,直接被其他项目所用,这使得开发者能够很好地利用已有的大量的标准 C 程序资源与丰富的库函数,减少重复劳动。(3)可移植性好。为某种型号单片机开发的 C 语言程序,只需将与硬件相关之处和编译连接的参数进行适当修改,就可以方便地移植到其他型号的单片机上。例如,为 51 单片机编写的程序
3、通过改写头文件以及少量的程序行,就可以方便地移植到 PIC 单片机上。(4)代码效率高。当前较好的 C51 语言编译系统编译出来的代码效率只比直接使用汇编语言低 10 20%左右,如果使用优化编译选项,效果会更好。【例】 利用单片机的 P1 口接 8 个发光二极管,P0 口接 8 个开关,编程实现,当开关动作时,对应的发光二极管亮或灭。只须把 P0 口的内容读入后,通过 P1 口输出即可。汇编程序:ORG 0100HMOV P0,#0FFHLOOP: MOV A,P0MOV P1,ASJMP LOOPC51 语言程序:#include void main(void)unsigned char
4、i;P0=0xFF;while(1) i=P0;P1=i;3.1.2 Keil C51 的开发环境Keil C51 是德国 Keil Software 公司开发的用于 51 系列单片机的 C51 语言开发软件。Keil C51 在兼容 ANSI C 的基础上,又增加很多与 51 单片机硬件相关的编译特性,使得开发 51 系列单片机程序更为方便和快捷,程序代码运行速度快,所需存储器空间小,完全可以和汇编语言相媲美。它支持众多的 MCS-51 架构的芯片,同时集编辑、编译、仿真等功能于一体,具有强大的软件调试功能,是众多的单片机应用开发软件中最优秀的软件之一。目前,Keil C51 已被完全集成到
5、一个功能强大的全新集成开发环境( IDE)Vision3 中, 该环境集成了 文件编辑处理、编译链接、项目( Project)管理、窗口、工具引用和仿真软件模拟器以及 Monitor51 硬件目标调试器等多种功能,这些功能可在 Keil Vision3 环境中进行操作。Vision3 内部集成了源程序编辑器,并允许用户在编辑源文件时就可设置程序调试断点,便于在程序调试过程中快速检查和修改程序。此外,Vision3还支持软件模拟仿真(Simulator)和用户目标板调试 (Monitor51)两种工作方式。在软件模拟仿真方式下不需任何 51 单片机及其外围硬件即可完成用户程序仿真调试。在用户目标
6、板调试方式下,利用硬件目标板中的监控程序可以直接调试目标硬件系统,使用户节省购买硬件仿真器的费用。3.1.3 C51 与标准 C 的主要区别不同的嵌入式处理器的 C 编译系统与标准 C 的不同之处 ,主要是它们所针对的嵌入式处理器的硬件系统不同。Keil C51 的基本语法与标准 C 相同,但对标准 C 进行了扩展。理解 Keil C51 对标准 C 的扩展部分是掌握 Keil C51 的关键。C51 与标准 C 的主要区别如下:(1)头文件的差异。51 系列单片机厂家有多个,它们的差异在于内部资源如定时器、中断、I/O 等数量以及功能的不同,而对使用者来说,只需要将相应的功能寄存器的头文件加
7、载在程序内,就可实现所具有的功能。因此,Keil C51 系列的头文件集中体现了各系列芯片的不同资源及功能。(2)数据类型的不同。51 系列单片机包含位操作空间和位操作指令,因此Keil C51 与 ANSI C 相比又扩展了 4 种类型,以便能够灵活地进行操作。(3)数据存储类型的不同。C 语言最初是为通用计算机设计的,在通用计算机中只有一个程序和数据统一寻址的内存空间,而 51 系列单片机有片内、外程序存储器,还有片内、外数据存储器。标准 C 并没有提供这部分存储器的地址范围的定义。此外,对于 AT89C51 单片机中大量的特殊功能寄存器也没有定义。(4)标准 C 语言没有处理单片机中断的
8、定义。(5)Keil C51 与标准 C 的库函数有较大的不同。由于标准 C 的中的部分库函数不适于嵌入式处理器系统,因此被排除在Keil C51 之外,如字符屏幕和图形函数。有一些库函数可以继续使用,但这些库函数都必须针对 51 单片机的硬件特点来作出相应的开发,与标准 C 库函数的构成与用法有很大的不同。例如库函数 printf 和 scanf,在标准 C 中,这两个函数通常用于屏幕打印和接收字符,而在 Keil C51 中,它们主要用于串行口数据的收发。(6)程序结构的差异。由于 51 单片机的硬件资源有限,它的编译系统不允许太多的程序嵌套。其次,标准 C 所具备的递归特性不被 Keil
9、 C51 支持,在C51 中,要使用递归特性,必须用 reentrant 进行声明才能使用。但是从数据运算操作、程序控制语句以及函数的使用上来说,Keil C51 与标准 C 几乎没有什么明显的差别。如果程序设计者具备了有关标准 C 的编程基础,只要注意 Keil C51 与标准 C 的不同之处,并熟悉 AT89S51 单片机的硬件结构,就能够较快地掌握 Keil C51 的编程。3.2 C51 语言程序设计基础3.2.1 C51 语言中的数据1. 数据类型Keil C51 的基本数据类型如表 3-1 所示。针对 AT89S51 单片机的硬件特点,C51 在标准 C 的基础上,扩展了 4 种数
10、据类型(见表中最后 4 行) 。注意:扩展的 4 种数据类型,不能使用指针对它们存取。表3-1 Keil C51支持的数据类型数据类型 位数 字节数 值 域signed char 8 1 -128 +127unsigned char 8 1 0255signed short 16 2 -32768+32767unsigned short 16 2 065535signed int 16 2 -32768+32767unsigned int 16 2 065535signed long 32 4 -2147483648+2147483647unsigned long 32 4 042949672
11、95float 32 4 1.175494E-383.402823E+38bit 1 0 或 1sbit 1 0 或 1sfr 8 1 0255sfr16 16 2 065536在 C51 语言程序中,有可能会出现在运算中数据类型不一致的情况。C51允许任何标准数据类型的隐式转换。隐式转换的优先级顺序如下:Bit charintlong floatsignedunsigned也就是说,当 char 型与 int 型进行运算时,先自动对 char 型扩展为 int 型,然后与 int 型进行运算,运算结果为 int 型。C51 除了支持隐式类型转换外,还可以通过强制类型转换符“() ”对数据类型
12、进行人为的强制转换。2. C51 的扩展数据类型80C51 系列单片机用特殊功能寄存器 SFR 来控制定时器、计数器、串口、并口和外围设备。它们分别用位、字节和字进行访问。与此对应,编译器提供bit、sbit、sfr 和 sfr16 数据类型访问 SFR。下面对表 3-1 中扩展的 4 种数据类型进行说明。(1)位变量 bit 类型bit 的值可以是 1(true), 也可以是 0(false ) 。在 C51 中,允许用户通过位类型符定义位变量。位类型符有两个:bit 和sbit。可以定义两种位变量。bit 位类型符用于定义一般的可位处理位变量。它的格式如下:bit 位变量名;例如:bit
13、flag1;bit flag2;所有的 bit 变量放在 80C51 内部数据存储区的(20H2FH)位段。因为这个区域只有 16 个字节长,所以在某个范围内最多只能定义 128 个位变量。在格式中可以加上各种修饰,但注意存储器类型只能是bdata、data、idata。只能是片内 RAM 的可位寻址区,严格来说只能是 bdata。例如:bit data a1; /*正确*/bit bdata a2; /*正确*/bit pdata a3; /*错误*/bit xdata a4; /*错误*/(2)特殊功能寄存器 sfr 类型特殊功能寄存器可以用 sfr 来定义,通过名字或地址来引用特殊功能寄
14、存器。AT89S51 特殊功能寄存器在片内 RAM 区的 80HFFH 之间, “sfr” 数据类型占用一个内存单元。利用它可访问 AT89S51 内部的所有特殊功能寄存器。例如:sfr P0=0x80 这一语句定义 P0 口在片内的寄存器,在后面语句中可用“P0=0xff”( 使 P0 的所有引脚输出为高电平)之类的语句来操作特殊功能寄存器。在等号(赋值号)的右边指定的地址必须是一个常数,不允许用带操作数的表达式。80C51 系列支持 SFR 的地址范围为:0x80 0xFF 。同样:sfr p1=0x90;sfr p2=0xa0;sfr p3=0xb0;p1、p2 和 p3 是声明的 SF
15、R 名。【这些定义在 REG51.H 文件中已经定义过】(3)特殊功能寄存器 sfr16 类型“sfr16”数据类型 占用两个内存单元,用来定义 16 位的特殊功能寄存器。sfr16 和 sfr 一样用于操作特殊功能寄存器。所不同的是它用于操作占两个字节的特殊功能寄存器。特殊功能寄存器名一般用大写字母表示。地址一般用直接地址形式。例如 DPTR。通过名字或地址来引用特殊功能寄存器。编译器提供 sfr16 数据类型,将两个 8 位的 SFR 作为一个 16 位的 SFR 来访问。例如: sfr16 DPTR=0x82 语句定义了片内 16 位数据指针寄存器 DPTR,其低 8 位字节地址为 82
16、H。在后面的语句中可以对 DPTR 进行操作。在等号(赋值号)的右边指定的地址必须是一个常数,不允许用带操作数的表达式。而且必须是 SFR 的低位和高位字节中的低位字节的地址 。(4)特殊功能位 sbit可位寻址的特殊功能寄存器的位变量定义用关键字 sbit。格式如下:sbit 位变量名= 位地址;位地址可有 2 种形式:位直接地址,其取值范围为 0x800xff特殊功能寄存器名带位号,特殊功能寄存器与位号之间一般用“”作间隔。例如:sbit EA=0xAF; 【IE 寄存器的 D7,即 IE.7】sbit EA=IE7; 【IE 寄存器的 D7,即 IE.7】符号“”前 面是特殊功能寄存器的
17、名字 , “”的后 面数字定义特殊功能寄存器可寻址位在寄存器中的位置,取值必须是 07。注意,不要把 bit 与 sbit 混淆。bit 用来定义普通的位变量,值只能是二进制的 0 或 1(位变量在 RAM:20H2FH 之间) 。而 sbit 定义的是特殊功能寄存器的可寻址位,其值是可进行位寻址的特殊功能寄存器的位绝对地址。【例 4-5】sbit 型变量的定义。sbit P=0xd0;【 sbit P=PSW0; 】sbit CY=0xd7;【 sbit P=PSW7; 】sfr P1=0x90;sbit P1_0=P10;sbit P1_1=P11;sbit P1_2=P12;sbit P
18、1_3=P13;sbit P1_4=P14;sbit P1_5=P15;sbit P1_6=P16;sbit P1_7=P17;在 C51 中,为了用户处理方便,C51 编译器把 MCS-51 单片机的常用的特殊功能寄存器和特殊位进行了定义,放在一个“reg51.h”或“reg52.h ”的头文件中,当用户要使用时,只需要在使用之前用一条预处理命令#include 或#include 把这个头文件包含到程序中,然后就可使用殊功能寄存器名和某些特殊位名称。3. 数据的存储类型针对 80C51 存储空间的特点,可以利用存储空间的修饰符,来指明所定义的变量应分配在什么样的存储空间。C51 存储类型与
19、 AT89S51 的实际存储空间的对应关系见表 3-2。下面对表3-2 作以说明。(1)片内数据存储器片内数据存储区是可读/写的。80C51 系列最多可有 256 字节的内部数据存储区。内部数据区,可以分成 3 个不同的存储类型data、idata 和 bdata。data:片内直接寻址区,位于片内 RAM 的低 128 字节。为片内直接寻址的 RAM 空间,寻址范围为 0127(00H7FH, 30H7FH) 。在此空间内,存取速度最快。声明中,如果没有修饰符,则数据默认的存储空间为 data 型,也就是在片内 RAM 中。idata:片内间接寻址区,片内 RAM 所有地址单元(00HFFH
20、) 。为片内间接寻址的 RAM 空间,寻址范围 0255。由于只能间接寻址,访问速度比直接寻址慢。bdata:片内位寻址区,位于片内 RAM 位寻址区 20H2FH,位地址范围位0127。在此空间允许按字节和按位寻址。bit 定义的变量,严格来说只能是 bdata。表 3-2 存储器类型 说 明code 外部程序存储器空间(64KB)data 直接访问的内部数据存储器,访问速度最快(位于片内RAM 的低 128 字节)idata 间接访问的内部数据存储器,可以访问所有的内部存储空间(256 字节)bdata 可位寻址的内部数据存储器,可用字节方式,也可用位方式访问,位于 20H2FH(16 字
21、节)xdata 外部数据存储器 RAM(64KB)pdata 片外 RAM 的一个分页寻址区,每页 256 字节(2)片外数据存储器外部数据存储区是可读/写的。可通过一个数据指针加载一个地址来间接访问外部数据区。因此,访问外部数据区比访问内部数据存储区慢。外部数据存储区最多可有 64KB。由于硬件设计时,要把外围设备映射到该存储区,所以这些地址不一定都能用来作为数据存储区。编译器提供两种不同的存储类型来访问外部数据xdata 和 pdata。xdata 该标识是指外部数据存储区(64KB)内的任何地址,寻址范围为065535。pdata 该标识符仅指一页或 256 字节的外部数据存储区,寻址范
22、围为0255。在定义变量时,通过指明存储器类型,可以将所定义的变量存储在指定的存储区域中。访问内部数据存储器比访问外部数据存储器快得多。因此,应该把频繁使用的变量放置在内部数据存储器中,把很少使用的变量放在外部数据存储器中。在变量声明中,可以包括存储器类型和 singed 或 unsinged 属性。例如:char data var1;char code text=”Enter Parameter”;unsigned long xdata array100;float idata x,y,z;unsigned int pdata dimension;unsigned char xdata ve
23、ctor1044;char bdata flag; 【如果位变量区有未用的单元,可以进行字节访问】如果在变量的定义中,没有包括存储器类型,那么将自动选用默认的存储器类型。 【默认的是什么?】C51 编译器在头文件“reg51.h”中定义了全部 sfr/sfr16 和 sbit 变量。用一条预处理命令#include 把这个头文件包含到 C51 程序中,无需重定义即可直接使用它们的名称。REG51.H 文件中定义的内容Header file for generic 80C51 and 80C31 microcontroller.-*/#ifndef _REG51_H_#define _REG51
24、_H_/* BYTE Register */ 字节寄存器sfr P0 = 0x80;sfr P1 = 0x90;sfr P2 = 0xA0;sfr P3 = 0xB0;sfr PSW = 0xD0;sfr ACC = 0xE0;sfr B = 0xF0;sfr SP = 0x81;sfr DPL = 0x82;sfr DPH = 0x83;sfr PCON = 0x87;sfr TCON = 0x88;sfr TMOD = 0x89;sfr TL0 = 0x8A;sfr TL1 = 0x8B;sfr TH0 = 0x8C;sfr TH1 = 0x8D;sfr IE = 0xA8;sfr IP
25、= 0xB8;sfr SCON = 0x98;sfr SBUF = 0x99;/* BIT Register */ 可位寻址的位变量/* PSW */sbit CY = 0xD7;sbit AC = 0xD6;sbit F0 = 0xD5;sbit RS1 = 0xD4;sbit RS0 = 0xD3;sbit OV = 0xD2;sbit P = 0xD0;/* TCON */sbit TF1 = 0x8F;sbit TR1 = 0x8E;sbit TF0 = 0x8D;sbit TR0 = 0x8C;sbit IE1 = 0x8B;sbit IT1 = 0x8A;sbit IE0 = 0x
26、89;sbit IT0 = 0x88;/* IE */sbit EA = 0xAF;sbit ES = 0xAC;sbit ET1 = 0xAB;sbit EX1 = 0xAA;sbit ET0 = 0xA9;sbit EX0 = 0xA8;/* IP */ sbit PS = 0xBC;sbit PT1 = 0xBB;sbit PX1 = 0xBA;sbit PT0 = 0xB9;sbit PX0 = 0xB8;/* P3 */sbit RD = 0xB7;sbit WR = 0xB6;sbit T1 = 0xB5;sbit T0 = 0xB4;sbit INT1 = 0xB3;sbit INT0 = 0xB2;sbit TXD = 0xB1;sbit RXD = 0xB0;/* SCON */sbit SM0 = 0x9F;sbit SM1 = 0x9E;sbit SM2 = 0x9D;sbit REN = 0x9C;sbit TB8 = 0x9B;sbit RB8 = 0x9A;sbit TI = 0x99;sbit RI = 0x98;#endif