第第第 汇汇汇汇汇汇汇汇
Jan 16, 2016
第四章 汇编语言程序设计
所谓程序设计,就是按照给定的任务要求,编写出完整的计算机程序。要完成同样的任务,使用的方法或程序并不是唯一的。因此,程序设计的质量将直接影响到计算机系统的工作效率、运行可靠性。
前面我们学过了汇编语言形式的指令系统,本章重点介绍汇编语言程序结构以及如何利用汇编语言指令进行程序设计的方法。
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 (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” 的过程,求反可用 C
PL 指令实现;加 1 时应注意,加 1 只能加在低 8 位的最低位上。因为现在是 16 位数,有两个字节,因此要考虑进位问题,即低8 位取反加 1 ,高 8 位取反后应加上低 8 位加 1 时可能产生的进位,还要注意这里的加 1 不能用 INC 指令,因为 INC 指令不影响 CY 标志。
程序如下: ORG 0200H
MOV A , R0 ;低 8 位送 ACPL A ;取反ADD A , #01H ;加 lMOV R2 , A ;存结果MOV A , R1 ;高 8 位送 A
CPL A ;取反ADDC A , #00H ;加进位MOV R3 , A ;存结果 (R1R0---R3R2)END
【例 4-4 】编程将 20H 单元中的 8 位无符号二进制数转换成3 位 BCD 码,并存放在 22H(百位 ) 和 21H(10 位,个位 ) 两个单元中。
程序如下: ORG 1000H
MOV A , 20H ;取数送 AMOV B , # 64H ;除数 100 送 B 中DIV AB ;商 (百位数 BCD 码 ) 在 A 中,余数在
B 中MOV 22H , A ;百位数送 22HMOV 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-5] 一变量放在内部 RAM 的 20H ,取值为 00H-05H 。编写程序,求该变量的平方值,将结果放片内 21H
ORG 1000H
START : MOV DPTR, #2000H; or MOV DPTR, #TABLE MOV A , 20H MOVC A , @A+DPTR MOV 21H, A SJMP $ ORG 2000HTABLE: DB 00, 01, 04, 09, 16, 25 END
图 4.2 分支程序结构
图 4.2 ( a ) 结构使用条件转移指令来实现分支,当给出的条件成立时,执行程序段A,否则执行程序段B。
图 4.2 (b) 结构使用散转指令 JMP 来实现多分支转移,它首先将分支程序按序号的值来实现分支转移。
4.3 分支程序设计
【例 4-6 】设补码 X 放在内部 RAM30H 单元中,函数 Y 与 X有如下的关系式:
试编写程序,根据 X 的值求出 Y ,并放回原单元。 解 取出 X 后先做取值范围的判断,用累加器 A状态
转移指令判断 X 是否为 0 ,用位状态转移指令判断 X是大于 0还是小于 0 。程序流程图如图 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-7 】内部 RAM40H 和 41H 单元中各有一无符号数,比较其大小,将大数存放于内部 RAM60H 单元,小数存放于内部 RAM61H单元,如两数相等,则分别送往这 2 个单元。 解 用比较不等转移指令 CJNE比较力两个无符号书,先确定它们是否相等,若不相等时再根据借位标志确定这两个无符号书的大小。程序框图如图 4.4 所示。程序如下:
Y
开始
A (41H)
A (61H)与 交换
结束
(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)有借位; A 与 (61H)交换
AGEQ:
MOV 60H , A
SJMP $
END
【例 4-8 】某温度控制系统,采集的温度值( 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-9 】 N路分支程序, N≤8 。要求程序根据其运行中所产生的寄存器 R3 的值,来决定如何进行分支。
MOV A , R3 MOV DPTR , #BRTAB MOVC A , @A+DPTR JMP @A+DPTRBRTAB AJMP BR0 AJMP BR1 AJMP BR2 AJMP BR3BR0 : SETB P1.0 SJMP BRKBR1 : SETB P1.1 SJMP BRKBR2 : SETB P1.2 SJMP BRKBR3 : SETB P1.3BRK : SJMP BRK
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 个单字节数,依次存放在内部 R
AM 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 2000H
COMP: CLR A
MOV R2, #LEN
MOV R1, #BLOCK
MOV R3, A
LOOP: CLR C
MOV A, R3
SUBB A, @R1
JNC NEXT ; A> ((R1))
MOV A, @R1 : A< ((R1))
MOV R3, A
NEXT: INC R1
DJNZ R2 , LOOP
MOV A, R3
MOV MAX, A
【例 4-12 】假设从内存 RAM 的 50H 单元,连续存放一串字符,以回车符 ( 其 ASCII 码为 0DH) 作为结束标志,要求测出该字符串的长度。测试方法可采用将该字符串的每一个字符与回车符依次相比,若不相等,则将统计字符串长度的计数器加 l ,继续比较;若比较相等,则表示该字符串结束,这时计数器中的值就是字节符串的长度。 程序如下:
ORG 8000HCOUNT: MOV R2 , # 0FFH MOV R0, # 4FH LOOP: INC R0 ;50H begin INC R2 ;0 begin CJNE @ R0, #0DH, LOOP SJMP $
【例 4-13 】编制用软件方法延时1 S 的程序软件延时时间与执行指令的时间有关。如果使用6MHz晶振,一个机器周期为 2µs(=12 /6M) 。计算出执行每一条指令以及一个循环所需要的时间,根据要求的延时时间确定循环次数,如果单循环时间不够长,可以采用多重循环。
程序如下: MOV R5 , #05H ;1 机器周期DELY0: MOV R6 , #0C8H ; 1DELY1 : MOV R7 , #0F8H ;1 NOP ;1DELY2 : DJNZ R7 , DELY2 ;2…..x 248=496 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 ;无交换,表示已排好序,结束
开始
数据区受地址 R0
比较次数 R7
置交换标志
取前一个操作数
取后一个操作数
前数<后数?
前数和后数交换
置交换标志?
本轮比较完毕?
本轮有交换?
结束
Y
Y
N
Y
N
图 4.10 程序流程图
4.5 子程序设计
在汇编语言源程序中使用子程序时,一般要注意两个问题:
1. 现场保护2. 参数传递
A. 在主程序中保护现场: PUSH ACC PUSH PSW
PUSH B PUSH R0
LCALL 。。。。 POP R0
POP B POP PSW POP ACC RET
B 、在子程序中保护SUB1: PUSH PSW PUSH ACC PUSH B MOV PSW, #10H POP B POP ACC POP PSW RET
参数传递 1--- 利用累加器和寄存器• 编写程序实现 c=a^2+b^2, a, b, c 存内部 RAM 30H-32H.START : MOV A , 30H ACALL SQR MOV R1 , A MOV A , 31H ACALL SQR ADD A , R1 MOV 32H , A SJMP $SQR: MOV DPTR, #TAB MOVC A, @A+DPTR RETTAB: DB 0, 1, 4, 9, ………81
参数传递 1--- 利用存储器传 递
参数传递 1--- 利用堆栈
利用存储器传 递: 在要传递的数据比较多时,可以将数据事先写入到程序存储器或在主程序中写入到数据存储器,进一步在子程序中读出
利用堆栈传 递: ,可以将数据主程序中压入到堆栈,进一步在子程序中弹出。也可以在子程序中压入,再在返回后弹出。
【例 4-16 】将内部数据存储器某一单元中的一个字节的十六进制数转换成两位 ASCII 码,结果存放在内部数据存储器的两个连续单元中。
假设一个字节的十六进制数在内部数据存储器 40H 单元,结果存于 41H 、 42H 单元中,用堆栈进行参数传递。
MAIN:
MOV SP , #55H
MOV R1 , #41H ; R1 为存结果指针
MOV A , 40H ;取要转换的数据
SWAP A ;先转换高位字节
PUSH ACC ;压栈
ACALL HEASC ;调用低半字节转换成 ASCII 码程序
POP ACC ; 要转换的数据出栈 MOV @ R1 , A ;存高半字节转换结果 INC R1
PUSH 40H
ACALL 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 ,下条到数据表差 2 字节 MOVC A , @A+PC ;查表 XCH A , @R0 ;结果送回堆栈 RET
TAB : DB 30H , 31H , 32H ,…
;ACALL 时 , SP=SP+2, 分别放入口地址 PC 15-8, PC 7-0 , 若 LCALL , SP=SP+3
【例 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 ;比较完,子程序返回
作业 1
• 若振荡频率为 12MHz, 分析下列时延程序的时延时间 DEL0 : MOV R7 , #200 DEL1 : MOV R6 , #123 NOP DEL2 : DJNZ R6 , DEL2 DJNZ R7 , DEL1 RET
作业 2
10 个无符号数依次放在 BLOCK1 开始的 10 个内存字节单元。
1) 编成寻找最小值,放内存 MIN 单元 ;
2) 编程将该数据块传输到 BLOCK2 开始的内存区。