8051单片机,堆栈指针SP默认值是0x07,它总是先加1再存数据。0x00-0x07留作R0-R7用。因此SP第一个可以存执的地址是0x08
系统堆栈和用户堆栈?
系统堆栈在片内,所有任务公用。任务堆栈设置在片外,每个任务都有自己的任务堆栈。仿真堆栈也设置在片外,每个任务都有自己的仿真堆栈。
SP指向片内RAM,也就是SP所指向的栈空间为系统堆栈,是所有任务所公用的堆栈。
任务堆栈设置在片外RAM,用于保存各个任务上次运行时寄存器的值。
关于这个更详尽的解释可以查看任哲的《嵌入式实时操作系统μCOS-II原理及应用(第2版).pdf》。如下:
SP里面存的是什么内容?
明白这个问题之前,先看下面几个说明。
SP和LCALL的关系(ACALL差不多)
SP所指向的栈空间是向高地址增长,里面存的是函数的返回地址,压栈的步骤是由处理器自动完成的。SP保存函数的返回地址值是在什么时候完成的?汇编里面有LCALL语句是用来调用函数的。LCALL语句执行时,会自动将当前PC值+3存到SP内,低字节先入栈。SP是先加1后存储。存储PC的值需要两个字节,因此执行LCALL之后SP加2。LCALL语句说明:http://www.keil.com/support/man/docs/is51/is51_lcall.htm。
C函数调用执行和LCALL有相同的操作。
SP和RET的关系
RET是子程序返回时调用。这条语句会将SP指针弹出到PC,高字节先弹出,低字节后弹出,并将SP-2。弹出到PC的地址就是子程序返回后将运行的地址。RET说明:http://www.keil.com/support/man/docs/is51/is51_ret.htm
C函数返回时和RET有相同的操作。
SP和RETI的关系
RETI是中断返回时调用。这条语句和RET一样,会将SP指针弹出到PC,高字节先弹出,低字节后弹出,并将SP-2。它不会恢复PSW的值,如果用户需要保存,需要自己压入栈。RETI还会做些其他操作,参考:http://blog.csdn.net/astrotycoon/article/details/8881809。RETI的说明:http://www.keil.com/support/man/docs/is51/is51_reti.htm
C中断函数直接返回的话,会将之前压入栈的寄存器值出栈。
SP和中断函数的关系
RETI可以使中断函数返回到之前运行的程序地址,但是之前的程序地址是在什么时候入栈的?如果中断函数使用汇编编写,CPU会自动将中断前运行程序的地址存入到SP内,留作RETI返回时使用。其它的内容就得用户自己手动入栈保存了。如果中断函数是用C语言编写,经过Keil编译后,运行时CPU会先将中断前运行程序的地址存入到SP内,然后是将寄存器的值入栈。
SP里面存的是什么内容?
对于正常的函数调用,SP里面存的是函数返回后将运行的地址,这个地址的入栈和出栈操作操作是由CPU自动完成的。
对于中断级的函数调用,分为汇编函数和C函数。在汇编函数被调用里:SP里存函数返回后将运行的地址(CPU自动),各个寄存器入栈需手动进行;在汇编函数返回时:寄存器出栈需手动进行,即将运行程序地址是自动弹出到PC。在C函数被调用里:SP里存函数返回后将运行的地址(CPU自动),各个寄存器入栈也是自动进行(不太确定);在C函数返回时:寄存器出栈自动进行(不太确定),即将运行程序地址是自动弹出到PC。
针对上面的陈述,不敢保证完全正确。在不是用using的前提下,有一点应该可以肯定。在调用汇编时,只有PC指针压入了SP,其它的寄存器值都需要手动入栈。调用C函数时,PC会入栈,其它的就不太确定。关于using,参考:http://www.51hei.com/mcu/766.html?
我想这也是为什么只在汇编里进行任务切换,因为在汇编里要保存什么内容是清晰明了的。
?C_XBP是仿真堆栈,是51特有的,方向是向低地址增长。里面存的是函数局部变量的内容,它设置在片外RAM里。仿真堆栈参数入栈也是有处理器自动完成。
SP和?C_XBP都是有处理器自动完成,但是需要手动设置初值。
如果要切换任务,
被中止的任务要做的操作就是:将此时的系统堆栈内容保存到自己任务的任务堆栈里面就可以了。拷贝数据的长度是SP当前值减SP的基址OSStkStart,拷贝的长度也要保存到任务堆栈里。当前的?C_XBP值也必须保存到任务堆栈里。
要运行的任务要做的操作就是:将自己任务的任务堆栈里面的内容拷贝到SP所指的系统堆栈空间就可以了。是从SP的基址OSStkStart拷贝,长度之前已经保存过了。同时恢复?C_XBP的值,这个值之前已经保存在任务堆栈里了。
上面是任务建立完之后的任务切换,第一个任务是怎么切换运行的。
第一个任务原理一样,将任务堆栈的数据拷贝到SP所指向的系统堆栈内,长度一开始是在OSTaskStkInit设置的。同时将仿真堆栈?C_XBP赋值,这个初值也是在OSTaskStkInit里设置的。
堆栈初始化函数
最后有一问题:
在51上运行ucos时,加入了这样一个程序。在串口中断内调用ossempost函数,在一个任务内调用ossempend函数。串口中断收到固定字节数据后调用ossempost,此时调用ossempend的函数就会得到执行,此任务打印串口收到的数据。就这样的一个程序,在用串口助手交互1次或者数次(小于10次)后就会导致单片机重启,或者是一些变量的值被更改从而导致程序死在一个地方。调试发现任务堆栈没有溢出,程序已经简化到非常简单的地步,没有发现野指针。和这个网址http://www.dzsc.com/dzbbs/20051110/20076520187187109.html上的现象还不太一样,不过都是串口中断内使用信号量。使用邮箱也有问题,使用全局变量作为标志就没有这个现象。此问题查了很久也没有解决,先告一段落。
有人知此情,还望明示。