单片机C语言入门讲座
流水灯——循环程序的设计

🏠 首页 《无线电》杂志 2004年 🔗 第3期 🔗 第38页 分类:电脑与单片机 🔗 周坚 🔗

前面的课程中我们学习了分支程序的设计,下面学习程序设计中另一种常用的程序结构——循环结构。

循环程序简介

在一个实用的程序中,循环结构是必不可少的。循环是反复执行某一部分的操作,有两类循环结构:

(1) 当型循环,即当给定的条件成立时,执行循环体部分,执行完毕回来再次判断条件,如果条件成立继续循环,否则退出循环。

(2) 直到型循环,即先执行循环体,然后判断给定的条件,只要条件成立就继续循环,直到判断出给定的条件不成立时退出循环。

下面我们就通过一些例子来看C语言提供的循环语句,及如何利用这些循环语句写循环程序。


例1:
使P1口所接LED以流水灯状态显示

#include “reg51.h”

#include“intrins.h”//该文件包含有-crol-(...)函数的说明

void mDelay(unsigned int De-layTime)

9{unsigned int j=0;

for(;DelayTime>0;Delay-Time——)

9{for(j=0;j<125;j++)};}}}

void main()

9{unsigned char OutData=0xfe;

while(1)

9{P1=OutData;

OutData=-crol-(OutData,1); //循环左移

mDelay(1000);/*延时1000ms*/

}}


程序分析1:
输入源程序,并命名为exam31.c,建立并设置工程,这个例子使用实验仿真板(dpj.dll文件,见本期配刊光盘的“本期程序”文件夹,具体使用方法见今年第1期杂志第38页相关内容)演示的过程请自行完成。如果在演示时,发现灯“流动”的速度太快,几乎不能看清,那么可以将mDelay(1000)中的1000改大一些,如2000、3000或更大。软件仿真无法实现硬件实验同样的速度,这是软件仿真的固有弱点,下面介绍如何用www.mcustudio.com网上介绍的单片机实验电路板(见图1)来实现这个例子。

图2
图2 🔍原图 (425×301)

将实验板带的串口电缆连接到PC机的串口上,设置Keil工程时,应选中De-bug页,点击右侧的“Use Keil Monitor_51 Drive”,然后选中“Load Application at Start”和“Go Till main”,如图2所示。选择完成后,点击“Setting”按钮,选择你所用的PC上的串口(COM1或COM2),波特率(通常可以使用38400),其他设置一般不需要更改,如图3所示。点击“OK”回到Debug页面后即可完成设置。

图3
图3 🔍原图 (272×160)
图1
图1 🔍原图 (341×226)

编译、连接正确后,点击菜单De-bugStart /Stop Debug Session,可以看到在窗口右下角的命令窗口提示正确连接到了Monitor-51,如图4所示。此时,即可使用Keil提供的单步、过程单步、执行到当前行、设置断点等调试方法进行程序的调试。如果全速运行程序,就可看到流水灯的实验效果。


程序分析2:
这段程序中在两处用到了循环语句,首先是主程序中,按前面的分析,主程序应该是一个无限循环的过程,只要通电,程序就能够不断地运行下去。因此主程序使用了“while(1)9邀......9妖”这样的循环语句写法,在9邀9妖中的所有程序将会不断地循环执行,直到断电为止;第二处是延时程序,使用了for循环语句的形式。下面我们就对循环语句作一个介绍。

while语句

while语句用于实现“当型”循环结构,其一般形式如下:

while(表达式) 语句

当表达式为非0值(真)时,执行while语句中的内嵌语句。其特点是:先判断表达式,后执行语句。

在例1中,表达式使用了一个常数“1”,这是一个非零值,即“真”,因此,条件总是满足,所以语句总是会被执行,这样就构成了一个无限循环的过程。下面再举一例说明:


例2:
当K1键被按下时,流水灯工作,否则灯全部熄灭。

#include “reg51.h”

#include“intrins.h”//该文件包含有-crol-(...)函数的说明

void mDelay(unsigned int De-layTime)

9{ ......与例1同9}

void main()

9{ unsigned char OutData=0xfe;

while(1)

9{ P3|=0x3c;

while((P3|0xfb)!=0xff)

9{ P1=OutData;

OutData=_crol_(OutData,1); //循环左移

mDelay(1000);/9*延时1000ms*/}

P1=0xff;

}}


程序分析:
这个程序中的第二个while语句中的表达式用来判断K1键是否被按下,如被按下,则执行循环体内的程序,否则执行“P1=0xff”;程序行,实现题目的要求,有关实验,请读者自己完成。

do-while语句

do-while语句用来实现“直到型”循环,特点是先执行循环体,然后判断循环条件是否成立。其一般形式如下:

do 循环体语句

while(表达式)

对同一个问题,既可以用while语句处理,也可以用do_while语句处理。但是这两个语句是有区别的,下面我们用do_while语句改写例2。


例3:
用do-while语句实现如下功能:K1按下,流水灯工作,K2松开,灯全熄灭。

#include “reg51.h”

#include“intrins.h”//该文件包含有-crol-(...)函数的说明

void mDelay(unsigned int De-layTime)

9{ ......与例1同9}

void main()

9{ unsigned char OutData=0xfe;

while(1)

9{ P3|=0x3c;

do

9{ P1=OutData;

OutData=-crol-(OutData,1); //循环左移

mDelay(1000);/*延时1000ms*/

9} while((P3|0xfb)!=0xff)

P1=0xff;

9}}


程序分析:
这个程序除主程序中将while用do_while替代外,没有其他的变化,初步的想法,如果while()括号中的表达式为“真”即K1键被按下,应该执行程序体,否则不执行,效果应该与例2相同。但是事实上,实际做这个练习就会发现,不论K1是否被按下,流水灯都在工作。为何会有这样的结果呢?

单步运行程序可以发现,如果K1键被按下,的确是在执行循环体内的程序,与设想相同。而当K1没有被按下时,按设想,循环体内的程序不应该被执行,但事实上,do后面的语句至少要被执行一次才去判断条件是否成立,所以程序依然会去执行do后的循环体部分,只是在判断条件不成立(K1没有被按下)时,转去执行P1=0xff;然后又继续循环,而下一次循环中又会先执行一次循环体部分,因此,K1是否被按下的区别仅在于“P1=0xff;”这一程序行是否会被执行到。

for语句

C语言中的for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况,它完全可以替代while语句。

for语句的一般形式为:

for(表达式1;表达式2;表达式3) 语句

它的执行过程是:

(1) 先求解表达式1。

(2) 求解表达式2,如果其值为真,则执行for语句中指定的内嵌语句(循环体),然后执行第(3)步;如果为假,则结束循环。

(3) 求解表达式3。

(4) 转回上面的第(2)步继续执行。

典型的应用是这样的一种形式:

for(循环变量初值;循环条件;循环变量增值) 语句

例如上述例子中的延时程序有这样的程序行:“for(j=0;j<125;j++)9{;}”,执行这行程序时,首先执行j=0,然后判断j是否小于125,如果小于125则去执行循环体(这里循环体没有做任何工作),然后执行j++,执行完后再去判断j是否小于125……如此不断循环,直到条件不满足(j>=125)为止。

如果用while语句来改写,应该这么写:

j=0;

while(j<125)

9{……

j++;

9}

可见,用for语句更简单、方便。

如果变量初值在for语句前面赋值,则for语句中的表达式1应省略,但其后的分号不能省略。上述程序中有:“for(;DelayTime>0;DelayTime——)9邀…9妖”的写法,省略掉了表达式1,因为这里的变量DelayTime是由参数传入的一个值,不能在这个式子里赋初值。

表达式2也可以省略,但是同样不能省略其后的分号,如果省略该式,将不判断循环条件,循环无终止地进行下去,也就是认为表达式始终为真。

表达式3也可以省略,但此时编程者应该另外设法保证循环能正常结束。

表达式1、2和3都可以省略,即形成如for(;;)的形式,它的作用相当于是while(1),即构成一个无限循环的过程。

循环可以嵌套,如上述延时程序中就是两个for语句嵌套使用构成二重循环,C语言中的三种循环语句可以相互嵌套。

break语句

在一个循环程序中,可以通过循环语句中的表达式来控制循环程序是否结束,除此之外,还可以通过break语句强行退出循环结构。


例4:
开机后,全部LED不亮,按下K1则从LED1开始依次点亮,至LED8后停止并全部熄灭,等待再次按下K1键,重复上述过程。如果中间K2键被按下,LED立即全部熄灭,返回起始状态。

#include “reg51.h”

#include“intrins.h”//该文件包含有-crol-(...)函数的说明

void mDelay(unsigned int De-layTime)

9{...与例1同9}

void main()

9{ unsigned char OutData=0xfe;

unsigned char i;

while(1)

9{ P3|=0x3c;

if((P3|0xfb)!=0xff)//K1键被按下

9{ OutData=0xfe;

for(i=0;i<8;i++)

9{ mDelay(1000);/*延时1000ms*/

tmp=0xfe;

if((P3|0xf7)!=0xff)//K2键被按下

break;

OutData=_crol_(OutData,i);

P1&=OutData;

9}

9}

P1=0xff;

9}

9}

请读者输入程序、建立工程,使用实验仿真板来验证这一功能,注意,K2按下的时间必须足够长,因为这里每1s才会检测一次K2是否被按下。本期配刊光盘中“本期程序”文件夹下的ex-am34.avi记录了实验过程,可供参考。


程序分析:
开机后,当检测到K1键被按下,执行一个for(i=0;i<8;i++)9邀…9妖的循环,即循环8次后即停止,而在这段循环体中,又用到了如下的程序行:“if((P3|0xf7)!=0xff) break;”即判断K2是否按下,如果K2被按下,则立即结束本次循环。

continue语句

该语句的用途是结束本次循环,即跳过循环体中下面的语句,接着进行下一次是否执行循环的判定。

Continue语句和break语名的区别是:continue语句只结束本次循环,而不是终止整个循环的执行;而break语句则是结束整个循环过程,不会再去判断循环条件是否满足。


例5:
将上述例4中的break语句改为continue语句,会有什么结果?


程序分析:
开机后,检测到K1键被按下,各灯开始依次点亮,如果K2键没有被按下,将循环8次,直到所有灯点亮,又加到初始状态,即所有灯灭,等待K1按键。如果K2键被按下,不是立即退出循环,而只是结束本次循环,即不执行continue语句下面的“OutData=_crol_(OutData,i);P1&=OutData;”语句,但要继续转去判断循环条件是否满足,因此,不论K2键是否被按下,循环总是要经过8次才会终止,差别在于是否执行了上述两行程序。如果上述程序行有一次未被执行,意味着有一个LED未被点亮,因此,如果按下K2过一段时间(1、2s)松开,中间将会有一些LED不亮,直到最后一个LED被点亮,又回到全部熄灭的状态,等待K1被按下。

本期配刊光盘中“本期程序”文件夹下的exam35.avi记录了实验过程,可供参考。该文件夹下还有本期涉及的其他实例的C语言源程序,读者均可直接在Keil软件中打开。


练习:
固定每点亮2个LED后,第三个LED不亮,请编程实现。

凌阳16位单片机暨大学计划研讨会即将召开

2004年3月20日,凌阳16位单片机暨大学计划研讨会,将在北京航空航天大学举行。

届时,包括业界专家、高校教师、台湾凌阳科技(SUNPLUS)资深IC设计专家及系统工程师在内的数百人,将齐聚一堂,就IC设计技术、十六位单片机的发展形势、凌阳十六位单片机原理及开发等内容展开广泛的探讨。同时,还将向与会者发布凌阳系列16位单片机的应用方案,与大家共享。

据悉,本次研讨会是在2002凌阳科技大学计划发布会全国站、2003凌阳16位单片机百所高校巡回讲座后,凌阳大学计划举行的又一系列的全国研讨会。

会议诚邀各高校教师及电子工程师参加,届时会有精美礼品等待您。

详情请参见凌阳大学计划网站:http://www.unsp.com.cn

(周坚)