慎用51单片机中的RET指令

来源:互联网 发布:ie8中文版官方mac 编辑:程序博客网 时间:2024/06/05 11:17

 题目:已知有四个按键依次连接单片机中的P3口的0到3的IO口,有四个LED灯连接P1的0到3 IO口,写一程序,满足以下条件:当按下按一个按键,对应的LED会发亮,比如

按下P3.0的按键,连接P1.0的LED就发亮。y

以下是我同学编写的程序:

org 0000h
mov P1,#0ffh
loop:
jnb P3.0,led1;*
jnb P3.1,led2;*
jnb P3.2,led3;*
jnb P3.3,led4;*
ljmp loop
led1:
clr P1.0
ret
led2:
clr P1.1
ret
led3:
clr P1.2
ret
led4:
clr P1.3
ret
end

程序的意图是,制造一个死循环,不断检查按键是否按下,如果按下,就令对应的灯亮。程序经过测试,能够满足题目的要求。

 

但是,问题出现在上面带*号的那一部分代码,程序意图是想要当P3的某个位为0的时候,就调用LED灯的子程序,执行CLR P1.0语句,再返回到原来程序调用子程序的地方继续执行代码。

 

我对的子程序的理解是:在一个地方启动一段代码,当这段代码运行完毕之后,就返回到原来的地方继续运行剩下的代码。

 

那么CPU单片机是如何返回原来的地址的呢?

 

首先,当程序执行到A处进入子程序时,将A的下一个条指令(即PC+2所指的地方)压入栈中,即将栈指针SP+1,PCL进栈,SP再加1,PCH进栈。

 

然后,把PC的值改为子程序代码的入口。

 

子程序执行完毕之后,从栈中弹出原来的PC值,赋值给当前的PC寄存器。


最后,程序返回到原来调用子程序的地方的下一条指令继续运行。

(详细步骤请查看RET和ACALL,LCALL指令)

 

上面的代码很明显想调用一个子程序,但是51单片机中,只有ACALL和LCALL指令会在跳转前讲PC+2值压栈,其他跳转指令都不会。

 

代码中使用了JNB作为跳转指令,所以并没有压栈,但是当跳转之后遇到RET,还是一如既往地弹栈,这样,只有出,没有进,会导致堆栈不平衡。

 

但为什么这个程序依然有效呢?

 

这个因为SP初始指针指向了一个空白的单元(全是0),所以,当遇到RET后,把PC寄存器给初始化,程序由头开始重新执行,阴差阳错地满足的题目的要求。

 

所以RET指令必须和ACALL和LCALL配套使用,才能组成为真正意义上的子程序