子程序是什么?
实际编程过程中,为减轻程序代码的量,将一些经常用到的指令集称之为子程序;这里可以类比一下C语言中的延时函数delay()等等;
功能:
为了解决同一组程序代码被反复使用的麻烦操作,每次要用到的时候调用一下就好了;
子程序被调执行特点:
1. 被其他程序调用;
2. 执行完后又需要把执行流程返回到该子程序的主程序;
~~~~一条华丽的分割线
以上说的内容过于简单,相信学过一点其他语言的同志都可以深刻理解了,这里我们主要是针对汇编语言进行的讲解;来阐述我们经常用高级语言却习惯性忽视的细节(细节也即是高级语言直接优化,而不被看到的功能)
重点来了!
子程序调用时要注意的两点:
1. 现场的保护和恢复;
2. 主程序与子程序参数的传递
现场的保护和恢复
来想一个场景:
在调用子程序之前,单片机的某个通用单元里(累加器A)储存了一个等子程序执行完毕后继续用的有价值的数据;但是由于这个通用单元在执行子程序的时候被用了;出子程序后发现那个值已经不复存在了;GG了;
好吧,给予这种情况下,我们有了保护和恢复的说法;
通用单元有:R0~R7,累加器A,数据指针DPTR,标志和状态等;
参数传递
-
利用累加器传送参数
这种方式可以直接 “将计就计”,反正值已经在累加器A中了,在子函数中直接用就好了,子程序返回值得时候也是直接返回值给累加器A就好;举个栗子:
/*假设内存地址30H,31H,32H,33H,分别用来存放a,b,c,d的数值,现在要求除d=a平方+b平方+c平方*/
ORG 0000H ;设置程序起始存储地址
START: MOV A,30H ;将地址30H中的数据取出来送给累加器A
ACALL SQR ;调用查平方表 (平方表就是DB定义的数组)
MOV R1,A ;a平方暂放在R1中
MOV A,31H ;将地址30H中的b的值取出来送给累加器A
ACALL SQR ;调用子程序`这里写代码片`
ADD A,R1 ;A=a方+b方
MOV R1,A ;R1 = A
MOV A,32H
ACALL SQR
ADDC A,R1
MOV 33H,A
SJMP $
;子程序
SQR: MOV DPTR,#TAB ;将平方表的首元素地址给DPTR数据指针
MOVC A,@A+DPTR ;将TAB平方表中的相应数据值赋予给累加器A
RET ;子程序返回指令,执行后,栈顶的两个内容弹出送到PC中,SP(栈顶指针)减2;然后将16位地址放入PC中;
TAB: DB 0,1,4,9,6,25,36,49,64,81 ;DB为定义字节指令,后面的每个数据都占一个字节;
END
-
利用堆栈传送参数
这里补充一个堆栈概念:
堆栈是什么:储存区域;内存RAM中开辟的一块储存区域;
堆栈作用:专门用来暂时存放数据或存放返回地址;堆栈传送参数是什么:
利用堆栈传递参数常用于子程序嵌套中长采用的一种方法;
怎样传递参数:
在调用子程序之前,用PUSH指令将子程序中所需数据压入堆栈,
执行子程序时,再用POP指令从堆栈中弹出数据;举个栗子:
/*问题:
编写0!+1!+2!+3!+4!的子程序。
20H单元存放要进行阶乘的数,30H单元存放每次调用子程序计算后的阶乘
遇到的问题在程序的第30行和第31行
*/
ORG 0000H
MOV SP,#60H ;初始化SP=60H;注意,这里的60H是地址而不是整数;
MOV 20H,#0H ;存放要进行阶乘的数;
MOV R2,#04H ;循环次数
MOV A,#0H
START: PUSH 20H ;SP=SP+1,将整数20H给地址为61H的堆栈区寄存器
PUSH ACC ;SP=SP+1,这里将ACC内储存的整数值赋予到地址为62H的寄存器单元,这里不应该是A而是ACC
ACALL MU ;调用MU处的子程序,SP=SP+2,将此处PC的地址送给SP
POP ACC
POP 30H
ADD A,30H
INC 20H
DJNZ R2,START
SJMP $
MU: MOV R1,SP ;借用R1为堆栈指针
DEC R1 ;程序时要保护现场
DEC R1 ;程序时要保护现场
DEC R1
DEC R1 ;R1指向要进行阶乘的数
MOV A,@R1 ;取出要进行阶乘的数
ADD A,#02H ; ?为什么A要加2?是不是和第31行的程序有关?
MOVC A,@A+PC ;查表 ?CP怎样从自上而下执行程序的状态转变成查表状态的
XCH A,@R1 ;整字节交换
RET ;返回主程序的地址
TAB: DB 0,1,2,6,24,120
END
知识补充:
在以上程序代码的时候出现过这么一个问题:
PUSH A
POP A
报错:表达式不匹配
PUSH ACC
POP ACC
不报错;
错误原因:
虽然A和ACC是一个东西;但是……PUSH和POP指令后面一定是直接地址,然而A被编译成累加器A(非地址),编译器编译时A被认为是寄存器,ACC被认为是特殊功能寄存器(地址);所以报错;