当前位置:主页 > 小家电方案开发 >

小家电方案开发

case

单片机怎么管理内存和修改优化内存的?

时间:2021-04-28 11:24 点击次数:
    单片机怎么管理内存和修改优化内存的?
 
多数单片机中有EEPROM或FLASHROM 可以在程序中读取或写入但写入速度比较慢,过去是数ms,现在需几十到几百微秒好在现今许多单shu片机片上RAM多达1k或数K,基本上可以解决内存不足的情况如STC的51兼容型单片机片上最大RAM达8K,32位高档单片机有几十k至几百K不等的RAM。那么单片机是怎么管理内存的?又是怎么修改内存的?
 
 
 
单片机是怎么管理内存的?
 
1、内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。前面我们有介绍过如何使用 FSMC 外扩 SRAM,实现内存容量的扩展。通过扩展后,我们可使用的内存(外扩 SRAM 容量是 1MB,STM32F103ZET6 内部 SRAM 有64KB)就比较多了。
 
2、尽管有这么多可使用的内存,但我们还是得使用某种方式来有效管理这些内存,这种方式和我们学习 C 语言时,使用 malloc 和 free 函数来申请内存和内存释放类似,malloc 用于申请内存,free 用于释放内存。本文我们就来介绍一种简单的内存管理方式:分块内存管理。该方法的实现原理如图 44.1.1 所示:


  从上图我们可以看出,内存管理由内存池和内存管理表两部分组成,内存池被分成 n 个内存块,然后对应内存管理表,内存管理表就是用来标识相应的内存块是否使用,我们可以对它进行设置。当相应的内存管理表设置为 0 的时候,表示该内存块没有被使用;而当内存管理表设置为非零的时候,表示该内存已被使用,而内存管理表里面的数字表示当前申请内存块的数量,且申请的内存块是连续的。
 
从上图中我们还可以看出内存分配的方向是从底到顶的分配方向,即首先从最末端开始找空内存。通常当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
 
 
单片机内存的分配原理和释放原理:
 
1、内存分配原理
 
当指针 p 调用 malloc 申请内存的时候,先判断 p 要分配的内存块数(m),然后从第 n 项开始,向下查找,直到找到 m 块连续的空内存块(即对应内存管理表项为 0),然后将这 m 个内存管理表项的值都设置为 m(标记被占用),最后,把最后的这个空内存块的地址返回指针 p,完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的 m 块空闲内存),则返回 NULL 给 p,表示分配失败。

2、内存释放原理
 
当 p 申请的内存用完,需要释放的时候,调用 free 函数实现。free 函数先判断 p 指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到p 所占用的内存块数目 m(内存管理表项目的值就是所分配内存块的数目),将这 m 个内存管理表项目的值都清零,标记释放,完成一次内存释放。关于分块式内存管理,我们就介绍到这里,想要了解更多内存管理的知识可以百度查找,网上也有很多这样的介绍。
 
 
怎么将51单片机进行内存优化修改?
 
对51单片机内存的认识,很多人有误解,最常见的是以下两种
 
① 超过变量128后必须使用compact模式编译

实际的情况是只要内存占用量不超过 256.0 就可以用 small 模式编译
 
② 128以上的某些地址为特殊寄存器使用,不能给程序用
 
与 PC机不同,51单片机不使用线性编址,特殊寄存器与 RAM 使用重复的重复的地址。但访问时采用不同的指令,所以并不会占用 RAM 空间。
 
由于内存比较小,一般要进行内存优化,尽量提高内存的使用效率。
 
以 Keil C 编译器为例,small 模式下未指存储类型的变量默认为data型,即直接寻址,只能访问低 128 个字节,但这 128 个字节也不是全为我们的程序所用,寄存器 R0-R7必须映射到低RAM,要占去 8 个字节,如果使用寄存组切换,占用的更多。
 
如何将51单片机进行内存优化
 
所以可以使用 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个字节未充分利用或根本未用到
 
出现这种情况最常见的原因是局变量太多、多个子程序中的局部变量数目差异太大、使用了寄存器切换但未充分利用。
 

Copyright © 2001-2020 深圳单片机公司版权所有 备案号:粤ICP备09008620号-23

在线客服 联系方式 二维码

服务热线

13430882266

扫一扫,关注我们