单片机C语言教程:C51函数
扫描二维码
随时随地手机看文章
其实一直出现在例子中的 main()也算是一个函数,只不过它比较特殊,编译时以它做为程序的开始段。有了函数C 语言就有了模块化的优点,一般功能较多的程序,会在编写程序时把每项单独的功能分成数个子程序模块,每个子 程序就能用函数来实现。函数还能被反复的调用,因此一些常用的函数能做成函数库以供在编写程序时直接调用,从而更好的实现模块化的设计,大大提高编程工作的效率。
一.函数定义
通常 C 语言的编译器会自带标准的函数库,这些都是一些常用的函数,Keil uv 中也不 例外。标准函数已由编译器软件商编写定义,使用者直接调用就能了,而无需定义。但是 标准的函数不足以满足使用者的特殊要求,因此 C 语言允许使用者根据需要编写特定功能的 函数,要调用它必须要先对其进行定义。定义的模式如下:
函数类型 函数名称(形式参数表)
{
函数体
}
函数类型是说明所定义函数返回值的类型。返回值其实就是一个变量,只要按变量
类型来定义函数类型就行了。如函数不需要返回值函数类型能写作“void”表示该函数没 有返回值。注意的是函数体返回值的类型一定要和函数类型一致,不然会造成错误。函数名 称的定义在遵循 C 语言变量命名规则的同时,不能在同一程序中定义同名的函数这将会造成 编译错误(同一程序中是允许有同名变量的,因为变量有全局和局部变量之分)。形式参数 是指调用函数时要传入到函数体内参与运算的变量,它能有一个、几个或没有,当不需要 形式参数也就是无参函数,括号内能为空或写入“void”表示,但括号不能少。函数体中 能包含有局部变量的定义和程序语句,如函数要返回运算值则要使用 return 语句进行返 回。在函数的{}号中也能什么也不写,这就成了空函数,在一个程序项目中能写一些 空函数,在以后的修改和升级中能方便的在这些空函数中进行功能扩充。
二.函数的调用
函数定义好以后,要被其它函数调用了才能被执行。C 语言的函数是能相互调用的, 但在调用函数前,必须对函数的类型进行说明,就算是标准库函数也不例外。标准库函数的 说明会被按功能分别写在不一样的头文件中,使用时只要在文件最前面用#include 预处理语 句引入相应的头文件。如前面一直有使用的 printf 函数说明就是放在文件名为 stdio.h 的 头文件中。调用就是指一个函数体中引用另一个已定义的函数来实现所需要的功能,这个时候函 数体称为主调用函数,函数体中所引用的函数称为被调用函数。一个函数体中能调用数个 其它的函数,这些被调用的函数同样也能调用其它函数,也能嵌套调用。笔者本人认为 主函数只是相对于被调用函数而言。在 c51 语言中有一个函数是不能被其它函数所调用的, 它就是 main 主函数。调用函数的一般形式如下:
函数名 (实际参数表) “函数名”就是指被调用的函数。实际参数表能为零或多个参数,多个参数时要用逗
号隔开,每个参数的类型、位置应与函数定义时所的形式参数一一对应,它的作用就是把参 数传到被调用函数中的形式参数,如果类型不对应就会产生一些错误。调用的函数是无参函 数时不写参数,但不能省后面的括号。
在以前的一些例子我们也能看不一样的调用方式:
1.函数语句
如 printf ("Hello World!n"); 这是在 我们的第一个程序中出现的,它以 "Hello
World!n"为参数调用 printf 这个库函数。在这里函数调用被看作了一条语句。
2.函数参数 “函数参数”这种方式是指被调用函数的返回值当作另一个被调用函数的实际参
数,如 temp=StrToInt(CharB(16));CharB 的返回值作为 StrToInt 函数的实际参数传递。
3.函数表达式
而在上一篇的例子中有 temp = Count();这样一句,这个时候函数的调用作为一个运算 对象出现在表达式中,能称为函数表达式。例子中 Count()返回一个 int 类型的返回 值直接赋值给 temp。注意的是这种调用方式要求被调用的函数能返回一个同类型的值, 不然会出现不可预料的错误。
前面说到调用函数前要对被调用的函数进行说明。标准库函数只要用#include 引入已 写好说明的头文件,在程序就能直接调用函数了。如调用的是自定义的函数则要用如下形 式编写函数类型说明
类型标识符 函数的名称(形式参数表); 这样的说明方式是用在被调函数定义和主调函数是在同一文件中。你也能把这些写到
文件名.h 的文件中用#include "文件名.h"引入。如果被调函数的定义和主调函数不是在同 一文件中的,则要用如下的方式进行说明,说明被调函数的定义在同一项目的不一样文件之上, 其实库函数的头文件也是如此说明库函数的,如果说明的函数也能称为外部函数。
extern 类型标识符 函数的名称(形式参数表); 函数的定义和说明是完全不一样的,在编译的角度上看函数的定义是把函数编译存放在
ROM 的某一段地址上,而函数说明是告诉编译器要在程序中使用那些函数并确定函数的地 址。如果在同一文件中被调函数的定义在主调函数之前,这个时候能不用说明函数类型。也就 是说在 main 函数之前定义的函数,在程序中就能不用写函数类型说明了。能在一个函 数体调用另一个函数(嵌套调用),但不允许在一个函数定义中定义另一个函数。还要注意 的是函数定义和说明中的“类型、形参表、名称”等都要相一致。
三.中断函数
中断服务函数是编写单片机应用程序不可缺少的。中断服务函数只有在中断源请求响应
中断时才会被执行,这在处理突发事件和实时控制是十分有效的。例如:电路中一个按钮, 要求按钮后 LED 点亮,这个按钮何时会被按下是不可预知的,为了要捕获这个按钮的事件, 通常会有三种方法,一是用循环语句不断的对按钮进行查询,二是用定时中断在间隔时间内 扫描按钮,三是用外部中断服务函数对按钮进行捕获。在这个应用中只有单一的按钮功能, 那么第一种方式就能胜任了,程序也很简单,但是它会不停的在对按钮进行查询浪费了
CPU 的时间。实际应用中一般都会还有其它的功能要求同时实现,这个时候能根据需要选用第 二或第三种方式,第三种方式占用的 CPU 时间最少,只有在有按钮事件发生时,中断服务函 数才会被执行,其余的时间则是执行其它的任务。
如果你学习过汇编语言的话,刚开始写汇编的中断应用程序时,你一定会为出入堆栈的 问题而困扰过。单片机c语言 语言扩展了函数的定义使它能直接编写中断服务函数,你能不必考 虑出入堆栈的问题,从而提高了工作的效率。扩展的关键字是 interrupt,它是函数定义时 的一个选项,只要在一个函数定义后面加上这个选项,那么这个函数就变成了中断服务函数。
在后面还能加上一个选项 using,这个选项是指定选用 51 芯片内部 4 组工作寄存器中的
那个组。开始学习者能不必去做工作寄存器设定,而由编译器自动选择,避免产生不必要的错 误。定义中断服务函数时能用如下的形式。
函数类型 函数名 (形式参数) interrupt n [using n]
interrupt 关键字是不可缺少的,由它告诉编译器该函数是中断服务函数,并由后面的
n 指明所使用的中断号。n 的取值范围为 0-31,但具体的中断号要取决于芯片的型号,像 AT89c51 实际上就使用 0-4 号中断。每个中断号都对应一个中断向量,具体地址为 8n+3, 中断源响应后处理器会跳转到中断向量所处的地址执行程序,编译器会在这地址上产生一个 无条件跳转语句,转到中断服务函数所在的地址执行程序。下表是 51 芯片的中断向量和中 断号。
表 9-1 AT89c51 芯片中断号和中断向量
使用中断服务函数时应注意:中断函数不能直接调用中断函数;不能通过形参传速参数; 在中断函数中调用其它函数,两者所使用的寄存器组应相同。限于篇幅其它与函数相关的知 识这里不能一一加以说明,如变量的传递、存储,局部变量、全部变量等,有兴趣的朋友可 以访问笔者的网站 阅读更多相关文章。
下面是简单的例子。首先要在前面做好的实验电路中加多一个按钮,接在 P3.2(12 引脚外 部中断 INT0)和地线之间。把编译好后的程序烧录到芯片后,当接在 P3.2 引脚的按钮接下 时,中断服务函数 Int0Demo 就会被执行,把 P3 当前的状态反映到 P1,如按钮接下后 P3.7
(之前有在这脚装过一按钮)为低,这个时候 P1.7 上的 LED 就会熄灭。放开 P3.2 上的按钮后,
P1LED 状态保持先前按下 P3.2 时 P3 的状态。
#include
unsigned char P3State(void); //函数的说明,中断函数不用说明
void main(void)
{
IT0 = 0; //设外部中断 0 为低电平触发
EX0 = 1; //允许响应外部中断 0
EA = 1; //总中断开关
while(1);
}
//外部中断 0 演示,使用 2 号寄存器组
void Int0Demo(void) interrupt 0 using 2
{
unsigned int Temp; //定义局部变量
P1 = ~P3State(); //调用函数取得 p2 的状态反相后并赋给 P1
for (Temp=0; Temp<50; Temp++); //延时 这里只是演示局部变量的使用
}
//用于返回 P3 的状态,演示函数的使用
unsigned char P3State(void)
{
unsigned char Temp;
Temp = P3; //读取 P3 的引脚状态并保存在变量 Temp 中
//这样只有一句语句实在没必要做成函数,这里只是学习函数的基本使用方法
return Temp;
}