打印
[其他]

单片机内存不够用怎么办?

[复制链接]
1105|17
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
beacherblack|  楼主 | 2025-4-28 10:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1.为什么要优化内存?
在嵌入式开发中,内存是一项关键资源。优化单片机的内存使用对于提高性能、节约成本、扩展功能、降低能耗和提高可靠性都有重要意义。在嵌入式系统开发中,充分利用有限的内存资源,合理规划和管理内存使用是非常关键的工作。具体来讲,优化内存往往出于以下几个原因:

节约成本:单片机的内存是有限的资源,内存容量越大,成本越高。通过优化内存使用,可以减少芯片成本,特别是在大规模生产中,成本的降低是非常重要的考虑因素。
提高性能:内存的读写速度相对于外部存储器较快,优化内存使用可以提高程序的执行效率和响应速度。内存优化可以减少访问外部存储器的次数,减少读写延迟,提高系统的性能。
扩展功能:某些单片机的内存容量有限,如果内存不足以支持需要的功能和算法,可能无法实现复杂的应用需求。通过优化内存使用,可以释放出更多的内存空间,以便支持更多的功能和算法。
提高可靠性:内存的稳定性对于系统的可靠性至关重要。合理优化内存使用可以减少内存碎片、减少指针错误、避免内存泄漏等问题,提高系统的稳定性和可靠性。
节约能耗:内存的使用也会影响功耗消耗。内存容量越大,读写功耗也会相应增加。通过优化内存使用,减少内存访问次数和数据复制,可以降低功耗消耗,延长电池寿命。
2.内存是如何分布的?
在正式开始对单片机内存进行优化之前,首先要对内存分布有一个清晰的认识。通常对于栈生长方向向下的单片机,内存模型和每一段内存所包含的内容如下图所示:


中断向量表段:存放各种中断的处理程序入口地址。
代码段:存放代码信息。
数据段:存放各变量,如未初始化变量、常量、已初始化全局变量和静态变量等。
堆段:存放动态分配的数据。
栈段:存放局部变量和在函数内部定义的非静态变量等。
在对内存分布有初步了解后,我们便可以进入下一阶段了,即确定优化的重点。我们需要确定哪个内存段,哪个功能模块的编译产物体积较大。那如何知道这一部分信息呢?这个时候map文件就开始发挥出它的作用了。

3. map文件浅析
.map 文件是编译器链接时生成的一个文件,它往往分布在output文件夹下,主要包含了交叉链接信息。通过.map 文件,我们可以知道整个工程的函数调用关系、FLASH 和 RAM 占用情况及其详细汇总信息, 能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可以对代码进行优化。

要生成.map文件,只需要在Keil里的Listing选项卡里打开以下设置,然后全编译一下,就可以在相应文件夹找到map文件了。


3.1 map文件的基本概念:
为了更好的分析 map 文件,我们先对需要用到的一些基础概念进行一个简单介绍,相关概念如下:

RO: Read Only的缩写,包括只读数据(RO data)和代码(RO code)两部分内容占用FLASH空间。
RW: Read Write 的缩写,包含可读写数据(RW data,有初值,且不为 0),占用 FLASH(存储初值)和 RAM(读写操作) 。
ZI: Zero initialized 的缩写,包含初始化为 0 的数据(ZI data),占用 RAM 空间。
3.2 map信息详解:
.map文件可以分为以下5个组成部分:

程序段交叉引用关系 (Section Cross References)
删除映像未使用的程序段 (RemovingUnused input sections from the image)
映像符号表 (ImageSymbol Table)
映像内存分布图 (MemoryMapoftheimage)
映像组件大小 (Image component sizes)
下面我们就逐个分别来看一下:

3.3 程序段较差引用关系 (Section Cross References)
这部分内容描述了各个文件(.c/.s 等)之间函数(程序段)的调用关系,如下图所示,红色框框中区域所代表的含义是:

main.c文件中的main函数 调用了 led.c文件中的led_init函数。
这一部分的信息可以为我们后续进行内容优化提供关联信息,比如我们想要裁切掉某个没什么用功能的时候,可以查一下这个地方来确定是否关联性较小,裁切之后不会产生什么影响。


3.4 删除映像未使用的程序段 (RemovingUnused input sections from the image)
这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据),为了便于后续代码维护,可以确认下这里的代码是否后续业务也不会用到,如果没用的话可以直接删掉。如果不希望这一部分代码编译后占用内存的话,可以通过选中选项卡里的One ELF Section per Function选项实现此需求。



3.5 映像符号表 (ImageSymbol Table)
映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器 中的存储地址、类型、大小等信息。利用这一部分信息我们可以确定哪些东西占用内存较大,是需要重点来关注的。


3.6 映像内存分布图 (MemoryMapoftheimage)
映像文件分为加载域(Load Region)和运行域(Execution Region),一个加载域必须有至少一个运行域(可以有多个运行域),而一个程序又可以有多个加载域。加载域为映像程序的实际存储区域,而运行域则是 MCU 上电后的运行状态。加载域和运行域的简化关系如下图所示:


RW 区也是存放在 ROM(FLASH)里面的,在执行 main 函数之前,RW(有 初值且不为 0 的变量)数据会被拷贝到 RAM 区,同时还会在 RAM 里面创建 ZI 区(初始化 为 0 的变量)。

了解了加载域和运行域的作用及关系,我们再来看映像内存分布图:


上图列出了所有加载域及其运行域的具体内存分布,我们可以很方便的查看任何一个函数所在的运行域、入口地址、占用空间等信息。如 sys_stm32_clock_init 函数: 该函数在 ER_m_stmflash 运行域;入口地址为:0X0800 2BC8;大小为:0X168 字节;是 sys.c里面的函数。了解这些信息,对我们分析及优化程序非常有用。

3.7 映像组件大小(Image component sizes)

映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇总信息,通过这里我们可以清晰、直观、快速的确定要重点优化的区域,每一段所占用的内存都已经列好,如下图所示。


4.内存优化常用方法
确定完重点要优化的区域后,下面就可以正式根据具体情况开始优化了,下面简单整理了一些内存优化常用的方法,由于笔者水平有限,所以只写了自己知道的一部分,欢迎大家评论区补充。

根据结构体对齐原则检查并调整结构体内变量的定义顺序。
struct替换为union不造成影响的地方优先使用union。
能够使用条件编译(ifdef)的地方就使用条件编译而不使用if 语句。
根据需要选择合适的编译优化选项。
使用尽量小的数据类型。
裁切没有用到并且将来不会用到的功能。
RAM和ROM只有一个内存不足的情况下可以考虑改变变量类型,让变量存在内存充足的区域。

使用特权

评论回复
沙发
eefas| | 2025-5-3 21:45 | 只看该作者
合理选择数据类型,避免使用过大的数据类型。

使用特权

评论回复
板凳
averyleigh| | 2025-5-3 22:32 | 只看该作者
避免碎片,优先静态分配。              

使用特权

评论回复
地板
pixhw| | 2025-5-4 00:05 | 只看该作者
减少全局变量、使用紧凑数据结构、关闭未使用功能。

使用特权

评论回复
5
janewood| | 2025-5-4 03:09 | 只看该作者
利用编译器的优化选项,如代码压缩、内联函数等。

使用特权

评论回复
6
olivem55arlowe| | 2025-5-4 19:58 | 只看该作者
减少循环内计算,提前计算循环不变量

使用特权

评论回复
7
1988020566| | 2025-5-6 09:08 | 只看该作者
优先选择集成更多资源的芯片              

使用特权

评论回复
8
benjaminka| | 2025-5-6 10:51 | 只看该作者
通过函数复用、宏定义减少重复代码

使用特权

评论回复
9
lzmm| | 2025-5-6 12:46 | 只看该作者
优化函数调用,避免递归,减少局部变量占用

使用特权

评论回复
10
pentruman| | 2025-5-6 14:33 | 只看该作者
减少全局变量,改用局部变量。              

使用特权

评论回复
11
tabmone| | 2025-5-6 16:38 | 只看该作者
使用更高效的算法和数据结构。              

使用特权

评论回复
12
bestwell| | 2025-5-6 18:23 | 只看该作者
优化电路布局,减少不必要的外设占用

使用特权

评论回复
13
geraldbetty| | 2025-5-6 20:07 | 只看该作者
调整线程堆栈大小              

使用特权

评论回复
14
pixhw| | 2025-5-6 21:48 | 只看该作者
对于小数运算,如果精度要求不高,可以使用整数代替浮点数进行运算,因为浮点数运算通常需要更多的内存和处理时间。

使用特权

评论回复
15
wilhelmina2| | 2025-5-11 10:55 | 只看该作者
对于支持并行总线的单片机,可以通过并行接口扩展外部 SRAM

使用特权

评论回复
16
1988020566| | 2025-5-11 12:36 | 只看该作者
选择时间复杂度更低的算法              

使用特权

评论回复
17
jimmhu| | 2025-5-11 17:24 | 只看该作者
通过总线扩展外部存储器              

使用特权

评论回复
18
zhengshuai888| | 2025-5-12 07:52 | 只看该作者
如果内存不够可以外挂RAM芯片

使用特权

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

本版积分规则

16

主题

1398

帖子

1

粉丝