refactor(isp): read DMA input directly from flash

Avoid the PSRAM copy for unencrypted flash
This commit is contained in:
morris
2026-07-20 12:44:18 +08:00
committed by Gao Xu
parent 3e162786cb
commit 8163ee0de8
10 changed files with 77 additions and 52 deletions

View File

@@ -17,14 +17,15 @@ extern "C" {
/**
* @brief Process one ISP DMA frame: feed the input buffer through the ISP and wait for completion
*
* @note Input buffer content should be ready before calling this function. If the buffers are in
* cacheable memory, the caller should synchronize them around DMA access. Buffer sizes are
* derived from the ISP processor resolution and pixel formats. Both buffers must be 8-byte
* aligned. This function blocks until both input and output DMA channels finish.
* @note The driver synchronizes cacheable buffers before and after DMA access. Input buffers use
* an unaligned cache write-back, while cacheable output buffers and their derived frame
* sizes must be aligned to the cache line size.
* @note This function blocks until both input and output DMA channels finish. On timeout,
* the output buffer may still be owned by DMA and must not be accessed.
*
* @param[in] proc Processor handle
* @param[in] output_buffer Destination buffer for ISP output (8-byte aligned)
* @param[in] input_buffer Source input buffer for RAW frame data (8-byte aligned)
* @param[in] output_buffer Destination buffer for ISP output
* @param[in] input_buffer Source input buffer for RAW frame data
* @param[in] timeout_ms Timeout in milliseconds for waiting transfer completion
*
* @return

View File

@@ -9,6 +9,7 @@
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
@@ -56,6 +57,17 @@ static dw_gdma_burst_items_t s_isp_dma_burst_len_to_items(uint32_t burst_len)
}
}
static esp_err_t s_isp_dma_sync_cacheable_buffer(void *buffer, size_t size, int flags, const char *buffer_name)
{
size_t cache_line_size = esp_cache_get_line_size_by_addr(buffer);
if (cache_line_size == 0) {
return ESP_OK;
}
ESP_RETURN_ON_ERROR(esp_cache_msync(buffer, size, flags), TAG, "sync %s buffer cache failed", buffer_name);
return ESP_OK;
}
static void s_isp_dma_frame_ctx_destroy(struct esp_isp_dma_frame_ctx_t *ctx)
{
if (!ctx) {
@@ -243,6 +255,21 @@ esp_err_t esp_isp_dma_process_frame(isp_proc_handle_t proc, void *output_buffer,
ESP_RETURN_ON_FALSE((((uintptr_t)input_buffer) % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "input buffer not 8-byte aligned");
esp_isp_dma_frame_ctx_t *ctx = proc->dma_frame_ctx;
size_t input_frame_size = ctx->input_frame_size_64bit * sizeof(uint64_t);
size_t output_frame_size = ctx->output_frame_size_64bit * sizeof(uint64_t);
ESP_RETURN_ON_ERROR(s_isp_dma_sync_cacheable_buffer((void *)input_buffer, input_frame_size,
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED,
"input"),
TAG, "sync input buffer cache failed");
/*
* Discard dirty CPU cache lines before DMA writes the frame. Otherwise a
* later cache write-back could overwrite data produced by the ISP.
*/
ESP_RETURN_ON_ERROR(s_isp_dma_sync_cacheable_buffer(output_buffer, output_frame_size,
ESP_CACHE_MSYNC_FLAG_DIR_M2C, "output"),
TAG, "sync output buffer cache failed");
ctx->dma_out_trans.dst.addr = (uint32_t)output_buffer;
ctx->dma_in_trans.src.addr = (uint32_t)input_buffer;

View File

@@ -248,7 +248,7 @@ ISP DMA Input
Besides image streams from camera controllers, the ISP can also read image frames from system memory through DW-GDMA. To use DMA input, set :cpp:member:`esp_isp_processor_cfg_t::input_data_source` in :cpp:type:`esp_isp_processor_cfg_t` to :cpp:enumerator:`ISP_INPUT_DATA_SOURCE_DWGDMA`, and configure the input format, output format, and resolution according to the image frame.
DMA input is useful for feeding software-generated data, offline RAW images, or other test images in memory into the ISP. It can be used to validate an ISP pipeline without a camera sensor, reproduce issues with a specific input image. Call :cpp:func:`esp_isp_dma_process_frame` to send one input buffer to the ISP and write the processed image into an output buffer. The input and output buffers must be accessible by DMA; if cacheable memory is used, perform the required cache synchronization before and after the DMA transfer.
DMA input is useful for feeding software-generated data, offline RAW images, or other test images in memory into the ISP. It can be used to validate an ISP pipeline without a camera sensor, reproduce issues with a specific input image. Call :cpp:func:`esp_isp_dma_process_frame` to send one input buffer to the ISP and write the processed image into an output buffer. The input and output buffers must be accessible by DMA. The driver synchronizes cacheable buffers automatically. Input buffers use an unaligned cache write-back; cacheable output buffer addresses and their derived frame sizes must be aligned to the cache line size.
ISP AF Controller
~~~~~~~~~~~~~~~~~

View File

@@ -248,7 +248,7 @@ ISP DMA 输入
除来自摄像头控制器的数据流外ISP 还可以通过 DW-GDMA 从系统存储中读取图像帧作为输入。使用 DMA 输入时,应在 :cpp:type:`esp_isp_processor_cfg_t` 中将 :cpp:member:`esp_isp_processor_cfg_t::input_data_source` 配置为 :cpp:enumerator:`ISP_INPUT_DATA_SOURCE_DWGDMA`,并根据输入图像格式设置输入、输出格式及分辨率。
DMA 输入适用于将软件生成的数据、离线保存的 RAW 图像或其他内存中的测试图像送入 ISP 进行处理。它可用于无摄像头传感器参与时验证 ISP 流水线、复现特定输入图像的问题。调用 :cpp:func:`esp_isp_dma_process_frame` 可以将一帧输入缓冲区送入 ISP并将处理后的图像写入输出缓冲区。输入和输出缓冲区需要满足 DMA 访问要求;若使用带 cache 的内存,请在 DMA 传输前后执行必要的 cache 同步
DMA 输入适用于将软件生成的数据、离线保存的 RAW 图像或其他内存中的测试图像送入 ISP 进行处理。它可用于无摄像头传感器参与时验证 ISP 流水线、复现特定输入图像的问题。调用 :cpp:func:`esp_isp_dma_process_frame` 可以将一帧输入缓冲区送入 ISP并将处理后的图像写入输出缓冲区。输入和输出缓冲区需要满足 DMA 访问要求。驱动会自动同步带 cache 的缓冲区:输入使用允许未对齐的 cache writeback带 cache 的输出地址及其派生帧大小必须按 cache line 大小对齐
ISP AF 控制器
~~~~~~~~~~~~~

View File

@@ -52,6 +52,16 @@ The accompanying pytest script captures the `IMAGE_META` and `IMAGE_BASE64` outp
It also compares the generated result with `golden_result.ppm` by hashing the decoded RGB pixel content. This turns the example into a regression test as well as a visual demo: the image must both render correctly for a human and match the stored golden output for CI.
### Viewing The Result Locally
Build and flash the example for your target, then run the pytest script from this example's directory:
```bash
pytest pytest_async_color_convert.py --target esp32p4 --port PORT
```
Replace `esp32p4` with another supported target and `PORT` with the board's serial device. Pytest prints the artifact directory after the test completes; open `async_color_convert_result.ppm` from that directory with a PPM-compatible image viewer.
## Replacing The Embedded UYVY Asset
The example embeds `main/assets/sample_96x64_uyvy.yuv`.

View File

@@ -5,19 +5,18 @@
## Overview
This example embeds a 240 x 280 RAW8 Bayer image of a real scene in flash, copies it into a DMA-capable PSRAM input buffer, feeds it into the ISP through DW-GDMA, and prints the RGB888 output as base64. The pytest script decodes the output into a PPM image and compares it with the checked-in golden image.
This example embeds a 240 x 280 RAW8 Bayer image of a real scene in flash, feeds it directly into the ISP through DW-GDMA, and prints the RGB888 output as base64. The ISP driver synchronizes the DMA buffers' cache automatically. The pytest script decodes the output into a PPM image and compares it with the checked-in golden image.
The data flow is:
1. The embedded BGGR RAW8 image is copied from flash into the ISP DMA input buffer.
2. The image is transferred into the ISP via `DW-GDMA → ISP DMA input`.
3. The ISP processes the data (demosaic, color adjustment) and outputs RGB888 (BGR24 byte layout).
4. The RGB888 frame is base64-encoded and printed with machine-parseable markers.
5. pytest decodes the payload, swaps BGR→RGB, saves one PPM file per frame, and compares it with the golden image.
1. The embedded 16-byte-aligned BGGR RAW8 image is transferred from mapped flash into the ISP via `DW-GDMA → ISP DMA input`.
2. The ISP processes the data (demosaic, color adjustment) and outputs RGB888 (BGR24 byte layout).
3. The RGB888 frame is base64-encoded and printed with machine-parseable markers.
4. pytest decodes the payload, swaps BGR→RGB, saves one PPM file per frame, and compares it with the golden image.
## Hardware Required
- An ESP32-P4 devkit with PSRAM (this example allocates the ISP DMA input/output buffers from PSRAM).
- An ESP32-P4 devkit with PSRAM (this example allocates the ISP output buffer from PSRAM).
## How to Use
@@ -43,7 +42,3 @@ IMAGE_BASE64_END
Frame 0 done
ISP DMA visual demo done.
```
## Reference
- [ESP-IDF: Image Signal Processor](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/isp.html)

View File

@@ -1,7 +1,7 @@
idf_component_register(SRCS "isp_dma_main.c"
idf_component_register(SRCS "isp_dma_example_main.c"
PRIV_REQUIRES esp_driver_isp esp_mm esp_psram mbedtls
INCLUDE_DIRS ".")
target_add_binary_data(${COMPONENT_LIB}
"${CMAKE_CURRENT_LIST_DIR}/assets/sensor_240x280_bggr.raw"
BINARY RENAME_TO "sensor_raw")
BINARY RENAME_TO "sensor_raw" ALIGN 16)

View File

@@ -7,13 +7,11 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mbedtls/base64.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "driver/isp_dma.h"
#include "driver/isp_core.h"
@@ -22,20 +20,14 @@
#define EXAMPLE_WIDTH 240
#define EXAMPLE_HEIGHT 280
#define EXAMPLE_BASE64_CHUNK_LEN 384
#define EXAMPLE_BASE64_DELAY_MS 10
#define EXAMPLE_DMA_ALIGN 64
#define EXAMPLE_FRAME_COUNT 1
/* CMake embeds this RAW asset in mapped flash with 16-byte alignment. */
extern const uint8_t sensor_raw_start[] asm("_binary_sensor_raw_start");
extern const uint8_t sensor_raw_end[] asm("_binary_sensor_raw_end");
static void *s_alloc_dma_buffer(size_t size)
{
return heap_caps_aligned_calloc(EXAMPLE_DMA_ALIGN, 1, size,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
}
static void s_configure_neutral_color(isp_proc_handle_t isp_proc)
static void example_configure_neutral_color(isp_proc_handle_t isp_proc)
{
esp_isp_color_config_t color_cfg = {
.color_contrast = { .integer = 1, .decimal = 0 },
@@ -48,21 +40,22 @@ static void s_configure_neutral_color(isp_proc_handle_t isp_proc)
ESP_ERROR_CHECK(esp_isp_color_enable(isp_proc));
}
static void s_print_base64_payload(const unsigned char *encoded, size_t encoded_len)
static void example_print_base64_payload(const unsigned char *encoded, size_t encoded_len)
{
printf("IMAGE_BASE64_BEGIN\n");
fflush(stdout);
size_t chunk_count = 0;
for (size_t offset = 0; offset < encoded_len; offset += EXAMPLE_BASE64_CHUNK_LEN) {
size_t chunk_len = encoded_len - offset;
if (chunk_len > EXAMPLE_BASE64_CHUNK_LEN) {
chunk_len = EXAMPLE_BASE64_CHUNK_LEN;
}
printf("IMAGE_BASE64 %.*s\n", (int)chunk_len, (const char *)&encoded[offset]);
fflush(stdout);
vTaskDelay(pdMS_TO_TICKS(EXAMPLE_BASE64_DELAY_MS));
if ((++chunk_count % 16) == 0) {
/* Let the test host drain the UART without delaying every chunk. */
vTaskDelay(1);
}
}
printf("IMAGE_BASE64_END\n");
fflush(stdout);
}
void app_main(void)
@@ -87,15 +80,15 @@ void app_main(void)
};
ESP_ERROR_CHECK(esp_isp_new_processor(&isp_cfg, &isp_proc));
ESP_ERROR_CHECK(esp_isp_enable(isp_proc));
s_configure_neutral_color(isp_proc);
example_configure_neutral_color(isp_proc);
uint8_t *isp_in_buf = s_alloc_dma_buffer(in_size);
uint8_t *isp_out_buf = s_alloc_dma_buffer(out_size);
assert(isp_in_buf && isp_out_buf);
/* ISP writes this RGB frame through DMA, so PSRAM must be DMA-capable. */
uint8_t *isp_out_buf = heap_caps_aligned_calloc(EXAMPLE_DMA_ALIGN, 1, out_size,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
assert(isp_out_buf);
size_t embedded_raw_size = sensor_raw_end - sensor_raw_start;
assert(embedded_raw_size == in_size);
memcpy(isp_in_buf, sensor_raw_start, embedded_raw_size);
size_t encoded_len = 0;
int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, isp_out_buf, out_size);
@@ -105,24 +98,25 @@ void app_main(void)
printf("Feeding %d frames through ISP DMA input...\n", EXAMPLE_FRAME_COUNT);
for (int frame = 0; frame < EXAMPLE_FRAME_COUNT; frame++) {
ESP_ERROR_CHECK(esp_cache_msync(isp_in_buf, in_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M));
ESP_ERROR_CHECK(esp_isp_dma_process_frame(isp_proc, isp_out_buf, isp_in_buf, 1000));
ESP_ERROR_CHECK(esp_cache_msync(isp_out_buf, out_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C));
/*
* The RAW image is immutable mapped flash. Its aligned address can be
* read by DMA directly, so no PSRAM copy is needed.
*/
ESP_ERROR_CHECK(esp_isp_dma_process_frame(isp_proc, isp_out_buf, sensor_raw_start, 1000));
size_t out_len = 0;
ESP_ERROR_CHECK(mbedtls_base64_encode(encoded, encoded_len + 1, &out_len, isp_out_buf, out_size) == 0 ? ESP_OK : ESP_FAIL);
printf("IMAGE_META frame=%d width=%u height=%u format=BGR24 encoding=base64\n",
frame, (unsigned)h_res, (unsigned)v_res);
s_print_base64_payload(encoded, out_len);
example_print_base64_payload(encoded, out_len);
printf("Frame %d done\n", frame);
}
printf("ISP DMA visual demo done.\n");
free(encoded);
ESP_ERROR_CHECK(esp_isp_color_disable(isp_proc));
ESP_ERROR_CHECK(esp_isp_disable(isp_proc));
ESP_ERROR_CHECK(esp_isp_del_processor(isp_proc));
heap_caps_free(isp_in_buf);
heap_caps_free(isp_out_buf);
free(encoded);
free(isp_out_buf);
}

View File

@@ -60,8 +60,7 @@ The test writes the `PPM` file and compares it with `golden_output.ppm`. This ma
To run the pytest helper locally on hardware, build the example for your target first, then invoke the test script with the target and serial port:
```bash
idf.py set-target esp32p4 build
pytest --target esp32p4 --port PORT pytest_jpeg_decode.py
pytest pytest_jpeg_decode.py --target esp32p4 --port PORT
```
Replace `esp32p4` with another supported target such as `esp32s31` when needed.

View File

@@ -55,8 +55,7 @@ It also compares the generated JPEG with `golden_output.jpeg`. This turns the ex
To run the pytest helper locally on hardware, build the example for your target first, then invoke the test script with the target and serial port:
```bash
idf.py set-target esp32p4 build
pytest --target esp32p4 --port PORT pytest_jpeg_encode.py
pytest pytest_jpeg_encode.py --target esp32p4 --port PORT
```
Replace `esp32p4` with another supported target such as `esp32s31` when needed.