51系列单片机学习5—C编程程序语句
#include
#include
void main(void)
{
unsigned int I = 1;
unsigned int SUM = 0; //设初值
SCON = 0x50; //串行口方式 1,允许接收
TMOD = 0x20; //定时器 1 定时方式 2
TCON = 0x40; //设定时器 1 开始计数
TH1 = 0xE8; //11.0592MHz 1200 波特率
TL1 = 0xE8; TI = 1;
TR1 = 1; //启动定时器
while(I<=10)
{
SUM = I + SUM; //累加
printf ("%d SUM=%d",I,SUM); //显示
I++;
}
while(1); //这句是为了不让程序完后,程序指针继续向下造成程序“跑飞”
}
//最后运行结果是 SUM=55;
do while 语句
do while 语句能说是 while 语句的补充,while 是先判断条件是否成立再执行循环体,
而 do while 则是先执行循环体,再根据条件判断是否要退出循环。这样就决定了循环体无论在任何条件下都会至少被执行一次。它的语法如下:
do 语句 while (条件表达式)
用 do while 怎么写上面那个例程呢?先想一想,再参考下面的程序。
#include
#include
void main(void)
{
SUM = I + SUM; //累加
printf ("%d SUM=%d",I,SUM); //显示 I++;
}在上面的程序看来 do while 语句和 while 语句似乎没有什么两样,但在实际的应用中要注
意任何 do while 的循环体一定会被执行一次。如把上面两个程序中 I 的初值设为 11,那么前一个程序不会得到显示结果,而后一个程序则会得到 SUM=11。
for 语句
#include
#include
void main(void)
{
unsigned int I;
unsigned int SUM = 0; //设初值
SCON = 0x50; //串行口方式 1,允许接收 TMOD = 0x20; //定时器 1 定时方式 2
TCON = 0x40; //设定时器 1 开始计数
TI = 1;
TR1 = 1; //启动定时器
for (I=1; I<=10; I++) //这里能设初始值,所以变量定义时能不设
{
printf ("%d SUM=%d",I,SUM); //显示
}
while(1);
}
如果我们把程序中的 for 改成 for(; I<=10; I++)这样条件的初值会变成当前 I 变量的
值。如果改成 for(;;)会怎么样呢?试试看。
continue 语句
continue 语句是用于中断的语句,通常使用在循环中,它的作用是结束本次循环,跳过循环体中没有执行的语句,跳转到下一次循环周期。语法为:continue;
continue 同时也是一个无条件跳转语句,但功能和前面说到的 break 语句有所不一样, continue 执行后不是跳出循环,而是跳到循环的开始并执行下一次的循环。在上面的例子 中的循环体加入 if (I==5) continue;看看什么结果?
return 语句
return 语句是返回语句,不属于循环语句,是要学习的最后一个语句所以一并写下了。 返回语句是用于结束函数的执行,返回到调用函数时的位置。语法有二种:
return (表达式);
return; 语法中因带有表达式,返回时先计算表达式,再返回表达式的值。不带表达式则返回的
值不确定。下面是一个同样是计算 1-10 的累加,所不一样是的用了函数的方式。
#include
#include
int Count(void); //声明函数
void main(void)
{
unsigned int temp;
TCON = 0x40; //设定时器 1 开始计数
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;TR1 = 1; //启动定时器
temp = Count();
printf ("1-10 SUM=%d",temp); //显示
while(1);
}
int Count(void)
{
unsigned int I, SUM;
for (I=1; I<=10; I++)
{
SUM = I + SUM; //累加
}
return (SUM);
上一篇的最后一个例子中有用到函数,其实一直出现在例子中的 main()也算是一个函数,只不过它比较特殊,编译时以它做为程序的开始段。有了函数 C 语言就有了模块化的优点,一般功能较多的程序,会在编写程序时把每项单独的功能分成数个子程序模块,每个子程序就能用函数来实现。函数还能被反复的调用,因此一些常用的函数能做成函数库以供在编写程序时直接调用,从而更好的实现模块化的设计,大大提高编程工作的效率。
一.函数定义
通常 C 语言的编译器会自带标准的函数库,这些都是一些常用的函数,Keil uv 中也不例外。标准函数已由编译器软件商编写定义,使用者直接调用就能了,而无需定义。但是 标准的函数不足以满足使用者的特殊要求,因此 C 语言允许使用者根据需要编写特定功能的 函数,要调用它必须要先对其进行定义。定义的模式如下:
函数类型函数名称(形式参数表)
{
函数体
}
函数类型是说明所定义函数返回值的类型。返回值其实就是一个变量,只要按变量类型来定义函数类型就行了。如函数不需要返回值函数类型能写作“void”表示该函数没有返回值。注意的是函数体返回值的类型一定要和函数类型一致,不然会造成错误。函数名称的定义在遵循 C 语言变量命名规则的同时,不能在同一程序中定义同名的函数这将会造成编译错误(同一程序中是允许有同名变量的,因为变量有全局和局部变量之分)。形式参数是指调用函数时要传入到函数体内参与运算的变量,它能有一个、几个或没有,当不需要形式参数也就是无参函数,括号内能为空或写入“void”表示,但括号不能少。函数体中能包含有局部变量的定义和程序语句,如函数要返回运算值则要使用 return 语句进行返回。在函数的{}号中也能什么也不写,这就成了空函数,在一个程序项目中能写一些空函数,在以后的修改和升级中能方便的在这些空函数中进行功能扩充。
二.函数的调用
函数定义好以后,要被其它函数调用了才能被执行。C 语言的函数是能相互调用的,但在调用函数前,必须对函数的类型进行说明,就算是标准库函数也不例外。标准库函数的说明会被按功能分别写在不一样的头文件中,使用时只要在文件最前面用#include 预处理语句引入相应的头文件。如前面一直有使用的printf 函数说明就是放在文件名为 stdio.h 的头文件中。调用就是指一个函数体中引用另一个已定义的函数来实现所需要的功能,这个时候函数体称为主调用函数,函数体中所引用的函数称为被调用函数。一个函数体中能调用数个其它的函数,这些被调用的函数同样也能调用其它函数,也能嵌套调用。笔者本人认为 主函数只是相对于被调用函数而言。在 c51 语言中有一个函数是不能被其它函数所调用的, 它就是 main 主函数。
调用函数的一般形式如下:
函数名 (实际参数表) “函数名”就是指被调用的函数。实际参数表能为零或多个参数,多个参数时要用逗号隔开,每个参数的类型、位置应与函数定义时所的形式参数一一对应,它的作用就是把参数传到被调用函数中的形式参数,如果类型不对应就会产生一些错误。调用的函数是无参函数时不写参数,但不能省后面的括号。
在以前的一些例子我们也能看不一样的调用方式:
1.函数语句
如 printf ("Hello World!"); 这是在 我们的第一个程序中出现的,它以 "HelloWorld!"为参数调用 printf 这个库函数。在这里函数调用被看作了一条语句。
2.函数参数 “函数参数”这种方式是指被调用函数的返回值当作另一个被调用函数的实际参数,如 temp=StrToInt(CharB(16));CharB 的返回值作为 StrToInt 函数的实际参数传递。
3.函数表达式
而在上一篇的例子中有 temp = Count();这样一句,这个时候函数的调用作为一个运算对象出现在表达式中,能称为函数表达式。例子中 Count()返回一个 int 类型的返回 值直接赋值给 temp。注意的是这种调用方式要求被调用的函数能返回一个同类型的值, 不然会出现不可预料的错误。
前面说到调用函数前要对被调用的函数进行说明。标准库函数只要用#include 引入已写好说明的头文件,在程序就能直接调用函数了。如调用的是自定义的函数则要用如下形式编写函数类型说明类型标识符
这样的说明方式是用在被调函数定义和主调函数是在同一文件中。你也能把这些写到文件名.h 的文件中用#include "文件名.h"引入。如果被调函数的定义和主调函数不是在同一文件中的,则要用如下的方式进行说明,说明被调函数的定义在同一项目的不一样文件之上,其实库函数的头文件也是如此说明库函数的,如果说明的函数也能称为外部函数。
extern 类型标识符 函数的名称(形式参数表);
三.中断函数
中断服务函数是编写单片机应用程序不可缺少的。中断服务函数只有在中断源请求响应中断时才会被执行,这在处理突发事件和实时控制是十分有效的。例如:电路中一个按钮,要求按钮后 LED 点亮,这个按钮何时会被按下是不可预知的,为了要捕获这个按钮的事件,通常会有三种方法,一是用循环语句不断的对按钮进行查询,二是用定时中断在间隔时间内扫描按钮,三是用外部中断服务函数对按钮进行捕获。在这个应用中只有单一的按钮功能,那么第一种方式就能胜任了,程序也很简单,但是它会不停的在对按钮进行查询浪费了CPU 的时间。实际应用中一般都会还有其它的功能要求同时实现,这个时候能根据需要选用第 二或第三种方式,第三种方式占用的 CPU 时间最少,只有在有按钮事件发生时,中断服务函 数才会被执行,其余的时间则是执行其它的任务。
函数类型 函数名 (形式参数) interrupt n [using n]
interrupt 关键字是不可缺少的,由它告诉编译器该函数是中断服务函数,并由后面的
n 指明所使用的中断号。n 的取值范围为 0-31,但具体的中断号要取决于芯片的型号,像 AT89c51 实际上就使用 0-4 号中断。每个中断号都对应一个中断向量,具体地址为 8n+3, 中断源响应后处理器会跳转到中断向量所处的地址执行程序,编译器会在这地址上产生一个无条件跳转语句,转到中断服务函数所在的地址执行程序。下表是 51 芯片的中断向量和中 断号。
中断号 | 中断源 | 中断向量 |
0 | 外部中断 0 | 0003H |
1 | 定时器/计数器 0 | 000BH |
2 | 外部中断 1 | 0013H |
3 | 定时器/计数器 1 | 001BH |
4 | 串行口 | 0023H |
表 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;
数据类型 | 数组名 | [常量表达式]; |
数据类型 | 数组名 | [常量表达式 1]...... [常量表达式 N]; |
unsigned int xcount [10]; //定义无符号整形数组,有 10 个数据单元
char inputstring [5]; //定义字符形数组,有 5 个数据单元
float outnum [10],[10];//定义浮点型数组,有 100 个数据单元
在 C 语言中数组的下标是从 0 开始的而不是从 1 开始,如一个具有 10 个数据单元的数组 count,它的下标就是从 count[0]到 count[9],引用单个元素就是数组名加下标,如 count[1] 就是引用 count 数组中的第 2 个元素,如果错用了 count[10]就会有错误出现了。还有一点要 注意的就是在程序中只能逐个引用数组中的元素,不能一次引用整个数组,但是字符型的数组就能一次引用整个数组。
数据类型 [存储器类型] 数组名 [常量表达式] = {常量表达式};
数据类型 [ 存储器类型] 数组名 [ 常量表达式 1]...... [ 常量表达式 N]={{ 常量表达 式}...{常量表达式 N}};
在定义并为数组赋初值时,开始学习的朋友一般会搞错初值个数和数组长度的关系,而致使编译出错。初值个数必须小于或等于数组长度,不指定数组长度则会在编译时由实际的初值 个数自动设置。
unsigned char LEDNUM[2]={12,35}; //一维数组赋初值
int Key[2][3]={{1,2,4},{2,2,1}}; //二维数组赋初值
unsigned char IOStr[]={3,5,2,5,3}; //没有指定数组长度,编译器自动设置
unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //数据保存在 code 区
下面的一个简单例子是对数组中的数据进行排序,使用的是冒泡法,一来了解数组的使用,二来掌握基本的排序算法。冒泡排序算法是一种基本的排序算法,它每次顺序取数组中的两个数,并按需要按其大小排列,在下一次循环中则取下一次的一个数和数组中下一个数 进行排序,直到数组中的数据全部排序完成。
#include
#include
void taxisfun (int taxis2[])
{
unsigned char TempCycA,TempCycB,Temp;
for (TempCycA=0; TempCycA<=8; TempCycA++)
for (TempCycB=0; TempCycB<=8-TempCycA; TempCycB++)
{//TempCycB<8-TempCycA 比用 TempCycB<=8 少用很多循环
if (taxis2[TempCycB+1]>taxis2[TempCycB]) //当后一个数大于前一个 数
{
Temp = taxis2[TempCycB]; //前后 2 数交换
taxis2[TempCycB] = taxis2[TempCycB+1];
taxis2[TempCycB+1] = Temp; //因函数参数是数组名调用形参的变动影响实参
}
}
}
评论