KeilC单片机C语言应用研究入门基础篇Word格式.docx
《KeilC单片机C语言应用研究入门基础篇Word格式.docx》由会员分享,可在线阅读,更多相关《KeilC单片机C语言应用研究入门基础篇Word格式.docx(18页珍藏版)》请在冰豆网上搜索。
![KeilC单片机C语言应用研究入门基础篇Word格式.docx](https://file1.bdocx.com/fileroot1/2023-1/28/9926bd2d-d34c-4765-9a55-b5307a0cd8aa/9926bd2d-d34c-4765-9a55-b5307a0cd8aa1.gif)
KeilC语言对数据进行了许多专业性的处理,避免了运行中间非异步的破坏;
它对不同函数的数据实行覆盖,有效利用片上有限的RAM空间;
它提供复杂的数据类型(数组、结构、联合、枚举、指针等),极大地增强了程序处理能力和灵活性;
它还提供auto、static、const等存储类型和专门针对8051单片机的data、idata、pdata、xdata、code等存储类型,自动为变量合理地分配地址;
提供small、compact、large等编译模式,以适应片上存储器的大小;
KeilC的中断服务程序的现场保护和恢复,中断向量表的填写,是直接与单片机相关的,都由KeilC编译器代办;
它还提供了常用的标准函数库,以供用户直接使用;
头文件中定义宏、说明复杂数据类型和函数原型,有利于程序的移植和支持单片机的系列化产品的开发;
它有严格的句法检查,错误很少,可容易地在高级语言的水平上迅速地被排掉;
并可方便地接受多种实用程序的服务:
如片上资源的初始化有专门的实用程序自动生成;
再如,有实时多任务操作系统可调度多道任务,简化用户编程,提高运行的安全性等等。
与TurboC相比KeilC可以更有效地利用片上有限的RAM空间,利用其特有的特殊功能寄存器用sfr可以更加有效地对硬件的I/O接口,提供了更有效率的内部RAM可寻址位或特殊功能寄存器中的可寻址位操作,KeilC编译器支持C中大部分的函数但是一些不适用于嵌入式系统应用的库函数则没有包含,而是由C51扩充了一些非C标准函数使其更加适合单片机的操作,它同时针对8051单片机的自身特点作了一些特殊扩展。
德国keil公司的单片机C语言编译器KeilC支持九种基本数据类型,变量可存放在bit、data、bdta、idata、xdata、pdata等不同类型的存储器空间;
支持AMD和DALLAS公司的80320等单片机的双数据指针和PHILIPS公司的807651等单片机的指令集以及Infieon(Siemens)公司80c517单片机中的算术单元和多重数据指针,支持数据覆盖。
KeilC是目前流行的单片机C语言调试和开发软件,Keil提供了包括C编译器、宏汇编、连接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(µ
Vision)将这些部份组合在一起。
KeilC51软件提供丰富的库函数和功能强大的Windows界面集成开发调试工具。
重要的一点是KeilC51生成的目标代码效率非常高,多数语句生成的汇编代码很紧凑,容易理解。
在开发大型软件时,更能体现高级语言的优势。
KeilC编译器能产生可重入代码,而且用KeilC语言可以打开和关闭中断。
KeilC程序结构与一般C语言没有什么差别。
一个C51源程序大体上是一个函数定义的集合,一个C源程序至少包括一个函数,在这个集合中有且仅有一个名为main()的函数(主函数),也可能包含其它函数,因此函数是C程序的基本单位。
主函数是程序的入口,一个C语言程序,总是从main()函数开始执行的,而不管物理位置上这个main()放在什么地方,主函数中的所有程序执行完毕,则程序执行完毕。
主程序通过直接书写语句和调用其它函数来实现有关功能,这些其它函数可以是由C语言本身提供给我们的,这样的函数称之为库函数;
也可以是用户自己编写的,这样的函数称之为用户自定义函数。
那么库函数和用户自定义函数有什么区别呢?
库函数是C51在库文件中已定义的函数,其函数说明在相关的头文件中。
对于这类函数用户在编程时只要用include预处理指令将头文件包含在用户文件中直接调用即可,简单地说,任何使用KeilC语言的人,都可以直接调用C的库函数而不需要为这个函数写任何代码,只需要包含具有该函数说明的相应的头文件即可;
而自定义函数则是完全个性化的,是用户根据自己需要而编写的。
KeilC提供了100多个库函数供我们直接使用。
在KeilC中,函数定义由函数类型、函数名、参数表和函数体四部分组成。
例如:
void mDelay (unsigned int DelayTime)。
函数名是一个标示符,标示符都是区分大小写的,最长为255个字符。
参数表是用圆括号括起来的若干参数,项与项之间用逗号隔开。
函数体是用大括号括起来的若干C语句,语句间用分号隔开,即函数首部下面的大括号“{}”内的部份,如果一个函数内有多个大括号,则最外层的一对“{}”为函数体的范围。
最后一个语句一般是return(在主函数中可以省略)。
每一个函数都返回一个值,该值由return语句中的表达式指定(省略时为零)。
函数体一般包括:
声明部份:
在这部份中定义所用到的变量;
执行部份:
由若干个语句组成。
在某此情况下也可以没有声明部份,甚至即没有声明部份,也没有执行部份,
如:
voidmDelay(){}
这是一个空函数,什么也不干,但它是合法的。
在编写程序时,可以利用空函数,比如主程序需要调用一个延时函数,可具体延时多少,怎么个延时法,暂时还不清楚,我们可以主程序的框架结构弄清,先编译通过,把架子搭起来再说,至于里面的细节,可以在以后慢慢地填,这时利用空函数,先写这么一个函数,这样在主程序中就可以调用它了。
函数的类型就是返回值的类型,函数类型(除整形外)均需在函数名前加以指定。
C51的一般格式如下:
类型 函数名(参数表)
参数说明:
{
数据说明部分;
执行语句部分;
}
一个函数在程序中可以三种形态出现:
函数定义、函数调用和函数说名。
函数定义相当于汇编程序中的一般子程序。
函数调用相当于调用子程序的CALL语句,在C中更普遍地规定函数调用可以出现在表达式中。
函数定义和函数调用不分先后,但若调用在定义之前就必须先进行函数说明在调用前。
函数说明是一个没有函数体的函数定义,而函数调用则要求有函数名和实参数表。
主程序中的mDelay如果写成mdelay就会编译出错,即C语言区分大小写,这一点往往让初学者非常困惑,尤其是学过一门其它语言的人,有人喜欢,有人不喜欢,但不管怎样,你得遵守这一规定。
C语言书写的格式自由,每个语句和资料定义的最后必须有一个分号,分号是C语句的必要组成部份。
C语言本身没有输入输出语句。
输入和输出操作是由库函数scanf和printf等函数来完成的。
C对输入输出实行“函数化”。
而C51编译器提供的输入输出库函数scanf和printf是通过8051单片机的串口实现的,在程序中使用这种输入输出库函数必须先对8051单片机的串口进行初始化。
但对于单片机应用系统来说,由于具体要求不同,应用系统输入输出方式多种多样,不可能一律采用串行口作输入输出而应根据实际需要,由应用系统的研制人员自己来编写满足特定需求的输入输出函数。
可以用/*…..*/[item1]的形式为C程序的任何一部份作注释,特别地,KeilC也支持C++风格的注释,就是用“//”引导的后面的语句是注释。
KeilC的主要特点和它与C的不同之处
下面将介绍KeilC的主要特点和它与C的不同之处,并给你一些对8051使用C的启发。
Keil编译器除了少数一些关键地方外基本类似于C,差异主要是KeilC可以让用户针对8051的结构进行程序设计,其它差异主要是由8051的一些局限引起的。
KeilC51编译器不支持16位宽的字符,而C提供对宽字符国际字符集的支持。
KeilC有C的所有标准数据类型,除此之外,为了更加有利的利用8051的结构还加入了一些特殊的数据类型,C语言中的基本数据类型有char、int、short、long、float和double,但对于C51编译器来说,short类型与int类型相同double类型与float类型相同。
下表显示了标准数据类型在8051中占据的字节数,注意整型和长整型的符号位字节在最低的地址中。
数据类型
大小
char/unsignedchar
8bit
int/unsignedchar
16bit
long/unsignedlong
32bit
float/double
genericpointer
24bit
除了这些标准数据类型外,编译器还支持一种位数据类型(bit:
位类型),一个位变量存在于内部RAM的可位寻址区中,可像操作其它变量那样对位变量进行操作,而位数组和位指针是违法的。
是C51编译器的一种扩充数据类型,利用它可定义一个变量,但不能定义位指针,也不能定义位数组。
1.特殊功能寄存器
特殊功能寄存器用sfr来定义,而sfr16用来定义16位的特殊功能寄存器,
DPTR
sfr是C51编译器的一种扩充数据类型,利用它可定义8051单片机的所有内部8位特殊功能寄存器。
sfr型数据占用一个内存单元变量,取值范围是0-255。
sfr16这是C51编译器的一种扩充数据类型,利用它可定义8051单片机内部16位特殊功能寄存器。
sfr型数据占用两个内存单元变量,取值范围是0-65535。
通过名字或地址来引用特殊功能寄存器,地址必须高于80H可位寻址的特殊功能寄存器的位变量定义用关键字sbit,SFR的定义如列表0-1所示对于大多数8051成员Keil提供了一个包含了所有特殊功能寄存器和他们的位的定义的头文件,通过包含头文件可以很容易的进行新的扩展,利用它可以定义8051单片机内部RAM中的可寻址位或特殊功能寄存器中的可寻址位。
列表0-1
sfrSCON=0X98;
//定义SCON
sbitSM0=0X9F;
//定义SCON的各位
sbitSM1=0X9E;
sbitSM2=0X9D;
sbitREN=0x9C;
sbitTB8=0X9B;
sbitRB8=0X9A;
sbitTI=0X99;
sbitRI=0X98;
2.存储类型
Keil允许使用者指定程序变量的存储区,这使使用者可以控制存储区的使用编译器,可识别以下存储区:
存储区
描述
DATA
RAM的低128个字节,可在一个周期内直接寻址
BDATA
DATA区的16个字节的可位寻址区
IDATA
RAM区的高128个字节,必须采用间接寻址
PDATA
外部存储区的256个字节,通过P0口的地址对其寻址
使用指令MOVX@Rn,需要两个指令周期
XDATA
外部存储区,使用DPTR寻址
CODE
程序存储区,使用DPTR寻址
2.1DATA区
对DATA区的寻址是最快的,所以应该把使用频率高的变量放在DATA区,由于空间有限,必须注意使用DATA区除了包含程序变量外,还包含了堆栈和寄存器组,DATA区的声明如列表0-2
列表0-2
unsignedchardatasystem_status=0;
unsignedintdataunit_id[2];
chardatainp_string[16];
floatdataoutp_value;
mytypedatanew_var;
标准变量和用户自定义变量都可存储在DATA区中,只要不超过DATA区的范围。
因为C51使用默认的寄存器组来传递参数,你至少失去了8个字节。
另外,要定义足够大的堆栈空间,当你的内部堆栈溢出的时候,你的程序会莫名其妙的复位,实际原因是8051系列微处理器没有硬件报错机制,堆栈溢出只能以这种方式表示出来
2.2BDATA区
你可以在DATA区的位寻址区定义变量,这个变量就可进行位寻址,并且声明位变量。
这对状态寄存器来说是十分有用的,因为它需要单独的使用变量的每一位,不一定要用位变量名来引用位变量,下面是一些在BDATA段中声明变量和使用位变量的例子
列表0-3
unsignedcharbdatastatus_byte;
unsignedintbdatastatus_word;
unsignedlongbdatastatus_dword;
sbitstat_flag=status_byte^4;
if(status_word^15){
…
}
stat_flag=1;
编译器不允许在BDATA段中定义float和double类型的变量,如果你想对浮点数的每位寻址,可以通过包含float和long的联合来实现。
列表0-4
typedefunion{//定义联合类型
unsignedlonglvalue;
//长整型32位
floatfvalue;
//浮点数32位
}bit_float;
//联合名
bit_floatbdatamyfloat;
//在BDATA段中声名联合
sbitfloat_ld=myfloat^31//定义位变量名
下面的代码访问状态寄存器的特定位。
把访问定义在DATA段中的一个字节和通过位名和位号访问同样的可位寻址字节的位的代码对比。
注意,对变量位进行寻址产生的汇编代码比检测定义在DATA段的状态字节位所产生的汇编代码要好,如果你对定义在BDATA段中的状态字节中的位采用偏移量进行寻址,而不是用先前定义的位变量名时,编译后的代码是错误的。
下面的例子中
列表0-5
1//定义一个字节宽状态寄存器
2unsignedchardatabyte_status=0x43;
3
4//定义一个可位寻址状态寄存器
5unsignedcharbdatabit_status=0x43;
6//把bit_status的第3位设为位变量
7sbitstatus_3=bit_status^3;
8
9bituse_bit_status(void);
10
11bituse_bitnum_status(void);
12
13bituse_byte_status(void);
14
15voidmain(void){
16unsignedchartemp=0;
17if(use_bit_status()){//如果第3位置位temp加1
18temp++;
19}
20if(use_byte_status()){//如果第3位置位temp再加1
21temp++;
22}
23if(use_bitnum_status()){//如果第3位置位temp再加1
24temp++;
25}
26}
27
28bituse_bit_status(void){
29return(bit)(status_3);
30}
31
32bituse_bitnum_status(void){
33return(bit)(bit_status^3);
34}
35
36bituse_byte_status(void){
37returnbyte_status&
0x04;
38}
2.3IDATA段
IDATA段也可存放使用比较频繁的变量,使用寄存器作为指针进行寻址,在寄存器中设置8位地址,进行间接寻址,和外部存储器寻址比较,它的指令执行周期和代码长度都比较短。
unsignedcharidatasystem_status=0;
unsignedintidataunit_id[2];
charidatainp_string[16];
floatidataoutp_value;
2.4PDATA和XDATA段
在这两个段声明变量和在其它段的语法是一样的,PDATA段只有256个字节,而XDATA段可达65536个字节,下面是一些例子:
unsignedcharxdatasystem_status=0;
unsignedintpdataunit_id[2];
charxdatainp_string[16];
floatpdataoutp_value;
对PDATA和XDATA的操作是相似的,对PDATA段寻址比对XDATA段寻址要快,因为对PDATA段寻址只需要装入8位地址,而对XDATA段寻址需装入16位地址,所以尽量把外部数据存储在PDATA段中,对PDATA和XDATA寻址要使用MOVX指令,需要两个处理周期。
列表0-6
1#include<
reg51.h>
2
3unisgnedcharpdatainp_reg1;
4
5unsignedcharxdatainp_reg2;
6
7voidmain(void){
8inp_reg1=P1;
9inp_reg2=P3;
10}
经常,外部地址段中除了包含存储器地址外还包含I/O器件的地址,对外部器件寻址可通过指针或C51提供的宏,我建议使用宏对外部器件进行寻址,因为这样更有可读性宏定义使得存储段看上去像char和int类型的数组,下面是一些绝对寄存器寻址的例子。
列表0-7
inp_byte=XBYTE[0x8500];
//从地址8500H读一个字节
inp_word=XWORD[0x4000];
//从地址4000H读一个字和2001H
c=*((charxdata*)0x0000);
//从地址0000读一个字节
XBYTE[0x7500]=out_val;
//写一个字节到7500H
可对除BDATA和BIT段之外的其它数据段采用以上方法寻址。
通过包含头文件absacc.h来进行绝对地址访问。
2.5CODE段
代码段的数据是不可改变的,8051的代码段不可重写,一般代码段中可存放数据表,跳转向量和状态表,对CODE段的访问和对XDATA段的访问的时间是一样的,代码段中的对象在编译的时候初始化,否则,你就得不到你想要的值,下面是代码段的声明例子。
unsignedintcodeunit_id[2]=1234;
unsignedchar
{
0x00,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位偏移量。
指针类型
通用指针
3字节
XDATA指针
2字节
CODE指针
IDATA指针
1字节
DATA指针
PDATA指针
Keil允许使用者规定指针指向的存储段这种指针叫具体指针,使用具体指针的好处是节省了存储空间,编译器不用为存储器选择和决定正确的存储器操作指令产生代码,这样就使代码更加简短,但你必须保证指针不指向你所声明的存储区以外的地方,否则会产生错误而且很难调试。
下面的例子反映出使用具体指针比使用通用指针更加高效,使用通用指针的第一个循环需要378处理周期,使用具体指针只需要151个处理周期。
列表0-8
1#include<
absacc.h>
2
3char*generic_ptr;
4
5chardata*xd_ptr;
6
7charmystring[]="
Testoutput"
;
9main(){
101generic_ptr=mystring;
111while(*generic_ptr){
122XBYTE[0x0000]=*generic_ptr;
132generic_ptr++;
142}
151
161xd_ptr=mystring;
171while(*xd_ptr){
182XBYTE