子程序结构是将某些运算和操作设计成一小段可被其他程序调用的程序段,需要的时候直接调用这些程序段的程序结构。其中能够完成特定功能、可以被其他程序调用的程序段称为子程序。调用子程序的程序称为主程序,调用子程序的过程称为子程序调用。子程序执行完后返回主程序的过程称为子程序返回。
在实际的程序设计中,经常会遇到一些相同的操作,如多字节加法、代码处理等。此时,采用子程序结构将会省去很多重复编写程序段的麻烦,而且可以缩短程序代码,使程序紧凑,结构清晰明了。
子程序是具有特定功能的独立程序段,子程序的结构需要具备如下特点。
●子程序必须提供入口地址,以便主程序调用;
●子程序必须以返回指令RET结束子程序。
在汇编主程序中调用子程序时,需要注意参数传递和现场保护两个问题。参数传递需要用户自己安排,在主程序中,调用子程序的指令不带任何参数。汇编程序通常采用下面两种方法来进行参数传递。
●传递数据。在主程序调用子程序前,将需要传递的参数送入工作寄存器R0~R7或累加器A,供子程序读取使用。
●传递地址。主程序将要传递的参数存放在数据存储器中,其地址送入工作寄存器R0~R7或数据指针DPTR,供子程序读取使用。
现场保护是指在调用子程序前,将这些寄存器和累加器中的内容保护起来。现场保护是一个很重要的问题。在主程序调用子程序之前,累加器A或工作寄存器中可能存放着主程序运算的中间结果,这些中间结果在主程序中仍然有用,而子程序中也要用到这些寄存器或累加器。这时就需要现场保护,否则将会使前面的寄存器内容丢失。当子程序执行完毕后,将这些数据取出,送到原来的寄存器或累加器中,这个过程称为恢复现场。
现场保护一般采用堆栈来操作。在需要保护现场的时候,用压栈指令PUSH将需要保护的寄存器内容压入堆栈。在子程序执行完后,在返回指令RET前使用弹栈指令POP,把堆栈中保护的内容恢复到原来的寄存器中。
注意 :在汇编程序中,堆栈操作是“先入后出”的,因此,先压入堆栈的参数应该后恢复,才能保证将数据恢复到原来的寄存器。
51系列单片机指令集中提供了两个指令可以用来调用子程序,其使用格式如下:
LCALL ADDR16
以及如下:
ACALL ADDR11
LCALL称为长调用指令,指令的操作数ADDR16给出了子程序的16位入口地址;ACALL称为绝对调用指令,其中的操作数ADDR11提供了子程序的低11位入口地址,这个地址和程序计数器PC的高5位并在一起,便构成了16位的调用地址,即子程序的入口地址。
在执行时,子程序调用指令首先将PC中的内容(调用指令下一条指令地址,称为断点地址)压入堆栈,即断点保护,然后将调用地址送入PC中,使程序跳转到子程序的入口地址。
返回指令RET,用于子程序的返回。该指令将堆栈中存放的返回地址(即断点地址)弹出堆栈,送回到PC中,使程序返回到主程序的断点处继续向下执行。
下面首先举一个简单的例子,用汇编语言计算Y=a×a+b×b。
首先假定a存放在20H单元,b存放在21H单元,计算的结果存放在22H单元中。其中的a和b是小于10的无符号整数。这里a×a的运算可以采用子程序的方法,在子程序中,通过查表来求得平方值。在调用子程序时,将a送入累加器A中,然后在子程序结束的时候,将a×a保存在累加器A中,从而实现参数的传递。主程序示例如下:
子程序示例如下:
接着,再举一个多字节BCD码加法的子程序。程序代码示例如下:
在该子程序中,两数均按照高位字节数存放于低地址单元,而相加运算则从低位字节数开始,R0、R1指针在运算前均指向最高字节数的地址,故需要转换成指向最低字节数的地址,然后进行相加运算。
本例中的参数传递是这样的:将R0指针的内部RAM中N个字节的BCD码被加数,与R1指针的内部RAM中N个字节的BCD码加数进行相加。R2中存放字节数N。相加的结果BCD码和数存入R0指针的内部RAM中,即原被加数单元。这段程序的流程图如图5-9所示。