Merge branch 'feat/isp_crop_driver_v5.5' into 'release/v5.5'

feat(isp): support Crop driver on p4 rev3 (v5.5)

See merge request espressif/esp-idf!43446
This commit is contained in:
morris
2025-12-26 09:51:13 +08:00
27 changed files with 806 additions and 54 deletions
@@ -15,7 +15,14 @@
#include "example_dsi_init_config.h"
#include "sdkconfig.h"
void example_dsi_resource_alloc(esp_lcd_dsi_bus_handle_t *mipi_dsi_bus, esp_lcd_panel_io_handle_t *mipi_dbi_io, esp_lcd_panel_handle_t *mipi_dpi_panel, void **frame_buffer)
static const char *TAG = "example_dsi_init";
void example_dsi_resource_alloc(const example_dsi_alloc_config_t *config,
esp_lcd_dsi_bus_handle_t *mipi_dsi_bus,
esp_lcd_panel_io_handle_t *mipi_dbi_io,
esp_lcd_panel_handle_t *mipi_dpi_panel,
void** fb0,
void** fb1)
{
//---------------DSI resource allocation------------------//
esp_lcd_dsi_bus_config_t bus_config = {
@@ -32,7 +39,22 @@ void example_dsi_resource_alloc(esp_lcd_dsi_bus_handle_t *mipi_dsi_bus, esp_lcd_
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(*mipi_dsi_bus, &dbi_config, mipi_dbi_io));
// Use default config if not provided
example_dsi_alloc_config_t default_config = EXAMPLE_DSI_ALLOC_CONFIG_DEFAULT();
if (config == NULL) {
config = &default_config;
}
if (config->num_fbs < 1 || config->num_fbs > 2) {
ESP_LOGE(TAG, "Invalid num_fbs: %d, must be 1 or 2", config->num_fbs);
return;
}
uint8_t num_fbs = config->num_fbs;
ESP_LOGI(TAG, "Allocating DSI resources with %d frame buffer(s)", num_fbs);
esp_lcd_dpi_panel_config_t dpi_config = {
.num_fbs = num_fbs,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = EXAMPLE_MIPI_DSI_DPI_CLK_MHZ,
.virtual_channel = 0,
@@ -83,7 +105,23 @@ void example_dsi_resource_alloc(esp_lcd_dsi_bus_handle_t *mipi_dsi_bus, esp_lcd_
ESP_ERROR_CHECK(esp_lcd_new_panel_ek79007(*mipi_dbi_io, &lcd_dev_config, mipi_dpi_panel));
#endif
ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(*mipi_dpi_panel, 1, frame_buffer));
// Get frame buffer addresses
if (fb0 != NULL) {
if (num_fbs == 2) {
if (fb1 != NULL) {
ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(*mipi_dpi_panel, 2, fb0, fb1));
ESP_LOGD(TAG, "Frame buffer[0] allocated at: %p", *fb0);
ESP_LOGD(TAG, "Frame buffer[1] allocated at: %p", *fb1);
} else {
ESP_LOGW(TAG, "num_fbs is 2 but fb1 is NULL, only getting fb0");
ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(*mipi_dpi_panel, 1, fb0));
ESP_LOGD(TAG, "Frame buffer[0] allocated at: %p", *fb0);
}
} else {
ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(*mipi_dpi_panel, 1, fb0));
ESP_LOGD(TAG, "Frame buffer[0] allocated at: %p", *fb0);
}
}
}
void example_dpi_panel_reset(esp_lcd_panel_handle_t mipi_dpi_panel)
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -14,14 +14,37 @@ extern "C" {
#endif
/**
* @brief DSI init function
*
* @param[out] mipi_dsi_bus MIPI DSI bus handle
* @param[out] mipi_dbi_io MIPI DBI io handle
* @param[out] mipi_dpi_panel MIPI DPI panel handle
* @param[out] frame_buffer frame buffer
* @brief DSI allocation configuration
*/
void example_dsi_resource_alloc(esp_lcd_dsi_bus_handle_t *mipi_dsi_bus, esp_lcd_panel_io_handle_t *mipi_dbi_io, esp_lcd_panel_handle_t *mipi_dpi_panel, void **frame_buffer);
typedef struct {
uint8_t num_fbs; /*!< Number of frame buffers (1-2). */
} example_dsi_alloc_config_t;
/**
* @brief Default DSI allocation configuration
*/
#define EXAMPLE_DSI_ALLOC_CONFIG_DEFAULT() { \
.num_fbs = 1, \
}
/**
* @brief DSI init function with configurable frame buffers
*
* @param[in] config DSI allocation configuration. If NULL, uses default (single buffer)
* @param[out] mipi_dsi_bus MIPI DSI bus handle
* @param[out] mipi_dbi_io MIPI DBI io handle
* @param[out] mipi_dpi_panel MIPI DPI panel handle
* @param[out] fb0 Pointer to receive the address of frame buffer 0 (first frame buffer)
* @param[out] fb1 Pointer to receive the address of frame buffer 1 (second frame buffer). If NULL, single buffer mode is used.
*
* @note The number of frame buffers allocated is determined by config->num_fbs. If config is NULL, single buffer mode is used.
*/
void example_dsi_resource_alloc(const example_dsi_alloc_config_t *config,
esp_lcd_dsi_bus_handle_t *mipi_dsi_bus,
esp_lcd_panel_io_handle_t *mipi_dbi_io,
esp_lcd_panel_handle_t *mipi_dpi_panel,
void **fb0,
void **fb1);
/**
* @brief DPI panel reset function
@@ -57,10 +57,10 @@ void app_main(void)
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
//---------------DSI Init------------------//
example_dsi_resource_alloc(&mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer);
example_dsi_resource_alloc(NULL, &mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer, NULL);
//---------------Necessary variable config------------------//
frame_buffer_size = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
frame_buffer_size = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
ESP_LOGD(TAG, "CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, EXAMPLE_RGB565_BITS_PER_PIXEL);
ESP_LOGD(TAG, "frame_buffer_size: %zu", frame_buffer_size);
@@ -108,7 +108,7 @@ void app_main(void)
}
//--------Allocate Camera Buffer----------//
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
void *cam_buffer = esp_cam_ctlr_alloc_buffer(cam_handle, cam_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
if (!cam_buffer) {
ESP_LOGE(TAG, "no mem for cam_buffer");
@@ -13,6 +13,8 @@ extern "C" {
#endif
#define EXAMPLE_RGB565_BITS_PER_PIXEL (16)
#define EXAMPLE_RGB565_BYTES_PER_PIXEL (EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
#define EXAMPLE_DVP_CAM_XCLK_FREQ_HZ (20000000)
#define EXAMPLE_DVP_CAM_DATA_WIDTH (8)
@@ -36,6 +38,7 @@ extern "C" {
#define EXAMPLE_DVP_CAM_HSYNC_IO (-1)
#else
#define EXAMPLE_DVP_CAM_SCCB_SCL_IO (33)
#define EXAMPLE_DVP_CAM_SCCB_SDA_IO (32)
@@ -89,10 +89,10 @@ void app_main(void)
* ISP convert to RGB565
*/
//---------------DSI Init------------------//
example_dsi_resource_alloc(&mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer);
example_dsi_resource_alloc(NULL, &mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer, NULL);
//---------------Necessary variable config------------------//
frame_buffer_size = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
frame_buffer_size = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
ESP_LOGD(TAG, "CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, EXAMPLE_RGB565_BITS_PER_PIXEL);
ESP_LOGD(TAG, "frame_buffer_size: %zu", frame_buffer_size);
@@ -157,7 +157,7 @@ void app_main(void)
}
//--------Allocate Camera Buffer----------//
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
void *cam_buffer = esp_cam_ctlr_alloc_buffer(cam_handle, cam_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
if (!cam_buffer) {
ESP_LOGE(TAG, "no mem for cam_buffer");
@@ -13,6 +13,7 @@ extern "C" {
#endif
#define EXAMPLE_RGB565_BITS_PER_PIXEL 16
#define EXAMPLE_RGB565_BYTES_PER_PIXEL (EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
#define EXAMPLE_ISP_DVP_CAM_SCCB_SCL_IO (33)
#define EXAMPLE_ISP_DVP_CAM_SCCB_SDA_IO (32)
@@ -24,7 +24,7 @@
static const char *TAG = "dvp_spi_lcd";
#define BUFFER_SIZE (CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
#define BUFFER_SIZE (CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL)
typedef struct {
esp_lcd_panel_handle_t panel_hdl;
@@ -164,7 +164,7 @@ void app_main(void)
}
//--------Allocate Camera Buffer----------//
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
void *cam_buffer = NULL;
cam_buffer = esp_cam_ctlr_alloc_buffer(cam_handle, cam_buffer_size, EXAMPLE_DVP_CAM_BUF_ALLOC_CAPS);
@@ -14,6 +14,7 @@ extern "C" {
//----------CAM Config------------//
#define EXAMPLE_RGB565_BITS_PER_PIXEL 16
#define EXAMPLE_RGB565_BYTES_PER_PIXEL (EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
#define EXAMPLE_DVP_CAM_SCCB_SCL_IO (5)
#define EXAMPLE_DVP_CAM_SCCB_SDA_IO (4)
@@ -13,6 +13,7 @@ extern "C" {
#endif
#define EXAMPLE_RGB565_BITS_PER_PIXEL 16
#define EXAMPLE_RGB565_BYTES_PER_PIXEL (EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
#define EXAMPLE_MIPI_IDI_CLOCK_RATE (50000000)
#define EXAMPLE_MIPI_CSI_LANE_BITRATE_MBPS 200 //line_rate = pclk * 4
@@ -51,10 +51,10 @@ void app_main(void)
* ISP convert to RGB565
*/
//---------------DSI Init------------------//
example_dsi_resource_alloc(&mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer);
example_dsi_resource_alloc(NULL, &mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer, NULL);
//---------------Necessary variable config------------------//
frame_buffer_size = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
frame_buffer_size = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
ESP_LOGD(TAG, "CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, EXAMPLE_RGB565_BITS_PER_PIXEL);
ESP_LOGD(TAG, "frame_buffer_size: %zu", frame_buffer_size);
@@ -16,6 +16,9 @@ This example demonstrates how to use the ISP (image signal processor) to work wi
- ISP GAMMA feature
- ISP Color feature
- ISP LSC feature
- ISP Crop feature (need to enable in `idf.py menuconfig`)
Additionally, this example also implements **Dual Frame Buffer (Ping-Pong Buffering)**, which eliminates screen tearing for smooth video display
## Usage
@@ -121,6 +124,21 @@ Remember to select the LCD screen model and set corresponding correct horizontal
Available options for the camera sensor output horizontal/vertical resolution can be seen in ``menuconfig`` > ``Example Configuration``. Note that the horizontal resolution for the camera should be the same as the LCD screen horizontal resolution.
#### Optional: Image Cropping Configuration
This example supports optional image cropping, which allows you to capture and display a specific region of the camera output. To enable this feature:
1. Navigate to `menuconfig` > `Example Configuration`
2. Enable `Enable ISP Image Cropping`
3. Configure the crop region:
- `ISP Crop Top-Left Horizontal`: X coordinate of top-left corner
- `ISP Crop Top-Left Vertical`: Y coordinate of top-left corner
- `ISP Crop Bottom-Right Horizontal`: X coordinate of bottom-right corner
- `ISP Crop Bottom-Right Vertical`: Y coordinate of bottom-right corner
**Note**: When cropping is enabled:
- The cropped image maintains its relative position on the screen
- A dedicated frame processing task handles the image transformation
### Build and Flash
@@ -142,15 +160,23 @@ To exit the serial monitor, use `Ctrl` + `]`.
If you see the following console output, your example should be running correctly:
```
I (1085) main_task: Calling app_main()
I (1095) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x5c
I (1125) gpio: GPIO[31]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (1125) gpio: GPIO[34]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (1295) ov5647: Detected Camera sensor PID=0x5647 with index 0
I (1305) cam_dsi: fmt[0].name:MIPI_2lane_24Minput_RAW8_800x1280_50fps
I (1305) cam_dsi: fmt[1].name:MIPI_2lane_24Minput_RAW8_800x640_50fps
I (1315) cam_dsi: fmt[2].name:MIPI_2lane_24Minput_RAW8_800x800_50fps
I (1355) cam_dsi: Format in use:MIPI_2lane_24Minput_RAW8_800x640_50fps
I (1425) main_task: Calling app_main()
I (1425) example_dsi_init: Allocating DSI resources with 2 frame buffer(s)
I (1485) example_dsi_init: Frame buffer[0] allocated at: 0x48000a40
I (1485) example_dsi_init: Frame buffer[1] allocated at: 0x481f4a80
I (1485) isp_dsi: Original CSI resolution: 800x640
I (1485) isp_dsi: Display resolution: 800x640, bits per pixel: 16
I (1495) isp_dsi: frame_buffer_size: 2048000
I (1495) isp_dsi: Frame buffers: fb0=0x48000a40, fb1=0x481f4a80
I (1515) ov5647: Detected Camera sensor PID=0x5647
I (1535) sensor_init: fmt[0].name:MIPI_2lane_24Minput_RAW8_800x1280_50fps
I (1535) sensor_init: fmt[1].name:MIPI_2lane_24Minput_RAW8_800x640_50fps
I (1535) sensor_init: fmt[2].name:MIPI_2lane_24Minput_RAW8_800x800_50fps
I (1535) sensor_init: fmt[3].name:MIPI_2lane_24Minput_RAW10_1920x1080_30fps
I (1545) sensor_init: fmt[4].name:MIPI_2lane_24Minput_RAW10_1280x960_binning_45fps
I (2025) sensor_init: Format in use:MIPI_2lane_24Minput_RAW8_800x640_50fps
I (2045) isp_dsi: ISP Crop not configured
I (2105) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x5c
```
Below picture is from the video stream of OV5647 and ILI9881C. The camera module is auto-focused and calibrated by ESP on-chip ISP hardware. The edge is over-sharpened as example code configured.
@@ -44,4 +44,57 @@ menu "Example Configuration"
default 600 if EXAMPLE_MIPI_CSI_VRES_600
default 640 if EXAMPLE_MIPI_CSI_VRES_640
default 1280 if EXAMPLE_MIPI_CSI_VRES_1280
config EXAMPLE_ISP_CROP_ENABLE
depends on (ESP32P4_REV_MIN_FULL >= 300)
bool "Enable ISP crop functionality"
default n
help
Enable ISP crop functionality. When enabled, you can configure
the crop area for the ISP pipeline.
menu "ISP Crop Configuration"
visible if EXAMPLE_ISP_CROP_ENABLE
config EXAMPLE_ISP_CROP_TOP_LEFT_H
int "ISP crop top-left horizontal coordinate"
default 0
range 0 799 if EXAMPLE_MIPI_CSI_HRES_800
range 0 1023 if EXAMPLE_MIPI_CSI_HRES_1024
help
Horizontal coordinate of the top-left corner for ISP crop.
Must be an even number and less than bottom-right horizontal coordinate.
config EXAMPLE_ISP_CROP_TOP_LEFT_V
int "ISP crop top-left vertical coordinate"
default 0
range 0 599 if EXAMPLE_MIPI_CSI_VRES_600
range 0 639 if EXAMPLE_MIPI_CSI_VRES_640
range 0 1279 if EXAMPLE_MIPI_CSI_VRES_1280
help
Vertical coordinate of the top-left corner for ISP crop.
Must be an even number and less than bottom-right vertical coordinate.
config EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H
int "ISP crop bottom-right horizontal coordinate"
default 799 if EXAMPLE_MIPI_CSI_HRES_800
default 1023 if EXAMPLE_MIPI_CSI_HRES_1024
range 1 799 if EXAMPLE_MIPI_CSI_HRES_800
range 1 1023 if EXAMPLE_MIPI_CSI_HRES_1024
help
Horizontal coordinate of the bottom-right corner for ISP crop.
Must be an odd number, greater than top-left horizontal coordinate, and not exceed display width.
config EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V
int "ISP crop bottom-right vertical coordinate"
default 599 if EXAMPLE_MIPI_CSI_VRES_600
default 639 if EXAMPLE_MIPI_CSI_VRES_640
default 1279 if EXAMPLE_MIPI_CSI_VRES_1280
range 1 599 if EXAMPLE_MIPI_CSI_VRES_600
range 1 639 if EXAMPLE_MIPI_CSI_VRES_640
range 1 1279 if EXAMPLE_MIPI_CSI_VRES_1280
help
Vertical coordinate of the bottom-right corner for ISP crop.
Must be an odd number, greater than top-left vertical coordinate, and not exceed display height.
endmenu
endmenu
@@ -11,6 +11,7 @@ extern "C" {
#endif
#define EXAMPLE_RGB565_BITS_PER_PIXEL 16
#define EXAMPLE_RGB565_BYTES_PER_PIXEL (EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
#define EXAMPLE_MIPI_SCCB_FREQ (100000)
#define EXAMPLE_MIPI_CSI_LANE_BITRATE_MBPS 200 //line_rate = pclk * 4
@@ -33,6 +33,103 @@ 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
*
* 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
*/
static void process_frame_with_blanks(void *buffer, pingpong_buffer_ctx_t *ctx)
{
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 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
---------------------------------------------------------------*/
@@ -185,13 +282,47 @@ static uint32_t s_gamma_correction_curve(uint32_t x)
return pow((double)x / 256, 0.7) * 256;
}
#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);
}
}
}
#endif // CONFIG_EXAMPLE_ISP_CROP_ENABLE
void app_main(void)
{
esp_err_t ret = ESP_FAIL;
esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
esp_lcd_panel_handle_t mipi_dpi_panel = NULL;
void *frame_buffer = NULL;
void *fb0 = NULL;
void *fb1 = NULL;
size_t frame_buffer_size = 0;
//mipi ldo
@@ -207,18 +338,54 @@ void app_main(void)
* Sensor use RAW8
* ISP convert to RGB565
*/
//---------------DSI Init------------------//
example_dsi_resource_alloc(&mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &frame_buffer);
//---------------DSI Init with Dual Frame Buffers------------------//
example_dsi_alloc_config_t dsi_alloc_config = {
.num_fbs = 2, // Enable dual frame buffers
};
example_dsi_resource_alloc(&dsi_alloc_config, &mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, &fb0, &fb1);
//---------------Necessary variable config------------------//
frame_buffer_size = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
int display_h_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES;
int display_v_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES;
ESP_LOGD(TAG, "CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES: %d, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES: %d, bits per pixel: %d", CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES, 8);
ESP_LOGD(TAG, "frame_buffer_size: %zu", frame_buffer_size);
ESP_LOGD(TAG, "frame_buffer: %p", frame_buffer);
#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
frame_buffer_size = CONFIG_EXAMPLE_MIPI_DSI_DISP_HRES * CONFIG_EXAMPLE_MIPI_DSI_DISP_VRES * EXAMPLE_RGB565_BYTES_PER_PIXEL;
ESP_LOGI(TAG, "Original CSI resolution: %dx%d", CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES);
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
};
#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE
if (pp_ctx.frame_ready_sem == NULL) {
ESP_LOGE(TAG, "Failed to create frame ready semaphore");
return;
}
#endif
esp_cam_ctlr_trans_t new_trans = {
.buffer = frame_buffer,
.buffer = pp_ctx.csi_buffer,
.buflen = frame_buffer_size,
};
@@ -248,8 +415,8 @@ void app_main(void)
//---------------CSI Init------------------//
esp_cam_ctlr_csi_config_t csi_config = {
.ctlr_id = 0,
.h_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES,
.v_res = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES,
.h_res = display_h_res,
.v_res = display_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,
@@ -268,7 +435,7 @@ void app_main(void)
.on_get_new_trans = s_camera_get_new_vb,
.on_trans_finished = s_camera_get_finished_trans,
};
if (esp_cam_ctlr_register_event_callbacks(handle, &cbs, &new_trans) != ESP_OK) {
if (esp_cam_ctlr_register_event_callbacks(handle, &cbs, &pp_ctx) != ESP_OK) {
ESP_LOGE(TAG, "ops register fail");
return;
}
@@ -422,6 +589,29 @@ void app_main(void)
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
typedef struct af_task_param_t {
isp_proc_handle_t isp_proc;
esp_sccb_io_handle_t dw9714_io_handle;
@@ -433,12 +623,20 @@ void app_main(void)
};
xTaskCreatePinnedToCore(af_task, "af_task", 8192, &af_task_param, 5, NULL, 0);
#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------------------//
example_dpi_panel_reset(mipi_dpi_panel);
//init to all white
memset(frame_buffer, 0xFF, frame_buffer_size);
esp_cache_msync((void *)frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
//init 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);
if (esp_cam_ctlr_start(handle) != ESP_OK) {
ESP_LOGE(TAG, "Driver start fail");
@@ -454,14 +652,30 @@ void app_main(void)
static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data)
{
esp_cam_ctlr_trans_t new_trans = *(esp_cam_ctlr_trans_t *)user_data;
trans->buffer = new_trans.buffer;
trans->buflen = new_trans.buflen;
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
}
@@ -329,7 +329,7 @@ void app_main(void)
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
//---------------DSI Init------------------//
example_dsi_resource_alloc(&mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, NULL);
example_dsi_resource_alloc(NULL, &mipi_dsi_bus, &mipi_dbi_io, &mipi_dpi_panel, NULL, NULL);
example_dpi_panel_reset(mipi_dpi_panel);
example_dpi_panel_init(mipi_dpi_panel);