mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'isp_dma_one_frame_v5.5' into 'release/v5.5'
feat(isp): support isp dma input and add example (v5.5) See merge request espressif/esp-idf!50756
This commit is contained in:
@@ -6,10 +6,16 @@ set(public_include "include")
|
||||
|
||||
set(priv_requires "esp_driver_gpio")
|
||||
|
||||
set(requires)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
set(requires "")
|
||||
else()
|
||||
set(requires "esp_hw_support" "esp_mm")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_ISP_SUPPORTED)
|
||||
list(APPEND srcs "src/isp_core.c"
|
||||
"src/isp_dma.c"
|
||||
"src/isp_af.c"
|
||||
"src/isp_ccm.c"
|
||||
"src/isp_awb.c"
|
||||
|
||||
@@ -32,6 +32,7 @@ typedef struct {
|
||||
uint32_t h_res; ///< Input horizontal resolution, i.e. the number of pixels in a line
|
||||
uint32_t v_res; ///< Input vertical resolution, i.e. the number of lines in a frame
|
||||
color_raw_element_order_t bayer_order; ///< Bayer order
|
||||
uint32_t dma_burst_size; ///< DMA output burst length in units of 64-bit beats. Set to 0 to use default value 16
|
||||
int intr_priority; ///< The interrupt priority, range 0~3, if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3)
|
||||
struct {
|
||||
uint32_t bypass_isp : 1; ///< Bypass ISP pipelines
|
||||
|
||||
40
components/esp_driver_isp/include/driver/isp_dma.h
Normal file
40
components/esp_driver_isp/include/driver/isp_dma.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/isp_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Process one ISP DMA frame: feed the input buffer through the ISP and wait for completion
|
||||
*
|
||||
* @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
|
||||
* @param[in] input_buffer Source input buffer for RAW frame data
|
||||
* @param[in] timeout_ms Timeout in milliseconds for waiting transfer completion
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG Invalid argument
|
||||
* - ESP_ERR_TIMEOUT Wait timeout
|
||||
*/
|
||||
esp_err_t esp_isp_dma_process_frame(isp_proc_handle_t proc, void *output_buffer, const void *input_buffer, uint32_t timeout_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -60,6 +60,8 @@ typedef enum {
|
||||
/*---------------------------------------------------------------
|
||||
Driver Context
|
||||
---------------------------------------------------------------*/
|
||||
typedef struct esp_isp_dma_frame_ctx_t esp_isp_dma_frame_ctx_t;
|
||||
|
||||
typedef struct isp_processor_t {
|
||||
int proc_id;
|
||||
isp_clk_src_t clk_src;
|
||||
@@ -72,10 +74,14 @@ typedef struct isp_processor_t {
|
||||
portMUX_TYPE spinlock;
|
||||
color_space_pixel_format_t in_color_format;
|
||||
color_space_pixel_format_t out_color_format;
|
||||
isp_input_data_source_t input_data_source;
|
||||
uint32_t h_res;
|
||||
uint32_t v_res;
|
||||
color_raw_element_order_t bayer_order;
|
||||
bool bypass_isp;
|
||||
uint32_t dma_in_burst_len; // ISP DMA input burst length (64-bit beats), kept in sync with the input GDMA dst MSIZE
|
||||
uint32_t dma_out_burst_len; // ISP DMA output burst length (64-bit beats)
|
||||
esp_isp_dma_frame_ctx_t *dma_frame_ctx;
|
||||
/* sub module contexts */
|
||||
isp_af_ctlr_t af_ctlr[SOC_ISP_AF_CTLR_NUMS];
|
||||
isp_awb_ctlr_t awb_ctlr;
|
||||
@@ -133,6 +139,45 @@ bool esp_isp_awb_isr(isp_proc_handle_t proc, uint32_t awb_events);
|
||||
bool esp_isp_sharpen_isr(isp_proc_handle_t proc, uint32_t sharp_events);
|
||||
bool esp_isp_hist_isr(isp_proc_handle_t proc, uint32_t hist_events);
|
||||
|
||||
/*---------------------------------------------------------------
|
||||
DMA INPUT
|
||||
---------------------------------------------------------------*/
|
||||
/**
|
||||
* @brief Configure the ISP DMA input path (frame size, burst length, data type)
|
||||
*
|
||||
* @note Internal helper invoked while creating a processor whose input source is DWGDMA.
|
||||
*
|
||||
* @param[in] proc Processor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG Invalid input color format or frame size
|
||||
*/
|
||||
esp_err_t isp_dma_configure_input(isp_proc_handle_t proc);
|
||||
|
||||
/**
|
||||
* @brief Create the ISP DMA frame context
|
||||
*
|
||||
* @note Internal helper invoked while creating a processor whose input source is DWGDMA.
|
||||
*
|
||||
* @param[in] proc Processor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG Invalid processor, burst length, or frame size
|
||||
* - ESP_ERR_INVALID_STATE Processor input source is not DWGDMA or the context already exists
|
||||
* - ESP_ERR_NO_MEM Failed to allocate the context or synchronization objects
|
||||
* - Other errors returned by the underlying DMA driver
|
||||
*/
|
||||
esp_err_t isp_dma_new_frame_ctx(isp_proc_handle_t proc);
|
||||
|
||||
/**
|
||||
* @brief Delete the ISP DMA frame context
|
||||
*
|
||||
* @param[in] proc Processor handle
|
||||
*/
|
||||
void isp_dma_del_frame_ctx(isp_proc_handle_t proc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "esp_private/mipi_csi_share_hw_ctrl.h"
|
||||
#include "hal/hal_utils.h"
|
||||
#include "hal/color_hal.h"
|
||||
#include "hal/mipi_csi_brg_ll.h"
|
||||
#include "soc/mipi_csi_bridge_struct.h"
|
||||
#include "soc/isp_periph.h"
|
||||
#include "soc/soc_caps.h"
|
||||
@@ -79,7 +80,19 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
ESP_RETURN_ON_FALSE(proc_config && ret_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(proc_config->h_res <= ISP_LL_HSIZE_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid h_res");
|
||||
ESP_RETURN_ON_FALSE(proc_config->input_data_source != ISP_INPUT_DATA_SOURCE_DWGDMA, ESP_ERR_NOT_SUPPORTED, TAG, "input source not supported yet");
|
||||
if (proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA) {
|
||||
ESP_RETURN_ON_FALSE((proc_config->input_data_color_type == ISP_COLOR_RAW8) ||
|
||||
(proc_config->input_data_color_type == ISP_COLOR_RAW10) ||
|
||||
(proc_config->input_data_color_type == ISP_COLOR_RAW12),
|
||||
ESP_ERR_INVALID_ARG, TAG, "dma input only supports RAW8/RAW10/RAW12");
|
||||
uint32_t in_bits_per_pixel = color_hal_pixel_format_get_bit_depth((color_space_pixel_format_t) {
|
||||
.color_type_id = proc_config->input_data_color_type,
|
||||
});
|
||||
uint64_t frame_bits = (uint64_t)proc_config->h_res * proc_config->v_res * in_bits_per_pixel;
|
||||
ESP_RETURN_ON_FALSE((frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "frame bits should be 64-bit aligned");
|
||||
ESP_RETURN_ON_FALSE((frame_bits / 64) <= ((1UL << 22) - 1), ESP_ERR_INVALID_ARG, TAG, "frame size exceeds hardware limit");
|
||||
ESP_RETURN_ON_FALSE(proc_config->dma_burst_size <= 16, ESP_ERR_INVALID_ARG, TAG, "dma burst size out of range");
|
||||
}
|
||||
if (proc_config->flags.bypass_isp) {
|
||||
ESP_RETURN_ON_FALSE(proc_config->input_data_color_type == proc_config->output_data_color_type, ESP_ERR_INVALID_ARG, TAG, "isp is bypassed, input and output data color type should be same");
|
||||
}
|
||||
@@ -176,7 +189,9 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
|
||||
isp_ll_yuv_set_range(proc->hal.hw, proc_config->yuv_range);
|
||||
}
|
||||
|
||||
if (out_color_format.color_space == COLOR_SPACE_RGB && proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DVP) {
|
||||
if ((out_color_format.color_type_id == ISP_COLOR_RGB888 || out_color_format.color_type_id == ISP_COLOR_RGB565) &&
|
||||
(proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DVP ||
|
||||
proc_config->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA)) {
|
||||
isp_ll_color_enable(proc->hal.hw, true); // workaround for DIG-474
|
||||
}
|
||||
if (proc_config->flags.byte_swap_en) {
|
||||
@@ -187,16 +202,23 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
|
||||
|
||||
proc->in_color_format = in_color_format;
|
||||
proc->out_color_format = out_color_format;
|
||||
proc->input_data_source = proc_config->input_data_source;
|
||||
proc->h_res = proc_config->h_res;
|
||||
proc->v_res = proc_config->v_res;
|
||||
proc->bayer_order = proc_config->bayer_order;
|
||||
proc->bypass_isp = proc_config->flags.bypass_isp;
|
||||
proc->dma_out_burst_len = proc_config->dma_burst_size;
|
||||
if (proc->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA) {
|
||||
ESP_GOTO_ON_ERROR(isp_dma_configure_input(proc), err, TAG, "configure dma input failed");
|
||||
ESP_GOTO_ON_ERROR(isp_dma_new_frame_ctx(proc), err, TAG, "create dma frame context failed");
|
||||
}
|
||||
|
||||
*ret_proc = proc;
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
isp_dma_del_frame_ctx(proc);
|
||||
if (proc->intr_hdl) {
|
||||
esp_isp_deregister_isr(proc, ISP_SUBMODULE_GENERAL);
|
||||
}
|
||||
@@ -210,6 +232,8 @@ esp_err_t esp_isp_del_processor(isp_proc_handle_t proc)
|
||||
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&proc->isp_fsm) == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "processor isn't in init state");
|
||||
|
||||
isp_dma_del_frame_ctx(proc);
|
||||
|
||||
//declaim first, then do free
|
||||
ESP_RETURN_ON_ERROR(s_isp_declaim_processor(proc), TAG, "declaim processor fail");
|
||||
#if SOC_ISP_SHARE_CSI_BRG
|
||||
@@ -341,20 +365,26 @@ static void IRAM_ATTR s_isp_isr_dispatcher(void *arg)
|
||||
do_dispatch = false;
|
||||
}
|
||||
|
||||
if ((error_events & ISP_LL_EVENT_DATA_TYPE_ERR) || (error_events & ISP_LL_EVENT_DATA_TYPE_SETTING_ERR)) {
|
||||
ESP_EARLY_LOGE(TAG, "data type error");
|
||||
}
|
||||
if ((error_events & ISP_LL_EVENT_ASYNC_FIFO_OVF) || (error_events & ISP_LL_EVENT_BUF_FULL)) {
|
||||
ESP_EARLY_LOGE(TAG, "fifo overflow");
|
||||
}
|
||||
if ((error_events & ISP_LL_EVENT_HVNUM_SETTING_ERR) || (error_events & ISP_LL_EVENT_MIPI_HNUM_UNMATCH)) {
|
||||
ESP_EARLY_LOGE(TAG, "hnum / vnum setting error");
|
||||
}
|
||||
if (error_events & ISP_LL_EVENT_GAMMA_XCOORD_ERR) {
|
||||
ESP_EARLY_LOGE(TAG, "gamma xcoord error");
|
||||
}
|
||||
if (error_events & ISP_LL_EVENT_CROP_ERR) {
|
||||
ESP_EARLY_LOGE(TAG, "crop error");
|
||||
if (error_events) {
|
||||
if ((error_events & ISP_LL_EVENT_DATA_TYPE_ERR) || (error_events & ISP_LL_EVENT_DATA_TYPE_SETTING_ERR)) {
|
||||
ESP_EARLY_LOGE(TAG, "data type error");
|
||||
}
|
||||
if ((error_events & ISP_LL_EVENT_ASYNC_FIFO_OVF)) {
|
||||
ESP_EARLY_LOGE(TAG, "fifo overflow");
|
||||
}
|
||||
if ((error_events & ISP_LL_EVENT_BUF_FULL)) {
|
||||
//This error does not affect the operation of the ISP
|
||||
ESP_EARLY_LOGD(TAG, "buffer full");
|
||||
}
|
||||
if ((error_events & ISP_LL_EVENT_HVNUM_SETTING_ERR) || (error_events & ISP_LL_EVENT_MIPI_HNUM_UNMATCH)) {
|
||||
ESP_EARLY_LOGE(TAG, "hnum / vnum setting error");
|
||||
}
|
||||
if (error_events & ISP_LL_EVENT_GAMMA_XCOORD_ERR) {
|
||||
ESP_EARLY_LOGE(TAG, "gamma xcoord error");
|
||||
}
|
||||
if (error_events & ISP_LL_EVENT_CROP_ERR) {
|
||||
ESP_EARLY_LOGE(TAG, "crop error");
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
|
||||
293
components/esp_driver_isp/src/isp_dma.c
Normal file
293
components/esp_driver_isp/src/isp_dma.c
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <esp_types.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_cache.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/isp_dma.h"
|
||||
#include "esp_private/dw_gdma.h"
|
||||
#include "esp_private/isp_private.h"
|
||||
#include "hal/color_hal.h"
|
||||
#include "hal/isp_ll.h"
|
||||
#include "hal/mipi_csi_brg_ll.h"
|
||||
|
||||
#define ISP_DMA_IN_BURST_LEN_DFT 8
|
||||
#define ISP_DMA_OUT_BURST_LEN_DFT 16
|
||||
|
||||
struct esp_isp_dma_frame_ctx_t {
|
||||
isp_proc_handle_t proc;
|
||||
csi_brg_dev_t *csi_brg_hw;
|
||||
dw_gdma_channel_handle_t dma_in_chan;
|
||||
dw_gdma_channel_handle_t dma_out_chan;
|
||||
SemaphoreHandle_t in_done_sem;
|
||||
SemaphoreHandle_t out_done_sem;
|
||||
dw_gdma_block_transfer_config_t dma_out_trans;
|
||||
dw_gdma_block_transfer_config_t dma_in_trans;
|
||||
uint32_t input_frame_size_64bit;
|
||||
uint32_t output_frame_size_64bit;
|
||||
};
|
||||
|
||||
static const char *TAG = "ISP_DMA";
|
||||
|
||||
static bool IRAM_ATTR s_isp_dma_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
(void)chan;
|
||||
(void)event_data;
|
||||
BaseType_t high_task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(*(SemaphoreHandle_t *)user_data, &high_task_woken);
|
||||
return high_task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
static dw_gdma_burst_items_t s_isp_dma_burst_len_to_items(uint32_t burst_len)
|
||||
{
|
||||
switch (burst_len) {
|
||||
case 1: return DW_GDMA_BURST_ITEMS_1;
|
||||
case 4: return DW_GDMA_BURST_ITEMS_4;
|
||||
case 8: return DW_GDMA_BURST_ITEMS_8;
|
||||
default: return DW_GDMA_BURST_ITEMS_8;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t s_isp_dma_sync_cacheable_buffer(void *buffer, size_t size, int flags, const char *buffer_name)
|
||||
{
|
||||
// Flash-mapped input is not data-cache backed and must not be passed to esp_cache_msync().
|
||||
if (!esp_ptr_internal(buffer) && !esp_ptr_external_ram(buffer)) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
if (ctx->csi_brg_hw) {
|
||||
mipi_csi_brg_ll_enable(ctx->csi_brg_hw, false);
|
||||
}
|
||||
if (ctx->dma_in_chan) {
|
||||
dw_gdma_channel_enable_ctrl(ctx->dma_in_chan, false);
|
||||
dw_gdma_del_channel(ctx->dma_in_chan);
|
||||
}
|
||||
if (ctx->dma_out_chan) {
|
||||
dw_gdma_channel_enable_ctrl(ctx->dma_out_chan, false);
|
||||
dw_gdma_del_channel(ctx->dma_out_chan);
|
||||
}
|
||||
if (ctx->in_done_sem) {
|
||||
vSemaphoreDelete(ctx->in_done_sem);
|
||||
}
|
||||
if (ctx->out_done_sem) {
|
||||
vSemaphoreDelete(ctx->out_done_sem);
|
||||
}
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
esp_err_t isp_dma_configure_input(isp_proc_handle_t proc)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(proc->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA, ESP_ERR_INVALID_ARG, TAG, "processor input source is not dwgdma");
|
||||
|
||||
bool valid_format = isp_ll_dma_set_data_type(proc->hal.hw, (isp_color_t)proc->in_color_format.color_type_id);
|
||||
ESP_RETURN_ON_FALSE(valid_format, ESP_ERR_INVALID_ARG, TAG, "dma input only supports RAW8/RAW10/RAW12");
|
||||
|
||||
uint32_t in_bits_per_pixel = color_hal_pixel_format_get_bit_depth(proc->in_color_format);
|
||||
uint64_t frame_bits = (uint64_t)proc->h_res * proc->v_res * in_bits_per_pixel;
|
||||
ESP_RETURN_ON_FALSE((frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "frame bits should be 64-bit aligned");
|
||||
|
||||
uint64_t frame_size_in_64bit = frame_bits / 64;
|
||||
ESP_RETURN_ON_FALSE(frame_size_in_64bit <= ((1UL << 22) - 1), ESP_ERR_INVALID_ARG, TAG, "frame size exceeds hardware limit");
|
||||
|
||||
/*
|
||||
* The input burst length is fixed at ISP_DMA_IN_BURST_LEN_DFT: it must be representable as a
|
||||
* DW-GDMA MSIZE (1/4/8) and not exceed the ISP input async FIFO capacity (8 x 64-bit). It also
|
||||
* has to equal the input GDMA dst MSIZE (derived from proc->dma_in_burst_len in
|
||||
* isp_dma_new_frame_ctx), otherwise the async FIFO overflows.
|
||||
*/
|
||||
isp_ll_dma_set_frame_size(proc->hal.hw, (uint32_t)frame_size_in_64bit);
|
||||
isp_ll_dma_set_burst_len(proc->hal.hw, ISP_DMA_IN_BURST_LEN_DFT);
|
||||
isp_ll_dma_apply_config(proc->hal.hw);
|
||||
proc->dma_in_burst_len = ISP_DMA_IN_BURST_LEN_DFT;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t isp_dma_new_frame_ctx(isp_proc_handle_t proc)
|
||||
{
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
ESP_RETURN_ON_FALSE(proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(proc->input_data_source == ISP_INPUT_DATA_SOURCE_DWGDMA, ESP_ERR_INVALID_STATE, TAG, "processor input source is not dwgdma");
|
||||
ESP_RETURN_ON_FALSE(!proc->dma_frame_ctx, ESP_ERR_INVALID_STATE, TAG, "dma frame context already exists");
|
||||
|
||||
uint32_t output_burst_len = proc->dma_out_burst_len ? proc->dma_out_burst_len : ISP_DMA_OUT_BURST_LEN_DFT;
|
||||
ESP_RETURN_ON_FALSE(output_burst_len > 0 && output_burst_len <= 16, ESP_ERR_INVALID_ARG, TAG, "output burst len out of range");
|
||||
|
||||
uint32_t in_bits_per_pixel = color_hal_pixel_format_get_bit_depth(proc->in_color_format);
|
||||
uint32_t out_bits_per_pixel = color_hal_pixel_format_get_bit_depth(proc->out_color_format);
|
||||
uint64_t input_frame_bits = (uint64_t)proc->h_res * proc->v_res * in_bits_per_pixel;
|
||||
uint64_t output_frame_bits = (uint64_t)proc->h_res * proc->v_res * out_bits_per_pixel;
|
||||
ESP_RETURN_ON_FALSE((input_frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "input frame bits should be 64-bit aligned");
|
||||
ESP_RETURN_ON_FALSE((output_frame_bits % 64) == 0, ESP_ERR_INVALID_ARG, TAG, "output frame bits should be 64-bit aligned");
|
||||
|
||||
struct esp_isp_dma_frame_ctx_t *ctx = heap_caps_calloc(1, sizeof(struct esp_isp_dma_frame_ctx_t), ISP_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, err, TAG, "no mem for dma frame context");
|
||||
|
||||
ctx->proc = proc;
|
||||
ctx->input_frame_size_64bit = input_frame_bits / 64;
|
||||
ctx->output_frame_size_64bit = output_frame_bits / 64;
|
||||
|
||||
csi_brg_dev_t *csi_brg_hw = MIPI_CSI_BRG_LL_GET_HW(proc->csi_brg_id);
|
||||
ctx->csi_brg_hw = csi_brg_hw;
|
||||
mipi_csi_brg_ll_set_intput_data_h_pixel_num(csi_brg_hw, proc->h_res);
|
||||
mipi_csi_brg_ll_set_intput_data_v_row_num(csi_brg_hw, proc->v_res);
|
||||
mipi_csi_brg_ll_set_burst_len(csi_brg_hw, 512);
|
||||
mipi_csi_brg_ll_enable(csi_brg_hw, true);
|
||||
|
||||
ctx->out_done_sem = xSemaphoreCreateBinary();
|
||||
ESP_GOTO_ON_FALSE(ctx->out_done_sem, ESP_ERR_NO_MEM, err, TAG, "no mem for output semaphore");
|
||||
ctx->in_done_sem = xSemaphoreCreateBinary();
|
||||
ESP_GOTO_ON_FALSE(ctx->in_done_sem, ESP_ERR_NO_MEM, err, TAG, "no mem for input semaphore");
|
||||
|
||||
dw_gdma_channel_alloc_config_t dma_out_alloc = {
|
||||
.src = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_PERIPH_CSI,
|
||||
.handshake_type = DW_GDMA_HANDSHAKE_HW,
|
||||
.num_outstanding_requests = 5,
|
||||
.status_fetch_addr = MIPI_CSI_BRG_MEM_BASE,
|
||||
},
|
||||
.dst = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.handshake_type = DW_GDMA_HANDSHAKE_HW,
|
||||
.num_outstanding_requests = 5,
|
||||
},
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_SRC,
|
||||
.chan_priority = 1,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_out_alloc, &ctx->dma_out_chan), err, TAG, "create output dma channel failed");
|
||||
|
||||
dw_gdma_channel_alloc_config_t dma_in_alloc = {
|
||||
.src = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.handshake_type = DW_GDMA_HANDSHAKE_SW,
|
||||
.num_outstanding_requests = 2,
|
||||
},
|
||||
.dst = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_CONTIGUOUS,
|
||||
.role = DW_GDMA_ROLE_PERIPH_ISP,
|
||||
.handshake_type = DW_GDMA_HANDSHAKE_HW,
|
||||
.num_outstanding_requests = 1,
|
||||
},
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_DST,
|
||||
.chan_priority = 1,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_in_alloc, &ctx->dma_in_chan), err, TAG, "create input dma channel failed");
|
||||
|
||||
ctx->dma_out_trans = (dw_gdma_block_transfer_config_t) {
|
||||
.src = {
|
||||
.addr = MIPI_CSI_BRG_MEM_BASE,
|
||||
.width = DW_GDMA_TRANS_WIDTH_64,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_FIXED,
|
||||
.burst_items = DW_GDMA_BURST_ITEMS_512,
|
||||
.burst_len = output_burst_len,
|
||||
},
|
||||
.dst = {
|
||||
.width = DW_GDMA_TRANS_WIDTH_64,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.burst_items = DW_GDMA_BURST_ITEMS_512,
|
||||
.burst_len = output_burst_len,
|
||||
},
|
||||
.size = ctx->output_frame_size_64bit,
|
||||
};
|
||||
ctx->dma_in_trans = (dw_gdma_block_transfer_config_t) {
|
||||
.src = {
|
||||
.width = DW_GDMA_TRANS_WIDTH_64,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.burst_items = DW_GDMA_BURST_ITEMS_32,
|
||||
},
|
||||
.dst = {
|
||||
.addr = MIPI_CSI_BRG_MEM_BASE,
|
||||
.width = DW_GDMA_TRANS_WIDTH_64,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_FIXED,
|
||||
.burst_items = s_isp_dma_burst_len_to_items(proc->dma_in_burst_len),
|
||||
},
|
||||
.size = ctx->input_frame_size_64bit,
|
||||
};
|
||||
|
||||
dw_gdma_event_callbacks_t dma_cbs = {
|
||||
.on_full_trans_done = s_isp_dma_done_cb,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(ctx->dma_out_chan, &dma_cbs, &ctx->out_done_sem), err, TAG, "register output dma callback failed");
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(ctx->dma_in_chan, &dma_cbs, &ctx->in_done_sem), err, TAG, "register input dma callback failed");
|
||||
|
||||
proc->dma_frame_ctx = ctx;
|
||||
return ESP_OK;
|
||||
err:
|
||||
s_isp_dma_frame_ctx_destroy(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void isp_dma_del_frame_ctx(isp_proc_handle_t proc)
|
||||
{
|
||||
if (!proc || !proc->dma_frame_ctx) {
|
||||
return;
|
||||
}
|
||||
s_isp_dma_frame_ctx_destroy(proc->dma_frame_ctx);
|
||||
proc->dma_frame_ctx = NULL;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dma_process_frame(isp_proc_handle_t proc, void *output_buffer, const void *input_buffer, uint32_t timeout_ms)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(proc && proc->dma_frame_ctx && output_buffer && input_buffer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE((((uintptr_t)output_buffer) % 8) == 0, ESP_ERR_INVALID_ARG, TAG, "output buffer not 8-byte aligned");
|
||||
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;
|
||||
|
||||
ESP_RETURN_ON_ERROR(dw_gdma_channel_config_transfer(ctx->dma_out_chan, &ctx->dma_out_trans), TAG, "configure output transfer failed");
|
||||
ESP_RETURN_ON_ERROR(dw_gdma_channel_config_transfer(ctx->dma_in_chan, &ctx->dma_in_trans), TAG, "configure input transfer failed");
|
||||
dw_gdma_channel_enable_ctrl(ctx->dma_out_chan, true);
|
||||
dw_gdma_channel_enable_ctrl(ctx->dma_in_chan, true);
|
||||
|
||||
isp_ll_dma_trigger_frame(ctx->proc->hal.hw);
|
||||
|
||||
if (xSemaphoreTake(ctx->in_done_sem, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "wait input done timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
if (xSemaphoreTake(ctx->out_done_sem, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "wait output done timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -409,6 +409,8 @@ static inline void dw_gdma_ll_channel_set_dst_master_port(dw_gdma_dev_t *dev, ui
|
||||
{
|
||||
if (mem_addr == MIPI_DSI_BRG_MEM_BASE) {
|
||||
dev->ch[channel].ctl0.dms = DW_GDMA_LL_MASTER_PORT_MIPI_DSI;
|
||||
} else if (mem_addr == MIPI_CSI_BRG_MEM_BASE) {
|
||||
dev->ch[channel].ctl0.dms = DW_GDMA_LL_MASTER_PORT_MIPI_CSI;
|
||||
} else {
|
||||
dev->ch[channel].ctl0.dms = DW_GDMA_LL_MASTER_PORT_MEMORY;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ extern "C" {
|
||||
#define ISP_LL_GET_HW(num) (((num) == 0) ? (&ISP) : NULL)
|
||||
|
||||
#define ISP_LL_HSIZE_MAX 1920
|
||||
#define ISP_LL_VSIZE_MAX 1080
|
||||
#define ISP_LL_VSIZE_MAX 1280
|
||||
|
||||
/*---------------------------------------------------------------
|
||||
Clock
|
||||
@@ -529,6 +529,82 @@ static inline void isp_ll_enable_line_end_packet_exist(isp_dev_t *hw, bool en)
|
||||
hw->frame_cfg.hsync_end_exist = en;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set DMA input data type
|
||||
*
|
||||
* @param[in] hw Hardware instance address
|
||||
* @param[in] format color format, see `isp_color_t`
|
||||
*
|
||||
* @return true for valid format, false for invalid format
|
||||
*/
|
||||
static inline bool isp_ll_dma_set_data_type(isp_dev_t *hw, isp_color_t format)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
switch (format) {
|
||||
case ISP_COLOR_RAW8:
|
||||
hw->dma_cntl.dma_data_type = 0x2A;
|
||||
valid = true;
|
||||
break;
|
||||
case ISP_COLOR_RAW10:
|
||||
hw->dma_cntl.dma_data_type = 0x2B;
|
||||
valid = true;
|
||||
break;
|
||||
case ISP_COLOR_RAW12:
|
||||
hw->dma_cntl.dma_data_type = 0x2C;
|
||||
valid = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set DMA input burst length in units of 64-bit
|
||||
*
|
||||
* @param[in] hw Hardware instance address
|
||||
* @param[in] burst_len Number of 64-bit beats in one DMA burst
|
||||
*/
|
||||
static inline void isp_ll_dma_set_burst_len(isp_dev_t *hw, uint32_t burst_len)
|
||||
{
|
||||
hw->dma_cntl.dma_burst_len = burst_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set DMA input total frame size in units of 64-bit
|
||||
*
|
||||
* @param[in] hw Hardware instance address
|
||||
* @param[in] num64b Number of 64-bit words in one frame
|
||||
*/
|
||||
static inline void isp_ll_dma_set_frame_size(isp_dev_t *hw, uint32_t num64b)
|
||||
{
|
||||
hw->dma_raw_data.dma_raw_num_total = num64b;
|
||||
hw->dma_raw_data.dma_raw_num_total_set = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply DMA input registers
|
||||
*
|
||||
* @param[in] hw Hardware instance address
|
||||
*/
|
||||
static inline void isp_ll_dma_apply_config(isp_dev_t *hw)
|
||||
{
|
||||
hw->dma_cntl.dma_update_reg = 1;
|
||||
while (hw->dma_cntl.dma_update_reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger one DMA input frame transfer
|
||||
*
|
||||
* @param[in] hw Hardware instance address
|
||||
*/
|
||||
static inline void isp_ll_dma_trigger_frame(isp_dev_t *hw)
|
||||
{
|
||||
hw->dma_cntl.dma_en = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get if demosaic is enabled
|
||||
*
|
||||
|
||||
@@ -43,6 +43,7 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_demosaic.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_sharpen.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_core.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_dma.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_gamma.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_hist.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_isp/include/driver/isp_color.h \
|
||||
|
||||
@@ -996,6 +996,10 @@ To embed a file into a project, rather than a component, you can call the functi
|
||||
|
||||
Place this line after the ``project()`` line in your project CMakeLists.txt file. Replace ``myproject.elf`` with your project name. The final argument can be ``TEXT`` to embed a null-terminated string, or ``BINARY`` to embed the content as-is.
|
||||
|
||||
Use the optional ``ALIGN`` argument to align the embedded data's start symbol to a positive power of two. For example, to align binary data to 16 bytes::
|
||||
|
||||
target_add_binary_data(myproject.elf "main/data.bin" BINARY ALIGN 16)
|
||||
|
||||
For an example of using this technique, see the "main" component of the file_serving example :example_file:`protocols/http_server/file_serving/main/CMakeLists.txt` - two files are loaded at build time and linked into the firmware.
|
||||
|
||||
.. highlight:: cmake
|
||||
|
||||
@@ -67,6 +67,7 @@ The ISP driver offers following services:
|
||||
|
||||
- :ref:`isp-resource-allocation` - covers how to allocate ISP resources with properly set of configurations. It also covers how to recycle the resources when they finished working.
|
||||
- :ref:`isp-enable-disable` - covers how to enable and disable an ISP processor.
|
||||
- :ref:`isp-dma-input` - covers how to feed image frames stored in memory into the ISP through DW-GDMA.
|
||||
- :ref:`isp-af-statistics` - covers how to get AF statistics one-shot or continuously.
|
||||
- :ref:`isp-awb-statistics` - covers how to get AWB white patches statistics one-shot or continuously.
|
||||
- :ref:`isp-ae-statistics` - covers how to get AE statistics one-shot or continuously.
|
||||
@@ -240,6 +241,15 @@ Before doing ISP pipeline, you need to enable the ISP processor first, by callin
|
||||
|
||||
Calling :cpp:func:`esp_isp_disable` does the opposite, that is, put the driver back to the **init** state.
|
||||
|
||||
.. _isp-dma-input:
|
||||
|
||||
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. 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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -847,7 +857,6 @@ Calling :cpp:func:`esp_isp_crop_disable` does the opposite, that is, put the dri
|
||||
- The crop region cannot exceed the boundaries of the original image
|
||||
- Adjust the display medium (such as LCD) size according to the cropped resolution to ensure complete display and avoid black borders or stretching.
|
||||
|
||||
|
||||
.. _isp-callback:
|
||||
|
||||
Register Event Callbacks
|
||||
@@ -957,6 +966,8 @@ Application Examples
|
||||
--------------------
|
||||
|
||||
* :example:`peripherals/isp/multi_pipelines` demonstrates how to use the ISP pipelines to process the image signals from camera sensors and display the video on LCD screen via DSI peripheral.
|
||||
* :example:`peripherals/isp/dma_input` demonstrates how to feed a RAW8 BGGR image in memory into the ISP through DW-GDMA. ``pytest_isp_dma_input.py`` saves the processed RGB888 frames as PPM images and compares them pixel by pixel with the checked-in golden image.
|
||||
* `esp_video/examples <https://github.com/espressif/esp-video-components/tree/master/esp_video/examples>`_ provides some examples of enabling ISP control algorithms.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
@@ -976,5 +987,6 @@ API Reference
|
||||
.. include-build-file:: inc/isp_color.inc
|
||||
.. include-build-file:: inc/isp_crop.inc
|
||||
.. include-build-file:: inc/isp_core.inc
|
||||
.. include-build-file:: inc/isp_dma.inc
|
||||
.. include-build-file:: inc/components/esp_driver_isp/include/driver/isp_types.inc
|
||||
.. include-build-file:: inc/components/hal/include/hal/isp_types.inc
|
||||
|
||||
@@ -996,6 +996,10 @@ CMake 文件可以使用 ``IDF_TARGET`` 变量来获取当前的硬件目标。
|
||||
|
||||
并将这行代码放在项目 CMakeLists.txt 的 ``project()`` 命令之后,修改 ``myproject.elf`` 为你自己的项目名。如果最后一个参数是 ``TEXT``,那么构建系统会嵌入以 null 结尾的字符串,如果最后一个参数被设置为 ``BINARY``,则将文件内容按照原样嵌入。
|
||||
|
||||
可选的 ``ALIGN`` 参数用于将嵌入数据的起始符号对齐到指定的正整数 2 的幂。例如,将二进制数据按 16 字节对齐::
|
||||
|
||||
target_add_binary_data(myproject.elf "main/data.bin" BINARY ALIGN 16)
|
||||
|
||||
有关使用此技术的示例,请查看 file_serving 示例 :example_file:`protocols/http_server/file_serving/main/CMakeLists.txt` 中的 main 组件,两个文件会在编译时加载并链接到固件中。
|
||||
|
||||
.. highlight:: cmake
|
||||
|
||||
@@ -67,6 +67,7 @@ ISP 驱动程序提供以下服务:
|
||||
|
||||
- :ref:`isp-resource-allocation` - 涵盖如何通过正确的配置来分配 ISP 资源,以及完成工作后如何回收资源。
|
||||
- :ref:`isp-enable-disable` - 涵盖如何启用和禁用 ISP 处理器。
|
||||
- :ref:`isp-dma-input` - 涵盖如何通过 DW-GDMA 将存储在内存中的图像帧送入 ISP。
|
||||
- :ref:`isp-af-statistics` - 涵盖如何单次或连续获取 AF 统计信息。
|
||||
- :ref:`isp-awb-statistics` - 涵盖如何单次或连续获取 AWB 白块统计信息。
|
||||
- :ref:`isp-ae-statistics` - 涵盖如何单次或连续获取 AE 统计信息。
|
||||
@@ -240,6 +241,15 @@ ISP
|
||||
|
||||
调用 :cpp:func:`esp_isp_disable` 函数会执行相反的操作,即将驱动程序恢复到 **init** 状态。
|
||||
|
||||
.. _isp-dma-input:
|
||||
|
||||
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 的缓冲区:输入使用允许未对齐的 cache writeback;带 cache 的输出地址及其派生帧大小必须按 cache line 大小对齐。
|
||||
|
||||
ISP AF 控制器
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
@@ -956,6 +966,8 @@ Kconfig 选项 :ref:`CONFIG_ISP_CTRL_FUNC_IN_IRAM` 支持:
|
||||
--------
|
||||
|
||||
* :example:`peripherals/isp/multi_pipelines` 演示了如何使用 ISP 流水线处理来自摄像头传感器的图像信号,并通过 DSI 外设在 LCD 屏幕上显示视频。
|
||||
* :example:`peripherals/isp/dma_input` 演示了如何通过 DW-GDMA 将内存中的 RAW8 BGGR 图像送入 ISP。``pytest_isp_dma_input.py`` 会将处理后的 RGB888 帧保存为 PPM 图片,并与示例中提交的 golden 图片进行逐像素比较。
|
||||
* `esp_video/examples <https://github.com/espressif/esp-video-components/tree/master/esp_video/examples>`_ 中包含自动启用 ISP 控制算法的一些示例。
|
||||
|
||||
API 参考
|
||||
--------
|
||||
@@ -975,5 +987,6 @@ API 参考
|
||||
.. include-build-file:: inc/isp_color.inc
|
||||
.. include-build-file:: inc/isp_crop.inc
|
||||
.. include-build-file:: inc/isp_core.inc
|
||||
.. include-build-file:: inc/isp_dma.inc
|
||||
.. include-build-file:: inc/components/esp_driver_isp/include/driver/isp_types.inc
|
||||
.. include-build-file:: inc/components/hal/include/hal/isp_types.inc
|
||||
|
||||
@@ -171,6 +171,13 @@ examples/peripherals/i2s/i2s_recorder:
|
||||
- esp_driver_spi
|
||||
- esp_driver_i2s
|
||||
|
||||
examples/peripherals/isp/dma_input:
|
||||
disable:
|
||||
- if: SOC_ISP_SUPPORTED != 1
|
||||
depends_components:
|
||||
- esp_driver_isp
|
||||
- soc
|
||||
|
||||
examples/peripherals/isp/multi_pipelines:
|
||||
disable:
|
||||
- if: SOC_MIPI_CSI_SUPPORTED != 1
|
||||
@@ -515,7 +522,6 @@ examples/peripherals/uart/uart_dma_ota:
|
||||
- if: SOC_UHCI_SUPPORTED != 1
|
||||
depends_components:
|
||||
- esp_driver_uart
|
||||
- esp_driver_dma
|
||||
- app_update
|
||||
- esp_ringbuf
|
||||
- soc
|
||||
|
||||
8
examples/peripherals/isp/dma_input/CMakeLists.txt
Normal file
8
examples/peripherals/isp/dma_input/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(isp_dma_input)
|
||||
44
examples/peripherals/isp/dma_input/README.md
Normal file
44
examples/peripherals/isp/dma_input/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
| Supported Targets | ESP32-P4 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# ISP DMA Input Visual Test Example
|
||||
|
||||
## Overview
|
||||
|
||||
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 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 output buffer from PSRAM).
|
||||
|
||||
## How to Use
|
||||
|
||||
Run the test locally. It builds and flashes the example, captures the serial output, and saves one PPM artifact per frame:
|
||||
|
||||
```
|
||||
cd examples/peripherals/isp/dma_input
|
||||
pytest pytest_isp_dma_input.py --target esp32p4 --port PORT
|
||||
```
|
||||
|
||||
The pytest log directory (`dut.logdir`) contains `isp_dma_input_frame00.ppm`, the decoded RGB888 ISP output from the current hardware run. The repository includes [golden/golden.ppm](golden/golden.ppm), the checked-in reference image. The test compares the decoded RGB888 pixels with this reference image, making image-quality regressions visible in review and detectable in CI.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example processes one embedded 240 x 280 BGGR RAW8 frame.
|
||||
|
||||
```text
|
||||
Feeding 1 frames through ISP DMA input...
|
||||
IMAGE_META frame=0 width=240 height=280 format=BGR24 encoding=base64
|
||||
IMAGE_BASE64_BEGIN
|
||||
IMAGE_BASE64 ...
|
||||
IMAGE_BASE64_END
|
||||
Frame 0 done
|
||||
ISP DMA visual demo done.
|
||||
```
|
||||
BIN
examples/peripherals/isp/dma_input/golden/golden.ppm
Normal file
BIN
examples/peripherals/isp/dma_input/golden/golden.ppm
Normal file
Binary file not shown.
7
examples/peripherals/isp/dma_input/main/CMakeLists.txt
Normal file
7
examples/peripherals/isp/dma_input/main/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
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" ALIGN 16)
|
||||
File diff suppressed because one or more lines are too long
122
examples/peripherals/isp/dma_input/main/isp_dma_example_main.c
Normal file
122
examples/peripherals/isp/dma_input/main/isp_dma_example_main.c
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mbedtls/base64.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "driver/isp_dma.h"
|
||||
#include "driver/isp_core.h"
|
||||
#include "driver/isp_color.h"
|
||||
|
||||
#define EXAMPLE_WIDTH 240
|
||||
#define EXAMPLE_HEIGHT 280
|
||||
#define EXAMPLE_BASE64_CHUNK_LEN 384
|
||||
#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 example_configure_neutral_color(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
esp_isp_color_config_t color_cfg = {
|
||||
.color_contrast = { .integer = 1, .decimal = 0 },
|
||||
.color_saturation = { .integer = 1, .decimal = 0 },
|
||||
.color_hue = 0,
|
||||
.color_brightness = 0,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_isp_color_configure(isp_proc, &color_cfg));
|
||||
ESP_ERROR_CHECK(esp_isp_color_enable(isp_proc));
|
||||
}
|
||||
|
||||
static void example_print_base64_payload(const unsigned char *encoded, size_t encoded_len)
|
||||
{
|
||||
printf("IMAGE_BASE64_BEGIN\n");
|
||||
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]);
|
||||
if ((++chunk_count % 16) == 0) {
|
||||
/* Let the test host drain the UART without delaying every chunk. */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
printf("IMAGE_BASE64_END\n");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
const uint32_t h_res = EXAMPLE_WIDTH;
|
||||
const uint32_t v_res = EXAMPLE_HEIGHT;
|
||||
const size_t in_size = (size_t)h_res * v_res; // RAW8: 1 byte/pixel
|
||||
const size_t out_size = (size_t)h_res * v_res * 3; // RGB888: 3 bytes/pixel
|
||||
|
||||
isp_proc_handle_t isp_proc = NULL;
|
||||
esp_isp_processor_cfg_t isp_cfg = {
|
||||
.clk_hz = 240 * 1000 * 1000,
|
||||
.input_data_source = ISP_INPUT_DATA_SOURCE_DWGDMA,
|
||||
.input_data_color_type = ISP_COLOR_RAW8,
|
||||
.output_data_color_type = ISP_COLOR_RGB888,
|
||||
.bayer_order = COLOR_RAW_ELEMENT_ORDER_BGGR,
|
||||
.has_line_start_packet = false,
|
||||
.has_line_end_packet = false,
|
||||
.h_res = h_res,
|
||||
.v_res = v_res,
|
||||
.dma_burst_size = 8,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_isp_new_processor(&isp_cfg, &isp_proc));
|
||||
ESP_ERROR_CHECK(esp_isp_enable(isp_proc));
|
||||
example_configure_neutral_color(isp_proc);
|
||||
|
||||
/* 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);
|
||||
|
||||
size_t encoded_len = 0;
|
||||
int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, isp_out_buf, out_size);
|
||||
ESP_ERROR_CHECK((ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) ? ESP_OK : ESP_FAIL);
|
||||
unsigned char *encoded = calloc(encoded_len + 1, 1);
|
||||
assert(encoded);
|
||||
|
||||
printf("Feeding %d frames through ISP DMA input...\n", EXAMPLE_FRAME_COUNT);
|
||||
for (int frame = 0; frame < EXAMPLE_FRAME_COUNT; frame++) {
|
||||
/*
|
||||
* 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);
|
||||
example_print_base64_payload(encoded, out_len);
|
||||
printf("Frame %d done\n", frame);
|
||||
}
|
||||
printf("ISP DMA visual demo done.\n");
|
||||
|
||||
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));
|
||||
free(encoded);
|
||||
free(isp_out_buf);
|
||||
}
|
||||
182
examples/peripherals/isp/dma_input/pytest_isp_dma_input.py
Normal file
182
examples/peripherals/isp/dma_input/pytest_isp_dma_input.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import base64
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
IMAGE_META_PATTERN = (
|
||||
r'IMAGE_META frame=(?P<frame>\d+) width=(?P<width>\d+) height=(?P<height>\d+) '
|
||||
r'format=(?P<format>\w+) encoding=(?P<encoding>\w+)'
|
||||
)
|
||||
IMAGE_META_RE = re.compile(IMAGE_META_PATTERN)
|
||||
IMAGE_CHUNK_PATTERN = (
|
||||
r'IMAGE_BASE64 (?P<payload>[A-Za-z0-9+/=]+?)'
|
||||
r'(?=\r?\n|IMAGE_BASE64|IMAGE_BASE64_END|Frame )'
|
||||
)
|
||||
IMAGE_OUTPUT_TEMPLATE = 'isp_dma_input_frame{frame:02d}.ppm'
|
||||
REFERENCE_IMAGE_PATH = Path(__file__).parent / 'golden' / 'golden.ppm'
|
||||
EXPECTED_PIXEL_FORMAT = 'BGR24'
|
||||
EXPECTED_ENCODING = 'base64'
|
||||
RGB888_BYTES_PER_PIXEL = 3
|
||||
PPM_MAGIC = b'P6'
|
||||
PPM_MAX_VALUE = b'255'
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ImageMetadata:
|
||||
frame: int
|
||||
width: int
|
||||
height: int
|
||||
pixel_format: str
|
||||
encoding: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RgbImage:
|
||||
width: int
|
||||
height: int
|
||||
pixels_rgb888: bytes
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
expected_size = self.width * self.height * RGB888_BYTES_PER_PIXEL
|
||||
if len(self.pixels_rgb888) != expected_size:
|
||||
raise ValueError(f'Expected {expected_size} RGB bytes, got {len(self.pixels_rgb888)}')
|
||||
|
||||
|
||||
def parse_image_metadata(meta_line: str) -> ImageMetadata:
|
||||
match = IMAGE_META_RE.fullmatch(meta_line)
|
||||
if not match:
|
||||
raise ValueError(f'Invalid image metadata line: {meta_line}')
|
||||
|
||||
return ImageMetadata(
|
||||
frame=int(match.group('frame')),
|
||||
width=int(match.group('width')),
|
||||
height=int(match.group('height')),
|
||||
pixel_format=match.group('format'),
|
||||
encoding=match.group('encoding'),
|
||||
)
|
||||
|
||||
|
||||
def collect_base64_payload(dut: Dut) -> List[str]:
|
||||
payload_lines: List[str] = []
|
||||
while True:
|
||||
match = dut.expect(rf'IMAGE_BASE64_END|{IMAGE_CHUNK_PATTERN}', timeout=60)
|
||||
if match.group(0).decode('utf-8') == 'IMAGE_BASE64_END':
|
||||
return payload_lines
|
||||
|
||||
payload_lines.append(match.group('payload').decode('utf-8'))
|
||||
|
||||
|
||||
def _bgr24_to_rgb888(raw_bytes: bytes) -> bytes:
|
||||
rgb_bytes = bytearray(len(raw_bytes))
|
||||
for offset in range(0, len(raw_bytes), 3):
|
||||
blue, green, red = raw_bytes[offset : offset + 3]
|
||||
rgb_bytes[offset : offset + 3] = (red, green, blue)
|
||||
return bytes(rgb_bytes)
|
||||
|
||||
|
||||
def _encode_ppm(image: RgbImage) -> bytes:
|
||||
header = b'%s\n%d %d\n%s\n' % (PPM_MAGIC, image.width, image.height, PPM_MAX_VALUE)
|
||||
return header + image.pixels_rgb888
|
||||
|
||||
|
||||
def decode_bgr24_base64_image(metadata: ImageMetadata, payload_lines: List[str]) -> RgbImage:
|
||||
if metadata.pixel_format != EXPECTED_PIXEL_FORMAT:
|
||||
raise ValueError(f'Unsupported pixel format: {metadata.pixel_format}')
|
||||
if metadata.encoding != EXPECTED_ENCODING:
|
||||
raise ValueError(f'Unsupported payload encoding: {metadata.encoding}')
|
||||
|
||||
raw_bytes = base64.b64decode(''.join(payload_lines), validate=True)
|
||||
expected_size = metadata.width * metadata.height * RGB888_BYTES_PER_PIXEL
|
||||
if len(raw_bytes) != expected_size:
|
||||
raise ValueError(f'Expected {expected_size} decoded bytes, got {len(raw_bytes)}')
|
||||
|
||||
return RgbImage(width=metadata.width, height=metadata.height, pixels_rgb888=_bgr24_to_rgb888(raw_bytes))
|
||||
|
||||
|
||||
def save_ppm_artifact(image: RgbImage, output_path: Path) -> None:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
output_path.write_bytes(_encode_ppm(image))
|
||||
except OSError:
|
||||
logging.exception('Failed to save ISP DMA artifact to %s', output_path)
|
||||
return
|
||||
|
||||
logging.info('Saved ISP DMA artifact to %s', output_path)
|
||||
|
||||
|
||||
def load_ppm_image(image_path: Path) -> RgbImage:
|
||||
ppm_data = image_path.read_bytes()
|
||||
try:
|
||||
magic, dimensions, max_value, pixels_rgb888 = ppm_data.split(b'\n', 3)
|
||||
width, height = (int(value) for value in dimensions.split())
|
||||
except ValueError as error:
|
||||
raise ValueError(f'Invalid PPM image: {image_path}') from error
|
||||
|
||||
if magic != PPM_MAGIC or max_value != PPM_MAX_VALUE:
|
||||
raise ValueError(f'Unsupported PPM image: {image_path}')
|
||||
|
||||
return RgbImage(width=width, height=height, pixels_rgb888=pixels_rgb888)
|
||||
|
||||
|
||||
def assert_image_matches_reference(result_image: RgbImage, reference_image: RgbImage) -> None:
|
||||
assert (result_image.width, result_image.height) == (reference_image.width, reference_image.height), (
|
||||
f'ISP output dimensions do not match reference image: '
|
||||
f'{result_image.width}x{result_image.height} != {reference_image.width}x{reference_image.height}'
|
||||
)
|
||||
|
||||
if result_image.pixels_rgb888 == reference_image.pixels_rgb888:
|
||||
return
|
||||
|
||||
mismatch_offset = next(
|
||||
offset
|
||||
for offset, (actual, expected) in enumerate(zip(result_image.pixels_rgb888, reference_image.pixels_rgb888))
|
||||
if actual != expected
|
||||
)
|
||||
pixel_index, channel = divmod(mismatch_offset, RGB888_BYTES_PER_PIXEL)
|
||||
raise AssertionError(
|
||||
f'ISP output does not match reference image at pixel {pixel_index}, channel {channel}: '
|
||||
f'{result_image.pixels_rgb888[mismatch_offset]} != {reference_image.pixels_rgb888[mismatch_offset]}'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_ISP_SUPPORTED == 1'), indirect=['target'])
|
||||
def test_isp_dma_input_example(dut: Dut) -> None:
|
||||
reference_image = load_ppm_image(REFERENCE_IMAGE_PATH)
|
||||
frame_count_match = dut.expect(r'Feeding (?P<frame_count>\d+) frames through ISP DMA input...')
|
||||
expected_frame_count = int(frame_count_match.group('frame_count').decode('utf-8'))
|
||||
logging.info('Expecting %d ISP DMA frame(s)', expected_frame_count)
|
||||
|
||||
for expected_frame in range(expected_frame_count):
|
||||
metadata = parse_image_metadata(dut.expect(IMAGE_META_PATTERN).group(0).decode('utf-8'))
|
||||
assert metadata.frame == expected_frame, f'Out-of-order frame: expected {expected_frame}, got {metadata.frame}'
|
||||
logging.info(
|
||||
'Received frame metadata: frame=%d size=%dx%d format=%s',
|
||||
metadata.frame,
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
metadata.pixel_format,
|
||||
)
|
||||
dut.expect_exact('IMAGE_BASE64_BEGIN')
|
||||
logging.info('Receiving base64 image payload for frame %d', metadata.frame)
|
||||
payload_lines = collect_base64_payload(dut)
|
||||
logging.info('Received %d base64 chunk(s) for frame %d', len(payload_lines), metadata.frame)
|
||||
|
||||
result_image = decode_bgr24_base64_image(metadata, payload_lines)
|
||||
logging.info('Decoded frame %d to RGB888 image', metadata.frame)
|
||||
output_path = Path(dut.logdir) / IMAGE_OUTPUT_TEMPLATE.format(frame=metadata.frame)
|
||||
save_ppm_artifact(result_image, output_path)
|
||||
assert_image_matches_reference(result_image, reference_image)
|
||||
dut.expect_exact(f'Frame {expected_frame} done')
|
||||
|
||||
dut.expect_exact('ISP DMA visual demo done.')
|
||||
1
examples/peripherals/isp/dma_input/sdkconfig.defaults
Normal file
1
examples/peripherals/isp/dma_input/sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
@@ -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.
|
||||
|
||||
@@ -56,8 +56,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.
|
||||
|
||||
@@ -74,6 +74,9 @@ append_line(".data")
|
||||
append_line("#if !defined (__APPLE__) && !defined (__linux__)")
|
||||
append_line(".section .rodata.embedded")
|
||||
append_line("#endif")
|
||||
if(DEFINED DATA_ALIGNMENT)
|
||||
append_line(".balign ${DATA_ALIGNMENT}")
|
||||
endif()
|
||||
make_and_append_identifier("${varname}")
|
||||
make_and_append_identifier("_binary_${varname}_start" "for objcopy compatibility")
|
||||
append("${data}")
|
||||
|
||||
@@ -75,9 +75,10 @@ endfunction()
|
||||
|
||||
# target_add_binary_data adds binary data into the built target,
|
||||
# by converting it to a generated source file which is then compiled
|
||||
# to a binary object as part of the build
|
||||
# to a binary object as part of the build. ALIGN optionally sets the
|
||||
# alignment of the embedded data's start symbol.
|
||||
function(target_add_binary_data target embed_file embed_type)
|
||||
cmake_parse_arguments(_ "" "RENAME_TO" "DEPENDS" ${ARGN})
|
||||
cmake_parse_arguments(_ "" "RENAME_TO;ALIGN" "DEPENDS" ${ARGN})
|
||||
idf_build_get_property(build_dir BUILD_DIR)
|
||||
idf_build_get_property(idf_path IDF_PATH)
|
||||
|
||||
@@ -91,11 +92,24 @@ function(target_add_binary_data target embed_file embed_type)
|
||||
set(rename_to_arg -D "VARIABLE_BASENAME=${__RENAME_TO}")
|
||||
endif()
|
||||
|
||||
set(align_arg)
|
||||
if(DEFINED __ALIGN)
|
||||
if(NOT __ALIGN MATCHES "^[1-9][0-9]*$")
|
||||
message(FATAL_ERROR "ALIGN must be a positive integer")
|
||||
endif()
|
||||
math(EXPR alignment_mask "${__ALIGN} & (${__ALIGN} - 1)")
|
||||
if(NOT alignment_mask EQUAL 0)
|
||||
message(FATAL_ERROR "ALIGN must be a power of two")
|
||||
endif()
|
||||
set(align_arg -D "DATA_ALIGNMENT=${__ALIGN}")
|
||||
endif()
|
||||
|
||||
add_custom_command(OUTPUT "${embed_srcfile}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "DATA_FILE=${embed_file}"
|
||||
-D "SOURCE_FILE=${embed_srcfile}"
|
||||
${rename_to_arg}
|
||||
${align_arg}
|
||||
-D "FILE_TYPE=${embed_type}"
|
||||
-P "${idf_path}/tools/cmake/scripts/data_file_embed_asm.cmake"
|
||||
MAIN_DEPENDENCY "${embed_file}"
|
||||
|
||||
Reference in New Issue
Block a user