首先来看,51的系统栈(又叫系统栈,或者硬件栈),就是SP所指向的栈,他是一个满增栈(注释1),位于片内RAM的128 bytes之中,上电之后系统堆栈指针SP的初值等于多少呢?这个要从51的启动文件来分析,启动文件中有这样的汇编代码:
?STACK SEGMENT IDATA ;定义一个片内数据段,段名:?STACK
RSEG ?STACK ;选择之前定义过的一个可重定位的段?STACK,下面的汇编语句将会被放置到该段,直到遇到下一个段定位指令,例如CSEG/RSEG。
DS 1 ;预留存储区命令。声明先占用一个字节的空间,在编译时,这个预留的空间不会被其他变量所使用。在这里的意义是,给硬件栈分配1个byte(实际这样是有问题的,应该为硬件栈预留更多空间)
还有:
MOV SP,#?STACK-1
由上可见,SP被初始化为#?STACK-1,在#?STACK地址处,DS指令预留了N个字节的空间,这些空间就是硬件栈的空间
但启动文件的代码中,DS 1相当于只给硬件栈预留了1个字节,这实际上会出问题,原因如下:片内RAM中会有多个数据段,只要使用XX SEGMENT IDATA指令即可在片内RAM中声明一个数据段XX,如果整个工程程序中,声明了多个数据段,?STACK数据段就只是片内RAM中众多数据段中的一个,如果只给?STACK段预留1个字节,而?STACK数据段后面又有别的数据段,那么我们的硬件栈就只有1个字节了,一旦发生中断,CPU寄存器自动入栈立即导致栈溢出,溢出后踩了别的变量的内存,程序基本崩溃;对于这个问题,keil是这样处理的:keil在链接阶段总是把?STACK数据段链接为片内RAM中的最后一个数据段,即使我们只给他预留了1个字节,那也不要紧,反正该段后面没有别的变量占用,只要SP别超出0X7F(片内RAM地址的上限)就行了。通过观察.m51(map文件)我们发现,keil确实是把?STACK数据段放到了片内RAM的最后,下面是某个51工程生成的map文件摘抄:
* * * * * * * D A T A M E M O R Y * * * * * * *
REG 0000H 0008H ABSOLUTE "REG BANK 0"
DATA 0008H 0002H UNIT ?C?LIB_DATA
IDATA 000AH 000DH UNIT ?ID?UCOS_II
0017H 0009H *** GAP ***
BIT 0020H.0 0000H.1 UNIT ?BI?SERIAL
0020H.1 0000H.7 *** GAP ***
IDATA 0021H 0041H UNIT ?STACK ; 作者注:就是这一行!
* * * * * * * X D A T A M E M O R Y * * * * * * *
XDATA 0000H 080EH UNIT ?XD?SERIAL
XDATA 080EH 0804H UNIT ?XD?MAIN
XDATA 1012H 0490H UNIT ?XD?UCOS_II
XDATA 14A2H 005CH UNIT _XDATA_GROUP_
为避免系统栈不够用,一个比较稳妥的办法就是,用汇编指令DS给?STACK数据段预留更多的空间,上面这个51工程中在另一个汇编文件中又给?STACK数据留出了40H个字节,这样总共就有41H个字节了。这样做的好处是可以在编译链接阶段即可排查堆栈错误,举个例子: 假设片内RAM中的数据段有很多,以至于,除了?STACK数据段之外,片内RAM只剩2个字节了,而?STACK数据段我们只默认采用了启动文件中的配置预留一个字节,这样编译没有任何问题,keil给编译通过了,但是运行过程中系统栈只有2个字节,肯定是分分钟就发生栈溢出,然后崩溃;假设片内RAM中的数据段有很多,以至于,除了?STACK数据段之外,片内RAM只剩2个字节了,而如果我们给?STACK数据段用DS指令分配40H个字节,这样keil在编译时就会发现51的片内RAM不足而报错,无法编译,从而在编译链接阶段帮助我们发现堆栈问题。
继续上面的问题,SP复位后的初值是多少,SP复位后等于0X07,但是立即就被启动文件通过语句MOV SP,#?STACK-1给改掉了,所以在进入main函数时SP的值是启动文件修改后的值,也即#?STACK-1(注,很好理解,这里-1是满增栈的特性),那么#?STACK的值又是多少呢?看上面的汇编语句?STACK SEGMENT IDATA,这一句声明?STACK段为一个可重定位的段,也就是说,?STACK段的首地址(#?STACK)在编译器进行程序链接时才能确定下来,也就是说,#?STACK的值是在链接时由编译器自动分配的,编译阶段不分配。仍然以上面摘抄的这段map文件为例,我们发现,?STACK段的起始地址是0021H,也就是说,#?STACK就等于21H。
仿真栈是keil为51生成可重入函数时用的(通过给函数使用关键词 REENTRANT限定,可使该函数具备可重入特性),对于STM32来说,默认生成的函数(不含全局变量和静态局部变量的函数)就是可重入的,而keil为51生成的函数,即使这个函数不含全局变量和静态局部变量,默认情况下keil也不会把这个函数汇编成可重入的,我认为keil主要是考虑到51的片内RAM匮乏,在不外接RAM的情况下,函数如果被编译为可重入的,可重入函数的执行需要占用一定的栈空间(尤其是由可重入函数嵌套调用产生的长的调用链,所需的栈更多)。
可重入函数在执行过程中是需要使用栈的,那么51的可重入函数使用的栈在哪呢?是SP指向的那个系统栈吗?答案是:不是。下面是解释:
当我们给51外扩了大的片外RAM时,就不用担心RAM不够的问题了,但是还有一个问题,系统栈指针SP只能寻址0~7FH共128字节的空间,可重入函数肯定不允许被编译成使用系统栈,否则,就算外扩了RAM,这个外扩RAM又无法供系统栈来使用,外扩RAM就没有意义了,所以keil为51打造了一个仿真栈的概念,keil在启动文件中声明了一个1或2字节的变量作为栈指针,这个栈指针的名字和大小根据编译模式的不同而不同,以大编译模式(注释2)为例,大编译模式下,启动文件中的XBPSTACK常量需要程序员手动设置为1,这样启动文件中使用到的条件编译,将会引用到一个2字节的仿真栈指针?C_XBP,由于keil把仿真栈作为满减栈,所以这个仿真栈指针?C_XBP被初始化为片外RAM地址的最大值加1,若我们外接了一个64K的片外RAM,该RAM的最大地址是0XFFFF,那么栈指针?C_XBP被初始化为0XFFFF 1=溢出为0x0000。再举一个小编译模式的例子,小编译模式是用来给没有外扩RAM的51用的,这样51只能使用片内0~127共128字节的RAM(这128RAN中还有一部分是Rn等,留给程序可用的RAM就更少了),在小编译模式下,keil给51生成的仿真栈指针名叫?C_IBP,同时需要程序员手动把IBPSTACK常量设置为1,指针?C_IBP的初值被初始化为可用RAM的最大地址(127)加1,也即0x7f 1。关于小编译模式small、压缩编译模式compact、大编译模式large在堆栈处理上方面的不同,可参考这篇文章点击打开链接,如果链接挂了,可自行搜索:《Keil模式设置和编程事项》。
注释1:满增栈,满指的是SP总是指向最后一个入栈的字节的地址,增指的是每入栈一次,SP变大。相应的,还有空增栈、空减栈、满减栈,空指的是SP总是指向栈中下一个空闲位置的地址。
注释2:如何选择大编译模式:以keil5为例,依次选择->魔术棒->Target选项卡,Memory Model选择Large:var...,Code Rom Size选择Large....
附:举一个不可重入函数使用中可能发生的陷阱,假设有分别有如下两个函数,第一个可重入,第二个不可重入
int add5_re(char a1,char a2,char a3,char a4,char a5) REENTRANT
{
int sum;
sum=a1 a2 a3 a4 a5;
return sum;
}
int add5(char a1,char a2,char a3,char a4,char a5)
{
int sum;
sum=a1 a2 a3 a4 a5;
return sum;
}
这两个函数的形参以及局部变量分配等信息我们查阅.m51文件,分别如下(分号后面的注释是博主自己加上的):
[plain] view plain copy------- PROC _?ADD5_RE
x:0002H SYMBOL a1 ;注意,地址标号前为小x,指a1倍分配到了仿真栈中
x:0003H SYMBOL a2
x:0004H SYMBOL a3
x:0005H SYMBOL a4
x:0006H SYMBOL a5
------- DO
x:0000H SYMBOL sum
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
------- PROC _ADD5
D:0007H SYMBOL a1 ;R7
D:0005H SYMBOL a2 ;R5
D:0003H SYMBOL a3 ;R3
X:14ABH SYMBOL a4 ;注意地址标号前为大X,指外部RAM
X:14ACH SYMBOL a5
------- DO
D:0006H SYMBOL sum ;R6
我们发现,add5中的形参和局部变量a1/a2/a3/sum分到了Rn中,a4/A5分到了外部RAM xdata的绝对地址处,如果我们在main的调用链中和中断函数中都调用了add5这个函数,就会发生错误,假设恰好在main的调用链中执行add5时发生了中断,切换到中断函数中去执行add5,那么main调用链中的a1/a2/a3/sum因为被分到了Rn中,进入中断会切换register BANK,使得main调用链中的a1/a2/a3/sum没有被破坏,得以幸免,但是a4/a5因为被分配到了绝对地址中,在中断执行完add5以后,main链条中的add5的a4/a5肯定会被破坏!!
对于可重入的add5_re函数,即使main调用链和中断同时调用它也不会出现上述被破坏的情形,因为add5_re的形参和局部变量全部都被定义到了仿真栈中(见上述代码注释),main调用链中使用add5_re函数会申请栈空间,中断时add5_re又会申请新的栈空间。
还要注意的是,因为keil编译51程序时,使用了覆盖技术(不同函数的形参和局部变量可分时共享同一个绝对内存单元),这也有可能产生陷阱,假设这样一种情况:有一个函数func2( )的局部变量b在编译后被分配到了绝对xdata的地址14ABH处,和上文的add5的a4变量共享内存,这种情况下,即使 { func2( )仅在中断中被调用,main调用链中不调用func2( )}、且{ add5仅在main调用链中被调用,中断中不调用add5 },也会出问题,原因是显而易见的,如果在add5执行过程中发生中断,中断中使用过变量b之后,会破坏add5中的变量a4。究其原因在于,共享地址的编译方式生成的函数,只要分时调用就不会产生被破坏的情形,但是发生中断导致了分时机制被破坏,以至于产生了同时调用。
结论:中断中使用的函数,要么是可重入的,要么是该函数的局部变量全部是独享内存单元的。
上一篇:51单片机定时器使用经验总结
下一篇:51单片机存储器小结
推荐阅读
史海拾趣
随着电子行业的快速发展,CETC意识到单打独斗难以应对市场的快速变化。于是,公司积极寻求与其他企业的合作,共同推动产业的发展。在与某知名通信设备制造商的合作中,CETC提供了先进的电子元件和解决方案,双方共同开发出了多款畅销产品,实现了市场的共赢。这种合作模式不仅提升了CETC的市场竞争力,也促进了整个电子行业的健康发展。
CETC深知人才是企业发展的根本。因此,公司一直注重人才培养和团队建设。公司设立了完善的培训体系,为员工提供各种学习和发展的机会。同时,CETC还积极引进国内外优秀人才,打造了一支高素质、专业化的团队。这支团队在公司的各个领域都发挥着重要作用,为公司的持续创新和发展提供了有力保障。
为了进一步提升公司的国际影响力,CETC开始积极布局海外市场。公司先后在美国、欧洲等地设立了研发中心和分支机构,与当地企业开展深度合作,共同研发适应市场需求的产品。同时,CETC还积极参加国际电子展会和论坛,展示公司的最新技术和产品,吸引了众多国际客户的关注。通过这些努力,CETC成功打开了海外市场的大门,为公司的长远发展注入了新的动力。
DESCO公司成立于XX年代初,创始人[XXXXX]先生凭借其深厚的电子工程背景和敏锐的市场洞察力,决定投身于防静电产品的研发与生产。在创业初期,公司面临资金紧张、技术壁垒高等诸多困难,但[XXXXX]先生坚持技术创新,带领团队攻克了一个又一个技术难关,成功研发出首款高性能防静电垫,为公司的后续发展奠定了坚实基础。
面对数字化浪潮和智能制造的兴起,DESCO公司积极拥抱变革。公司投入大量资金引进先进的自动化设备和智能化生产线,提高生产效率和产品质量。同时,DESCO还加强了数字化营销和客户服务体系建设,提升了客户体验和市场响应速度。这些举措使公司在激烈的市场竞争中保持了领先地位。
除了关注业务发展外,AverLogic公司还积极履行企业社会责任,推动可持续发展。公司注重环保和节能,采用环保材料和生产工艺,减少对环境的影响。同时,公司还积极参与公益事业,为社会做出贡献。这些举措不仅提升了公司的社会形象,也为其在电子行业中树立了良好的口碑。
需要注意的是,这些故事是基于一般性的电子行业趋势和公司可能的发展路径构建的,并不代表AverLogic公司的实际发展历程。如需了解AverLogic公司的具体发展故事,建议查阅相关的行业报告、公司年报或新闻报道。
数字万用表是利用模/数转换原理,将被测量转化为数字量,并将测量结果以数字形式显示出来的一种测量仪表。数字万用表与指针式万用表相比,具有精度高、速度快、输入阻抗大、数字显示、读数准确、抗干扰能力强,测量自动化程度高等优点而被广泛应用 ...… 查看全部问答∨ |
|
corelibc.lib(pegwmain.obj) : error LNK2019: unresolved external symbol WinMain referenced in function WinMainCRTStartup ARMV4IRel/test_t.exe : fatal error LNK1120: 1 unresolved externals Error executing link.exe. 请高手赐教 ...… 查看全部问答∨ |
PLC控制两台伺服电机工作:主轴做固定的圆周运动,要求从轴跟随主轴在一定的距离内(2MM左右)做循环正反转(两台伺服电机的加减速时间设各一致),我现在在实际的应用中困惑的是:主轴在经过加减速时间就能达设定的转速,并且恒速保持下去直到停止 ...… 查看全部问答∨ |
各位 我的 ucos 中断一直进不去 static void BSP_IntHandler (CPU_DATA int_id) { #if (CPU_CFG_CRITICAL_METHOD == CPU_CRITICAL_METHOD_STATUS_LOCAL) CPU_SR cpu_sr; #endif CPU_FNCT_VOID isr; CPU ...… 查看全部问答∨ |
|
2011-10-14-23:37回来继续MARK,原来帖子还是可以重复编辑的,不错刚刚发了个win7下面那个LaunchPad_Temp_GUI.exe有毛病的帖子貌似还没审核通过?恩,是的,等通过了一并贴过来================================2011-10-14-xx:xx:xx如题,做个记号 ...… 查看全部问答∨ |
请问可编程压力传感器的调理补偿是如何进行的呢?(如MLX90807)待测传感器需要放进容器里面进行调理补偿么(在容器里进行压力和温度的改变)?还是直接用一个普通传感器放到容器里找到几个点,将值输到软件里进行调理补偿(待测传感器放在外面的? ...… 查看全部问答∨ |