一、程序流程图
通常在编写程序之前,需要画程序流程图。流程图是一种图形语言,它用各种图形符号来说明程序的执行过程。常常采用的图形符号有以下几种:
● 圆角矩形框——为端点框,表示一个程序模块的开始或结束;
● 矩形框——为任务框,表示要处理的任务;
● 菱形框——为判断框,表示要判断的因素,不同的判断结果将导致程序走入不同的分支(菱形框也有时用两端带尖的条形框取代);
● 指向线——为带有箭头的线段,表示程序的走向。
程序按其执行顺序或者行进路线可分为4种基本结构:顺序结构、分支结构、循环结构和子程序结构。可以说,无论多么庞大和复杂的程序均可看成由这4种基本结构组合或嵌套而成。
二、顺序程序结构
顺序程序结构在流程图中表示为任务框一个一个地串行连接。在计算机执行程序时表现为,从头至尾严格按照次序一条语句、一条语句地顺序执行,并且每一条语句均被执行一遍。流程图如图1所示。图中的A、B和C可以分别代表一条语句或一段程序。

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

有一点需要声明的是,实现同一功能的程序不是唯一的,可以有多种不同的设计和编写方法。
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所示。

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

[例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 ;子程序返回主程序
大海创作室