写点什么

C51 基本函数、中断函数和库函数的详解

作者:timerring
  • 2022 年 8 月 06 日
  • 本文字数:3354 字

    阅读完需:约 11 分钟

函数的定义和调用

此外,C51 编译器本身还提供了丰富的库函数,用户可以根据需要随时调用,从而大大提高编程效率。

1. 函数的定义

函数定义的一般形式为:


​ 函数类型 函数名(形参列表)

​ { 局部变量定义

​ 函数体

​ }


在用户的角度看来,有两类函数可以调用:用户自定义的函数标准库函数


  • 标准库函数是 C51 编译其提供的,不需要由用户进行定义,只需要包含相应的头文件即可(见库函数的说明)。

  • 用户自定义的函数是根据自己需要实现的功能编写的函数,必须先定义后调用(先调用后定义也可,不过要在调用前进行声明,就像上例。

2. 重入函数

通常情况下,C51 的函数不能被递归调用,也不能应用导致递归调用的结构。


重入函数特性允许你声明一个重入函数,使得该函数能够被递归调用。 实际上,当多个进程需要同时使用同一个函数时,这个函数就应定义成重入函数。当一个重入函数被调用运行时,另外的一个进程可以中断此运行过程,然后再次调用此重入函数。


定义重入函数的方法就是在函数声明时,用关键字“reentrant”进行声明。


例如:


#include  <reg52.h>  //包含特殊功能寄存器库
#include <stdio.h> //包含I/O函数库
extern serial_initial();
int fac(int n) reentrant
{
int result;
if (n == 0)
result=1;
else
result=n*fac(n-1);
return(result);
}
main()
{
int fac_result;
serial_initial();
fac_result=fac(11);
printf(“%d\n”,fac_result);
}
复制代码


重入函数在实时应用中,在中断服务程序代码和非中断程序代码必须共用一个函数的场合中经常用到。


需要注意的是,可以选择哪些必须的函数为重入函数而不需将全部程序声明为重入函数。把全部程序声明为重入函数,将增加目标代码的长度并减慢运行速度。

中断函数的定义

中断系统对于单片机系统来说十分重要,C51 编译器支持用 C 语言编写中断函数,从而减轻了用汇编语言编写中断服务程序的繁琐程度。中断服务程序的一般格式如下:


函数类型 函数名(形参列表) interrupt n [using m];


  • 中断函数类型一般为 void。

  • interrupt 后面的 n 是中断号,取值为 0~4,编译器从 8n+3 处产生一条长跳转指令,转向中断号为 n 的中断服务程序。

  • using m 用于选择不同的工作寄存器组。m 的取值范围为 0~3,分别对应与低 128 字节内部 RAM 区中的四组寄存器

  • 该项为可选项。



编写 MCS-51 中断函数注意如下:


  • 中断函数不能进行参数传递,如果中断函数中包含任何参数声明都将导致编译出错。

  • 中断函数没有返回值,如果企图定义一个返回值将得不到正确的结果,建议在定义中断函数时将其定义为 void 类型,以明确说明没有返回值。

  • 在任何情况下都不能直接调用中断函数,否则会产生编译错误。因为中断函数的返回是由 8051 单片机的 RETI 指令完成的,RETI 指令影响 8051 单片机的硬件中断系统。如果在没有实际中断情况下直接调用中断函数,RETI 指令的操作结果会产生一个致命的错误。

  • 如果在中断函数中调用了其它函数,则被调用函数所使用的寄存器组必须与中断函数相同。否则会产生不正确的结果。


C51 编译器对中断函数编译时会自动在程序开始和结束处加上相应的内容,具体如下:


  • 在程序开始处对 ACC、B、DPH、DPL 和 PSW 入栈,结束时出栈。

  • 中断函数未加 using m 修饰符的,开始时还要将 R0~R7 入栈,结束时出栈。

  • 如中断函数加 using m 修饰符,则在开始将 PSW 入栈后还要修改 PSW 中的工作寄存器组选择位。


中断函数定义示例:


  #include<reg51.h>
unsigned char status;
bit flag;
void service_int1( ) interrupt 2 using 2 /* INT1中断服务程序,使用第2组工作寄存器 */
{
flag=1; /* 设置标志 */
status=p1; /* 存输入口状态 */
}
复制代码

C51 库函数的说明

C51 提供了可直接调用的库函数。调用这些库函数可以使程序代码简单、结构清晰、易于调试和维护。


  • 本征库函数:指在编译时直接将固定的代码插入当前行,而不是用 ACALL 或 LCALL 进行函数调用(类似于宏的处理),这样就大大提高了访问效率。

  • 非本征函数: 并不是把固定代码插入当前行,而是通过 ACALL 或 LCALL 进行函数调用

本征库函数 9 个

(1)crol_和_cror 将 char 型变量循环向左(右)移动指定位数后返回。


(2)irol_和_iror 将 int 型变量循环向左(右)移动指定位数后返回。


(3)lrol_和_lror 将 long 型变量循环向左(右)移动指定位数后返回。


(4)nop 相当于插入汇编指令 nop


(5)testbit 相当于 JBC Label 测试该位变量并跳转同时清除该位


(6)chkfloat 测试并返回浮点数状态


上面所列举的本征函数的说明都包含在头文件 <intrins.h> 中,因此若想使用上述本征函数,必须在源程序开头包涵该头文件即:#include<intrins.h>。

非本征函数

  • <reg51.h>或<reg52.h>:文件 reg51.h 中包括了所有 80C51 的 SFR 及其位定义;reg52.h 中包含了所有的 80C52 的 SFR 及其位定义。

  • 一般源程序中都包涵该头文件。即:#include <reg51.h>或 #include <reg52.h>。

  • 包含了这个头文件后,就可以在程序中使用这些特殊功能寄存器及其位, P0 = 0x01;

  • <absacc.h>:该文件中定义了几个宏,以确定各存储空间的绝对地址。例如


     #define XBYTE ((unsigned char volatile xdata *) 0)
复制代码


  • <stdlib.h>:该文件中包涵了动态内存分配函数的声明。

  • <string.h>:该文件包涵了缓冲区处理函数的声明。其中包括字符串复制、比较、移动等函数,


如:memccpy,strcat,strcmp 等。这样可以很方便的对缓冲区进行处理。

如:extern char *strcat (char *s1, char *s2);


  • <stdio.h>:该文件包涵了输入输出流函数的声明。流函数通过串口或用户定义的 I/O 口读写数据,默认为串口,如果需要修改,可修改 lib 目录中的 getkey.c 和 putchar.c 源文件,然后在库中替换他们即可。例 extern char putchar (char);

  • <math.h>:虽然单片机不适合大量的数学运算,不过 C51 还是提供了一些基本的数学运算函数,该函数的声明都包含在<math.h>中。例:extern float sqrt (float val);


注意: 除了上面介绍的几种常用的头文件,C51 的头文件还包括 ctype.h,assert.h,errno.h,float.h,ltmits.h,rtx51tny.h,srom.h,setjmp.h,stdarg.h 等以上所介绍的头文件在 keil 目录下的 C51\INC 文件夹中。

运算符及实例

1. 算术运算符及其规则

C51 最基本的算术运算符有五种:


A、 +

B、 -

C、 *

D、 /

E、 %

2. C51 关系运算符

C51 提供 6 种关系运算符


​ < (小于)

​ >(大于)

​ <= (小于或等于)

​ >= (大于或等于)

​ == (测试等于)

​ != (测试不等于)

3. 关系运算符的优先级

(1) 关系运算符的优先级低于算术运算符


(2) 关系运算符的优先级高于赋值运算符

C51 逻辑运算符

C51 提供三种逻辑运算符:


&& 逻辑与


| | 逻辑或


! 逻辑非

C51 位操作

C51 提供了如下位操作运算符:


& 按位求与


| 按位求或


^ 按位求异或


~ 按位求反


<< 位左移


>> 位右移


#include <reg51.h>                                 /*包含的头文件*/
#include <absacc.h>
#define ADC XBYTE[0x4000] /*定义需访问的绝对地址*/
#define CON8255 XBYTE[0x2003]
#define PA8255 XBYTE[0x2000]
#define PB8255 XBYTE[0x2001]
uchar code disp[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
​ 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; /*7段LED译码表*/
uchar data ad_w_l,ad_w_h;
void delay(uchar t) /*延时子程序*/
{uchar i,j;
for(i=0;i<=100;i++);
for(j=0;j<=t;j++);
}
void dis_p(uchar disp_h,disp_l) /*两位LED数码显示子程序*/
{PB8255=disp[disp_l];
PA8255=0xf7;
delay(100);
PB8255=disp[disp_h];
PA8255=0xfb;
delay(100);
}
void main(void)
{uchar temp1;
CON8255=0x89;
IT0=1;
EX0=1;
EA=1;
ADC=temp1;
while(1) /*主程序中的主体循环*/
{dis_p(ad_w_h,ad_w_l);}
}

adint() interrupt 0 using 1 /*外部中断0中断服务子程序*/
{uchar adc_w,temp; /*变量的定义*/
EA=0;
adc_w=ADC;
if(adc_w>=255){adc_w=adc_w-5;}
temp=adc_w/5;
ad_w_h=temp/10;
ad_w_l=temp%10;
EA=1;
ADC=adc_w;
}
复制代码


发布于: 刚刚阅读数: 6
用户头像

timerring

关注

还未添加个人签名 2022.07.14 加入

还未添加个人简介

评论

发布
暂无评论
C51 基本函数、中断函数和库函数的详解_8月月更_timerring_InfoQ写作社区