《第五章 单片机的C语言程序设计及仿真调试.ppt》由会员分享,可在线阅读,更多相关《第五章 单片机的C语言程序设计及仿真调试.ppt(111页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第五章 单片机的C语言程序设计及仿真调试,任课教师:刘忠国 山东大学课程中心网站: stc15系列单片机器件手册等 Keil Software Cx51 编译器用户手册: Cx51编译器-对传统和扩展的8051微处理器的优化的C编译器和库参考,2,14:51:35,第五章 单片机的C语言程序设计及仿真调试,本章学习目标 掌握单片机C语言程序中的常用功能 掌握Keil C的程序设计 掌握IAP15W4K58S4单片机C语言程序调试过程,3,14:51:35,成绩考核方式: 期末考试(开卷80%)+平时成绩(20%),平时成绩包含: 考勤,作业,上课提问,实验,关于考核,4,第五章 单片机的C语言
2、程序设计及仿真调试,5.1 C51程序的基本语法 5.1.1 关键字 5.1.2 C51程序的一般结构 5.1.3 数据类型 5.1.4 运算符和表达式 5.2 Keil C51程序的语句 5.2.1 表达式语句 5.2.2 条件语句 5.2.3 开关语句 5.2.4 循环语句 5.2.5 goto、break、continue和return语句 5.3 函数 5.3.1 函数的定义与调用 5.3.2 Keil C51函数 5.4 Keil C51库函数 5.5 预处理命令 5.6 单片机C语言程序框架,5,14:51:35,汇编语言和C语言的选择问题,设计规模较小的嵌入式应用系统时,可以使用
3、汇编语言。因为代码一般不长,且较简单。 当程序比较复杂,且没有很好的注释时,使用汇编语言编写的程序,可读性和可维护性会很差,代码的可重用性也比较低。 使用C语言编程,编写简单、直观易读、便于维护、通用性好。 在控制任务比较复杂或者具有大量运算的系统中,C语言优势明显。由于模块化,用C语言编写的程序具有很好的可移植性。,6,14:51:35,5.1 C51程序的基本语法,1标准C语言(ANSI C)的关键字(32个) (1) 数据类型关键字 1)基本数据类型 void、char、int、float、double 2)类型修饰关键字 short、long、signed、unsigned 3)复杂类
4、型关键字 struct、union、enum、typedef、sizeof 4)存储级别关键字 auto、static、register、extern、const、volatile,5.1.1 关键字,7,14:51:35,5.1.1 关键字,(2)流程控制关键字 1)跳转结构 return 、 continue 、 break 、 goto 2)分支结构 if 、 else 、 switch 、 case、 default 3)循环结构 for 、 do 、 while 2Keil C51编译器支持的关键字(13个) bit、sbit、sfr、sfr16、data、bdata、idata、p
5、data、 xdata、 code、 interrupt、 reentrant、 using,8,14:51:35,预处理命令 /以#开头的命令,用于包含头文件、定义常数等 全局变量声明 /全局变量虽然方便传递参数,但不宜多 函数1的声明 . 函数n的声明 void main(void)/主函数 局部变量声明 /局部变量只能在所定义的函数内部引用 可执行语句 函数调用 无限循环 ,5.1.2 C51程序的一般结构,9,14:51:35,/一般函数的定义 函数1(形式参数声明) 局部变量声明 可执行语句 . 函数n(形式参数声明) 局部变量声明 可执行语句 ,5.1.2 C51程序的一般结构,1
6、0,14:51:36,/中断函数的实现 void ISRname(void) interrupt n /n为中断号 局部变量声明 可执行语句 ,C51程序注意如下几点: (1)所有函数以花括号“”开始,以花括号“”结束,包含在“ ”内的部分称为函数体。花括号必须成对出现,如果一个函数内有多对花括号,则最外层的花括号为函数体的范围。为了增加程序的可读性,应采用缩进方式书写。,5.1.2 C51程序的一般结构,11,14:51:36,注意如下几点: (2)建议一行写一条语句,每条语句最后必须以一个分号“;”结尾。 (3)每个变量必须先定义后引用。在函数内部定义的变量为局部变量,又称为内部变量,只有
7、定义它的那个函数才能使用。在函数外部定义的变量为全局变量,又称为外部变量,在定义它的那个程序文件中的函数都可以使用。 (4)程序语句的注释放在双斜杠“/”之后,或者放在“/*.*/”之内。,5.1.2 C51程序的一般结构,12,14:51:36,1常量和变量 常量包括整形常量、浮点型常量、字符型常量(如a: 单引号字符 ) 及字符串常量(双引号单个或多个字符,如 a, Happy)等。 变量是一种在程序执行过程中其值不断变化的量。使用一个变量之前,必须先进行定义。,5.1.3 数据类型 1常量和变量,2数据类型 (1)基本数据类型 基本数据类型是不可以再分解为其他类型的数据类型。如char
8、(字符型)、int(整型)、long(长整型)、float (浮点型)等。,13,14:51:36,2数据类型 (2)构造数据类型 构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。,5.1.3 数据类型 2数据类型,在C语言中,构造类型有以下几种: 数组类型 结构类型 联合类型,14,14:51:36,(3)指针类型 指针是一种特殊的,具有重要作用的数据类型。用来表示某个量在内存中的地址。,5.1.3 数据类型 2数据类型,(4)空类型 函数调用后并不需要向调用者返回函数值, 这种函数可以定义为“空类型”。其类型说明符为void。,15,14:51:36,C51编译器除了支持
9、上述数据类型外, 还支持以下几种扩充数据类型: (I)bit:位类型。bit型变量的位地址由编译器分配。 (II)sfr:特殊功能寄存器,用来控制中断、定时器、计数器、串口、I/O及其他部件。 (III)sfr16:16位特殊功能寄存器, 定义DPTR. (IV)sbit:位寻址。,5.1.3 数据类型 2数据类型,bit与sbit的用法区别:定义的bit型变量的位地址在00H7FH之间, 具体地址值不定, 由编译器随机分配。 sbit位寻址: 先指定一个可位寻址的变量作为基地址, 然后确定变量的某个位号来得到一个实际的位地址。,16,位地址不定,定义为bdata型变量或SFR的位,14:51
10、:36,(I)bit:位类型。可以定义一个位变量, 但不能定义位指针, 也不能定义位数组。 C51程序中, 函数参数和返回值也可以是位变量。例: bit finish_flag=0; 位地址具体值由编译器分配 bit testfunc(bit var1, bit var2) . return(0); ;,5.1.3 数据类型 2数据类型,所有bit型的变量都被定位在8051片内RAM的可位寻址区20H2FH(00H7FH), 共16个字节, 所以最多只能声明128个bit型位变量。,17,14:51:36,(II)sfr:字节寻址。语法如下: sfr sfr_name = int_consta
11、nt(地址值); 如 sfr P0=0 x80; 0 x80为P0口的地址, “=”后为常数, 且这个常数必须在特殊功能寄存器的地址范围内, 即0 x80到0 xFF之间。,5.1.3 数据类型 2数据类型,(III) sfr16:字寻址 如sfr16 DPTR=0 x82; 指定DPTR的地址DPL=0 x82,DPH=0 x83。 (IV) sbit: 位寻址。用于定义可位寻址变量: 可定义为片内RAM的 bdata存储类型(字节地址20H 2FH)变量的可寻址位, 或特殊功能寄存器的可寻址位。,18,14:51:36,5.1.3 数据类型 2数据类型,(IV) sbit:位寻址。定义为片
12、内bdata型变量(或特殊功能寄存器)的可寻址位。 bdata存储类型变量位于片内RAM的可位寻址区(20H 2FH), 可字节寻址, 或位寻址。 注意:使用bdata和sbit定义的变量必须是全局变量。,sbit声明方法: sbit bitname=bdata型变量(或sfr_name ) bit_number; 其中, sfr_name 是SFR的名字, bit_number是位号, 位号数值取决于基址对象的数据类型, 对char和SFR 而言是(07) ,对int型而言是(015) ,对long型而言是(031) 。,19,14:51:36,5.1.3 数据类型 2数据类型,sbit使用
13、举例: unsigned char bdata flag ; /flag为bdata型无符号字符变量 int bdata ibase ; /定义ibase为bdata整型变量 使用sbit定义可位寻址变量: sbit flag7=flag7 ; /定义flag7为flag的第7位 sbit mybit15=ibase15 ; /mybit15为ibase的第15位 sbit CY=PSW7 ;/定义CY为PSW的第7位 sbit P00=P00 ;/定义P0.0口线的名称是P00,20,14:51:36,sbit声明方法: sbit bitname=bdata型变量(或sfr_name ) b
14、it_number;,对特殊功能寄存器SFR, sbit可以有下面声明方法: 方法1:sbit bitname=sfr_namebit_number; 其中, sfr_name是SFR名字, bit_number是位号(07) 如:sbit CY=PSW7;/定义CY为PSW的第7位。 方法2:sbit bitname=sfr_addressbit_number; 其中, sfr_address是SFR的地址(0 x800 xff) 如:sbit OV=0 xD02;/定义PSW中的OV位 方法3:sbit bitname=bit_address; 其中, bit_address是位地址。 如
15、:sbit EA=0 xAF;/第0 xAF位为EA,5.1.3 数据类型 2数据类型,21,14:51:36,Keil C51编译器支持的数据类型如表5-1所示。,表5-1 Keil C51编译器支持的数据类型,5.1.3 数据类型 2数据类型,22,14:51:36,3存储器类型,表5-2 Keil C51编译器支持的存储器类型,对变量定义格式:数据类型 存储器类型 变量名表;,5.1.3 数据类型 3存储器类型,例如: unsigned char data buffer;,23,当AUXR.2(位EXTRAM)=0时,“MOVX Ri” 指令只能访问片内扩展的EXT_RAM的(00H0F
16、FH), 与P2口无关。 当AUXR.2(位EXTRAM)=1时,“MOVX Ri” 可访问片外扩展的EXT_RAM的一页, 页数由P2口输出决定(参见4.5.1.1 数据传送指令中MOVX指令的介绍 ),14:51:36,存储类型的指定: 变量或参数的存储类型可由存储模式指定缺省类型, 也可由关键字code、data、idata、xdata、pdata直接声明指定。 例如: unsigned char data buffer; data buffer; /没有指定数据类型, 默认为int型 unsigned char code numtab16=0 xC0, 0 xF9, 0 xA4, 0
17、xB0, 0 x99, 0 x92, 0 x82, 0 xFB, 0 x80, 0 x90, 0 x88, 0 x83, 0 xC6, 0 xA1, 0 x86, 0 x8E ; /定义LED显示字模(参见137页) unsigned char xdata arr1044;,5.1.3 数据类型 3存储器类型,对变量定义格式:数据类型 存储器类型 变量名表;,24,14:51:36,5.1.3 数据类型 3.存储器类型,数据存储类型的指定: 变量或函数参数存储类型可由存储模式 (Small, large, Compact) (Options for Target Target 1.选项)指定缺
18、省存储类型; 在small模式下, 函数参数和局部变量位于由data定义的单片机片内数据RAM(007FH)中; 在compact模式下, 函数参数和局部变量位于pdata定义的扩展数据RAM中(访问用MOVX Ri)。 在large模式下, 函数参数和局部变量位于xdata定义的扩展数据RAM中(访问用MOVX DPTR),数据,存储模式 (Small, large, Compact),25,当AUXR.2(位EXTRAM)=0时,“MOVX Ri” 指令只能访问片内扩展的EXT_RAM的(00H0FFH), 与P2口无关。 当AUXR.2(位EXTRAM)=1时,“MOVX Ri” 可访问
19、片外扩展的EXT_RAM的一页, 页数由P2口输出决定(参见4.5.1.1 数据传送指令中MOVX指令的介绍 ),14:51:36,在Xtal(MHz)右侧框输入6, 其余按默认设置。,4-3 汇编语言程序调试,Project窗口,选中Target 1, 并单击右键, 出现浮动菜单。浮动菜单中选中 Options for Target Target 1.选项。,出现Options for Target Target 1 对话框界面。在该界面中,点击Target标签。在该标签界面中,按下面设置参数:,在此设置使用晶振的频率,针对目标硬件设置工具选项,Memory Model右侧下拉选择: Sma
20、ll, Compact, large 的存储模式。,26,14:51:36,4关于指针数据类型 指针变量的值是一个地址, 这个地址不仅可以是变量的地址, 也可以是其他数据结构的地址。 Keil C51编译器支持两种指针类型:一般指针(Generic Pointer)和存储器指针(Memory Specific Pointer,在*前用data,xdata等指定存储区的指针)。 一般指针的声明和使用均与标准C相同, 同时还可以说明指针的存储类型(见下页)。 char *s ; /* string ptr */ int *numptr; /* int ptr */,5.1.3 数据类型 4关于指针
21、数据类型,27,14:51:36,char data * str; /存储器指针,str指向data区中char型数据,一般指针的声明和使用均与标准C相同, 同时还可以说明指针的存储类型。 char *s ; /* string ptr */ int *numptr; /* int ptr */ 可用存储类型标识符指定一个一般指针的存储区如: char * xdata strptr; /* generic ptr stored in xdata */ int * data numptr; /* generic ptr stored in data */ long * idata varptr;
22、 /* generic ptr stored in idata */ 这些例子指向可能保存在任何存储区中的变量 但是 指针分别保存在xdata、data和idata中。,5.1.3 数据类型 4关于指针数据类型,28,14:51:36,一般指针用3个字节存放: 存储器类型, 存储器地址高8位偏移量和低8位偏移量。 char data * str; /存储器指针 /str指向data区中char型数据 int xdata * pow; /存储器指针 /pow指向外部RAM的int型整数 这种指针存放时, 只需1(或2)个字节就够了, 因只需存放偏移量。,存储器指针(指定存储区的指针), 说明时即
23、指定了存储类型, 例如:,4.关于指针数据类型,存储器类型是在编译时需要的, 既已指出,所以指针只需存偏移量即可,参考:Keil Help: Cx51 Compiler Users GuideLanguage Extensions Pointers,29,存放指针所指向数据的存储器类型,14:51:36,5.1.3 数据类型 4.关于指针数据类型,象一般指针一样 可指定一个存储器指针的保存存储区, 即在指针声明前加存储类型标识符, 例如: unsigned char xdata *pt; /存储器指针 /pt本身依存储模式存放 unsigned char xdata * data pt; /存
24、储器指针 /pt被保存在内部RAM中 unsigned char xdata * xdata pt; /存储器指针 /pt被保存在外部RAM中 上面的语句都声明pt为指向保存在外部RAM中unsigned char数据的指针, 但pt本身的保存位置却不同。,存储器指针,30,14:51:36,运算符是告诉编译程序执行特定算术或逻辑操作的符号,表达式则是由运算符及运算对象所组成的具有特定含义的一个式子。 在任意一个表达式的后面加一个分号“;”就构成了一个表达式语句。C51程序就是由多个表达式语句构成的语句集合。 运算符可以分为赋值运算符、算术运算符、关系运算符、逻辑运算符、位运算符、复合赋值运算
25、符、逗号运算符、条件运算符、指针和地址运算符、强制类型转换运算符等。,5.1.4 运算符和表达式,31,14:51:36,1赋值运算符 符号“=”为赋值运算符,它的作用是将一个数据的值或表达式的值赋给一个变量。利用赋值运算符将一个变量与一个表达式连接起来的式子成为赋值表达式,在赋值表达式的后面加一个分号“;”便构成了赋值语句。,5.1.4 运算符和表达式 1赋值运算符,32,14:51:36,2算术运算符 算术运算符用于各类数值运算。包括加(+)、减或取负值(-)、乘(*)、除(/)、取余(或称模运算,%)、自增(+)、自减(-)共七种。 在除法运算中,如果是两个整数相除,其结果为整数,舍去小
26、数部分。 用算术运算符将运算对象连接起来的式子就是算术表达式。,5.1.4 运算符和表达式 2算术运算符,33,14:51:36,计算一个算术表达式的值时,要按照运算符的优先级高低顺序进行。算术运算符中,取负值(-)的优先级最高,其次是乘法(*)、除法(/)和取余(%)运算符,加法(+)和减法(-)运算符的优先级最低。需要时,可在算术表达式中必要的地方采用圆括号来改变优先级,括号的优先级最高。 在使用自增(+)运算符和自减(-)运算符时,要注意运算符的位置。例如,+i和i+的意义完全不同,前者为在使用i之前先使i加1,而后者则是在使用i之后再使i加1。在实际应用中,尽可能使用后者的方式,即i+
27、的形式。,5.1.4 运算符和表达式 2算术运算符,34,14:51:36,3关系运算符 关系运算符用于比较运算。包括大于()、小于(=)、小于等于(=)、等于(= =)和不等于(!=)六种。 前四种关系运算符具有相同的优先级,后两种关系运算符也具有相同的优先级;但前四种的优先级高于后两种。用关系运算符将两个表达式连接起来即构成关系表达式。,5.1.4 运算符和表达式 3关系运算符,35,14:51:36,4逻辑运算符 逻辑运算符包括与( / 第1行 a1=(+b,c-,d+3); / 第2行, a1的值为8 a2=+b, c-, d+3; / 第3行, a2的值为4,5.1.4 运算符和表达
28、式 7逗号运算符,43,14:51:36,8条件运算符 条件运算符( ? : )是一个三目运算符,用于条件求值。它要求有三个运算对象,使用它可以将三个表达式连接构成一个条件表达式。条件表达式的一般形式为: 逻辑表达式 ?表达式1:表达式2 其功能是,首先计算逻辑表达式的值,当逻辑表达式的值为真(非0值)时,将表达式1的值作为整个条件表达式的值;当逻辑表达式的值为假(0值)时,将表达式2的值作为整个条件表达式的值。,5.1.4 运算符和表达式 8条件运算符,44,min= (ab) ? a : b;,14:51:36,9指针和地址运算符 变量的指针就是该变量的地址, 而存放变量地址的变量称为指针
29、变量。为表示指针变量和它所指向的变量地址之间的关系, C语言有运算符: 取内容(*)和取地址(,例,14:51:36,使用指针可以进行外部扩展I/O口的访问。在C51中有两种方法访问外部I/O端口。 方法1:使用自定义指针。由于片外I/O端口与片外存储器统一编址,所以可以定义xdata类型的指针访问外部I/O端口。 例如, 某单片机应用系统中, 使用8255(见第1版8.3节)扩展I/O端口, 采用线选法对8255进行地址译码, 单片机的P2.7(A15)接8255的片选引脚, 因8255的命令口地址为7FF3H, PA口地址为7FF0H, PB口地址为7FF1H, PC口地址为7FFF2H。
30、访问8255的C程序如下:,5.1.4 运算符和表达式 9指针和地址运算符,46,8255因目前停产较少用, 本版教材不再介绍(用法见第1版8.3节),14:51:36,方法1:使用自定义指针访问外部扩展I/O口,写端口程序: char xdata *com8255; /定义指向外部存储区(片外I/O端口)的指针 com8255=0 x7ff3; /使指针指向8255的控制口(命令口)地址7FF3H *com8255=0 x81; /输出命令字81H到命令口寄存器 / 81H: PA, PB口都是模式0,直接输出, PC口直接输入 以上C程序相当于下面的汇编语言程序: MOV DPTR,#7F
31、F3H MOV A,#81H MOVX DPTR,A,具体命令介绍见见第1版8.3节:并行接口扩展方法,309页图8-52,47,14:51:36,方法1:使用自定义指针访问外部扩展I/O口,读端口程序: char xdata *com8255; /定义指针 char i; com8255=0 x7FF0; /使指针指向8255的PA口地址7FF0H i=*com8255; /读PA端口内容到变量 i,48,14:51:36,5.1.4 运算符和表达式 9指针和地址运算符,为了方便访问外部存储器及I/O端口, 在C51的absacc.h头文件做了如下定义, 利用这些定义可以方便地访问外部I/O
32、端口。 #define CBYTE (unsigned char volatile code *) 0) #define DBYTE (unsigned char volatile data *) 0) #define PBYTE (unsigned char volatile pdata *) 0) #define XBYTE (unsigned char volatile xdata *) 0),volatile修饰了的变量随程序的执行其值会被改变, “易变”,方法2:使用C51预定义指针访问外部扩展I/O口,49,14:51:36,预处理命令,#define XBYTE (unsigne
33、d char volatile xdata *) 0) 以XBYTE为例介绍上述宏定义使用方法, XBYTE使用格式: XBYTE 地址,方法2:使用C51预定义指针访问外部扩展I/O口,例如: #include #define PORTA XBYTE0 x7ff0 /其中,PORTA为程序定义的I/O端口名称, 内的内容 7ff0H为PORTA的地址 void main(void) char a; PORTA=0 x81; /*输出81H到端口7ff0H a=PORTA; /读端口7ff0H到变量a ,定义XBYTE数组的首地址是0,XBYTE 0 x7ff0的中括号内的值0 x7ff0,
34、指出了与(定义)数组XBYTE首地址的偏移地址,50,14:51:36,10强制类型转换运算符 强制类型转换运算符的作用是将表达式或变量的类型强制转换成为括号内所指定的类型。 强制类型转换运算符的一般使用形式为: 变量=(类型)表达式 如: pxdata=(char xdata *) 0 x3000; / pxdata为在xdata中定义的char类型指针变量,5.1.4 运算符和表达式 10强制类型转换运算符,51,14:51:36,表达式语句是最基本的一种语句。在表达式的后面加一个分号“;”就构成了表达式语句。 表达式语句也可以仅由一个分号“;”构成,这种语句称为空语句。空语句不执行具体的
35、动作。程序设计时,有时需要用到空语句。例如,使用循环语句延时程序中的循环体内可以使用空语句。,5.2 Keil C51程序的语句,5.2.1 表达式语句,52,14:51:36,条件语句又称为分支语句,使用关键字“if ”构成。C51提供了三种形式的条件语句。 1if (条件表达式) 语句体; ,5.2.2 条件语句,53,2 if (条件表达式) 语句体1; else 语句体2; ,14:51:36,3. if (条件表达式1) 语句体1; else if(条件表达式2) 语句体2; , else if(条件表达式m) 语句体m; Else 语句体n; ,5.2.2 条件语句,54,14:5
36、1:36,开关语句也是一种用来实现多条件分支的语句。, case 常量表达式n: 语句体n; break; default: 语句体d ,5.2.3 开关语句,55,switch(表达式) case 常量表达式1: 语句体1; break; case 常量表达式2: 语句体2; break;,14:51:36,1while语句 利用while语句构成循环结构的一般形式如下: while(条件表达式) 语句体; ,5.2.4 循环语句,56,2do-while语句 采用do-while语句构成循环结构的一般形式如下: do 语句体; while(条件表达式);,14:51:36,3for语句 采
37、用for语句构成循环结构的一般形式如下: for (初值设定表达式; 循环条件表达式; 更新表达式) 语句体; ,5.2.4 循环语句,57,14:51:36,goto语句是一个无条件转向语句,其一般形式为: goto 语句标号; break语句也可以用于跳出循环语句,其一般形式为: break; continue是一种中断语句,其功能是中断本次循环,继续下一次循环,一般形式为: continue; return语句用于终止函数的执行,并控制程序返回到调用该函数的位置。一般形式为: return (表达式); return ;,5.2.5 goto、break、continue和return语
38、句,58,14:51:36,59,在“bit bit_var=0; ”语句的bit型变量bit_var的定义中, 下面关于bit_var的位地址的说法,正确的是:,位地址在0127间;,位地址可位于0255之间;,位地址可位于特殊功能寄存器的位寻址区;,bit_var的位地址就是0。,A,B,C,D,提交,60,下面关于sbit的使用方法中,正确的用法是:,sbit OV=0 xD02;,sbit Define_bit=0 x202;,sbit Def_bit=0 x102;,int bdata myflag ; sbit myflag0=myflag0 ;,A,B,C,D,提交,sbit P
39、27=P27; sbit CY=PSW7;,E,A、B、C、D、E选项都不正确;,F,sfr PSW=0 xD0; /程序状态字,61,在C51语句“unsigned char xdata * data pt ;”中,指针pt和pt所指向的字符型数据分别存储在:,外部RAM和内部RAM中;,内部RAM和外部扩展RAM中;,内部RAM和内部RAM中;,外部扩展RAM和外部扩展RAM中;,A,B,C,D,提交,两种函数: Keil C51定义的标准库函数和用户自定义函数。,5.3.1 函数的定义与调用,5.3 函数,62,函数定义的一般形式为: 函数返回值类型 函数名 (形式参数表) 局部变量定义
40、 函数体语句 函数调用的一般形式为: 变量= 函数名 (实际参数表),14:51:36,5.3.2 Keil C51函数,C51的函数声明对ANSI C作了扩展,具体包括: 1、中断函数声明,中断函数通过使用interrupt关键字和中断号(031)来声明。中断号告诉编译器中断服务程序入口地址。,IAP15W4K58S4单片机的中断号及中断服务程序入口地址如表5-3,63,14:51:37,中断服务函数的一般形式为: void 函数名(void) interrupt 中断号 using n 中断函数通过使用interrupt关键字和中断号来声明。中断号告诉编译器中断服务程序的入口地址。也就是说
41、,C51通过中断号来区分各个不同的中断,而与中断函数的名字无关。 其中using n用于选择单片机不同的寄存器组,n为03的常整型数,分别选中4个不同寄存器组中的一个。Using是一个可选项,可以不用。不用时,由编译器自动选择一个寄存器组。,64,5.3.2 Keil C51函数 1、中断函数声明,14:51:37,5.3.2 Keil C51函数 1、中断函数声明,例如,串行口1的中断函数(4号中断)可以声明如下: void UART1_ISR (void) interrupt 4 using 1 /* 中断服务程序的代码 */ 上述代码声明了串行口1中断服务函数。其中, interrupt
42、 4指明是串行口1的中断, using 1指明采用工作寄存器区1区, using 1在中括号中, 说明该部分可省略。 其他中断函数的定义与此类似。中断函数具体是哪个中断的函数, 与中断号有关, 而与函数名 (如UART1_ISR )无关 , 但一般赋予与相应中断相符合的名称。,无输入参数,无返回参数,65,14:51:37,5.3.2 Keil C51函数 1、中断函数声明,当需要指定函数中使用的工作寄存器区时,使用关键字using后跟一个0到3的数,对应着工作寄存器0到3区。 例如,在下面的函数中使用了工作寄存器1区(相当于PSW.4=0, PSW.3=1): unsigned char G
43、etKey(void) using 1 /*用户程序代码*/ ,指定工作寄存器区,66,14:51:37,程序状态标志寄存器PSW (8位),RS1,RS0(PSW.4PSW.3):工作寄存器组选择控制位,其详细介绍见后续内容。 OV(PSW.2):溢出标志位。指示运算过程中是否发生了溢出,在执行指令过程中自动形成。,RS1,RS0,OV,P(PSW.0):奇偶标志位 累加器ACC中1的个数为偶数,P=0;否则P=1。每个指令周期都由硬件来置“1”或清“0”。在具有奇偶校验的串行数据通信中,可根据P设置奇偶校验位。,P,67,14:51:37,工作寄存器组的选择:,PSW寄存器中的RS1和RS
44、0两位组合决定当前使用的工作寄存器组。可通过位操作指令修改RS1和RS0的内容,选择不同的工作寄存器组。,表3-1 工作寄存器组选择,68,14:51:37,5.3.2 Keil C51函数2、指定存储模式,用户可以使用small,compact 及large说明存储模式。 例如: void fun1(void) small 提示:small说明的函数内部变量全部使用内部RAM。关键的、经常性的、耗时的地方可以这样声明,以提高运行速度。,2、指定存储模式,69,用compact说明时, 函数参数和局部变量位于pdata定义的扩展数据RAM中。 用large说明时, 函数参数和局部变量位于xda
45、ta定义的扩展数据RAM中。,14:51:37,5.3.2 Keil C51函数3、函数的重入,一个可重入函数, 简单来说, 就是可以被中断的函数, 即可以在这个函数执行的任何时刻中断它, 可重入函数可以被递归调用。 递归或可重入函数在单片机系统中容易产生问题, 因为单片机和PC不同, PC使用(调用)堆栈传递参数, 且静态变量以外的内部变量都在堆栈中; 而单片机一般使用寄存器传递参数, 内部变量一般在固定的RAM中, 函数重入时会破坏(覆盖)上次调用的数据。 可在函数前声明函数的不可重入性(不可中断性)。若声明为不可重入(中断)的, 则该函数调用过程中将不可被中断。,typedef unsi
46、gned char uchar; #pragma disable /* Disable Interrupts */ uchar dfunc (uchar p1, uchar p2) return (p1 * p2 + p2 * p1);,70,(#pragma的作用是设定编译器的状态或者指示编译器完成一些特定的动作),14:51:37,5.3.2 Keil C51函数3、函数的重入,可用以下两种方法解决函数的重入(及不可中断)问题: 第一种方法: 在相应的函数前使用“#pragma disable”声明, 即只允许主程序或中断之一调用该函数(函数调用后不再被中断)。 第二种方法: 将该函数说明
47、为可重入的。如下: void func(param.) reentrant . ;,因为单片机内部堆栈空间的限制,C51没有像大系统那样使用调用堆栈: 一般在C语言中调用过程时,会把过程的参数和过程中使用的局部变量入栈。 Keil C51编译后将生成一个可重入变量堆栈,然后就可以模拟通过调用堆栈传递变量的方法。,71,这样函数重入时, 不用担心变量数据被覆盖的问题。,14:51:37,5.3.2 Keil C51函数3、函数的重入,为了提高效率,C51没有提供这种调用堆栈,而是提供一种压缩栈。每个过程被给定同一个空间,用于存放局部变量。过程中的每个变量都存放在这个空间的固定位置。当递归调用这个
48、过程时,会导致变量被覆盖。 在某些实时应用中,非重入函数是不可取的。因为,函数调用时可能会被中断程序中断,而在中断程序中可能再次调用这个函数,所以C51允许将函数定义成重入函数。 重入函数可被递归调用和多重调用, 而不用担心变量被覆盖, 因C51可模拟生成一个可重入变量堆栈 随存储模式(small, compact, large)不同, 堆栈可在不同的存储器区(idata, pdata, xdata), 每次函数调用时的局部变量都会被单独保存。因为这些堆栈是模拟的,重入函数一般都比较大,运行起来也比较慢。,不常使用,72,14:51:37,5.3.2 Keil C51函数3、函数的重入,由于一般可重入函数由主程序和中断调用(即递归调用), 所以通常中断程序使用与主程序不同的工作寄存器组。 另外,对可重入函数,在相应的函数前面加上开关#pragma noaregs,以禁止编译器使用绝对寄存器寻址,可生成不依赖于寄存器组的代码。 所以对不依赖于寄存器组的函数可被其他使用了不同的寄存器组的多个函数调用。,extern char func (); #pragma NOAREGS noaregfunc () k = func () + func (); #pragma AREGS are