# SPI0/1 上 Flash 的并发约束

[中文]

SPI0/1 总线在缓存和 SPI1 外设（由包括此 SPI Flash 驱动在内的驱动程序控制）之间共享。对 SPI1 的操作可能会对缓存以及整个系统造成重大影响。连接到其他 SPI 总线的 flash 芯片没有此类约束和影响，不在本文档的讨论范围中。

SPI0/1 总线上可能发生三种活动：

- Flash 写入操作（通过 SPI1）。例如，擦除、页面编程或状态寄存器写入命令（例如，`SE`、`PP` 和 `WRSR`）。在这些命令期间，flash 处于不可读状态。CPU 和缓存必须等待直到写入命令完成。以下 API 可以触发写入命令：

  > - 调用非加密 SPI flash 写入 API（`esp_flash_write()`、`esp_flash_erase_region()` 等）

  > - 调用 `esp_flash_write_encrypted()`

- 短操作（通过 SPI1，包括非写入 flash 命令）。以下 API 可以触发短操作：

  > - 调用非加密 SPI flash 读取 API（`esp_flash_read()` 等）

- 缓存读取（通过 SPI0）。以下 API 和操作可以触发缓存读取：

  > - 从 SPI Flash 或 PSRAM 执行代码

  > - 从 SPI Flash 或 PSRAM 获取 .data/.rodata/.bss 段的静态数据

  > - 通过堆或 esp_himem 对 PSRAM 的所有其他读/写操作

  > - 从映射到 SPI Flash 的区域读取，包括：

  >   > - 类似 mmap 的函数：`spi_flash_mmap()`、`spi_flash_mmap_pages()`、`esp_mmu_map()`、`bootloader_mmap()` 和 `esp_partition_mmap()`。

  >   > - 依赖 `spi_flash_mmap()` 的函数：`esp_partition_find()`、`esp_partition_register_external()`。

  >   > - 加密 flash 读/写 API：`esp_flash_read_encrypted()` 和 `esp_flash_write_encrypted()` （在 esp32 上，或用于数据验证）。

所有 SPI flash API 通过驱动程序提供的某些内部互斥锁实现互斥访问。

对于所有 SPI1 操作（读/写），默认情况下在这些操作期间缓存会被禁用，因此无法访问 Flash/PSRAM，大多数任务将被禁用。有关更多详细信息，请参阅 缓存禁用（默认）。

有一些选项可以帮助减轻缓存禁用的影响。写入操作的影响在不同模式下是不同的。

- XIP from PSRAM：在此模式下，所有过去从 Flash 执行的段都改为从 PSRAM 加载和执行。因此，缓存能在 flash 擦除/写入期间保持启用状态，代码执行在大多数情况下不会受到写入操作的影响。有关更多详细信息，请参阅 从 PSRAM 执行代码功能。

有关软件实现的详细信息，请参阅 OS 函数 和 SPI 总线锁。

## 缓存禁用（默认）

默认情况下，在 SPI1 操作期间缓存会被禁用。所有 SPI1 操作将自动透明地禁用缓存。

当禁用缓存时，所有非 IRAM 安全的中断将被禁用，所有其他任务将被暂停。只有 IRAM 安全的中断处理程序将被执行。这些将在 Flash 操作完成时恢复。

有关如何在禁用缓存时防止中断处理程序被禁用的信息，请参阅 IRAM 安全中断处理程序。

当禁用缓存时，所有 CPU 应该只从内部 RAM 执行代码和访问数据。有关内部 RAM（例如，IRAM、DRAM）和 flash 缓存之间的差异，请参阅 应用程序内存布局 文档。

### IRAM 安全中断处理程序

如果需要在 flash 操作期间运行中断处理程序（比如低延迟操作），请在 注册中断处理程序 时设置 `ESP_INTR_FLAG_IRAM`。

请确保中断处理程序访问的所有数据和函数（包括其调用的数据和函数）都存储在 IRAM 或 DRAM 中。参见 如何将代码放入 IRAM。

在未将函数或符号正确放入 IRAM/DRAM 的情况下，在 flash 操作期间，中断处理程序从 flash cache 中读取数据时，会导致程序崩溃。这可能是因为代码未正确放入 IRAM，产生了非法指令异常，也可能是因为常数未正确放入 DRAM，读取到了垃圾数据。

备注

在 ISRs 中处理字符串时，不建议使用 `printf` 和其他输出函数。为了方便调试，在从 ISRs 中获取数据时，请使用 `ESP_DRAM_LOGE()` 和类似的宏。请确保 `TAG` 和格式字符串都放置于 `DRAM` 中。

### 非 IRAM 安全中断处理程序

如果在注册时没有设置 `ESP_INTR_FLAG_IRAM` 标志，当禁用 cache 时，将不会执行中断处理程序。一旦 cache 恢复，非 IRAM 安全的中断将重新启用，中断处理程序随即再次正常运行。这意味着，只要禁用了 cache，就不会发生相应的硬件事件。

## 从 PSRAM 执行代码功能

选择 CONFIG_SPIRAM_XIP_FROM_PSRAM 配置以启用此模式。在此模式下，代码从 PSRAM 执行，在大多数情况下，缓存不会在写入 API 期间被禁用。

在此模式下，flash `.text` 段（用于指令）和 flash `.rodata` 段（用于只读数据）将在启动时加载到 PSRAM。相应的虚拟地址将映射到 PSRAM。您无需确保在 flash 被擦除/编程时执行的代码/数据位于 IRAM 中。

### 例外：当缓存映射 flash 中的区域时

由于 SPI Nor Flash 器件的限制，在 flash 被擦除/写入时，仍然不允许访问 Flash 中的缓存映射区域（通过 spi_flash_mmap 等 API 映射），无论擦写区域和映射区域是否重合。在这种情况下，在擦写 Flash 时仍应禁用缓存以防止从缓存读取错误的数据。

为了防止缓存禁用，在 SPI Flash 驱动程序中实现了一个锁，以确保缓存映射的存在和 Flash 写入是互斥的，大多数进行了 Flash 映射的 ESP-IDF API 都使用了该标志。如果您自己调用类似 mmap 的 API，可以指定此标志 `SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` 以防止缓存禁用。您不能在 `spi_flash_mmap` 和 `spi_flash_munmap` 之间使用 `esp_flash_erase_*` 或者 `esp_flash_write` 的任务中使用此标志（无论写入区域和映射区域是否重叠），否则会造成死锁。有关该标志的详细信息，请参阅 关于 SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE 标志。

如果在没有此标志的情况下调用类似 mmap 的 API，当 Flash 发生擦除或者写入时，缓存仍将被禁用。

此文档对您有帮助吗？

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

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