本文将介绍ESP32 RAM以及IRAM和DRAM的区别,同时提供IRAM和DRAM内存不足的解决方案。

1 ESP32 存储

  • 地址空间

    • 对称地址映射
    • 数据总线与指令总线分别有 4 GB (32-bit) 地址空间
    • 1296 KB 片上存储器地址空间
    • 19704 KB 片外存储器地址空间
    • 512 KB 外设地址空间
    • 部分片上存储器与片外存储器既能被数据总线也能被指令总线访问
    • 328 KB DMA 地址空间
  • 片上存储器

    • 448 KB Internal ROM
    • 520 KB Internal SRAM
    • 8 KB RTC FAST Memory
    • 8 KB RTC SLOW Memory
  • 片外存储器 片外 SPI 存储器可作为片外存储器被映射到可用的地址空间。部分片上存储器可用作片外存储器的 Cache。

    • 最大支持 16 MB 片外 SPI Flash
    • 最大支持 8 MB 片外 SPI SRAM

片上存储器分为 Internal ROMInternal SRAMRTC FAST MemoryRTC SLOW Memory 四个部分,其容量分 别为 448 KB、520 KB、8 KB、8 KB。

  • 448 KB Internal ROM 分为 384 KB Internal ROM 064 KB Internal ROM 1 两部分;
  • 520 KB Internal SRAM 分为 192 KB Internal SRAM 0128 KB Internal SRAM 1200 KB Internal SRAM 2 三部分。

Internal SRAM 0 的容量为 192 KB,通过配置,硬件的头 64 KB 可以作为 Cache 来缓存片外存储器。不作为Cache 使用时,头 64 KB 可以被两个 CPU 通过指令总线0x4007_0000 ~ 0x4007_FFFF 读写,其余 128 KB 可 以被两个 CPU 通过指令总线0x4008_0000 ~ 0x4009_FFFF 读写。

2 ESP32 RAM

ESP32包含多种类型的RAM:

  • DRAM:是用于保存数据的存储器。这是作为堆访问的最常见的内存类型;
  • IRAM:通常只保存可执行数据。如果作为通用内存访问,所有访问必须是32 位对齐的;
  • D/IRAM:是可用作指令或数据 RAM 的 RAM。

IRAM:

根据手册描述:内部IRAM可用192K,当然这是在不启动外部RAM的情况下。如果启动了外部RAM,IRAM头64K作为Cache,则可用的IRAM大小为128K。

DRAM:

IRAM占用了192K,内部RAM有520K,因此DRAM有328K。

3 解决DRAM内存不够

有时候我们使用了太多ESP32组件,比如bt、wifi同时使用,还使用了音频ADF框架,那么DRAM的空间可能不太够用。由于ESP32默认是不开启使用外部PSRAM,ESP32支持4M的片外SPI RAM,因此可以足够然我运行占用内存空间大的程序,只不过不能被DMA使用,但还是满足日常需求。 DRAM内存不够仅仅发生在运行期间,是不会造成编译不通过的现象

3.1 使能外部RAM

  • ESP32-specific=>Support for external, SPI-connected RAM勾选

3.2 配置外部RAM

  • 集成片外 RAM 到 ESP32 内存映射

SPI RAM config=>SPI RAM access method 中选择 Integrate RAM into ESP32 memory map(集成片外 RAM 到 ESP32 内存映射)。 这是集成片外 RAM 最基础的设置选项,大多数用户需要用到其他更高级的选项。 ESP-IDF 启动过程中,片外 RAM 被映射到以 0x3F800000 起始的数据地址空间(字节可寻址),空间大小正好为 RAM 的大小 (4 MB)。 应用程序可以通过创建指向该区域的指针手动将数据放入片外存储器,同时应用程序全权负责管理片外 RAM,包括协调 Buffer 的使用、防止发生损坏等。

  • 添加片外 RAM 到内存分配程序

SPI RAM config=>SPI RAM access method 中选择 Make RAM allocatable using heap_caps_malloc(…, MALLOC_CAP_SPIRAM)选项。 启用上述选项后,片外 RAM 被映射到地址 0x3F800000,并将这个区域添加到 内存分配程序 里携带 MALLOC_CAP_SPIRAM 的标志。 程序如果想从片外存储器分配存储空间,则需要调用 heap_caps_malloc(size, MALLOC_CAP_SPIRAM),之后可以调用 free() 函数释放这部分存储空间。

  • 调用 malloc() 分配片外 RAM (默认)

SPI RAM config=>SPI RAM access method 中选择 Make RAM allocatable using malloc() as well选项。 启用此选项后,片外存储器将被添加到内存分配程序(与上一选项相同),同时也将被添加到由标准 malloc() 返回的 RAM 中。

这允许应用程序使用片外 RAM 而无需重写代码以使用 heap_caps_malloc(..., MALLOC_CAP_SPIRAM)

如果某次内存分配偏向于片外存储器,您也可以使用 CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL` 设置分配空间的大小阈值,控制分配结果:

  • 如果分配的空间小于阈值,分配程序将首先选择内部存储器。

  • 如果分配的空间等于或大于阈值,分配程序将首先选择外部存储器。

如果优先考虑的内部或外部存储器中没有可用的存储块,分配程序则会选择其他类型存储。

由于有些 Buffer 仅可在内部存储器中分配,因此需要使用第二个配置项 CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL 定义一个内部存储池,仅限显式的内部存储器分配使用(例如用于 DMA 的存储器)。常规 malloc() 将不会从该池中分配,但可以使用 MALLOC_CAP_DMAMALLOC_CAP_INTERNAL 表示从该池中分配存储器。

  • 允许 .bss 段放入片外存储器

设置 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY 启用该选项,此选项配置与上面三个选项互不影响。

启用该选项后,从 0x3F800000 起始的地址空间将用于存储来自 lwip、net80211、libpp 和 bluedroid ESP-IDF 库中零初始化的数据(BSS 段)。

EXT_RAM_ATTR 宏应用于任何静态声明(未初始化为非零值)之后,可以将附加数据从内部 BSS 段移到片外 RAM。

启用此选项可以减少 BSS 段占用的内部静态存储。

剩余的片外 RAM 也可以通过上述方法添加到内存分配程序中。

3.3 外部RAM使用限制

使用片外 RAM 有下面一些限制:

  • Flash cache 禁用时(比如,正在写入 flash),片外 RAM 将无法访问;同样,对片外 RAM 的读写操作也将导致 cache 访问异常。出于这个原因,ESP-IDF 不会在片外 RAM 中分配任务堆栈(详见下文)。

  • 片外 RAM 不能用于储存 DMA 描述符,也不能用作 DMA 读写操作的缓冲区 (Buffer)。与 DMA 搭配使用的 Buffer 必须先使用 heap_caps_malloc(size, MALLOC_CAP_DMA) 进行分配,之后可以调用标准 free() 回调释放 Buffer。

  • 片外 RAM 与片外 flash 使用相同的 cache 区域,即频繁在片外 RAM 访问的变量可以像在片上 RAM 中一样快速读取和修改。但访问大块数据时(大于 32 KB),cache 空间可能会不足,访问速度将回落到片外 RAM 访问速度。此外,访问大块数据可以“挤出” flash cache,可能会降低代码执行速度。

  • 片外 RAM 不可用作任务堆栈存储器。因此 xTaskCreate() 及类似函数将始终为堆栈和任务 TCB 分配片上储存器,而 xTaskCreateStatic() 类型的函数将检查传递的 Buffer 是否属于片上存储器。但对于不以任何方式直接或间接调用 ROM 中代码的任务,menuconfig 选项 CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY 将消除 xTaskCreateStatic 中的检查,从而允许任务堆栈存储在外部 RAM 中。但是,不建议使用此方法。

  • 默认情况下,片外 RAM 初始化失败将终止 ESP-IDF 启动。如果想禁用此功能,可启用 CONFIG_SPIRAM_IGNORE_NOTFOUND 配置选项。如果启用 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORYCONFIG_SPIRAM_IGNORE_NOTFOUND 选项将不能使用,这是因为在链接时,链接器已经向片外 RAM 分配符号。

  • 时钟频率为 80 MHz 时,片外 RAM 须占用 HSPI 总线或 VSPI 总线。请使用 CONFIG_SPIRAM_OCCUPY_SPI_HOST 选择要用的 SPI 主机。

3.4 修改分区表

由于使用了外部RAM会使partitionstable.bin增加,0x8000的偏移地址会出现覆盖,所以需要修改分区表和partitionstable.bin的偏移地址: idf.py menuconfig->Partition Table修改成0x10000

4 解决IRAM内存不够问题

当IRAM内存不够时,会导致编译不通过,而且不报iram0_0_seg' overflowed错误。

4.1 降低代码运行优化

使用idf.py size-components可以查看内存分布和存储情况。 通常情况下,我们尽量不使用IRAM内存,虽然IRAM会比外部Flash性能高,但是容易造成IRAM空间不足。

由于ESP32默认情况下是开启了WIFI、LWIP速度优化的以及libc库放在了内部IRAM等,具体可通过idf.py size-components查看,我们可以在menuconfig中失能这些选项,只能关闭部分,但足以让我们的工程满足日常需求。 根据实际需求可在以下图中红色区域自行裁剪:

4.2 通过编译选项优化

menuconfig -> Compiler option -> Optimization Level -> Optimize for size (-Os)

参考资料

  1. ESP32 Memory Types
  2. ESP32 external spo-connected RAM
  3. ESP32 系统和存储器
  4. ESP32 优化 IRAM 内存方法整理
  5. How to fit my program in IRAM? Section .iram0.text will not fit in region iram0__0seg. (IDFGH-408) #2566