警告

This document is not updated for ESP32H4 yet, so some of the content may not be correct.

This warning was automatically inserted due to the source file being in the add_warnings_pages list.

# 片外 RAM

[English]

## 简介

ESP32-H4 提供了好几百 KB 的片上 RAM，可以满足大部分需求。但有些场景可能需要更多 RAM，因此 ESP32-H4 另外提供了高达 Value not updated 的虚拟地址，供片外 PSRAM（伪静态随机存储器）存储器使用。片外 RAM 已经集成到内存映射中，在某些范围内与片上 RAM 使用方式相同。

## 硬件

ESP32-H4 支持与 SPI flash 芯片并联的 PSRAM。虽然 ESP32-H4 支持多种类型的 RAM 芯片，但 ESP-IDF 当前仅支持乐鑫品牌的 PSRAM 芯片，如 ESP-PSRAM32、ESP-PSRAM64 等。

备注

备注

乐鑫同时提供模组和系统级封装芯片，集成了兼容的 PSRAM 和 flash，可直接用于终端产品 PCB 中。如需了解更多信息，请前往乐鑫官网。注意，ESP-IDF SDK 可能与定制的 PSRAM 芯片不兼容。

有关将 SoC 或模组管脚连接到片外 PSRAM 芯片的具体细节，请查阅 SoC 或模组技术规格书。

## 配置片外 RAM

备注

只有在构建中包含 `esp_psram` 组件时，`SPI RAM` 配置选项才可用。要在项目中包含 `SPI RAM` 功能，使用 `idf_component_register` 注册组件时，将 `esp_psram` 组件添加为 `REQUIRES` 或 `PRIV_REQUIRES` 的依赖项。

ESP-IDF 完全支持将片外 RAM 集成到你的应用程序中。在启动并完成片外 RAM 初始化后，可以将 ESP-IDF 配置为用多种方式处理片外 RAM：

- 集成片外 RAM 到 ESP32-H4 内存映射

- 添加片外 RAM 到堆内存分配器

- 调用 malloc() 分配片外 RAM （默认方式）

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

- 允许 .noinit 段放入片外存储器

- 在 PSRAM 中直接执行代码

### 集成片外 RAM 到 ESP32-H4 内存映射

在 CONFIG_SPIRAM_USE 中选择 `Integrate RAM into memory map` 选项，以集成片外 RAM 到 ESP32-H4 内存映射。

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

ESP-IDF 启动过程中，片外 RAM 被映射到数据虚拟地址空间，该地址空间是动态分配的，其长度为 PSRAM 大小和可用数据虚拟地址空间大小之间的最小值。

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

建议通过 ESP-IDF 堆内存分配器访问 PSRAM（见下一小节）。

### 添加片外 RAM 到堆内存分配器

在 CONFIG_SPIRAM_USE 中选择 `Add RAM to heap_caps allocator (malloc() stays internal by default)` 选项。

启用上述选项后，片外 RAM 被映射到数据虚拟地址空间，并将这个区域添加到携带 `MALLOC_CAP_SPIRAM` 标志的 堆内存分配器。由于该内存区域同时带有 `MALLOC_CAP_DEFAULT` 标志，因此 `heap_caps_malloc(size, MALLOC_CAP_DEFAULT)` 这类调用仍然可能返回 PSRAM 指针。

如果程序想显式地从片外存储器分配存储空间，则需要调用 `heap_caps_malloc(size, MALLOC_CAP_SPIRAM)`，之后可以调用 `free()` 函数释放这部分存储空间。

在该模式下，标准 `malloc()` 默认不会从外部 RAM 分配内存，因为它使用的是单独的默认分配策略。

### 调用 `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 在启动阶段（PSRAM 初始化完成后，于 `main_task` 中）预留一块内部 DMA 可用内存池。该内存从常规内部堆中分配出，并重新注册为独立的内存池。

预留池通过堆能力优先级机制管理：

- 中优先级 — `MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL`：显式使用这些标志的 `heap_caps_malloc()` 请求会优先从此池分配。

- 低优先级 — `MALLOC_CAP_DEFAULT`：仅当其他具有更高优先级 `MALLOC_CAP_DEFAULT` 的内部堆耗尽后，`malloc()` 才会回退使用此池。

因此，在正常情况下 `malloc()` 不会从该池分配；仅当其他内部内存耗尽后，`malloc()` 才会将其作为最后的回退来源以避免分配完全失败。

### 允许 .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 到堆内存分配器。

#### 在 PSRAM 中直接执行代码

启用 CONFIG_SPIRAM_XIP_FROM_PSRAM 选项后能在 PSRAM 中直接执行代码。通常放置在 flash 中的段，如 `.text` 部分的数据（用于指令）和 `.rodata` 部分的数据（用于只读数据），将被加载到 PSRAM 中。

启用此选项后，SPI1 flash 操作期间 cache 保持启用状态，因此需要执行的代码在此期间不必放置在内部 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 分配片上储存器。任务堆栈也可选择放入片外 RAM——详见下方 将任务堆栈放入片外存储器。

### 将任务堆栈放入片外存储器

有三种方式可将任务堆栈放入片外 RAM：

1. 单任务（显式） – 使用 `MALLOC_CAP_SPIRAM` 标志调用 `xTaskCreateWithCaps()`。需要启用 CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM。

2. 单任务（静态） – 使用 `xTaskCreateStatic()`，提供位于片外 RAM 的调用方自定义缓冲区。需要启用 CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM。

3. 全局默认（自动） – 启用 CONFIG_FREERTOS_PLACE_TASK_STACKS_IN_EXT_RAM。启用后，`xTaskCreate()` / `xTaskCreatePinnedToCore()` 的每次调用都会优先从 PSRAM 分配任务堆栈，若 PSRAM 耗尽则回退到片上 RAM。TCB 始终保留在片上 DRAM 中。

启用 CONFIG_FREERTOS_PLACE_TASK_STACKS_IN_EXT_RAM 后，还需注意以下额外限制：

- Flash 操作 – 任何会暂时禁用 CPU cache 的代码路径（flash 擦除/写入、NVS、OTA）必须在堆栈位于片上 RAM 的任务中运行，或通过 espressif/esp_flash_dispatcher 组件路由，该组件会在专用片上 RAM 任务中执行 flash 操作。

- 深度睡眠 – 从堆栈位于 PSRAM 的任务中调用 `esp_deep_sleep_start()` 会记录错误日志并继续执行，但在睡眠过程中禁用 cache 时极有可能发生崩溃。建议改用 `esp_deep_sleep_try_to_start()`：当从 PSRAM 堆栈任务中调用时，该函数会返回 `ESP_ERR_NOT_ALLOWED` 而不会崩溃。若应用程序需要深度睡眠，请从堆栈位于片上 RAM 的任务中发起调用。

- 浅睡眠 – 从堆栈位于 PSRAM 的任务中直接调用 `esp_light_sleep_start()` 同样会返回 `ESP_ERR_NOT_ALLOWED`。Tickless idle 自动浅睡眠是安全的，因为它由 idle 任务发起，而 idle 任务的堆栈始终位于片上 RAM；堆栈在 PSRAM 中的任务在唤醒后可正常恢复执行。

- pthread – `pthread_create()` 委托给 `xTaskCreate()`，因此启用此选项后，pthread 堆栈也会移至 PSRAM。

相关演示请参考 system/freertos/psram_stack 示例。

## 初始化失败

默认情况下，片外 RAM 初始化失败将终止 ESP-IDF 启动。如果想禁用此功能，可启用 CONFIG_SPIRAM_IGNORE_NOTFOUND 配置选项。

## 加密

可以为存储在外部 RAM 中的数据启用自动加密功能。启用该功能后，通过缓存读写的任何数据将被外部存储器加密硬件自动加密、解密。

只要启用了 flash 加密功能，就会启用这个功能。关于如何启用 flash 加密以及其工作原理，请参考 flash 加密。

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。