资源预览内容
第1页 / 共165页
第2页 / 共165页
第3页 / 共165页
第4页 / 共165页
第5页 / 共165页
第6页 / 共165页
第7页 / 共165页
第8页 / 共165页
第9页 / 共165页
第10页 / 共165页
亲,该文档总共165页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
4.1 4.1 概述概述 4.2 4.2 简单程序设计简单程序设计 4.3 4.3 分支程序设计分支程序设计 4.4 4.4 循环程序设计循环程序设计 4.5 4.5 查表程序查表程序 4.6 4.6 子程序设计与堆栈技术子程序设计与堆栈技术 4.7 4.7 实用汇编子程序举例实用汇编子程序举例0404第四章第四章 程序设计程序设计4.1 4.1 概概 述述单片机程序设计语言主要有三类:机器语言、汇编语言和高级语言。机器语言(MachineLanguage)是指直接用机器码编写程序、能够为计算机直接执行的机器级语言。机器码是一串由二进制代码“0”和“1”组成的二进制数据,其执行速度快,但是可读性极差。机器语言一般只在简单的开发装置中使用,程序的设计、输入、修改和调试都很麻烦,在实训1和实训3中直接固化或输入的程序都是机器语言程序。汇编语言(AssemblyLanguage)是指用指令助记符代替机器码的编程语言。汇编语言程序结构简单,执行速度快,程序易优化,编译后占用存储空间小,是单片机应用系统开发中最常用的程序设计语言。汇编语言的缺点是可读性比较差,只有熟悉单片机的指令系统,并具有一定的程序设计经验,才能研制出功能复杂的应用程序,实训4中的3个程序都是用汇编语言设计的。高级语言(High-LevelLanguage)是在汇编语言的基础上用自然语言的语句来编写程序,例如PL/M-51、FranklinC51、MBASIC51等,程序可读性强,通用性好,适用于不熟悉单片机指令系统的的用户。高级语言编写程序的缺点是实时性不高,结构不紧凑,编译后占用存储空间比较大,这一点在存储器有限的单片机应用系统中没有优势。目前,大多数用户仍然使用汇编语言进行单片机应用系统的软件设计,本章将介绍MCS-51单片机汇编语言的程序设计方法。单片机汇编语言程序设计的基本步骤如下:(1)题意分析。熟悉并了解汇编语言指令的基本格式和主要特点,明确被控对象对软件的要求,设计出算法等。(2)画出程序流程图。编写较复杂的程序,画出程序流程图是十分必要的。程序流程图也称为程序框图,是根据控制流程设计的,它可以使程序清晰,结构合理,便于调试。(3)分配内存工作区及有关端口地址。分配内存工作区,要根据程序区、数据区、暂存区、堆栈区等预计所占空间大小,对片内外存储区进行合理分配并确定每个区域的首地址,便于编程使用。(4)编制汇编源程序。(5)仿真、调试程序。(6)固化程序。4.2 4.2 简单程序设计简单程序设计简单程序也就是顺序程序,实训4中的程序1就是顺序程序结构,它是最简单、最基本的程序结构,其特点是按指令的排列顺序一条条地执行,直到全部指令执行完毕为止。不管多么复杂的程序,总是由若干顺序程序段所组成的。本节通过实例介绍简单程序的设计方法。例4.14字节(双字)加法。将内部RAM30H开始的4个单元中存放的4字节十六进制数和内部RAM40H单元开始的4个单元中存放的4字节十六进制数相加,结果存放到40H开始的单元中。例4.1题意分析示意图 (1) 题意分析。题意分析。题目的要求如图所示。ORG0000HMOV A,30HADD A,40HMOV 40H,A;最低字节加法并送结果MOV A,31HADDCA,41HMOV 41H,A;第二字节加法并送结果(2) 汇编语言源程序。汇编语言源程序。按照双字节加法的思路,实现4字节加法的源程序如下:MOV A,32HADDCA,42HMOV 42H,A;第三字节加法并送结果MOV A,33HADDCA,43HMOV 43H,A;第四字节加法并送结果,进位位在CY中END显然,上面程序中,每一步加法的步骤很相似,因此我们可以采用循环的方法来编程,使得源程序更加简洁,结构更加紧凑。用循环方法编制的源程序见习题4.3题。例4.2数据拼拆程序。将内部RAM30H单元中存放的BCD码十进制数拆开并变成相应的ASCII码,分别存放到31H和32H单元中。(1) 题意分析。题意分析。 题目要求如图所示。例4.2题意分析示意图本题中,首先必须将两个数拆开,然后再拼装成两个ASCII码。数字与ASCII码之间的关系是:高4位为0011H,低4位即为该数字的8421码。 (2) 汇编语言源程序。汇编语言源程序。源程序如下:ORG0000HMOVR0,#30HMOVA,#30HXCHDA,R0;A的低4位与30H单元的低4位交换MOV32H,A;A中的数值为低位的ASCII码MOVA,R0SWAPA;将高位数据换到低位ORLA,#30H;与30H拼装成ASCII码MOV31H,AEND4.3 4.3 分支程序设计分支程序设计4.3.1分支程序实例分支程序实例1两分支程序设计两分支程序设计例4.3两个无符号数比较(两分支)。内部RAM的20H单元和30H单元各存放了一个8位无符号数,请比较这两个数的大小,比较结果显示在实训的实验板上:若(20H)(30H),则P1.0管脚连接的LED发光;若(20H)(30H),则P1.1管脚连接的LED发光。 (1) 题意分析。题意分析。本例是典型的分支程序,根据两个无符号数的比较结果(判断条件),程序可以选择两个流向之中的某一个,分别点亮相应的LED。比较两个无符号数常用的方法是将两个数相减,然后判断有否借位CY。若CY=0,无借位,则XY;若CY=1,有借位,则XY。程序的流程图如图所示。两数比较流程图 (2)汇编语言源程序。源程序如下:XDATA20H;数据地址赋值伪指令DATAYDATA30HORG0000HMOVA,X;(X)ACLRC;CY=0SUBBA,Y;带借位减法,A-(Y)-CYAJCL1 ;CY=1,转移到L1CLRP1.0;CY=0,(20H)(30H),点亮P1.0连接的LEDSJMPFINISH;直接跳转到结束等待L1:CLRP1.1;(20H)(30H),则P1.1管脚连接的LED发光;若(20H)(30H),则P1.2管脚连接的LED发光。 (1) 题意分析。题意分析。有符号数在计算机中的表示方式与无符号数是不相同的:正数以原码形式表示,负数以补码形式表示,8位二进制数的补码所能表示的数值范围为+127-128。计算机本身无法区分一串二进制码组成的数字是有符号数或无符号数,也无法区分它是程序指令还是一个数据。编程员必须对程序中出现的每一个数据的含义非常清楚,并按此选择相应的操作。例如,数据FEH看作无符号数其值为254,看作有符号数为-2。比较两个有符号数X和Y大小要比无符号数麻烦得多。这里提供一种比较思路:先判别两个有符号数X和Y的符号,如果X、Y两数符号相反,则非负数大;如果X、Y两数符号相同,将两数相减,然后根据借位标志CY进行判断。这一比较过程如图所示。比较两个有符号数X、Y的流程图 (2) 汇编语言源程序。汇编语言源程序。源程序如下:XDATA20HYDATA30HORG0000HMOVA,XXRL A,Y;(X)与(Y)进行异或操作JBACC.7,NEXT1;累加器A的第7位为1,两数符号不同,转移到NEXT1MOVA,XCJNEA,Y,NEQUAL;(X)(Y),转移到NEQUALCLRP1.0;(X)=(Y),点亮P1.0连接的LEDSJMPFINISHNEQUAL:JCXXY;(X)(Y),转移到XDYNEXT1:MOV A,XJNB ACC.7,XDY ;判断(X)的最高位D7,以确定其正负XXY:CLRP1.2;(X)(Y),点亮P1.1连接的LEDFINISH:SJMP $END(3)程序说明。判断两个有符号数符号异同的方法。本例中使用逻辑异或指令,将(X)与(Y)进行异或操作,那么,(X)的符号位(X)7与(Y)的符号位(Y)7异或的结果如下:若(X)7与(Y)7相同,则(X)7(Y)70;若(X)7与(Y)7不相同,则(X)7(Y)71。本例中,(X)与(Y)的异或结果存放在累加器A中,因此判断ACC.7是否为零即可知道两个数的符号相同与否。比较两个有符号数的其它方法。除了本例中使用的比较两个有符号数的方法之外,我们还可以利用溢出标志OV的状态来判断两个有符号数的大小。具体算法如下:若X-Y为正数,则OV=0时XY;OV=1时XY。若X-Y为负数,则OV=0时XY。采用这种比较方式的汇编语言源程序自己编写。 3.散转程序散转程序散转程序是指经过某个条件判断之后,程序有多个流向(三个以上)。在后面的键盘接口程序设计中经常会用到散转功能根据不同的键码跳转到相应的程序段。例4.5在实训4程序2的基础之上,先设计两个开关,使CPU可以察知两个开关组合出的4种不同状态。然后对应每种状态,使8个LED显示出不同的亮灭模式。(1)硬件设计。在实训1的电路中,我们使用单片机的并行口P1的输出功能来控制8个LED的显示。现在我们使用其P3口的输入功能来设计两个输入开关,硬件原理图如图所示。例4.5硬件原理图如图4.9所示,当开关S0接通2时,P3.4管脚接地,P3.4=0;当S0接通1时,P3.4接+5V,P3.4=1。同样,当开关S1接通2时,P3.5管脚接地,P3.5=0;当S1接通1时,P3.5接+5V,P3.5=1。假设要求P3口的开关状态对应的P1口的8个LED的显示方式如下:P3.5P3.4显示方式0 0全亮01交叉亮10低4位连接的灯亮,高4位灭11低4位连接的灯灭,高4位亮 (2) 软件设计。软件设计。程序设计思想。散转程序的特点是利用散转指令实现向各分支程序的转移,程序流程图如图4.10所示。散转程序流程图汇编语言源程序。ORG0000HMOV P3,#00110000B;使P3口锁存器相应位置位MOV A,P3;读P3口相应引脚线信号ANLA,#00110000B;“逻辑与”操作,屏蔽掉无关位SWAP A;将相应位移位到低位RLA;循环左移一位,A*2AMOV DPTR,#TABLE;转移指令表的基地址送数据指针DPTRJMPA+DPTR;散转指令ONE:MOVP1,#00H ;第一种显示方式,S0接地,S1接地SJMP$TWO:MOVP1,#55H;第二种显示方式,S0接+5V,S1接地SJMP$THREE:MOVP1,#0FH;第三种显示方式,S0接地,S1接+5VSJMP$FOUR:MOVP1,#0F0H;第四种显示方式,S0接+5V,S1接地SJMP$TABLE:AJMP ONE;转移指令表AJMP TWOAJMP THREEAJMP FOUREND (3) 程序说明。程序说明。读P3口的管脚状态。MCS-51的4个I/O端口共有三种操作方式:输出数据方式,读端口数据方式和读端口引脚方式。输出数据方式举例:MOVP1,#00H;输出数据00HP1端口锁存器P1引脚读端口数据方式举例:MOVA,P3;AP3端口锁存器读端口引脚方式举例:MOVP3,#0FFH;P3口端口锁存器各位置1MOVA,P3 ;AP3端口引脚状态注意:读引脚方式必须连续使用两条指令,首先必须使欲读的端口引脚所对应的锁存器置位,然后再读引脚状态。散转指令JMPA+DPTR。散转指令是单片机指令系统中专为散转操作提供的无条件转移指令,指令格式如下:JMPA+DPTR;PCDPTR+A一般情况下,数据指针DPTR固定,根据累加器A的内容,程序转入相应的分支程序中去。本例采用最常用的转移指令表法,就是先用无条件转移指令按一定的顺序组成一个转移表,再将转移表首地址装入数据指针DPTR中,然后将控制转移方向的数值装入累加器A中作变址,最后执行散转指令,实现散转。指令转移表的存储格式如图4.11所示。指令转移表的存储格式由于无条件转移指令AJMP是两字节指令,因此控制转移方向的A中的数值为A=0转向AJMP ONEA=2转向AJMP TWOA=4转向AJMP THREEA=6转向AJMP FOUR程序中,从P3口读入的数据分别为0、1、2、3,因此必须乘以2来修正A的值。如果A=2,散转过程如下: JMPA+DPTR PC=TABLE+2 AJMPTWO三种无条件转移指令LJMP、AJMP和SJMP的比较。三种无条件转移指令在应用上的区别有以下三点:一是转移距离不同,LJMP可在64KB范围内转移,AJMP指令可以在本指令取出后的2 KB范围内转移,SJMP的转移范围是以本指令为核心的-126+129B范围内转移;二是汇编后机器码的字节数不同,LJMP是三字节指令,AJMP和SJMP都是两字节指令。三是LJMP和AJMP都是绝对转移指令,可以计算得到转移目的地址,而SJMP是相对转移指令,只能通过转移偏移量来进行计算。选择无条件转移指令的原则是根据跳转的远近,尽可能选择占用字节数少的指令。例如,动态暂停指令一般都选用SJMP$,而不用LJMP$。4.3.2分支程序结构分支程序结构分支程序比顺序程序的结构复杂得多,其主要特点是程序的流向有两个或两个以上的出口,根据指定的条件进行选择确定。编程的关键是如何确定供判断或选择的条件以及选择合理的分支指令。通常,根据分支程序中出口的个数分为单分支结构程序(两个出口)和多分支结构程序(三个或三个以上出口)。1.单分支结构程序的形式单分支结构程序的形式单分支结构在程序设计中应用最广,拥有的指令也最多。单分支结构一般为:一个入口,两个出口。如图4.12所示,单分支结构程序有以下两种典型形式: 图4.12(a)表示当条件满足时执行分支程序1,否则执行分支程序2,例4.3就是这样的一种结构。 图4.12(b)表示当条件满足时跳过程序段2,从程序段3往下执行,否则顺序执行程序段2和3。图4.12单分支结构程序的典型形式 另外,分支结构程序允许嵌套,即一个分支接一个分支,形成树形多级分支结构程序,例4.4就是这样的结构。2.散转程序散转程序 在实际应用中,常常需要从两个以上的出口中选一个,称为多分支程序或散转程序。MCS-51单片机指令系统中专门提供了散转指令,使得散转程序的编制更加简洁。 例4.5中采用转移地址表法实现散转程序,转移表是由双字节短转移指令“AJMP”组成的,各转移指令地址依次相差两个字节,所以累加器A中变址值必须作乘2修正。若转移表是由三字节长转移指令“LJMP”组成,则累加器A中变址值必须乘3。当修正值有进位时,则应将进位先加在数据指针高位字节DPH上。 此外,转移表中使用“AJMP”指令,这就限制了转移的入口地址ONE、TWO、THREE、FOUR必须和散转表首地址TABLE位于同一个2 KB范围内。为了克服上述局限性,除了可以使用“LJMP”指令组成跳转表外,还可采用双字节的寄存器存放散转值,并利用对DPTR进行加法运算的方法,直接修改DPTR,然后再用散转指令实现散转。 散转程序除了转移指令表法之外,还可以采用地址偏移量表法、转向地址表法及利用“RET”指令(子程序返回指令)实现散转程序等。3.转移条件的形成转移条件的形成分支程序中的转移条件一般都是程序状态字(PSW)中标志位的状态,因此,保证分支程序正确流向的关键如下:(1)在判断之前,应执行对有关标志位有影响的指令该标志位能够适应问题的要求,这就要求编程员要十分了Vcv解指令对标志位的影响情况。(2)当某一标志位处于某一状态时,在未执行下一条影响此标志位的指令前,它一直保持原状态不变。(3)正确理解PSW中各标志位的含义及变化情况,才能正确地判断转移。4.4循环程序设计循环程序设计4.4.1循环程序实例循环程序实例1.单重循环程序设计单重循环程序设计例4.6实训4的程序3设计。用P1口连接的8个LED模拟霓虹灯的显示方式。编程实现P1口连接的8个LED显示方式如下:按照从P1.0到P1.7的顺序,依次点亮其连接的LED。 (1) 题意分析。题意分析。这种显示方式是一种动态显示方式,逐一点亮一个灯,使人们感觉到点亮灯的位置在移动。根据点亮灯的位置,我们要向P1口依次送入如下的立即数:FEH点亮P1.0连接的LED MOV P1,#0FEHFDH点亮P1.1连接的LED MOV P1,#0FDHFBH点亮P1.2连接的LED MOV P1,#0FBH7FH点亮P1.7连接的LED MOV P1,#7FH以上完全重复地执行往P1口传送立即数的操作,会使程序结构松散。我们看到,控制LED点亮的显示模式字立即数0FEH、0FDH、0FBH7FH之间存在着每次左移一位的规律,因此我们可以试用循环程序来实现。初步设想的程序流程图如图所示。例4.6初步设想的程序流程图用汇编语言实现的程序如下:ORG0000HSTART:MOV R2,#08H;设置循环次数MOV A,#0FEH;从P1.0P1.7使LED逐;个亮过去NEXT:MOV P1,A;点亮LEDRLA;左移一位DJNZ R2,NEXT;次数减1,不为;零,继续点亮下一个LEDSJMP START;反复点亮END 执行上面程序后,结果是8个灯全部被点亮,跟预想的结果不符,为什么呢?这是因为程序执行得很快,逐一点亮LED的间隔太短,在我们看来就是同时点亮了,因此,必须在点亮一个LED后加一段延时程序,使该显示状态稍事停顿,人眼才能区别开来。(2)汇编语言源程序。汇编语言源程序。 由于程序设计中经常会出现如图所示的次数控制循环程序结构,为了编程方便,单片机指令系统中专门提供了循环指令DJNZ,以适用于上述结构的编程。 DJNZR2,NEXT;R2中存放控制次数, ;R2-1R2,R20,转移到NEXT继续循环, ;否则执行下面指令常见循环程序结构2双重循环程序设计双重循环程序设计延时程序设计延时程序设计 在上例中使用了延时程序段之后,我们才能看到正确的显示结果。延时程序在单片机汇编语言程序设计中使用非常广泛,例如,键盘接口程序设计中的软件消除抖动、动态LED显示程序设计、LCD接口程序设计、串行通信接口程序设计等。所谓延时,就是让CPU做一些与主程序功能无关的操作(例如将一个数字逐次减1直到为0)来消耗掉CPU的时间。由于我们知道CPU执行每条指令的准确时间,因此执行整个延时程序的时间也可以精确计算出来。也就是说,我们可以写出延时长度任意而且精度相当高的延时程序。例4.7 设计一个延时1 s的程序,设单片机时钟晶振频率为fosc=6 MHz。(1)题意分析。题意分析。 设计延时程序的关键是计算延时时间。延时程序一般采用循环程序结构编程,通过确定循环程序中的循环次数和循环程序段两个因素来确定延时时间。对于循环程序段来讲,必须知道每一条指令的执行时间,这里涉及到几个非常重要的概念时钟周期、机器周期和指令周期。 时钟周期T时钟是计算机基本时间单位,同单片机使用的晶振频率有关。题目给定fosc=6 MHz,那么T时钟=1/fosc=1/6 M=166.7 ns。 机器周期T机器是指CPU完成一个基本操作所需要的时间,如取指操作、读数据操作等,机器周期的计算方法:T机器=12T时钟=166.7 ns12=2 s。 指令周期是指执行一条指令所需要的时间。由于指令汇编后有单字节指令、双字节指令和三字节指令,因此指令周期没有确定值,一般为14个T机器。在附录2的指令表中给出了每条指令所需的机器周期数,可以计算每一条指令的指令周期。 现在,我们可以来计算一下实训4程序3中延时程序段的延时时间。延时程序段如下:DELAY1:MOV R3,#0FFH DEL2: MOV R4,#0FFHDEL1: NOP DJNZ R4,DEL1 DJNZ R3,DEL2 经查指令表得到:指令MOV R4,#0FFH、NOP、DJNZ的执行时间分别为2 s、2 s和4 s。NOP为空操作指令,其功能是取指、译码,然后不进行任何操作进入下一条指令,经常用于产生一个机器的延迟。延时程序段为双重循环,下面分别计算内循环和外循环的延时时间。内循环:内循环的循环次数为255(0FFH)次,循环为以下两条指令:NOP;2sDJNZR4,DEL1;4s内循环延时时间为:255(2+4)=1530s。外循环:外循环的循环次数为255(0FFH)次,循环内容如下:MOVR4,#0FFH;2s1530us内循环;1530sDJNZR3,DEL2;4s外循环一次时间为1530s+2s+4s=1536s,循环255次,另外加上第一条指令MOVR3,#0FFH;2s的循环时间2s,因此总的循环时间为2s+(1530s+2s+4s)255=391682s392ms以上是比较精确的计算方法,一般情况下,在外循环的计算中,经常忽略比较小的时间段,例如将上面的外循环计算公式简化为1530s255=390150s390ms了解了延时时间的计算方法,本例我们使用三重循环结构。程序流程图如图4.15所示。内循环选择为1ms,第二层循环达到延时10ms(循环次数为10),第三层循环延时到1s(循环次数为100)。(2)汇编语言源程序段。一般情况下,延时程序均是作为一个子程序段使用,不会独立运行它,否则单纯的延时没有实际意义。延时1 s的程序流程图 DELAY:MOV R0,#100;延时1s的循环次数DEL2: MOV R1,#10;延时10ms的循环次数DEL1: MOV R2,#7DH;延时1ms的循环次数DEL0: NOPNOPDJNZ R2,DEL0DJNZ R1,DEL1DJNZ R0,DEL2 (3) 程序说明。程序说明。本例中,第二层循环和外循环都采用了简化计算方法,编程关键是延时1ms的内循环程序如何编制。首先确定循环程序段的内容如下:NOP ;2sNOP;2sDJNZR2,DEL0;4s内循环次数设为count,计算方法如下式:(一次循环时间)count=1ms从而得到count=1ms/(2s+2s+4s)=125=7DH本例提供了一种延时程序的基本编制方法,若需要延时更长或更短时间,只要用同样的方法采用更多重或更少重的循环即可。值得注意的是,延时程序的目的是白白占用CPU一段时间,此时不能做任何其它工作,就像机器在不停地空转一样,这是程序延时的缺点。若在延时过程中需要CPU做指定的其它工作,就要采用单片机内部的硬件定时器或片外的定时芯片(如8253等)。3.数据传送程序数据传送程序例4.8不同存储区域之间的数据传输。将内部RAM30H单元开始的内容依次传送到外部RAM0100H单元开始的区域,直到遇到传送的内容是0为止。 (1) 题意分析。题意分析。本例要解决的关键问题是:数据块的传送和不同存储区域之间的数据传送。前者采用循环程序结构,以条件控制结束;后者采用间接寻址方式,以累加器A作为中间变量实现数据传输。程序流程图如图所示。例4.8程序流程图(2)汇编语言源程序。ORG0000HMOVR0,#30H; R0指 向 内部RAM数据区首地址 MOVDPTR,#0100H;DPTR指向外部RAM数据区首地址TRANS:MOVA,R0;A(R0)MOVXDPTR,A;(DPTR)ACJNEA,#00H,NEXTSJMPFINISH;A=0,传送完成NEXT:INCR0;修改地址指针INCDPTRAJMP TRANS;继续传送FINISH:SJMP $END (3) 程序说明。程序说明。间接寻址指令。在单片机指令系统中,对内部RAM读/写数据有两种方式:直接寻址方式和间接寻址方式。例如:直接方式:MOVA,30H ;内部RAM(30H)累加器A间接方式:MOVR0,#30H;30HR0MOVA,R0;内部RAM(R0)累加器A对外部RAM的读/写数据只有间接寻址方式,间接寻址寄存器有R0、R1(寻址范围是00HFFH)和DPTR(寻址范围0000HFFFFH,整个外部RAM区)。不同存储空间之间的数据传输。MCS-51系列单片机存储器结构的特点之一是存在着4种物理存储空间,即片内RAM、片外RAM、片内ROM和片外ROM。不同的物理存储空间之间的数据传送一般以累加器A作为数据传输的中心,如图所示。不同的存储空间是独立编址的,在传送指令中的区别在于不同的指令助记符,例如:MOV R0,#30HMOV A,R0;内部RAM(30H)AMOVXA,R0;外部RAM(30H)A以累加器A为中心的不同存储空间的数据传送示意图4.4.2循环程序结构循环程序结构1.循环程序组成循环程序组成从以上循环程序实例中,我们看到循环程序的特点是程序中含有可以重复执行的程序段。循环程序由以下4部分组成:(1)初始化部分。程序在进入循环处理之前必须先设立初值,例如循环次数计数器、工作寄存器以及其它变量的初始值等,为进入循环做准备。(2)循环体。循环体也称为循环处理部分,是循环程序的核心。循环体用于处理实际的数据,是重复执行部分。(3)循环控制。在重复执行循环体的过程中,不断修改和判别循环变量,直到符合循环结束条件。一般情况下,循环控制有以下几种方式:计数循环如果循环次数已知,用计数器计数来控制循环次数,这种控制方式用得比较多。循环次数要在初始化部分预置,在控制部分修改,每循环一次,计数器内容减1。例4.6、例4.7都属于计数循环控制方式。条件控制循环在循环次数未知的情况下,一般通过设立结束条件来控制循环的结束,例4.8就是用条件A=0来控制循环结束的。开关量与逻辑尺控制循环这种方法经常用在过程控制程序设计中,这里不再详述。(4)循环结束处理。这部分程序用于存放执行循环程序所得结果以及恢复各工作单元的初值等。2.循环程序的基本结构循环程序的基本结构循环程序通常有两种编制方法:一种是先处理再判断,另一种是先判断后处理,如图所示。循环程序的两种基本结构(a)先执行后判断;(b)先判断后执行3.多重循环结构程序多重循环结构程序有些复杂问题,必须采用多重循环的程序结构,即循环程序中包含循环程序或一个大循环中包含多个小循环程序,称为多重循环程序结构,又称循环嵌套。多重循环程序必须注意的是各重循环不能交叉,不能从外循环跳入内循环。例4.7的延时程序就是一个典型的三重循环结构。4.循环程序与分支程序的比较循环程序与分支程序的比较循环程序本质上是分支程序的一种特殊形式,凡是分支程序可以使用的转移指令,循环程序一般都可以使用,并且由于循环程序在程序设计中的重要性,单片机指令系统还专门提供了循环控制指令,如DJNZ等。4.5 4.5 查查 表表 程程 序序在单片机汇编语言程序设计中,查表程序的应用非常广泛,在LED显示程序和键盘接口程序设计中都用到了查表程序段。例4.9在程序中定义一个09的平方表,利用查表指令找出累加器A=05H的平方值。 (1) 题意分析。题意分析。所谓表格是指在程序中定义的一串有序的常数,如平方表、字型码表、键码表等。因为程序一般都是固化在程序存储器(通常是只读存储器ROM类型)中,因此可以说表格是预先定义在程序的数据区中,然后和程序一起固化在ROM中的一串常数。查表程序的关键是表格的定义和如何实现查表。 (2) 汇编语言源程序。汇编语言源程序。ORG0000HMOVDPTR,#TABLE;表首地址DPTR(数据指针)MOVA,#05 ;05AMOVC A,A+DPTR ;查表指令,25A,A=19HSJMP$;程序暂停TABLE: DB0,1,4,9,16,25,36,49,64,81;定义09平方表END (3) 程序说明。程序说明。从程序存储器中读数据时,只能先读到累加器A中,然后再送到题目要求的地方。单片机提供了两条专门用于查表操作的查表指令:MOVCA,A+DPTR ;(A+DPTR)AMOVCA,A+PC;PC+1PC,(A+PC)ADPTR为数据指针,一般用于存放表首地址。用指令MOVCA,A+PC实现查找平方表的源程序如下:ORG0000HMOVA,#05;05AADDA,#02;修正累加器A的值,修正值为查表指令距离表格首地址;的字节数减去1MOVCA,A+PC;25ASJMP $TABLE:DB0,1,4,9,16,25,36,49,64,81;定义09平方表END在解决实际问题时,经常会遇到一个程序中多次使用同一个程序段,例如延时程序、查表程序、算术运算程序段等功能相对独立的程序段。在实训4中,我们反复使用了延时程序段。为了节约内存,我们把这种具有一定功能的独立程序段编成子程序,例如延时子程序。当需要时,可以去调用这些独立的子程序。调用程序称为主程序,被调用的程序称为子程序。本节用实例介绍子程序和堆栈的使用方法。4.6子程序设计与堆栈技术子程序设计与堆栈技术4.6.1子程序实例子程序实例例4.10延时子程序:编程使P1口连接的8个LED按下面方式显示:从P1.0连接的LED开始,每个LED闪烁10次,再移向下一个LED,同样闪烁10次,循环不止。 (1) 题意分析。题意分析。在前面的例子中,我们已经编了一些LED模拟霓虹灯的程序,按照题目要求画出本例的程序流程图如图4.19所示。在图4.19中,两次使用延时程序段,因此我们把延时程序编成子程序。例4.10程序流程图(2) 汇编语言源程序。汇编语言源程序。ORG0000HMAIN:MOV A,#0FE;送显示初值LP:MOV R0,#10;送闪烁次数LP0:MOV P1,A;点亮LEDLCALLDELAY;延时MOV P1,#0FFH;熄灭灯LCALLDELAY;延时DJNZR0,LP0;闪烁次数不够10次,继续RLA;否则A左移,下一个灯闪烁SJMPLP;循环不止DELAY:MOVR3,#0FFH;延时子程序DEL2:MOVR4,#0FFHDEL1:NOPDJNZR4,DEL1DJNZR3,DEL2RET (3) 程序说明。程序说明。子程序调用和返回过程。在上例中,MAIN为主程序,DELAY为延时子程序。当主程序MAIN需要延 时 功 能 时 , 就 用 一 条 调 用 指 令 ACALL( 或LCALL)DELAY即可。子程序DELAY的编制方法与一般程序遵循的规则相同,同时也有它的特殊性。子程序的第一条语句必须有一个标号,如DELAY,代表该子程序第一个语句的地址,也称为子程序入口地址,供主程序调用;子程序的最后一条语句必须是子程序返回指令RET。子程序一般紧接着主程序存放,例4.10的主程序和子程序在存储器中的存储格式如下:主程序:地址机器码指令000512*LCALLDELAY;第一次调用子程序0008*MOVP1,#0FFH;LCALL指令的下一条指令首址0008H称为断点地址子程序:0013*MOV R3,#0FFH;子程序开始001C22RET;子程序返回主程序两次调用子程序及子程序返回过程如图所示。子程序两次调用、返回过程示意图子程序只需书写一次,主程序可以反复调用它。CPU执行LCALL指令所进行的具体操作(以第一次调用为例)是:(a)PC的自动加1功能使PC=0008H,指向下一条指令MOVP1,#0FFH的首址,PC中即为断点地址;(b)保存PC中的断点地址0008H;(c) 将子程序DELAY的入口地址0013H赋给PC,PC=0012H;(d)程序转向DELAY子程序运行。CPU执行RET指令的具体操作(以第一次调用为例)是:(a)取出执行调用指令时保存的断点地址0008H,并将它赋给PC,PC=0008H;(b)程序转向断点处继续执行主程序。从以上分析来看,在子程序调用过程中,断点地址0008H是自动保存和取出的,那么断点地址究竟存放在什么地方呢?这里引出一个新的存储区域概念堆栈,它是一个存放临时数据(例如断点地址)的内存区域。堆栈的巧妙设计使程序员不必操心数据的具体存放地址。子程序嵌套。修改上面的程序,将一个灯的闪烁过程也编成子程序形式。修改后的源程序如下:ORG0000HMAIN:MOV A,#0FEH;送显示初值COUN:ACALLFLASH;调闪烁子程序RLA;A左移,下一个灯闪烁SJMP COUN;循环不止FLASH:MOV R0,#10;送闪烁次数FLASH1:MOV P1,A;点亮LEDLCALLDELAY;延时MOV P1,#0FFH;熄灭灯LCALLDELAY;延时DJNZ R0,FLASH1;闪烁次数不够10次,继续RETDELAY:MOV R3,#0FFH;延时子程序DEL2:MOV R4,#0FFHDEL1:NOPDJNZ R4,DEL1DJNZ R3,DEL2RETEND上面程序中,主程序调用了闪烁子程序FLASH,闪烁子程序中又调用延时子程序DELAY,这种主程序调用子程序,子程序又调用另外的子程序的程序结构,称为子程序的嵌套。一般来说,子程序嵌套层数理论上是无限的,但实际上,受堆栈深度的影响,嵌套层数是有限的。与子程序的多次调用不同,嵌套子程序的调用过程如图所示。例4.10程序中嵌套子程序的执行过程 例4.11查表子程序。假设a、b均小于10,计算c=a2+b2,其中a事先存在内部RAM的31H单元,b事先存在32H单元,请把c存入33H单元。(1)题意分析。本例两次使用平方的计算,在前面的例4.9中已经编过查平方表得到平方值的程序,在此我们采用把求平方编为子程序的方法。(2)汇编语言源程序。ORG0000H;主程序MOVSP,#3FH;设置栈底MOVA,31H;取数a存放到累加器A中作为入口参数LCALL SQRMOVR1,A;出口参数平方值存放在A中MOVA,32H ADDA,R1 MOV33H,A SJMP$ ;子程序:SQR ;功能:通过查表求出平方值y=x2 ;入口参数:x存放在累加器A中 ;出口参数:求得的平方值y存放在A中 ;占用资源:累加器A,数据指针DPTRSQR:PUSHDPH;保护现场,将主程序中DPTR的高8位放入堆栈PUSHDPL;保护现场,将主程序中DPTR的低8位放入堆栈MOVDPTR,#TABLE;在子程序中重新使用DPTR,表首地址DPTRMOVCA,A+DPTR;查表POPDPL;恢复现场,将主程序中DPTR的低8位从堆栈中弹出POPDPH;恢复现场,将主程序中DPTR的高8位从堆栈中弹出RETTABLE:DB0,1,4,9,16,25,36,49,64,81 (3)执行程序。在运行程序之前,利用单片机开发系统先在内部RAM的31H、32H存放两个小于10的数,执行完之后,结果放在33H单元。(4)程序说明。参数传递。主程序调用查表子程序时,子程序需要从主程序中得到一个参数已知数x,这个参数称为子程序的入口参数。查表子程序执行完以后,必须将结果传送给主程序,这个子程序向主程序传递的参数称为子程序的出口参数。本例中入口和出口参数都是通过累加器A来传送的。现场保护和现场恢复。子程序在编制过程中经常会用到一些通用单元,如工作寄存器、累加器、数据指针DPTR以及PSW等。而这些工作单元在调用它的主程序中也会用到,为此,需要将子程序用到的这些通用编程资源加以保护,称为保护现场。在子程序执行完后需恢复这些单元的内容,称为恢复现场。本例中,保护和恢复现场是在子程序中利用堆栈操作实现的,在子程序的开始部分把子程序中要用到的编程资源都保护起来,在执行返回指令之前恢复现场,这是一种比较规范的方法。另外,也可以在主程序中实现保护和恢复现场。在调用子程序前保护现场,子程序返回后恢复现场,这种方式比较灵活,可以根据当时的需要确定要保护的内容。子程序的说明。在查表子程序前,以程序注释的形式对子程序进行了说明,说明内容如下:(a)子程序名:提供给主程序调用的名字。(b)子程序功能:简要说明子程序能完成的主要功能。(c)入口参数:主程序需要向子程序提供的参数。(d)出口参数:子程序执行完之后向主程序返回的参数。(e)占用资源:该子程序中使用了哪些存储单元、寄存器等。这些说明是写给程序员看的,供以后使用子程序时参考。4.6.2堆栈结构堆栈结构1.堆栈概念堆栈概念堆栈实际上是内部RAM的一部分,堆栈的具体位置由堆栈指针SP确定。SP是一个8位寄存器,用于存放堆栈的栈底(初始化)地址和栈顶地址。单片机复位或上电时,SP的初值是07H,表示堆栈栈底为07H,存入数据后,地址增1,SP中的地址值随着加1。SP的值总是指向最后放进堆栈的一个数,此时,SP中的地址称为栈顶地址。堆栈结构示意图如图所示。堆栈结构示意图2.堆栈操作堆栈操作堆栈有两种最基本操作:向堆栈存入数据称为“入栈”或“压入堆栈”(PUSH);从堆栈取出数据称为“出栈”或“弹出堆栈”(POP)。堆栈中数据的存取采用后进先出方式,即后入栈的数据,弹出时先弹出,类似货栈堆放货物的存取方式,“堆栈”一词因此而得名。由于单片机初始化的堆栈区域同第1组工作寄存器区重合,也就是说,当把堆栈栈底设在07H处时,就不能使用第1组工作寄存器,如果堆栈存入数据量比较大的话,甚至第2组和第3组工作寄存器也不能使用了。因此,在汇编语言程序设计中,通常总是把堆栈区的位置设在用户RAM区。例如MOVSP,#60H;将堆栈栈底设在内部RAM的60H处3.堆栈的功能堆栈的功能最初,堆栈是为了子程序调用和返回而设计的,执行调用指令(LCALL、ACALL)时,CPU自动把断点地址压栈;执行返回指令RET时,自动从堆栈中弹出断点地址。由于堆栈操作简单,程序员也经常用堆栈暂存中间结果或数据。只是使用时需要注意堆栈先进后出的特点。例如,例4.11中的子程序SQR中,恢复现场的顺序就不能弄反,先保护的DPH后恢复出来。另外,在子程序调用时,CPU会自动利用堆栈进行保护现场和恢复现场。4.堆栈操作与堆栈操作与RAM操作的比较操作的比较堆栈作为内部RAM的一个特殊区域,又有其独特性,为汇编语言程序设计提供了更多的方便。同内部RAM的操作相比较,使用堆栈有以下优点:(1)使用内部RAM必须知道单元具体地址,而堆栈只需设置好栈底地址,就可放心使用,无需再记住单元具体地址。(2)当我们需要重新分配内存工作单元时,程序中使用内部RAM的地方,都要修改单元地址,而堆栈只需修改栈底地址就行了。(3)堆栈所特有的先进后出特点,使得数据弹出之后,存储单元自动回收、再次使用,充分提高了内存的利用率;而内部RAM的操作是不可能实现自动回收再利用的,必须通过编程员的重新分配,才能再次使用。4.6.3子程序结构子程序结构1.子程序的编程原则子程序的编程原则在实际的单片机应用系统软件设计中,为了程序结构更加清晰,易于设计,易于修改,增强程序可读性,基本上都要使用子程序结构。子程序作为一个具有独立功能的程序段,编程时需遵循以下原则:(1)子程序的第一条指令必须有标号,明确子程序入口地址。(2)以返回指令RET结束子程序。(3)简明扼要的子程序说明部分。(4)较强的通用性和可浮动性,尽可能避免使用具体的内存单元和绝对转移地址等。(5)注意保护现场和恢复现场。2.参数传递的方法参数传递的方法主程序调用子程序时,主程序和子程序之间存在着参数互相传递的问题。参数传递一般有以下三种方法。 1) 寄存器传递参数寄存器传递参数如例4.11那样,通过寄存器A传递入口参数和出口参数。 2) 利用堆栈传递参数利用堆栈传递参数修改例4.11,利用堆栈来传递参数,源程序如下:ORG 0000H;主程序MOV SP,#3FH;设置栈底PUSH31H ;将数a存放到堆栈中,作为入口参数LCALLSQRPOPACCMOV R1,A;出口参数平方值存放在A中PUSHACCLCALLSQRPOPACCADDA,R1MOV33H,ASJMP$;子程序:SQR;功能:通过查表求出平方值y=x2;入口参数:x存放在堆栈中;出口参数:求得的平方值y存放在堆栈中;占用资源:累加器A,数据指针DPTRSQR:MOVR0,SP;R0作为参数指针DECR0;堆栈指针退回子程序调用前的地址DECR0XCHA,R0;保护ACC,取出参数MOVDPTR,#TABLE;表首地址DPTRMOVCA,A+DPTR;查表XCHA,R0;查表结果放回堆栈中RETTABLE: DB0,1,4,9,16,25,36,49,64,81 3) 利用地址传递参数利用地址传递参数将要传递的参数存放在数据存储器中,将其地址通过间接寻址寄存器传递,供子程序读取参数。3.子程序调用中应注意的问题子程序调用中应注意的问题由于子程序调用过程中,CPU自动使用了堆栈,因此,容易出现以下几种错误:(1)忘记给堆栈指针SP赋栈底初值,堆栈初始化位置与第1组工作寄存器重合,如果以不同的方式使用了同一个内存区域,会导致程序乱套。(2)程序中的PUSH和POP没有配对使用,使RET指令执行时不能弹出正确的断点地址,造成返回错误。(3)堆栈设置太小,栈操作增长太大,使栈区与其它内存单元重合。4.7实用汇编子程序举例实用汇编子程序举例4.7.1代码转换程序代码转换程序在计算机内部,任何数据最终都是以二进制形式出现的。但是人们通过外部设备与计算机交换数据采用的常常又是一些别的形式。例如标准的编码键盘和标准的CRT显示器使用的都是ASCII码;人们习惯使用的是十进制,在计算机中表示为BCD码等。因此,汇编语言程序设计中经常会碰到代码转换的问题,这里介绍BCD码、ASCII码与二进制数相互转换的基本方法和子程序代码。例4.12BCD码转换为二进制数:把累加器A中的BCD码转换成二进制数,结果仍存放在累加器A中。 (1) 题意分析。题意分析。A中存放的BCD码数的范围是099,转换成二进制数后是00H63H,所以仍然可以存放在累加器A中。本例采用将A中的高半个字节(十位)乘以10,再加上A的低半个字节(个位)的方法进行转换,计算公式是A74*10+A30。 (2) 汇编语言源程序。汇编语言源程序。;程序名:BCDBIN;功能:BCD码转换为二进制数;入口参数:要转换的BCD码存在累加器A中;出口参数:转换后的二进制数存放在累加 ;器A中占用资源:寄存器BBCDBIN:PUSHB;保护现场PUSHPSWPUSHACC;暂存A的内容ANL A,#0F0H;屏蔽掉低4位SWAPA;将A的高4位与低4位交换MOVB,#10MULAB;乘法指令,ABBA,A中高半字节乘以10MOVB,A;乘积不会超过256,因此乘积在A中,暂存到BPOPACC;取原BCD数ANLA,#0FH;屏蔽掉高4位ADDA,B;个位数与十位数相加POPPSWPOPB;恢复现场RET例4.13二进制数转换为BCD码,将累加器A中的二进制数0FFH内的任一数转换为BCD码(0255)。 (1) 题意分析。题意分析。BCD码是每4位二进制数表示一位十进制数。本例所要求转换的最大BCD码为255,需要12位二进制数,超过了一个字节(8位),因此我们把高4位存放在B的低4位,高4位清零;低8位存放在A中。0000001001010101BA转换的方法是将A中二进制数除以100、10,所得商即为百、十位数,余数为个位数。 (2) 汇编语言源程序。汇编语言源程序。;程序名:BINBCD;功能:二进制数转换为BCD码;入口参数:要转换的二进制数存在累加器A中;(0FFH)出口参数:转换后的BCD码存放在B(百位)和A(十位和个位)中BINBCD: PUSH PSWMOVB,#100DIVAB;除法指令,A/B商在A中,余数在B中PUSHACC;把商(百位数)暂存在堆栈中MOVA,#10XCHA,B;余数交换到A中,B=10DIVAB;A/B商(十位)在A中,余数(个位)在B中SWAPA;十位数移到高半字节ADDA,B;十位数和个位数组合在一起POPB;百位数存放到B中POPPSWRET例4.14ASCII码转换为二进制数,将累加器A中的十六进制数的ASCII码(09,AF)转换成4位二进制数。 (1) 题意分析。题意分析。在单片机汇编程序设计中,主要涉及到十六进制的16个符号“0F”的ASCII码同其数值的转换。ASCII码是按一定规律表示的,数字09的ASCII码即为该数值加上30H,而对于字母“AF”的ASCII码即为该数值加上37H。0F对应的ASCII码如下:0F0123456789ABCDEFASCII码30313233343536373839414243444546(十六进制) (2) 汇编语言源程序。汇编语言源程序。;程序名:ASCBCD;功能:ASCII码转换为二进制数;入口参数:要转换的ASCII码(30H39H,41H46H)存在A中;出口参数:转换后的4位二进制数(0F)存放在A中ASCBCD:PUSHPSW;保护现场PUSHBCLRC;清CYSUBBA,#30H;ASCII码减30HMOVB,A;结果暂存B中SUBBA,#0AH;结果减10JCSB10;如果CY=1,表示该值9XCHA,B;否则该值9,必须再减7SUBBA,#07HSJMPFINISHSB10:MOVA,BFINISH:POPB;恢复现场POPPSWRET例4.15二进制数转换为ASCII码,将累加器A中的一位16进制数(A中低4位,x0HxFH)转换成ASCII码,还存放在累加器A中。汇编语言源程序如下:;程序名:BINASC;功能:二进制数转换为ASCII码;;入口参数:要转换的二进制数存在A中;出口参数:转换后的ASCII码存放在A中BINASC:PUSHPSW;保护现场ANLA,#0FH;屏蔽掉高4位PUSHACC;将A暂存堆栈中CLRC;清CYSUBBA,#0AH;A-10JCLOOP;判断有否借位POP ACC;如果没有借位,表示A10ADD A,#37HSJMPFINISHLOOP:POP ACC;否则A10ADDA,#30HFINISH:POPPSWRET4.7.2算术运算子程序算术运算子程序单片机指令系统中只提供了单字节二进制数的加、减、乘、除指令,对于多字节数和BCD码的四则运算则必须由用户自己编程实现。例4.1给出了多字节无符号数加法程序,这里再提供一些简单的算术运算的子程序。例4.16单字节十进制数(BCD码)减法程序。已知工作寄存器R6、R7中有两个BCD数,R6中的数作为被减数,R7中的数作为减数,计算两数之差,并将差值存入累加器A中。 (1) 题意分析。题意分析。单片机指令系统中没有十进制减法调整指令,因此我们采用BCD补码运算法则,把减法转换为加法:被减数-减数被减数+减数的补数然后对加法结果进行十进制加法调整。操作步骤如下:求BCD减数的补数公式:9AH-减数。这里的9AH代表两位BCD码的模100。被减数加上减数的补数。对第步的加法之和进行十进制加法调整,调整结果即为所求的减法结果。 (2) 汇编语言源程序。汇编语言源程序。;程序名:BCDSUB;功能:单字节十进制数(BCD码)减法;入口参数:被减数存放在R6中,减数存放在R7中;出口参数:差数存放在累加器A中BCDSUB:CLRCMOVA,#9AH;两位BCD模ASUBBA,R7;求减数的补数AADDA,R6;被减数+减数的补数ADAA;十进制加法调整CLRC;想一想为什么?RET例4.17双字节无符号数乘法。设被乘数存放在R2、R3寄存器中,乘数存放在R6、R7中,乘积存放在以R0内容为首地址的连续4个单元内。 (1) 题意分析。题意分析。我们先用一个具体例子来分析乘法的具体过程:设被乘数=6,乘数=5,相乘公式如下:110(b2b1b0)101(c2c1c0)110000+11011110把乘数(c=c2c1c0=101)的每一位分别与被乘数(b=b2b1b0=110)相乘,操作过程如下:相乘的中间结果称为部分积,假设为x,y(x保存低位,y保存高位),预设CY=0,x=0,y=0。c0=1,x=x+b=b2b1b0。x、y右移一位,CYxyCY,把x的低位移入y中,则x=0b2b1,y=b0。c1=0,x=x+000。(5)CYxyCY,x=00b2,y=b1b0。c2=1,x=x+b=00b2+b2b1b0=111,CY=0,y=b1b0=10。右移一次:CYxyCY。乘数的每一位都计算完毕,x和y中的值合起来即为所求乘积。由以上分析可见,对于三位二进制乘法,部分积x、y也是三位的,这种方法称为部分积右移计算方法。部分积右移算法归纳如下:将存放部分积的寄存器清0,设置计数位数,用来表示乘数位数。从最低位开始,检验乘数的每一位是0还是1,若该位是1,部分积加上被乘数;若该位为0,就跳过去不加。部分积右移1位。判断计数器是否为0,若计数器不为0,重复步骤,否则乘法完成。部分积右移算法的程序流程图如图4.23所示。图4.23 部分积右移算法流程图(2)汇编语言源程序。;程序名:DMUL;功能:双字节无符号数乘法;入口参数:被乘数存放在R2、R3(R2高位,R3低位)寄存器中乘数存放在R6、R7(R6;高位,R7低位)中;出口参数:乘积存放在R4、R5、R6、R7寄存器中(R4为高位,R7为低位)DMUL: PUSH ACC;保护现场PUSH PSWMOVR4,#0 ;部分积清0MOVR5,#0MOV R0,#16;计数器清0CLRC;CY=0NEXT:ACALLRSHIFT;部分积右移一位,CYR4R5R6R7CYJNCNEXT1;判断乘数中相应的位是否为0,若是,转移到NEXT1MOVA,R5;否则,部分积加上被乘数(双字节加法)ADDA,R3MOVR5,AMOVA,R4ADDC A,R2MOVR4,ANEXT1:DJNZR0,NEXT;移位次数是否为0,若不为0转移到NEXTACALL RSHIFT;部分积右移一位POPPSW;恢复现场POPACCRET;程序名:RSHIFT;功能:部分积右移一位;入口参数:部分积R4、R5、R6、R7;出口参数:CYR4R5R6R7CYRSHIFT:MOVA,R4RRCA;CYR4CYMOVR4,AMOVA,R5RRCA;CYR5CYMOVR5,AMOVA,R6RRCA;CYR6CYMOVR6,AMOVA,R7RRCA;CYR7CYMOVR7,ARET例4.1816位8位无符号数除法。被除数存放在R6、R5(R6高8位,R5低8位)中,除数存放在R2中,商存放在R5中,余数存放在R6中。 (1) 题意分析。题意分析。与实现双字节乘法的部分积右移算法类似,双字节除法采用部分余数左移的算法。该算法仿照手算的方法编制,基本思想如下:商数除数被除数-除数;试做减法,够减商上1余数 -除数;再试做减法,不够减,商上0,并恢复减法前的余数余数-除数;再做减法手工算法中,习惯将余数右移对齐。在计算机中,保留了手工算法的特点,但采用部分余数左移的方法。部分余数左移算法流程图如图所示。部分余数左移算法流程图 (2) 汇编语言源程序。汇编语言源程序。;程序名:DDIV;功能:16位8位无符号数除法;入口参数:被除数存放在R6、R5(R6高8位,R5低8位)中,除数存放在R2中;出口参数:商存放在R5中,余数存放在R6中;占用资源:位地址单元07H作为标志位暂存单元DDIV:PUSH PSWMOVR7,#08H;R7为计数器初值寄存器,R7=08HDDIV1:CLRC;清CYMOVA,R5;部分余数左移一位(第一次为被除数移位)RLCA;CYR5R60MOVR5,AMOVA,R6RLCAMOV07H,C;位地址单元07H用作标志位单元,存放中间结果CLRCSUBB A,R2;高位余数除数JB07H,NEXT;若标志位为1,则够减JNCNEXT;没有借位,也说明够减ADDA,R2;否则,不够减,恢复余数00SJMPNEXT1NEXT:INCR5;够减,商上1NEXT1:MOV R6,A;保存余数DJNZ R7,DDIV1POPPSWRET4.7.3查找、排序程序查找、排序程序例4.19片内RAM中数据检索程序设计。片内RAM中有一数据块,R0指向块首地址,R1中为数据块长度,请在该数据块中查找关键字,关键字存放在累加器A中。若找到关键字,把关键字在数据块中的序号存放到A中;若找不到关键字,A中存放序号00H。 (1) 程序流程图。程序流程图。本例程序流程图如图4.25所示。 (2) 汇编语言源程序。汇编语言源程序。数据检索程序流程图;程序名:FIND;功能:片内RAM中数据检索;入口参数:R0指向块首地址,R1中为数据块长度,关键字存放在累加器A中;出口参数:若找到关键字,把关键字在数据块中的序号存放到A中,若找不到关;键字,A中存放序号00H;占用资源:R0,R1,R2,A,PSWFIND:PUSH PSWPUSH ACCMOV R2,#00HLOOP: POPACCMOV B,AXRLA,R0;关键字与数据块中的数据进行异或操作INCR0;指向下一个数INCR2;R2中的序号加1JZLOOP1;找到PUSHBDJNZR1,LOOPMOVR2,#00H;找不到,R2中存放00HLOOP1:MOV A,R2POPPSWRET例4.20查找无符号数据块中的最大值。内部RAM有一无符号数据块,工作寄存器R1指向数据块的首地址,其长度存放在工作寄存器R2中,求出数据块中最大值,并存入累加器A中。 (1) 题意分析。题意分析。本题采用比较交换法求最大值。比较交换法先使累加器A清零,然后把它和数据块中每个数逐一进行比较,只要累加器中的数比数据块中的某个数大就进行下一个数的比较,否则把数据块中的大数传送到A中,再进行下一个数的比较,直到A与数据块中的每个数都比较完,此时A中便可得到最大值。程序流程图如图4.26所示。查找无符号数据块中的最大值流程图 (2) 汇编语言源程序。汇编语言源程序。;程序名:MAX;功能:查找内部RAM中无符号数据块的最大值;入口参数:R1指向数据块的首地址,数据块长度存放在工作寄存器R2中;出口参数:最大值存放在累加器A中;占用资源:R1,R2,A,PSWMAX:PUSHPSWCLRA;清A作为初始最大值LP:CLRC;清进位位SUBBA,R1;最大值减去数据块中的数JNCNEXT;小于最大值,继续MOVA,R1;大于最大值,则用此值作为最大值SJMPNEXT1NEXT:ADD A,R1;恢复原最大值NEXT1:INCR1;修改地址指针DJNZ R2,LPPOP PSW例4.21片内RAM中数据块排序程序。内部RAM有一无符号数据块,工作寄存器R0指向数据块的首地址,其长度存放在工作寄存器R2中,请将它们按照从大到小顺序排列。 (1) 题意分析。题意分析。排序程序一般采用冒泡排序法,又称两两比较法。程序流程图如图4.27所示。冒泡排序法程序流程图;程序名:BUBBLE;功能:片内RAM中数据块排序程序;入口参数:R0指向数据块的首地址,数据块长度存放在工作寄存器R2中;出口参数:排序后数据仍存放在原来位置;占用资源:R0,R1,R2,R3,R5,A,PSW;位单元00H作为交换标志存放单元BUBBLE: MOV A,R0MOV R1,A;把R0暂存到R1中MOV A,R2MOV R5,A;把R2暂存到R5中BUBB1:CLR00H;交换标志单元清0DECR5;个数减1MOV A,R1BUB2:DECR1MOVR1,AINCR1MOVA,R1DJNZR5,BUB1 ;没有比较完,转向BUB1INCR0MOVR1,R0DECR2MOVR5,R2JB 00H,BUBB1;交换标志为1,继续下一轮两两比较RETEND
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号