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:

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

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

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


Was this page helpful?