mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
refactor(isp): split isp pipeline example into different srcs
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user