第四章
汇编语言程序设计
所谓程序设计,就是按照给定的任务要求,编写
出完整的计算机程序。要完成同样的任务,使用的方
法或程序并不是唯一的。因此,程序设计的质量将直
接影响到计算机系统的工作效率、运行可靠性。
前面我们学过了汇编语言形式的指令系统,本章
重点介绍汇编语言程序结构以及如何利用汇编语言指
令进行程序设计的方法。
4.1 汇编语言程序设计概述
4.1.1 汇编语言程序设计步骤
使用汇编语言设计一个程序大致上可分为以下几个步骤 。
(1) 分析题意, 明确要求 。
(2) 确定算法 。
(3) 画程序流程图, 用图解来描述和说明解题步骤 。
图 4.1 常用的流程图符号
(4) 分配内存工作单元, 确定程序与数据区的存放地址 。
(5) 编写源程序
(6) 程序优化 。
( 7)上机调试、修改和最后确定源程序。
4.2.2 伪指令语句
伪指令并不是真正的指令, 也不产生相应的机器
码, 它们只是在计算机将汇编语言转换为机器码时,
指导汇编过程, 告诉汇编程序如何汇编 。 下面介绍一
些 MCS-51汇编程序常用的伪指令 。
( 1) 汇编起始伪指令 ORG
格式,[标号,] ORG 16位地址
功能:规定程序块或数据块存放的起始地址 。 如:
ORG 8000H
START,MOV A, #30H
……
该指令规定第一条指令从地址 8000H单元开始存放,
即标号 START的值为 8000H。
( 2) 汇编结束伪指令 END
格式,[标号,] END [表达式 ]
功能:结束汇编 。
例如:
ORG 2000H
START,MOV A, # 00H
……
END START
表示标号 START开始的程序段结束 。
( 3) 等值指令 EQU
格式:字符名称 EQU 项
例如, TEST EQU R0
MOV A,TEST
( 4) 定义字节指令 DB
格式,[标号,] DB 8位二进制数表
DB命令是从指定的地址单元开始, 定义若干个 8
位内存单元的内容 。 例如,
ORG 1000H
TAB; DB 23H,73,,6”,,B”
TABl,DB 110B
以上伪指令经汇编以后, 将对从 1000H开始的若干
内存单元赋值:
(1000H)=23H (1001H)=49H
(1002H)=36H (1003H)=42H
(1004H)=06H
其中 36H和 42H分别是字符 6和 B的 ASCII码, 其余
的十进制数 ( 73) 和二进制数 ( 110B) 也都换算为十
六进制数了 。
( 5) 定义字命令 DW
格式,[标号,] DW 16位二进制数表
例如,
ORG 1000H
TAB,DW 1234H, 0ABH, 10
汇编后:
( 1000H) =12H (1001H ) = 34H
(1002H ) = 00H ( 1003H ) = ABH
(1004H ) =00H (1005H) =0AH
DB,DW伪指令都只对程序存储器起作用,不能
用来对数据存储器的内容进行赋值或进行其它初始化
的工作。
4.2 顺序程序设计
顺序结构程序是一种最简单, 最基本的程序 (也称为简单
程序 ),它是一种无分支的直线形程序, 按照程序编写的顺序
依次执行 。
【 例 4-1】 两个 8位无符号数相加, 和仍为 8位 。
假设两个无符号数 X1,X2分别存放于内部 RAM60H、
61H单元中, 求其和并将和送入 62H单元 。
程序如下:
ORG 0000H
CLR C
MOV R0, # 60H ;设 R0为数据指针
MOV A,@R0 ;取 X1
1NC R0
ADDC A,@R0 ; X1+X2
1NC R0
MOV @R0,A ;保存结果
END
【 例 4-2】 两个无符号双字节数相加 。 设被加数存放在内部存
储器 30H( 高位字节 ), 31H( 低位字节 ) 单元, 加数存放在内
部存储器 40H( 高位字节 ), 41H( 低位字节 ) 单元, 和存入 30H
( 高位字节 ), 31H( 低位字节 ) 单元 。
程序如下:
ORG 0000H
CLR C ;将 C清零
MOV R0, #31H ;送被加数首址
MOV R1, #41H ;送加数首址
MOV A,@R0 ;取被加数低字节
ADD A,@R1 ;两个低字节相加
MOV @R0, A ;低字节和存人被加数低字节
DEC R0 ;修改指针, 指向被加数高字节
DEC R1 ;修改指针, 指向加数高字节
MOV A,@R0 ;取被加数高字节
ADDC A,@R1 ;高字节相加
MOV @R0, A ;存结果
END
【 例 4-3】 编写 16位二进制数求补程序
二进制数的求补可归结为, 求反加 1”的过程, 求反可用
CPL指令实现;加 1时应注意, 加 1只能加在低 8位的最低位上 。
因为现在是 16位数, 有两个字节, 因此要考虑进位问题, 即低 8
位取反加 1,高 8位取反后应加上低 8位加 1时可能产生的进位,
还要注意这里的加 1不能用 INC指令, 因为 INC指令不影响 CY标
志 。
程序如下:ORG 0200H
MOV A,R0 ;低 8位送 A
CPL A ;取反
ADD A,#01H ;加 l
MOV R2, A ;存结果
MOV A,R1 ;高 8位送 A
CPL A ;取反
ADDC A,#00H ;加进位
MOV R3, A ;存结果
END
【 例 4-4】 编程将 20H单元中的 8位无符号二进制数转换成 3位
BCD码, 并存放在 22H(百位 )和 21H(10位, 个位 )两个单元中 。
程序如下:
ORG 1000H
MOV A,20H ;取数送 A
MOV B, # 64H ;除数 100送 B中
DIV AB ; 商 (百位数 BCD码 )在 A中, 余数在 B中
MOV 22H, A ;百位数送 22H
MOV A,B ;余数送 A做被除数
MOV B, #0AH ;除数 10送 B中
DIV AB ;十位数 BCD码在 A中, 个位数在 B中
SWAP A ;十位数 BCD码移至高 4位
ORL A,B ;并入个位数的 BCD码
MOV 21H, A ;十位, 个位 BCD码存人 21H
END
分支程序的结构有两种, 如图 4.2所示 。
图 4.2 分支程序结构
图 4.2( a) 结构使用条件转移指令来实现分支,
当给出的条件成立时, 执行程序段A, 否则执行程序
段B 。
图 4.2 (b) 结构使用散转指令 JMP来实现多分支转
移,它首先将分支程序按序号的值来实现分支转移。
【 例 4-5】 设补码 X放在内部 RAM30H单元中, 函数
Y与 X有如下的关系式:
试编写程序, 根据 X的值求出 Y,并放回原单元 。
解 取出 X后先做取值范围的判断, 用累加器 A状态转
移指令判断 X是否为 0,用位状态转移指令判断 X是大
于 0还是小于 0。 程序流程图如图 4.3所示 。
程序如下:
4.3 分支程序设计
MOV A,30H
JZ ZER0
JNB ACC.7,PLUS
ADD A,#5
MOV 30H,A
PLUS,SJMP $
ZERO,MOV 30H,#20H
SJMP $
END
开始
结束
A=0?
A>0?
(30H) A
(30H)+5 (30H)
20H (30H)
图4,3 [ 例4- 5 ] 程序框图
【 例 4-6】 内部 RAM40H和 41H单元中各有
一无符号数, 比较其大小, 将大数存放于内部
RAM60H单元, 小数存放于内部 RAM61H单元,
如两数相等, 则分别送往这 2个单元 。
解 用比较不等转移指令 CJNE比较力两个
无符号书, 先确定它们是否相等, 若不相等时
再根据借位标志确定这两个无符号书的大小 。
程序框图如图 4.4所示 。
程序如下:
Y
开始
A (41H)
A与 (6 1H )交 换
结束
(40H) (60H)
A (60H)
N
图4,4 [例 4- 6] 程序框图
(40H) A
MOV A,40H
MOV 61H,41H
CJNE A, 41H,
LOOP
AJMP AGEQ
LOOP,JNC AGEQ ; A≥(41H)则无借位
XCH A,61H ; A< (41H)有借位
AGEQ,MOV 60H,A ; A与 (61H)交换
SJMP $
END
【 例 4-7】 某温度控制系统, 采集的温度值 ( Ta) 放在累加器
A中 。 此外, 在内部 RAM54H单元存放控制温度下限制 ( T54),
在 55H单元存放控制温度上限制 ( T55) 。 若 Ta >T55,程序转向
JW( 降温处理程序 ) ;若 Ta<T54,则程序转向 SW( 升温处理程
序 ) ; T55≥Ta≥T54,则程序转向 FH( 返回主程序 ) 。
程序如下:
CJNE A,55H,LOOP1 ; Ta≠T55,转向 LOOPl
AJMP FH ; Ta=T55,返回
LOOPl,JNC JW ;若 (CY)=0,表明 Ta>T55,转降温处理程序
CJNE A,54H,LOOP2 ; Ta≠T54,转向 LOOP2
AJMP FH ; Ta=54,返回
LOOP2,JC SW ;若 (CY)=1,表明 Ta<T54,转升温处理程序
FH,RET ; T55≥Ta≥T54,返回主程序
【 例 4-8】 将 ASCII码制转换为十六进制数 。 如果不是十六
进制数得 ASCII码, 用户标志位置 1。
解:由 ASCII码表知, 30H~ 39H为 0~ 9的 ASCII码, 41H~
46H为 A~ F的 ASCII码 。 在这一范围的 ASCII码减去 37H就可以
获得对应的十六进制 。
设 ASCII码放在累加器 A中, 转换结果放回 A中 。
程序流程图如图 4.5所示
START,CLC C
SUBB A,#30H
JC NASC
CJNE A,# 0AH,NN
MM,JC ASC
SUBB A,# 07H
CJNE A,# 0AH,NN
NN,JC NASC
CJNE A,# 10H,LL
LL,JC ASC
NASC,SETB F0
ASC,RET
开始
A (A)-30H
(A) 0?
(A) 0AH?
A (A)-07H
(A)>09H?
(A)>10H?
F0 1
返回
N
N
N
N
图4, 5 [ 例4 - 8 ] 程序框图
【 例 4-9】 N路分支程序, N≤8。 要求程序根据其运
行中所产生的寄存器 R3的值, 来决定如何进行分支 。
解:为实现 N路分支, 可以多次使用比较转移指令:
CJNE R3,#data, rel流程图如图 4.6所时, 但这样比
较次数太多, 当 N较大时, 执行速度就较慢 。
我们可以用变址寻址的转移指令 JMP@A+DPTR使
问题得到解决 。 首先安排存放一个转移分支入口的地
址表, 把入口地址表的首地址送到 DPTR,再把 R3的
内容送累加器 A。 现以 4个分支为例写出程序 。 假设 4
个分支的不同功能分别是给不同的位置 。
程序如下:
MOV A, R3
MOV DPTR, #BRTAB
MOVC A, @A+DPTR
JMP @A+DPTR
BRTAB DB BR0-BRTAB
DB BRl-BRTAB
DB BR2-BRTAB
DB BR3-BRTAB
BR0,SETB P1.0
SJMP BRK
BR1,SETB P1.1
SJMP BRK
BR2,SETB P1.2
SJMP BRK
BR3,SETB P1.3
BRK,SJMP BRK
R 3 = 0
R 3 = 2
R 3 = 1
Y
Y
N
N
N
Y
转 向 第 0 分 支
转 向 第 1 分 支
转 向 第 2 分 支
4.4 循环程序设计
循环程序一般由 4部分组成 。
( 1) 置循环初值 。
( 2) 循环体 。
( 3) 循环修改 。
( 4) 循环控制 。
图 4.7(a)结构是, 先执行后判断,, 适用于循环次数已
知的情况 。
图 4.7(b)结构是, 先判断后执行,, 适用于循环次数
未知的情况 。
Y
N
开 始
置 循 环 初 值
循 环 处 理
循 环 修 改
结 束 处 理
循 环 结 束?
结 束
Y
( a ) 先 执 行 后 判 断
开 始
结 束
置 循 环 初 值
循 环 结 束?
循 环 处 理
循 环 修 改
结 束 处 理
N
( b ) 先 判 断 后 执 行
二, 程序清单
ORG OOOOH
START, MOV A,#01H ;使 L1灯亮, 其它不亮
LOOP,MOV P1,A ;从 P1口输出到发光二极管
MOV R1,#10H ;延时 1秒
DEL1,MOV R2,#200
DEL2,MOV R3,#126
DEL3,DJNZ R3,DEL3
DJNZ R2,DEL2
DJNZ R1,DEL1
RL A ;左移一位, 下一个发光二极管亮
AJMP LOOP ;循环
END
【 例 4-10】 多个单字节数求知 。
已知有 10个单字节数, 依次存放在内部
RAM 40H单元开始的数据存储区中, 求和并将
结果存人寄存器 R2,R3中 (高位存 R2,低位存
R3)。
本题中,要重复进行加法运算,因此采用
循环结构程序。循环次数就是数据块字节数,这
是已知的。在置初值时,将数据块长度置人寄存
器 R5;将数据块首地址送人寄存器 R0,即以 R0
作为数据块的地址指针,采用间接寻址方式:每
做一次加法之后,修改地址指针,以便取出下一
个数来相加,并且使计数器 R5减 l。到 R5减为 0
时,求和结束。程序流程图如图 4.8所示。
ORG 2000H
SUM,MOV R0,#40H ;设地址指针
MOV R5,#0AH ;计数器初值送 R5
SUM,MOV A,#00H
MOV R2,A
LP,ADD A,@R0
JNC LP1
INC R2 ;若有进位, 和的高八位 +1
LP1,INC R0 ;地址指针 +1
DJNZ R5,LP ;判循环结束条件
MOV R3,A ;存和的低八位
END
【 例 4-11】 从内存 BLOCK单元开始有一个无符号
数的数据块, 其长度为 LEN,试求出其最大值, 并存
入 MAX单元 。
这是一个搜索问题 。 这里采用依次进行比较和取
代的方法来寻找最大值 。 具体做法是:先取出第一个
数作为基准, 和第二个数比较, 若比较结果基准数大,
不作变动;若比较结果基准数小, 则用大数来代替原
基准数, 然后再和下一个数作比较 。 到比较结束时,
基准数就是所求的最大值 。
为了进行两数的比较,采用两数相减以后判断 CY
的值来确定哪个数大,这比用 CJNE指令更简单。比较
时将基准数放在累加器 A中。若 A中先放人零,比较次
数等于 LEN;若 A中先放人第一个数,则比较次数等于
LEN-1。采用 R2作为计数器,R1作为地址指针。程序
流程如图 4.9所示。
ORG 8000H
COMP,CLR A
MOV R2,# LEN
MOV R1,# BLOCK
LOOP,CLR C ;清 CY
SUBB A,@R1 ;用减法作比较
JNC NEXT ;若( A) > ((R1)), 转移
MOV A,@R1 ;若( A) < ((R1)), 则(( R1)) → ( A)
SJMP NEXT1
NEXT,ADD A,@R1 ;恢复( A)
NEXT1,INC R1
DJNZ R2,LOOP ; 判循环结束条件
MOV MAX,A ;存最大数
SJMP $
LEN EQU 0AH
MAX EQU 40H
END EQU 41H
图 4.8 程序流程图 图 4.9 程序流程图
N
结束
设计数器初值设地址
指针求和单元清零
和低8 位+ (指针)
近位= 1?
和高8 位+ 1
计数值- 1 = 0?
指针+ 1
存和数
Y
开始
Y
N
开始
结束
0 A
计数器初值 R 2
指针值 R 1
(A)>((R1))?
((R1)) A
(R1)+1 R1
(R2)-1=0?
存最大数
Y
【 例 4-12】 假设从内存 RAM的 50H单元, 连续存放一串字
符, 以回车符 (其 ASCII码为 0DH)作为结束标志, 要求测出该
字符串的长度 。 测试方法可采用将该字符串的每一个字符与
回车符依次相比, 若不相等, 则将统计字符串长度的计数器
加 l,继续比较;若比较相等, 则表示该字符串结束, 这时计
数器中的值就是字节符串的长度 。
程序如下:
ORG 8000H
COUNT,MOV R2,#0FFH
MOV R0,#4FH
LOOP,INC R0
INC R2
CJNE @ R0,#0DH,LOOP
SJMP $
程序中使用指令 CJNE @R0,# 0DH, LOOP实现字
符串比较及控制循环的任务 。 由于在循环体中比较转移之
前, 将 R2和 R0的内容加1, 因此 R2和 R0的初值要减去
1 。 循环次数由对于循环条件的判定而定, 当循环结束时,
R2中的内容就是字符串的长度 。
【 例 4-13】 编制用软件方法延时1 S的程序
软件延时时间与执行指令的时间有关 。 如果使用 6MHz晶
振, 一个机器周期为 2μs 。 计算出执行每一条指令以及一
个循环所需要的时间, 根据要求的延时时间确定循环次数,
如果单循环时间不够长, 可以采用多重循环 。
程序如下:
MOV R5,#05H
DELY0,MOV R6,#0C8H
DELY1,MOV R7,#0F8H
NOP
DELY2,DJNZ R7,DELY2
DJNZ R6,DELY1
DJNZ R5,DELY0
这是一个三重循环程序 。 前4条指令的机器周期
数为1, 后3条指令的机器周期数为2 。 执行内循环
所用的机器周期数为 248× 2=496,执行中间循环所用
的机器周期数 (496+4)× 200=100000;执行外循环所用
的机器周期数为 (100000+3)× 5=500015,再加上1
( 执行第一条指令 ) 就是执行整段程序所用的机器周
期数 。 因此这段程序的延时时间位 ( 500015+1)
× 2μs=1.000032s。
【 例 4-14】 编写无符号数排序程序 。
假设在片内 RAM中, 起始地址为 40H的 10个单元中
存放有 10个无符号数 。 试进行升序排序 。
解 数据排序常用方法是冒泡排序法 。 这种方法的过
程类似水中气泡上浮, 故称冒泡法 。 执行时从前向后进行
相邻数的比较, 如数据的大小次序与要求的的顺序不符就
将这两个数互换, 否则不互换 。 对于升序排序通过这种相
邻数的互换, 使小数向前移动, 大数向后移动;从前向后
进行一次冒泡 (相邻数的互换 ),就会把最大的数换到最后;
再进行一次冒泡就会把次大的数排在倒数第二的位置 。 依
此类推, 完成由小到大的排序 。
编程中选用 R7做比较次数计数器,初始值为 09H,位地址
00H作为冒泡过程中是否有数据互换的标志位,若 (00H)
=0,表明无互换发生,已排序完毕。 (00H) =1,表明有
互换发生。流程图如图 4-11所示。
ORG 0400H
START,MOV R0,#40H ;数据区首址送 R0
MOV R7,#09H ;各次冒泡比较次数送 R7
CLR 00H ;互换标志位清零
LOOP,MOV A,@R0 ;取前数送 A中
MOV 2BH,A ;暂存到 2BH单元中
INC R0 ;修改地址指针
MOV 2AH,@R0 ;取后数暂存到 2AH单元中
CLR C ;清 CY
SUBB A,@R0 ;前数减后数
JC NEXT ;前数小于后数, 则转 (不互换 )
MOV @R0,2BH ;前数大于后数, 两数交换
DEC R0
MOV @R0,2AH
INC R0 ;地址加 1,准备下一次比较
SETB 00H ;置互换标志
NEXT,DJNZ R7,LOOP ;未比较完, 进行下一次比较
JB 00H,START ;有交换, 表示未排完序, 进行下一轮冒泡
END ;无交换, 表示已排好序, 结束
开 始
数 据 区 受 地 址 R
0
比 较 次 数 R
7
置 交 换 标 志
取 前 一 个 操 作 数
取 后 一 个 操 作 数
前 数 < 后 数?
前 数 和 后 数 交 换
置 交 换 标 志?
本 轮 比 较 完 毕?
本 轮 有 交 换?
结 束
Y
Y
N
Y
N
图 4.10 程序流程图
4.5 子程序设计
在汇编语言源程序中使用子程序时, 一般要注意
两个问题:参数传递和现场保护 。 参数传递一般可采
用以下方法:
传递数据 。 将数据通过工作寄存器 R0~ R7或累加
器来传送 。 即主程序和子程序在交接处, 上述寄存器
和累加器存储的是同一参数 。
传送地址 。 数据存放在数据寄存器中, 参数传递
时只通过 R0,R1,DPTR传递数据所存放的地址 。
通过堆栈传递参数 。 在调用之前, 先把要传送的
参数压入堆栈, 进入子程序之后, 再将压入堆栈的参
数弹出到工作寄存器或者其他内存单元 。
通过位地址传送参数 。 例如:
SUBROU,PUSH ACC
PUSH B
PUSH R0
POP R0
POP B
POP PSW
POP ACC
RET
至于每个具体的子程序是否需要现场保护, 以及哪些参
数应该保护, 则应视具体情况而定 。
【 例 4-15】 单字节二进制数据转换为 BCD码子程序
SBTOD。
功能:将单字节二进制数转换为三位 BCD码 。
入口,R2中存放要转换的二进制数 。
出口,(R0)给出百位 BCD码的存放地址 。 (R0)+1
给出十位和个位 BCD码的存放地址, 高半字节放十位,
低半字节放个位 。
占用寄存器,A,B,R0,R2。
SBTOD,MOV SP,#60H
PUSH ACC
MOV A,R2 ;取数
MOV B,#64H
DIV AB ;除以 100,(A)为百位数
MOV @R0,A ;存人 (R0)单元
MOV A,#0AH
XCH A,B ;余数 (B)送 A
DIV A,B ;除以 10,得十位和个位
SWAP A ;十位数放于高半字节
ADD A,B ;个位数放于低半字节
INC R0
MOV @R0,A ;个位存入 (R0)+1单元
POP ACC
RET
【 例 4-16】 将内部数据存储器某一单元中的一个字节的
十六进制数转换成两位 ASCII码, 结果存放在内部数据存储
器的两个连续单元中 。
假设一个字节的十六进制数在内部数据存储器 40H单元,
结果存于 41H,42H单元中,用堆栈进行参数传递。
MAIN,MOV SP,#55H
MOV R1,#41H ; R1为存结果指针
MOV A,40H ;取要转换的数据
SWAP A ;先转换高位字节
PUSH ACC ;压栈
LCALL HEASC ;调用低半字节转换成
ASCII码程序
POP ACC ; 要转换的数据出栈
MOV @ R1, A ;存高半字节转换结果
INC R1
PUSH 40H
LCALL HEASC
POP ACC
MOV @ R1,A ;存低半字节转换结果
END
HEASC,MOV R0,SP
DEC R0
DEC R0
XCH A,@R0 ;取被转换数据
AND A,# 0FH ;保留低半字节
ADD A,#2 ;修改 A
MOVC A,@A+PC ;查表
XCH A,@R0 ;结果送回堆栈
RET
TAB,DB 30H,31H,32H,…
【 例 4-17】 求两个无符号数据块中的最大值 。 数据
块的首地址分别为 60H和 70H,每个数据块的第一个字
节都存放数据块的长度, 结果存人 5FH单元 。
解 本例可采用分别求出两个数据块的最大值, 然
后比较其大小的方法, 求最大值的过程可采用子程序 。
子程序名称,QMAX。
子程序入口条件,R1中存有数据块首地址。出口
条件:最大值在 A中,
下面分别编写主程序和子程序 。
主程序:
ORG 2000H
MOV SP,#2FH ;设堆栈指针
MOV R1,#60H ;取第一数据块首地址送 R1中
ACALL QMAX ;第一次调用求最大值子程序
MOV 40H,A ;第一个数据块的最大值暂存 40H
MOV R1,#70H ;取第二数据块首地址送 R1中
ACALL QMAX ;第二次调用求最大值子程序
CJNE A,40H,NEXT ;两个最大值进行比较
NEXT,JNC LP ; A大, 则转 LP
MOV A,40H ; A小, 则把 40H中内容送人 A
LP,MOV 5FH,A
SJMP $
子程序:
ORG 2200H
QMAX, MOV A,@R1 ;取数据块长度
MOV R2,A ; R2做计数器
CLR A ; A清零, 准备做比较
LP1,INC R1 ;指向下一个数据地址
CLR C ; 0+cY,准备做减法
SUBB A,@R1 ;用减法做比较
JNC LP3 ;若 A大, 则转 LP3
MOV A,@R1 ; A小, 则将大数送 A中
SJMP LP4 ;五条件转 LP4
LP3,ADD A,@R1 ;恢复 A中值
LP4,DJNZ R2,LP1 ;计数器减 1,不为零, 转继续比较
RET ;比较完, 子程序返回
汇编语言程序设计
所谓程序设计,就是按照给定的任务要求,编写
出完整的计算机程序。要完成同样的任务,使用的方
法或程序并不是唯一的。因此,程序设计的质量将直
接影响到计算机系统的工作效率、运行可靠性。
前面我们学过了汇编语言形式的指令系统,本章
重点介绍汇编语言程序结构以及如何利用汇编语言指
令进行程序设计的方法。
4.1 汇编语言程序设计概述
4.1.1 汇编语言程序设计步骤
使用汇编语言设计一个程序大致上可分为以下几个步骤 。
(1) 分析题意, 明确要求 。
(2) 确定算法 。
(3) 画程序流程图, 用图解来描述和说明解题步骤 。
图 4.1 常用的流程图符号
(4) 分配内存工作单元, 确定程序与数据区的存放地址 。
(5) 编写源程序
(6) 程序优化 。
( 7)上机调试、修改和最后确定源程序。
4.2.2 伪指令语句
伪指令并不是真正的指令, 也不产生相应的机器
码, 它们只是在计算机将汇编语言转换为机器码时,
指导汇编过程, 告诉汇编程序如何汇编 。 下面介绍一
些 MCS-51汇编程序常用的伪指令 。
( 1) 汇编起始伪指令 ORG
格式,[标号,] ORG 16位地址
功能:规定程序块或数据块存放的起始地址 。 如:
ORG 8000H
START,MOV A, #30H
……
该指令规定第一条指令从地址 8000H单元开始存放,
即标号 START的值为 8000H。
( 2) 汇编结束伪指令 END
格式,[标号,] END [表达式 ]
功能:结束汇编 。
例如:
ORG 2000H
START,MOV A, # 00H
……
END START
表示标号 START开始的程序段结束 。
( 3) 等值指令 EQU
格式:字符名称 EQU 项
例如, TEST EQU R0
MOV A,TEST
( 4) 定义字节指令 DB
格式,[标号,] DB 8位二进制数表
DB命令是从指定的地址单元开始, 定义若干个 8
位内存单元的内容 。 例如,
ORG 1000H
TAB; DB 23H,73,,6”,,B”
TABl,DB 110B
以上伪指令经汇编以后, 将对从 1000H开始的若干
内存单元赋值:
(1000H)=23H (1001H)=49H
(1002H)=36H (1003H)=42H
(1004H)=06H
其中 36H和 42H分别是字符 6和 B的 ASCII码, 其余
的十进制数 ( 73) 和二进制数 ( 110B) 也都换算为十
六进制数了 。
( 5) 定义字命令 DW
格式,[标号,] DW 16位二进制数表
例如,
ORG 1000H
TAB,DW 1234H, 0ABH, 10
汇编后:
( 1000H) =12H (1001H ) = 34H
(1002H ) = 00H ( 1003H ) = ABH
(1004H ) =00H (1005H) =0AH
DB,DW伪指令都只对程序存储器起作用,不能
用来对数据存储器的内容进行赋值或进行其它初始化
的工作。
4.2 顺序程序设计
顺序结构程序是一种最简单, 最基本的程序 (也称为简单
程序 ),它是一种无分支的直线形程序, 按照程序编写的顺序
依次执行 。
【 例 4-1】 两个 8位无符号数相加, 和仍为 8位 。
假设两个无符号数 X1,X2分别存放于内部 RAM60H、
61H单元中, 求其和并将和送入 62H单元 。
程序如下:
ORG 0000H
CLR C
MOV R0, # 60H ;设 R0为数据指针
MOV A,@R0 ;取 X1
1NC R0
ADDC A,@R0 ; X1+X2
1NC R0
MOV @R0,A ;保存结果
END
【 例 4-2】 两个无符号双字节数相加 。 设被加数存放在内部存
储器 30H( 高位字节 ), 31H( 低位字节 ) 单元, 加数存放在内
部存储器 40H( 高位字节 ), 41H( 低位字节 ) 单元, 和存入 30H
( 高位字节 ), 31H( 低位字节 ) 单元 。
程序如下:
ORG 0000H
CLR C ;将 C清零
MOV R0, #31H ;送被加数首址
MOV R1, #41H ;送加数首址
MOV A,@R0 ;取被加数低字节
ADD A,@R1 ;两个低字节相加
MOV @R0, A ;低字节和存人被加数低字节
DEC R0 ;修改指针, 指向被加数高字节
DEC R1 ;修改指针, 指向加数高字节
MOV A,@R0 ;取被加数高字节
ADDC A,@R1 ;高字节相加
MOV @R0, A ;存结果
END
【 例 4-3】 编写 16位二进制数求补程序
二进制数的求补可归结为, 求反加 1”的过程, 求反可用
CPL指令实现;加 1时应注意, 加 1只能加在低 8位的最低位上 。
因为现在是 16位数, 有两个字节, 因此要考虑进位问题, 即低 8
位取反加 1,高 8位取反后应加上低 8位加 1时可能产生的进位,
还要注意这里的加 1不能用 INC指令, 因为 INC指令不影响 CY标
志 。
程序如下:ORG 0200H
MOV A,R0 ;低 8位送 A
CPL A ;取反
ADD A,#01H ;加 l
MOV R2, A ;存结果
MOV A,R1 ;高 8位送 A
CPL A ;取反
ADDC A,#00H ;加进位
MOV R3, A ;存结果
END
【 例 4-4】 编程将 20H单元中的 8位无符号二进制数转换成 3位
BCD码, 并存放在 22H(百位 )和 21H(10位, 个位 )两个单元中 。
程序如下:
ORG 1000H
MOV A,20H ;取数送 A
MOV B, # 64H ;除数 100送 B中
DIV AB ; 商 (百位数 BCD码 )在 A中, 余数在 B中
MOV 22H, A ;百位数送 22H
MOV A,B ;余数送 A做被除数
MOV B, #0AH ;除数 10送 B中
DIV AB ;十位数 BCD码在 A中, 个位数在 B中
SWAP A ;十位数 BCD码移至高 4位
ORL A,B ;并入个位数的 BCD码
MOV 21H, A ;十位, 个位 BCD码存人 21H
END
分支程序的结构有两种, 如图 4.2所示 。
图 4.2 分支程序结构
图 4.2( a) 结构使用条件转移指令来实现分支,
当给出的条件成立时, 执行程序段A, 否则执行程序
段B 。
图 4.2 (b) 结构使用散转指令 JMP来实现多分支转
移,它首先将分支程序按序号的值来实现分支转移。
【 例 4-5】 设补码 X放在内部 RAM30H单元中, 函数
Y与 X有如下的关系式:
试编写程序, 根据 X的值求出 Y,并放回原单元 。
解 取出 X后先做取值范围的判断, 用累加器 A状态转
移指令判断 X是否为 0,用位状态转移指令判断 X是大
于 0还是小于 0。 程序流程图如图 4.3所示 。
程序如下:
4.3 分支程序设计
MOV A,30H
JZ ZER0
JNB ACC.7,PLUS
ADD A,#5
MOV 30H,A
PLUS,SJMP $
ZERO,MOV 30H,#20H
SJMP $
END
开始
结束
A=0?
A>0?
(30H) A
(30H)+5 (30H)
20H (30H)
图4,3 [ 例4- 5 ] 程序框图
【 例 4-6】 内部 RAM40H和 41H单元中各有
一无符号数, 比较其大小, 将大数存放于内部
RAM60H单元, 小数存放于内部 RAM61H单元,
如两数相等, 则分别送往这 2个单元 。
解 用比较不等转移指令 CJNE比较力两个
无符号书, 先确定它们是否相等, 若不相等时
再根据借位标志确定这两个无符号书的大小 。
程序框图如图 4.4所示 。
程序如下:
Y
开始
A (41H)
A与 (6 1H )交 换
结束
(40H) (60H)
A (60H)
N
图4,4 [例 4- 6] 程序框图
(40H) A
MOV A,40H
MOV 61H,41H
CJNE A, 41H,
LOOP
AJMP AGEQ
LOOP,JNC AGEQ ; A≥(41H)则无借位
XCH A,61H ; A< (41H)有借位
AGEQ,MOV 60H,A ; A与 (61H)交换
SJMP $
END
【 例 4-7】 某温度控制系统, 采集的温度值 ( Ta) 放在累加器
A中 。 此外, 在内部 RAM54H单元存放控制温度下限制 ( T54),
在 55H单元存放控制温度上限制 ( T55) 。 若 Ta >T55,程序转向
JW( 降温处理程序 ) ;若 Ta<T54,则程序转向 SW( 升温处理程
序 ) ; T55≥Ta≥T54,则程序转向 FH( 返回主程序 ) 。
程序如下:
CJNE A,55H,LOOP1 ; Ta≠T55,转向 LOOPl
AJMP FH ; Ta=T55,返回
LOOPl,JNC JW ;若 (CY)=0,表明 Ta>T55,转降温处理程序
CJNE A,54H,LOOP2 ; Ta≠T54,转向 LOOP2
AJMP FH ; Ta=54,返回
LOOP2,JC SW ;若 (CY)=1,表明 Ta<T54,转升温处理程序
FH,RET ; T55≥Ta≥T54,返回主程序
【 例 4-8】 将 ASCII码制转换为十六进制数 。 如果不是十六
进制数得 ASCII码, 用户标志位置 1。
解:由 ASCII码表知, 30H~ 39H为 0~ 9的 ASCII码, 41H~
46H为 A~ F的 ASCII码 。 在这一范围的 ASCII码减去 37H就可以
获得对应的十六进制 。
设 ASCII码放在累加器 A中, 转换结果放回 A中 。
程序流程图如图 4.5所示
START,CLC C
SUBB A,#30H
JC NASC
CJNE A,# 0AH,NN
MM,JC ASC
SUBB A,# 07H
CJNE A,# 0AH,NN
NN,JC NASC
CJNE A,# 10H,LL
LL,JC ASC
NASC,SETB F0
ASC,RET
开始
A (A)-30H
(A) 0?
(A) 0AH?
A (A)-07H
(A)>09H?
(A)>10H?
F0 1
返回
N
N
N
N
图4, 5 [ 例4 - 8 ] 程序框图
【 例 4-9】 N路分支程序, N≤8。 要求程序根据其运
行中所产生的寄存器 R3的值, 来决定如何进行分支 。
解:为实现 N路分支, 可以多次使用比较转移指令:
CJNE R3,#data, rel流程图如图 4.6所时, 但这样比
较次数太多, 当 N较大时, 执行速度就较慢 。
我们可以用变址寻址的转移指令 JMP@A+DPTR使
问题得到解决 。 首先安排存放一个转移分支入口的地
址表, 把入口地址表的首地址送到 DPTR,再把 R3的
内容送累加器 A。 现以 4个分支为例写出程序 。 假设 4
个分支的不同功能分别是给不同的位置 。
程序如下:
MOV A, R3
MOV DPTR, #BRTAB
MOVC A, @A+DPTR
JMP @A+DPTR
BRTAB DB BR0-BRTAB
DB BRl-BRTAB
DB BR2-BRTAB
DB BR3-BRTAB
BR0,SETB P1.0
SJMP BRK
BR1,SETB P1.1
SJMP BRK
BR2,SETB P1.2
SJMP BRK
BR3,SETB P1.3
BRK,SJMP BRK
R 3 = 0
R 3 = 2
R 3 = 1
Y
Y
N
N
N
Y
转 向 第 0 分 支
转 向 第 1 分 支
转 向 第 2 分 支
4.4 循环程序设计
循环程序一般由 4部分组成 。
( 1) 置循环初值 。
( 2) 循环体 。
( 3) 循环修改 。
( 4) 循环控制 。
图 4.7(a)结构是, 先执行后判断,, 适用于循环次数已
知的情况 。
图 4.7(b)结构是, 先判断后执行,, 适用于循环次数
未知的情况 。
Y
N
开 始
置 循 环 初 值
循 环 处 理
循 环 修 改
结 束 处 理
循 环 结 束?
结 束
Y
( a ) 先 执 行 后 判 断
开 始
结 束
置 循 环 初 值
循 环 结 束?
循 环 处 理
循 环 修 改
结 束 处 理
N
( b ) 先 判 断 后 执 行
二, 程序清单
ORG OOOOH
START, MOV A,#01H ;使 L1灯亮, 其它不亮
LOOP,MOV P1,A ;从 P1口输出到发光二极管
MOV R1,#10H ;延时 1秒
DEL1,MOV R2,#200
DEL2,MOV R3,#126
DEL3,DJNZ R3,DEL3
DJNZ R2,DEL2
DJNZ R1,DEL1
RL A ;左移一位, 下一个发光二极管亮
AJMP LOOP ;循环
END
【 例 4-10】 多个单字节数求知 。
已知有 10个单字节数, 依次存放在内部
RAM 40H单元开始的数据存储区中, 求和并将
结果存人寄存器 R2,R3中 (高位存 R2,低位存
R3)。
本题中,要重复进行加法运算,因此采用
循环结构程序。循环次数就是数据块字节数,这
是已知的。在置初值时,将数据块长度置人寄存
器 R5;将数据块首地址送人寄存器 R0,即以 R0
作为数据块的地址指针,采用间接寻址方式:每
做一次加法之后,修改地址指针,以便取出下一
个数来相加,并且使计数器 R5减 l。到 R5减为 0
时,求和结束。程序流程图如图 4.8所示。
ORG 2000H
SUM,MOV R0,#40H ;设地址指针
MOV R5,#0AH ;计数器初值送 R5
SUM,MOV A,#00H
MOV R2,A
LP,ADD A,@R0
JNC LP1
INC R2 ;若有进位, 和的高八位 +1
LP1,INC R0 ;地址指针 +1
DJNZ R5,LP ;判循环结束条件
MOV R3,A ;存和的低八位
END
【 例 4-11】 从内存 BLOCK单元开始有一个无符号
数的数据块, 其长度为 LEN,试求出其最大值, 并存
入 MAX单元 。
这是一个搜索问题 。 这里采用依次进行比较和取
代的方法来寻找最大值 。 具体做法是:先取出第一个
数作为基准, 和第二个数比较, 若比较结果基准数大,
不作变动;若比较结果基准数小, 则用大数来代替原
基准数, 然后再和下一个数作比较 。 到比较结束时,
基准数就是所求的最大值 。
为了进行两数的比较,采用两数相减以后判断 CY
的值来确定哪个数大,这比用 CJNE指令更简单。比较
时将基准数放在累加器 A中。若 A中先放人零,比较次
数等于 LEN;若 A中先放人第一个数,则比较次数等于
LEN-1。采用 R2作为计数器,R1作为地址指针。程序
流程如图 4.9所示。
ORG 8000H
COMP,CLR A
MOV R2,# LEN
MOV R1,# BLOCK
LOOP,CLR C ;清 CY
SUBB A,@R1 ;用减法作比较
JNC NEXT ;若( A) > ((R1)), 转移
MOV A,@R1 ;若( A) < ((R1)), 则(( R1)) → ( A)
SJMP NEXT1
NEXT,ADD A,@R1 ;恢复( A)
NEXT1,INC R1
DJNZ R2,LOOP ; 判循环结束条件
MOV MAX,A ;存最大数
SJMP $
LEN EQU 0AH
MAX EQU 40H
END EQU 41H
图 4.8 程序流程图 图 4.9 程序流程图
N
结束
设计数器初值设地址
指针求和单元清零
和低8 位+ (指针)
近位= 1?
和高8 位+ 1
计数值- 1 = 0?
指针+ 1
存和数
Y
开始
Y
N
开始
结束
0 A
计数器初值 R 2
指针值 R 1
(A)>((R1))?
((R1)) A
(R1)+1 R1
(R2)-1=0?
存最大数
Y
【 例 4-12】 假设从内存 RAM的 50H单元, 连续存放一串字
符, 以回车符 (其 ASCII码为 0DH)作为结束标志, 要求测出该
字符串的长度 。 测试方法可采用将该字符串的每一个字符与
回车符依次相比, 若不相等, 则将统计字符串长度的计数器
加 l,继续比较;若比较相等, 则表示该字符串结束, 这时计
数器中的值就是字节符串的长度 。
程序如下:
ORG 8000H
COUNT,MOV R2,#0FFH
MOV R0,#4FH
LOOP,INC R0
INC R2
CJNE @ R0,#0DH,LOOP
SJMP $
程序中使用指令 CJNE @R0,# 0DH, LOOP实现字
符串比较及控制循环的任务 。 由于在循环体中比较转移之
前, 将 R2和 R0的内容加1, 因此 R2和 R0的初值要减去
1 。 循环次数由对于循环条件的判定而定, 当循环结束时,
R2中的内容就是字符串的长度 。
【 例 4-13】 编制用软件方法延时1 S的程序
软件延时时间与执行指令的时间有关 。 如果使用 6MHz晶
振, 一个机器周期为 2μs 。 计算出执行每一条指令以及一
个循环所需要的时间, 根据要求的延时时间确定循环次数,
如果单循环时间不够长, 可以采用多重循环 。
程序如下:
MOV R5,#05H
DELY0,MOV R6,#0C8H
DELY1,MOV R7,#0F8H
NOP
DELY2,DJNZ R7,DELY2
DJNZ R6,DELY1
DJNZ R5,DELY0
这是一个三重循环程序 。 前4条指令的机器周期
数为1, 后3条指令的机器周期数为2 。 执行内循环
所用的机器周期数为 248× 2=496,执行中间循环所用
的机器周期数 (496+4)× 200=100000;执行外循环所用
的机器周期数为 (100000+3)× 5=500015,再加上1
( 执行第一条指令 ) 就是执行整段程序所用的机器周
期数 。 因此这段程序的延时时间位 ( 500015+1)
× 2μs=1.000032s。
【 例 4-14】 编写无符号数排序程序 。
假设在片内 RAM中, 起始地址为 40H的 10个单元中
存放有 10个无符号数 。 试进行升序排序 。
解 数据排序常用方法是冒泡排序法 。 这种方法的过
程类似水中气泡上浮, 故称冒泡法 。 执行时从前向后进行
相邻数的比较, 如数据的大小次序与要求的的顺序不符就
将这两个数互换, 否则不互换 。 对于升序排序通过这种相
邻数的互换, 使小数向前移动, 大数向后移动;从前向后
进行一次冒泡 (相邻数的互换 ),就会把最大的数换到最后;
再进行一次冒泡就会把次大的数排在倒数第二的位置 。 依
此类推, 完成由小到大的排序 。
编程中选用 R7做比较次数计数器,初始值为 09H,位地址
00H作为冒泡过程中是否有数据互换的标志位,若 (00H)
=0,表明无互换发生,已排序完毕。 (00H) =1,表明有
互换发生。流程图如图 4-11所示。
ORG 0400H
START,MOV R0,#40H ;数据区首址送 R0
MOV R7,#09H ;各次冒泡比较次数送 R7
CLR 00H ;互换标志位清零
LOOP,MOV A,@R0 ;取前数送 A中
MOV 2BH,A ;暂存到 2BH单元中
INC R0 ;修改地址指针
MOV 2AH,@R0 ;取后数暂存到 2AH单元中
CLR C ;清 CY
SUBB A,@R0 ;前数减后数
JC NEXT ;前数小于后数, 则转 (不互换 )
MOV @R0,2BH ;前数大于后数, 两数交换
DEC R0
MOV @R0,2AH
INC R0 ;地址加 1,准备下一次比较
SETB 00H ;置互换标志
NEXT,DJNZ R7,LOOP ;未比较完, 进行下一次比较
JB 00H,START ;有交换, 表示未排完序, 进行下一轮冒泡
END ;无交换, 表示已排好序, 结束
开 始
数 据 区 受 地 址 R
0
比 较 次 数 R
7
置 交 换 标 志
取 前 一 个 操 作 数
取 后 一 个 操 作 数
前 数 < 后 数?
前 数 和 后 数 交 换
置 交 换 标 志?
本 轮 比 较 完 毕?
本 轮 有 交 换?
结 束
Y
Y
N
Y
N
图 4.10 程序流程图
4.5 子程序设计
在汇编语言源程序中使用子程序时, 一般要注意
两个问题:参数传递和现场保护 。 参数传递一般可采
用以下方法:
传递数据 。 将数据通过工作寄存器 R0~ R7或累加
器来传送 。 即主程序和子程序在交接处, 上述寄存器
和累加器存储的是同一参数 。
传送地址 。 数据存放在数据寄存器中, 参数传递
时只通过 R0,R1,DPTR传递数据所存放的地址 。
通过堆栈传递参数 。 在调用之前, 先把要传送的
参数压入堆栈, 进入子程序之后, 再将压入堆栈的参
数弹出到工作寄存器或者其他内存单元 。
通过位地址传送参数 。 例如:
SUBROU,PUSH ACC
PUSH B
PUSH R0
POP R0
POP B
POP PSW
POP ACC
RET
至于每个具体的子程序是否需要现场保护, 以及哪些参
数应该保护, 则应视具体情况而定 。
【 例 4-15】 单字节二进制数据转换为 BCD码子程序
SBTOD。
功能:将单字节二进制数转换为三位 BCD码 。
入口,R2中存放要转换的二进制数 。
出口,(R0)给出百位 BCD码的存放地址 。 (R0)+1
给出十位和个位 BCD码的存放地址, 高半字节放十位,
低半字节放个位 。
占用寄存器,A,B,R0,R2。
SBTOD,MOV SP,#60H
PUSH ACC
MOV A,R2 ;取数
MOV B,#64H
DIV AB ;除以 100,(A)为百位数
MOV @R0,A ;存人 (R0)单元
MOV A,#0AH
XCH A,B ;余数 (B)送 A
DIV A,B ;除以 10,得十位和个位
SWAP A ;十位数放于高半字节
ADD A,B ;个位数放于低半字节
INC R0
MOV @R0,A ;个位存入 (R0)+1单元
POP ACC
RET
【 例 4-16】 将内部数据存储器某一单元中的一个字节的
十六进制数转换成两位 ASCII码, 结果存放在内部数据存储
器的两个连续单元中 。
假设一个字节的十六进制数在内部数据存储器 40H单元,
结果存于 41H,42H单元中,用堆栈进行参数传递。
MAIN,MOV SP,#55H
MOV R1,#41H ; R1为存结果指针
MOV A,40H ;取要转换的数据
SWAP A ;先转换高位字节
PUSH ACC ;压栈
LCALL HEASC ;调用低半字节转换成
ASCII码程序
POP ACC ; 要转换的数据出栈
MOV @ R1, A ;存高半字节转换结果
INC R1
PUSH 40H
LCALL HEASC
POP ACC
MOV @ R1,A ;存低半字节转换结果
END
HEASC,MOV R0,SP
DEC R0
DEC R0
XCH A,@R0 ;取被转换数据
AND A,# 0FH ;保留低半字节
ADD A,#2 ;修改 A
MOVC A,@A+PC ;查表
XCH A,@R0 ;结果送回堆栈
RET
TAB,DB 30H,31H,32H,…
【 例 4-17】 求两个无符号数据块中的最大值 。 数据
块的首地址分别为 60H和 70H,每个数据块的第一个字
节都存放数据块的长度, 结果存人 5FH单元 。
解 本例可采用分别求出两个数据块的最大值, 然
后比较其大小的方法, 求最大值的过程可采用子程序 。
子程序名称,QMAX。
子程序入口条件,R1中存有数据块首地址。出口
条件:最大值在 A中,
下面分别编写主程序和子程序 。
主程序:
ORG 2000H
MOV SP,#2FH ;设堆栈指针
MOV R1,#60H ;取第一数据块首地址送 R1中
ACALL QMAX ;第一次调用求最大值子程序
MOV 40H,A ;第一个数据块的最大值暂存 40H
MOV R1,#70H ;取第二数据块首地址送 R1中
ACALL QMAX ;第二次调用求最大值子程序
CJNE A,40H,NEXT ;两个最大值进行比较
NEXT,JNC LP ; A大, 则转 LP
MOV A,40H ; A小, 则把 40H中内容送人 A
LP,MOV 5FH,A
SJMP $
子程序:
ORG 2200H
QMAX, MOV A,@R1 ;取数据块长度
MOV R2,A ; R2做计数器
CLR A ; A清零, 准备做比较
LP1,INC R1 ;指向下一个数据地址
CLR C ; 0+cY,准备做减法
SUBB A,@R1 ;用减法做比较
JNC LP3 ;若 A大, 则转 LP3
MOV A,@R1 ; A小, 则将大数送 A中
SJMP LP4 ;五条件转 LP4
LP3,ADD A,@R1 ;恢复 A中值
LP4,DJNZ R2,LP1 ;计数器减 1,不为零, 转继续比较
RET ;比较完, 子程序返回