李永奉头像
杰理可视化SDK开发-AC700N芯片优化节省RAM空间方法教程封面图

杰理可视化SDK开发-AC700N芯片优化节省RAM空间方法教程

前言

        现在为止也开发了许多杰理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 里的真实来源链路(全部源码可查证):

  1. maskrom_stubs.ld 是"芯片掩膜(mask)导出文件",按具体芯片型号生成,其中定义了本芯片的内存界限符号:
_IRQ_MEM_ADDR   = ABSOLUTE(0x2bf00);
_MASK_MEM_BEGIN = ABSOLUTE(0x2bbe0);
_MASK_MEM_SIZE  = ABSOLUTE(0x30c);

  1. 链接脚本 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 区大小

  1. 最终链接内存布局(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可执行指令(代码函数)flashflash是否
    .rodata只读常量(杰理并入 .text 段)flashflash是否
    .data_code可执行指令(放 RAM 跑)flashRAM是是
    .data已初始化全局/静态变量flash(存初始值)RAM是是
    .bss未初始化全局/静态变量无RAM否是
    堆动态内存(malloc)无RAM否是
    栈局部变量、函数调用无RAM否是
    overlay内存复用空间flashRAM是是

    记住这一条就够了:真正吃 RAM 的只有五类——放 RAM 运行的代码(.data_code/overlay)、.data、.bss、堆、栈。 第三部分的所有优化手段都是在这五类上做文章。

    1.4 代码放 RAM 还是放 flash?这是省 RAM 的核心原理

    同一段算法代码,SDK 允许放在两个地方执行:

    放置方式运行机制效率代价
    放 RAM启动时由启动代码从 flash 拷贝到 RAM,CPU 再跳转到 RAM 地址执行更高、省电(避免频繁读 flash)占 RAM
    放 flashCPU 程序计数器直接指向 flash 地址取指执行一般不占 RAM,占 flash

    公版 SDK 默认把部分算法代码放 RAM 提效率。所以 RAM 不够时最直接的办法:把这些代码放回 flash(第三部分第 3、4 招就是这么干的)。

    1.4 RAM 爆了有什么表现(先认症状)

    现象说明
    串口打印 ASSERT: Rom No enough physics memory名字带 Rom 极具迷惑性,实际是 RAM 不够(ROM 不够在下载固件时就报错了)
    串口打印 ASSERT: vmalloc_147RAM 不足,断言死机
    蓝牙断连回连莫名断开又自动回连,反复出现——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_ENABLEanc + icsd 系列多任务 + ANC 参数
    按键提示音TCFG_KEY_TONE_ENkey_tone 任务 256/32
    设备管理TCFG_DEV_MANAGER_ENABLEdev_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

    文章来源转载

    评论

    评论列表

    微信小程序
    QQ小程序

    关于作者

    点赞数:0
    关注数:0
    粉丝:0
    文章:0
    关注标签:0
    加入于:--