单片机C语言之函数
函数定义
函数是一个自我包含的完成一定相关功能的执行代码段。通常C语言的编译器会自带标准的函数库,这些都是一些常用的函数。标准函数已由编译器软件商编写定义,使用者直接调用就能了,而无需定义。但是标准的函数不足以满足使用者的特殊要求,因此C语言允许使用者根据需要编写特定功能的函数,要调用它必须要先对其进行定义。
定义的模式如下:
函数类型 函数名称(形式参数表)
函数类型是说明所定义函数返回值的类型。返回值其实就是一个变量,只要按变量类型来定义函数类型就行了。如函数不需要返回值函数类型能写作“void”表示该函数没有返回值。注意的是函数体返回值的类型一定要和函数类型一致,不然会造成错误。
函数名称的定义在遵循C语言变量命名规则的同时,不能在同一程序中定义同名的函数,这将会造成编译错误(同一程序中是允许有同名变量的,因为变量有全局和局部变量之分)。
形式参数是指调用函数时要传入到函数体内参与运算的变量,它可以是一个、几个或没有。当函数不需要形式参数时(即无参函数),括号内为空或写入“void”表示,但括号不能少。
函数体中能包含有局部变量的定义和程序语句,如函数要返回运算值则要使用return语句进行返回。若在函数的{}号中也能什么也不写,这就成了空函数。在一个程序项目中可以写一些空函数,在以后的修改和升级中能方便的在这些空函数中进行功能扩充。
函数的调用
(一)函数调用的一般说明函数定义好以后,要被其它函数调用了才能被执行。C语言的函数是能相互调用的,但在调用函数前,必须对函数的类型进行说明,就算是标准库函数也不例外。
标准库函数的说明会被按功能分别写在不一样的头文件中,使用时只要在文件最前面用#include预处理语句引入相应的头文件。如前面使用的printf函数的说明是放在文件名为stdio.h的头文件中。
调用就是指一个函数体中引用另一个已定义的函数来实现所需要的功能,这个时候函数体称为主调用函数,函数体中所引用的函数称为被调用函数。主函数只是相对于被调用函数而言。
一个函数体中能调用数个其它的函数,这些被调用的函数同样也能调用其它函数,也能嵌套调用。但是在c51语言中有一个函数是不能被其它函数所调用的,它就是main主函数。
标准库函数只要用#include引入已写好说明的头文件,在程序就能直接调用函数了。如调用的是自定义的函数则要用如下形式编写函数类型说明:
类型标识符 函数的名称(形式参数表);
这样的说明方式是用在被调函数定义和主调函数是在同一文件中。也能把这些写到文件名.h的文件中用#include“文件名.h”引入。
如果被调函数的定义和主调函数不是在同一文件中的,则要用如下的方式进行说明,说明被调函数的定义在同一项目的不一样文件之上,这样说明的函数也能称为外部函数,定义如下:
extern类型标识符 函数的名称(形式参数表);
函数的定义和说明是完全不一样的,在编译的角度上看函数的定义是把函数编译存放在ROM的某一段地址上,而函数说明是告诉编译器要在程序中使用那些函数并确定函数的地址。如果在同一文件中被调函数的定义在主调函数之前,这个时候能不用说明函数类型。也就是说在main函数之前定义的函数,在程序中就能不用写函数类型说明了。能在一个函数体调用另一个函数(嵌套调用),但不允许在一个函数定义中定义另一个函数。还要注意的是函数定义和说明中的“类型、形参表、名称”等都要相一致。
(二)函数调用的一般形式调用函数的一般形式如下:
函数名 (实际参数表)
“函数名”就是指被调用的函数。
实际参数表能为零或多个参数,多个参数时要用逗号隔开,每个参数的类型、位置应与函数定义时所的形式参数一一对应,它的作用就是把参数传到被调用函数中的形式参数,如果类型不对应就会产生一些错误。调用的函数是无参函数时不写参数,但不能省后面的括号。
下面我们看一下在实际应用中函数不同的调用方式:
1. 函数语句
例如printf(“Hello World!\n”);
它以“Hello World!\n”为参数调用printf这个库函数,在这里函数调用被看作了一条语句。
2. 函数参数
“函数参数”这种方式是指被调用函数的返回值当作另一个被调用函数的实际参数,如temp=StrToInt(CharB(16));CharB的返回值作为StrToInt函数的实际参数传递。
3. 函数表达式
例如temp=Count();
这个函数的调用作为一个运算对象出现在表达式中,称为函数表达式。例子中Count()返回一个int类型的返回值直接赋值给temp。注意的是这种调用方式要求被调用的函数能返回一个同类型的值,不然会出现不可预料的错误。
C51常用头文件
下面介绍一些常用的C51头文件:
absacc.h——包含允许直接访问8051不同存储区的宏定义;
assert.h——文件定义assert 宏,可以用来建立程序的测试条件;
ctype——字符转换和分类程序;
intrins.h——文件包含指示编译器产生嵌入式固有代码的程序的原型;
math.h——数学程序;
reg51.h——51的特殊寄存器;
reg52.h——52的特殊寄存器;
setjmp.h——定义jmp_buf类型和setjmp和longjmp程序的原型;
stdarg.h——可变长度参数列表程序;
stdlib.h——存储区分配程序;
stdio.h——标准输入和输出程序;
string.h——字符串操作程序、缓冲区操作程序。
对于常用的MCS-51单片机,必须包含reg51.h的头文件,因为该文件对51单片机的相关寄存器及位进行了定义,这样在程序中才可以使用这些资源。
reg51.h文件的具体内容如下:
#ifndef __REG51_H__
#define __REG51_H__
/* BYTE Register */ //单元定义
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA 0 ;
sfr P3 = 0xB0;
sfr PSW = 0xD0;
sfr ACC = 0xE0;
sfr B = 0xF0;
sfr SP = 0x81;
sfr DPL = 0x82;
sfr DPH = 0x83;
sfr PCON = 0x87;
sfr TCON = 0x88;
sfr TMOD = 0x89;
sfr TL0 = 0x8A;
sfr TL1 = 0x8B;
sfr TH0 = 0x8C;
sfr TH1 = 0x8D;
sfr IE = 0xA8;
sfr IP = 0xB8;
sfr SCON = 0x98;
sfr SBUF = 0x99;
/* BIT Register */
/* PSW */ //位定义
sbit CY = 0xD7;
sbit AC = 0xD6;
sbit F0 = 0xD5;
sbit RS1 = 0xD4;
sbit RS0 = 0xD3;
sbit OV = 0xD2;
sbit P = 0xD0;
/* TCON */
sbit TF1 = 0x8F;
sbit TR1 = 0x8E;
sbit TF0 = 0x8D;
sbit TR0 = 0x8C;
sbit IE1 = 0x8B;
sbit IT1 = 0x8A;
sbit IE0 = 0x89;
sbit IT0 = 0x88;
/* IE */
sbit EA = 0xAF;
sbit ES = 0xAC;
sbit ET1 = 0xAB;
sbit EX1 = 0xAA;
sbit ET0 = 0xA9;
sbit EX0 = 0xA8;
/* IP */
sbit PS = 0xBC;
sbit PT1 = 0xBB;
sbit PX1 = 0xBA;
sbit PT0 = 0xB9;
sbit PX0 = 0xB8;
/* P3 */
sbit RD = 0xB7;
sbit WR = 0xB6;
sbit T1 = 0xB5;
sbit T0 = 0xB4;
sbit INT1 = 0xB3;
sbit INT0 = 0xB2;
sbit TXD = 0xB1;
sbit RXD = 0xB0;
/* SCON */
sbit SM0 = 0x9F;
sbit SM1 = 0x9E;
sbit SM2 = 0x9D;
sbit REN = 0x9C;
sbit TB8 = 0x9B;
sbit RB8 = 0x9A;
sbit TI = 0x99;
sbit RI = 0x98;
#endif
而absacc.h为对8051单片机的不同存储区的宏定义,具体如下:
#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) //定义片外数据存储区。
而intrins.h文件对指示编译器产生嵌入式代码,如空操作执行、位指令、栈操作指令等,该文件的具体内容如下:
extern void _nop_ (void); //空操作8051 NOP指令
extern bit _testbit_ (bit); //测试并清零位8051 JBC指令
extern unsigned char _cror_ (unsigned char,unsigned char); //字符循环左移
extern unsigned int _iror_ (unsigned int,unsigned char); //字符循环右移
extern unsigned long _lror_ (unsigned long,unsigned char); //整数循环右移
extern unsigned char _crol_ (unsigned char,unsigned char); //整数循环右移
extern unsigned int _irol_ (unsigned int,unsigned char); //整数循环左移
extern unsigned long _lrol_ (unsigned long,unsigned char); //长整数循环左移
extern unsigned char _chkfloat_(float); //测试并返回源点数状态
extern void _push_ (unsigned char _sfr); //压入堆栈
extern void _pop_ (unsigned char _sfr); //弹出堆栈
凔海笔记之单片机(一):莫在浮沙筑高台
笔者是在大一暑假接触的单片机,现今已是大三狗了。在这一年半的时间里,我用过stm32、学过FPGA,从40腿的控制器件到140多脚的处理器件,从几十行代码到上百上千行代码,也算是一种“步步高升”吧。然而对单片机的态度却是从崇拜到鄙视再到敬重的转换。也深深懂得了什么叫做“莫在浮沙筑高台”。
记得起初,花了150多大洋买的单片机开发板,对单片机的顶列膜拜与对教科书的“万马奔腾”齐头并进。对了,现价竟是78,靠!想当初,自己是多么单纯——哇哦,这样灯就亮了、额滴神,这就是流水灯啊、我勒个去,竟然还能和电脑交流,真让人兴奋。那种感觉就好像刚恋爱那样,虽已存活二十余载仍就“孑然一身”,但觉得应该是这种感觉吧<(^-^)>。当然,让人苦恼的莫过于看不懂的教科书,那都是些什么鬼,一个又一个搞不清的名词,一段有一段不理解的句子,心中奔腾的马必然是草泥马。不过现在我明白了,人到达了一定的高度就会忽略当初的幼稚,然而如同《编码的奥妙》这样是个人都能看懂的书,也只有大师级的人才能够完成,显然,他们不是。
记得去年暑假,在偶然的机会接触了stm32F4,个头虽比51小,但却有144条腿(当时并没有意识到这是贴片的)。而且还可以库开发,真是简直了,经过一个多月的学习,心中油然而生对51的鄙夷,那玩意简直是low爆了。随后又用到了FPGA,学会了用它来描述出自己想要的东西,哎呀,这才是我想要的嘛。对51 的鄙夷之情再次加深。我想我已经脱离低级转向高端,可摆脱菜鸟
身份了吧。而然,这一切的一切只能说明
到了大三,学了数电,开了一个名叫《单片机原理及其应用技术》的课程,开始接触单片机的内部原理,ALU、ACC、PC、DPRT一系列名词铺面而来,JC、SUBB、INC、DEC一连串指令符迎头痛击,彻底击败了我那脆弱的自信心,只得对自己说一句
棍哥都说了海明威的“一个人可以被毁灭,但不能给打败”。我岂能落后,我相信只要思想不滑坡,方法总比困难多。于是萌生了搞懂单片机的念头,还提出了一个所谓的三步走战略,即用C语言会使单片机,用汇编理解单片机,用verliog掌握单片机。
就我而言,初学时任何一个名词都可能成为一个很大的障碍,对单片机内部结构的了解更简直是噩梦。但是,对单片机的程序控制而言,程序的本质是寄存器之间的数据传递。用C语言我们可以学会用单片机,但对其内部结构不了解就难以掌握真正的掌握单片机。单片机不是凭空造出来的,用C语言,只是学习“单片机编程”。用汇编,才是学习“单片机”的正确姿势,写汇编其实就是帮你理清楚内在原理。当然了,如果只追求会用的话,的确不需要学汇编。然而学校开设单片机一课落脚于原理,在笔者看来懂其原理才能融会贯通,才能游刃于各种控制器件,才能不只是学了80C51单片机。可以说学单片机就是学微机原理,不懂微机原理即便再会写程序,也只是代码的搬运工。
Verilog是可编程逻辑语言,它的魅力就在于描绘出自己想要的逻辑器件,若是用它来造个单片机,虽然没有什么实用价值,但对我们掌握单片机可以说是绝妙之笔。
故此,为了自己不再是菜鸟,为了不做代码的搬运工,写下层层深入单片机的系列笔记。
相关问答
51 单片机 中“ ACC ”怎么用?[最佳回答]A和ACC的实质是一样的,对应地址都是0E0H,只是汇编在使用时,在格式上取了两个名字。你看看它们的使用方法,有的地方用A,有的地方用ACC,会发现(查成交...
单片机 里JB ACC .7?是将累加器a的最低位置1。是写模式;jbacc.0,loop才是读模式。a中最低位是1,转到loop去是将累加器a的最低位置1。是写模式;jbacc.0,loop才是读模式。a中最低...
51 单片机 中“ ACC ”怎么用?A和ACC的实质是一样的,对应地址都是0E0H,只是汇编在使用时,在格式上取了两个名字。你看看它们的使用方法,有的地方用A,有的地方用ACC,会发现有一定的规律...
ACC 寄存器有多少位?累加器ACC为8位寄存器,它是AT89S51单片机中最繁忙的寄存器,用于向ALU提供操作数,许多运算的结果也存放在累加器中。寄存器B为8位寄存器,主要用于乘、除法运...
单片机 中 ACC =0xFE;if( ACC 4==0);啥意思?地址映射。在数字芯片中,所谓的“地址”实际上是对内部资源的编码。在传统的51单片机(如AT89C51)中内部寻址范围是0~FFH,共256个地址,但实际上RAM只有128字...
acc 是什么寻址?Acc是位寻址。累加器ACC是一个8位的存储单元,是用来放数据的。但是,这个存储单元有其特殊的地位,是单片机中一个非常关键的单元,很多运算都要通过ACC来进行...
累加器 ACC 的作用?累加器Acc是80C51单片机中最常用的寄存器,许多指令的操作数取自于Acc,许多运算的结果存放在Acc中,乘除法指令必须通过Acc进行。A是累加器的指令助记符...累...
单片机 运算前PSW=80H,MOVA,#0ABHSUBBA,#0C3H求 ACC =CY=AC=OV=...[最佳回答]1、由于是用0ABH-0C3H=0E8H(也就是十进制的-24,补码表示),因此ACC=0E8H;2、由于AB比C3小,因此运算时必然借位,因此CY=1;3、由于AB=10101011,C3=1100...
单片机 的CPU有哪两部分?单片机的CPU由运算器ALU和控制器EU两部分组成。ALU进行算术逻辑运算,实现加减乘除移位等运算,操作结果大部分送往ACC累加器,同时修改状态寄存器PSW的值。EU...
单片机 里面的DA A指令用法。求教?daa是16进制转换10进制的指令防止出现字母LCD无法显示例:mova,33hmovr1,amova,39hadda,r1;此时结果在ACCdaar1;这样结果转换1...