refactor(isp): split isp pipeline example into different srcs

This commit is contained in:
Chen Chen
2025-11-18 11:00:48 +08:00
parent 02fd823de9
commit ba1d0516ff
11 changed files with 1418 additions and 572 deletions
@@ -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
)
@@ -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;
}
@@ -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
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#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;
}
@@ -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 <stdint.h>
#include <stdbool.h>
#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
@@ -6,6 +6,8 @@
#pragma once
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
@@ -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);
}
@@ -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
@@ -0,0 +1,508 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <math.h>
#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;
}
@@ -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
@@ -6,18 +6,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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
}