单片机C语言入门讲座
按键灯亮——分支程序的设计

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

上期我们了解了单片机C语言的特点,学会了建立C语言的开发、调试环境。这一讲中将通过一些例子来学习如何编写分支程序。

什么是分支程序

例1:用2个键实现启动和停止:P1口接有8个LED,P3.2~P3.5接有4个按键。按下K1键LED全亮,按下K2键LED全灭。

实现:参考图1,输入源程序,建立并设置工程,在Debug页左侧Parameter文本框中加入-ddpj。进入调试打开实验仿真板(见本期配刊光盘的“本期程序”文件夹的Dpj.dll文件),用鼠标点击K1按钮可以看到8个LED全亮,点击K2按钮可以看到LED全部熄灭。

图3
图3 🔍原图 (446×223)

程序分析:程序首先定义了一个变量KeyValue,用于暂存读取的数据。然后用P3|=0xc3将P3.2~P3.5置为高电平,而不影响P3.0、P3.1、P3.6、P3.7。为理解这一程序,需要了解以下的内容:

(1) “|”是C语言中的一个操作符,用途是按位或,即将参与运算的两个数逐位相或,该运算相当于51单片机的ORL指令。例:

a=0x33,执行a=a|0x45:

0110011

01000101或01110111

结果是0x77,这个值通过“=”又赋给了a,所以结果是a=0x77。

(2) C语言中常用复合运算符。如程序行“a=a*3;”在C语言中可以这么写“a*=3;”两者的效果是一样的,该程序中的“P3|=0xc3;”相当于是执行“P3=P3|0xc3;”。这是C语言中常用的写法,因为这么写可以提高C编译效率,对于编程者来说可以节省程序输入的时间,所以C程序员往往习惯于这样写程序,初学时请注意适应。

(3) 80C51单片机的P1、P2和P3口是准双向I/O口,在作为输入之前一定要将相应的引脚置1,而其他不作为按键用的引脚则不应当发生变化,将数0x3c(00111100B)与P3按位相或,结果是中间4位被置1,其余4位不发生变化。

随后用“KeyValue=P3|0xfb;”程序行来判断K1是否被按下,原理如下:0xfb即11111011B,将该数与任一个数相或,除D.2位外,其余各位一定是“1”,而D.2位的值取决于参于运算的P3值,如果P3值该位是1,那么相或后结果是11111111即0xff,如果P3值该位是0,相或后结果是11111011,不等于0xff。因此,相或后如果该变量的值不等于0xff,就说明K1被按下,同样的方法可以判断K2是否被按下。

程序中的“!=”是C语言提供的一个运算符,其意义为“不等于”。

判断有键被按下后就可以执行不同的操作。这里用到了C语言提供的判断语句if,其形式如下:

if(表达式) 语句

如果表达式的结果为真,则执行语句,否则不执行。

如果K1被按下则将数“0”送往P1口,点亮所有LED,而K2被按下则将数“0xff”送往P1口,熄灭所有的LED。

从这个例子可以看到,if语句并不难理解,关键是判断条件是否满足要求,这需要熟悉if语句中表达式的内容,下面对此作一个介绍。

关系运算符和关系表达式

所谓“关系运算”实际上是两个值作比较,判断比较的结果是否符合给定的条件。关系运算的结果只有2种可能,即“真”和“假”。例:3>2的结果为真,而3<2的结果为假。

C语言一共提供了6种关系运算符:“<”(小于)、“<=”(小于等于)、“>”(大于)、“>=(大于等于)”、“==”(等于)和“!=”(不等于)。

用关系运算符将两个表达式连接起来的式子,称为关系表达式。例:

a>b,a+b>b+c,(a=3)>=(b=5)等都是合法的关系表达式。关系表达式的值只有两种可能,即“真”和“假”。在C语言中,没有专门的逻辑型变量,如果运算的结果是“真”,用数值“1”表示,而运算的结果是“假”则用数值“0”表示。

如式子:x1=3>2的结果是x1等于1,原因是3>2的结果是“真”,用1表示,该结果被“=”号赋给了x1,这里须注意,“=”不是等于之意(C语言中等于用“==”表示),而是赋值号,即将该号后面的值赋给该号前面的变量,所以最终结果是x1等于1。

逻辑运算符和逻辑表达式

用逻辑运算符将关系表达式或逻辑量连接起来的式子就是逻辑表达式。C语言提供了三种逻辑运算符:“&&”(逻辑与)、“||”(逻辑或)和“!”(逻辑非)。

C语言编译系统在给出逻辑运算的结果时,用“1”表示真,而用“0”表示假,但是在判断一个量是否是“真”时,以0代表“假”,而以非0代表“真”,这一点务必要注意。以下是一些例子:

(1) 若a=10,则!a的值为0,因为10被作为“真”处理,取反之后为“假”,系统给出的“假”的值为0。

(2) 如果a=——2,结果与上完全相同,原因也同上,初学时常会误以为负值为假,这里特别提醒注意。

(3) 若a=10,b=20,则a&&b的值为1,a||b的结果也为1,原因为参于逻辑运算时不论a与b的值究竟是多少,只要是非零,就被当作是“真”,“真”与“真”相与或者相或,结果都为真,系统给出的结果是1。

(4) 不要把逻辑运算符与按位逻辑运算符搞混淆了。它们首先是形式上不一样,按位逻辑运算有按位与、按位或和按位取反三种,它们的符号分别是“&(按位与)”、“|(按位或)“和”~(按位取反);其次它们的运算过程不一样,逻辑运算的过程前已叙及,“|”已作过介绍,“&”相当于51汇编指令中的“ANL”指令,而“~”则相当于51汇编指令中的“CPL”;最后它们的结果不一样,逻辑运算的结果只有真(1)和假(0)两种,而按位逻辑运算的结果是一些具体的数值。

(5) if后面括号中表达式究竟是什么并不重要,不管是关系表达式、算术运算式,还是一个常量都没有关系,关键是将这个式子的值计算出来,然后判断其是否是“0”,是则条件不成立,否则条件成立,这就是C语言的灵活之处,也是很多学过其他语言的读者感到困惑的地方。

选择语句if

if语句用以选择和判断,其基本形式是

用法1:if (表达式) 语句

描述:如果表达式为真,则执行语句,否则执行if语句后面的语句

用法2:if (表达式) 语句1 else 语句2

描述:如果表达式的结果为真,则执行语句1,否则执行语句2

用法3:if(表达式1) 语句1

else if(表达式2) 语句2

else if(表达式3) 语句3

else if(表达式m) 语句m

else 语句n

描述:如果表达式1的结果为真,则执行语句1,并退出if语句,否则去判断表达式2,如果表达式2为真则执行语句2,并退出if语句,否则去判断表达式3……最后,如果表达式m也不成立,就去执行else后面的语句n。else和语句n也可以省略不用。

下面我们通过一些例子学习这两种if语句的用法。

例2:要求按键K1被按下时灯亮,而K1松开后灯即灭。

参考图2输入源程序并建立工程,并设置使用实验仿真板进行演示。例中使用了if语句的第二种形式,即如果条件满足,则执行P1=0否则执行P1=0xff。

图1
图1 🔍原图 (428×172)

例1中按要求K1按下灯亮,而K2按下灯灭,但是如果K1和K2同时按下又会如何呢?实际做一下这个实验,如果使用实验仿真板,可以用鼠标按下K2键不要松开,移出按钮范围,K2即不弹起,然后按下K1键。可以发现灯不断地亮、灭。原因在于该程序的两个判断语句是一种顺序结构,相互之间没有制约。这样的程序在某些场合不能满足要求,如控制机器的动作,要求只要停止按钮被按下,就不能开启机器,这就需要用到if语句的第三种形式。

例3:K1点亮LED,K2熄灭LED,且K2优先,只要K2被按下LED就不能被点亮。

参考图3,输入源程序,建立工程,设置工程使用实验仿真板,进入调试,可以看到按下K1灯亮,按下K2灯灭,但是如果按下K2不松开鼠标左键,移出K2按钮范围,再去按K1键,会发现LED没有任何变化。

图2
图2 🔍原图 (459×204)

程序分析:程序中首先用“if((P3|0xf7)!=0xff)”来判断K2键是否被按下,如果该键被按下就执行“P1=0xff;”程序行,然后退出if语句,如果该条件不成立转去“else if((P3|0xfb)!=0xff)”判断K1键是否被按下,如果K1键被按下则执行“P1=0;”程序行,否则退出if语句,这样就实现了K2键优先的要求。当要求K1键优先时,只要将判断的次序改变一下即可,请读者自行完成这一练习。

if语句的嵌套:

在if语句中又包含一个或多个语句称为if语句的嵌套,通过嵌套可以实现更复杂的判断工作,其一般形式如下:

if()

if() 语句1

else 语句2

else

if() 语句3

else 语句4

使用if语句的嵌套时一定要注意if与else的配对关系,else总是与它上面的最近的if配对。如果写成:

if()

if()语句1

else

语句2

编程者的本意是外层的if与else配对,缩进的if语句为内嵌的if语句,但C语言并不识别书写的形式,else总是与它上面的最近的if配对,因此这里的else将与缩进的那个if配对,从而造成歧义。为避免这种情况,建议编程时使用大括号将内嵌的if语句括起来。

例4:如果K1被按下,K2也被按下,那么灯全亮,松开K2,灯也不灭,如果K1松开,则灯全灭。

该实验请参考图4自行完成,完成后,可以将第一个if后的大括号去掉试一试,看一看结果是否有变化。

图4
图4 🔍原图 (541×277)

switch/case语句

当程序中有多个分支时,可以使用if嵌套实现,但是当分支较多时,则嵌套的if语层数多,程序冗长而且可读性降低。C语言提供了switch语句直接处理多分支选择。switch的一般形式如下:

switch(表达式)

9 {case 常量表达式1:语句1

case 常量表达式2:语句2

……

case 常量表达式n:语句n

default:语句n+1

9 }

说明:switch后面括号内的“表达式”,ANSI标准允许它为任何类型;当表达式的值与某一个case后面的常量表达式相等时,就执行此case后面的语句,若所有的case中的常量表达式的值都没有与表达式值匹配的,就执行default后面的语句;每一个case的常量表达式的值必须不相同;各个case和default的出现次序不影响执行结果。

另外特别需要说明的是,执行完一个case后面的语句后,并不会自动跳出switch,转而去执行其后面的语句。

例5:按下K1键,P1.0和P1.3所接LED亮,按下K2键,P1.1和P1.7所接LED亮,按下K3键,P1.2和P1.5所接LED亮,按下K4键,P1.4和P1.6所接LED亮。

请读者自行输入源程序、建立工程,使用实验仿真板进行演示,点按各键,观察灯亮的情况,是否如题所要求。完成以后,可以将case 0xfb:…这一行后的break去掉,然后再试一试,这里会发现,按下K1和K2的结果是一样的,都是P1.0和P1.7所接灯亮,而其他按钮则正常。出现这一现象的原因是按下K1后,的确如我们设想执行了P1=0xf6的语句,但是并没有退出switch语句,而是继续执行下面的语句即case 0xf7:P1=0x7d;break,结果是按下K1和按下K2得到了同样的结果。

分支程序是一种常用的程序设计方法,在实际工作中,凡是涉及到判断的工作几乎都要用到这种程序设计方法,如常用的按键识别、根据给定条件做相应的工作等。

这一部分的知识就介绍到这里,下期将介绍循环程序的设计。

(周坚)