调用指令

本文深入探讨了单片机嵌入式开发中硬件堆栈与软件堆栈的区别与作用,包括它们如何用于保存调用子程序的返回地址、局部变量、工作寄存器等关键信息。文章还介绍了如何利用人工堆栈实现特定功能,如在不使用系统堆栈的情况下运行自定义函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

硬件堆栈用来保存调用子程序的返回地址,软件堆栈用来保存局部变量和工作寄存器。

 

一般会把返回地址自动存入堆栈,而没有被单片机自动入栈但是也需要保存的内容比如状态寄存器、通用寄存器等,就得通过PUSH等指令把它们人为地保存到堆栈中。自动入栈和“人为入栈”可能使用的是一个堆栈指针。有的单片机可以分开,比如AVR,可以通过“ST -Y, R0”这样的指令把R0存入软件堆栈区(Y是由R28和R29两个寄存器的值组成的16位指针),有的单片机缺少这样的指令,就会把软件堆栈和硬件堆栈放在一个栈空间,都使用SP,比如51.

 

硬件堆栈:或许也可以称作系统堆栈,是位于片内RAM区。有人说,只要能使用PUSH,POP指令的单片机,都可以说含有硬件堆栈。这样的说法我个人觉得不是很全面。通过指令进行压栈和出栈操作只是系统堆栈中的一种操做。系统堆栈还可以被隐含调用。例如,当调用子程序时,系统会主动把断点压入堆栈,并不需要用户通过指令操作。系统堆栈可以用来保存数据,或在任务子程序间传递数据。

    软件堆栈:也可以说是用户堆栈。可以被定义在内部或外部 RAM中。它是用户为任务建立的专用数据堆栈,与系统堆栈的数据区是隔开的。它可以保存用户想保存的任何寄存器和状态字。

     在嵌入式系统的移植中,理解移植对象的堆栈结构,有着重要的意义。

 

人工堆栈

在单片机的指令集中,一类指令是专门与堆栈和PC指针打道的,它们是
     rcall    相对调用子程序指令
     icall    间接调用子程序指令
     ret      子程序返回指令
     reti     中断返回指令   

     对于ret和reti,它们都可以将堆栈栈顶的两个字节被弹出来送入程序计数器PC中,一般用来从子程序或中断中退出。其中reti还可以在退出中断时,重开全局中断使能。
     有了这个基础,就可以建立我们的人工堆栈了。
     例:
#include <avr/io.h>
void fun1(void)
{
   unsigned char i=0;
   while(1)
   {
     PORTB=i++;
     PORTC=0x01<<(i%8);
   }
}

unsigned char Stack[100]; //建立一个100字节的人工堆栈

void RunFunInNewStack(void (*pfun)(),unsigned char *pStack)
{
   *pStack--=(unsigned int)pfun>>8;     //将函数的地址高位压入堆栈,
   *pStack--=(unsigned int)pfun;         //将函数的地址低位压入堆栈,
   SP=pStack;                             //将堆栈指针指向人工堆栈的栈顶
   __asm__ __volatile__("RET
\t");     //返回并开中断,开始运行fun1()

}

int main(void)
{
    RunFunInNewStack(fun1,&Stack[99]);
}
      RunFunInNewStack(),将指向函数的指针的值保存到一个unsigned   char的数组Stack中,作为人工堆栈。并且将栈顶的数值传递组堆栈指针SP,因此当用"ret"返回时,从SP中恢复到PC中的值,就变为了指向fun1()的地址,开始运行fun1().

     上面例子中在RunFunInNewStack()的最后一句嵌入了汇编代码 "ret",实际上是可以去除的。因为在RunFunInNewStack()返回时,编译器已经会加上"ret"。我特意写出来,是为了让大家看到用"ret"作为返回后运行fun1()的过程。


;范例9 ;16位整数被乘数*16位小数乘数-->16位整数积,精确到0.5 MUL165: RCALL MUL16 ;先得到32位积 SBRS R14,7 ;积小数部分最高位为1,将整数部分加1 RET ;否则返回 LDI R17,255 SUB R13,R17 SBC R12,R17 ;以减去-1($FFFF)替代加1 RET ;范例10 ;32位被除数/16位除数-->16位商,精确到1 DIV16: LDI R16,16 ;(r12r13r14r15)/(r10r11)-->r14r15 DLOOP: LSL R15 ROL R14 ROL R13 ROL R12 ;被除数左移1位 BRCS DI1 SUB R13,R11 SBC R12,R10 ;移出位为0,被除数高位字减去除数试商 BRCC DI2 ;够减,本位商为1 ADD R13,R11 ADC R12,R10 ;否则恢复被除数 RJMP DI3 ;本位商0 DI1: SUB R13,R11 SBC R12,R10 ;移出位为1,被除数高位字减去除数 DI2: INC R15 ;本位商1 DI3: DEC R16 BRNE DLOOP RET ;范例11 ;32位被除数/16位除数-->16位商,精确到0.5 ;可能产生溢出!例$7FFFC000/$8000=$FFFF.8->$10000! DIV165: RCALL DIV16 ;(r12r13r14r15)/(r10r11)-->r14r15 LSL R13 ROL R12 ;余数乘2 BRCS D165 ;有进位,转5入 SUB R13,R11 SBC R12,R10 ;否则,余数乘2减去除数 BRCS D164 ;不够减,转4舍 D165: CLR R13 ;否则将商增1 SEC ADC R15,R13 ADC R14,R13 ADC R13,R13 ;若有溢出,溢出位在R13中 RET D164: CLR R13 RET ;范例12 ;32位整数/16位整数->16整数+16位小数->4字节浮点数 ;(r12r13r14r15)/(r10r11)-->r12r13r14r15 DIV16F: RCALL DIV16 ;先做整数除法 MOV R9,r15 MOV R8,r14 ;保存整数部分 CLR R15 CLR R14 RCALL DIV16 ;除得小数部分 MOV R11,R15 MOV R15,R14 MOV R13,R8 MOV R14,R9 ;整数部分在r13r14,小数部分在r15r11 LDI R17,$90 ;预设阶码$90(整数为16位) MOV R12,R17 LDI R17,32 ;设32次右移 DIV16L: SBRC R13,7 RJMP NMLDN ;最高位为1,已完成规格化 LSL R11 ;否则继续右移R13,R14,R15,R11 ROL R15 ROL R14 ROL R13 DEC R12 ;阶码减1 DEC R17 BRNE DIV16L CLR R12 ;右移达32次,浮点数为零,置零阶 RET NMLDN: SBRS R11,7 RJMP DIVRT ;欲舍去部分(R11)最高位为0,转4舍 RCALL INC3 ;否则尾数部分增1 BRNE DIVRT INC R12 ;尾数增1后变为0,改为0.5,并将阶码增1 DIVRT: LDI R17,$7F ;将尾数最高位清除,表示正数(负数不要清除) AND R13,R17 ;规格化浮点数在R12(阶码)R13R14R15(尾数)中 RET ;范例13 ;(R16,R12,R13,R14,R15)/(R10,R11)-->R13,R14,R15 DIV24: CLR R16 ;32位整数/16位整数->24位整数,要求(R10)不为0;否则 ;要求(R12)24位整数 要求(R16,R12) LXP: LSL R15 ; <(R10,R11) ROL R14 ROL R13 ROL R12 ROL R16 BRCC LXP1 SUB R12,R11 ;右移后C=1 够减 SBC R16,R10 ;被除数减
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值