前言
现在为止也开发了许多杰理TWS蓝牙耳机、音响项目 SDK的案子,在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总,方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习;
本章详解讲解杰理可视化SDK开发-AC700N优化节省RAM空间方法教程
适用工程:
SDK/apps/earphone(AC700N TWS 蓝牙耳机公版 SDK)
编写原则:所有结论均来自本工程真实源码(附文件链接与行号)
本文最重要的提醒(来自 app_config.h 用户注释):
打印 ASSERT: Rom No enough physics memory 时,实际是 RAM 不够;真正的 ROM 不够在下载固件时就会报错

RAM不足的情况下,打印会出现断言死机,ASSERT: vmalloc_147
RAM 空间爆了会大概率导致芯片出现断连回连状态
RAM 非常紧张时,提示音必须改为打断式播放,叠加播放非常占用 RAM
一、介绍 RAM 空间
1.1 先搞懂:RAM 和 flash 是什么关系
JL 硬件设备包含两大物理存储介质,分工完全不同:
| 介质 | 特性 | 主要存什么 | 成本 |
|---|---|---|---|
| flash | 断电后数据不丢失 | 编译好的代码(.text 段)、常量、.data 段的初始值 | 相对低 |
| RAM | 断电后数据丢失 | 变量(全局变量、堆、栈)、函数调用现场数据 | 相对高 |
一句话:flash 负责"存",RAM 负责"跑"。程序运行时动态变化的东西全部在 RAM 里。
重要:RAM 有多大,由芯片型号决定(AC700F4/F6/F8 等各不同),同一套 SDK 烧到不同芯片 RAM 大小不一样。想知道自己芯片实际多大,最直接的方式就是用第二部分的方法打印出来看;另外下载/烧录工具在下载固件时也会报告 RAM 用量。
1.2 RAM 大小由芯片型号决定,不由统一 SDK 决定
杰理 SDK 软件包是统一的一套代码,同一份工程可以编译烧录到 AC700F4、AC700F6、AC700F8 等不同芯片——每个芯片的 RAM 空间大小都不一样,它由芯片硬件(mask,掩膜)决定,而不是写死在统一软件代码里的固定数字。
RAM 大小在 SDK 里的真实来源链路(全部源码可查证):
maskrom_stubs.ld是"芯片掩膜(mask)导出文件",按具体芯片型号生成,其中定义了本芯片的内存界限符号:
_IRQ_MEM_ADDR = ABSOLUTE(0x2bf00);
_MASK_MEM_BEGIN = ABSOLUTE(0x2bbe0);
_MASK_MEM_SIZE = ABSOLUTE(0x30c);

- 链接脚本
sdk_ld.c据此计算物理 RAM 范围:
UPDATA_BEG = _MASK_MEM_BEGIN - UPDATA_SIZE; // 0x2bbe0 - 0x80
RAM_LIMIT_H = UPDATA_BEG;
PHY_RAM_SIZE = RAM_LIMIT_H - RAM_LIMIT_L; // 当前这份 mask ≈ 175KB
RAM0_BEG = RAM_LIMIT_L; // 应用 ram0 区起点 = 0
RAM0_SIZE = RAM0_END - RAM0_BEG; // ram0 区大小

- 最终链接内存布局(
sdk.ld的 MEMORY 定义):ram0(rwx) : ORIGIN = RAM0_BEG, LENGTH = RAM0_SIZE。
结论:_MASK_MEM_BEGIN 来自芯片 mask,换一颗芯片型号,这个值就不同,RAM 总大小就不同。所以讨论 RAM 优化时不要记"AC700N 总 RAM 是 XX KB"这种死数字——app_config.h 注释里的 ram:640-128*TCFG_LOWPOWER_RAM_SIZE 只是 SDK 针对某一目标芯片的说明,直接看编译产物(下载工具报告的 RAM 用量),或用第 2 章的测量手段打印。
1.3 一张段模型表看懂"谁在占 RAM"
程序编译链接后生成的可执行文件(ELF)被划分为多个段(section),每个段放特定类型的数据/代码,烧录和运行时的位置如下:
| 段 | 内容 | 存储位置(烧录后) | 运行时位置 | 占 flash | 占 RAM |
|---|---|---|---|---|---|
.text | 可执行指令(代码函数) | flash | flash | 是 | 否 |
.rodata | 只读常量(杰理并入 .text 段) | flash | flash | 是 | 否 |
.data_code | 可执行指令(放 RAM 跑) | flash | RAM | 是 | 是 |
.data | 已初始化全局/静态变量 | flash(存初始值) | RAM | 是 | 是 |
.bss | 未初始化全局/静态变量 | 无 | RAM | 否 | 是 |
| 堆 | 动态内存(malloc) | 无 | RAM | 否 | 是 |
| 栈 | 局部变量、函数调用 | 无 | RAM | 否 | 是 |
overlay | 内存复用空间 | flash | RAM | 是 | 是 |
记住这一条就够了:真正吃 RAM 的只有五类——放 RAM 运行的代码(.data_code/overlay)、.data、.bss、堆、栈。 第三部分的所有优化手段都是在这五类上做文章。
1.4 代码放 RAM 还是放 flash?这是省 RAM 的核心原理
同一段算法代码,SDK 允许放在两个地方执行:
| 放置方式 | 运行机制 | 效率 | 代价 |
|---|---|---|---|
| 放 RAM | 启动时由启动代码从 flash 拷贝到 RAM,CPU 再跳转到 RAM 地址执行 | 更高、省电(避免频繁读 flash) | 占 RAM |
| 放 flash | CPU 程序计数器直接指向 flash 地址取指执行 | 一般 | 不占 RAM,占 flash |
公版 SDK 默认把部分算法代码放 RAM 提效率。所以 RAM 不够时最直接的办法:把这些代码放回 flash(第三部分第 3、4 招就是这么干的)。
1.4 RAM 爆了有什么表现(先认症状)
| 现象 | 说明 |
|---|---|
串口打印 ASSERT: Rom No enough physics memory | 名字带 Rom 极具迷惑性,实际是 RAM 不够(ROM 不够在下载固件时就报错了) |
串口打印 ASSERT: vmalloc_147 | RAM 不足,断言死机 |
| 蓝牙断连回连 | 莫名断开又自动回连,反复出现——RAM 爆了的信号 |
出现任一症状 → 先用第二部分抓 log 确认 RAM 水位 → 再按第三部分逐招优化。
二、抓 log 实时查看设备 RAM 使用大小
2.1 打开监控宏(一步搞定)
app_config.h 注释区里有现成的开关,把前面的 // 去掉:

//【查看当前运行RAM剩余】开启定时器打印
#define LYF_USER_RAM_MONITOR_ENABLE
配套的打印代码(app_main.c,开机初始化完成后启动定时器):

#ifdef LYF_USER_RAM_MONITOR_ENABLE
static void ram_printf(void *_arg) //定时器回调,打印当前RAM空间
{
extern void mem_stats(void); //打印当前内存
mem_stats();
}
#endif
// 在 app_task_init() 返回之后:
#ifdef LYF_USER_RAM_MONITOR_ENABLE
sys_timer_add(NULL, ram_printf, 2000); //初始化完成后,每2000ms打印一次
#endif
打开宏 → 重新编译烧录 → 串口抓 log。系统会每 2 秒自动打印一次当前 RAM 情况。不用了就注释掉宏重新编译,量产固件零开销(宏不开时打印代码完全不参与编译)。
2.2 抓到的 log 长这样
Current free heap 278424 bytes, minimum ever free heap 278160 bytes, physics memory size 98304 bytes , Miniphy 98176 bytes
2.3 这一行数字怎么读
| 字段 | 含义 | 怎么用 |
|---|---|---|
Current free heap |
当前堆空闲总量(RAM0+RAM1) | 看当前还剩多少 |
minimum ever free heap | 开机以来堆空闲历史最低水位(只降不升) | 最重要的安全指标 |
physics memory size | 物理内存区当前空闲量(RAM0) |
看当前还剩多少:RAM空间主要看这个 |
Miniphy | 物理内存历史最低水位(只降不升) | 本次开机以来最紧张时刻的剩余量 |
三、RAM 不足时怎么优化(七招,按性价比排序)
总原则:每改一处 → 重新编译烧录 → 抓 log 对比水位 → 实测功能。一次只改一处,出了问题才能定位。
第 1 招:A2DP 蓝牙缓存缩小


注意:置1后需要实测抗干扰卡顿是否能够接受,必须在弱信号/干扰环境实测音乐是否卡顿。
第 2 招:提示音改打断式(叠加播放非常占 RAM)
用户注释区原话:"提示音叠加播放非常占用RAM空间很容易造成RAM空间爆了"。
原因:每播一个提示音就创建一个 tone_player 解码实例,叠加播放 = 多个实例同时解码并存;打断式只有一个实例,RAM 占用恒定。
改法很简单——函数名规律:带 _alone 的就是打断式:
play_tone_file(...) //叠加(不打断当前提示音/音乐)
play_tone_file_alone(...) //打断式(先停当前再播新的)→ 省 RAM
tws_play_tone_file(...) //TWS 叠加
tws_play_tone_file_alone(...) //TWS 打断式 → 省 RAM
顺序连播多个提示音用 play_tone_files(...)(排队依次播放),同样只占单实例 RAM。注意:打断式会改变交互体验(后音截断前音),需产品端确认。
第 3 招:通话语音算法代码放回 flash(省大头)
audio_config_def.h 中,宏值 1 = 放 RAM,0 = 放 FLASH。当前工程全部为 1(放 RAM),逐个改 0:
新版本修改这个


旧版本修改这个

代价:代码移到 flash 后运行时取指变慢(语音算法实时性敏感)、flash 占用增加。必须逐个改、逐个实测通话效果。
第 4 招:AAC 解码代码放回 flash
新版本修改这里

#define AUD_AAC_DEC_AT_RAM 1 //AAC解码 → 改 0
旧版本修改这里

原理同第 3 招。代价:音乐解码性能下降,需实测 AAC 音乐播放。
第 5 招:音乐 PLC 调 Lite 版
PLC(丢包隐藏)有两种版本,SDK 注释:"0 - PLC Advance 比 1 - PLC Lite 更占资源"。改 sdk_config.h 的 TCFG_MUSIC_PLC_TYPE 取值(当前工程值 4096 含义源码未说明,可推测4096就是跟1等价吧)。
新版本修改如下


旧版本修改如下

第 6 招:关闭不用的功能(功能级回收)
| 功能 | 开关宏 | 说明 |
|---|---|---|
| 音频录制 | TCFG_AUDIO_DATA_EXPORT_DEFINE | 打开会多出 2 个任务(512+256 栈 ×2) |
| ANC 降噪 | CONFIG_ANC_ENABLE | anc + icsd 系列多任务 + ANC 参数 |
| 按键提示音 | TCFG_KEY_TONE_EN | key_tone 任务 256/32 |
| 设备管理 | TCFG_DEV_MANAGER_ENABLE | dev_mg 任务 512/512 |
| 产测 | TCFG_AUDIO_DUT_ENABLE | 关闭连带释放 SPP/在线调试协议栈 RAM |
本工程当前这些功能基本都已关闭——保持关闭就是省 RAM,注意别重新打开。
第7招:项目是采用双麦算法的话,使用第二套算法,可以省RAM空间。
注意:会增加FLASH空间消耗
新版本修改如下

旧版本修改如下

注意:如果把功能放入到FLASH中,出现代码跑不过来现象可以提高flash通信速率和提搞系统时钟(都需要确认该芯片支持该这两个功能)



制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_86262955/article/details/170732693




