打印

关于51中ram内存不够用和局部变量

[复制链接]
27074|34
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
samyoju|  楼主 | 2011-11-1 22:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 samyoju 于 2011-11-1 22:05 编辑

小弟最近接手一个项目,用的主控芯片是51芯片STC12C5A60S2。它有256字节内部ram。还有1024字节内部扩展ram。现在发现内存不够用了,用的是keil。memory model选项是选Large:variables in XDATA.

编译提示 Program Size: data=67.2 xdata=904 code=20163

还有一点ram空间,但是项目还有几个大功能还没实现。
现在想请教大家几个问题。
1、我建立局部变量的时候,它占用的是公共ram域,而不是堆栈里面。如果我再多建几个局部变量(数组),就很快超过1024.再加上内部ram的256字节剩下的空间估计要实现剩下的功能也不够了。如过超过了,程序也不会报错。程序能不够运行啊?就算能够运行,会不会可能因为内存出现一些莫名奇妙的问题?我最怕内存引起的问题了。
我在网上面查了一下,得出以下几点:
    ①局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
  ②在编译链接时,即已经完成局部变量的定位;
  ③如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
其中第3点中说到,局部变量空间可覆盖,什么意思啊?怎么覆盖啊?难道因为它可覆盖,我可以再建立局部变量,而不去理会编译时候提示的data超过芯片的256字节和xdata超过芯片的1024字节?
还有网上面说iar是把局部变量放在堆栈里面的,难道同一芯片,只因编译器的不同,就能解决因局部变量引起的ram内存不够的问题??

2、我是不是要严格按照data 为256字节上限,xdata为1024字节上限来编程呢?还有内部ram的256字节是不是不能用完,剩下一点给堆栈空间等等???

3、有什么办法可以优化一下ram内存的使用缓解一下ram不够用的问题啊??

希望高手们帮一下我这个菜鸟啊。感激不尽!

相关帖子

沙发
strang| | 2011-11-1 23:26 | 只看该作者
1# samyoju
这种问题我也遇到过,数据手册上所讲的RAM为256字节和XDATA为1024字节,但实际到不了这么多,系统本身就会占有一定的空间。建议你用外扩吧。

使用特权

评论回复
板凳
ejack| | 2011-11-2 07:34 | 只看该作者
设计之初你就应当对变量分配有个统筹,能不全局变量的就不要全局变量,能不静态的就不要静态。
真到了捉襟见肘的时候,部分变量可以用寄存器+程序代码交换掉,以时间换空间。
你可以先检查一下程序,看看有多少全局变量从声明之日起便独守空闺、一年也临幸不了一两回的……

使用特权

评论回复
地板
ayb_ice| | 2011-11-2 08:01 | 只看该作者
1# samyoju
这种问题我也遇到过,数据手册上所讲的RAM为256字节和XDATA为1024字节,但实际到不了这么多,系统本身就会占有一定的空间。建议你用外扩吧。 ...
strang 发表于 2011-11-1 23:26

不懂不要瞎说

使用特权

评论回复
5
xyz769| | 2011-11-2 08:18 | 只看该作者
 同意3楼。

使用特权

评论回复
6
murex| | 2011-11-2 08:22 | 只看该作者
系统资源跟这个RAM是相互独立的

使用特权

评论回复
7
dqyubsh| | 2011-11-2 11:02 | 只看该作者
我倒是觉得LZ的代码2M了,RAM还不到1K,已经很了不起了。

话说KEIL编译后的代码确实够大,最近写一个程序,16K字节的ROM+RAM已经基本耗尽,捉襟见肘,真恨自己当初为什么没扩展一片RAM。

C51,该死。

使用特权

评论回复
8
airwill| | 2011-11-2 12:17 | 只看该作者
同意3楼:
有很多减少 RAM 占用的技巧.
1. 优化程序, 让局部变量尽量放在寄存器里, 而不是 RAM 里.
2.多个数组, 怎么共享同一片内存区域.
3. 通常 Keil 是不会把局部变量放堆栈的. 但是可以设置函数为可重入(reentrant 属性)的方式.

使用特权

评论回复
9
Cortex-M0| | 2011-11-2 13:25 | 只看该作者
如压缩后,还是不行,RAM不够用,建议LZ换个51芯片试试。

LS052A 有 64K Flash ,  16K  RAM ,  应该够用了吧~~~

使用特权

评论回复
10
Cortex-M0| | 2011-11-2 13:30 | 只看该作者
给本 LS052A 使用说明书,供参考~~~

LS052A使用说明书.pdf (4.71 MB)

使用特权

评论回复
11
z_no1| | 2011-11-2 14:58 | 只看该作者
做开发最痛苦的就是写到快完工时,发现代码或者RAM不够用了,再优化也优化不下去了。
所以开始做规划时,一定要多规划些资源,产品成熟了再换便宜的。

使用特权

评论回复
12
Tonney_zzb| | 2011-11-2 15:24 | 只看该作者
定值数组不要用xdata 用code 关键字,能用BYTE 不用WORD  能用WORD不用DWORD
尽量变量复用,
对于不是0就是1 的用bit 型

使用特权

评论回复
13
chenluck| | 2011-11-2 15:42 | 只看该作者
全局变量有时候也是可以复用的,特别是当你需要一个比较长的局部数组时,可以借用全局数组,当然要注意数据的有效性问题,我就是经常使用复用方式的,见一下如下代码
xdata uchar UartRev[100] _at_ 100; //通信程序中的缓冲
xdata uchar UartSend[100] _at_ 200;
xdata uchar UartTemp[100] _at_ 300;
如此定义,则 UartRev最大的可用长度可以达到300,使用时注意点,可以省下不少的空间呀,
还有一些常量数组一定要声明成CODE类型的,则否........

使用特权

评论回复
14
afeikuo7| | 2011-11-2 15:49 | 只看该作者
顶三楼的~~尤其是最后一句 生动形象~

使用特权

评论回复
15
nolan19| | 2011-11-2 17:06 | 只看该作者
看了,学习下了。

使用特权

评论回复
16
hubinnihao| | 2011-11-2 19:06 | 只看该作者
sdcwsdewdewdwewe

使用特权

评论回复
17
samyoju|  楼主 | 2011-11-2 19:30 | 只看该作者
在网上找到了一篇《浅谈C51内存优化》,非常不错!!转过来看一下

浅谈C51内存优化(data idata xdata)

“Keil Cx51编译器提供三条编译模式控制命令:SMALL,COMPACT,LARGE,它们对变量存储器空间的影响如下。
SMALL:所有变量都被定义在8051单片机的片内RAM中,对这种变量的访问速度最快。另外,堆栈也必须位于片内RAM中,而堆栈的长度是很重要的,实际栈长取决与不同函数的嵌套深度。采用SMALL编译模式与定义变量时指定data存储器类型具有相同效果。

COMPACT:所有变量被定义在分页寻址的片外XRAM中,每一页片外XRAM的长度为256字节。这时对变量的访问是通过寄存器间接寻址(MOVX @R0,MOVX @R1)进行的,变量的低8位地址由R0和R1确定,变量的高8位地址由P2口确定。采用这种模式时,必须适当改变配置文件STARTUP.A51中的参数:PDATASTART和PDATALEN;同时还必须对uVision2的“Options选项/BL51 Locator 标签页/Pdata框”中键入合适的地址参数,以确保P2口能输出所需要的高8位地址。采用COMPACT编译模式与定义变量时指定pdata存储器类型具有相同效果。

LARGE:所有变量被定义在片外XRAM中(最大可达64KB),使用数据指针DPTR来间接访问变量(MOVX @DPTR),这种编译模式对数据访问的效率最低,而且将增加程序的代码长度。采用LARGE编译模式与定义变量时指定xdata存储器类型具有相同效果。”
摘自《Keil Cx51 V7.0单片机高级语言编程与uVision2应用实践》

如何设置编译模式,我剪了一个图如下,右键单击Target选择“Options for Target'Target1'”就会出来一个框,在memory model中选择好像就可以了。

对 51 单片机内存的认识,很多人有误解,最常见的是以下两种
① 超过变量128后必须使用compact模式编译
   实际的情况是只要内存占用量不超过 256.0 就可以用 small 模式编译
② 128以上的某些地址为特殊寄存器使用,不能给程序用
   与 PC 机不同,51 单片机不使用线性编址,特殊寄存器与 RAM 使用重复的重复的地址。但访问时采用不同的指令,所以并不会占用 RAM 空间。
    由于内存比较小,一般要进行内存优化,尽量提高内存的使用效率。
    以 Keil C 编译器为例,small 模式下未指存储类型的变量默认为data型,即直接寻址,只能访问低 128 个字节,但这 128 个字节也不是全为我们的程序所用,寄存器 R0-R7必须映射到低RAM,要占去 8 个字节,如果使用寄存组切换,占用的更多。
    所以可以使用 data 区最大为 120 字节,超出 120 个字节则必须用 idata 显式的指定为间接寻址,另外堆栈至少要占用一个字节,所以极限情况下可以定义的变量可占 247 个字节。当然,实际应用中堆栈为一个字节肯定是不够用的,但如果嵌套调用层数不深,有十几个字节也够有了。
为了验上面的观点,写了个例子
#define LEN 120
data UCHAR tt1[LEN];
idata UCHAR tt2[127];
void main()
{
    UCHAR i,j;
    for(i = 0;  i < LEN; ++i )
    {
        j = i;
        tt1[j] = 0x55;
    }
}
可以计算 R0-7(8) + tt1(120) + tt2(127) + SP(1) 总共 256 个字节
keil 编译的结果如下:
Program Size: data=256.0 xdata=0 code=30
creating hex file from ".\Debug\Test"...
".\Debug\Test" - 0 Error(s), 0 Warning(s).
(测试环境为 XP + Keil C 7.5)
    这段代码已经达到了内存分配的极限,再定义任何全局变量或将数组加大,编译都会报错 107
    这里要引出一个问题:为什么变量 i、j 不计算在内?
    这是因为 i、j 是局部变量,编译器会试着将其优化到寄存器 Rx 或栈。问题也就在这了,如果局部变量过多或定义了局部数组,编译器无法将其优化,就必须使用 RAM 空间,虽然全局变量的分配经过精心计算没有超出使用范围,仍会产生内存溢出的错误!
    而编译器是否能成功的优化变量是根据代码来的
    上面的代码中,循环是臃肿的,变量 j 完全不必要,那么将代码改成
UCHAR i;
UCHAR j;
for(i = 0;  i < LEN; ++i )
{
    tt1[i] = 0x55;
}
再编译看看,出错了吧!
因为编译器不知道该如何使用 j,所以没能优化,j 须占 RAM 空间,RAM 就溢出了。
(智能一点的编译器会自动将这个无用的变量去掉,但这个不在讨论之列了)
另外,对 idata 的定义的变量最好放在 data 变量之后
对于这一种定义
uchar c1;
idata uchar c2;
uchar c3;
变量 c2 肯定会以间接寻址,但它有可能落在 data 区域,就浪费了一个可直接寻址的空间
变量优化一般要注意几点:
    ①让尽可能多的变量使用直接寻址,提高速度
      假如有两个单字节的变量,一个长119的字符型数组
      因为总长超过 120 字节,不可能都定义在 data 区
      按这条原则,定义的方式如下:
      data UCHAR tab[119];
      data UCAHR c1;
      idata UCHaR c2;
      但也不是绝的,如果 c1, c2 需要以极高的频率访问,而 tab 访问不那么频繁
      则应该让访问量大的变量使用直接寻址:
      data UCAHR c1;
      data UCHaR c2;
      idata UCHAR tab[119];
      这个是要根据具体项目需求来确定的
    ②提高内存的重复利用率
      就是尽可能的利用局部变量,局部变量还有个好处是访问速度比较快
      由前面的例子可以看出,局部变量 i, j 是没有单独占用内存的
      子程序中使用内存数目不大的变量尽量定义为局部变量
    ③对于指针数组的定义,尽可能指明存储类型
       尽量使用无符号类型变量
      一般指针需要一个字节额外的字节指明存储类型
     8051 系列本身不支持符号数,需要外加库来处理符号数,一是大大降低程序运行效率,二是需要额外的内存
    ④避免出现内存空洞
      可以通过查看编译器输出符号表文件(.M51)查看
      对前面的代码,M51文件中关于内存一节如下:
* * * * * * *   D A T A   M E M O R Y   * * * * * * *
REG     0000H     0008H     ABSOLUTE     "REG BANK 0"
DATA    0008H     0078H     UNIT         ?DT?TEST
IDATA   0080H     007FH     UNIT         ?ID?TEST
IDATA   00FFH     0001H     UNIT         ?STACK
第一行显示寄存器组0从地址0000H开始,占用0008H个字节
第二行显示DATA区变量从0008H开始,占用0078H个字节
第三行显示IDATA区变量从0080H开始,占用007F个字节
第四行显示堆栈从00FFH开始,占0001H个字节
由于前面代码中变量定义比较简单,且连续用完了所有空间,所以这里显示比较简单
变量定义较多时,这里会有很多行
如果全局变量与局部变量分配不合理,就有可能出现类似下面的行
0010H     0012H                  *** GAP ***
      该行表示从0010H开始连续0012H个字节未充分利用或根本未用到
出现这种情况最常见的原因是局变量太多、多个子程序中的局部变量数目差异太大、使用了寄存器切换但未充分利用

使用特权

评论回复
18
samyoju|  楼主 | 2011-11-2 19:33 | 只看该作者
还有这篇,也不错。《指针类型和存储区的关系详解》

指针类型和存储区的关系详解

一、存储类型与存储区关系

    data     --->    可寻址片内ram
    bdata    --->    可位寻址的片内ram
    idata    --->    可寻址片内ram,允许访问全部内部ram
    pdata    --->    分页寻址片外ram (MOVX @R0) (256 BYTE/页)
    xdata    --->    可寻址片外ram (64k 地址范围)
    code     --->    程序存储区 (64k 地址范围),对应MOVC @DPTR

二、指针类型和存储区的关系

    对变量进行声明时可以指定变量的存储类型如:
    uchar data x和data uchar x相等价都是在内ram区分配一个字节的变量。

    同样对于指针变量的声明,因涉及到指针变量本身的存储位置和指针所指向的存储区位置不同而进行相应的存储区类型关键字的
使用如:

    uchar xdata * data pstr

    是指在内ram区分配一个指针变量("*"号后的data关键字的作用),而且这个指针本身指向xdata区("*"前xdata关键字的作用),
可能初学C51时有点不好懂也不好记。没关系,我们马上就可以看到对应“*”前后不同的关键字的使用在编译时出现什么情况。

    ......
    uchar xdata tmp[10];    //在外ram区开辟10个字节的内存空间,地址是外ram的0x0000-0x0009
    ......

    第1种情况:

    uchar data * data pstr;
    pstr=tmp;

    首先要提醒大家这样的代码是有bug的, 他不能通过这种方式正确的访问到tmp空间。 为什么?我们把编译后看到下面的汇编
代码:

    MOV 0x08,#tmp(0x00)        ;0x08是指针pstr的存储地址

    看到了吗!本来访问外ram需要2 byte来寻址64k空间,但因为使用data关键字(在"*"号前的那个),所以按KeilC编译环境来说
就把他编译成指向内ram的指针变量了,这也是初学C51的朋友们不理解各个存储类型的关键字定义而造成的bug。特别是当工程中的
默认的存储区类为large时,又把tmp[10] 声明为uchar tmp[10] 时,这样的bug是很隐秘的不容易被发现。

    第2种情况:

    uchar xdata * data pstr;
    pstr = tmp;

    这种情况是没问题的,这样的使用方法是指在内ram分配一个指针变量("*"号后的data关键字的作用),而且这个指针本身指向
xdata区("*"前xdata关键字的作用)。编译后的汇编代码如下。

    MOV 0x08,#tmp(0x00)        ;0x08和0x09是在内ram区分配的pstr指针变量地址空间
    MOV 0x09,#tmp(0x00)

    这种情况应该是在这里所有介绍各种情况中效率最高的访问外ram的方法了,请大家记住他。

    第3种情况:

    uchar xdata * xdata pstr;
    pstr=tmp;

    这中情况也是对的,但效率不如第2种情况。编译后的汇编代码如下。

    MOV DPTR, #0x000A        ;0x000A,0x000B是在外ram区分配的pstr指针变量地址空间
    MOV A, #tmp(0x00)
    MOV @DPTR, A
    INC DPTR
    MOV A, #tmp(0x00)
    MOVX @DPTR, A

    这种方式一般用在内ram资源相对紧张而且对效率要求不高的项目中。

    第4种情况:

    uchar data * xdata pstr;
    pstr=tmp;

    如果详细看了第1种情况的读者发现这种写法和第1种很相似,是的,同第1 种情况一样这样也是有bug的,但是这次是把pstr分
配到了外ram区了。编译后的汇编代码如下。

    MOV DPTR, #0x000A        ;0x000A是在外ram区分配的pstr指针变量的地址空间
    MOV A, #tmp(0x00)
    MOVX @DPTR, A

    第5种情况:

    uchar * data pstr;
    pstr=tmp;

    大家注意到"*"前的关键字声明没有了,是的这样会发生什么事呢?下面这么写呢!对了用齐豫的一首老歌名来说就是 “请跟我
来”,请跟我来看看编译后的汇编代码,有人问这不是在讲C51吗? 为什么还要给我们看汇编代码。C51要想用好就要尽可能提升C51
编译后的效率,看看编译后的汇编会帮助大家尽快成为生产高效C51代码的高手的。还是看代码吧!

    MOV 0x08, #0X01            ;0x08-0x0A是在内ram区分配的pstr指针变量的地址空间
    MOV 0x09, #tmp(0x00)
    MOV 0x0A, #tmp(0x00)

    注意:这是新介绍给大家的,大家会疑问为什么在前面的几种情况的pstr指针变量都用2 byte空间而到这里就用3 byte空间了
呢?这是KeilC的一个系统内部处理,在KeilC中一个指针变量最多占用 3 byte空间,对于没有声明指针指向存储空间类型的指针,
系统编译代码时都强制加载一个字节的指针类型分辩值。具体的对应关系可以参考KeilC的help中C51 User's Guide。

    第6种情况:

    uchar * pstr;
    pstr=tmp;

    这是最直接最简单的指针变量声明,但他的效率也最低。还是那句话,大家一起说好吗!编译后的汇编代码如下。

    MOV DPTR, #0x000A        ;0x000A-0x000C是在外ram区分配的pstr指针变量地址空间
    MOV A, #0x01
    MOV @DPTR, A
    INC DPTR
    MOV DPTR, #0x000A
    MOV A, #tmp(0x00)
    MOV @DPTR, A
    INC DPTR
    MOV A, #tmp(0x00)
    MOVX @DPTR, A

    这种情况很类似第5种和第3种情况的组合,既把pstr分配在外ram空间了又增加了指针类型的分辨值。

    小结一下:大家看到了以上的6种情况,其中效率最高的是第2种情况,既可以正确访问ram区又节约了代码,效率最差的是第 6
种,但不是说大家只使用第2种方式就可以了,还要因情况而定,一般说来应用51系列的系统架构的内部ram资源都很紧张,最好大家
在定义函数内部或程序段内部的局部变量使用内ram,而尽量不要把全局变量声明为内ram区中。所以对于全局指针变量我建议使用第
3 种情况,而对于局部的指针变量使用第2种方式。

    C51是很灵活的,也很好理解和使用,但要成为笑傲江湖的一代高手还是要多想多练,没有实际项目的锻炼是不容易提高的。希望这篇**对大家一点用处。

使用特权

评论回复
19
samyoju|  楼主 | 2011-11-2 19:35 | 只看该作者
常量数组一定要声明成CODE类型,这个其实程序里面也没多少常量数组。也声明了。

使用特权

评论回复
20
samyoju|  楼主 | 2011-11-2 19:37 | 只看该作者
设计之初你就应当对变量分配有个统筹,能不全局变量的就不要全局变量,能不静态的就不要静态。
真到了捉襟见肘的时候,部分变量可以用寄存器+程序代码交换掉,以时间换空间。
你可以先检查一下程序,看看有多少全局 ...
ejack 发表于 2011-11-2 07:34


这个:部分变量可以用寄存器+程序代码交换掉,以时间换空间。
请教一下,怎么做啊??不太懂!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

35

帖子

2

粉丝