在用汇编语言编程时,应考虑到恰当地使用子程序,使整个程序的结构清楚,而且阅读和理解方便。使用子程序还可以减少源程序和目标程序的长度。在多次调用同样的程序段时,采用子程序就不必每次重复书写同样的指令,而只需书写一次。当然从程序的执行来看,每调用一次子程序都要附加保护断点、进栈和出栈等操作,增加程序的执行时间。但一般来说,付出这些代价总是值得的。 在汇编语言源程序中使用子程序时,一般要注意两个问题:参数传递和现场保护。参数传递一般可采用以下方法: ·传递数据。将数据通过工作寄存器R0~R7或累加器来传送。即主程序和子程序在交接处,上述寄存器和累加器存储的是同一参数。 ·传送地址。数据存放在数据寄存器中,参数传递时只通过R0、R1、DPTR传递数据所存放的地址。 ·通过堆栈传递参数。在调用之前,先把要传送的参数压入堆栈,进入子程序之后,再将压入堆栈的参数弹出到工作寄存器或者其他内存单元。 ·通过位地址传送参数。在进入汇编子程序时,特别是进入中断服务子程序时还应注意现场保护问题,即对于那些不需要进行传递的参数,包括内存单元的内容、工作寄存器的内容,以及各标志的状态等都不应因凋用子程序而改变。方法就是在进入子程序时,将需要保护的数据推入堆栈,而空出这些数据所占用的工作单元,供在子程序中使用。在返回调用程序之前,则将推入堆栈的数据弹出到原有的工作单元,恢复其原来的状态,使调用程序可以继续往下执行。由于堆栈操作是“先入后出”,因此,先压入堆栈的参数应该后弹出,才能保证恢复原来的状态。例如: SUBROU: PUSH ACC PUSH B PUSH R0 POP R0 POP B POP PSW POP ACC RET 至于每个具体的子程序是否需要现场保护,以及哪些参数应该保护,则应视具体情况而定。 调用子程序过程由调用指令完成,MCS-51单片机有两条调用指令:ACALL addr11和ACAL addrl6。指令中的地址为子程序的入口地址,在汇编语言中通常用标号来代表。在执行这二条指令时,单片机将当前的PC值压入堆栈。子程序的最后是返回指令RET,这条指令将堆栈的内容传入PC中,保证程序返回调用的地方继续运行。 在子程序执行过程中调用其他子程序,这种现象称为子程序嵌套。MCS-51单片机允许多重嵌套。 【例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 ;比较完,子程序返回 |