# Asynchronous Memory Copy

[中文]

The Async Memcpy driver uses DMA to copy data while the CPU performs other work. Use it for sufficiently large buffers when waiting for `memcpy` would delay useful work, such as preparing the next frame or processing the previous buffer.

This document starts with a blocking copy, then shows how to submit copies without blocking the calling task.

## Before You Start

The driver is available only on targets that support asynchronous memory copy. Add `esp_driver_dma` to your project's component dependencies before including `esp_async_memcpy.h`.

DMA must be able to access both buffers. Allocate destination buffers in DMA-capable RAM. Whether a particular DMA backend supports PSRAM depends on the target and backend.

Important

Do not read or modify the destination buffer until its copy has completed. Do not modify the source buffer until its copy has completed either.

## Quick Start

The typical workflow is:

            flowchart TD
        install["Install driver"] --> choose{"How should the task wait?"}
        choose --> blocking["Blocking copy<br/>esp_memcpy_blocking"]
        choose --> async["Async copy<br/>esp_async_memcpy"]
        async --> callback["Callback notifies task"]
        blocking --> use["Use destination buffer"]
        callback --> use
        use --> more{"More copies?"}
        more -->|Yes| choose
        more -->|No| uninstall["Uninstall driver"]

        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

## Scenario 1: Copy One Buffer and Wait

Start with `esp_memcpy_blocking()` if the next operation needs the copied data immediately. It uses DMA for suitable buffers and waits until the copy is complete. For small buffers, it safely falls back to a CPU copy.

    #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,
    };

    // Explicitly select the AHB GDMA backend.
    ESP_ERROR_CHECK(esp_async_memcpy_install_gdma_ahb(&config, &memcpy_hdl));

    // src and dst are DMA-accessible buffers. The call returns after dst is ready.
    ESP_ERROR_CHECK(esp_memcpy_blocking(memcpy_hdl, dst, src, copy_size, -1));

    // It is now safe to use dst.
    process_data(dst, copy_size);

    ESP_ERROR_CHECK(esp_async_memcpy_uninstall(memcpy_hdl));

`timeout_ms` must be `-1`, which waits indefinitely. The blocking API must be called from task context, not from an ISR.

### Installing the Driver

Select a DMA backend explicitly when installing the driver. The AHB GDMA backend used in the previous example is available only on targets with AHB GDMA support. Choose an install function that is available on your target and matches the DMA engine your application intends to use:

- `esp_async_memcpy_install_gdma_ahb()`

For a single blocking copy, set `async_memcpy_config_t::backlog` to 1. Increase it when multiple copies can be pending. `async_memcpy_config_t::dma_burst_size` controls the burst size in bytes; start with 16 and tune it only after measuring your workload. Set `async_memcpy_config_t::weight` to 0 unless weighted arbitration is supported and your application needs to adjust its average bus bandwidth.

## Scenario 2: Continue Working While DMA Copies

Use `esp_async_memcpy()` when the task has useful work to do while DMA transfers the buffer. The function queues the request and returns before the copy finishes. A callback then notifies the task that owns the destination 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 is copying. Do work that does not access src or dst here.
    prepare_next_operation();

    xSemaphoreTake(done, portMAX_DELAY);
    // The callback has run and dst is ready.
    process_data(dst, copy_size);

The driver is thread-safe, so tasks can submit requests through the same handle. Requests are processed in submission order. Set `backlog` high enough for the maximum number of copies that your application may have pending.

Warning

The callback runs in ISR context. Keep it short and use only ISR-safe functions, such as `xSemaphoreGiveFromISR` or `xQueueSendFromISR`. Do not call blocking APIs, perform lengthy processing, or submit another copy from the callback.

## Buffer Size and Alignment

The driver handles unaligned source and destination addresses. It uses the CPU for unaligned edge bytes and DMA for the cache-aligned body, so applications do not need to manually align ordinary buffers.

For `esp_async_memcpy()`, a cached destination buffer must be at least two cache lines long. Smaller requests return `ESP_ERR_INVALID_SIZE`; use standard `memcpy` instead. `esp_memcpy_blocking()` automatically uses a CPU copy for that case.

Note

DMA is not automatically faster for every transfer. For a short copy, CPU `memcpy` avoids DMA setup overhead. Measure with representative buffer sizes before moving a performance-critical path to DMA.

## Finishing and Releasing the Driver

Keep the driver installed while it is needed. Before calling `esp_async_memcpy_uninstall()`, wait for every queued copy to finish and ensure no task can submit another request. The handle and its resources are no longer valid after a successful uninstall.

## ETM Event

A completed copy can generate an event for the ETM module. Call `esp_async_memcpy_new_etm_event()` with `ASYNC_MEMCPY_ETM_EVENT_COPY_DONE` to obtain the event handle, then connect it to an ETM task as described in the ETM documentation.

## API Reference

### Async Memcpy Driver Functions

#### 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.

  Parameters:

    - config -- [in] Configuration of async memcpy

    - mcp -- [out] Returned driver handle

  Returns:

    - 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.

  Parameters:

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

  Returns:

    - 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.

  Note

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

  Parameters:

    - 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

  Returns:

    - 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.

  Note

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

  Note

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

  Parameters:

    - 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.

  Returns:

    - 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.

  Note

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

  Parameters:

    - 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

  Returns:

    - 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.

  Note

  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

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.