广州周立功单片机发展有限公司Te:(020)38730916387309173870976387097Fax389305 第二章用C对8051编程 1为什么要用高级语言? 当设计一个小的嵌入式系统时,一般我们都用汇编语言。在很多工程中,这是一个很 好的方法,因为,代码一般都不超过8K,而且都比较简单。如果硬件工程师要同时设计软 件和硬件,经常会采用汇编语言来做程序。我的经验告述我,硬件工程师一般不熟系像C 类的高级语言。 使用汇编的麻烦在于它的可读性和可维护性,特别当程序没有很好的标注的时候。代 码的可重用性也比较低。如果使用C的话,可以很好的解决这些问题。 用C编写的程序,因为C语言很好的结构性和模块化,更容易阅读和维护,而且由于 模块化,用C语言编写的程序有很好的可移植性。功能化的代码能够很方便的从一个工程 移植到另一个工程,从而减少了开发时间 用C编写程序比汇编更符合人们的思考习惯。开发者可以更专心的考虑算法而不是考 虑一些细节问题。这样就减少了开发和调试的时间。 使用像C这样的语言,程序员不必十分熟系处理器的运算过程。这意味着对新的处理 器也能很快上手,不必知道处理器的具体内部结构,使得用C编写的程序比汇编程序有更 好的可移植性。很多处理器支持C编译器。 所有这些并不说明汇编语言就没了立足之地,很多系统,特别是实时时钟系统都是用 C和汇编语言联合编程。对时钟要求很严格时,使用汇编语言成了唯一的方法,除此之外 根据我的经验,包括硬件接口的操作都应该用C来编程。C的特点就是,可以使你尽量少 地对硬件进行操作,是一种功能性和结构性很强的语言。 2c语言的一些要点 这里不是教你如何使用C语言。关于C语言的书有很多,像 Kernighan和 Ritchie所 著的C编程语言等,这本书被认为是C语言的权威著作。Keil的C51完全支持C的标准指 令和很多用来优化8051指令结构的C的扩展指令 我们将复习关于C的一些概念,如结构,联合和类型定义。可能会使一些人伤脑筋 2.1结构 结构是一种定义类型,它允许程序员把一系列变量集中到一个单元中。当某些变量相 关的时候使用这种类型是很方便的。例如,你用一系列变量来描述一天的时间,你需要定 义时,分,秒三个变量 unsigned char hour, min, sec 还要定义一个天的变量 unsigned int days 通过使用结构,你可以把这四个变量定义在一起,给他们一个共同的名字。声明结构 的语法如下 struct time str ed char hour, min, sec unsigned int days ) time of day 这告述编译器定义一个类型名为time_str的结构,并定义一个名为 time of day的结 构变量。变量成员的引用为结构变量名.结构成员 ne of day. he TE [HOURS time of day days=XBYTE [DAYS]
广州周立功单片机发展有限公司 Tel 020 38730916 38730917 38730976 38730977 Fax:38730925 16 第二章 用 C 对 8051 编程 1 为什么要用高级语言 当设计一个小的嵌入式系统时 一般我们都用汇编语言 在很多工程中 这是一个很 好的方法 因为 代码一般都不超过 8K 而且都比较简单 如果硬件工程师要同时设计软 件和硬件 经常会采用汇编语言来做程序 我的经验告述我 硬件工程师一般不熟系像 C 一类的高级语言 使用汇编的麻烦在于它的可读性和可维护性 特别当程序没有很好的标注的时候 代 码的可重用性也比较低 如果使用 C 的话 可以很好的解决这些问题 用 C 编写的程序 因为 C 语言很好的结构性和模块化 更容易阅读和维护 而且由于 模块化 用 C 语言编写的程序有很好的可移植性 功能化的代码能够很方便的从一个工程 移植到另一个工程 从而减少了开发时间 用 C 编写程序比汇编更符合人们的思考习惯 开发者可以更专心的考虑算法而不是考 虑一些细节问题 这样就减少了开发和调试的时间 使用像 C 这样的语言 程序员不必十分熟系处理器的运算过程 这意味着对新的处理 器也能很快上手 不必知道处理器的具体内部结构 使得用 C 编写的程序比汇编程序有更 好的可移植性 很多处理器支持 C 编译器 所有这些并不说明汇编语言就没了立足之地 很多系统 特别是实时时钟系统都是用 C 和汇编语言联合编程 对时钟要求很严格时 使用汇编语言成了唯一的方法 除此之外 根据我的经验 包括硬件接口的操作都应该用 C 来编程 C 的特点就是 可以使你尽量少 地对硬件进行操作 是一种功能性和结构性很强的语言 2 C 语言的一些要点 这里不是教你如何使用 C 语言 关于 C 语言的书有很多 像 Kernighan 和 Ritchie 所 著的 C 编程语言等 这本书被认为是 C 语言的权威著作 Keil 的 C51 完全支持 C 的标准指 令和很多用来优化 8051 指令结构的 C 的扩展指令 我们将复习关于 C 的一些概念 如结构 联合和类型定义 可能会使一些人伤脑筋 2.1 结构 结构是一种定义类型 它允许程序员把一系列变量集中到一个单元中 当某些变量相 关的时候使用这种类型是很方便的 例如 你用一系列变量来描述一天的时间 你需要定 义时 分 秒三个变量 unsighed char hour,min,sec; 还要定义一个天的变量 unsighed int days; 通过使用结构 你可以把这四个变量定义在一起 给他们一个共同的名字 声明结构 的语法如下 struct time_str{ unsigned char hour,min,sec; unsigned int days; }time_of_day; 这告述编译器定义一个类型名为 time_str 的结构 并定义一个名为 time_of_day 的结 构变量 变量成员的引用为结构 变量名.结构成员 time_of_day.hour=XBYTE[HOURS]; time_of_day.days=XBYTE[DAYS];
广州周立功单片机发展有限公司Te:(020)38730916387309173870976387097Fax389305 time of day. min=time of day. sec curdays=time of day. days 成员变量和其它变量是一样的,但前面必须有结构名。你可以定义很多结构变量,编 译器把他们看成新的变量。例如 struct time str oldtime. net 这样就产生了两个新的结构变量,这些变量都是相互独立的,就像定义了很多int类 型的变量一样。结构变量可以很容易的复制: oldtime=time of day 这使代码很容易阅读,也减少了打字的工作量。当然,你也可以一句一句的复制 oldtime, hour=newtime hour oldtime days=newtime. days-1 在 Keil c和大多数C编译器中,结构被提供了连续的存储空间,成员名被用来对结构 内部进行寻址。这样,结构tine_str被提供了连[ Offset MemberBytes 续5个字节的空间。空间内的变量顺序和定义时 的变量顺序一样。如表0-1 如果你定义了一个结构类型,它就像一个变量 新的变量类型。你可建立一个结构数组,包含结构 表0-1 的结构,和指向结构的指针。 2.2联合 联合和结构很相似,它由相关的变量组成,这些变量构成了联合的成员。但是这些成 员只能有一个起作用。联合的成员变量可以是任何有效类型,包括C语言本身拥有的类型 和用户定义的类型,如结构和联合。一个定义联合的类型如下 union time type I unsigned long secs in year: struct time str time I mytime 用一个长整形来存放从这年开始到现在的秒数,另一个可选项是用 time str结构来存 储从这年开始到现在的时间 不管联合包含什么,可在任何时候引用他的成员,如下例: mytime. secs in year=JUNEIST: mytime. time hour=5 curdays=mytime. time. days 像结构一样,联合也以连续的空间存储,空间大小等于联合中最大的成员所需的空间 Offset Bytes Mytime 表 因为最大的成员需要5个字节,联合的存储大小为5个字节。当联合的成员为 secs in year时,第5个字节没有使用 联合经常被用来提供同一个数据的不同的表达方式。例如,假设你有一个长整型变量 用来存放四个寄存器的值。如果希望对这些数据有两种表达方法,可以在联合中定义一个 长整型变量,同时再定义一个字节数组,如下例 unsigned char status [4]
广州周立功单片机发展有限公司 Tel 020 38730916 38730917 38730976 38730977 Fax:38730925 17 time_of_day.min=time_of_day.sec curdays=time_of_day.days; 成员变量和其它变量是一样的 但前面必须有结构名 你可以定义很多结构变量 编 译器把他们看成新的变量 例如 struct time_str oldtime,newtime; 这样就产生了两个新的结构变量 这些变量都是相互独立的 就像定义了很多 int 类 型的变量一样 结构变量可以很容易的复制 oldtime=time_of_day; 这使代码很容易阅读 也减少了打字的工作量 当然 你也可以一句一句的复制 oldtime.hour=newtime.hour; oldtime.days=newtime.days-1; 在 Keil C 和大多数 C 编译器中 结构被提供了连续的存储空间 成员名被用来对结构 内部进行寻址 这样 结构 time_str 被提供了连 续 5 个字节的空间 空间内的变量顺序和定义时 的变量顺序一样 如表 0-1: 如果你定义了一个结构类型 它就像一个变量 新的变量类型 你可建立一个结构数组 包含结构 的结构 和指向结构的指针 2.2 联合 联合和结构很相似 它由相关的变量组成 这些变量构成了联合的成员 但是这些成 员只能有一个起作用 联合的成员变量可以是任何有效类型 包括 C 语言本身拥有的类型 和用户定义的类型 如结构和联合 一个定义联合的类型如下 union time_type { unsigned long secs_in_year; struct time_str time; }mytime; 用一个长整形来存放从这年开始到现在的秒数 另一个可选项是用 time_str 结构来存 储从这年开始到现在的时间 不管联合包含什么 可在任何时候引用他的成员 如下例 mytime.secs_in_year=JUNEIST; mytime.time.hour=5; curdays=mytime.time.days; 像结构一样 联合也以连续的空间存储 空间大小等于联合中最大的成员所需的空间 Offset Member Bytes 0 Secs_in_year 4 0 Mytime 5 表 0-2 因为最大的成员需要 5 个字节 联合的存储大小为 5 个字节 当联合的成员为 secs_in_year 时 第 5 个字节没有使用 联合经常被用来提供同一个数据的不同的表达方式 例如 假设你有一个长整型变量 用来存放四个寄存器的值 如果希望对这些数据有两种表达方法 可以在联合中定义一个 长整型变量 同时再定义一个字节数组 如下例 union status_type{ unsigned char status[4]; Offset Member Bytes 0 hour 1 1 min 1 2 sec 1 3 days 2 表 0-1
广州周立功单片机发展有限公司Te:(020)38730916387309173870976387097Fax389305 unsigned long status val Jio status io status, status val=0x12345678 if(io status status [2]&0x10)[ 2.3指针 指针是一个包含存储区地址的变量。因为指针中包含了变量的地址,它可以对它所指 向的变量进行寻址,就像在8051DATA区中进行寄存器间接寻址和在 XDATA区中用DPTR 进行寻址一样。使用指针是非常方便的,因为它很容易从一个变量移到下一个变量,所以 可以写出对大量变量进行操作的通用程序。 指针要定义类型,说明指向何种类型的变量。假设你用关键字1ong定义一个指针,C 就把指针所指的地址看成一个长整型变量的基址。这并不说明这个指针被强迫指向长整型 的变量,而是说明C把该指针所指的变量看成长整型的。下面是一些指针定义的例子 unsigned char =my ptr, *anther ptr unsigned int * kint pt float *float pt time str *time ptr 指针可被赋予任何已经定义的变量或存储器的地址。 My ptr=&char va Int ptr=&int array [10] Time str=Roldtime 可通过加减来移动指针,指向不同的存储区地址。在处理数组的时候,这一点特别有 用。当指针加1的时候,它加上指针所指数据类型的长度 Time_ptr=( time str*)(0x10000);//指向地址0 Time ptr++ //指向地址5 指针间可像其它变量那样互相赋值。指针所指向的数据也可通过引用指针来赋值 time ptr=oldtime ptr /两个指针指向同一地址 kint_ptr=0x4500 //把0X4500赋给int_ptr所指的变量 当用指针来引用结构或联合的成员时,可用如下方法 time _ ptr->days=234 time ptr hour=12 还有一个指针用得比较多的场合是链表和树结构。假设你想产生一个数据结构,可以 进行插入和查询操作,一种最简单的方法就是建立一个双向查询树,你可以像下面那样定 义树的节点: struct bst node unsigned char name [20] //存储姓名 struct bst node*left, right;//分别指向左,右子树的指针 可通过定位新的变量,并把他的地址赋给查询树的左指针或右指针来使双向查询树变 长或缩短。有了指针后,对树的处理变得简单
广州周立功单片机发展有限公司 Tel 020 38730916 38730917 38730976 38730977 Fax:38730925 18 unsigned long status_val; }io_status; io_status.status_val=0x12345678; if(i0_status.status[2]&0x10){ … } 2.3 指针 指针是一个包含存储区地址的变量 因为指针中包含了变量的地址 它可以对它所指 向的变量进行寻址 就像在 8051 DATA 区中进行寄存器间接寻址和在 XDATA 区中用 DPTR 进行寻址一样 使用指针是非常方便的 因为它很容易从一个变量移到下一个变量 所以 可以写出对大量变量进行操作的通用程序 指针要定义类型 说明指向何种类型的变量 假设你用关键字 long 定义一个指针 C 就把指针所指的地址看成一个长整型变量的基址 这并不说明这个指针被强迫指向长整型 的变量 而是说明 C 把该指针所指的变量看成长整型的 下面是一些指针定义的例子 unsigned char *my_ptr,*anther_ptr; unsigned int *int_ptr; float *float_ptr; time_str *time_ptr; 指针可被赋予任何已经定义的变量或存储器的地址 My_ptr=&char_val; Int_ptr=&int_array[10]; Time_str=&oldtime; 可通过加减来移动指针 指向不同的存储区地址 在处理数组的时候 这一点特别有 用 当指针加 1 的时候 它加上指针所指数据类型的长度 Time_ptr=(time str *) (0x10000L); //指向地址 0 Time_ptr++; //指向地址 5 指针间可像其它变量那样互相赋值 指针所指向的数据也可通过引用指针来赋值 time_ptr=oldtime_ptr //两个指针指向同一地址 *int_ptr=0x4500 //把 0X4500 赋给 int_ptr 所指的变量 当用指针来引用结构或联合的成员时 可用如下方法 time_ptr->days=234; *time_ptr.hour=12; 还有一个指针用得比较多的场合是链表和树结构 假设你想产生一个数据结构 可以 进行插入和查询操作 一种最简单的方法就是建立一个双向查询树 你可以像下面那样定 义树的节点 struct bst_node{ unsigned char name[20]; //存储姓名 struct bst_node *left, right; //分别指向左 右子树的指针 }; 可通过定位新的变量 并把他的地址赋给查询树的左指针或右指针来使双向查询树变 长或缩短 有了指针后 对树的处理变得简单
广州周立功单片机发展有限公司Te:(020)38730916387309173870976387097Fax389305 2.4类型定义 在C中进行类型定义就是对给定的类型一个新的类型名。换句话说就是给类型一个新 的名字。例如,你想给结构 time str一个新的名字 typedef struct time str unsigned char hour, min, sec unsigned int days I time typ 这样,就可以像使用其它变量那样使用time_type的类型变量 time_type time, *time ptr, time array [10] 类型定义也可用来重新命名C的标准类型 typedef unsigned char UBYTE typedef char *strptr strptr name 使用类型定义可使你的代码的可读性加强,节省了一些打字的时间。但是很多程序员 大量的使用类型定义,别人再看你的程序时就十分困难了, 3Keic和ANs|c 下面将介绍 Keil c的主要特点和它与 ANSI C的不同之处,并给你一些对8051使用C 的启发。 Keil编译器除了少数一些关键地方外,基本类似于 ANSI C。差异主要是Keil可以让 户针对8051的结构进行程序设计,其它差异主要是8051的一些局限引起的。 3.1数据类型 Keil c有 ANSI C的所有标准数据类型,除此之外,为了更加有利的利用8051的结构, 还加入了一些特殊的数据类型。下表显示了标准数据类型在8051中占据的字节数。注意 整型和长整型的符号位字节在最低的地址中 数据类型 除了这些标准数据类型外,编译器还支持| char/unsigned char8bin 种位数据类型。一个位变量存在于内部RAM 的可位寻址区中。可像操作其它变量那样对位「 ong/unsigned lor 32 bit loat/doubl 变量进行操作,而位数组和位指针是违法的。 24 bit 表0-3 3.2特殊功能寄存器 特殊功能寄存器用sfr来定义,而sfr16用来定义16位的特殊功能寄存器如DPTR。 通过名字或地址来引用特殊功能寄存器。地址必须高于80H。可位寻址的特殊功能寄存器 的位变量定义用关键字sbit。SFR的定义如列表0-1所示。对于大多数8051成员,Keil 提供了一个包含了所有特殊功能寄存器和他们的位的定义的头文件。通过包含头文件可以 很容易的进行新的扩展。 列表0-1 //定义SCON //定义SCON的各位 sbit sm2=0X9D sbit ren=ox9c sbit tB8=0X9B
广州周立功单片机发展有限公司 Tel 020 38730916 38730917 38730976 38730977 Fax:38730925 19 2.4 类型定义 在 C 中进行类型定义就是对给定的类型一个新的类型名 换句话说就是给类型一个新 的名字 例如 你想给结构 time_str 一个新的名字 typedef struct time_str{ unsigned char hour,min,sec; unsigned int days; }time_type; 这样 就可以像使用其它变量那样使用 time_type 的类型变量 time_type time,*time_ptr,time_array[10]; 类型定义也可用来重新命名 C 的标准类型 typedef unsigned char UBYTE; typedef char *strptr; strptr name; 使用类型定义可使你的代码的可读性加强 节省了一些打字的时间 但是很多程序员 大量的使用类型定义 别人再看你的程序时就十分困难了 3 Keil C 和 ANSI C 下面将介绍 Keil C 的主要特点和它与 ANSI C 的不同之处 并给你一些对 8051 使用 C 的启发 Keil 编译器除了少数一些关键地方外 基本类似于 ANSI C 差异主要是 Keil 可以让 户针对 8051 的结构进行程序设计 其它差异主要是 8051 的一些局限引起的 3.1 数据类型 Keil C 有 ANSI C 的所有标准数据类型 除此之外 为了更加有利的利用 8051 的结构 还加入了一些特殊的数据类型 下表显示了标准数据类型在 8051 中占据的字节数 注意 整型和长整型的符号位字节在最低的地址中 除了这些标准数据类型外 编译器还支持 一种位数据类型 一个位变量存在于内部 RAM 的可位寻址区中 可像操作其它变量那样对位 变量进行操作 而位数组和位指针是违法的 3.2 特殊功能寄存器 特殊功能寄存器用 sfr 来定义 而 sfr16 用来定义 16 位的特殊功能寄存器如 DPTR 通过名字或地址来引用特殊功能寄存器 地址必须高于 80H 可位寻址的特殊功能寄存器 的位变量定义用关键字 sbit SFR 的定义如列表 0-1 所示 对于大多数 8051 成员 Keil 提供了一个包含了所有特殊功能寄存器和他们的位的定义的头文件 通过包含头文件可以 很容易的进行新的扩展 列表 0-1 sfr SCON=0X98; //定义 SCON sbit SM0=0X9F; //定义 SCON 的各位 sbit SM1=0X9E; sbit SM2=0X9D; sbit REN=0x9C; sbit TB8=0X9B; 数据类型 大小 char/unsigned char 8 bit int/unsigned char 16 bit long/unsigned long 32 bit float/double 32 bit generic pointer 24 bit 表 0-3
广州周立功单片机发展有限公司Te:(020)38730916387309173870976387097Fax389305 sbit rB8=0X9A sbit ti=0X9 sbit ri=ox98 4存储类型 Keil允许使用者指定程序变量的存储区。这使使用者可以控制存储区的使用。编译器 可识别以下存储区 存储区 描述 RAM的低128个字节,可在一个周期内直接寻址 BDATA DATA区的16个字节的可位寻址区 IDATA RAM区的高128个字节,必须采用间接寻址 PDATA外部存储区的256个字节,通过P0口的地址对其寻址 使用指令MOVX@Rn,需要两个指令周期 XDATA外部存储区,使用DPTR寻址 CODE 程序存储区,使用DPTR寻址 4.1DATA区 对DATA区的寻址是最快的,所以应该把使用频率高的变量放在DATA区。由于空间有 限,必须注意使用。DATA区除了包含程序变量外,还包含了堆栈和寄存器组,DATA区的声 明如列表0-2: 列表0-2 unsigned char data system status=0 unsigned int data unit id[2] char data inp string[16] float data outp value type data new var 标准变量和用户自定义变量都可存储在DATA区中,只要不超过DATA区的范围。因为 C51使用默认的寄存器组来传递参数,你至少失去了8个字节。另外,要定义足够大的堆 栈空间。当你的内部堆栈溢出的时候,你的程序会莫名其妙的复位。实际原因是8051系列 微处理器没有硬件报错机制,堆栈溢出只能以这种方式表示出来。 4.2 BDATA区 你可以在DATA区的位寻址区定义变量,这个变量就可进行位寻址,并且声明位变量 这对状态寄存器来说是十分有用的,因为它需要单独的使用变量的每一位。不一定要用位 变量名来引用位变量。下面是一些在 BDATA段中声明变量和使用位变量的例子 列表0-3 unsigned char bdata status byte unsigned int bdata status word unsigned long bdata status dword sbit stat flag=status byte 4: if(status word 15)( stat flag=l 编译器不允许在 BDATA段中定义f1oat和 double类型的变量。如果你想对浮点数的每
广州周立功单片机发展有限公司 Tel 020 38730916 38730917 38730976 38730977 Fax:38730925 20 sbit RB8=0X9A; sbit TI=0X99; sbit RI=0X98; 4 存储类型 Keil 允许使用者指定程序变量的存储区 这使使用者可以控制存储区的使用 编译器 可识别以下存储区 存储区 描述 DATA RAM 的低 128 个字节 可在一个周期内直接寻址 BDATA DATA 区的 16 个字节的可位寻址区 IDATA RAM 区的高 128 个字节 必须采用间接寻址 PDATA 外部存储区的 256 个字节 通过 P0 口的地址对其寻址 使用指令 MOVX @Rn,需要两个指令周期 XDATA 外部存储区 使用 DPTR 寻址 CODE 程序存储区 使用 DPTR 寻址 4.1 DATA 区 对 DATA 区的寻址是最快的 所以应该把使用频率高的变量放在 DATA 区 由于空间有 限 必须注意使用 DATA 区除了包含程序变量外 还包含了堆栈和寄存器组 DATA 区的声 明如列表 0-2 列表 0-2 unsigned char data system_status=0; unsigned int data unit_id[2]; char data inp_string[16]; float data outp_value; mytype data new_var; 标准变量和用户自定义变量都可存储在 DATA 区中 只要不超过 DATA 区的范围 因为 C51 使用默认的寄存器组来传递参数 你至少失去了 8 个字节 另外 要定义足够大的堆 栈空间 当你的内部堆栈溢出的时候 你的程序会莫名其妙的复位 实际原因是 8051 系列 微处理器没有硬件报错机制 堆栈溢出只能以这种方式表示出来 4.2 BDATA 区 你可以在 DATA 区的位寻址区定义变量 这个变量就可进行位寻址 并且声明位变量 这对状态寄存器来说是十分有用的 因为它需要单独的使用变量的每一位 不一定要用位 变量名来引用位变量 下面是一些在 BDATA 段中声明变量和使用位变量的例子 列表 0-3 unsigned char bdata status_byte; unsigned int bdata status_word; unsigned long bdata status_dword; sbit stat_flag=status_byte^4; if(status_word^15){ … } stat_flag=1; 编译器不允许在 BDATA 段中定义 float 和 double 类型的变量 如果你想对浮点数的每