片外 RAM

[English]

简介

ESP32 提供了 520 KB 的片上 SRAM,可以满足大部分需求。但有些场景可能需要更多 RAM,因此 ESP32 另外提供了高达 4 MB 的片外 SPI RAM 存储器以供用户使用。片外 RAM 被添加到内存映射中,在某些范围内与片上 RAM 使用方式相同。

硬件

ESP32 支持与 SPI Flash 芯片并联的 SPI PSRAM。ESP32 支持多种类型的 RAM 芯片,但 ESP32 SDK 当前仅支持 ESP-PSRAM32 芯片。

ESP-PSRAM32 芯片的工作电压为 1.8 V,只能与 1.8 V flash 并联使用。请确保在启动时将 MTDI 管脚设置为高电平,或者将 ESP32 中的熔丝设置为始终使用 1.8 V 的 VDD_SIO 电平,否则有可能会损坏 PSRAM 和/或 flash 芯片。

要将 ESP-PSRAM 芯片连接到 ESP32D0W*,请连接以下信号:
  • PSRAM /CE (pin 1) > ESP32 GPIO 16
  • PSRAM SO (pin 2) > flash DO
  • PSRAM SIO[2] (pin 3) > flash WP
  • PSRAM SI (pin 5) > flash DI
  • PSRAM SCLK (pin 6) > ESP32 GPIO 17
  • PSRAM SIO[3] (pin 7) > flash HOLD
  • PSRAM Vcc (pin 8) > ESP32 VCC_SDIO

ESP32D2W* 芯片的连接方式有待确定。

注解

乐鑫同时提供 ESP32-WROVER 模组,内部搭载 ESP32 芯片,集成 1.8 V flash 和 ESP-PSRAM32,可直接用于终端产品 PCB 中。

配置片外 RAM

ESP-IDF 完全支持将外部存储器集成到您的应用程序中。您可以将 ESP-IDF 配置成启动并完成初始化后以多种方式处理片外 RAM:

集成片外 RAM 到 ESP32 内存映射

CONFIG_SPIRAM_USE 中选择 “Integrate RAM into ESP32 memory map(集成片外 RAM 到 ESP32 内存映射)” 选项。

这是集成片外 RAM 最基础的设置选项,大多数用户需要用到其他更高级的选项。

ESP-IDF 启动过程中,片外 RAM 被映射到以 0x3F800000 起始的数据地址空间(字节可寻址),空间大小正好为 RAM 的大小 (4 MB)。

应用程序可以通过创建指向该区域的指针手动将数据放入片外存储器,同时应用程序全权负责管理片外 RAM,包括协调 Buffer 的使用、防止发生损坏等。

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

CONFIG_SPIRAM_USE 中选择 “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

CONFIG_SPIRAM_USE 中选择 “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 也可以通过上述方法添加到内存分配程序中。

片外 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 主机。

芯片版本

有些 ESP32 芯片版本存在某些已知问题,可能会影响片外 RAM 的使用。请参考 ESP32 勘误表,查看详细信息。为了解决这些问题,ESP-IDF 采取了以下措施:

ESP32 rev v0

ESP-IDF 尚未提供针对此版本硅片 bug 的解决方法,因此在 ESP32 rev v0 中,ESP-IDF 无法将片外 PSRAM 映射到 ESP32 主内存映射中。

ESP32 rev v1

当某些机器指令序列在片外存储器位置上运行时,此芯片版本中的错误可能会引发芯片故障(详情见 ESP32 勘误表 第 3.2 章节)。 为了解决这个问题,用于编译 ESP-IDF 项目的 GCC 编译器扩展了一个旗标:-mfix-esp32-psram-cache-issue。在命令行中将此旗标传递给 GCC,编译器对这些序列进行处理,然后仅输出可以安全执行的代码。如需启用此旗标,请选择 CONFIG_SPIRAM_CACHE_WORKAROUND

ESP-IDF 还采取了其他措施确保不同时使用 PSRAM 访问和出错指令集:

  • 链接到使用 GCC 旗标重新编译的 Newlib 版本;
  • 避免使用某些 ROM 函数;
  • 为 Wi-Fi 栈分配静态内存。