51单片机堆栈
2012-04-03 21:40:31| 分类: 单片机|字号 订阅
堆栈是一种数据结构。一直以为堆栈是一个寄存器,惭愧!教科书定义:所谓堆栈,就是只允许在其一端进行数据插入和数据删除的线性表。51单片机的单片机的堆栈是在内部RAM中开辟的。这句话表明了堆栈的位置。
那么堆栈到底有什么作用?
堆栈主要是为子程序调用和中断操作而设立的,因此对应有两项功能:保护断点和保护现场。
单片机的程序归根结底是个死循环,反复在执行Main函数(主程序),你可以只写一个函数Main,那么你这个函数随着功能的增多而变得异常大,而且非常不具备可读,这个时候就需要子函数(子程序)了。主函数在调用完子函数后会返回到主函数中,这样就可以调用其它函数并且继续这个死循环。在计算机去执行子函数或者中断服务函数,如何确保程序能够正确地返回到主函数中并且继续正确执行后面的内容?因为在执行子函数或者中断服务函数时,很有可能会破坏寄存器单元的内容,但这些寄存器单元在子函数必须要用到?这个问题看起来比较难解决了。这个时候就要用到 断点保护和现场保护了。
保护断点:在调用子程序和堆栈时,将返回地址(执行完子程序或者中断后要执行的下一个指令的的地址(PC寄存器值)) 送入堆栈,程序返回时,这个值自动弹回PC。这种方式是自动使用堆栈的,程序中一般无需理会。在这个过程中,地址送入堆栈时,堆栈指针SP+2,因为51的寻址范围是64KB,再查看RAM中堆栈单元就可以发现这个时候已经变成了PC的值,在返回的时候SP-2,但是堆栈中的内容在下一次堆栈操作之前不会发生变化。来看下面的仿真图:
![51单片机堆栈 - dzdesigned80 - dzdesigned80的博客](http://static.oschina.net/uploads/img/201307/30093713_2u9i.jpg)
程序初始化将SP设置为0x07,这个时候RAM中全部被清零。
单步运行。
![51单片机堆栈 - dzdesigned80 - dzdesigned80的博客](http://static.oschina.net/uploads/img/201307/30093713_lMIY.jpg)
这个时候SP的值已经变为0x30,但是内容依旧没有改变。
运行到断点。
![51单片机堆栈 - dzdesigned80 - dzdesigned80的博客](http://static.oschina.net/uploads/img/201307/30093714_9SHp.jpg)
运行到断点PC的值没什么变化,在执行完Lcall之后,发现RAM中30单元起了变化,0013,PC也+2,而0013就是sjmp st1的地址。
以上是用Protues仿真的结果。在用KEIL做软仿的时候发现SP会变化,但是对应的内容却没有变化,这个是不是KEIL的一个BUG呢?
保护断点是比较简单的,编程者一般不用理会。现场保护就和编程扯上关系了。现场保护:在转中断服务程序或者子程序之前,要把单片机中个有关单元的内容保存起来,这就是所谓的现场保护。有保护当然就有恢复。现场恢复:返回主程序之后恢复寄存器的内容到调用之前的状态。
这里就首先要牵涉一个using的用法了。
写过中断服务函数的人都知道,中断服务函数一般都是 这种形式的
void isr(void) interrupt x (using x) //using x是可选的
{
}
教单片机的老师当时说你搞不清using 的含义,写中断服务程序不用用它就万事大吉了。这个说法不能说是没有道理的。很长一段时间我写中断服务程序也是不管这个,直到有一次中断RAM溢出,才感到这个using用好了还是有点用处的。 “8051是一个基于累加器的单片机,具有8个通用寄存器,每个寄存器都是一个单字节的寄存器。这8个寄存器通用寄存器可以认为是一组寄存器或者一个通用寄存器组。8051提供四组可用的寄存器组。当使用中断时,多组寄存器切换将带来许多方便。典型8051 C程序不需要选择或者切换寄存器组,默认使用寄存器组0。寄存器组1·2·3在中断服务程序中使用。”引自北航版《单片机的C语言应用程序设计》
来看具体的程序。
不实用using,默认使用寄存器组0
void Timer0(void) interrupt 1 { TH0 = TH0_VAL; TL0 = TL0_VAL; time_1ms_counter++; if(0==(time_1ms_counter%10)) { ScanCardFlag=1; LCMDisplayAndScanKey();//显示时间 } if(64==time_1ms_counter) { time_1ms_counter=1; } }
对应的汇编语言程序
Timer0: C:0x9D70 C0E0 PUSH ACC(0xE0) C:0x9D72 C0F0 PUSH B(0xF0) C:0x9D74 C083 PUSH DPH(0x83) C:0x9D76 C082 PUSH DPL(0x82) C:0x9D78 C0D0 PUSH PSW(0xD0)
C:0x9D7A 75D000 MOV PSW(0xD0),#os_buf(0x00) ;选择寄存器组0 C:0x9D7D C000 PUSH os_buf(0x00) C:0x9D7F C001 PUSH 0x01 C:0x9D81 C002 PUSH 0x02 C:0x9D83 C003 PUSH 0x03 C:0x9D85 C004 PUSH 0x04 C:0x9D87 C005 PUSH 0x05 C:0x9D89 C006 PUSH 0x06 C:0x9D8B C007 PUSH 0x07 C:0x9D8D 758CB7 MOV TH0(0x8C),#IPH0(0xB7) C:0x9D90 758AFF MOV TL0(0x8A),#0xFF C:0x9D93 90007E MOV DPTR,#time_1ms_counter(0x007E) C:0x9D96 E0 MOVX A,@DPTR C:0x9D97 04 INC A C:0x9D98 F0 MOVX @DPTR,A C:0x9D99 E0 MOVX A,@DPTR C:0x9D9A 75F00A MOV B(0xF0),#0x0A C:0x9D9D 84 DIV AB C:0x9D9E E5F0 MOV A,B(0xF0) C:0x9DA0 7005 JNZ C:9DA7 C:0x9DA2 D208 SETB ScanCardFlag(0x21.0) C:0x9DA4 12905C LCALL LCMDisplayAndScanKey(C:905C) C:0x9DA7 90007E MOV DPTR,#time_1ms_counter(0x007E) C:0x9DAA E0 MOVX A,@DPTR C:0x9DAB B44003 CJNE A,#0x40,C:9DB1 C:0x9DAE 7401 MOV A,#0x01 C:0x9DB0 F0 MOVX @DPTR,A C:0x9DB1 D007 POP 0x07 C:0x9DB3 D006 POP 0x06 C:0x9DB5 D005 POP 0x05 C:0x9DB7 D004 POP 0x04 C:0x9DB9 D003 POP 0x03 C:0x9DBB D002 POP 0x02 C:0x9DBD D001 POP 0x01 C:0x9DBF D000 POP os_buf(0x00) C:0x9DC1 D0D0 POP PSW(0xD0) C:0x9DC3 D082 POP DPL(0x82) C:0x9DC5 D083 POP DPH(0x83) C:0x9DC7 D0F0 POP B(0xF0) C:0x9DC9 D0E0 POP ACC(0xE0) C:0x9DCB 32 RETI
使用using 1
void Timer0(void) interrupt 1 using 1 { TH0 = TH0_VAL; TL0 = TL0_VAL; time_1ms_counter++; if(0==(time_1ms_counter%10)) { ScanCardFlag=1; LCMDisplayAndScanKey();//显示时间 } if(64==time_1ms_counter) { time_1ms_counter=1; } }
对应的汇编语言程序
Timer0: C:0xA131 C0E0 PUSH ACC(0xE0) C:0xA133 C0F0 PUSH B(0xF0) C:0xA135 C083 PUSH DPH(0x83) C:0xA137 C082 PUSH DPL(0x82) C:0xA139 C0D0 PUSH PSW(0xD0) C:0xA13B 75D008 MOV PSW(0xD0),#0x08 ;选择寄存器组1 C:0xA13E 758CB7 MOV TH0(0x8C),#IPH0(0xB7) C:0xA141 758AFF MOV TL0(0x8A),#0xFF C:0xA144 90007E MOV DPTR,#time_1ms_counter(0x007E) C:0xA147 E0 MOVX A,@DPTR C:0xA148 04 INC A C:0xA149 F0 MOVX @DPTR,A C:0xA14A E0 MOVX A,@DPTR C:0xA14B 75F00A MOV B(0xF0),#0x0A C:0xA14E 84 DIV AB C:0xA14F E5F0 MOV A,B(0xF0) C:0xA151 7005 JNZ C:A158 C:0xA153 D208 SETB ScanCardFlag(0x21.0) C:0xA155 12905C LCALL LCMDisplayAndScanKey(C:905C) C:0xA158 90007E MOV DPTR,#time_1ms_counter(0x007E) C:0xA15B E0 MOVX A,@DPTR C:0xA15C B44003 CJNE A,#0x40,C:A162 C:0xA15F 7401 MOV A,#0x01 C:0xA161 F0 MOVX @DPTR,A C:0xA162 D0D0 POP PSW(0xD0) C:0xA164 D082 POP DPL(0x82) C:0xA166 D083 POP DPH(0x83) C:0xA168 D0F0 POP B(0xF0) C:0xA16A D0E0 POP ACC(0xE0)