义隆单片机课堂
EM78P447S单片机入门与实作系列讲座

🏠 首页 《无线电》杂志 2002年 🔗 第7期 🔗 第1065353216页 分类:电脑与单片机 🔗 大海创作室 🔗

一、程序流程图

通常在编写程序之前,需要画程序流程图。流程图是一种图形语言,它用各种图形符号来说明程序的执行过程。常常采用的图形符号有以下几种:

● 圆角矩形框——为端点框,表示一个程序模块的开始或结束;

● 矩形框——为任务框,表示要处理的任务;

● 菱形框——为判断框,表示要判断的因素,不同的判断结果将导致程序走入不同的分支(菱形框也有时用两端带尖的条形框取代);

● 指向线——为带有箭头的线段,表示程序的走向。

程序按其执行顺序或者行进路线可分为4种基本结构:顺序结构、分支结构、循环结构和子程序结构。可以说,无论多么庞大和复杂的程序均可看成由这4种基本结构组合或嵌套而成。

二、顺序程序结构

顺序程序结构在流程图中表示为任务框一个一个地串行连接。在计算机执行程序时表现为,从头至尾严格按照次序一条语句、一条语句地顺序执行,并且每一条语句均被执行一遍。流程图如图1所示。图中的A、B和C可以分别代表一条语句或一段程序。

图1
图1 🔍原图 (142×395)

[例1]
当用LED数码管对某一RAM存储器单元的内容进行显示时,因为一位数码管一般只能显示4位二进制数(即1位十六进制数)的值,所以通常需要将被显示单元内的8比特数据,按高4位和低4位拆分成两个“半字节”。在本例中,假设将RAM中通用寄存器20H单元的数据分解后,依次将低4位和高4位分别放入21H和22H单元,并将这两个单元中空余的高4位补零。图2就是实现这一功能的程序流程图和程序片段。

图2
图2 🔍原图 (142×387)

有一点需要声明的是,实现同一功能的程序不是唯一的,可以有多种不同的设计和编写方法。

MOV A,20H ;将20H单元的内容送入A

AND A,@0FH ;将高4位清零,低4位保持不变

MOV 21H,A ;将拆分后的低4位送21H单元

SWAPA 20H ;将20H单元内容高、低半字节换位后送A

AND A,@0FH ;将A的高4位清零,低4位保持不变

MOV 22H,A ;将拆分后的高4位送22H单元

设Z是一位二进制数,同“1”和“0”进行逻辑“与”运算时,结果分别是维持原样和清零,即Z&1=Z和Z&0=0。基于这一道理,采用“AND A,0FH”指令,我们可以将一个8比特数据同常数0FH相“与”,实现清零高4位和保留低4位的目的。但这一操作只能在累加器A内才能完成。

程序执行前,假设原先20H单元的内容为BAH,21H和22H单元的内容是随机值;程序执行后,20H单元内容不变,而21H和22H单元的内容变成了0AH和0BH。如图3所示。

图3
图3 🔍原图 (425×210)

三、分支程序结构

分支程序流程图中都包含着判断框,该判断框具有一个入口和两个出口,形成程序的两个分支。如图4所示,究竟执行B还是C,要由判断框内的条件“是”、“否”成立来决定。语句A执行完之后通常产生一个条件码,当条件判为“是”(记为Y)时进入B分支,当条件判为“否”(记为N)时进入C分支。由此可见,只有一个分支中的程序被执行了一遍,而另一分支中的程序没有得到执行。在实际编程时,不仅会用到上述的“二分支”程序结构,还会用到分支数多于两个的“多分支”程序结构。不过,多分支结构可以看作由二分支结构嵌套而成,即分支中又包含分支。下面举一个二分支结构的例子。

图4
图4 🔍原图 (425×445)

[例2]将RAM的20H单元和21H单元中预先存放的两个数作比较,把其中的大数挑出并存入22H单元。方法是将两个参与比较的数做减法运算,如果被减数小于减数,就会发生借位(C=0),否则,不发生借位(C=1)。判断标志位C的值,就可以挑出大数。下面是沿着这个思路设计的程序流程图和程序片段。流程图见图5,图中带括号的数与不带括号的数含义不同,如“(21H)”表示以21H为地址的单元的内容,而“22H”则表示以22H为地址的单元。程序如下:

STATUS == 03H ;定义STATUS寄存器地址为03H

C == 0 ;定义进位/借位标志C在STATUS中的位地址为0

MOV A,20H ;将20H单元的内

容送入A

SUB A,21H ;21H单元内容减去A内容,结果留在A中

JBS STATUS,C;若C=1没借位,21H中的数大,则跳一步到R21BIG处

JMP R20BIG ;若C=0有借位,20H中的数较大,则跳转至R20BIG处

R21BIG:MOV A,21H ;将21H中的数送入A

MOV 22H,A ;再将它转存到22H单元

JMP STOP ;跳过下面两条指令到程序末尾

R20BIG:MOV A,20H ;将20H单元的数送入A

MOV 22H,A ;再将它转存到22H单元

STOP: JMP STOP ;任务完成,停机,原地踏步

对程序中的指令运用作几点说明:

1. 凡是需要两个数参与的逻辑运算(与、或、异或)和算术运算(加、减),都需要事先将其中一个操作数放入A中。在此使用减法指令更要格外注意,应预先把“减数”放入A中,或者说,预先放入A中的数,在运算中被当作减数,而寄存器中的数充当了被减数;

2. 一条条件跨步跳转指令往往需要跟随一条无条件远跳跳转指令JMP,才能实现长距离的转移和程序的分支;

3. EM78单片机的指令系统中没有设置专用的停机指令,可以用一条跳转到自身的无条件跳转指令JMP来实现动态停机。

4. 由于该单片机指令系统中没有可以实现把8位常数直接送到RAM单元的指令,故常用累加器A作中转站。

四、循环程序结构

在程序设计过程中,有时要求对某一段程序重复执行多遍,用循环程序结构,会有利于缩短程序,节省ROM空间。在一个循环程序的结构中包含以下4个组成部分:


1. 循环变量设置。
在循环开始时,往往需要指定或定义一个循环变量(可以是循环次数计数器、地址指针等),并且给它设置一个初始值。


2. 循环体。
要求重复执行的程序段,即循环程序的主体部分。


3. 循环变量修改。
修改循环变量的值,为下一次的循环准备条件。


4. 循环控制。
在循环程序中必须给出循环结束的条件,否则就成为“死循环”。循环控制就是根据循环结束条件,判断是否跳出循环。

流程图如图6所示,有两种画法。在图6(a) 中循环体至少执行一次,这是因为先执行循环体,后判断循环结束条件;而在图6(b) 中,先判断结束条件,再执行循环体,如果一开始就满足结束条件,则循环体将一次都不被执行。在实际编程时,不仅会用到上述的“单一循环”程序结构,还会用到“多重循环”程序结构。不过,多重循环结构可以看作是由单一循环结构嵌套而成,即循环体中又包含有一个或多个循环。下面举一个单一循环结构的例子。

[例3]假设需要在RAM数据存储器中,将地址从10H开始的30个单元都填入00H。这时我们借助于间接寻址寄存器RSR(当作地址指针),可以采用循环结构编写程序。实现该功能的程序流程图见图7,程序片段如下:

COUNT == 08H ;指定08H单元作为循环次数计数器(即循环变量)

RSR == 04H ;定义RSR寄存器地址为04H

IAR == 00H ;定义IAR寄存器地址为00H

MOV A,@30 ;把计数器初始值30送入A

MOV COUNT,A;再把30转入计数器(作为循环变量的初始值)

MOV A,@10H ;将10H(起始地址)送入A

MOV RSR,A ;再把10H转入寄存器RSR(用作地址指针)

NEXT: CLR IAR ;把以RSR内容为地址所指定的单元清零

INC RSR ;地址指针内容加1,指向下一个单元

DJZ COUNT ;计数值减1,结果若为0就跳过下一条指令

JMP NEXT ;否则,跳转回去并执行下一次循环

STOP: JMP STOP ;结束循环之后执行该语句,实现停机

在这段程序中,只需对语句MOV A,@30中的30作改动,就可以很容易地实现对任意多个单元清零。同理,只需对语句MOV A,@10H中的10H作改动,就可以很容易地实现对从任意地址开始的单元清零。

上述功能当然也可以采用顺序程序结构来设计,不过程序会比循环结构长得多,并且还会随着填充单元个数的增加而增长。

五、子程序结构

在实际程序中,常常会遇到一些完全相同的计算和操作,如果每次都编制完全相同的程序段,不仅麻烦,而且浪费宝贵的程序存储器空间。因此,可以编制标准化的程序模块,存储于程序存储器的指定区域,每次需要时调出使用,这种程序模块就称为“子程序”,调用子程序的程序称为“主程序”或者“调用程序”。子程序结构是程序设计标准化和模块化的有效手段,所以子程序结构也可叫模块化程序结构。

对EM78系列单片机编程时,在主程序的适当地方放置CALL指令来实现调用,在子程序的开头需要设置地址标号(又可兼作子程序的名称)、末尾需要放置RET或RETL指令,以便于主程序的调用和子程序的返回。在主程序调用子程序时,有时会遇到参数传递和现场保护这两个问题。

所谓参数传递,就是在调用子程序前,主程序应先把有关参数放到某些约定的寄存器单元,进入子程序后,可以从约定的单元取出有关参数加以处理。待处理完之后,子程序结束之前,应把处理结果送到约定单元。在返回主程序后,主程序可以从这些约定单元获得所需结果。在主程序和子程序之间传递8位参数,用累加器A是理想的选择。

所谓现场保护,就是主程序在运行过程中使用了一些寄存器来存放临时数据(或中间结果),在子程序运行过程中有时也要用到这些寄存器,为了避免对于主程序还有用的临时数据被子程序覆盖掉,就要设法保护这些临时数据。在执行完子程序返回主程序时,还要恢复这些数据,称为现场恢复。

[例4]假设在RAM中10H开始的3个单元中存放着3个不同的数。现在要求编制一段程序,把3个数中最大者找出,并放入15H单元。为了尽可能利用现有的程序段,我们可以把[例3]中比较两个数的程序段改造成一个子程序SUB1,该子程序具有两个入口参数X和Y和一个出口参数Z。再编写一个主程序MAIN来反复调用SUB1即可。下面是实现该功能的程序流程图(见图8)和程序段。

STATUS == 03H ;定义STA-TUS寄存器地址为03H

C == 0 ;定义进位/借位标志C在STATUS中的位地址为0

X == 20H ;定义变量X为子程序的一个入口参数

Y == 21H ;定义变量Y为子程序的另一个入口参数

Z == 22H ;定义变量Z为子程序的出口参数

;——————————————

;主程序:

;——————————————

MAIN:

MOV A,10H ;第1个数经A

转送给X

MOV X,A

MOV A,11H ;第2个数经A

转送给Y

MOV Y,A

CALL SUB1 ;第1次调用子程序

MOV A,Z ;该次比较结果经A转送给X

MOV X,A ;作为下次参加比较的数之一

MOV A,12H ;第3个数经A转送给Y

MOV Y,A

CALL SUB1 ;第2次调用子程

MOV A,Z ;将最终结果Z经A

转送到15H单元

MOV 15H,A

STOP:JMP STOP ;停机

;————————————-

;子程序:(入口参数:X和Y,出口参数:Z)

;————————————-

SUB1:

MOV A,X ;将X的内容送

SUB A,Y ;Y内容减去A内容,结果留在A中

JBS STATUS,C ;若测得C=1,即没发生借位,意味着Y中的数大,则跳一步,即跳转到程序的Y-BIG处

JMP X-BIG ;否则,C=0发生了借位,意味着X中的数大,则跳转

Y-BIG:

MOV A,Y ;将Y中的数送入

€?€?A

MOV Z,A ;再将它转存到Z

JMP END ;跳过下面两条指令

到程序末尾

X-BIG:

MOV A,X ;将X中的数送入

€?€?A

MOV Z,A ;再将它转存到Z

END:RET ;子程序返回主程序

大海创作室