微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 嵌入式设计讨论 > MCU和单片机设计讨论 > 89C52堆栈的范围到底是低地址的128字节还是整个RAM?

89C52堆栈的范围到底是低地址的128字节还是整个RAM?

时间:10-02 整理:3721RD 点击:

手头有个STC90C52的程序,keil C写的,修订版的上电几分钟就重启,没有用看门狗,排除了硬件掉电、数组或指针的越界操作,考虑是否堆栈溢出,手动更改了START51.A文件将STACK放在了DATA区,并将STACK长度定义为30H(子程序调用最大8层,开串口中断,串口中断调用一个非重入处理子函数,这个结构貌似不合理,但因为某些原因,只能这么搞,汗~~~),结果重启没有那么频繁了,大概半小时一次,这下要考虑重新排查数组指针越界了。
这里面有个问题,子程序调用压栈操作2字节PC指针,中断现场要再压栈7字节(这个芯片的DPTR有两组,所以多2字节)寄存器和2字节PC,算起来最多是:8*2(普通子程序)+7(反汇编中断的直接PUSH和POP指令,状态寄存器)+2(中断现场)+2(中断中的子程序)=27字节=0x1B字节,所以30H字节的STACK应该足够用。
好吧~~~这些都不是重点,重点是:STC的手册和Keil的手册上对51的STACk都是只要在RAM中就行,STC的手册中更是明确提出更建议在80H以后,也就是128~255地址的RAM;但在另外的文章里指出,系统的STACK应该是在7FH以下,因为POP和PUSH操作只能针对直接寻址单元操作,也就是低128字节,而高128字节则因为对应的直接寻址是SFR而不能用堆栈操作。而且貌似在多数的C51手册里 对0x00~0x7F地址RAM的说明里都有出现“大多数程序会把堆栈放在这以空间”的说法。那么问题来了~~~到底谁说的对?   
  还有——系统在子程序调用(ACALL、LCALL)和中断处理时的压栈和出栈操作我们不用去写PUSH和POP指令的(中断有部分需要),但是它是不是也和PUSH和POP完全一样,会不会用到不是直接寻址的单元,比如高128字节,只是它是内部操作,用别的办法代替了@Ri一类的间接寻址?

为此自己试验了下,在不更改STACK长度和位置时,重新编译下载,大约每2分钟重启,M51文件中看到的STACK位置是8EH,长度是1,当然后面都是GAP了,反汇编窗口看到的中断程序入口PUSH了7个字节,出口POP了7个字节。然后我更改STACK长度和位置定为DATA后,每30分钟随机左右重启,而且之前的工作中也遇到过编译后的程序占用data超过128字节时类似的问题,采用large模式后就正常了。所以一厢情愿的相信了STACK应该在低128字节,不知道对不对。
呵呵,所以又去请教了原厂的技术支持,得到的答案是用C的时候不要去管汇编,不要去动STACK或SP,keil会把一切搞的妥妥的.当然去除个人意见和对这位高手水准的猜测,我愿意相信他说的是对的,但仍然不知道到底STACK在高128字节时会不会出问题,好想弄清楚...
                           所以……高手们来a,谢谢不吝赐教^_^    ^_^   ^_^
~好吧,已经解决了,AT89C52 E文原版P6左上角有说明,堆栈本身实际就是间接寻址(用的SP寄存器),所以对于C52以后的片子来说0~256整个RAM空间都是可做为堆栈的。但PUSH和POP的源数据必须是直接寻址(这个指令里有说明)。至于别的,已经回复给了各位,等待论坛审核过了应该就可以看到了。
而如果说堆栈要放到80H以后是为了安全的话,个人觉得不如说是为了将80H前的留给更能发挥其作用的数据用,不过如果放到更低的地址,比如30H或者07H确实有可能因为别的数据可能是被分配在了堆栈后面的地址,比如将变量全局变量XX分配在了7DH,这样如果程序运行是的堆栈操作很多,可能最后导致栈顶SP指向了7DH,而在之后的堆栈操作比如函数调用时压栈PC将会改变XX的值,还可能因为改变XX值的操作而导致出栈时的PC值已经不是原先的值了(这是已经变为了XX改变后的值),这些都是不符合期望的。
问题已解决,是一个忘记的数组越界操作了。之前说的文章里有提到堆栈应操作应该是00~7FH是本人理解有误,其本意应该是在说PUSH和POP的操作数也就是源数据是00~7FH而不是堆栈地址应该在00~7FH。手册中所提到的多数程序将堆栈放在00~7FH也确有其事,但不是一定要放在这里。



好吧,自己先来坐个沙发

你不用汇编,什么都不用管

3Q,大神,不过有时外围也需要用汇编的,所以……STACK被分配到了高128字节到底会不会导致片子重启?

我觉得小编研究的很深刻啊,很多我也不是很懂。
以前的51只有128字节RAM,所以会有说堆栈在7FH以内。后来出了52,RAM扩展到了256字节,所以30H后到FFH应该都是堆栈。我没弄清楚的是为啥STC官方推荐将SP设置到80H,30H不可以吗?都说是这样安全,至于为啥不安全我没有看到有效的解释?
至于调用子程序的PUSH和POP肯定是在有需要的时候调用的,比如主程序正在使用R0,里面存着一个临时变量,然后调到子程序去了,那肯定要保存好这个R0了。至于是谁保存我也不知道调用规则是怎样的?指令效果肯定和小编说的中断里是一样的,不会说存的位置很特殊。
小编的代码量很庞大吗?为何这么容易复位?导致复位的原因我看15系列中有一条是访问非法地址会导致程序复位,90应该也是把。一种可能数组越界了,一种就是堆栈溢出,代码结构嵌套太多?临时变量很庞大?小编有结果了吗?
小编有新进展了请发帖让我们都学习一下哈,很感兴趣,一起交流。

论坛隐居的大牛们,如果看到这个帖子了请指点一二啊

谢谢关注,我那个代码量不大,只是程序结构比较复杂而已。
关于你提到的R0,实际了解一下,如果是C51的话调用或中断的现场保存不包括通用寄存器R0~R7,而是通过using关键字声明,另外在传参的过程中R0~R7如果正在被使用,编译时在调用子函数的过程中会把它们提前处理掉,也就是把它们的内容复制到参数变量里。但C51的中断处理中(反汇编可以看到)是明确用到PUSH和POP指令的。
而如果是汇编的话可以自己选择R0~r7是否要压栈,一般来说函数调用和中断压栈是PC指针的两个字节,这个是硬件完成的,不需写指令。
另外关于我自己的问题,刚刚在AT89C52 的E文手册里看到这个:Note that stack operations are examples of indirect addressing, so the upper 128 bytes of data RAM are available as stack space.
所以……压栈和出栈实际用的是SP,就是间接寻址,是自己遇到问题的时候搞糊涂了,PUSH和POP指令的操作数要求是直接寻址的direct,这个是压栈的数据地址而不是目标地址。
之所以说是建议要用80H以后的字节作为堆栈个人理解是为了系统能够尽可能的利用data空间,以使运行效率会提高,而且可以避免因为堆栈占用了data而导致的其他一些问题。
而关于keil C对堆栈的处理是连接时放在了所有已占用的地址之后的紧邻字节,也就是说如果程序本身用了data 226 Bytes,那么它的初始SP将是226=(227-1),而堆栈的初始长度是1,它是不会去校验堆栈长度是否会导致运行问题的,所以实际SP也不能太大,以保证程序的安全运行,避免运行过程的堆栈溢出RAM范围(实际Keil里对栈顶的最大取值也是有限定的)。
这个问题就算是搞清楚了,STACK是可以在整个RAM空间的,包括高128字节,而20H以下因为是通用寄存器,实际是不合适用的,20~30H是bit寻址空间,如果有位变量也是必须预留一些的。
至于我的这个片子重启已经排除了数组和指针的越界操作,仍然没有解决问题,所以下面只能在硬件复位这块再排除一下了。这个帖子只是排除故障过程中产生的想法,以前对于C没有太多关注堆栈这个事,所以请教下诸位。现在算是解决了,呵呵~再次感谢下诸位~

@HARRY007 好吧,HARRY同志,首先谢谢关注。
这块已经搞明白了,STACK是可以在0~256的整个空间的,另外关于你提的一些问题的一些个人所见也已经回复给你了,呵呵~不过论坛说需要审核,不知道什么时候会不会公布出来。Note that stack operations are examples of indirect addressing, so the upper 128 bytes of data RAM are available as stack space. ——来自《AT89C52 E文原版手册 Page6 左上》,所以堆栈操作因为用到SP的缘故,本身应该就是间接寻址,只是压栈的源数据是直接寻址。至于为什么是要在80H以后个人觉得是为了腾出data(0~128)给别的能更有效使用它的数据吧,不算是为了系统安全。另外keil C貌似是不校验堆栈是否会在运行时越界的,所以一般还是多留点空间吧,这个倒是真为了系统安全。

看到你的回复了,也学到很多,谢谢。Using的用法我刚才大概百度了下,没怎么用过,以后还得深入了解下。我上文提到的R0操作指的就是汇编代码中,不过51汇编好久没用过了,上学的时候学过,那会儿学的还蛮认真的,现在都忘记了,哈哈。
最近在看操作系统任务切换那里的汇编代码,M3内核的,感觉汇编还是很有用的,深刻理解栈空间。
小编最终根本问题找出来了记得在帖子里@我一下说一下哈,多谢

只是路过看看。

兜纳K1

http://www.51hei.com/mcu/1626.html

@HARRY007  弄好了,是一个预留给升级用的数组越界了

@HARRY007 数组越界导致重启,这个没有任何理由可讲,是前两遍排查的时候忽略了这个只用来保存升级数据的数组会受到一个循环计数器变量(没有人为复位为0)的影响。

那就说明堆栈还是够用的啊,哈哈。我以前就碰到过数组越界的,一个局部变量做循环来拷贝,结果局部循环变量没有初始化,在特定的条件下就会触发,通过仿真可以看到这个数值会变的很大(随机),然后就跳到中断里的异常函数里死在while里了

恭喜恭喜。
我觉得论坛里的这个帖子讲的很好
http://bbs.elecfans.com/jishu_412923_1_1.html

@HARRY007 嗯,以前也有过数组越界或者指针越界导致数据莫名其妙的变化或者重启的,这次这个确实没想到,开始就排查了可能的越界操作,呵呵,最后还是没想到是这个认为基本都忘了的数组,到一步步注释掉所有的过程和函数,注释到这个过程所在的子程序里发现就没有重启了,于是给计数器变量复位成0,一切OK。才想到这里还有个数组,呵呵。
之前因为硬件限制,这个程序的结构搞的比较复杂,中断里还有些嵌套过程,所以在第一遍排查过越界操作后就怀疑堆栈了,尤其是改了堆栈地址以后重启频率还明显有改善,加上之前确实遇到过复杂程序堆栈搞到很大还溢出,所以就先靠堆栈来了,不过计算了嵌套层数和中断后把堆栈长度做到48字节还是重启……于是反过来再排查越界,还是忽略了这个数组,呵呵,

128低的还有128高的,stack用C的话自动分配,不用管。

还有做广告的?

好吧,广告不是我的

是多少

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top