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
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_dmacomponent. To declare that your component depends onesp_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_cpdma(const async_memcpy_config_t *config, async_memcpy_handle_t *mcp)
Install async memcpy driver, with CPDMA as the backend.
Note
CPDMA is a CPU peripheral, aiming for memory copy.
- 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=-1is 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_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
-
uint32_t backlog
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