片外 RAM
简介
ESP32 提供了好几百 KB 的片上 RAM,可以满足大部分需求。但有些场景可能需要更多 RAM,因此 ESP32 另外提供了高达 4 MB 的虚拟地址,供片外 PSRAM(伪静态随机存储器)存储器使用。片外 RAM 已经集成到内存映射中,在某些范围内与片上 RAM 使用方式相同。
硬件
ESP32 支持与 SPI flash 芯片并联的 PSRAM。虽然 ESP32 支持多种类型的 RAM 芯片,但 ESP-IDF 当前仅支持乐鑫品牌的 PSRAM 芯片,如 ESP-PSRAM32、ESP-PSRAM64 等。
备注
PSRAM 芯片的工作电压分为 1.8 V 和 3.3 V。其工作电压必须与 flash 的工作电压匹配。请查询相应 PSRAM 芯片以及 ESP32 的技术规格书获取准确的工作电压。对于 1.8 V 的 PSRAM 芯片,请确保在启动时将 MTDI 管脚设置为高电平,或者将 ESP32 中的 eFuses 设置为始终使用 1.8 V 的 VDD_SIO 电平,否则有可能会损坏 PSRAM 和/或 flash 芯片。
备注
乐鑫同时提供模组和系统级封装芯片,集成了兼容的 PSRAM 和 flash,可直接用于终端产品 PCB 中。如需了解更多信息,请前往乐鑫官网。注意,ESP-IDF SDK 可能与定制的 PSRAM 芯片不兼容。
有关将 SoC 或模组管脚连接到片外 PSRAM 芯片的具体细节,请查阅 SoC 或模组技术规格书。
配置片外 RAM
ESP-IDF 完全支持将片外 RAM 集成到你的应用程序中。在启动并完成片外 RAM 初始化后,可以将 ESP-IDF 配置为用多种方式处理片外 RAM:
调用 malloc() 分配片外 RAM (default)
集成片外 RAM 到 ESP32 内存映射
在 CONFIG_SPIRAM_USE 中选择 Integrate RAM into memory map
选项,以集成片外 RAM 到 ESP32 内存映射。
这是集成片外 RAM 最基础的设置选项,大多数用户需要用到其他更高级的选项。
ESP-IDF 启动过程中,片外 RAM 被映射到数据虚拟地址空间,该地址空间是动态分配的,其长度为 PSRAM 大小和可用数据虚拟地址空间大小之间的最小值。
应用程序可以创建指向该区域的指针,手动将数据放入片外存储器,并全权负责管理片外 RAM,包括协调缓存占用、防止发生损坏等。
建议通过 ESP-IDF 堆内存分配器访问 PSRAM(见下一小节)。
添加片外 RAM 到堆内存分配器
在 CONFIG_SPIRAM_USE 中选择 Make RAM allocatable using heap_caps_malloc(..., MALLOC_CAP_SPIRAM)
选项。
启用上述选项后,片外 RAM 被映射到数据虚拟地址空间,并将这个区域添加到携带 MALLOC_CAP_SPIRAM
标志的 堆内存分配器。
程序如果想从片外存储器分配存储空间,则需要调用 heap_caps_malloc(size, MALLOC_CAP_SPIRAM)
,之后可以调用 free()
函数释放这部分存储空间。
调用 malloc()
分配片外 RAM
在 CONFIG_SPIRAM_USE 中选择 Make RAM allocatable using malloc() as well
选项,该选项为默认选项。
启用此选项后,片外存储器将被添加到内存分配程序(与上一选项相同),同时也将被添加到由标准 malloc()
函数返回的 RAM 中。
应用程序因此可以使用片外 RAM,无需重写代码就能使用 heap_caps_malloc(..., MALLOC_CAP_SPIRAM)
。
如果某次内存分配偏向于片外存储器,也可以使用 CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL 设置分配空间的大小阈值,控制分配结果:
如果分配的空间小于或等于阈值,分配程序将首先选择内部存储器。
如果分配的空间大于阈值,分配程序将首先选择外部存储器。
如果优先考虑的内部或外部存储器中没有可用的存储块,分配程序则会选择其他类型存储。
由于有些内存缓冲器仅可在内部存储器中分配,因此需要使用第二个配置项 CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL 定义一个内部内存池,仅限显式的内部存储器分配使用(例如用于 DMA 的存储器)。常规 malloc()
将不会从该池中分配,但可以使用 MALLOC_CAP_DMA 和 MALLOC_CAP_INTERNAL
标志从该池中分配存储器。
允许 .bss 段放入片外存储器
通过勾选 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY 启用该选项。
启用该选项后,PSRAM 被映射到的数据虚拟地址空间将用于存储来自 lwip、net80211、libpp、wpa_supplicant 和 bluedroid ESP-IDF 库中零初始化的数据(BSS 段)。
通过将宏 EXT_RAM_BSS_ATTR
应用于任何静态声明(未初始化为非零值),可以将附加数据从内部 BSS 段移到片外 RAM。
也可以使用链接器片段方案 extram_bss
将组件或库的 BSS 段放到片外 RAM 中。
启用此选项可以减少 BSS 段占用的内部静态存储。
剩余的片外 RAM 也可以通过上述方法添加到堆分配器中。
允许 .noinit 段放入片外存储器
通过勾选 CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY 启用该选项。启用该选项后,PSRAM 被映射到的数据虚拟地址空间将用于存储未初始化的数据。即使在启动或重新启动期间,放置在该段中的值也不会被初始化或修改。
通过应用 EXT_RAM_NOINIT_ATTR
宏,可以将数据从内部 NOINIT 段移到片外 RAM。剩余的片外 RAM 也可以通过上述方法添加到堆分配器中,具体请参考 添加片外 RAM 到堆内存分配器。
片外 RAM 使用限制
使用片外 RAM 有下面一些限制:
flash cache 禁用时(比如,正在写入 flash),片外 RAM 将无法访问;同样,对片外 RAM 的读写操作也将导致 cache 访问异常。因此,ESP-IDF 不会在片外 RAM 中分配任务堆栈(详见下文)。
片外 RAM 与片外 flash 使用相同的 cache 区域,这意味着频繁在片外 RAM 访问的变量可以像在片上 RAM 中一样快速读取和修改。但访问大块数据时(大于 32 KB),cache 空间可能会不足,访问速度将降低到片外 RAM 的访问速度。此外,访问大块数据会挤出 flash cache,可能在之后降低代码的执行速度。
一般来说,片外 RAM 不会用作任务堆栈存储器。
xTaskCreate()
及类似函数始终会为堆栈和任务 TCB 分配片上储存器。
可以使用 CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM 选项将任务堆栈放入片外存储器。这时,必须使用 xTaskCreateStatic()
指定从片外存储器分配的任务堆栈缓冲区,否则任务堆栈将仍从片上存储器分配。
初始化失败
默认情况下,片外 RAM 初始化失败将终止 ESP-IDF 启动。如果想禁用此功能,可启用 CONFIG_SPIRAM_IGNORE_NOTFOUND 配置选项。
如果启用 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY,忽略失败的选项将无法使用,这是因为在链接时,链接器已经向片外存储器分配标志符。
PSRAM 中的栈:对于不以任何直接或间接方式调用 ROM 中代码的任务,选项 CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM 会取消
xTaskCreateStatic()
中的检查,允许任务栈存放在外部 RAM 中。但 不建议 使用此选项。在以 80 MHz 时钟速度运行时,外部 RAM 还必须占用 HSPI 或 VSPI 总线。设置 CONFIG_SPIRAM_OCCUPY_SPI_HOST 选项可以选择使用的 SPI 主机。
芯片版本
某些 ESP32 版本存在与外部 RAM 使用相关的问题,这些问题记录在 ESP32 系列芯⽚勘误表 文档中。ESP-IDF 会以下列特定方式处理上述错误:
ESP32 rev v0.0
ESP-IDF 并未针对该版芯片的错误提供解决方案,也不能将外部 PSRAM 映射到 ESP32 的主存储器映射中。
ESP32 rev v1.0
某些机器指令序列对外部内存进行操作时,该版芯片的错误会引发问题,详情请参阅 ESP32 系列芯⽚勘误表 第 3.2 节。为此,ESP32 GCC 编译器增加了标志 -mfix-esp32-psram-cache-issue
,用于过滤这些序列,只输出可以安全执行的代码。请启用 CONFIG_SPIRAM_CACHE_WORKAROUND 选项以使用此方法。
启用此选项后,ESP-IDF 会链接到重新编译且带有额外标志的 Newlib,此外还会执行以下操作:
避免使用某些 ROM 函数
为 Wi-Fi 栈分配静态内存
ESP32 rev v3.0
ESP32 rev v3.0 修复了 rev v1.0 中发现的 PSRAM 缓存问题。当 CONFIG_ESP32_REV_MIN 选项设置为 rev v3.0
时,会禁用与 PSRAM 相关的编译器解决方案。有关 ESP32 v3.0 的更多详情,请参阅 ESP32 芯⽚版本 v3.0 使⽤指南。