1 / 34

单片机 C 语言开发技术

单片机 C 语言开发技术. 第五章 函数. 内容概述. 函数是 C51 程序的基本组成部分, C51 程序的全部工作都是由各式各样的函数完成的。本章主要介绍函数的定义、调用、参数的传递、变量的作用域等。. 教学目标. 1 .理解函数的概念,能根据需要说明、定义一个函数,确定函数的返回值的类型,函数的形参的数据类型和格式,能正确的调用函数。 2 .理解调用函数在调用函数时的参数传递过程,掌握函数形参传递单个数组元素的方法。 3. 掌握 return 返回一个数值、多个数值的方法。 4 .理解函数递归调用的概念,能利用递归调用解决相关的计算问题。

Download Presentation

单片机 C 语言开发技术

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 单片机C语言开发技术 第五章 函数

  2. 内容概述 • 函数是C51程序的基本组成部分,C51程序的全部工作都是由各式各样的函数完成的。本章主要介绍函数的定义、调用、参数的传递、变量的作用域等。

  3. 教学目标 1.理解函数的概念,能根据需要说明、定义一个函数,确定函数的返回值的类型,函数的形参的数据类型和格式,能正确的调用函数。 2.理解调用函数在调用函数时的参数传递过程,掌握函数形参传递单个数组元素的方法。 3. 掌握return返回一个数值、多个数值的方法。 4.理解函数递归调用的概念,能利用递归调用解决相关的计算问题。 5.理解函数的作用域和变量的作用域的概念。

  4. 函数是C51源程序的基本模块, 通过对函数模块的调用实现特定的功能。 • C51语言中的函数相当于其它高级语言的子程序。 • C51语言不仅提供了极为丰富的库函数,还允许用户建立自己定义的函数。 • 用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。

  5. 5.1 函数的说明与定义 • C51中所有函数与变量一样,在使用之前必须说明。 • 所谓说明,是指说明函数是什么类型的函数, 一般库函数的说明都包含在相应的头文件<*.h>中,  例如:标准输入输出函数包含在“stdio.h”中, 非标准输入输出函数包含在“io.h”中,  • 在使用库函数时必须先知道该函数包含在什么样的头文件中, 在程序的开头用#include <*.h>或#include"*.h"说明。只有这样程序才会编译通过。

  6. 5.1.1 函数说明 •     形式为:  函数类型  函数名(数据类型  形式参数,  数据类型  形式参数, ......); • 其中: 函数类型是该函数返回值的数据类型, 可以是以前介绍的整型(int),长整型(long), 字符型(char), 单浮点型(float), 双浮点型(double)以及无值型 (void), 也可以是指针, 包括结构指针。无值型表示函数没有返回值。

  7. 函数名为C51的标识符, 小括号中的内容为该函数的形式参数说明。 • 可以只有数据类型而没有形式参数, 也可以两者都有。 • 对于经典的函数说明没有参数信息。 如: int putlll(int x,int y,int z,int color,char *p) /*说明一个整型函数*/      char *name(void);               /*说明一个字符串指针函数*/      void student(int n, char *str); /*说明一个不返回值的函数*/      float calculate(void);            /*说明一个浮点型函数*/ • 注意: 如果一个函数没有说明就被调用, 编译程序并不认为出错, 而将此函数默认为整型(int)函数。因此当一个函数返回其它类型, 又没有事先说明,  编译时将会出错。

  8. 5.1.2 函数定义      函数定义就是确定该函数完成什么功能以及怎么运行, 相当于其它语言的一个子程序。 • C51对函数的定义采用ANSI C规定的方式。即: 函数类型  函数名(数据类型形式参数; 数据类型 形式参数...)       { 函数体;        }

  9. 其中函数类型和形式参数的数据类型为C51的基本数据类型 • 函数体为C51提供的库函数和语句以及其它用户自定义函数调用语句的组合, 并包括在一对花括号“{”和“}”中。 • 需要指出的是一个程序必须有一个主函数, 其它用户定义的子函数可以是任意多个, 这些函数的位置也没有什么限制, 可以在main()函数前,  也可以在其后。 • C51将所有函数都被认为是全局性的。而且是外部的, 即可以被另一个文件中的任何一个函数调用。

  10. 5.2 函数的调用 5.2.1  函数的简单调用 • C51调用函数时直接使用函数名和实参的方法, 也就是把要赋给被调用函数的参量, 按该函数说明的参数形式传递过去, 然后进入子函数运行, 运行结束后再按子函数规定的数据类型返回一个值给调用函数。

  11. 例5-2-1 输入两个整数,输出其中较大的值。 #include<stdio.h> int max(int a,int b);/*说明一个用户自定义函数*/ int max(int a,int b) { if(a>b) return a; else return b; } void main() { int x,y,z; printf("input two numbers:\n"); scanf("%d%d",&x,&y); z=max(x,y); printf("maxmum=%d",z); }

  12. 5.2.2 函数的参数传递 5.2.2.1 调用函数向被调用函数以形式参数传递 • 用户编写的函数一般在对其说明和定义时就规定了形式参数类型, 因此调用这些函数时参量必须与子函数中形式参数的数据类型、顺序和数量完全相同

  13. 注意:当数组作为形式参数向被调用函数传递时, 只传递数组的地址, 而不是将整个数组元素都复制到函数中去, 即用数组名作为实参调用子函数, 调用时指向该数组第一个元素的指针就被传递给子函数。

  14. 用数组元素作为函数参数传递. 当传递数组的某个元素时, 数组元素作为实参, 此时按使用其它简单变量的方法使用数组元素。 #include<stdio.h>      void disp(int n);      int main()      {           int m[10], i;           for(i=0; i<10; i++){             m[i]=i;             disp(m[i]);  /*逐个传递数组元素*/           }           getch();           return 0;      }      void disp(int n)      {           printf("%3d\t");      }

  15. 5.2.2.2 被调用函数向调用函数返回值 •     一般使用return语句由被调用函数向调用函数返回值, 该语句有下列用途: • 它能立即从所在的函数中退出, 返回到调用它的程序中去。 • 返回一个值给调用它的函数。

  16. 有两种方法可以终止子函数运行并返回到调用它的函数中: • 一是执行到函数的最后一条语句后返回; • 一是执行到语句return时返回。 • 前者当子函数执行完后仅返回给调用函数一个0。 • 若要返回一个值, 就必须用return语句。只需在return 语句中指定返回的值即可。 • return语句可以向调用函数返回值, 但这种方法只能返回一个参数

  17. 5.2.2.3 用全程变量实现参数互传 •     如果将所要传递的参数定义为全程变量, 可使变量在整个程序中对所有函数都可见。 • 全程变量的数目受到限制, 特别对于较大的数组更是如此。

  18. 例5-2-4 以下示例程序中m[10]数组是全程变量,数据元素的值在disp()函数中被改变后,回到主函数中得到的依然是被改变后的值。#include<stdio.h> void disp(void); int m[10]; /*定义全程变量*/ int main() { int i; printf("In main before calling\n"); for(i=0; i<10; i++){ m[i]=i; printf("%3d", m[i]); /*输出调用子函数前数组的值*/ } disp(); /*调用子函数*/ printf("\nIn main after calling\n"); for(i=0; i<10; i++) printf("%3d", m[i]); /*输出调用子函数后数组的值*/ getchar(); return 0; }

  19. void disp(void) { int j; printf("In subfunc after calling\n");/*子函数中输出数组的值*/ for (j=0; j<10; j++){ m[j]=m[j]*10; printf("%3d", m[j]); } }

  20. 5.2.3 函数的递归调用 • C51允许函数自己调用自己, 即函数的递归调用, 递归调用可以使程序简洁、代码紧凑, 但要牺牲内存空间作处理时的堆栈。     如要求一个n!(n的阶乘)的值可用下面递归调用: 例5-2-5 求 n!实例程序。 #include<stdio.h> unsigned long mul(int n); int main() { int m; puts("Calculate n! n=?\n"); scanf("%d", &m); /*键盘输入数据*/ printf("%d!=%ld\n", m, mul(m));/*调用子程序计算并输出*/ getchar(); return 0; }

  21. unsigned long mul(int n) { unsigned long p; if(n>1) p=n*mul(n-1); /*递归调用计算n!*/ else p=1L; return(p); /*返回结果*/ }运行结果:           calculate n!  n=? 输入5时结果为:           5!=120

  22. 5.3 函数作用范围与变量作用域 • C51中每个函数都是独立的代码块, 函数代码归该函数所有, 除了对函数的调用以外, 其它任何函数中的任何语句都不能访问它。 • 例如使用跳转语句goto就不能从一个函数跳进其它函数内部。除非使用全程变量, 否则一个函数内部定义的程序代码和数据, 不会与另一个函数内的程序代码和数据相互影响。 • C51中所有函数的作用域都处于同一嵌套程度, 即不能在一个函数内再说明或定义另一个函数。 • C51中一个函数对其它子函数的调用是全程的, 即是函数在不同的文件中, 也不必附加任何说明语句而被另一函数调用, 也就是说一个函数对于整个程序都是可见的。 • 在C51中, 变量是可以在各个层次的子程序中加以说明, 也就是说, 在任何函数中, 变量说明有只允许在一个函数体的开头处说明, 而且允许变量的说明( 包括初始化)跟在一个复合语句的左花括号的后面, 直到配对的右花括号为止。它的作用域仅在这对花括号内, 当程序执行到出花括号时, 它将不复存在。当然, 内层中的变量即使与外层中的变量名字相同, 它们之间也是没有关系的。

  23. 例5-3-1 全局变量与局部变量示例。 • #include<stdio.h> • int i=10; • int main() • { • int i=1; • printf("%d\t", i); • { • int i=2; • printf("%d\t", i); • { • extern i; • i+=1; • printf("%d\t", i); • } • printf("%d\t", ++i); • } • printf("%d\n", ++i); • return 0; • } • 运行结果为 1    2    34    2

  24. 5.4 函数的递归调用与再入函数 如果在调用一个函数的过程中又直接或间接地调用该 函数本身,称为函数的递归调用。 例如计算阶乘函数f(n)=n!, C51编译器采用一个扩展关键字reentrant,作为定义函数时的 选项,需要将一个函数定义为再入函数时,只要在函数名后面 加上关键字reentrant即可。 定义再入函数的一般形式为: 函数类型 函数名 (形式参数表) [reentrant] 再入函数可被递归调用,无论何时,包括中断服务函数在内的 任何函数都可调用再入函数。

  25. 再入函数有如下规定: 1.再入函数不能传送bit类型的参数,也不能定义一个局部位标 量,再入函数不能包括位操作以及8051系列单片机的可位寻址 区。 2.与PL/M51兼容的函数不能具有reentrant属性,也不能调用 再入函数。 3.在编译时存储器模式的基础上为再入函数在内部或外部存储 器中建立一个模拟堆栈区,称为再入栈。再入函数的局部变量 及参数被放在再入栈中,从而使再入函数可以进行递归调用。 面非再入函数的局部变量被放在再入栈之外的暂存区内,如果 对非再入函数进行递归调用,则上次调用时使用的局部变量数 据将被覆盖。 4.在同一个程序中可以定义和使用不同存储器模式的再入函数, 任意模式的再入函数不能调用不同模式的再入函数,但可任意调 用非再入函数。 5.在参数的传递上,实际参数可以传递给间接调用的再入函数。 无再入属性的间接调用函数不能包含调用参数,但是可以使用定 义的全局变量来进行参数传递。

  26. 5.5 中断服务函数与寄存器组定义 C51编译器支持在C语言源程序中直接编写805l单片机的中断服 务函数程序,从而减轻了采用汇编语言编写中断服务程序的繁 琐程度。为了在C语言源程序中直接编写中断服务函数的需要, C51编译器对函数的定义进行了扩展,增加了—个扩展关键字 interrupt。关键字interrupt是函数定义时的一个选项,加上这个 选项即可以将—个函数定义成中断服务函数。 定义中断服务函数的一般形式为: 函数类型 函数名(形式参数表) [interrupt n][using n] 关键字interrupt后面的n为中断号,取值范围为0~31。

  27. 805l单片机的常用中断源和中断向量如表5.1所示:805l单片机的常用中断源和中断向量如表5.1所示: 表 5.1常用中断号与中断向量

  28. 8051系列单片机可以在内部RAM中使用4个不同的工作8051系列单片机可以在内部RAM中使用4个不同的工作 寄存器组,每个寄存器组中包含8个工作寄存器 (R0—R 7)。C51编译器扩展了一个关键字using,专门 用来选择805l单片机中不同的工作寄存器组。Using后 面的n是一个0~3的常整数,分别选中4个不同的工作 寄存器组。在定义一个函数时using是一个选项,如果 不用该选项,则由编译器选择一个寄存器组作绝对寄存 器组访问。需要注意的是,关键字using和interrupt的后 面都不允许跟一个带运算符的表达式。 关键字using对函数目标代码的影响如下: 在函数的入口处将当前工作寄存器组保护到堆栈中; 指定的工作寄存器内容不会改变;函数返回之前将被保 护的工作寄存器组从堆栈中恢复。

  29. 使用关键字using在函数中确定一个工作寄存器组时必须十分小使用关键字using在函数中确定一个工作寄存器组时必须十分小 心,要保证任何寄存器组的切换都只在仔细控制的区域内发生, 如果不做到这一点将产生不正确的函数结果。另外还要注意, 带using属性的函数原则上不能返回bit类型的值。并且关键字 using不允许用于外部函数。 关键字interrupt也不允许用于外部函数,它对中断函数目标代 码的影响如下: 在进入中断函数时,特殊功能寄存器ACC、B、DPH、DPL、 PSW将被保存入栈;如果不使用寄存组切换,则将中断函数中 所用到的全部工作寄存器都入栈;函数返回之前存器内容出栈; 中断函数由8051单片机指令RETI结束。 注意: 1、如果中断函数中用到浮点运算,必须保存浮点寄存器的状态。 (在math.h中保存浮点寄存器函数为pfsave,恢复浮点寄存器的 状态函数为fprestore)。 2、如果在中断函数中调用了其他函数,则被调函数所使用的工 作寄存器组与中断函数的要保持一致。

  30. 编写8051单片机中断函数时应遵循以下规则: 1.中断函数不能进行参数传递,如果中断函数中包含 任何参数声明都将导致编译出错。 2.中断函数没有返回值,如果企图定义一个返回值将 得到不正确的结果。因此建议在定义中断函数时将其定 义为void类型,以明确说明没有返回值。 3.在任何情况下都不能直接调用中断函数,否则会产 生编译错误。因为中断函数的退出是由8051单片机指 令RETI完成的,RETI指令影响8051单片机的硬件中断 系统。如果在没有实际中断请求的情况下直接调用中断 函数,RETI指令的操作结果会产生一个致命的错误。 4.如果中断函数中用到浮点运算,必须保存浮点寄存 器的状态,当没有其它程序执行浮点运算时可以不保存。 C51编译器的数学函数库math.h中,提供了保存浮点 寄存器状态的库函数pfsave和恢复浮点寄存器状态的库 函数fprestore。

  31. 5.如果在中断函数中调用了其它函数,则被调用函数5.如果在中断函数中调用了其它函数,则被调用函数 所使用的寄存器组必须与中断函数相同。用户必须保证 按要求使用相同的寄存器组,否则会产生不正确的结果, 这一点必须引起足够的注意。如果定义中断函数时没行 使用using选项,则由编译器选择一个寄存器组作绝对 寄存器组访问。另外,由于中断的产生不可预测,中断 函数对其它函数的调用可能形成递规调用,需要时可将 被中断函数所调用的其它函数定义成再入函数。 6.C51编译器从绝对地址8n十3处产生一个中断向量, 其中n为中断号。该向量包含一个到中断函数入口地址 的绝对跳转。在对源程序编译时,可用编译控制指令 NOINTVECTOR抑制中断向量的产生,从而使用户能 够从独立的汇编程序模块中提供中断向量。

  32. 5 .5 常用C51库函数 C5l编译器的运行库中包含有丰富的库函数,使用库函数可大大 简化用户的程序设计工作,提高编程效率。由于805l系列单片机 本身的特点,某些库函数的参数和调用格式与ANSIC标准有所 不同,例如函数isdigit的返回值类型为bit而不是char。每个库函 数都在相应的头文件中给出了函数原型声明,用户如果需要使 用库函数,必须在源程序的开始处采用预处理器指令#include 将有关的头文件包含进来。如果省略了头文件,将不能保证函 数的正确运行。C51库函数中类型的选择考虑到了805l系列单 片机的结构特性,用户在由己的应用程序中应尽可能地使用最 小的数据类型,以最大限度地发挥805l系列单片机的性能, 同时可减少应用程序的代码长度。下面将常用的C51库函数分 别作以解释。

  33. 1、一般I/0函数STDIO.H 2、字符函数CTYPE.H 3、字符串函数STRING .H 4、访问SFR和SFR_bit地址REGxxx.H 1、一般I/0函数STDIO.H C51库中包含有字符I/O函数,它们通过8051系列单片机的串 行接口工作、如果希望支持其他I/O接口,只需要改动getkey() 和putchar()函数,库中所有其他I/O支持函数部依赖于这两 个函数模块,不需要改动。另外需要注意,在使用8051系列单 片机的串行口之前,应先对其进行初始化。例如以2400波特率 (12MHz时钟频率)初始化串行口如下: SCON=0x52; /* SCON置初值*/ TMOD=0x20; /* T MOD量初值*/ THl=0xf3; /* T1量初值*/ TRl=1; /*启动T1*/ 当然也可以采用其他波特率来对串行口进行初始化。

  34. 5.6 预处理器 C语言与其它高级程序设计语言的一个主要区别就是对程序的 编译预处理功能,编译预处理器是C语言编译器的一个组成部 分。在C语言中,通过一些预处理命令可以在很大程度上为C 语言本身提供许多功能和符号等方面的扩充,增强了C语言的 灵活性和方便性。预处理命令可以在编写程序时加在需要的 地方,但它只在程序编译时起作用,且通常是按行进行处理的, 因此又称为编译控制行。C语言的预处理命令类似于汇编语言 中的伪指令。编译器在对整个程序进行编译之前,先对程序中 的编译控制行进行预处理,然后再将预处理的结果与整个C语 言源程序一起进行编译,以产生目标代码。C51编译器的预处 理器支持所有满足ANSI标准X3J11细则的预处理命令。常用 的预处理命令有:宏定义、文件包含和条件编译。为了与一般 C语言语句相区别,预处理命令由符号“#”开头。

More Related