7.4.2 散转程序
散转程序是分支程序的一种,使用指令JMP @A+DPTR,可实现多分支转移。它是根据某种输入或运算的结果,分别转向各个处理程序段取执行程序。下面以根据R2的内容转向各处理程序为例介绍几种散转程序的设计方法。
(1)转移指令表法(在2KB范围内转移)
把转移指令AJMP(或LJMP)顺序写入转移表。根据累加器A中的分支序号,通过JMP指令转向表中的某一条JMP指令,然后再执行AJMP指令,把程序转移到指定的分支入口。这种分支方法实际上是通过两次转移实现的。
例:根据R2的内容,转向各个处理程序。
1)题目分析
(R2)=0,转向PRG0
(R2)=1,转向PRG1
……
(R2)=255,转向PRG255
2)参考程序
MOV DPTR,#TAB ;转移表首地址
MOV A,R2 ;输入分支序号
ADD A,R2 ;乘2与转移指令双字节相对应
JNC NADD ;(R2)×2≤255跳到NADD
INC DPH ;(R2)×2 >255处理地址表高8位
NADD: JMP @A+DPTR ;散转至地址表
TAB: AJMP PRG0
AJMP PRG1
…
AJMP PRG255
3)程序说明
这个程序由于使用了AJMP指令,因此所有的处理程序入口PRG0、PRG1、…、PRG255和散转表TAB都必须在同一2KB范围内。如果一个2KB内放不下所有处理程序,可以把一些较长的处理程序放在其他地方,而在2KB内用LJMP指令转向这些处理程序。如上例PRG1放于2KB内的某地址处,而其实际的处理程序入口为WPRG1,可用如下指令实现:
PRG1: LJMP WPRG1
这个散转程序使用的AJMP指令为2字节指令,所以存于R2的分支序号需要乘以2才能保证正确地实现散转。若使用3字节的LJMP指令,存于R2的分支序号需要乘以3。如果(R2)乘以3大于255,则需要修改DPTR的高8位,程序如下:
MOV DPTR,#TAB
MOV A,R2
MOV B,#3
MUL AB
XCH A,B ;(R2)×3的高8位存在A中
ADD A,DPH ;(R2)×3的高8位加到DPH上
MOV DPH,A
XCH A,B ;(R2)×3的低8位在A中
JMP @A+DPTR
TAB: LJMP PRG0
LJMP PRG1
…
LJMP PRG255
(2)地址偏移表法(在256B范围内转移)
如果转向的程序均在同一页(256B),可以使用地址偏移表来实现转移。
例:案R2的内容转向4个处理程序。
1)题目分析
(R2)=0、1、2、3,分别转向PRG0、PRG1、PRG2、PRG3。
该方法利用指令JMP @A+DPTR与伪指令DB汇编时的计算功能实现散转,例如当(R2)=0时,执行MOVC A,@A+DPTR后,A中为PRG0~TAB,执行JMP @A+DPTR时,(A)+(DPTR)=PRG0-TAB+TAB=PRG0,故转向PRG0.
使用这种方法,转移表的大小加上各个程序长度必须小于256B,转移表和各处处理程序可以位于程序存储器的任何地方。
2)参考程序
MOV A,R2
MOV DPTR,#TAB ;转移地址表表首
MOVC A+@A+DPTR
JMP @A+DPTR
TAB: DB PRG0-TAB ;转移程序与转移地址首表的偏移量
…
DB PRG3-TAB
PRG0: 处理程序0
…
PRG3: 处理程序3
(3)转向地址表法(64KB范围内转移)
当转向范围较大时,可以直接使用转向地址表的方法,它的各项表格为各个转向程序的入口。散转时,使用查表指令,按某个单元的内容查表,找到对应的转向地址,把它装入DPTR中。然后将累加器A清0,再利用JMP @A+DPTR指令直接转向相应的处理程序。
例:根据(R2)转向各个处理程序
参考程序:
MOV DPTR,#TAB
MOV A,R2
ADD A,R2 ;A←(R2)×2
JNC NADD
INC DPH
NADD: MOV R3,A ;暂存
MOVC A,@A+DPTR
XCH A,R3 ;转移地址高8位
INC A
MOVC A,@A+DPTR
MOV DPL,A ;转移地址低8位
MOV DPH,R3
CLR A
JMP @A+DPTR
TAB: DW PRG0
DW PRG1
…
DW PRGn
用这种方法可以实现64KB范围内的转移,但散转数n≤255。如果n>255则应采用双字节加法运算来修改DPTR。
例:根据n(n>256)转向相应的处理子程序,n放在(R2、R3)中,各子程序入口地址放于双字节表格中。
参考程序:
MOV DPTR,#TAB
CLR C
MOV A,R3
RLC A
MOV R3,A
XCH A,R2
RLC A
XCH A,R2
ADD A,DPL
MOV DPL,A
MOV A,DPH
ADDC A,R2
MOV DPH,A
CLR A
MOVC A,@A+DPTR
MOV R2,A
CLR A
INC DPTR
CLR A
MOVC A,@A+DPTR
MOV DPL,A
MOV DPH,R2
CLR A
JMP @A+DPTR
TAB: DW PRG0
DW PRG1
…
DW PRGn
(4)通过堆栈操作实现多分支程序转移
分支入口程序地址放在表TAB中,表TAB为16位地址。根据分支程序序号,使用查表指令,实现多分支程序的转移。
例:根据(R2)转向n个处理程序(n≤255)
参考程序:
MOV SP,#60H ;设堆栈指针
MOV DPTR,#TAB ;分支程序入口地址表首地址
MOV A,R3
RL A ;分支序号×2
MOV R1,A ;暂存A
INC A ;先查地址低8位
MOVC A,@A+DPTR ;取地址低8位
PUSH ACC ;地址低9位压桟
MOV A,R1 ;恢复A
MOVC A,@A+DPTR ;取地址高8位
PUSH ACC ;地址高8位压桟
RET ;利用RET指令把PRGn弹给PC
TAB: DW PRG0
…
DW PRGn
7.5 常用子程序集
7.5.1子程序设计和参数传递方法
子程序是指能完成某一确定的任务并能被其他程序反复调用的程序段。调用子程序的程序称为调用程序。采用子程序结构可使程序简化,便于调试,并可实现程序模块化。子程序在结构上应具有通用性和独立性。
(1)编写子程序时应注意
1)程序第一条指令的地址称为入口地址。该指令前必须有标号,最好以子程序的任务定名。例如,显示程序常以DISP作为标号。
2)调用子程序指令设在主程序中,返回指令放在子程序的末尾。
3)子程序调用和返回指令能自动保护和恢复断点,但对于需要保护的寄存器和内存单元的内容,必须在子程序开始和末尾(RET指令前)安排保护和恢复它们的指令。
4)为使所编程序可以放在64KB程序存储器的任何地方并能被主程序调用,子程序内部必须使用相对转移指令而不能使用其他转移指令,以便汇编时生产浮动代码。
(2)参数传递方法
在调用子程序时会遇到主程序与子程序之间参数如何传递的问题。
入口参数:主程序调用子程序时,传入子程序的参数称为入口参数。
出口参数:子程序运算出的结果称为出口参数。
主程序把入口参数放到某些约定位置。主程序从约定位置得到运算结果。
1)用工作寄存器或累加器传递参数
例:编写完成c=a*a+b*b的程序。设a和b均为小于10的整数,a、b、c放在内部RAM的XA、XB、XC三个单元中。
◆题目分析
本程序由主程序和子程序组成。主程序通过A累加器传递子程序入口参数a或b,子程序也通过A累加器传递出口参数a平方或b平方给主程序,子程序为求平方的通用子程序。
◆硬件资源分配
内部RAM 40H~42H:存a、b、c
入口参数:(A)=a或b
出口参数:(A)=a*a或b*b
◆参考程序
ORG 2000H
XA DATA 40H
XB DATA 41H
XC DATA 42H
MOV A,XA ;入口参数a送A
ACALL SQR ;求a平方
MOV XC,A ;a平方送XC
MOV A,XB ;入口参数b送A
ACALL SQR ;求b平方
ADD A,XC ; a*a+b*b送A
MOV XC,A ;存结果
SJMP $
SQR: MOV B,A
MUL AB
RET
END
2)用指针寄存器传递参数
例:将R0和R1指出的内部RAM中两个3字节无符号数相加,结果送R0指出的内部RAM单元。
◆题目分析
R0指向加数单元低字节,R1指向被加数单元低字节。
◆硬件资源分配
R7:存放字节数
入口参数R0:指向加数低字节单元,R1:指向被加数低字节单元
出口参数R0:指向和高字节的单元
◆参考程序
NADD: MOV R7,#3
CLR C
NADD1:MOV A,@R0
ADDC A,@R1
MOV @R0,A
INC R0
INC R1
DJNZ R7,NADD1
DEC R0
RET
3)用堆栈传递参数
例:在寄存器R2中存有两位16进制数,试将它们分别转换成ASCII码,存入Y1和Y1+1单元。
◆题目分析
由于要进行两次转换,故可调用子程序来完成。在调用之前,先把要传送的参数压入堆栈。进入子程序后,再将压入堆栈的参数弹出到工作寄存器或者其他内存单元。这样的传送方法有一个优点,即可以根据需要将堆栈中的数据弹出到指定的工作单元。
◆硬件资源分配
内部RAM 30H~31H,存放两个转换的ASCII码
DPTR:数据表格表头地址
R2:待转换两位的16进制数
入口参数((SP)):两位16进制数
出口参数((SP)):1位16进制数对应的ASCII码
◆参考程序
ORG 3000H
Y1 DATA 30H
MOV SP,#50H ;设置堆栈指针初值
MOV DPTR,#TAB ;ASCII码表头地址送数据指针
PUSH 02H ;R2中16进制数进桟
ACALL HASC ;调用转换子程序
POP Y1 ;第一个ASCII码送Y1单元
MOV A,R2
SWAP A ;高4位与低4位交换
PUSH ACC ;第二个16进制数进桟
ACALL HASC ;再次调用
POP Y1+1 ;第二个ASCII码送Y1+1单元
SJMP $
HASC: DEC SP
DEC SP ;修改SP到参数位置
POP ACC ;弹出参数到A
ANL A,#0FH
MOVC A,@A+DPTR ;查表
PUSH ACC ;参数进桟
INC SP ;修改SP到返回地址
INC SP
RET
TAB: DB ‘0,1,2,3,4,5,6,7’
DB ‘8,9,A,B,C,D,E,F’
END
4)用程序段传递参数
当需要传递大量常数参数时,用以上几种方法不太有效,常采用程序段来传递参数。调用时,常数作为程序代码的一部分,紧跟在调用子程序后面。
例:编写发送字符串’MCS51 CONTROLLER’的程序,字符串以全0结束。
◆题目分析
字符串常数(ASCII码)放在程序存储器ROM中,用查表指令得到待发送数据,然后用串口发送指令发送数据。发送子程序SOUT不用RET指令返回,而是用JMP指令转移到DB定义的数据表格之后的第一条指令PROG0。
◆硬件资源分配
DPTR:数据表格表头地址
入口参数:((DPTR)):待发送数据
出口参数:((DPTR)):16位转移地址
◆参考程序
CLR TI
ACALL SOUT
TAB: DB ‘MCS51 CONTROLLER’
DB 00H
PROG0:…
…
SOUT: MOV DPTR,#TAB ;发送字符串子程序
SOUT1:CLR A
MOVC A,@A+DPTR ;查表指令查出发送字符
INC DPTR ;指向下一个字符
JZ SEND ;字符全为0则转SEND
MOV SBUF,A ;发送
JNB TI,$ ;等待发送结束
CLR TI ;软件清发送中断标志
SJMP SOUT1 ;发送下一个字符
SEND: JMP @A+DPTR ;返回主程序PROG0
◆程序说明
TI为串行发送中断标志。当发送结束时,TI由硬件置1,申请中断,必须由程序清0。