第3章
CHAPTER 3


MCS51指令系统及汇编
程序设计





本章讨论MCS51单片机的指令系统及汇编语言程序设计。内容主要有寻址方式、分类指令、伪指令和汇编语言程序设计基础。
通过学习指令系统和汇编语言,能够更深刻理解计算机的工作原理。本章是单片机程序设计的基础,虽然现在多以C语言编程为主,但对某些要求较高的部分,还是需要用汇编语言来写; 另外在使用Keil C调试、分析程序时,经常需要阅读反汇编窗口程序。
3.1汇编语言概述
3.1.1指令和机器语言
指令是计算机中CPU根据人的意图来执行某种操作的命令。一台计算机所能执行的全部指令的集合,称为这个CPU的指令系统。指令系统的强弱,决定了计算机智能的高低。MCS51单片机指令系统功能很强,有乘、除法指令、丰富的条件跳转指令、位操作指令等,并且使用方便、灵活。
要使计算机按照人们的要求完成一项工作,就必须让CPU按顺序执行预设的操作,即逐条执行人们编写的指令。这种按照人们要求所编排的指令操作的序列,称为程序。编写程序的过程叫程序设计。
程序设计语言就是编写程序的一整套规则和方法,是实现人机交换信息的基本工具,分为机器语言、汇编语言和高级语言。
机器语言用二进制编码表示每条指令,是计算机能够直接识别和执行的语言。用机器语言编写的程序,称为机器语言程序或机器码程序。因为机器只能够识别和执行这种机器码程序,所有语言程序最终都需要翻译成机器码程序,所以机器码程序又称为目标程序。
MCS51单片机是8位机,其机器语言以8位二进制码为单位(字节),有单字节、双字节和3字节指令。
例如,要做“13+25”的加法,在MCS51单片机中机器码程序为: 

0 1 1 1 0 1 0 00 0 0 0 1 1 0 1(把13放到累加器A中)

0 0 1 0 0 1 0 00 0 0 1 1 0 01(A加25,结果仍放回A中)


为了便于书写和记忆,可采用十六进制表示指令码。上面这两条指令可写为: 

74H0DH

24H19H

显然,用机器语言编写程序不易理解、不易查错、不易修改、不易记忆。
3.1.2汇编语言
直接用机器语言编写程序非常困难,为了克服机器语言编程中的问题,人们发明了用符号代替机器码的编程方法。这种符号就是助记符,一般采用相关的英文单词或其缩写来表示。这就是汇编语言。
汇编语言是用助记符、符号、数字等来表示指令的程序语言,相对于机器语言来说,汇编语言容易理解和记忆。它与机器语言是一一对应的。汇编语言不像高级语言(如C语言)那样具有通用性,而是属于某种CPU所独有的,与CPU内部硬件结构密切相关。用汇编语言编写的程序叫汇编语言程序。
例如,上面的“13+25”的例子可写成: 

汇编语言程序机器语言代码

MOVA,#0DH74H0DH

ADDA,#19H24H19H

汇编语言和机器语言都属于低级语言。尽管汇编语言相对机器语言有不少优点,但它仍然存在着机器语言的某些缺点,如与CPU的硬件结构紧密相关,不同的CPU其汇编语言不同。这使得汇编语言不能够移植,使用不便; 其次,要用汇编语言进行程序设计,必须了解所使用的CPU的硬件结构与性能,对程序设计人员有较高的要求。所以又出现了对MCS51单片机编程的高级语言,如PL/M、BASIC、C语言等,现在PL/M、BASIC等语言已经被淘汰,主要使用C语言。
3.1.3汇编语言格式
MCS51汇编语言指令由四部分组成,其一般格式如下: 

[标号: ]操作码[操作数][; 注释]

格式中的方括号表示可以没有相应部分,可见,可以没有标号、操作数和注释,但至少要有操作码。其操作数部分最多可以是三项: 

[操作数1][,操作数2][,操作数3]

操作数1常称为目的操作数,操作数2称为源操作数,操作数3多为跳转的目标。例如: 

START: MOVA,#23H;23H→A

START为标号,MOV为操作码,A、#23H为操作数,23H→A为注释。
标号是相应指令的标记,便于查找,用于程序入口、循环等。
操作码规定了指令所要执行的操作,由2~5个英文字母表示。例如,MOV、ADD、RRC、JZ、DJNZ、CJNE、LCALL等。
操作数指出了参与操作的数据来源、操作结果存放的地方,以及跳转的目标位置。操作数可以是一个数(立即数),也可以是数据所在的空间地址,即在执行指令时从指定的空间地址读取或写入数据。
注释主要使程序容易阅读。
操作码和操作数都有对应的二进制代码,指令代码由若干字节组成。对于不同的指令,指令的字节数不同。在MCS51指令系统中,有单字节指令、双字节指令和3字节指令。下面分别加以说明。
1. 单字节指令
单字节指令中的8位二进制代码,既包含操作码的信息,也包含操作数的信息。这种指令有两种情况。
(1) 指令码中隐含着对某一个寄存器的操作。
例如,INCA、MULAB、RLA、CLRC、INCDPTR等指令,都属于这一类,只需要一个字节就可以表示出执行什么操作、操作数是哪个。如数据指针DPTR增1指令INCDPTR,其8位二进制指令代码为A3H,格式为: 
10100011
(2) 由指令码中的r r r或i指定操作数。
例如,ADDA,Rn、INCRn、ANLA,@Ri、 MOV@Ri,A等指令,都属于这一类。如累加器A向工作寄存器传送数据指令MOVRn,A,其指令格式为: 
11111rrr
其中高5位为操作码内容,指出作传送数据操作,低3位的rrr的不同组合编码,用来表示向哪一个寄存器(R0~R7)传送数据,故一字节就够了。
MCS51单片机共有49条单字节指令。
2. 双字节指令
用一个字节表示操作码,另一个字节表示操作数或操作数所在的地址。其指令格式为: 
操作码
立即数或地址
MCS51单片机共有45条双字节指令。
3. 3字节指令
用一个字节表示操作码,另外两个字节表示操作数或操作数所在的地址。其指令格式为: 
操作码
立即数或地址
立即数或地址
MCS51单片机共有17条3字节指令。




3.2MCS51单片机寻址方式
所谓寻址方式,是指CPU寻找参与运算的操作数的方式,或者寻找数据保存位置的方式。寻址方式是汇编语言程序设计中最基本的内容之一,必须要十分熟悉。
MCS51单片机有7种寻址方式: 立即数寻址、寄存器寻址、直接寻址、寄存器间接寻址、变址寻址、位寻址和指令寻址。可以分为两类: 操作数寻址和指令寻址,在7种寻址方式中,除了指令寻址之外,其余6种都属于操作数寻址。
3.2.1立即数寻址
立即数寻址也叫立即寻址、常数寻址。其操作数就在指令中,是指令的一部分,紧跟在操作码后面,用“#”符号作前缀,以区别地址。访问的是code区域。例如: 

MOVA,#2CH; 2CH→A

MOVA,2CH; (2CH)→A

前者表示把2CH这个数送给累加器A,后者表示把片内RAM中地址为2CH单元的内容送给累加器A。
立即数也可以是16位的,如: 

MOVDPTR,#1234H

MOVTL2,#2345H

MOVRCAP2L,#3456H

对于第2条指令,立即数的低8位送给了TL2,高8位送给了TH2; 对于第3条指令,立即数的低8位送给了RCAP2L,高8位送给了RCAP2H。
3.2.2寄存器寻址
寄存器寻址就是由指令指出寄存器组R0~R7中某一个或寄存器A、B、DPTR和C(位处理器的累加器)的内容作为操作数。例如: 

MOVA,R7; (R7)→A

MOV36H,A; (A)→36H

ADDA,R0; (A)+(R0)→A

指令中给出的操作数是一个寄存器名,在此寄存器中存放着真正被操作的对象。工作寄存器的识别由操作码的低3位完成。其对应关系如表31所示。


表31低3位操作码与寄存器Rn的对应关系



低3位r r r000001010011100101110111

寄存器RnR0R1R2R3R4R5R6R7

例如,INCRn的机器码格式为00010rrr。若rrr=010B,则Rn=R2,即

INCR2; (R2)+1→R2

对于工作寄存器组的操作必须注意,要考虑PSW中RS1、RS0的值,即要确定当前使用的是哪一组寄存器,然后对其值进行操作。设(R2)=23H,使用第2组(RS1 RS0=10B)寄存器,则该指令的执行过程如图31所示。



图31寄存器寻址示意图


3.2.3直接寻址
直接寻址是指操作数存放在片内RAM中,指令中给出RAM中的地址。例如: 

MOVA,38H; (38H)→A

即片内RAM中38H单元的内容送入累加器A。
设(38H)=6DH,该指令的执行过程如图32所示。


图32直接寻址示意图


在MCS51单片机中,直接寻址方式可以访问片内RAM的低128字节(data区域)和所有的特殊功能寄存器(sfr区域),而不能够直接寻址访问片内RAM的高128字节,高128字节只能够间接访问。
对于特殊功能寄存器,既可以使用地址,也可以使用SFR名。例如: 

MOVA,P1; (P1)→A

是把SFR中P1口引脚的数据送给累加器A,也可以写成: 

MOVA,90H

其中,90H是P1口的地址。
直接寻址的地址占一字节,所以,一条直接寻址方式的指令至少占用内存两个单元。
3.2.4寄存器间接寻址
寄存器间接寻址是指操作数存放在片内或片外RAM中,操作数的地址存放在寄存器中,在指令执行时,通过指令中的寄存器内的地址,间接地访问操作数。存放地址的寄存器称为间址寄存器,指令中在寄存器前面加前缀“@”表示。
MCS51单片机规定只使用Ri(i=0、1,即指R0、R1)、SP和DPTR作间址寄存器。寄存器间接寻址的空间和范围有以下几种情况。
1. 使用Ri、SP间接访问片内RAM空间
这种情况间接访问的范围是片内RAM的256字节(idata区域),包括低128字节和高128字节,但不包括特殊功能寄存器。例如: 

MOVA,@Ri; ((Ri))→A

ADDA,@Ri; (A)+((Ri))→A



图33间接寻址
(MOVA,@R0)示意图


上面(Ri)表示Ri指向的单元,即单元的地址,((Ri))表示Ri指向单元中的数据。其操作如图33所示。
对使用SP间接访问片内RAM,仅用在堆栈操作中,见后面指令系统。
2. 使用Ri间接访问片外RAM空间
这种情况间接访问的范围是片外RAM的64KB全空间。其指令只有两条: 

MOVXA,@Ri; ((P2)(Ri))→A

MOVX@Ri,A; (A)→(P2 Ri)

P2中的值作为高8位地址,Ri中的值作为低8位地址。P2为0时,访问的区域为pdata。
3. 使用DPTR间接访问片外RAM空间
这种情况间接访问的范围是片外RAM的64KB全空间(xdata区域)。其指令也只有两条: 

MOVXA,@DPTR; ((DPTR))→A

MOVX@DPTR,A; (A)→(DPTR)

DPTR为16位地址。
3.2.5变址寻址


图34变址寻址示意图


变址寻址实际上是基址加变址的间接寻址,就是操作数的地址由基址寄存器的地址,加上变址寄存器的地址得到。
基址寄存器使用DPTR或程序计数器(PC),累加器A则为变址寄存器。因为变址寻址也是间接寻址,因此在地址寄存器前面要加上前缀“@”。例如: 

MOVCA,@A+DPTR; ((A)+(DPTR))→A

该指令的操作过程如图34所示。
变址寻址的空间为程序存储器。其范围为: 若使用DPTR为基址寄存器,寻址范围为64KB; 若使用PC为基址寄存器,寻址空间在PC之后256字节范围内。变址寻址访问的为code区域。变址寻址主要用于查表操作。
3.2.6位寻址
所谓位寻址,是指操作数是二进制位的地址。指令中给出的是操作数的位地址,位地址可以是片内RAM中20H~2FH(bdata区域)中的某一位,也可以是特殊功能寄存器(sfr区域)中能够按位寻址的某一位。位地址在指令中用bit表示。例如: 

SETBbit

MOVC,bit

在MCS51单片机中,位地址可以用以下4种方式表示: 
(1) 直接位地址(00H~FFH)。如32H。
(2) 字节地址带位号。如20H.1,表示20H单元的第1位。
(3) 特殊功能寄存器名带位号。如P1.7,表示P1口的第7位。
(4) 位符号地址。可以是特殊功能寄存器位名,也可以是用位地址符号命令BIT定义的位符号(如,flagBIT01H)。如TR0、flag,TR0表示定时器/计数器0的运行控制位,flag表示01H位。
3.2.7指令寻址
指令寻址使用于控制转移指令中,其操作数给出转移的目标位置的地址,访问的是code区域。在MCS51指令系统中,目标位置的地址的提供有两种方式,分别对应两种寻址方式。
1. 绝对寻址
绝对寻址是在指令的操作数中,直接提供目标位置的地址或地址的一部分。在MCS51指令系统中,长转移和长调用指令给出的是16位地址,寻址范围为64KB全空间。例如: 

LJMPSER_INT_T1; 无条件跳转到T1中断服务程序SER_INT_T1处

LCALLSUB_SORT; 调用排序子程序SUB_SORT

2. 相对寻址
相对寻址是以当前程序计数器(PC)值(为所执行指令的下一条指令的地址)为基地址,加上指令中给出的偏移量rel,得到目标位置的地址,即: 

目标地址=PC+rel

rel=目标地址-PC

偏移量rel为8位补码,其值为-128~+127。rel﹤0表明目标地址小、源地址大,程序向回跳转; rel﹥0,程序向前跳转。例如: 

JZFIRST; (A)=0,跳转到FIRST

DJNZR7,LOOP; (R7)-1≠0,跳转到LOOP

注意: 在实际编程中,不需要计算rel,由编译器自动计算(过去手工编译时需要程序员计算rel值); 当跳转范围超出了rel范围,编译器会提示,对程序做适当调整即可。
3.2.8寻址空间及指令中符号注释
1. 各寻址方式的寻址空间
表32给出了各种寻址方式所使用的操作数、寻址空间及范围。


表32操作数寻址方式、寻址空间及范围



寻址方式操作数寻址空间及范围示例指令

立即数寻址在程序存储空间,随指令读入MOVA,#46H

直接寻址片内RAM中; 低128字节和SFRMOVA,46H

寄存器寻址使用R0~R7、A、B、C、DPTRMOVA,R2


寄存器间接寻址片内RAM: 使用@Ri、SP; 范围为256B,不含SFR
片外RAM: 使用@Ri、@DPTR; 范围为64KBMOVA,@R0
MOVX@DPTR,A
变址寻址使用@A+PC、@A+DPTR; 在程序存储器中; 
范围分别为PC之后256B之内和64KB全空间MOVCA,@A+DPTR
MOVCA,@A+PC


位寻址使用位地址; 在位寻址空间; RAM的20H~2FH和SFRSETB36H

指令绝对寻址操作数是目标地址; 在程序存储空间; 
范围为64KB全空间LJMPSECON

指令相对寻址操作数是相对地址; 在程序存储空间; 范围-128~127SJMPLOOP

2. 指令中常用符号注释
Rn: n=0~7。当前选中的工作寄存器R0~R7。它们的具体地址由PSW中的RS1、RS0确定,可以是00H~07H(第0组)、08H~0FH(第1组)、10H~17H(第2组)或18H~1FH(第3组)。
Ri: i=0、1。当前选中的工作寄存器组中可作为地址指针的R0和R1。
#data: 8位立即数。
#data16: 16位立即数。
direct: 8位片内RAM单元地址,包括低128B和SFR,但不包括RAM的高128B。
addr16: 程序存储空间的16位目的地址,用于LCALL和LJMP指令中。目的地址在64KB程序存储空间的任意位置。
rel: 补码形式的8位地址偏移量。以下一条指令的第一个字节为基地址,地址偏移量在-128~+127。
bit: 片内RAM或SFR中的直接寻址位地址。
@: 在间接寻址方式中,间址寄存器的前缀符号。
(×): 表示×中的内容。
((×)): 表示由×中指向的地址单元的内容。
∧: 逻辑与。
∨: 逻辑或。
: 逻辑异或。
←、→: 指令操作流程,将内容送到箭头指向的地方。
3.3MCS51单片机指令系统
MCS51单片机指令系统有111条指令,其中单字节指令49条,双字节指令45条,3字节指令17条。从指令执行的时间来看,单周期指令64条,双周期指令45条,只有乘、除两条指令执行时间为4个周期。
MCS51单片机指令系统按其功能分,可以分为5大类: 
 数据传送指令(29条)。
 算术运算指令(24条)。
 逻辑操作指令(24条)。
 控制程序转移指令(17条)。
 位(布尔)操作指令(17条)。
虽然有111条指令,但由于没有复杂的寻址方式,没有难理解的指令,并且助记符只有42种,所以MCS51单片机的指令系统容易理解、容易记忆、容易掌握。
3.3.1数据传送指令
在通常的应用程序中,数据传送指令往往占有较大的数量,数据传送是否灵活、迅速,对整个程序的编写和执行都有很大的影响。
所谓传送,就是把源地址单元的内容传送到目的地址单元中去,而源地址单元中的内容不变。
数据传送指令共29条,是指令中数量最多、使用最频繁的一类指令。这类指令一般不影响程序状态字,只有目的操作数是累加器A时,才会影响标志位P。这类指令可以分为三组: 普通传送指令、数据交换指令和堆栈操作指令。
1. 普通传送指令
普通传送指令以助记符MOV为基础,分为片内数据存储器传送指令、片外数据存储器传送指令和程序存储器传送指令。
1) 片内数据存储器传送指令MOV

指令格式: MOV目的操作数,源操作数

其中: 源操作数可以是A、Rn、@Ri、direct、#data,目的操作数可以是A、Rn、@Ri、direct、DPTR。以目的操作数的不同可以分为五组,共16条指令。
(1) 以A为目的操作数。

汇编指令格式操作机器码(H)

MOVA,Rn; (Rn)→AE8~EF

MOVA,direct; (direct)→AE5 direct

MOVA,@Ri; ((Ri))→AE6、E7

MOVA,#data; data→A74 data

指令中的Rn,对应工作寄存器的R0~R7。Ri为间接寻址寄存器,i=0或1,即R0或R1。
本组4条指令都影响PSW中的P标志位。
(2) 以Rn为目的操作数。

汇编指令格式操作机器码(H)

MOVRn,A; (A)→RnF8~FF

MOVRn,direct; (direct)→RnA8~AF direct

MOVRn,#data; data→Rn78~7F data

本组指令都不影响PSW中的标志位。
(3) 以直接地址direct为目的操作数。

汇编指令格式操作机器码(H)

MOVdirect,A; (A)→directF5 direct

MOVdirect,Rn; (Rn)→direct88~8F direct

MOVdirect2,direct1; (direct1)→direct285 direct1 direct2

MOVdirect,@Ri; ((Ri))→direct86、87direct

MOVdirect,#data; data→direct75 direct data

本组指令都不影响PSW中的标志位。
(4) 以间接地址@Ri为目的操作数。

汇编指令格式操作机器码(H)

MOV@Ri,A; (A)→(Ri)F6、F7

MOV@Ri,direct; (direct)→(Ri)A6、A7 direct

MOV@Ri,#data; data→(Ri)76、77 data

本组指令都不影响PSW中的标志位。
(5) 以DPTR为目的操作数。

汇编指令格式操作机器码(H)

MOVDPTR,#data16; dataH→DPH,dataL→DPL90 data15~8 data7~0

该指令不影响PSW中的标志位。后面的指令不再给出机器码,其机器码可以参考附录A表的第3列。
【例31】设片内RAM(30H)=40H,(40H)=10H,(10H)=00H,(DPL)=CAH,分析以下程序执行后各单元及寄存器、P2口的内容。

MOVR0,#30H; ; 30H→R0

MOVA,@R0; ((R0))→A

MOVR1,A; (A)→R1

MOVB,@R1; ((R1))→B

MOV@R1, DPL; (DPL)→(R1)

MOVP2, DPL; (DPL)→P2

MOV10H,#20H; 20H→10H

执行上述指令后的结果为: (R0)=30H,(R1)=(A)=40H,(B)=10H,(40H)=CAH,(DPL)=(P2)=CAH,(10H)=20H。
2) 片外数据存储器传送指令MOVX
MCS51单片机对片外RAM或I/O口进行数据传送,采用的是寄存器间接寻址的方法,通过累加器A完成。这类指令共有以下4条单字节指令。

汇编指令格式操作

MOVXA,@Ri; ((P2)(Ri))→A 

MOVX@Ri,A; A→(P2, Ri)

MOVXA,@DPTR; ((DPTR))→A

MOVX@DPTR,A; A→(DPTR)

这4条指令都是执行总线操作,第1条和第3条指令是执行总线读操作,读控制信号RD有效; 第2条和第4条指令是执行总线写操作,写控制信号WR有效。
这组指令中第1、3两条指令影响P标志位,其他两条指令不影响任何标志位。
对前两条指令要特别注意: ①间址寄存器Ri提供低8位地址,隐含的P2提供高8位地址,在执行操作之前,必须先对P2和Ri分别赋高8位和低8位地址值; ②这两条指令的访问范围都是整个片外RAM,64KB全空间。
【例32】设片外RAM空间(0203H)=6FH,分析执行下面指令后的结果。

MOVDPTR,#0203H; 0203H→DPTR

MOVXA,@DPTR; ((DPTR))→A

MOV30H,A; A→30H

MOVA,#0FH; 0FH→A

MOVX@DPTR,A,A; A→(DPTR)

执行结果为: (DPTR)=0203H,(30H)=6FH,(0203H)=(A)=0FH。
3) 程序存储器传送指令MOVC
访问程序存储器的数据传送指令又称为查表指令,经常用于查表。查表指令采用基址加变址的间接寻址方式,把放在程序存储器中的表格数据读出,传送给累加器A。这类指令只有以下两条单字节指令。

汇编指令格式操作

MOVCA,@A+DPTR; ((A)+(DPTR))→A

MOVCA,@A+PC; ((A)+(PC))→A

前一条指令采用DPTR作基址寄存器,因此,可以很方便地把一个16位地址送到DPTR,实现在整个64KB程序存储空间任一单元到累加器A的数据传送。称为远程查表指令,即数据表格可以存放到程序存储空间的任何地方。
后一条指令以PC作为基址寄存器,其PC值是下一条指令的地址。另外,累加器A的内容为8位无符号数,所以查表范围限于256字节之内,称为近程查表指令。使用该条指令,关键要准确计算从本指令到数据所在地址的地址偏移量A。但在实际应用中,往往给出的是数据表的首地址和数据在表内的偏移量,因此,需要先计算出表首偏移量,其计算关系为: 
表首偏移量=表首地址-PC
数据地址偏移量A与表首偏移量、表内偏移量的关系为: 
数据地址偏移量A=表首偏移量+表内偏移量
这组指令都影响P标志位。
【例33】从片外程序存储器2000H单元开始存放0~9的平方值,以PC作为基址寄存器,执行查表指令得到6的平方值,并且送到片内RAM中的30H单元。
解: 设MOVC指令所在的地址为1FA0H,则表首偏移量=2000H-(1FA0H+1)=5FH,表内偏移量为6。
相应的程序为: 

MOVA,#5FH

ADDA,#06H

MOVCA,@A+PC

MOV30H,A

执行结果为: (PC)=1FA1H,(A)=(30H)=24H=36D。
如果使用以DPTR为基址寄存器的查表指令,其程序如下: 

MOVDPTR,#2000H

MOVA,#6

MOVCA,@A+DPTR 

MOV30H,A

通过本例对两条查表指令进行比较可以看出,以DPTR为基址寄存器的查表指令使用简单、方便。
2. 数据交换指令
普通数据传送指令完成的是把源操作数传送给目的操作数,指令执行后源操作数不变,数据传送是单向的。而数据交换指令则对数据作双向传送,传送后,前一个操作数传送到了后一个操作数所保存的地方,后一个操作数传送到了前一个操作数所保存的地方。
数据交换指令要求第一个操作数必须为累加器A。共5条指令,分为字节交换和半字节交换。
1) 字节交换指令

汇编指令格式操作

XCHA,Rn; (A)←→(Rn)

XCHA,direct; (A)←→(direct)

XCHA,@Ri; (A)←→((Ri))

这3条指令都影响P标志位。
2) 低半字节交换指令

汇编指令格式操作

XCHDA,@Ri; (A0~3)←→((Ri)0~3)

这条指令影响P标志位。
3) A自身半字节交换指令

汇编指令格式操作

SWAPA; (A0~3)←→(A4~7)

这条指令不影响任何标志位。
【例34】设(R0)=30H,(30H)=4AH,(A)=28H,则分别执行XCHA,@R0、XCHDA,@R0、SWAPA后各单元的内容。
解: 

执行: XCHA,@R0 ; 结果为(A)=4AH,(30H)=28H

执行: XCHDA,@R0; 结果为(A)=48H,(30H)=2AH

执行: SWAPA; 结果为(A)=84H,(30H)=2AH

R0中的内容一直未变,(R0)=30H。
3. 堆栈操作指令
堆栈操作有进栈和出栈,常用于保存和恢复现场。堆栈操作指令有两条。

汇编指令格式操作

PUSHdirect;先(SP)+ 1→SP,

; 后(direct)→(SP)

POPdirect; 先((SP))→direct,

; 后(SP)-1→SP

PUSH为进栈操作。进栈时,堆栈指针SP先加1,指向栈顶的一个空单元,然后将直接地址(direct)单元的内容压入SP所指向的空栈顶中。本指令不影响任何标志位。
POP为出栈操作。出栈时,先将栈顶的内容弹出送给直接地址direct单元,然后堆栈指针SP减1,使SP指向堆栈中有效的数据。本指令有可能影响P标志位,当操作数是累加器A时。
【例35】若在程序存储器中2000H单元开始的区域依次存放着0~9的平方值,用查表指令读取3的平方值并存于片内RAM中30H单元,要求操作后保持DPTR中原来的内容不变。
解: 为了使用DPTR,并且保持原来的内容不变,应该在使用DPTR前使其进栈,使用后再出栈恢复其原来内容。程序如下: 

PUSHDPH

PUSHDPL

MOVDPTR,#2000H

MOVA,#3

MOVCA,@A+DPTR

MOV30H,A

POPDPL

POPDPH

注意: 
(1) 进栈与出栈必须成对使用,否则会出现意想不到的问题,如在子程序中操作,会使子程序不能够正确返回; 
(2) 先进栈的必须后出栈,后进栈的必须先出栈,否则会出现DPL与DPH内容互换。
3.3.2算术运算指令
算术运算类指令共有24条,包括加法、减法、乘法、除法、BCD码调整等指令。MCS51单片机的算术/逻辑运算部件只能执行无符号二进制整数运算,可以借助于溢出标志位,实现有符号数的补码运算。借助于进位标志,可以实现高精度加、减运算。
算术运算结果会影响进位标志CY、半进位标志AC、溢出标志OV,但加1和减1指令不影响这些标志位。如果累加器A为目的操作数,还要影响奇偶标志位P。
算术运算指令多数以累加器A作为第一操作数,第二操作数可以是工作寄存器Rn、直接地址数据、间接地址数据和立即数。为了便于讨论,按运算将其分为5组。
1. 加法指令
加法指令分为不带进位加法指令、带进位加法指令和加1指令。
1) 不带进位加法指令ADD

汇编指令格式  操作

ADDA,Rn; (A)+(Rn)→A

ADDA,direct; (A)+(direct)→A

ADDA,@Ri; (A)+((Ri))→A

ADDA,#data; (A)+ data→A

这组指令的执行影响标志位CY、AC、OV和P,溢出标志OV只对有符号运算有意义。
【例36】设(A)=0C3H,(R0)=0AAH,试分析执行ADDA,R0后的结果及各标志位的值。
执行结果为: (A)=6DH。

各标志位为: CY=1,AC=0,P=1,OV=1。
 (A):11000011+(R0):1010101010110 1101
溢出标志OV为第7位与第6位的进位C7、C6的异或,即OV=C7C6。
2) 带进位加法指令ADDC

汇编指令格式操作

ADDCA,Rn; (A)+(Rn)+ CY→A

ADDCA,direct; (A)+(direct)+ CY→A

ADDCA,@Ri; (A)+((Ri))+ CY→A

ADDCA,#data; (A)+ data+ CY→A

这组指令的执行影响标志位CY、AC、OV和P,溢出标志OV只对有符号运算有意义。
【例37】试编写程序,把R1R2和R3R4中的两个16位数相加,结果存放在R5R6中。
解: 对于相加的两个数的低8位R2和R4使用不带进位的加法指令ADD,其和存放于R6中,对于高8位的R1和R3,使用带进位的加法指令ADDC,其和存放于R5中。程序段如下: 

MOVA,R2; (R2)→A

ADDA,R4; (A)+(R4)→A

MOVR6,A; (A)→R6

MOVA,R1; (R1)→A

ADDCA,R3; (A)+(R3)+ CY→A

MOVR5,A; (A)→R5

3) 加1指令INC

汇编指令格式操作

INCA; (A)+ 1→A

INCRn; (Rn)+ 1→Rn

INCdirect; (direct)+ 1→direct

INC@Ri; ((Ri))+ 1→(Ri)

INCDPTR; (DPTR)+ 1→DPTR

这组指令除了第一条影响标志位P之外,其他指令都不影响标志位。
2. 减法指令
减法指令分为带借位减法指令和减1指令。
1) 带借位减法指令SUBB

汇编指令格式操作

SUBBA,Rn; (A)(Rn)CY→A

SUBBA,direct; (A)(direct)CY→A

SUBBA,@Ri; (A)((Ri))CY→A

SUBBA,#data; (A)data CY→A

这组指令影响标志位CY、AC、OV和P,溢出标志OV只对有符号数运算有意义。
由于MCS51单片机没有不带借位的减法指令,对于不带借位的减法运算,可以先对CY清0(用CLRC),然后再用SUBB命令操作。
【例38】试编写实现R2R1→R3功能的程序。
解: 程序段如下: 

MOVA,R2

CLRC

SUBBA,R1

MOVR3,A

2) 减1指令DEC

汇编指令格式操作

DECA; (A)1→A

DECRn; (Rn)1→Rn

DECdirect; (direct)1→direct

DEC@Ri; ((Ri))1→(Ri)

这组指令除了第一条影响标志位P之外,其他指令都不影响标志位。
3. 乘法指令MUL
在MCS51单片机中,乘法指令只有一条。

汇编指令格式操作

MULAB; (A)×(B)→B(高字节)、A(低字节)

该指令的操作是: 把累加器A和寄存器B中两个8位无符号数相乘,所得的16位积的高字节存放在B中,低字节存放在A中。若乘积大于0FFH,OV置1,说明高字节B中不为0,否则OV清0,即B中为0。该指令还影响P标志位,并且对CY总是清0。
4. 除法指令DIV
在MCS51单片机中,除法指令也只有一条。

汇编指令格式操作

DIVAB; (A)/(B),商→A、余→B

该指令的操作是: 累加器A的内容除以寄存器B的内容,两个都是8位无符号整数,所得结果的整数商存放在A中,余数存放在B中。如果除数(B)=0,则标志位OV置1,否则清0。该指令还影响P标志位,并且CY总是被清0。
5. 十进制调整指令DA
在MCS51单片机中,十进制调整指令只有一条。

汇编指令格式操作

DAA; 调整累加器A内容为BCD码

该指令用于ADD或ADDC指令后,且只能用于压缩的BCD码相加结果的调整,目的是使单片机能够实现十进制加法运算功能。
调整过程如下: 
(1) 若累加器A的低4位为十六进制的A~F,或者半进位标志位AC为1,则累加器A的内容作加06H调整。
(2) 若累加器A的高4位为十六进制的A~F,或者进位标志位CY为1,则累加器A的内容作加60H调整。
该指令影响标志位CY、AC和P,但不影响OV。
【例39】试编写程序,对两个十进制数76、58相加,并且保持其结果为十进制数,把结果存于R3中。
解: 程序段如下: 

MOVA,#76H

ADDA,#58H

DAA

MOVR3,A

程序执行后,R3中的内容为34H(十进制数34),进位标志CY为1,则最后结果为134。
在编写程序时,对BCD码的写法必须注意: 要按十进制数格式写,然后在其后面加上H。
3.3.3逻辑操作指令
逻辑操作指令共有24条,包括与、或、异或、清0、求反、移位等操作指令。
参与逻辑操作的操作数可以是累加器A、工作寄存器Rn、直接地址数据、间接地址数据和立即数。
逻辑操作指令对标志位的影响: 如果累加器A为目的操作数,会影响奇偶标志P; 带进位移位操作,会影响进位标志CY。
为了便于讨论,将其分为5组。
1. 逻辑与指令ANL

汇编指令格式操作

ANLA,Rn; (A)∧(Rn)→A

ANLA,direct; (A)∧(direct)→A

ANLA,@Ri; (A)∧((Ri))→A

ANLA,#data; (A)∧data→A

ANLdirect,A; (direct)∧(A)→direct

ANLdirect,#data; (direct)∧data→direct

这组指令的前4条影响奇偶标志位P,后2条指令不影响任何标志位。
逻辑与操作往往用于使某些位清0。
2. 逻辑或指令ORL

汇编指令格式操作

ORLA,Rn; (A)∨(Rn)→A

ORLA,direct; (A)∨(direct)→A

ORLA,@Ri; (A)∨((Ri))→A

ORLA,#data; (A)∨data→A

ORLdirect,A; (direct)∨(A)→direct

ORLdirect,#data; (direct)∨data→direct

这组指令的前四条影响奇偶标志位P,后两条指令不影响任何标志位。
逻辑或操作往往用于使某些位置1。
3. 逻辑异或指令XRL

汇编指令格式  操作

XRLA,Rn; (A)(Rn)→A

XRLA,direct; (A)(direct)→A

XRLA,@Ri; (A)((Ri))→A

XRLA,#data; (A)data→A

XRLdirect,A; (direct)(A)→direct

XRLdirect,#data; (direct)data→direct

这组指令的前四条影响奇偶标志位P,后两条指令不影响任何标志位。
逻辑异或操作往往用于使某些位取反。
【例310】写出完成以下各功能的指令: 
(1) 对累加器A中的1、3、5位清0,其余位不变。
(2) 对A中的2、4、6位置1,其余位不变。
(3) 对A中的0、1位取反,其余位不变。
解: 对应指令如下: 

ANLA,#11010101B

ORLA,#01010100B

XRLA,#00000011B

4. 累加器A清0和求反指令

汇编指令格式操作

CLRA; 0→A

CPLA;(A)→A

前一条指令是对A清0,该指令影响奇偶标志位P。后一条指令是对A求反,不影响任何标志位。
5. 循环移位指令

指令名称指令格式操作

A循环左移RLA; a7←a0

A循环右移RRA; a7→a0

A带进位循环左移RLCA; a7←a0CY

A带进位循环右移RRCA;CYa7→a0


前两条指令不影响任何标志位,后两条指令影响进位位CY和奇偶标志位P。
注意: ①这4条指令,每执行一次只移动1位; ②左移一次相当于乘以2,右移一次相当于除以2。常用移位的方式进行乘除运算,因为移位指令比乘除指令速度快。

【例311】试编写程序,对8位二进制数01100101B=65H=101D乘以2。
解: 程序段如下: 

MOVA,#65H

CLRC

RLCA

程序执行后的结果为: (A)=CAH,CY=0。CAH=202D=101D×2。
3.3.4控制程序转移指令
计算机功能的强弱,主要取决于转移类指令的多少与功能,特别是条件转移指令。MCS51单片机有17条转移类指令,包括无条件转移指令、条件转移指令、子程序调用及返回指令等。
这类指令只有比较转移指令影响进位标志CY,其他指令不影响标志位。
为了便于讨论,将其分为4组。
1. 无条件转移指令
无条件转移指令是指,当程序执行该指令后,程序无条件地转移到指令所指定的地址去执行。无条件转移指令包括短转移、长转移和间接转移3条。
1) 短转移指令(相对转移指令)SJMP

汇编指令格式操作

SJMPrel; (PC)+rel→PC

指令的实际编写形式为: “SJMP  目标地址标号”。
指令的操作数是相对地址,rel是一个有符号字节数,其范围为-128~127,负数表示向回跳转,正数表示向前跳转。在使用时并不需要计算和写出rel值,看下面例子。
【例312】程序中有一无条件转移指令SJMPRELOAD,已知本指令的地址为0100H,标号RELOAD的地址为0123H,试计算相对地址偏移量rel。

rel=0123H(PC)=0123H(0100H+2)=21H

对于rel值,在手工汇编时需要计算,并且要把rel值写到该指令码的第2字节,指令码为8021H。现在都是计算机进行汇编,并不需要计算rel值,所以该指令的编写形式为: “SJMP目标地址标号”。对于后面所有的转移指令,由于使用计算机汇编,其编写形式均是如此。
2) 长转移指令LJMP

汇编指令格式操作

LJMPaddr16; addr16→PC

指令的实际编写形式为: “LJMP  目标地址标号”。
指令提供16位目标地址,执行时,直接将16位地址送给程序计数器(PC),程序无条件跳转到指定的目标地址去执行。
由于程序的目标地址是16位,因此程序可以跳转到64KB程序存储器空间的任何地方。
3) 间接转移指令JMP

汇编指令格式操作

JMP@A+DPTR; (A)+(DPTR)→PC

该指令转移的目标地址是由数据指针寄存器DPTR的内容与累加器A的内容相加得到,DPTR的内容一般为基址,A的内容为相对偏移,在64KB范围内无条件转移。DPTR一般为确定的值,累加器A为变值,根据A的值转移到不同的地方,因此该指令也叫作散转指令。在使用中,往往与一个转移指令表一起实现多分支转移。
【例313】分析下面多分支转移程序段,程序中,根据累加器A的值0、1、2、3转移到相应的TAB0~TAB3分支去执行。

MOVB,#3

MULAB

MOVDPTR,#TABLE; 转移表首地址送DPTR

JMP@A+DPTR; 根据A值转移

TABLE: 

LJMPTAB0; 初始(A)=0时转到TAB0执行

LJMPTAB1; 初始(A)=1时转到TAB1执行

LJMPTAB2; 初始(A)=2时转到TAB2执行

LJMPTAB3; 初始(A)=3时转到TAB3执行

…

2. 条件转移指令
条件转移指令是指,当指令中条件满足时,程序转到指定位置执行,条件不满足时,程序顺序执行。这类指令都属于相对转移,转移范围均为-128~127,负数表示向回跳转,正数表示向前跳转。
在MCS51系统中,条件转移指令有三种: 累加器A判0转移指令、比较转移指令、循环转移指令,共8条。需要注意的是,注释中的PC值,均为指向下一条指令的地址值。
1) 判0转移指令

指令名称指令格式操作

判A为0转移JZrel; (A)=0,(PC)+ rel→PC; 

; (A)≠0,顺序执行

判A非0转移JNZrel; (A)≠0,(PC)+ rel→PC; 

; (A)=0,顺序执行

指令的实际编写形式分别为: “JZ目标地址标号”和“JNZ目标地址标号”。
【例314】试编写程序,把片外RAM地址从2000H开始的数据,传送到片内RAM地址从30H开始的单元,直到出现0为止。
解: 程序段如下: 

MOVDPTR,#2000H; 用DPTR指向片外RAM的2000H单元

MOVR0,#30H; 用R0指向片内RAM的30H单元

LOOP: 

MOVXA,@DPTR; 从片外RAM中DPTR指向的单元读数据给A

MOV@R0,A; 把A中数据存于用R0指向的片内RAM的单元

INCR0; 片内RAM指针R0增1

INCDPTR; 片外RAM指针DPTR增1

JNZLOOP; (A)≠0跳转到LOOP去执行; 否则顺序向下执行

SJMP$; 程序无休止地执行本指令,停留到此

2) 比较转移指令CJNE
比较转移指令功能较强,共有4条指令,它的一般格式为: 

CJNE操作数1,操作数2,rel; 3字节指令

指令的功能是,两个操作数进行比较(操作数1减操作数2,置标志位,不保存结果),若不等则转移,否则顺序执行。该类指令影响进位标志位CY,而不改变两个操作数。
具体指令形式如下:  

汇编指令格式操作

CJNEA,direct,rel; 若(A) ﹥ (direct),则(PC)+rel→PC,0→CY

; 若(A) ﹥ (direct),则(PC)+rel→PC,1→CY

; 若(A)=(direct),则顺序执行,0→CY

CJNEA,#data,rel; 若(A) ﹥ data,则(PC)+rel→PC,0→CY

; 若(A) ﹥ data,则(PC)+rel→PC,1→CY

; 若(A)=data,则顺序执行,0→CY

CJNERn,#data,rel; 若(Rn) ﹥ data,则(PC)+rel→PC,0→CY

; 若(Rn) ﹥ data,则(PC)+rel→PC,1→CY

; 若(Rn)=data,则顺序执行,0→CY

CJNE@Ri,#data,rel; 若((Ri)) ﹥ data,则(PC)+rel→PC, 0→CY

; 若((Ri)) ﹥ data,则(PC)+rel→PC,1→CY

; 若((Ri))=data,则顺序执行,0→CY

指令的实际编写形式分别为: 

CJNEA,direct,目标地址标号

CJNEA,#data,目标地址标号

CJNERn,#data,目标地址标号

CJNE@Ri,#data,目标地址标号

3) 循环转移指令DJNZ
循环转移指令同样功能很强,共有两条指令。

汇编指令格式操作

DJNZRn,rel; (Rn)1→Rn

; 若(Rn)≠0,则(PC)+rel→PC

; 若(Rn)=0,则顺序执行

DJNZdirect,rel; (direct)1→direct

; 若(direct)≠0,则(PC)+rel→PC

; 若(direct)=0,则顺序执行


指令的实际编写形式分别为: 

DJNZRn,目标地址标号

DJNZdirect,目标地址标号

【例315】试编写程序,统计片内RAM中从40H单元开始的20个单元中0的个数,结果存于R2中。
解: 用R0作间址寄存器读取数据,R7作循环变量,用JNZ或CJNE判断数据是否为0,用DJNZ指令和R7控制循环。
程序段一: 程序段二: 

MOVR0,#40HMOVR0,#40H

MOVR7,#20MOVR7,#20

MOVR2,#0MOVR2,#0

LOOP:  LOOP: 

MOVA,@R0CJNE@R0,#0,NEXT

JNZNEXTINCR2

INCR2NEXT: 

NEXT: INCR0

INCR0DJNZR7,LOOP

DJNZR7,LOOP

3. 子程序调用和返回指令
这类指令有3条,一条子程序调用指令,两条程序返回指令。
1) 子程序调用(长调用)指令

汇编指令格式操作

LCALLaddr16; (SP)+1→SP、(PC7~0)→(SP),

; (SP)+1→SP、(PC15~8)→(SP),

; addr16→PC

本指令提供16位目标地址,因此可以调用64KB范围内任何地方的子程序。
指令的实际编写形式为: “LCALL目标地址标号或子程序名”。
2) 子程序返回指令

汇编指令格式操作

RET; ((SP))→PC15~8、(SP)1→SP,

; ((SP))→PC7~0、(SP)1→SP

子程序返回时,只需要将堆栈中的返回地址弹出送给PC,程序就自动接着调用前的程序继续执行。从堆栈中先弹出高8位地址,后弹出低8位地址。
3) 中断服务程序返回指令

汇编指令格式操作

RETI; ((SP))→PC15~8、(SP)1→SP,

; ((SP))→PC7~0、(SP)1→SP

中断服务程序返回指令RETI,除了具有“RET”指令的功能外,还将开放中断逻辑。
4. 空操作指令

汇编指令格式操作

NOP; 无任何操作

这是一条单字节指令,执行时,不做任何操作(即空操作),仅将程序计数器(PC)值加1,使CPU指向下一条指令继续执行,它要占用一个机器周期,常用来产生时间延迟和程序缓冲。
细心的读者会发现,以上只有15条指令,还少两条指令,这两条指令是AJMP和ACALL,称为绝对转移(也叫短转移)指令和绝对子程序调用(也叫短调用)指令,这两条指令的转移范围是绝对划定的2KB范围之内,用不好会出现错误,并且其编码也不好理解(见附录A),唯一的优点只是比LJMP和LCALL指令少一个字节。在存储器容量变大、价格低廉的今天,其唯一的优点也没有了意义,所以没有必要使用这两条指令。
3.3.5位操作指令
位操作指令又叫布尔处理指令。MCS51单片机有一个位处理器(布尔处理器),它具有一套处理位变量的指令集,有位数据传送指令、位逻辑操作指令、控制程序转移指令。
在进行位操作时,位累加器为C,即进位标志CY。位地址是片内RAM字节地址20H~2FH单元中连续的128个位(位地址为00H~7FH)和部分SFR,累加器A和寄存器B(位地址E0H~E7H和F0H~F7H)中的位与00H~7FH位一样,都可以作软件标志或位变量。
在汇编语言中,位地址可以用以下4种方式表示: 
(1) 直接位地址(00H~FFH)。如18H。
(2) 字节地址带位号。如20H.0,表示20H单元的第0位。
(3) 特殊功能寄存器名带位号。如P2.3,表示P2口的第3位。
(4) 位符号地址。可以是特殊功能寄存器位名,也可以是用位地址符号命令BIT定义的位符号,如flag(flag应在这之前定义过,如flagBIT05H)。
例如,用上述4种方式都可以表示PSW(D0H)中的第2位,分别为: D2H、D0H.2、PSW.2、OV。
MCS51单片机共17条位操作指令,为了讨论方便,将其分成三组。
1. 位传送指令
位传送指令有两条,实现位累加器C与一般位之间的数据传送。

汇编指令格式操作

MOVC,bit; (bit)→C 

MOVbit,C; (C)→bit

【例316】编写程序,把片内RAM中07H位的数值,传送到ACC.0位。
解: 程序段如下: 

MOVC,07H

MOVACC.0,C

注意: 一般位之间不能够直接传送,必须借助于C。
2. 位逻辑操作指令
位逻辑操作指令包括位清0、位置1、位取反、位与、位或,共10条指令。
1) 位清0指令

汇编指令格式操作

CLRC; 0→C

CLRbit;0→bit

2) 位置1指令

汇编指令格式操作

SETBC; 1→C

SETBbit; 1→bit

3) 位取反指令

汇编指令格式操作

CPLC; (C)→C

CPLbit; (bit)→bit

4) 位与指令

汇编指令格式操作

ANLC,bit; (C)∧(bit)→C

ANLC,bit;(C)∧(bit)→C

5) 位或指令

汇编指令格式操作

ORLC,bit; (C)∨(bit)→C

ORLC,bit; (C)∨(bit)→C

3. 位条件转移指令
位条件转移指令是以C或bit为判断条件的转移指令,共5条指令。
1) 以C为条件的转移指令

汇编指令格式操作

JCrel; 若(C)=1,则(PC)+rel→PC

; 若(C)=0,则顺序向下执行

JNCrel; 若(C)=0,则(PC)+rel→PC

; 若(C)=1,则顺序向下执行

2) 以bit为条件的转移指令

汇编指令格式操作

JBbit,rel; 若(bit)=1,则(PC)+rel→PC

; 若(bit)=0,则顺序向下执行

JNBbit,rel; 若(bit)=0,则(PC)+rel→PC

; 若(bit)=1,则顺序向下执行

JBCbit,rel; 若(bit)=1,则(PC)+rel→PC,且0→bit; 

; 若(bit)=0,则顺序向下执行

【例317】编写程序,利用位操作指令,实现图35所示的硬件逻辑电路功能。
解: 程序段如下:


图35硬件逻辑电路



MOVC,P1.1; (P1.1)→C

ORLC,P1.2 ; (C)∨(P1.2)→C

CPLC 

ANLC,P1.0 ; (C)∧(P1.0)→C

CPLC 

MOVF0,C ; (C)→F0位

MOVC,P1.3 ; (P1.3)→C

ANLC,P1.4; (C)∧(P1.4)→C

CPLC

ORLC,F0 ; (C)∨(F0)→C

MOVP1.5,C; (C)→P1.5

3.4MCS51单片机伪指令
伪指令是汇编程序中用于指示汇编程序如何对源程序进行汇编的指令。伪指令不同于指令,在汇编时并不翻译成机器代码,只是在汇编过程进行相应的控制和说明。
伪指令通常在汇编程序中用于定义数据、分配存储空间、控制程序的输入/输出等。在MCS51系统中,常用的伪指令有以下7条。
1. 汇编地址设置伪指令ORG
ORG常用于汇编语言某程序段或某个数据块的开始,指明其汇编地址。一般格式为:

[标号:]ORG16位地址

其标号为可选项。例如:

ORG0040H

MAIN:

MOVSP,#0DFH

MOV30H,#00H

 …… 

此处的ORG伪指令指明后面的程序从0040H单元开始存放。
2. 结束汇编伪指令END
END伪指令用于汇编语言程序段的末尾,指示源程序在END处结束汇编,即便是END后面还有程序,也不做处理。一般格式为:

……

END

3. 符号定义伪指令EQU
EQU也称为赋值伪指令,其一般格式为:

符号名EQU项(常数、常数表达式、字符串或地址标号)

EQU的功能是将右边的项赋给左边。在汇编过程中,遇到EQU定义的符号名,就用其右边的项代替符号名。需要注意的是,EQU只能先定义后使用。例子如下。

HOUREQU30H;定义变量HOUR的地址为30H

MINUEQU31H;定义变量MINU的地址为31H

REGIEQUR7;定义字符串R7

DISPEQU0800H;定义变量DISP的地址为0800H

MOVHOUR,#09H;变量HOUR赋值9

MOVR0,#HOUR;使指针R0指向30H单元

INC R0;指针R0增1

MOV@R0,#25;变量MINU赋值25

MOVREGI,A;(A)→R7

LCALL DISP;调用首地址为0800H处的子程序

4. 变量定义伪指令DATA
DATA伪指令称为数据地址符号伪指令。其一般格式为:

符号名DATA常数或常数表达式

DATA的功能与EQU相似,是将右边的项赋值给左边。在汇编过程中遇到DATA定义的符号名,就用其右边的项代替符号名。该伪指令用于定义片内数据区变量。
与DATA类似的还有IDATA、XDATA、CODE等伪指令,分别用于定义其他数据区的变量。
注意: DATA可以后定义先使用,当然也可以先定义后使用。例如:

HOURDATA30H;定义变量HOUR的地址为30H

MINUDATA31H;定义变量MINU的地址为31H

MOVHOUR,#09H;变量HOUR赋值9

MOVR0,#HOUR;使指针R0指向30H单元

INCR0;指针R0增1

MOV@R0,#25;变量MINU赋值25

5. 位变量定义伪指令BIT
BIT伪指令称为位地址符号伪指令。其格式为:

符号名BIT位地址

BIT伪指令的功能是把右边的地址赋给左边的符号名。位地址可以是前面所述的4种形式中的任一种。例如:

FLAGRUNBIT00H

FLAGMUSBIT01H

FLAGKEYBIT02H

FLAGALARBITP1.7

6. 字节数据定义伪指令DB
DB伪指令的一般格式为:

[标号:]DB项(字节数据、字节数表或字符、字符串)

它的功能是从指定单元开始定义并存储若干字节的数据或字符、字符串,字符或字符串需要用引号(单引号或双引号均可)括起来,即用ASCII码表示。其中标号是可选的。例如:

TABLE: DB32,24H,'A',"B",'abcd',"EFGH"

7. 字数据定义伪指令DW
DW伪指令的一般格式为:

[标号:]DW字数据或字数据表

DW伪指令的功能与DB相似,是从指定单元开始定义并存储若干字数据,每个数据都占2个字节,而用DB伪指令定义的数据只占一个字节。其中标号是可选的。例如:

ORG1000H

TABLE2:DW32,24H,1234H

上面这两行程序汇编后,从1000H单元开始,依次存放如下数据:

(1000H)=00H

(1001H)=20H

(1002H)=00H

(1003H)=24H

(1004H)=12H

(1005H)=34H

注意: 高字节存放在前面(低地址),低字节存放在后面(高地址)。

3.5汇编语言程序设计
3.5.1简单程序设计
程序的简单和复杂是相对而言的,这里所说的简单程序,是指顺序执行的程序。简单程序从第一条指令开始,依次执行每一条指令,直到程序执行完毕,之间没有任何转移和子程序调用指令,整个程序只有一个入口和一个出口。这种程序虽然在结构上简单,但它是复杂程序的基础。
1. 数据拆分
【例318】片内RAM的30H单元内存放着一压缩的BCD码,编写程序,将其拆开并转换成两个ASCII码,分别存入31H和32H单元中,高位在31H中。
解: 数字0~9的ASCII码为30H~39H,因此,将30H中的两个BCD码拆开后,分别加上30H即可。相应程序段如下: 

MOVR0,#30H; 用间址寄存器R0存取数据

MOVA,@R0; 取原BCD码数据

PUSHACC; 原BCD码数据进栈暂存

SWAPA; 将高位数交换到低4位

ANLA,#0FH; 先作高位转换,截取高位数

ORLA,#30H; 高位转换成ASCII码

INCR0; 使R0指向31H单元

MOV@R0,A; 保存高位ASCII码

POPACC ; 原BCD码数据出栈

ANLA,#0FH; 作低位转换,截取低位数

ORLA,#30H; 低位转换成ASCII码

INCR0; 使R0指向32H单元

MOV@R0,A; 保存低位ASCII码

SJMP$; CPU停留于此处

2. 数制转换
【例319】片内RAM的30H单元内存放着一8位二进制数,编写程序,将其转换成压缩的BCD码,分别存入30H和31H单元中,高位在30H中。
解: 其方法是用除法实现。原数除以10,余数为个位数,其商再除以10,所得新商为百位数,新余数为十位数。对应程序段如下: 

MOVA,30H; 取数据

MOVB,#10

DIVAB; 除以10后,个位在B,百位和十位在A

MOV31H,B ; 保存个位于31H中的低4位

MOVB,#10

DIVAB; 除以10后,十位在B,百位在A

MOV30H,A ; 保存百位数

MOVA,B; 十位数送A

SWAPA; 十位数被交换到高4位

ORL31H,A ; 将十位数存于31H中的高4位

SJMP$

3.5.2分支程序设计
在许多情况下,程序会根据不同的条件,转向不同的处理程序,这种结构的程序称为分支程序。使用条件转移指令、比较转移指令和位条件转移指令,可以实现程序的分支处理。
在汇编语言程序中,分支结构是比较麻烦的,初学时应特别注意。
1. 一般分支程序
【例320】片内RAM的30H、31H单元存放着两个无符号数,编写程序比较其大小,将其较大者存于30H中,较小者存于31H单元中。
解: 用减法判断,两个数相减后,通过借位标志位CY来判断。程序段如下: 

MOVA,30H

CLRC

SUBBA,31H

JNCL1; (30H)≥(31H)则转

MOVA,30H; (30H)中数小,两个数交换

XCHA,31H

MOV30H,A

L1: SJMP$

【例321】片内RAM的30H单元内存放着一有符号二进制数变量X,其函数Y与变量X的关系为: 

Y=X+5X>20

020≥X≥10

-5X<10


图36例321程序流程图

编写程序,根据变量值,将其对应的函数值送入31H中。
解: 这是一个三分支的条件转移程序,可以使用CJNE、JC、JNC等指令进行判断。程序流程图如图36所示,程序段如下:  

MOVA,30H

CJNEA,#10,L1

L1: 

JNCL2; X≥10转L2

MOV31H,#0FBH; X﹤10,Y=-5

SJMPL4; X≥10

L2: 

ADDA,#5; 先按X﹥20处理,Y=X+5

MOV31H,A

CJNEA,#26,L3 

L3: JNCL4; X﹥20,转

MOV31H,#0; 20≥X≥10,Y=0

L4: SJMP$


2. 多分支程序
利用间接转移指令JMP@A+DPTR,可以实现多分支转移,即实现散转。可以参考例313,不再举例。
3.5.3循环程序设计
在实际应用中,循环结构程序使用得非常多,必须要熟练掌握。循环程序一般由以下几个部分组成: 
(1) 循环初始化部分。这一部分位于循环程序的开始,用于对循环变量、其他变量和常量赋初值,做好循环前的准备工作。

图37例322程序流程图

(2) 循环体部分。这一部分由重复执行部分和循环控制部分组成。重复执行部分需要根据具体功能编写,要求尽可能简洁,以提高执行的效率。循环控制部分由修改循环控制变量和条件转移语句等组成,用于控制循环的次数。
(3) 循环结束部分。这一部分用于存放循环结果、恢复所占用寄存器或内存的数据等。

循环程序的关键是对循环变量的修改和控制,特别是循环次数的控制。在循环次数已知的情况下用计数的方法控制循环,在循环次数未知的情况下,往往需要根据给出的某种条件,判断是否结束循环。
1. 单层循环程序
【例322】在片内RAM的20H~2FH单元,存放着16个无符号字节数据,编写程序,计算这16个数的和。

解: 16个字节数的和不会超过两个字节,将和存于40H、41H中,高字节在40H中。用R0作取加数指针,R7作控制循环计数变量。流程图如图37所示,程序段如下。

MOVR0,#21H; R0指向21H单元

MOVR7,#15; 控制循环次数初值

MOV40H,#0; 高字节清0

MOVA,20H; 取第一个加数

LOOP: 

ADDA,@R0; 低字节加上一个数

JNCNEXT; 无进位跳转

INC40H; 有进位高字节加1

NEXT: 

INCR0; 指针增1

DJNZR7,LOOP; R7减1不为0继续循环

MOV41H,A; 保存低字节数据

SJMP$

2. 双层循环程序
【例323】设计一软件延时xms的子程序。设晶振频率为12MHz。
解: 机器周期为1μs。子程序如下: 

DELAYxMS: ; 机器周期数

; MOVR7,#x; 1。本句在调用程序中

LP1: MOVR6,#249; 1

NOP; 1

LP2: NOP ; 1

NOP; 1

DJNZR6,LP2; 2

DJNZR7,LP1; 2

RET; 2

延时时间: 
[1+1+4×249+2]×x+2=(1000x+2)μs=xms+2μs

延时时间为xms多2μs。如果考虑到子程序调用指令LCALL及其前面的R7赋值指令,分别需要用两个和一个机器周期,实际延时时间仅多出5μs,并且此误差与延时的ms数x无关。误差是非常小的。
需要注意的是,此延时子程序的延时范围为1~255ms。
3.5.4子程序设计
子程序是指完成某一确定任务,并且能够被其他程序反复调用的程序段。采用子程序,可以简化程序,提高编程效率。而且从程序结构上看,逻辑关系简单、清晰,便于阅读和调试,实现程序模块化。
子程序在结构上有一定的要求,编写时需要注意: 
①  子程序第一条指令的地址称为入口地址,该指令前必须要有标号,其标号一般要能够说明子程序的功能。
②  子程序末尾一定要有返回指令。而调用子程序的指令应该在其他程序中。
③  在子程序中,要注意保护在主调程序中使用的寄存器和存储单元中的数据,必要时在子程序的开始使其进栈保护,在子程序返回前再出栈恢复原来值。
④  在子程序中,要明确指出“入口参数”和“出口参数”, 入口参数就是在调用前需要给子程序准备的数据,出口参数就是子程序的返回值。
参数的传递有以下几种方式: 
(1) 通过寄存器R0~R7或累加器A。
(2) 传递地址、入口参数和出口参数的数据存放在存储器中,使用R0、R1或DPTR传递指向数据的地址。
(3) 通过堆栈传递参数。
1. 用寄存器传递参数
【例324】试编写程序,把存放在30H、31H和40H、41H中的两个双字节压缩的BCD码数相减,结果回存到被减数的30H、31H中。高位数在30H、40H中。要求使用子程序。
解: 由于计算机内部加减都是按照二进制数进行的,所以对BCD码数据相减后,需要进行十进制数调整,为了实现十进制数调整,将减法运算转变为加法运算。在子程序中完成BCD码相减,通过寄存器传递参数,程序如下: 

MOVR0,31H

MOVR1,41H

CLRC

LCALLBCDSUB; 计算低字节差值

MOV31H,A; 保存低字节差值

MOVR0,30H

MOVR1,40H

LCALLBCDSUB; 计算高字节差值

MOV30H,A; 保存高字节差值

SJMP$

; BCD码减法子程序

; 入口参数: R0被减数; R1减数; C借位位

; 出口参数: A差值,为BCD码; C借位位

BCDSUB: 

MOVA,#9AH

SUBBA,R1; 把减数转变成十进制数的补码

ADDA,R0; 被减数加上减数的补码

DAA; 做BCD码调整

CPLC

RET

2. 用堆栈传递参数
【例325】编写程序,把片内RAM的30H单元中的8位二进制数转换成ASCII码,分别存放到31H、32H中,31H中存放高位ASCII码。要求使用子程序。
解: 在子程序中通过查表完成转换,主调程序与子程序的参数通过堆栈进行传递。程序如下: 

MOVSP,#0DFH ; 设置堆栈指针,把堆栈放在片内RAM高端

MOVDPTR,#TAB; DPTR指向数表的首地址

MOVA,30H 

SWAPA; 先对高位进行转换

PUSHACC; 高位数据进栈

LCALLHEX_ASC; 调用转换子程序

POP31H; 转换结果出栈并保存在31H中

PUSH30H; 低位数据进栈

LCALLHEX_ASC; 调用转换子程序

POP32H; 转换结果出栈并保存在32H中

SJMP$



; 十六进制数转换ASCII码子程序

; 入口参数: 栈顶之下第2单元(对主调程序来说是栈顶)

; 出口参数: 存放在栈顶之下第2单元(对主调程序来说是栈顶)

HEX_ASC: 

MOVR0,SP; R0指针指向栈顶

DECR0; 修改指针使其指向栈顶之下第2单元

DECR0

MOVA,@R0; 从堆栈中读取参数

ANLA,#0FH; 屏蔽高4位

MOVCA,@A+DPTR; 查表读取ASCII码

MOV@R0,A; 将转换结果保存到栈顶之下第2单元

RET

TAB: DB"0123456789ABCDEF "

思考题与习题
(1) 简述MCS51汇编指令格式。
(2) 何谓寻址方式?MCS51单片机有哪些寻址方式,是怎样操作的?各种寻址方式的寻址空间和范围是什么?
(3) 访问片内RAM低128字节可使用哪些寻址方式?访问片内RAM高128字节使用什么寻址方式?访问SFR使用什么寻址方式?
(4) 访问片外RAM使用什么寻址方式?
(5) 访问程序存储器使用什么寻址方式?指令跳转使用什么寻址方式?
(6) 分析下面指令是否正确,并说明理由。

MOVR3,R7

MOVB,@R2

DECDPTR

MOV20H,F0H

PUSHDPTR

CPL36H

MOVPC,#0800H

(7) 分析下面各组指令,区分它们的不同之处。

MOVA,30H与MOV A,#30H

MOVA,R0与MOV A,@R0

MOVA,@R1与MOVXA,@R1

MOVXA,@R0与MOVX A,@DPTR

MOVXA,@DPTR与MOVC A,@A+DPTR

(8) 已知单片机的片内RAM中(30H)=38H、(38H)=40H、(40H)=48H、(48H)=90H。请说明下面各是什么指令和寻址方式,每条指令执行后目的操作数的结果。两段程序是独立的。
程序段一: 程序段二:

MOVP1,#0FHMOVA,40H

MOV40H,30HMOVRO,A

MOVP0,48HMOV@R0,30H

MOV48H,#30HMOVR0,38H

MOVDPTR,#1234HMOVA,@R0

(9) 已知单片机中(A)=23H、(R1)=65H、(DPTR)=1FECH,片内RAM中(65H)=70H, ROM中(205CH)=64H。试分析下列各条指令执行后目标操作数的内容。

MOVA,@R1

MOVX@DPTR,A

MOVCA,@A+DPTR

XCHDA,@R1

(10) 已知单片机中(R1)=76H、(A)=76H、(B)=4、CY=1,片内RAM中(76H)=0D0H、(80H)=6CH。试分析下列各条指令执行后目标操作数的内容和相应标志位的值。

ADDA,@R1

SUBBA,#75H

MULAB

DIVAB

ANL76H,#76H

ORLA,#0FH

XRL80H,A

(11) 已知单片机中(A)=83H、(R0)=17H,片内RAM中(17H)=34H。试分析当执行完下面程序段后累加器A、R0、17H单元的内容。

ANLA,#17H

ORL17H,A

XRLA,@R0

CPLA

(12) 阅读下面程序段,说明该段程序的功能。

MOVR0,#40H

MOVR7,#10

CLRA

LOOP: 

MOV@R0,A

INCA

INCR0

DJNZR7,LOOP

SJMP$

(13) 阅读下面程序段,说明该段程序的功能。

MOVR0,#50H

MOVR1,#00H

MOVP2,#01H

MOVR7,#20

LOOP: 

MOVA,@R0

MOVX@R1,A 

INCR0

INCR1

DJNZ R7,LOOP

SJMP$

(14) 阅读下面程序段,说明该段程序的功能。

MOVR0,#40H

MOVA,@R0

INCR0

ADDA,@R0

MOV43H,A

CLRA

ADDCA,#0

MOV42H,A

SJMP$

(15) 编写程序,用位处理指令实现“P1.4=P1.0∨(P1.1∧P1.2)∨P1.3”的逻辑功能。
(16) 编写程序,若累加器A的内容分别满足下列条件,则程序转到LABEL存储单元,否则顺序执行。设A中存放的是无符号数。
① A≥10;② A>10; ③ A≤10。
(17) 编写程序,把片外RAM从0100H开始存放的16字节数据,传送到片内从30H开始的单元中。用Keil C编译并调试运行,观察、对比两个储存器中的数据。
(18) 片内RAM30H和31H单元中存放着一个16位的二进制数,高位在前,低位在后。编写程序对其求补,并存回原处。
(19) 片内RAM的30H到33H单元中存放着两个16位的无符号二进制数,高位在前,低位在后,将其相加,其结果保存到30H、31H单元,高位放在前面。用Keil C编译并调试运行,观察、分析储存器中数据的变化情况。
(20) 片内RAM的30H到33H单元中存放着两个16位的无符号二进制数,高位在前,低位在后,将其相减(前面数减去后面数),其结果保存到30H、31H单元,高位放在前面。
(21) 片内RAM中有两个4字节压缩的BCD码形式存放的十进制数,一个存放在30H~33H单元中,另一个存放在40H~43H单元中,高位数在低地址。编写程序将它们相加,结果的BCD码存放在30H~33H中。用Keil C编译并调试运行,观察、分析储存器中的数据。
(22) 编写程序,查找片内RAM30H~50H单元中是否有55H这一数据,若有,则51H单元置为FFH; 若未找到,则将51H单元清0。用Keil C编译并调试运行,观察、分析51H中的数据是否正确。
(23) 编写程序,查找片内RAM的30H~50H单元中出现0的次数,并将查找的结果存入51H单元。用Keil C编译并调试运行,观察、分析51H中的数据是否正确。
(24) 编写程序,将程序存储区地址从0010H开始的20个字节数据,读取到片内RAM从30H单元开始的区域,然后用冒泡法从大到小进行排序。用Keil C编译并调试运行,观察、对比两个储存器中的数据,分析是否正确。