Cx51程序设计堆栈的计算方法

发布者:静默思考最新更新时间:2011-08-23 关键字:Cx51程序  程序设计  堆栈 手机看文章 扫描二维码
随时随地手机看文章

引言

  用C语言进行MCS51系列单片机程序设计是单片机开发和应用的必然趋势。Keil公司的C51编译器支持经典8051和8051派生产品的版本,通称为Cx51。应该说,Cx51是C语言在MCS51单片机上的扩展,既有C语言的共性,又有它自己的特点。本文介绍的是Cx51程序设计时堆栈的计算方法。

  1堆栈的溢出问题

  MCS51系列单片机将堆栈设置在片内RAM中,由于片内RAM资源有限,堆栈区的范围也是有限的。堆栈区留得太大,会减少其他数据的存放空间,留得太少则很容易溢出。所谓堆栈溢出,是指在堆栈区已经满了的时候还要进行新的压栈操作,这时只好将压栈的内容存放到非堆栈区的特殊功能寄存器(SFR)中或者堆栈外的数据区中。特殊功能寄存器的内容影响系统的状态,数据区的内容又很容易被程序修改,这样一来,之后进行出栈操作(如子程序返回)时内容已变样,程序也就乱套了。因此,堆栈区必须留够,宁可大一些。要在Cx51程序设计中防止堆栈的溢出,要解决两个问题:第一,精确计算系统分配给用户的堆栈大小,假设是M;第二,精确计算用户需要堆栈的大小,假设是N。要求M≥N,下面分别分析这两个问题。

  2计算系统

  分配给用户的堆栈大小Cx51程序设计中,因为动态局部变量是长驻内存中的,实际上相当于局部静态变量,即使在函数调用结束时也不释放空间(这一点不同于标准C语言)。Cx51编译器按照用户的设置,将所有的变量存放在片内和片外的RAM中。片内变量分配好空间后,将剩下的空间全部作为堆栈空间,这个空间是最大可能的堆栈空间。当然,因为Cx51是一种可以访问寄存器的C语言(特殊功能寄存器),因此可在程序中访问SP,将堆栈空间设置得小一点。不过,一般没有人这么做。本文只是讨论放在片内RAM的变量。我们把变量分为两种情况:

  ① 用作函数的参数和函数返回值的局部变量。这种变量尽量在寄存器组中存放。为了讨论方便,假设统一用寄存器组0,具体的地址为0x00~0x07。最多可以传递3个参数,如果参数的个数比较多,就将多余的参数放到内存(0x08以后的地址)中存放。这里,假设每个函数的参数都不大于3个。

  ② 我们在程序中定义的全局变量,以及不是用作函数的参数和函数返回值的局部变量。以上两种变量在内存中0x08地址以后存放,存放完毕后将堆栈指针SP指向分配了变量的片内RAM的最后一个字节。因为MCS51单片机的堆栈是一种满递增堆栈且堆栈的宽度为8位,所以在需要压栈操作时将堆栈指针先加1,后入栈有效内容。有了以上规则,就可以精确地计算出系统分配给用户的堆栈空间。以求两个数的最大公约数和最小公倍数的函数为例,代码如下:

  #include

  unsigned char max(unsigned char a, unsigned char b);

  unsigned char min(unsigned char a, unsigned char b);

  unsigned char M;

  void main (void) {

  unsigned char n;

  M = max(12, 9);

  n = min(12, 9);

  }

  unsigned char max(unsigned char a, unsigned char b){

  while(a != b) {

  if(a > b)

  a = a - b;

  else

  b = b - a;

  }

  return a;

  }

  unsigned char min(unsigned char a, unsigned char b){

  unsigned char k;

  k = a*b/M;

  return k;

  }

这段程序中资源的分配情况如下:一个全变量M(无符号字符型)存放最大公约数;主函数中定义一个局部变量n(无符号字符型)存放最小公倍数;求最大公约数的函数unsigned char max(unsigned char a, unsigned char b),有两个参数a和b;求最小公倍数的函数unsigned char min(unsigned char a, unsigned char b),有两个参数a和b,并且定义了一个变量k存放函数的返回值。可以由此计算出系统分配给变量的空间。函数的参数和返回值在工作寄存器组中存放,所以不占用0x08地址以后的空间。系统只给变量M和变量n分配存储空间,这两个变量占两个字节(地址为0x08和0x09),则堆栈指针SP应该指向0x09。Cx51系统编译后生成代码的系统资源占用情况如下:全局变量M的地址为0x08,n的地址为0x09,SP的值为0x09。这与我们的计算结果相符。

  3计算用户需要堆栈的大小

  堆栈区到底留多大才算足够呢? Cx51程序设计中,用户需要堆栈的大小可以从普通子函数和中断子程序的嵌套层数来计算。普通子函数的调用比较简单,每次调用时就是将函数的返回地址保存在堆栈中,这个地址占两个字节。函数嵌套调用时,从最内层的子函数算起,总的堆栈需求字节数为嵌套的层数乘以2。中断子程序的堆栈需求分为两种情况:

  ① 中断子程序使用中断发生前的寄存器组。在中断发生时,保存中断子程序的返回地址需要2个字节。中断发生后,在中断子程序中系统会自动进行如下操作:将ACC、B、DPH、DPL、PSW、R0~R7共13个寄存器压栈。加上中断返回地址,中断的堆栈需求为15个字节。

  ② 中断子程序使用自己专用的寄存器组。这种情况下不需要保存R0~R7的内容,可以减少堆栈需求,其他的内容仍需要压栈保护。中断发生时,保存中断子程序的返回地址需要2个字节。中断发生后,在中断子程序中系统会自动进行如下操作:将ACC、B、DPH、DPL、PSW共5个寄存器压栈。加上、中断返回地址,这种堆栈的需求为7个字节。但是这种情况应该注意:如果中断子程序中调用子函数,且函数需要参数和返回值,则被调用的子函数和中断子程序要使用相同的寄存器组,否则会出现不可预料的后果。以一个温度测试系统为例。系统采用8051作为处理器,温度信号在A/D转换结束后通过外部中断0提醒单片机接收处理。定时中断0作为监控程序,中断周期为20 ms。温度信号可以自动测量(每秒一次)或者手动测量(按测量键后测量),这两种测量方法可以通过控制键切换。中断子程序和普通子函数的嵌套情况为:在定时中断程序中调用显示子程序,外部中断0内部没有函数调用。部分程序如下:

  void int0(void) interrupt 0 using 1 {

  读取转换数据;

  数据处理;

  }

  void time0 (void) interrupt 1 {

  计数值重装;

  读键;

  按键处理;

  leddisp(adat);//显示

  }

  void main (void) {

  相关数据初始化和数码显示自检;

  外部中断和定时器初始化设置;

  单片机休眠;

  }

  void leddisp(unsigned char *pt) {

  用串口工作方式0发送显示数据,并经过74LS164转换后静态显示;

  }

  接下来分析这段程序的最大堆栈需求。假设定时器0中断时,调用了显示函数void leddisp(unsigned char *pt),在调用显示函数时A/D转换结束发生了外部中断0的中断。这时应该是程序对堆栈的最大需求,堆栈的大小是:定时器0(15字节)+显示函数(2字节)+外部中断0(7字节)=24字节。

  结语

  通过精确的计算编译系统分配给用户的堆栈空间和用户自己最大的堆栈需求,不仅能从根本上解决堆栈溢出的问题,还可以很好地安排单片机比较紧张的资源。此外,通过在片内存储器存放适量局部变量,还可以有效地提高软件的执行速度。

关键字:Cx51程序  程序设计  堆栈 引用地址:Cx51程序设计堆栈的计算方法

上一篇:基于P87C591的CAN总线信号采集节点的设计
下一篇:基于SX52BD单片机的嵌入式远程电网监测系统构建

推荐阅读最新更新时间:2024-03-16 12:39

堆栈指针寄存器 SP 详解
堆栈是一种具有 后进先出 (LIFO---Last In First Out)特殊访问属性的存储结构。堆 栈一般使用RAM 物理资源作为存储体,再加上LIFO 访问接口实现。 堆栈的实现方法: 在随机存储器区划出一块区域作为堆栈区,数据可以一个个顺序地存入(压入)到这个区域之中,这个过程称为 压栈 (push )。通常用一个指针(堆栈指针 SP---Stack Pointer)实现做一次调整,SP 总指向最后一个压入堆栈的数据所在的数据单元(栈顶)。从堆栈中读取数据时,按照堆栈 指针指向的堆栈单元读取堆栈数据,这个过程叫做 弹出 (pop ),每弹出一个数据,SP 即向相反方向做一次调整,如此就
[单片机]
Franklin C-51语言程序设计基础
1.1 Franklin C-51数据类型 Franklin C-51编译器支持下列数据类型: 数据类型 长度 值域 bit 1 字节 0 或 1 signed char 1 字节 -128~+127 unsigned char 1 字节 0~255 signed int 2 字节 -32768~+32867 unsigned int 2 字节 0~65535 signed long 4 字节 -2147483648~+2147483647 unsigned long 4 字节 0~4294967295 float 4 字节 ±1.176E-38~±3.40E+38 指针 1~3 字节 对象地址 sbi
[单片机]
STM32F0开发笔记6: 在Keil中使用C++进行程序设计
希望在Keil中使用C++进行程序设计,开始时,总是报错,经过仔细分析,原因时Keil在默认情况下不支持C++进行程序设计。本文将介绍在Keil中使用C++进行程序设计的方法。 1、在Keil中使用C++进行程序设计,首先需要系统支持,如下图所示。 2、建立类,需要2个文件,cpp和hpp,在hpp中对类进行描述,在cpp中完成函数实现。 依循上述方法就可以使用C++进行程序设计了。
[单片机]
STM32F0开发笔记6: 在Keil中使用C++进行<font color='red'>程序设计</font>
基于ARM的硬件启动程序设计-分配中断向量表
ARM要求中断向量表必须防放置在从0x00000000地址开始的连续32字节的空间内。ARM9定义的中断向量在向量表中的地址如下面所示: 当中断发生后,ARM处理器会强制把PC指针指向中断向量表中对应的终端类型的地址处。 中断向量表的程序设计如下: CODE32 AREA Startup,CODE,READONLY ; /* 异常向量表 */ Vectors LDR PC, ResetAddr ;把ResetAdde地址上的存储器的内容装载到PC上 LDR PC, UndefinedAddr LDR PC, SWI_Addr LDR PC, Prefetc
[单片机]
基于ARM的硬件启动<font color='red'>程序设计</font>-分配中断向量表
PLC程序设计应用于反渗透自动控制系统
摘要:该文以反渗透PLC自动控制系统为例,介绍了一种PLC程序设计方法。该方法优化了程序结构,增加了梯形图语言的可读性,使之更接近自然语言。   反渗透是一种膜分离技术,反渗透膜的孔径与水分子基本一致,只有与水分子大小相仿的粒子能够通过,其他粒子或杂质被分离出去,从而使原水得到净化,工艺流程如图1所示。   随着反渗透系统设备造价和运行费用的不断降低,越来越多的行业(电力、石油、煤炭、化工等)都在使用反渗透系统生产各种工艺用脱盐水,由于反渗透系统人工方式很难保证反渗透系统的长期稳定运行,因此采用PLC作为反渗透系统的自动控制设备就变得非常必要。本文结合实际,介绍一种反渗透PLC控制系统的编程方法,用来简化系统的逻辑关系
[工业控制]
PLC<font color='red'>程序设计</font>应用于反渗透自动控制系统
对ARM堆栈的理解
堆栈其实就是内存中的一段连续空间,只是有了堆栈指针,所以显得比较特别,堆栈一般分为两种: 向上生长:堆栈指针向高地址方向生长,称为递增堆栈。 向下生长:堆栈指针向低地址方向生长,称为递减堆栈。 对于堆栈来说,可以进行插入或者删除操作的一端称为栈顶,相应的,另一端称为栈底,由于堆栈只允许在一端进行操作,因而按照后进先出的原理进行运作。堆栈指针指向最后一个压入堆栈的有效数据项。 为什么说 向上生长或向下生长 呢,这是一个习惯的问题,一般画堆栈示意图,习惯上把低地址画在下面,高地址画在上面,如下图所示: ARM中,虽然对两种方式的堆栈均支持,但是一般程序编译器仅支持一种方式,即从上往
[单片机]
西门子PLC动态加密计时程序设计
这个时候点击HMI上的生成解锁码按钮,生成解锁码(解锁码是在动态验证码中挑选8位生成的,在此基础上还可以扩展出随机生成的解锁码..........)。 然后客户把解锁码告诉给调试人员,调试人员根据解锁码计算出解密密码告诉给客户解密(调试人员是需要知道这个解密算法)。 加密计时模块 程序块数据结构 程序完成步骤 1、首先调用RD_LOC_T读取日期时间存入到FB块本地临时变量中,读取秒数,根据秒数计算天数 2、调用西门子官方随机数生成块,并将随机数加上索引。 3、将随机数按照索引的顺序,放入相应的寄存器,并转成字符并显示在HMI上,可以看到动态的字符变化的炫酷效果 4、生成解锁码显示在HMI,客户把这个
[嵌入式]
西门子PLC动态加密计时<font color='red'>程序设计</font>
串行实时时钟芯片DSl302程序设计中的问题与对策
   摘 要: 指出了串行实时时钟芯片DSl302程序设计中几个易被疏忽而导致错误的问题,分析了问题的原因,并给出了解决问题的方法。     关键词: 串行时钟 程序设计 问题 原因 解决方法     美国Dallas公司推出的串行接口实时时钟芯片DSl302可对时钟芯片备份电池进行涓流充电。由于该芯片具有体积小、功耗低、接口容易、占用CPU I/O口线少等主要特点,故该芯片可作为实时时钟广泛应用于智能化仪器仪表中。     笔者在调试中发现在对DSl302编程中有几个问题易被疏忽而导致错误,现提供给读者参考。     1 读操作出现的错误     按照参考文献 的读操作程序框图和参考文献
[半导体设计/制造]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • ARM裸机篇--按键中断
    先看看GPOI的输入实验:按键电路图:GPF1管教的功能:EINT1要使用GPF1作为EINT1的功能时,只要将GPFCON的3:2位配置成10就可以了!GPF1先配 ...
  • 网上下的--ARM入门笔记
    简单的介绍打今天起菜鸟的ARM笔记算是开张了,也算给我的这些笔记找个存的地方。为什么要发布出来?也许是大家感兴趣的,其实这些笔记之所 ...
  • 学习ARM开发(23)
    三个任务准备与运行结果下来看看创建任务和任运的栈空间怎么样的,以及运行输出。Made in china by UCSDN(caijunsheng)Lichee 1 0 0 ...
  • 学习ARM开发(22)
    关闭中断与打开中断中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西,但程序突然中断了,又让另外一个 ...
  • 学习ARM开发(21)
    先要声明任务指针,因为后面需要使用。 任务指针 volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • 学习ARM开发(20)
  • 学习ARM开发(19)
  • 学习ARM开发(14)
  • 学习ARM开发(15)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved