From b5c40535efc86c2ea78b7f6e342a8be4cd91458c Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Tue, 18 Nov 2025 11:00:48 +0800 Subject: [PATCH] refactor(isp): split isp pipeline example into different srcs --- .../isp/multi_pipelines/main/CMakeLists.txt | 11 +- .../isp/multi_pipelines/main/example_af.c | 226 ++++++ .../isp/multi_pipelines/main/example_af.h | 89 +++ .../isp/multi_pipelines/main/example_buffer.c | 60 ++ .../isp/multi_pipelines/main/example_buffer.h | 101 +++ .../isp/multi_pipelines/main/example_config.h | 2 + .../isp/multi_pipelines/main/example_crop.c | 199 ++++++ .../isp/multi_pipelines/main/example_crop.h | 60 ++ .../multi_pipelines/main/example_pipelines.c | 508 +++++++++++++ .../multi_pipelines/main/example_pipelines.h | 60 ++ .../isp/multi_pipelines/main/isp_dsi_main.c | 674 +++--------------- 11 files changed, 1418 insertions(+), 572 deletions(-) create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_af.c create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_af.h create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_buffer.c create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_buffer.h create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_crop.c create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_crop.h create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_pipelines.c create mode 100644 examples/peripherals/isp/multi_pipelines/main/example_pipelines.h diff --git a/examples/peripherals/isp/multi_pipelines/main/CMakeLists.txt b/examples/peripherals/isp/multi_pipelines/main/CMakeLists.txt index 48af6aee24..e0f71b79ad 100644 --- a/examples/peripherals/isp/multi_pipelines/main/CMakeLists.txt +++ b/examples/peripherals/isp/multi_pipelines/main/CMakeLists.txt @@ -1,4 +1,13 @@ -idf_component_register(SRCS "isp_dsi_main.c" +set(srcs "isp_dsi_main.c" + "example_buffer.c" + "example_af.c" + "example_pipelines.c") + +if(CONFIG_EXAMPLE_ISP_CROP_ENABLE) + list(APPEND srcs "example_crop.c") +endif() + +idf_component_register(SRCS ${srcs} INCLUDE_DIRS "." REQUIRES esp_mm esp_driver_isp esp_driver_cam esp_driver_i2c esp_lcd dsi_init sensor_init ) diff --git a/examples/peripherals/isp/multi_pipelines/main/example_af.c b/examples/peripherals/isp/multi_pipelines/main/example_af.c new file mode 100644 index 0000000000..9cac0e3b26 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_af.c @@ -0,0 +1,226 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "example_af.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/isp_af.h" +#include "isp_af_scheme_sa.h" +#include "esp_sccb_intf.h" +#include "example_config.h" + +static const char *TAG = "isp_af"; + +// DW9714 register structure +typedef union { + struct { + uint16_t s : 4; + uint16_t d : 10; + uint16_t flag : 1; + uint16_t pd : 1; + }; + struct { + uint16_t byte2 : 8; + uint16_t byte1 : 8; + }; + uint16_t val; +} dw9714_reg_t; + +static example_isp_af_task_param_t s_af_task_param = {0}; +static TaskHandle_t s_af_task_handle = NULL; + +/** + * @brief AF environment change callback (called from ISR) + */ +static bool IRAM_ATTR s_af_env_change_cb(isp_af_ctlr_t af_ctrlr, const esp_isp_af_env_detector_evt_data_t *edata, void *user_data) +{ + BaseType_t mustYield = pdFALSE; + TaskHandle_t task_handle = (TaskHandle_t)user_data; + vTaskNotifyGiveFromISR(task_handle, &mustYield); + return (mustYield == pdTRUE); +} + +/** + * @brief Set focus value to DW9714 VCM + */ +static esp_err_t s_sensor_set_focus_val(int focus_val, void *arg) +{ + esp_sccb_io_handle_t dw9714_io_handle = arg; + + dw9714_reg_t reg = {0}; + reg.d = (uint16_t)((focus_val / 120.0) * 1023.0); + + uint8_t data[2] = {0}; + data[0] = reg.byte1; + data[1] = reg.byte2; + + uint16_t reg_addr = (data[0] << 8) + (data[1]); + uint8_t reg_val = 0; + + esp_err_t ret = esp_sccb_transmit_reg_a16v8(dw9714_io_handle, reg_addr, reg_val); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "dw9714 esp_sccb_transmit_reg_a16v8 failed"); + return ret; + } + + return ESP_OK; +} + +/** + * @brief AF task - continuously monitors and adjusts focus + */ +static void example_af_task(void *arg) +{ + TaskHandle_t task_handle = xTaskGetCurrentTaskHandle(); + example_isp_af_task_param_t af_task_param = *(example_isp_af_task_param_t *)arg; + + // Default AF window configuration (centered window) + int window_size = 100; + int edge_thresh = 128; + int env_interval = 10; + int first_step_val = 12; + int first_approx_cycles = 10; + int second_step_val = 2; + int second_approx_cycles = 10; + int focus_val_max = 120; + + // Use default values from example_config if available + int h_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES; + int v_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES; + + /** + * AF window configuration + * Windows for ISP hardware to record the luminance and definition + */ + esp_isp_af_config_t af_config = { + .window = { + [0] = { + .top_left = { + .x = (h_res / 2) - window_size, + .y = (v_res / 2) - window_size, + }, + .btm_right = { + .x = (h_res / 2) + window_size - 1, + .y = (v_res / 2) + window_size - 1, + }, + }, + [1] = { + .top_left = { + .x = (h_res / 2) - window_size, + .y = (v_res / 2) - window_size, + }, + .btm_right = { + .x = (h_res / 2) + window_size - 1, + .y = (v_res / 2) + window_size - 1, + }, + }, + [2] = { + .top_left = { + .x = (h_res / 2) - window_size, + .y = (v_res / 2) - window_size, + }, + .btm_right = { + .x = (h_res / 2) + window_size - 1, + .y = (v_res / 2) + window_size - 1, + }, + }, + }, + .edge_thresh = edge_thresh, + }; + + isp_af_ctlr_t af_ctrlr = NULL; + ESP_ERROR_CHECK(esp_isp_new_af_controller(af_task_param.isp_proc, &af_config, &af_ctrlr)); + + // Configure environment detector + esp_isp_af_env_config_t env_config = { + .interval = env_interval, + }; + ESP_ERROR_CHECK(esp_isp_af_controller_set_env_detector(af_ctrlr, &env_config)); + + // Register environment change callback + esp_isp_af_env_detector_evt_cbs_t cbs = { + .on_env_change = s_af_env_change_cb, + }; + ESP_ERROR_CHECK(esp_isp_af_env_detector_register_event_callbacks(af_ctrlr, &cbs, task_handle)); + + // Create SA (Step-and-Approach) scheme + isp_af_sa_scheme_config_t af_scheme_config = { + .first_step_val = first_step_val, + .first_approx_cycles = first_approx_cycles, + .second_step_val = second_step_val, + .second_approx_cycles = second_approx_cycles, + }; + isp_af_scheme_handle_t af_scheme = NULL; + ESP_ERROR_CHECK(isp_af_create_sa_scheme(af_ctrlr, &af_scheme_config, &af_scheme)); + + // Register sensor driver + isp_af_sa_scheme_sensor_drv_t sensor_driver = { + .af_sensor_set_focus = s_sensor_set_focus_val, + }; + isp_af_sa_scheme_sensor_info_t sensor_info = { + .focus_val_max = focus_val_max, + }; + ESP_ERROR_CHECK(isp_af_sa_scheme_register_sensor_driver(af_scheme, &sensor_driver, &sensor_info, af_task_param.dw9714_io_handle)); + + // Enable AF controller + int definition_thresh = 0; + int luminance_thresh = 0; + ESP_ERROR_CHECK(esp_isp_af_controller_enable(af_ctrlr)); + + ESP_LOGI(TAG, "AF task started"); + + // Main AF loop + while (1) { + // Wait for environment change notification + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + // Process AF: adjust focus based on definition and luminance + ESP_ERROR_CHECK(isp_af_process(af_scheme, &definition_thresh, &luminance_thresh)); + + // Update environment detector thresholds + ESP_ERROR_CHECK(esp_isp_af_controller_set_env_detector_threshold(af_ctrlr, definition_thresh, luminance_thresh)); + } +} + +esp_err_t example_isp_af_init(isp_proc_handle_t isp_proc, + esp_sccb_io_handle_t dw9714_io_handle, + const example_isp_af_config_t *config) +{ + if (isp_proc == NULL || dw9714_io_handle == NULL) { + ESP_LOGE(TAG, "Invalid arguments"); + return ESP_ERR_INVALID_ARG; + } + + s_af_task_param.isp_proc = isp_proc; + s_af_task_param.dw9714_io_handle = dw9714_io_handle; + + ESP_LOGI(TAG, "AF module initialized"); + return ESP_OK; +} + +esp_err_t example_isp_af_start(int task_priority, int core_id) +{ + if (s_af_task_param.isp_proc == NULL) { + ESP_LOGE(TAG, "AF not initialized, call example_isp_af_init() first"); + return ESP_ERR_INVALID_STATE; + } + + BaseType_t ret = xTaskCreatePinnedToCore(example_af_task, + "af_task", + 8192, + &s_af_task_param, + task_priority, + &s_af_task_handle, + core_id); + if (ret != pdPASS) { + ESP_LOGE(TAG, "Failed to create AF task"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "AF task created"); + return ESP_OK; +} diff --git a/examples/peripherals/isp/multi_pipelines/main/example_af.h b/examples/peripherals/isp/multi_pipelines/main/example_af.h new file mode 100644 index 0000000000..4fd52b6d0f --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_af.h @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file example_af.h + * @brief Auto Focus (AF) Module + * + * This module implements ISP auto focus functionality using hardware AF controller + * and software focus algorithm. + * + * How it works: + * - ISP hardware statistics luminance and definition (sharpness) in specified AF windows + * - Environment detector triggers callback when environment changes are detected + * - Focus algorithm adjusts lens focus based on definition values + * - Uses Step-and-Approach (SA) search algorithm to find optimal focus point + * + * This example uses DW9714 VCM (Voice Coil Motor) to drive lens focus. + */ + +#pragma once + +#include "esp_err.h" +#include "driver/isp.h" +#include "esp_sccb_intf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief AF task parameters + */ +typedef struct { + isp_proc_handle_t isp_proc; // ISP processor handle + esp_sccb_io_handle_t dw9714_io_handle; // DW9714 VCM SCCB handle +} example_isp_af_task_param_t; + +/** + * @brief AF configuration structure + */ +typedef struct { + int window_size; // AF window size (centered window) + int edge_thresh; // Edge threshold for definition calculation + int env_interval; // Environment detector interval (frames) + int first_step_val; // First step value for SA scheme + int first_approx_cycles; // First approximation cycles + int second_step_val; // Second step value for SA scheme + int second_approx_cycles; // Second approximation cycles + int focus_val_max; // Maximum focus value +} example_isp_af_config_t; + +/** + * @brief Initialize AF module + * + * This function creates and configures the AF controller, environment detector, + * and SA (Step-and-Approach) scheme. + * + * @param[in] isp_proc ISP processor handle + * @param[in] dw9714_io_handle DW9714 VCM SCCB handle + * @param[in] config AF configuration (can be NULL for default values) + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid arguments + * - ESP_ERR_NO_MEM: Out of memory + */ +esp_err_t example_isp_af_init(isp_proc_handle_t isp_proc, + esp_sccb_io_handle_t dw9714_io_handle, + const example_isp_af_config_t *config); + +/** + * @brief Start AF task + * + * This function creates a FreeRTOS task to handle AF processing. + * The task will continuously monitor focus and adjust when environment changes. + * + * @param[in] task_priority Task priority + * @param[in] core_id Core ID to pin the task + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid arguments + */ +esp_err_t example_isp_af_start(int task_priority, int core_id); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/isp/multi_pipelines/main/example_buffer.c b/examples/peripherals/isp/multi_pipelines/main/example_buffer.c new file mode 100644 index 0000000000..4c37d55632 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_buffer.c @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "example_buffer.h" +#include "esp_log.h" +#include "esp_lcd_panel_ops.h" +#include "example_config.h" +#include "esp_check.h" + +static const char *TAG = "isp_buffer"; + +esp_err_t example_isp_buffer_init(example_pingpong_buffer_ctx_t *ctx, + void *fb0, + void *fb1, + esp_lcd_panel_handle_t panel, + int h_res, + int v_res) +{ + ESP_RETURN_ON_FALSE(ctx != NULL && fb0 != NULL && fb1 != NULL && panel != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + + ctx->fb0 = fb0; + ctx->fb1 = fb1; + ctx->csi_buffer = fb0; // CSI starts writing to fb0 + ctx->dsi_buffer = fb1; // DSI starts displaying fb1 + ctx->panel = panel; + ctx->h_res = h_res; + ctx->v_res = v_res; + + return ESP_OK; +} + +void example_isp_buffer_swap(example_pingpong_buffer_ctx_t *ctx) +{ + if (ctx == NULL) { + return; + } + void *temp = ctx->csi_buffer; + ctx->csi_buffer = ctx->dsi_buffer; + ctx->dsi_buffer = temp; +} + +void *example_isp_buffer_get_csi_buffer(example_pingpong_buffer_ctx_t *ctx) +{ + if (ctx == NULL) { + return NULL; + } + return ctx->csi_buffer; +} + +void *example_isp_buffer_get_dsi_buffer(example_pingpong_buffer_ctx_t *ctx) +{ + if (ctx == NULL) { + return NULL; + } + return ctx->dsi_buffer; +} diff --git a/examples/peripherals/isp/multi_pipelines/main/example_buffer.h b/examples/peripherals/isp/multi_pipelines/main/example_buffer.h new file mode 100644 index 0000000000..00906cd7a9 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_buffer.h @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file example_buffer.h + * @brief Ping-Pong Buffer Management Module + * + * This module implements a ping-pong buffer management mechanism for synchronizing + * data between CSI image capture and DSI display. + * + * Why use dual buffers? + * - Ensure smooth display: Avoid frame tearing caused by different rates of CSI and DSI transfer + * + * How it works: + * - Two buffers (fb0 and fb1) are used alternately + * - CSI writes to the buffer pointed by csi_buffer + * - DSI reads and displays from the buffer pointed by dsi_buffer + * - After each frame is completed, the roles of the two buffers are swapped + */ + +#pragma once + +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_lcd_panel_ops.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Ping-Pong buffer context structure + */ +typedef struct { + void *fb0; // Frame buffer 0 + void *fb1; // Frame buffer 1 + void *csi_buffer; // Current buffer for CSI to write + void *dsi_buffer; // Current buffer for DSI to display + esp_lcd_panel_handle_t panel;// DPI panel handle + int h_res; // Horizontal resolution + int v_res; // Vertical resolution (full screen) +#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE + int crop_h_res; // Cropped horizontal resolution + int crop_v_res; // Cropped vertical resolution + void *pending_buffer; // Buffer pending to be displayed + SemaphoreHandle_t frame_ready_sem; // Semaphore to signal frame ready +#endif +} example_pingpong_buffer_ctx_t; + +/** + * @brief Initialize ping-pong buffer context + * + * @param[in] ctx Pointer to ping-pong buffer context + * @param[in] fb0 Pointer to frame buffer 0 + * @param[in] fb1 Pointer to frame buffer 1 + * @param[in] panel LCD panel handle + * @param[in] h_res Horizontal resolution + * @param[in] v_res Vertical resolution + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid arguments + */ +esp_err_t example_isp_buffer_init(example_pingpong_buffer_ctx_t *ctx, + void *fb0, + void *fb1, + esp_lcd_panel_handle_t panel, + int h_res, + int v_res); + +/** + * @brief Swap CSI write buffer and DSI display buffer + * + * @param[in,out] ctx Pointer to ping-pong buffer context + */ +void example_isp_buffer_swap(example_pingpong_buffer_ctx_t *ctx); + +/** + * @brief Get current CSI write buffer + * + * @param[in] ctx Pointer to ping-pong buffer context + * @return Pointer to current CSI buffer + */ +void *example_isp_buffer_get_csi_buffer(example_pingpong_buffer_ctx_t *ctx); + +/** + * @brief Get current DSI display buffer + * + * @param[in] ctx Pointer to ping-pong buffer context + * @return Pointer to current DSI buffer + */ +void *example_isp_buffer_get_dsi_buffer(example_pingpong_buffer_ctx_t *ctx); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/isp/multi_pipelines/main/example_config.h b/examples/peripherals/isp/multi_pipelines/main/example_config.h index f4ca7636f0..084353c54e 100644 --- a/examples/peripherals/isp/multi_pipelines/main/example_config.h +++ b/examples/peripherals/isp/multi_pipelines/main/example_config.h @@ -6,6 +6,8 @@ #pragma once +#include "sdkconfig.h" + #ifdef __cplusplus extern "C" { #endif diff --git a/examples/peripherals/isp/multi_pipelines/main/example_crop.c b/examples/peripherals/isp/multi_pipelines/main/example_crop.c new file mode 100644 index 0000000000..350acdd3f4 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_crop.c @@ -0,0 +1,199 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_lcd_panel_ops.h" +#include "esp_check.h" +#include "example_crop.h" +#include "example_buffer.h" +#include "example_config.h" + +static const char *TAG = "example_crop"; + +esp_err_t example_isp_crop_init(example_pingpong_buffer_ctx_t *ctx, int crop_h_res, int crop_v_res) +{ + ESP_RETURN_ON_FALSE(ctx != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + + ctx->crop_h_res = crop_h_res; + ctx->crop_v_res = crop_v_res; + ctx->pending_buffer = NULL; + ctx->frame_ready_sem = xSemaphoreCreateBinary(); + ESP_RETURN_ON_FALSE(ctx->frame_ready_sem != NULL, ESP_ERR_NO_MEM, TAG, "Failed to create frame ready semaphore"); + + return ESP_OK; +} + +/** + * @brief Process frame: Add blank areas to fill full screen resolution + * + * Algorithm: Fill from bottom to top to avoid overwriting crop data + * - Cropped image is placed at original position (relative to full frame) + * - Other areas are filled with white (0xFFFF for RGB565) + * + * Illustration of crop operation: + * + * @verbatim + * Full Frame (h_res x v_res) Cropped Area + * ┌─────────────────────────┐ ┌───────────────────────┐ + * │(0,0) │ │(crop_left, │ + * │ Blank Area │ │ crop_top) │ + * │ ┌──────────────┐ │ │ │ + * │ │ │ │ ===> │ │ + * │ │ CROP Area │ │ │ CROP Area │ + * │ │ │ │ │ │ + * │ └──────────────┘ │ │ │ + * │ (h_res, │ │ (crop_right, │ + * │ v_res) │ │ crop_bottom)│ + * └─────────────────────────┘ └───────────────────────┘ + * crop_width x crop_height + * @endverbatim + * + * @param[in,out] buffer Frame buffer to process (contains cropped image at start) + * @param[in] ctx Ping-pong buffer context + * + * @note This function is necessary to ensure that the display matches the cropped image resolution + */ +static void example_isp_process_frame_with_blanks(void *buffer, example_pingpong_buffer_ctx_t *ctx) +{ + if (ctx == NULL || buffer == NULL) { + return; + } + + if (ctx->crop_v_res == ctx->v_res) { + // No cropping, no need to process + return; + } + + uint16_t *fb = (uint16_t *)buffer; + + const int crop_left = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H; + const int crop_top = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V; + const int crop_right = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H; + const int crop_bottom = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V; + const int crop_width = crop_right - crop_left + 1; + + const int full_width = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES; + const int full_height = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES; + + // Helper macros for pixel indexing +#define SRC_PIXEL(x, y) fb[(y) * crop_width + (x)] +#define DST_PIXEL(x, y) fb[(y) * full_width + (x)] + + // ========== Step 1: Fill bottom blank region [crop_bottom+1, full_height) ========== + if (crop_bottom + 1 < full_height) { + memset(&DST_PIXEL(0, crop_bottom + 1), + 0xFF, + full_width * (full_height - crop_bottom - 1) * EXAMPLE_RGB565_BYTES_PER_PIXEL); + } + + // ========== Step 2: Process cropped region [crop_top, crop_bottom] ========== + for (int y = crop_bottom; y >= crop_top; y--) { + int src_y = y - crop_top; // Corresponding row in cropped data (0-based) + + // Fill right blank region first (crop_right+1, full_width) + if (crop_right + 1 < full_width) { + memset(&DST_PIXEL(crop_right + 1, y), + 0xFF, + (full_width - crop_right - 1) * EXAMPLE_RGB565_BYTES_PER_PIXEL); + } + + // Copy crop data from source to destination + memcpy(&DST_PIXEL(crop_left, y), + &SRC_PIXEL(0, src_y), + crop_width * EXAMPLE_RGB565_BYTES_PER_PIXEL); + + // Fill left blank region [0, crop_left) + if (crop_left > 0) { + memset(&DST_PIXEL(0, y), + 0xFF, + crop_left * EXAMPLE_RGB565_BYTES_PER_PIXEL); + } + } + + // ========== Step 3: Fill top blank region [0, crop_top) ========== + if (crop_top > 0) { + memset(&DST_PIXEL(0, 0), + 0xFF, + full_width * crop_top * EXAMPLE_RGB565_BYTES_PER_PIXEL); + } + +#undef SRC_PIXEL +#undef DST_PIXEL +} + +/** + * @brief Frame processing task + * + * This task processes frames when CROP is enabled: + * - Waits for frame ready signal from ISR + * - Processes the frame (adds blank areas if needed) + * - Swaps buffers + * - Triggers display update + */ +static void example_frame_processing_task(void *arg) +{ + example_pingpong_buffer_ctx_t *ctx = (example_pingpong_buffer_ctx_t *)arg; + + while (1) { + // Wait for frame ready signal from ISR + if (xSemaphoreTake(ctx->frame_ready_sem, portMAX_DELAY) == pdTRUE) { + // Process the frame: add blank areas if needed + example_isp_process_frame_with_blanks(ctx->pending_buffer, ctx); + + // Ping-Pong switch: swap CSI write buffer and DSI display buffer + example_isp_buffer_swap(ctx); + + // Trigger buffer switch by calling draw_bitmap + // DPI driver will detect which buffer we're using and switch to it + esp_err_t ret = esp_lcd_panel_draw_bitmap(ctx->panel, + 0, 0, + ctx->h_res, + ctx->crop_v_res, + ctx->pending_buffer); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to draw bitmap: %d", ret); + } + + ESP_LOGD(TAG, "Frame displayed: %p", ctx->pending_buffer); + } + } +} + +esp_err_t example_isp_crop_start_frame_processing_task(example_pingpong_buffer_ctx_t *ctx, + int task_priority, + int core_id) +{ + if (ctx == NULL) { + return ESP_ERR_INVALID_ARG; + } + + BaseType_t ret = xTaskCreatePinnedToCore(example_frame_processing_task, + "frame_proc", + 4096, + ctx, + task_priority, + NULL, + core_id); + if (ret != pdPASS) { + ESP_LOGE(TAG, "Failed to create frame processing task"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Frame processing task created"); + return ESP_OK; +} + +bool example_isp_crop_frame_ready_routine(example_pingpong_buffer_ctx_t *ctx, void *buffer) +{ + if (ctx == NULL || buffer == NULL) { + return false; + } + + BaseType_t high_task_wakeup = pdFALSE; + ctx->pending_buffer = buffer; + xSemaphoreGiveFromISR(ctx->frame_ready_sem, &high_task_wakeup); + return (high_task_wakeup == pdTRUE); +} diff --git a/examples/peripherals/isp/multi_pipelines/main/example_crop.h b/examples/peripherals/isp/multi_pipelines/main/example_crop.h new file mode 100644 index 0000000000..f8fcdc7bb7 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_crop.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +// SPDX-License-Identifier: Apache-2.0 +// +// ESP-IDF header for cropping configuration (example_crop.h) + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sdkconfig.h" +#include "example_buffer.h" + +#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE + +/** + * @brief Initialize cropping configuration + * + * @param[in] ctx Pointer to ping-pong buffer context + * @param[in] crop_h_res Cropped horizontal resolution + * @param[in] crop_v_res Cropped vertical resolution + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid arguments + * - ESP_ERR_NO_MEM: Failed to create semaphore (if CROP enabled) + */ +esp_err_t example_isp_crop_init(example_pingpong_buffer_ctx_t *ctx, int crop_h_res, int crop_v_res); +/** + * @brief Start frame processing task + * + * This task handles frame processing and buffer swapping when CROP is enabled. + * + * @param[in] ctx Pointer to ping-pong buffer context + * @param[in] task_priority Task priority + * @param[in] core_id Core ID to pin the task + * @return + * - ESP_OK: Success + * - ESP_FAIL: Failed to create frame processing task + * - ESP_ERR_INVALID_ARG: Invalid arguments + */ +esp_err_t example_isp_crop_start_frame_processing_task(example_pingpong_buffer_ctx_t *ctx, + int task_priority, + int core_id); + +/** + * @brief Signal that a frame is ready for processing + * + * This function should be called from ISR context when a frame is ready. + * + * @param[in,out] ctx Pointer to ping-pong buffer context + * @param[in] buffer Pointer to the completed frame buffer + * @return true if a higher priority task was woken + */ +bool example_isp_crop_frame_ready_routine(example_pingpong_buffer_ctx_t *ctx, void *buffer); +#endif // CONFIG_EXAMPLE_ISP_CROP_ENABLE + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/isp/multi_pipelines/main/example_pipelines.c b/examples/peripherals/isp/multi_pipelines/main/example_pipelines.c new file mode 100644 index 0000000000..5247582e89 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_pipelines.c @@ -0,0 +1,508 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "example_pipelines.h" +#include "esp_log.h" +#include "driver/isp.h" +#include "example_config.h" + +static const char *TAG = "isp_pipeline"; + +/** + * @brief Default gamma correction curve + */ +static uint32_t s_gamma_correction_curve(uint32_t x) +{ + return pow((double)x / 256, 0.7) * 256; +} + +esp_err_t example_create_isp_processor(const esp_isp_processor_cfg_t *config, isp_proc_handle_t *isp_proc) +{ + if (config == NULL || isp_proc == NULL) { + ESP_LOGE(TAG, "Invalid arguments"); + return ESP_ERR_INVALID_ARG; + } + + esp_err_t ret = esp_isp_new_processor(config, isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to create ISP processor: %d", ret); + return ret; + } + + ret = esp_isp_enable(*isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable ISP processor: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "ISP processor initialized"); + return ESP_OK; +} + +/** + * @brief Configure BLC (Black Level Correction) module + */ +static esp_err_t example_isp_pipelines_config_blc(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + +#if CONFIG_ESP32P4_REV_MIN_FULL >= 300 + /** + * This piece of BLC code is to show how to use the BLC related APIs. + * Suggested way to calibrate the BLC is by covering the lens and record the raw data. + * Then, use the recorded data to calibrate the BLC. + */ + esp_isp_blc_config_t blc_config = { + .window = { + .top_left = { + .x = 0, + .y = 0, + }, + .btm_right = { + .x = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, + .y = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES, + }, + }, + .filter_enable = true, + .filter_threshold = { + .top_left_chan_thresh = 128, + .top_right_chan_thresh = 128, + .bottom_left_chan_thresh = 128, + .bottom_right_chan_thresh = 128, + }, + .stretch = { + .top_left_chan_stretch_en = true, + .top_right_chan_stretch_en = true, + .bottom_left_chan_stretch_en = true, + .bottom_right_chan_stretch_en = true, + }, + }; + + esp_err_t ret = esp_isp_blc_configure(isp_proc, &blc_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure BLC: %d", ret); + return ret; + } + + ret = esp_isp_blc_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable BLC: %d", ret); + return ret; + } + + esp_isp_blc_offset_t blc_offset = { + .top_left_chan_offset = 20, + .top_right_chan_offset = 20, + .bottom_left_chan_offset = 20, + .bottom_right_chan_offset = 20, + }; + + ret = esp_isp_blc_set_correction_offset(isp_proc, &blc_offset); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set BLC offset: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "BLC module configured"); +#else + ESP_LOGW(TAG, "BLC not supported on this chip revision"); + return ESP_ERR_NOT_SUPPORTED; +#endif + + return ESP_OK; +} + +/** + * @brief Configure BF (Bayer Filter) module + */ +static esp_err_t example_isp_pipelines_config_bf(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_isp_bf_config_t bf_config = { + .denoising_level = 5, + .padding_mode = ISP_BF_EDGE_PADDING_MODE_SRND_DATA, + .bf_template = { + {1, 2, 1}, + {2, 4, 2}, + {1, 2, 1}, + }, + .padding_line_tail_valid_start_pixel = 0, + .padding_line_tail_valid_end_pixel = 0, + }; + + esp_err_t ret = esp_isp_bf_configure(isp_proc, &bf_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure BF: %d", ret); + return ret; + } + + ret = esp_isp_bf_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable BF: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "BF module configured"); + return ESP_OK; +} + +/** + * @brief Configure LSC (Lens Shading Correction) module + */ +static esp_err_t example_isp_pipelines_config_lsc(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + +#if CONFIG_ESP32P4_REV_MIN_FULL >= 100 + esp_isp_lsc_gain_array_t gain_array = {}; + esp_isp_lsc_config_t lsc_config = { + .gain_array = &gain_array, + }; + + size_t gain_size = 0; + esp_err_t ret = esp_isp_lsc_allocate_gain_array(isp_proc, &gain_array, &gain_size); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to allocate LSC gain array: %d", ret); + return ret; + } + + isp_lsc_gain_t gain_val = { + .decimal = 204, + .integer = 0, + }; + + for (int i = 0; i < gain_size; i++) { + gain_array.gain_r[i].val = gain_val.val; + gain_array.gain_gr[i].val = gain_val.val; + gain_array.gain_gb[i].val = gain_val.val; + gain_array.gain_b[i].val = gain_val.val; + } + + ret = esp_isp_lsc_configure(isp_proc, &lsc_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure LSC: %d", ret); + return ret; + } + + ret = esp_isp_lsc_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable LSC: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "LSC module configured"); +#else + ESP_LOGW(TAG, "LSC not supported on this chip revision"); + return ESP_ERR_NOT_SUPPORTED; +#endif + + return ESP_OK; +} + +/** + * @brief Configure DEMOSAIC module + */ +static esp_err_t example_isp_pipelines_config_demosaic(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_isp_demosaic_config_t demosaic_config = { + .grad_ratio = { + .integer = 2, + .decimal = 5, + }, + }; + + esp_err_t ret = esp_isp_demosaic_configure(isp_proc, &demosaic_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure DEMOSAIC: %d", ret); + return ret; + } + + ret = esp_isp_demosaic_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable DEMOSAIC: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "DEMOSAIC module configured"); + return ESP_OK; +} + +/** + * @brief Configure CCM (Color Correction Matrix) module + */ +static esp_err_t example_isp_pipelines_config_ccm(isp_proc_handle_t isp_proc, const float (*matrix)[3]) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /** + * CCM is used for color correction and white balance adjustment. + * It should be configured after demosaic and before gamma correction. + * + * The matrix format is: + * [R_out] [RR RG RB] [R_in] + * [G_out] = [GR GG GB] [G_in] + * [B_out] [BR BG BB] [B_in] + * + * For ESP32P4 ECO5: + * - Matrix coefficients range: ±15.996 (4-bit integer + 8-bit fraction) + * - For earlier versions: ±3.999 (2-bit integer + 10-bit fraction) + */ + esp_isp_ccm_config_t ccm_config = { + .matrix = { + // Default identity matrix (no color correction) + {1.0, 0.0, 0.0}, // R channel: R = 1.0*R + 0.0*G + 0.0*B + {0.0, 1.0, 0.0}, // G channel: G = 0.0*R + 1.0*G + 0.0*B + {0.0, 0.0, 1.0} // B channel: B = 0.0*R + 0.0*G + 1.0*B + }, + .saturation = false // Don't use saturation for out-of-range values + }; + + // Use provided matrix if available + if (matrix != NULL) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + ccm_config.matrix[i][j] = matrix[i][j]; + } + } + } + + esp_err_t ret = esp_isp_ccm_configure(isp_proc, &ccm_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure CCM: %d", ret); + return ret; + } + + ret = esp_isp_ccm_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable CCM: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "CCM module configured"); + return ESP_OK; +} + +/** + * @brief Configure GAMMA correction module + */ +static esp_err_t example_isp_pipelines_config_gamma(isp_proc_handle_t isp_proc, uint32_t (*gamma_curve)(uint32_t)) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // Use provided gamma curve or default + uint32_t (*curve_func)(uint32_t) = gamma_curve; + if (curve_func == NULL) { + curve_func = s_gamma_correction_curve; + } + + isp_gamma_curve_points_t pts = {}; + esp_err_t ret = esp_isp_gamma_fill_curve_points(curve_func, &pts); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to fill gamma curve points: %d", ret); + return ret; + } + + ret = esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_R, &pts); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure gamma R: %d", ret); + return ret; + } + + ret = esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_G, &pts); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure gamma G: %d", ret); + return ret; + } + + ret = esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_B, &pts); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure gamma B: %d", ret); + return ret; + } + + ret = esp_isp_gamma_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable gamma: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "GAMMA module configured"); + return ESP_OK; +} + +/** + * @brief Configure SHARPEN module + */ +static esp_err_t example_isp_pipelines_config_sharpen(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_isp_sharpen_config_t sharpen_config = { + .h_freq_coeff = { + .integer = 2, + .decimal = 0, + }, + .m_freq_coeff = { + .integer = 2, + .decimal = 0, + }, + .h_thresh = 255, + .l_thresh = 0, + .padding_mode = ISP_SHARPEN_EDGE_PADDING_MODE_SRND_DATA, + .sharpen_template = { + {1, 2, 1}, + {2, 4, 2}, + {1, 2, 1}, + }, + .padding_line_tail_valid_start_pixel = 0, + .padding_line_tail_valid_end_pixel = 0, + }; + + esp_err_t ret = esp_isp_sharpen_configure(isp_proc, &sharpen_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure SHARPEN: %d", ret); + return ret; + } + + ret = esp_isp_sharpen_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable SHARPEN: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "SHARPEN module configured"); + return ESP_OK; +} + +/** + * @brief Configure COLOR adjustment module + */ +static esp_err_t example_isp_pipelines_config_color(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_isp_color_config_t color_config = { + .color_contrast = { + .integer = 1, + .decimal = 0, + }, + .color_saturation = { + .integer = 1, + .decimal = 0, + }, + .color_hue = 0, + .color_brightness = 0, + }; + + esp_err_t ret = esp_isp_color_configure(isp_proc, &color_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure COLOR: %d", ret); + return ret; + } + + ret = esp_isp_color_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable COLOR: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "COLOR module configured"); + return ESP_OK; +} + +#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE + +/** + * @brief Configure CROP module + */ +static esp_err_t example_isp_pipelines_config_crop(isp_proc_handle_t isp_proc, + int crop_left, + int crop_top, + int crop_right, + int crop_bottom) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_isp_crop_config_t crop_config = { + .window = { + .top_left = { + .x = crop_left, + .y = crop_top + }, + .btm_right = { + .x = crop_right, + .y = crop_bottom + } + } + }; + + esp_err_t ret = esp_isp_crop_configure(isp_proc, &crop_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure CROP: %d", ret); + return ret; + } + + ret = esp_isp_crop_enable(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to enable CROP: %d", ret); + return ret; + } + + ESP_LOGI(TAG, "CROP module configured: (%d,%d) to (%d,%d)", + crop_left, crop_top, crop_right, crop_bottom); + + return ESP_OK; +} +#endif + +esp_err_t example_isp_init_all_pipelines(isp_proc_handle_t isp_proc) +{ + if (isp_proc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + ESP_ERROR_CHECK(example_isp_pipelines_config_blc(isp_proc)); + ESP_ERROR_CHECK(example_isp_pipelines_config_bf(isp_proc)); + ESP_ERROR_CHECK(example_isp_pipelines_config_lsc(isp_proc)); + ESP_ERROR_CHECK(example_isp_pipelines_config_demosaic(isp_proc)); + ESP_ERROR_CHECK(example_isp_pipelines_config_ccm(isp_proc, NULL)); + ESP_ERROR_CHECK(example_isp_pipelines_config_gamma(isp_proc, NULL)); + ESP_ERROR_CHECK(example_isp_pipelines_config_sharpen(isp_proc)); + ESP_ERROR_CHECK(example_isp_pipelines_config_color(isp_proc)); + +#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE + ESP_ERROR_CHECK(example_isp_pipelines_config_crop(isp_proc, + CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H, + CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V, + CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H, + CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V)); +#endif + + ESP_LOGI(TAG, "All ISP pipeline modules initialized"); + return ESP_OK; +} diff --git a/examples/peripherals/isp/multi_pipelines/main/example_pipelines.h b/examples/peripherals/isp/multi_pipelines/main/example_pipelines.h new file mode 100644 index 0000000000..80ff785462 --- /dev/null +++ b/examples/peripherals/isp/multi_pipelines/main/example_pipelines.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file example_pipeline.h + * @brief ISP Pipeline Processing Module + * + * This module implements configuration for various ISP image processing pipeline units, including: + * - BLC (Black Level Correction): Black level correction + * - BF (Bayer Filter): Denoising processing + * - LSC (Lens Shading Correction): Lens shading correction + * - DEMOSAIC: Demosaicing, converting RAW data to RGB + * - CCM (Color Correction Matrix): Color correction matrix + * - GAMMA: Gamma correction + * - SHARPEN: Sharpening processing + * - COLOR: Color adjustment (contrast, saturation, hue, brightness) + * - CROP: Image cropping + * + * These functions process image data sequentially according to the ISP hardware pipeline order. + */ + +#pragma once + +#include "esp_err.h" +#include "driver/isp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize ISP processor + * + * @param[in] config ISP processor configuration + * @param[out] isp_proc Output ISP processor handle + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid arguments + * - ESP_ERR_NO_MEM: Out of memory + */ +esp_err_t example_create_isp_processor(const esp_isp_processor_cfg_t *config, isp_proc_handle_t *isp_proc); + +/** + * @brief Initialize all ISP pipeline modules with default configuration + * + * This function configures all available ISP modules in the correct order. + * + * @param[in] isp_proc ISP processor handle + * @return + * - ESP_OK: Success + * - ESP_ERR_INVALID_ARG: Invalid arguments + */ +esp_err_t example_isp_init_all_pipelines(isp_proc_handle_t isp_proc); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c b/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c index 6a3df077fa..dde86cf4f7 100644 --- a/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c +++ b/examples/peripherals/isp/multi_pipelines/main/isp_dsi_main.c @@ -6,18 +6,13 @@ #include #include #include -#include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "esp_log.h" -#include "esp_heap_caps.h" -#include "esp_lcd_mipi_dsi.h" -#include "esp_lcd_panel_ops.h" -#include "esp_ldo_regulator.h" #include "esp_cache.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" #include "driver/i2c_master.h" -#include "driver/isp.h" -#include "isp_af_scheme_sa.h" #include "esp_cam_ctlr_csi.h" #include "esp_cam_ctlr.h" #include "esp_sccb_intf.h" @@ -28,292 +23,55 @@ #include "example_sensor_init.h" #include "example_config.h" +// Include modular ISP components +#include "example_buffer.h" +#include "example_af.h" +#include "example_pipelines.h" +#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE +#include "example_crop.h" +#endif + static const char *TAG = "isp_dsi"; -static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data); -static bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data); - -/*--------------------------------------------------------------- - Ping-Pong Buffer Management ----------------------------------------------------------------*/ -typedef struct { - void *fb0; // Frame buffer 0 - void *fb1; // Frame buffer 1 - void *csi_buffer; // Current buffer for CSI to write - void *dsi_buffer; // Current buffer for DSI to display - esp_lcd_panel_handle_t panel;// DPI panel handle - int h_res; // Horizontal resolution - int v_res; // Vertical resolution (full screen) -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - int crop_h_res; // Cropped horizontal resolution - int crop_v_res; // Cropped vertical resolution - void *pending_buffer; // Buffer pending to be displayed - SemaphoreHandle_t frame_ready_sem; // Semaphore to signal frame ready -#endif -} pingpong_buffer_ctx_t; - -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE /** - * @brief Process frame: Add blank areas to fill full screen resolution + * @brief Camera callback: Get new video buffer * - * Algorithm: Fill from bottom to top to avoid overwriting crop data - * - Cropped image is placed at original position (relative to full frame) - * - Other areas are filled with white (0xFFFF for RGB565) - * - * Example: Original 100x100, crop (50,50) to (100,100) → shows at bottom-right - * - * @param buffer Frame buffer to process (contains cropped image at start) - * @param ctx Ping-pong buffer context + * This callback is called when CSI needs a new buffer to write frame data. */ -static void process_frame_with_blanks(void *buffer, pingpong_buffer_ctx_t *ctx) +static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) { - if (ctx->crop_v_res == ctx->v_res) { - // No cropping, no need to process - return; - } + example_pingpong_buffer_ctx_t *ctx = (example_pingpong_buffer_ctx_t *)user_data; - uint16_t *fb = (uint16_t *)buffer; + // Provide the current CSI buffer for the next frame + trans->buffer = example_isp_buffer_get_csi_buffer(ctx); + trans->buflen = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL; - const int crop_left = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H; - const int crop_top = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V; - const int crop_right = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H; - const int crop_bottom = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V; - const int crop_width = crop_right - crop_left + 1; - - const int full_width = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES; - const int full_height = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES; - - // Helper macros for pixel indexing -#define SRC_PIXEL(x, y) fb[(y) * crop_width + (x)] -#define DST_PIXEL(x, y) fb[(y) * full_width + (x)] - - // ========== Step 1: Fill bottom blank region [crop_bottom+1, full_height) ========== - if (crop_bottom + 1 < full_height) { - memset(&DST_PIXEL(0, crop_bottom + 1), - 0xFF, - full_width * (full_height - crop_bottom - 1) * EXAMPLE_RGB565_BYTES_PER_PIXEL); - } - - // ========== Step 2: Process crop region [crop_top, crop_bottom] ========== - for (int y = crop_bottom; y >= crop_top; y--) { - int src_y = y - crop_top; // Corresponding row in cropped data (0-based) - - // Fill right blank region first (crop_right+1, full_width) - if (crop_right + 1 < full_width) { - memset(&DST_PIXEL(crop_right + 1, y), - 0xFF, - (full_width - crop_right - 1) * EXAMPLE_RGB565_BYTES_PER_PIXEL); - } - - // Copy crop data from source to destination - memcpy(&DST_PIXEL(crop_left, y), - &SRC_PIXEL(0, src_y), - crop_width * EXAMPLE_RGB565_BYTES_PER_PIXEL); - - // Fill left blank region [0, crop_left) - if (crop_left > 0) { - memset(&DST_PIXEL(0, y), - 0xFF, - crop_left * EXAMPLE_RGB565_BYTES_PER_PIXEL); - } - } - - // ========== Step 3: Fill top blank region [0, crop_top) ========== - if (crop_top > 0) { - memset(&DST_PIXEL(0, 0), - 0xFF, - full_width * crop_top * EXAMPLE_RGB565_BYTES_PER_PIXEL); - } - -#undef SRC_PIXEL -#undef DST_PIXEL -} -#endif - -/*--------------------------------------------------------------- - AF ----------------------------------------------------------------*/ -typedef union { - struct { - uint16_t s : 4; - uint16_t d : 10; - uint16_t flag : 1; - uint16_t pd : 1; - }; - struct { - uint16_t byte2 : 8; - uint16_t byte1 : 8; - }; - uint16_t val; -} dw9714_reg_t; - -static bool IRAM_ATTR s_af_env_change_cb(isp_af_ctlr_t af_ctrlr, const esp_isp_af_env_detector_evt_data_t *edata, void *user_data) -{ - BaseType_t mustYield = pdFALSE; - TaskHandle_t task_handle = (TaskHandle_t)user_data; - vTaskNotifyGiveFromISR(task_handle, &mustYield); - - return (mustYield == pdTRUE); + return false; } -static esp_err_t s_sensor_set_focus_val(int focus_val, void *arg) +/** + * @brief Camera callback: Frame transfer finished + * + * This callback is called when CSI finishes writing a frame. + * It handles buffer swapping and display update. + */ +static bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) { - esp_sccb_io_handle_t dw9714_io_handle = arg; - - dw9714_reg_t reg = {0}; - reg.d = (uint16_t)((focus_val / 120.0) * 1023.0); - - uint8_t data[2] = {0}; - data[0] = reg.byte1; - data[1] = reg.byte2; - - uint16_t reg_addr = (data[0] << 8) + (data[1]); - uint8_t reg_val = 0; - - esp_err_t ret = esp_sccb_transmit_reg_a16v8(dw9714_io_handle, reg_addr, reg_val); - if (ret != ESP_OK) { - printf("dw9714 esp_sccb_transmit_reg_a16v8 failed\n"); - return ret; - } - - return ESP_OK; -} - -static void af_task(void *arg) -{ - TaskHandle_t task_handle = xTaskGetCurrentTaskHandle(); - - typedef struct af_task_param_t { - isp_proc_handle_t isp_proc; - esp_sccb_io_handle_t dw9714_io_handle; - } af_task_param_t; - - af_task_param_t af_task_param = *(af_task_param_t *)arg; - - /** - * AF window, windows for ISP hardware to record the - * - luminance - * - definition - * of the current windows - */ - esp_isp_af_config_t af_config = { - .window = { - [0] = { - .top_left = { - .x = (CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES / 2) - 100, - .y = (CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES / 2) - 100, - }, - .btm_right = { - .x = (CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES / 2) + 99, - .y = (CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES / 2) + 99, - }, - }, - [1] = { - .top_left = { - .x = (CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES / 2) - 100, - .y = (CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES / 2) - 100, - }, - .btm_right = { - .x = (CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES / 2) + 99, - .y = (CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES / 2) + 99, - }, - }, - [2] = { - .top_left = { - .x = (CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES / 2) - 100, - .y = (CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES / 2) - 100, - }, - .btm_right = { - .x = (CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES / 2) + 99, - .y = (CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES / 2) + 99, - }, - }, - }, - .edge_thresh = 128, - }; - - isp_af_ctlr_t af_ctrlr = NULL; - ESP_ERROR_CHECK(esp_isp_new_af_controller(af_task_param.isp_proc, &af_config, &af_ctrlr)); - - esp_isp_af_env_config_t env_config = { - .interval = 10, - }; - ESP_ERROR_CHECK(esp_isp_af_controller_set_env_detector(af_ctrlr, &env_config)); - - esp_isp_af_env_detector_evt_cbs_t cbs = { - .on_env_change = s_af_env_change_cb, - }; - ESP_ERROR_CHECK(esp_isp_af_env_detector_register_event_callbacks(af_ctrlr, &cbs, task_handle)); - - isp_af_sa_scheme_config_t af_scheme_config = { - .first_step_val = 12, - .first_approx_cycles = 10, - .second_step_val = 2, - .second_approx_cycles = 10, - }; - isp_af_scheme_handle_t af_scheme = NULL; - ESP_ERROR_CHECK(isp_af_create_sa_scheme(af_ctrlr, &af_scheme_config, &af_scheme)); - - isp_af_sa_scheme_sensor_drv_t sensor_driver = { - .af_sensor_set_focus = s_sensor_set_focus_val, - }; - isp_af_sa_scheme_sensor_info_t sensor_info = { - .focus_val_max = 120, - }; - ESP_ERROR_CHECK(isp_af_sa_scheme_register_sensor_driver(af_scheme, &sensor_driver, &sensor_info, af_task_param.dw9714_io_handle)); - - int definition_thresh = 0; - int luminance_thresh = 0; - ESP_ERROR_CHECK(esp_isp_af_controller_enable(af_ctrlr)); - - while (1) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - ESP_ERROR_CHECK(isp_af_process(af_scheme, &definition_thresh, &luminance_thresh)); - ESP_ERROR_CHECK(esp_isp_af_controller_set_env_detector_threshold(af_ctrlr, definition_thresh, luminance_thresh)); - } -} - -/*--------------------------------------------------------------- - Gamma Correction ----------------------------------------------------------------*/ - -static uint32_t s_gamma_correction_curve(uint32_t x) -{ - return pow((double)x / 256, 0.7) * 256; -} + example_pingpong_buffer_ctx_t *ctx = (example_pingpong_buffer_ctx_t *)user_data; #ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE -/*--------------------------------------------------------------- - Frame Processing Task ----------------------------------------------------------------*/ -static void frame_processing_task(void *arg) -{ - pingpong_buffer_ctx_t *ctx = (pingpong_buffer_ctx_t *)arg; - - while (1) { - // Wait for frame ready signal from ISR - if (xSemaphoreTake(ctx->frame_ready_sem, portMAX_DELAY) == pdTRUE) { - // Process the frame: add blank areas if needed - process_frame_with_blanks(ctx->pending_buffer, ctx); - - // Ping-Pong switch: swap CSI write buffer and DSI display buffer - void *temp = ctx->csi_buffer; - ctx->csi_buffer = ctx->dsi_buffer; - ctx->dsi_buffer = temp; - - // Trigger buffer switch by calling draw_bitmap - // DPI driver will detect which buffer we're using and switch to it - ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(ctx->panel, - 0, 0, - ctx->h_res, - ctx->crop_v_res, - ctx->pending_buffer)); - - ESP_LOGD(TAG, "Frame displayed: %p", ctx->pending_buffer); - } + // Signal frame ready for processing (handled by frame processing task) + return example_isp_crop_frame_ready_routine(ctx, trans->buffer); +#else + // Simple ping-pong swap and display + example_isp_buffer_swap(ctx); + esp_err_t ret = esp_lcd_panel_draw_bitmap(ctx->panel, 0, 0, ctx->h_res, ctx->v_res, trans->buffer); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to draw bitmap: %d", ret); } + return false; +#endif } -#endif // CONFIG_EXAMPLE_ISP_CROP_ENABLE void app_main(void) { @@ -325,7 +83,7 @@ void app_main(void) void *fb1 = NULL; size_t frame_buffer_size = 0; - //mipi ldo + //---------------MIPI LDO Init------------------// esp_ldo_channel_handle_t ldo_mipi_phy = NULL; esp_ldo_channel_config_t ldo_mipi_phy_config = { .chan_id = CONFIG_EXAMPLE_USED_LDO_CHAN_ID, @@ -344,15 +102,11 @@ void app_main(void) }; example_dsi_resource_alloc(&dsi_alloc_config, &mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &fb0, &fb1); - //---------------Necessary variable config------------------// + //---------------Resolution Configuration------------------// int display_h_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES; int display_v_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES; - -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - // Use cropped resolution for frame buffer - display_h_res = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H - CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H + 1; - display_v_res = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V - CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V + 1; -#endif + int crop_h_res = display_h_res; + int crop_v_res = display_v_res; frame_buffer_size = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL; @@ -360,36 +114,36 @@ void app_main(void) ESP_LOGI(TAG, "Display resolution: %dx%d, bits per pixel: %d", display_h_res, display_v_res, EXAMPLE_RGB565_BITS_PER_PIXEL); ESP_LOGI(TAG, "Frame buffers: fb0=%p, fb1=%p", fb0, fb1); - //---------------Ping-Pong Buffer Context------------------// - pingpong_buffer_ctx_t pp_ctx = { - .fb0 = fb0, - .fb1 = fb1, - .csi_buffer = fb0, // CSI starts writing to fb0 - .dsi_buffer = fb1, // DSI starts displaying fb1 - .panel = mipi_dpi_panel, - .h_res = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES, - .v_res = CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - .crop_h_res = display_h_res, - .crop_v_res = display_v_res, - .pending_buffer = NULL, - .frame_ready_sem = xSemaphoreCreateBinary(), -#endif - }; + //---------------Ping-Pong Buffer Init------------------// + example_pingpong_buffer_ctx_t pp_ctx = {0}; + ret = example_isp_buffer_init(&pp_ctx, fb0, fb1, mipi_dpi_panel, + CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES, + CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize buffer: %d", ret); + return; + } #ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - if (pp_ctx.frame_ready_sem == NULL) { - ESP_LOGE(TAG, "Failed to create frame ready semaphore"); + // Use cropped resolution for frame buffer if crop is enabled + crop_h_res = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H - CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H + 1; + crop_v_res = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V - CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V + 1; + + ret = example_isp_crop_init(&pp_ctx, crop_h_res, crop_v_res); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize cropping: %d", ret); + return; + } + + // Start frame processing task + ret = example_isp_crop_start_frame_processing_task(&pp_ctx, 6, 0); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start frame processing task: %d", ret); return; } #endif - esp_cam_ctlr_trans_t new_trans = { - .buffer = pp_ctx.csi_buffer, - .buflen = frame_buffer_size, - }; - - //--------Camera Sensor and SCCB Init-----------// + //---------------Camera Sensor and SCCB Init------------------// example_sensor_handle_t sensor_handle = { .sccb_handle = NULL, .i2c_bus_handle = NULL, @@ -415,8 +169,8 @@ void app_main(void) //---------------CSI Init------------------// esp_cam_ctlr_csi_config_t csi_config = { .ctlr_id = 0, - .h_res = display_h_res, - .v_res = display_v_res, + .h_res = crop_h_res, + .v_res = crop_v_res, .lane_bit_rate_mbps = EXAMPLE_MIPI_CSI_LANE_BITRATE_MBPS, .input_data_color_type = CAM_CTLR_COLOR_RAW8, .output_data_color_type = CAM_CTLR_COLOR_RGB565, @@ -427,7 +181,7 @@ void app_main(void) esp_cam_ctlr_handle_t handle = NULL; ret = esp_cam_new_csi_ctlr(&csi_config, &handle); if (ret != ESP_OK) { - ESP_LOGE(TAG, "csi init fail[%d]", ret); + ESP_LOGE(TAG, "CSI init fail[%d]", ret); return; } @@ -436,14 +190,13 @@ void app_main(void) .on_trans_finished = s_camera_get_finished_trans, }; if (esp_cam_ctlr_register_event_callbacks(handle, &cbs, &pp_ctx) != ESP_OK) { - ESP_LOGE(TAG, "ops register fail"); + ESP_LOGE(TAG, "Camera callbacks register fail"); return; } ESP_ERROR_CHECK(esp_cam_ctlr_enable(handle)); - /*--------------------------------------------------------------- - ISP Init - ---------------------------------------------------------------*/ + + //---------------ISP Processor Init------------------// isp_proc_handle_t isp_proc = NULL; esp_isp_processor_cfg_t isp_config = { .clk_hz = 80 * 1000 * 1000, @@ -455,279 +208,58 @@ void app_main(void) .h_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, .v_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES, }; - ESP_ERROR_CHECK(esp_isp_new_processor(&isp_config, &isp_proc)); - ESP_ERROR_CHECK(esp_isp_enable(isp_proc)); - - /*--------------------------------------------------------------- - BF - ---------------------------------------------------------------*/ - esp_isp_bf_config_t bf_config = { - .denoising_level = 5, - .padding_mode = ISP_BF_EDGE_PADDING_MODE_SRND_DATA, - .bf_template = { - {1, 2, 1}, - {2, 4, 2}, - {1, 2, 1}, - }, - .padding_line_tail_valid_start_pixel = 0, - .padding_line_tail_valid_end_pixel = 0, - }; - ESP_ERROR_CHECK(esp_isp_bf_configure(isp_proc, &bf_config)); - ESP_ERROR_CHECK(esp_isp_bf_enable(isp_proc)); - - /*--------------------------------------------------------------- - BLC - ---------------------------------------------------------------*/ -#if CONFIG_ESP32P4_REV_MIN_FULL >= 300 - /** - * This piece of BLC code is to show how to use the BLC related APIs. - * Suggested way to calibrate the BLC is by covering the lens and record the raw data. - * Then, use the recorded data to calibrate the BLC. - */ - esp_isp_blc_config_t blc_config = { - .window = { - .top_left = { - .x = 0, - .y = 0, - }, - .btm_right = { - .x = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, - .y = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES, - }, - }, - .filter_enable = true, - .filter_threshold = { - .top_left_chan_thresh = 128, - .top_right_chan_thresh = 128, - .bottom_left_chan_thresh = 128, - .bottom_right_chan_thresh = 128, - }, - .stretch = { - .top_left_chan_stretch_en = true, - .top_right_chan_stretch_en = true, - .bottom_left_chan_stretch_en = true, - .bottom_right_chan_stretch_en = true, - }, - }; - ESP_ERROR_CHECK(esp_isp_blc_configure(isp_proc, &blc_config)); - ESP_ERROR_CHECK(esp_isp_blc_enable(isp_proc)); - - esp_isp_blc_offset_t blc_offset = { - .top_left_chan_offset = 20, - .top_right_chan_offset = 20, - .bottom_left_chan_offset = 20, - .bottom_right_chan_offset = 20, - }; - ESP_ERROR_CHECK(esp_isp_blc_set_correction_offset(isp_proc, &blc_offset)); -#endif - - /*--------------------------------------------------------------- - DEMOSAIC - ---------------------------------------------------------------*/ - esp_isp_demosaic_config_t demosaic_config = { - .grad_ratio = { - .integer = 2, - .decimal = 5, - }, - }; - ESP_ERROR_CHECK(esp_isp_demosaic_configure(isp_proc, &demosaic_config)); - ESP_ERROR_CHECK(esp_isp_demosaic_enable(isp_proc)); - - /*--------------------------------------------------------------- - CCM - ---------------------------------------------------------------*/ - /** - * CCM is used for color correction and white balance adjustment. - * It should be configured after demosaic and before gamma correction. - * - * The matrix format is: - * [R_out] [RR RG RB] [R_in] - * [G_out] = [GR GG GB] [G_in] - * [B_out] [BR BG BB] [B_in] - * - * For ESP32P4 ECO5: - * - Matrix coefficients range: ±15.996 (4-bit integer + 8-bit fraction) - * - For earlier versions: ±3.999 (2-bit integer + 10-bit fraction) - */ - esp_isp_ccm_config_t ccm_config = { - .matrix = { - // Default identity matrix (no color correction) - {1.0, 0.0, 0.0}, // R channel: R = 1.0*R + 0.0*G + 0.0*B - {0.0, 1.0, 0.0}, // G channel: G = 0.0*R + 1.0*G + 0.0*B - {0.0, 0.0, 1.0} // B channel: B = 0.0*R + 0.0*G + 1.0*B - }, - .saturation = false // Don't use saturation for out-of-range values - }; - - ESP_ERROR_CHECK(esp_isp_ccm_configure(isp_proc, &ccm_config)); - ESP_ERROR_CHECK(esp_isp_ccm_enable(isp_proc)); - - /*--------------------------------------------------------------- - GAMMA - ---------------------------------------------------------------*/ - isp_gamma_curve_points_t pts = {}; - ESP_ERROR_CHECK(esp_isp_gamma_fill_curve_points(s_gamma_correction_curve, &pts)); - ESP_ERROR_CHECK(esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_R, &pts)); - ESP_ERROR_CHECK(esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_G, &pts)); - ESP_ERROR_CHECK(esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_B, &pts)); - ESP_ERROR_CHECK(esp_isp_gamma_enable(isp_proc)); - - /*--------------------------------------------------------------- - SHARPEN - ---------------------------------------------------------------*/ - esp_isp_sharpen_config_t sharpen_config = { - .h_freq_coeff = { - .integer = 2, - .decimal = 0, - }, - .m_freq_coeff = { - .integer = 2, - .decimal = 0, - }, - .h_thresh = 255, - .l_thresh = 0, - .padding_mode = ISP_SHARPEN_EDGE_PADDING_MODE_SRND_DATA, - .sharpen_template = { - {1, 2, 1}, - {2, 4, 2}, - {1, 2, 1}, - }, - .padding_line_tail_valid_start_pixel = 0, - .padding_line_tail_valid_end_pixel = 0, - }; - ESP_ERROR_CHECK(esp_isp_sharpen_configure(isp_proc, &sharpen_config)); - ESP_ERROR_CHECK(esp_isp_sharpen_enable(isp_proc)); - - /*--------------------------------------------------------------- - COLOR - ---------------------------------------------------------------*/ - esp_isp_color_config_t color_config = { - .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_config)); - ESP_ERROR_CHECK(esp_isp_color_enable(isp_proc)); - -#if CONFIG_ESP32P4_REV_MIN_FULL >= 100 - /*--------------------------------------------------------------- - LSC - ---------------------------------------------------------------*/ - esp_isp_lsc_gain_array_t gain_array = {}; - esp_isp_lsc_config_t lsc_config = { - .gain_array = &gain_array, - }; - size_t gain_size = 0; - ESP_ERROR_CHECK(esp_isp_lsc_allocate_gain_array(isp_proc, &gain_array, &gain_size)); - - isp_lsc_gain_t gain_val = { - .decimal = 204, - .integer = 0, - }; - for (int i = 0; i < gain_size; i++) { - gain_array.gain_r[i].val = gain_val.val; - gain_array.gain_gr[i].val = gain_val.val; - gain_array.gain_gb[i].val = gain_val.val; - gain_array.gain_b[i].val = gain_val.val; + ret = example_create_isp_processor(&isp_config, &isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "ISP pipeline init fail[%d]", ret); + return; } - ESP_ERROR_CHECK(esp_isp_lsc_configure(isp_proc, &lsc_config)); - ESP_ERROR_CHECK(esp_isp_lsc_enable(isp_proc)); -#endif -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - /*--------------------------------------------------------------- - CROP - ---------------------------------------------------------------*/ - esp_isp_crop_config_t crop_config = { - .window = { - .top_left = { - .x = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H, - .y = CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V - }, - .btm_right = { - .x = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H, - .y = CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V - } - } - }; - ESP_ERROR_CHECK(esp_isp_crop_configure(isp_proc, &crop_config)); - ESP_ERROR_CHECK(esp_isp_crop_enable(isp_proc)); - ESP_LOGI(TAG, "ISP Crop enabled: (%d,%d) to (%d,%d)", - CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H, CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V, - CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H, CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V); -#endif + //---------------ISP Pipeline Configuration------------------// + // Initialize all ISP processing modules + ret = example_isp_init_all_pipelines(isp_proc); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "ISP pipeline modules init fail[%d]", ret); + return; + } - typedef struct af_task_param_t { - isp_proc_handle_t isp_proc; - esp_sccb_io_handle_t dw9714_io_handle; - } af_task_param_t; + //---------------AF Init and Start------------------// + ret = example_isp_af_init(isp_proc, dw9714_io_handle, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "AF init fail[%d]", ret); + return; + } - af_task_param_t af_task_param = { - .isp_proc = isp_proc, - .dw9714_io_handle = dw9714_io_handle, - }; - xTaskCreatePinnedToCore(af_task, "af_task", 8192, &af_task_param, 5, NULL, 0); + ret = example_isp_af_start(5, 0); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "AF start fail[%d]", ret); + return; + } -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - //---------------Frame Processing Task------------------// - xTaskCreatePinnedToCore(frame_processing_task, "frame_proc", 4096, &pp_ctx, 6, NULL, 0); - ESP_LOGI(TAG, "Frame processing task created"); -#endif - - //---------------DPI Reset------------------// + //---------------DPI Reset and Init------------------// example_dpi_panel_reset(mipi_dpi_panel); - //init both frame buffers to white + // Initialize both frame buffers to white memset(fb0, 0xFF, frame_buffer_size); memset(fb1, 0xFF, frame_buffer_size); esp_cache_msync((void *)fb0, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); esp_cache_msync((void *)fb1, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + //---------------Start Camera------------------// if (esp_cam_ctlr_start(handle) != ESP_OK) { - ESP_LOGE(TAG, "Driver start fail"); + ESP_LOGE(TAG, "Camera start fail"); return; } example_dpi_panel_init(mipi_dpi_panel); + ESP_LOGI(TAG, "ISP DSI example started"); + + // Main loop: receive frames while (1) { + esp_cam_ctlr_trans_t new_trans = { + .buffer = example_isp_buffer_get_csi_buffer(&pp_ctx), + .buflen = frame_buffer_size, + }; ESP_ERROR_CHECK(esp_cam_ctlr_receive(handle, &new_trans, ESP_CAM_CTLR_MAX_DELAY)); } } - -static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) -{ - pingpong_buffer_ctx_t *ctx = (pingpong_buffer_ctx_t *)user_data; - - // Provide the current CSI buffer for the next frame - trans->buffer = ctx->csi_buffer; - trans->buflen = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL; - - return false; -} - -bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data) -{ - pingpong_buffer_ctx_t *ctx = (pingpong_buffer_ctx_t *)user_data; - -#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE - BaseType_t high_task_wakeup = pdFALSE; - ctx->pending_buffer = trans->buffer; - xSemaphoreGiveFromISR(ctx->frame_ready_sem, &high_task_wakeup); - return (high_task_wakeup == pdTRUE); -#else - void *temp = ctx->csi_buffer; - ctx->csi_buffer = ctx->dsi_buffer; - ctx->dsi_buffer = temp; - - ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(ctx->panel, 0, 0, ctx->h_res, ctx->v_res, trans->buffer)); - return false; -#endif -}