# 异步内存复制

[English]

异步内存复制驱动使用 DMA 搬运数据，让 CPU 可以同时处理其他工作。它适合较大的 buffer，例如 CPU 在等待 `memcpy` 时，本可以准备下一帧或处理上一块数据的场景。

本文先完成一次阻塞复制，再介绍如何在复制期间继续执行任务。

## 开始前

该驱动仅在支持异步内存复制的目标芯片上可用。在包含 `esp_async_memcpy.h` 前，请为项目组件添加 `esp_driver_dma` 依赖。

DMA 必须能访问源和目标 buffer。目标 buffer 应分配在 DMA 可访问的 RAM 中。特定 DMA 后端是否支持 PSRAM，取决于目标芯片和所选后端。

重要

在复制完成前，不要读取或修改目标 buffer；也不要修改源 buffer。

## 快速开始

典型流程如下：

            flowchart TD
        install["安装驱动"] --> choose{"任务如何等待？"}
        choose --> blocking["阻塞复制<br/>esp_memcpy_blocking"]
        choose --> async["异步复制<br/>esp_async_memcpy"]
        async --> callback["回调通知任务"]
        blocking --> use["使用目标 buffer"]
        callback --> use
        use --> more{"还要继续复制？"}
        more -->|是| choose
        more -->|否| uninstall["卸载驱动"]

        classDef blocking fill:#E8F1FB,stroke:#3B82C4,color:#1B4F72
        classDef async fill:#F3E8FF,stroke:#8B5CF6,color:#5B2C8A
        classDef result fill:#E8F5E9,stroke:#43A047,color:#1B5E20
        classDef cleanup fill:#F5F5F5,stroke:#757575,color:#424242
        class blocking blocking
        class async,callback async
        class use,result result
        class uninstall cleanup

## 场景 1：复制一个 Buffer 并等待

如果下一步操作必须立刻使用复制结果，先使用 `esp_memcpy_blocking()`。它会对合适的 buffer 使用 DMA，并等待复制完成；对于较小的 buffer，它会安全地回退到 CPU 复制。

    #include "esp_async_memcpy.h"

    async_memcpy_handle_t memcpy_hdl = NULL;
    async_memcpy_config_t config = {
        .backlog = 1,
        .weight = 0,
        .dma_burst_size = 16,
    };

    // 显式选择 AHB GDMA 后端。
    ESP_ERROR_CHECK(esp_async_memcpy_install_gdma_ahb(&config, &memcpy_hdl));

    // src 和 dst 是 DMA 可访问的 buffer。函数返回时，dst 已经准备就绪。
    ESP_ERROR_CHECK(esp_memcpy_blocking(memcpy_hdl, dst, src, copy_size, -1));

    // 现在可以安全使用 dst。
    process_data(dst, copy_size);

    ESP_ERROR_CHECK(esp_async_memcpy_uninstall(memcpy_hdl));

`timeout_ms` 必须为 `-1`，表示一直等待到复制完成。阻塞 API 必须在任务上下文中调用，不能在 ISR 中调用。

### 安装驱动

安装驱动时请显式选择 DMA 后端。前面示例使用的 AHB GDMA 后端仅适用于支持 AHB GDMA 的目标芯片。请选择目标芯片支持且符合应用需求的 DMA 引擎：

- `esp_async_memcpy_install_gdma_ahb()`

对于一次阻塞复制，将 `async_memcpy_config_t::backlog` 设为 1 即可；若可能同时等待多个复制请求，应增大该值。`async_memcpy_config_t::dma_burst_size` 设置 DMA 突发大小，单位为字节；可从 16 开始，仅在性能测试后再调整。除非目标芯片支持加权仲裁且应用需要调节平均总线带宽，否则将 `async_memcpy_config_t::weight` 设为 0。

## 场景 2：在 DMA 复制期间继续工作

如果任务在 DMA 搬运期间还有其他工作，使用 `esp_async_memcpy()`。该函数将请求加入队列后立即返回，不会等待复制完成。随后由回调通知拥有目标 buffer 的任务。

    #include "freertos/FreeRTOS.h"
    #include "freertos/semphr.h"
    #include "esp_async_memcpy.h"

    static bool copy_done_cb(async_memcpy_handle_t memcpy_hdl,
                             async_memcpy_event_t *event,
                             void *user_ctx)
    {
        BaseType_t high_task_woken = pdFALSE;
        SemaphoreHandle_t done = (SemaphoreHandle_t)user_ctx;

        xSemaphoreGiveFromISR(done, &high_task_woken);
        return high_task_woken == pdTRUE;
    }

    SemaphoreHandle_t done = xSemaphoreCreateBinary();

    ESP_ERROR_CHECK(esp_async_memcpy(memcpy_hdl, dst, src, copy_size,
                                     copy_done_cb, done));

    // DMA 正在复制。在此执行不会访问 src 或 dst 的其他工作。
    prepare_next_operation();

    xSemaphoreTake(done, portMAX_DELAY);
    // 回调已运行，dst 已准备就绪。
    process_data(dst, copy_size);

驱动是线程安全的，多个任务可以通过同一个句柄提交请求。请求会按提交顺序处理。请将 `backlog` 设为应用可能同时待处理的最大复制请求数。

警告

回调运行在 ISR 上下文。应保持简短，并且只能调用 ISR-safe 函数，例如 `xSemaphoreGiveFromISR` 或 `xQueueSendFromISR`。不要在回调中调用阻塞 API、执行耗时处理或提交新的复制请求。

## Buffer 大小与对齐

该驱动支持未对齐的源和目标地址。它使用 CPU 复制未对齐的边缘字节，并使用 DMA 复制按缓存行对齐的主体，因此普通应用无需手动对齐 buffer。

对于 `esp_async_memcpy()`，若目标 buffer 位于缓存区，其长度至少应为两个缓存行。较小的请求会返回 `ESP_ERR_INVALID_SIZE`，应改用标准 `memcpy`。`esp_memcpy_blocking()` 在这种情况下会自动使用 CPU 复制。

备注

DMA 并不会自动让每次复制更快。短复制的 DMA 配置开销通常高于收益。请使用接近实际场景的 buffer 大小进行测量，再决定是否将性能关键路径迁移到 DMA。

## 结束与释放驱动

在需要时持续保持驱动已安装。调用 `esp_async_memcpy_uninstall()` 前，请等待所有排队的复制完成，并确保没有任务会再提交请求。成功卸载后，句柄及其资源不再有效。

## ETM 事件

复制完成时可以产生供 ETM 模块使用的事件。调用 `esp_async_memcpy_new_etm_event()` 并传入 `ASYNC_MEMCPY_ETM_EVENT_COPY_DONE`，即可获取事件句柄。如何将事件连接到 ETM 任务，请参阅 ETM 文档。

## API 参考

### 异步内存复制驱动程序函数

#### Header File

- components/esp_driver_dma/include/esp_async_memcpy.h

- This header file can be included with:

  >     #include "esp_async_memcpy.h"

- This header file is a part of the API provided by the `esp_driver_dma` component. To declare that your component depends on `esp_driver_dma`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_dma

  > or

  >     PRIV_REQUIRES esp_driver_dma

#### Functions

esp_err_t esp_async_memcpy_install_gdma_ahb(const async_memcpy_config_t *config, async_memcpy_handle_t *mcp)

  Install async memcpy driver, with AHB-GDMA as the backend.

  参数:

    - config -- [in] Configuration of async memcpy

    - mcp -- [out] Returned driver handle

  返回:

    - ESP_OK: Install async memcpy driver successfully

    - ESP_ERR_INVALID_ARG: Install async memcpy driver failed because of invalid argument

    - ESP_ERR_NO_MEM: Install async memcpy driver failed because out of memory

    - ESP_FAIL: Install async memcpy driver failed because of other error

esp_err_t esp_async_memcpy_uninstall(async_memcpy_handle_t mcp)

  Uninstall async memcpy driver.

  参数:

    mcp -- [in] Handle of async memcpy driver returned by an install function

  返回:

    - ESP_OK: Uninstall async memcpy driver successfully

    - ESP_ERR_INVALID_ARG: Uninstall async memcpy driver failed because of invalid argument

    - ESP_FAIL: Uninstall async memcpy driver failed because of other error

esp_err_t esp_async_memcpy(async_memcpy_handle_t mcp, void *dst, void *src, size_t n, async_memcpy_isr_cb_t cb_isr, void *cb_args)

  Send an asynchronous memory copy request.

  备注

  The callback function is invoked in interrupt context, never do blocking jobs in the callback.

  参数:

    - mcp -- [in] Handle of async memcpy driver returned by an install function

    - dst -- [in] Destination address (copy to)

    - src -- [in] Source address (copy from)

    - n -- [in] Number of bytes to copy

    - cb_isr -- [in] Callback function, which got invoked in interrupt context. Set to NULL can bypass the callback.

    - cb_args -- [in] User defined argument to be passed to the callback function

  返回:

    - ESP_OK: Send memory copy request successfully

    - ESP_ERR_INVALID_ARG: Send memory copy request failed because of invalid argument

    - ESP_FAIL: Send memory copy request failed because of other error

esp_err_t esp_memcpy_blocking(async_memcpy_handle_t mcp, void *dst, void *src, size_t n, int32_t timeout_ms)

  Blocking memory copy function with timeout.

  备注

  This function is blocking and should not be called from interrupt context.

  备注

  Only `timeout_ms=-1` is supported, which means waiting indefinitely.

  参数:

    - mcp -- [in] Handle of async memcpy driver returned by an install function

    - dst -- [in] Destination address (copy to)

    - src -- [in] Source address (copy from)

    - n -- [in] Number of bytes to copy

    - timeout_ms -- [in] Timeout in milliseconds. Only -1 is supported.

  返回:

    - ESP_OK: Copy memory successfully

    - ESP_ERR_INVALID_ARG: Copy memory failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Function called from ISR context or driver in invalid state

    - ESP_FAIL: Copy memory failed because of other error

esp_err_t esp_async_memcpy_new_etm_event(async_memcpy_handle_t mcp, async_memcpy_etm_event_t event_type, esp_etm_event_handle_t *out_event)

  Get the ETM event handle for async memcpy done signal.

  备注

  The created ETM event object can be deleted later by calling `esp_etm_del_event`

  参数:

    - mcp -- [in] Handle of async memcpy driver returned by an install function

    - event_type -- [in] ETM event type

    - out_event -- [out] Returned ETM event handle

  返回:

    - ESP_OK: Get ETM event successfully

    - ESP_ERR_INVALID_ARG: Get ETM event failed because of invalid argument

    - ESP_ERR_NOT_SUPPORTED: Get ETM event failed because the DMA hardware doesn't support ETM submodule

    - ESP_FAIL: Get ETM event failed because of other error

#### Structures

struct async_memcpy_event_t

  Async memory copy event data.

  Public Members

  void *data

    Event data

struct async_memcpy_config_t

  Type of async memcpy configuration.

  Public Members

  uint32_t backlog

    Maximum number of transactions that can be prepared in the background

  uint32_t weight

    Weight of async memcpy dma channel, higher weight means higher average bandwidth

  size_t dma_burst_size

    DMA transfer burst size, in bytes

  uint32_t flags

    Extra flags to control async memcpy feature

#### Macros

ASYNC_MEMCPY_DEFAULT_CONFIG()

  Default configuration for async memcpy.

#### Type Definitions

typedef struct async_memcpy_context_t *async_memcpy_handle_t

  Async memory copy driver handle.

typedef bool (*async_memcpy_isr_cb_t)(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t *event, void *cb_args)

  Type of async memcpy interrupt callback function.

  备注

  User can call OS primitives (semaphore, mutex, etc) in the callback function. Keep in mind, if any OS primitive wakes high priority task up, the callback should return true.

  Param mcp_hdl:

    Handle of async memcpy

  Param event:

    Event object, which contains related data, reserved for future

  Param cb_args:

    User defined arguments, passed from esp_async_memcpy function

  Return:

    Whether a high priority task is woken up by the callback function

#### Enumerations

enum async_memcpy_etm_event_t

  Async memory copy specific events that supported by the ETM module.

  Values:

  enumerator ASYNC_MEMCPY_ETM_EVENT_COPY_DONE

    memory copy finished

此文档对您有帮助吗？

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

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