ESP 内存占用优化

[English]

备注

虽然该文档主要针对 ESP32-C2 芯片的内存优化,但其中的大部分优化项适用于所有 ESP 系列芯片,因此在其他 ESP 芯片上进行内存优化时也可参考此文档。

引言

ESP32-C2 芯片可用物理内存为 256 KB,但是实际测试时,按照 IDF 指导文档编译 BLE + Wi-Fi 的共存示例 bleprph_wifi_coex,运行起来之后的剩余内存就只有 24 KB,这基本上很难在 ESP32-C2 上面开发复杂的应用。

造成这种问题的原因是 ESP32-C2 默认配置是性能优先,很多基础组件都编译到 IRAM 以提高运行速度。但是对于 ESP32-C2 这种低成本方案来而言,大部分场景只需要简单的控制操作,针对这样的应用,用户可以大幅度优化内存,在深度优化的情况下最多可以优化出超过 100 KB 的内存。

本文在 ESP-IDF release/v5.5 分支上对 ESP32-C2 的内存进行了优化,并说明这些优化对于内存的影响以及如何使能这些优化项。

警告

本文档所列的部分内存占用优化方法可能会降低系统性能及稳定性,在进行内存占用优化后应当进行充分的性能与稳定性测试,以确保满足应用需求。

获取当前剩余内存

在进行内存优化前,需要首先了解当前的内存占用情况,对于静态内存占用,可以在编译后使用 idf.py sizeidf.py size-components 命令对内存进行统计。

对于运行时内存占用,通过 esp_get_free_heap_size()esp_get_minimum_free_heap_size() 函数可以分别获得系统当前的剩余内存和自系统启动以来的最小剩余堆内存大小。具体可参考 ESP-IDF 堆内存说明

ESP32-C2 内存测试结果

下列数据是 ESP32-C2 在 tag v5.5-beta1 使用 ESP-IDF 示例在默认配置(即使用 idf.py set-target esp32c2 后的缺省配置),以及通过各种配置项优化内存后的剩余内存比较。

测试场景说明

默认配置内存占用对比

ESP32-C2 剩余内存对比

测试用例

默认配置 (KB)

优化配置 (KB)

station

95

169

WiFi station + 1TLS(MQTTS)

55

152

bleprph_wifi_coex

24

125

bleprph_wifi_coex + mbedtls + power_save

内存不足

115

备注

本文档仅针对 ESP32-C2 内存优化过程与效果进行说明。更多关于不同芯片、不同场景下的内存使用情况统计,请参考 内存使用情况对比

优化策略

本文的优化策略基于一个自己实现的测试用例 bleprph_wifi_coex + mbedtls + power_save demo,包含了 Wi-Fi + BLE + HTTPS + power save 自动休眠这些常用场景。具体 demo 和内存策略配置项可以参考 此链接。在未执行任何优化时,会因为执行时内存不足触发复位。

优化方案对比

不同优化方案下的剩余内存对比

优化方案

v5.4.1 (bytes)

v5.5 (bytes)

说明

无优化

内存不足

内存不足

使用 idf.py set-target esp32c2 后的缺省配置

基础优化

62840

60212

按照内存优化章节对配置项进行优化

进阶优化

91976

90576

在基础优化上继续优化,适用于 v5.4.x 及之前版本

v5.5 深度优化

118096

利用 v5.5 新特性进行深度优化

备注

其中:

  1. 无优化:是使用 idf.py set-target esp32c2 后的默认配置,仅保证功能可用(如使能蓝牙、PM 等基础配置),未对内存使用做任何专门优化。

  2. 基础优化:完全按照 ESP-IDF 内存优化章节 所述进行配置选项优化,涉及将各类函数从 IRAM 放置到 flash,减小 WiFi buffer 并优化 mbedtls 配置等。

  3. 进阶优化:基于基础优化,结合文档进一步深度优化,尤其适用于 v5.4.x 及之前版本。此时内存使用数据代表这些版本下”最终优化”结果。

  4. v5.5 深度优化:在进阶优化基础上继续优化,但只针对 v5.5 及之后 ESP-IDF 版本。该优化通过利用新特性(如 flash suspend)让更多代码可以放到 flash,从而释放 IRAM,最大程度减小内存占用。

基础优化

ESP-IDF 编程指南提供了一系列 内存优化 的方法,主要包括 IRAM 优化和堆内存优化,IRAM 优化主要将 FreeRTOS、Wi-Fi、heap 等组件的函数放到 flash;堆内存优化主要减少 Wi-Fi、lwip 等组件申请的静态内存数量。通过这些配置项优化(参考 sdkconfig.defaults.opt.1),可以在所有功能执行完之后将可用内存增加到 62 KB。

进阶优化

在基础优化基础上,继续优化,主要包含如下:

  1. 使能 CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY

    在使能 CONFIG_SPI_FLASH_AUTO_SUSPEND 后,通过使能 CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY 可以把蓝牙 controller 的代码将会全部放置在 flash,这将会再释放约 20 KB 的内存空间。

  2. 使能编译优化选项

    • CONFIG_COMPILER_OPTIMIZATION_SIZE

    • CONFIG_COMPILER_SAVE_RESTORE_LIBCALLS

    这几个编译优化可以继续释放约 8 KB 的内存空间。

在上述配置(基础优化+进阶优化)全部使能后(具体的配置项可以参考 sdkconfig.defaults.opt.2),可用内存增加到 90 KB。

v5.5 深度优化

在 ESP-IDF v5.5 版本,我们对 IRAM 占用进行了深度优化,可以将大部分 IRAM 函数放置在 flash,尤其是在使能 CONFIG_SPI_FLASH_AUTO_SUSPEND 这个配置时。有关所有此类配置的详细列表,请参见 sdkconfig.flash_auto_suspend_iram_reduction

全部使能这些优化项后(具体的配置项可以参考 sdkconfig.defaults.opt.3),相对于 v5.4.2 版本可以再节省 20 KB 内存。

其中 v5.5 连上 HTTPS 服务器后可用内存可以达到 118 KB,其中 IRAM 的 .text 段占用 10050 Bytes。

而基于 v5.4.2 版本测试,相同的优化配置,连上 HTTPS 服务器后可用内存为 95 KB,其中 IRAM 的 .text 段占用 31206 Bytes。

IRAM text 段对比

使用 idf.py size 命令获取到的 IRAM .text 段内存消耗数据如下:

IRAM .text 段内存占用对比

优化方案

v5.4.1 (bytes)

v5.5 (bytes)

无优化

93592

100616

基础优化

56384

60088

进阶优化

36130

35912

v5.5 深度优化

9742

其他芯片使用

虽然内存优化主要针对 ESP32-C2 芯片,但是其他新的芯片比如 ESP32-C61、ESP32-C5 同样可以优化,在默认配置项的基础上,可以增加约 80 KB 左右的内存。

特定场景内存优化

带有 PSRAM 的芯片

基础优化

针对带有 PSRAM 的 ESP 芯片,可根据实际应用和需求,调整以下配置项优化内存:

  • CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL:设置字节数阈值。当应用使用标准的 malloc() 申请内存时,如果申请大小大于等于这个阈值,系统将优先从 PSRAM 中分配,确保内部 RAM 不被大块内存占用。

备注

启用该配置后,malloc 大块内存可能分配到 PSRAM。对于必须使用片内内存的场景(如中断),应使用 heap_caps_malloc(MALLOC_CAP_INTERNAL) 等方式显式分配。

进阶优化

在 ESP-IDF v5.5.2 版本,针对带有 PSRAM 的 ESP 芯片,提供了一种将部分内部任务栈分配到 PSRAM 的内存优化方案。该方案可减少内部 RAM 的占用,在实际使用相关功能时,最多可节约约 20 K 内部内存。

可以通过以下步骤进行内存优化:

  1. 下载 补丁文件 move-system-task-on-psram.patch

  • 对于 v5.5.2 及之后版本,将该补丁文件放入 ESP-IDF 文件夹并执行以下指令:

git apply move-system-task-on-psram.patch

备注

该针对带有 PSRAM 芯片的内存优化方案适用于 IDF v4.4 和 v5.5 及以上,但此处提供的补丁文件仅适用于 v5.5.2 版本,若需作用于其他版本需要手动修改补丁文件。

另外需要注意,此补丁文件中关于 Bluetooth Controller 的修改仅针对 ESP32 进行了实现。由于其他搭载 PSRAM 的芯片(如 ESP32-S3 等)同样支持将相关任务分配至 PSRAM,因此如果需要在其他芯片上应用此优化,请参考补丁中对 components/bt/controller/esp32/bt.c 的修改方式,在对应芯片物理目录(如 components/bt/controller/esp32s3/bt.c)的 task_create_wrapper 函数中手动添加相同的修改。

修改示例如下:

static int32_t task_create_wrapper(void *task_func, const char *name, uint32_t stack_depth, void *param, uint32_t prio, void *task_handle, uint32_t core_id)
{
#if CONFIG_SPIRAM_REMOVE_BT_CONTROLLER_TASK
    return (uint32_t)xTaskCreatePinnedToCoreWithCaps(task_func, name, stack_depth, param, prio, task_handle, (core_id < CONFIG_FREERTOS_NUMBER_OF_CORES ? core_id : tskNO_AFFINITY), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
    return (uint32_t)xTaskCreatePinnedToCore(task_func, name, stack_depth, param, prio, task_handle, (core_id < CONFIG_FREERTOS_NUMBER_OF_CORES ? core_id : tskNO_AFFINITY));
#endif // CONFIG_SPIRAM_REMOVE_BT_CONTROLLER_TASK
}
  1. 进入 menuconfig 后依次选择 Component config FreeRTOS Extra,使能配置项。

  1. menuconfig 中依次选择 Component config ESP PSRAM Support for external, SPI-connected RAM SPI RAM config,使能如下配置项,将内部任务栈分配到 PSRAM。

重要

请注意,完成上述操作后,任务中不得进行 Flash 读写操作。例如,如果某个 event callback 会访问 Flash,该任务就不应分配到 PSRAM,否则在访问 Flash 时可能导致程序崩溃。

启用以上配置项后,相关内部任务栈将从 PSRAM 分配,而非内部内存。在程序中存在对应的功能或任务并实际分配到 PSRAM 时,最多可节约约 20 K 内部内存;若程序未使用这些功能或任务,则不会产生节约效果。