Merge branch 'feat/lcd_cam_dvp_driver_s3' into 'master'

DVP support and example for ESP32S3

Closes IDF-10475

See merge request espressif/esp-idf!39323
This commit is contained in:
Gao Xu
2025-06-23 15:55:02 +08:00
32 changed files with 1322 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
menu "ESP-Driver:Camera Controller Configurations"
depends on SOC_MIPI_CSI_SUPPORTED || SOC_LCDCAM_CAM_SUPPORTED
depends on SOC_MIPI_CSI_SUPPORTED || SOC_ISP_DVP_SUPPORTED || SOC_LCDCAM_CAM_SUPPORTED
config CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE
bool "CSI ISR Cache-Safe"
@@ -16,6 +16,7 @@ menu "ESP-Driver:Camera Controller Configurations"
config CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE # IDF-10093
bool "ISP_DVP ISR Cache-Safe"
depends on SOC_ISP_DVP_SUPPORTED
default n
select DW_GDMA_ISR_IRAM_SAFE
select DW_GDMA_CTRL_FUNC_IN_IRAM

View File

@@ -26,12 +26,24 @@
#include "../../dvp_share_ctrl.h"
#ifdef CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE
#define CAM_DVP_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#define DVP_CAM_CTLR_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define CAM_DVP_MEM_ALLOC_CAPS (MALLOC_CAP_DEFAULT)
#define DVP_CAM_CTLR_ALLOC_CAPS (MALLOC_CAP_DEFAULT)
#endif
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#if CONFIG_SPIRAM
#define DVP_CAM_BK_BUFFER_ALLOC_CAPS (MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)
#else
#define DVP_CAM_BK_BUFFER_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA)
#endif
#if SOC_PERIPH_CLK_CTRL_SHARED
#define DVP_CAM_CLK_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define DVP_CAM_CLK_ATOMIC()
#endif
#define ALIGN_UP_BY(num, align) ((align) == 0 ? (num) : (((num) + ((align) - 1)) & ~((align) - 1)))
#define DVP_CAM_CONFIG_INPUT_PIN(pin, sig, inv) \
{ \
@@ -209,7 +221,6 @@ static uint32_t IRAM_ATTR esp_cam_ctlr_dvp_get_jpeg_size(const uint8_t *buffer,
*/
static uint32_t IRAM_ATTR esp_cam_ctlr_dvp_get_recved_size(esp_cam_ctlr_dvp_cam_t *ctlr, uint8_t *rx_buffer, uint32_t dma_recv_size)
{
esp_err_t ret;
uint32_t recv_buffer_size;
if (ctlr->pic_format_jpeg) {
@@ -218,8 +229,10 @@ static uint32_t IRAM_ATTR esp_cam_ctlr_dvp_get_recved_size(esp_cam_ctlr_dvp_cam_
recv_buffer_size = ctlr->fb_size_in_bytes;
}
ret = esp_cache_msync(rx_buffer, recv_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
if (esp_ptr_external_ram(rx_buffer)) {
esp_err_t ret = esp_cache_msync(rx_buffer, recv_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
assert(ret == ESP_OK);
}
if (ctlr->pic_format_jpeg) {
recv_buffer_size = esp_cam_ctlr_dvp_get_jpeg_size(rx_buffer, dma_recv_size);
@@ -336,7 +349,7 @@ esp_err_t esp_cam_ctlr_dvp_init(int ctlr_id, cam_clock_source_t clk_src, const e
}
ESP_ERROR_CHECK(esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true));
PERIPH_RCC_ATOMIC() {
DVP_CAM_CLK_ATOMIC() {
cam_ll_enable_clk(ctlr_id, true);
cam_ll_select_clk_src(ctlr_id, clk_src);
};
@@ -367,7 +380,7 @@ esp_err_t esp_cam_ctlr_dvp_output_clock(int ctlr_id, cam_clock_source_t clk_src,
ESP_LOGD(TAG, "DVP clock source frequency %" PRIu32 "Hz", src_clk_hz);
if ((src_clk_hz % xclk_freq) == 0) {
PERIPH_RCC_ATOMIC() {
DVP_CAM_CLK_ATOMIC() {
cam_ll_set_group_clock_coeff(ctlr_id, src_clk_hz / xclk_freq, 0, 0);
};
@@ -390,7 +403,7 @@ esp_err_t esp_cam_ctlr_dvp_deinit(int ctlr_id)
{
ESP_RETURN_ON_FALSE(ctlr_id < CAP_DVP_PERIPH_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid argument: ctlr_id >= %d", CAP_DVP_PERIPH_NUM);
PERIPH_RCC_ATOMIC() {
DVP_CAM_CLK_ATOMIC() {
cam_ll_enable_clk(ctlr_id, false);
};
@@ -710,11 +723,11 @@ esp_err_t esp_cam_new_dvp_ctlr(const esp_cam_ctlr_dvp_config_t *config, esp_cam_
ESP_RETURN_ON_FALSE(config->external_xtal || config->pin_dont_init || config->pin->xclk_io != GPIO_NUM_NC, ESP_ERR_INVALID_ARG, TAG, "invalid argument: xclk_io is not set");
ESP_RETURN_ON_FALSE(config->external_xtal || config->xclk_freq, ESP_ERR_INVALID_ARG, TAG, "invalid argument: xclk_freq is not set");
ESP_RETURN_ON_ERROR(esp_cache_get_alignment(MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA, &alignment_size), TAG, "failed to get cache alignment");
ESP_RETURN_ON_ERROR(esp_cache_get_alignment(DVP_CAM_BK_BUFFER_ALLOC_CAPS, &alignment_size), TAG, "failed to get cache alignment");
ESP_RETURN_ON_ERROR(esp_cam_ctlr_dvp_cam_get_frame_size(config, &fb_size_in_bytes), TAG, "invalid argument: input frame pixel format is not supported");
ESP_RETURN_ON_ERROR(dvp_shared_ctrl_claim_io_signals(), TAG, "failed to claim io signals");
esp_cam_ctlr_dvp_cam_t *ctlr = heap_caps_calloc(1, sizeof(esp_cam_ctlr_dvp_cam_t), CAM_DVP_MEM_ALLOC_CAPS);
esp_cam_ctlr_dvp_cam_t *ctlr = heap_caps_calloc(1, sizeof(esp_cam_ctlr_dvp_cam_t), DVP_CAM_CTLR_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr, ESP_ERR_NO_MEM, fail0, TAG, "no mem for CAM DVP controller context");
ESP_GOTO_ON_ERROR(s_dvp_claim_ctlr(config->ctlr_id, ctlr), fail1, TAG, "no available DVP controller");
@@ -722,7 +735,7 @@ esp_err_t esp_cam_new_dvp_ctlr(const esp_cam_ctlr_dvp_config_t *config, esp_cam_
ESP_LOGD(TAG, "alignment: 0x%x\n", alignment_size);
fb_size_in_bytes = ALIGN_UP_BY(fb_size_in_bytes, alignment_size);
if (!config->bk_buffer_dis) {
ctlr->backup_buffer = heap_caps_aligned_alloc(alignment_size, fb_size_in_bytes, MALLOC_CAP_SPIRAM);
ctlr->backup_buffer = heap_caps_aligned_alloc(alignment_size, fb_size_in_bytes, DVP_CAM_BK_BUFFER_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(ctlr->backup_buffer, ESP_ERR_NO_MEM, fail2, TAG, "no mem for DVP backup buffer");
}

View File

@@ -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
*/
@@ -10,9 +10,24 @@
#include "esp_cache.h"
#include "esp_private/esp_cache_private.h"
#include "esp_cam_ctlr_dvp_dma.h"
#include "esp_memory_utils.h"
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#if defined(SOC_GDMA_TRIG_PERIPH_CAM0_BUS) && (SOC_GDMA_TRIG_PERIPH_CAM0_BUS == SOC_GDMA_BUS_AHB)
#define DVP_GDMA_NEW_CHANNEL gdma_new_ahb_channel
#define DVP_GDMA_DESC_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA)
#elif defined(SOC_GDMA_TRIG_PERIPH_CAM0_BUS) && (SOC_GDMA_TRIG_PERIPH_CAM0_BUS == SOC_GDMA_BUS_AXI)
#define DVP_GDMA_NEW_CHANNEL gdma_new_axi_channel
#if CONFIG_SPIRAM
#define DVP_GDMA_DESC_ALLOC_CAPS (MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)
#else
#define DVP_GDMA_DESC_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA)
#endif
#else
#error "Unsupported GDMA bus type for DVP"
#endif
static const char *TAG = "dvp_gdma";
/**
@@ -72,9 +87,9 @@ esp_err_t esp_cam_ctlr_dvp_dma_init(esp_cam_ctlr_dvp_dma_t *dma, uint32_t burst_
#endif
};
ESP_RETURN_ON_ERROR(esp_cache_get_alignment(MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA, &alignment_size), TAG, "failed to get cache alignment");
ESP_RETURN_ON_ERROR(esp_cache_get_alignment(DVP_GDMA_DESC_ALLOC_CAPS, &alignment_size), TAG, "failed to get cache alignment");
ESP_RETURN_ON_ERROR(gdma_new_axi_channel(&rx_alloc_config, &dma->dma_chan), TAG, "new channel failed");
ESP_RETURN_ON_ERROR(DVP_GDMA_NEW_CHANNEL(&rx_alloc_config, &dma->dma_chan), TAG, "new channel failed");
ESP_GOTO_ON_ERROR(gdma_connect(dma->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_CAM, 0)), fail0, TAG, "connect failed");
@@ -90,17 +105,21 @@ esp_err_t esp_cam_ctlr_dvp_dma_init(esp_cam_ctlr_dvp_dma_t *dma, uint32_t burst_
.access_ext_mem = true,
};
ESP_GOTO_ON_ERROR(gdma_config_transfer(dma->dma_chan, &transfer_config), fail1, TAG, "set trans ability failed");
size_t int_mem_align = 0;
size_t ext_mem_align = 0;
gdma_get_alignment_constraints(dma->dma_chan, &int_mem_align, &ext_mem_align);
dma->desc_count = size / ESP_CAM_CTLR_DVP_DMA_DESC_BUFFER_MAX_SIZE;
if (size % ESP_CAM_CTLR_DVP_DMA_DESC_BUFFER_MAX_SIZE) {
dma->desc_count++;
}
dma->size = size;
ESP_LOGD(TAG, "alignment: 0x%x\n", alignment_size);
alignment_size = (alignment_size == 0) ? 1 : alignment_size;
dma->desc_size = ALIGN_UP_BY(dma->desc_count * sizeof(esp_cam_ctlr_dvp_dma_desc_t), alignment_size);
dma->desc = heap_caps_aligned_alloc(alignment_size, dma->desc_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
ESP_LOGD(TAG, "alignment_size: %d, dma->desc_count: %d, dma->desc_size: %d", alignment_size, dma->desc_count, dma->desc_size);
dma->desc = heap_caps_aligned_alloc(alignment_size, dma->desc_size, DVP_GDMA_DESC_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dma->desc, ESP_ERR_NO_MEM, fail1, TAG, "no mem for DVP DMA descriptor");
return ESP_OK;
@@ -144,15 +163,15 @@ esp_err_t esp_cam_ctlr_dvp_dma_deinit(esp_cam_ctlr_dvp_dma_t *dma)
*/
esp_err_t IRAM_ATTR esp_cam_ctlr_dvp_dma_start(esp_cam_ctlr_dvp_dma_t *dma, uint8_t *buffer, size_t size)
{
esp_err_t ret;
ESP_RETURN_ON_FALSE_ISR(dma, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
ESP_RETURN_ON_FALSE_ISR(dma->size >= size, ESP_ERR_INVALID_ARG, TAG, "input buffer size is out of range");
esp_cam_ctlr_dvp_config_dma_desc(dma->desc, buffer, size);
ret = esp_cache_msync(dma->desc, dma->desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
if (esp_ptr_external_ram(dma->desc)) {
esp_err_t ret = esp_cache_msync(dma->desc, dma->desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
assert(ret == ESP_OK);
}
return gdma_start(dma->dma_chan, (intptr_t)dma->desc);
}

View File

@@ -1,2 +1,2 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
| Supported Targets | ESP32-P4 | ESP32-S3 |
| ----------------- | -------- | -------- |

View File

@@ -17,7 +17,7 @@ TEST_CASE("TEST DVP driver allocation", "[DVP]")
.h_res = 800,
.v_res = 640,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.dma_burst_size = 64,
.byte_swap_en = false,
.pin_dont_init = true,
.external_xtal = true,
@@ -42,7 +42,7 @@ TEST_CASE("TEST DVP driver allocation with JPEG input", "[DVP]")
.clk_src = CAM_CLK_SRC_DEFAULT,
.h_res = 800,
.v_res = 640,
.dma_burst_size = 128,
.dma_burst_size = 64,
.byte_swap_en = false,
.pin_dont_init = true,
.pic_format_jpeg = true,
@@ -69,7 +69,7 @@ TEST_CASE("TEST DVP driver no backup buffer usage", "[DVP]")
.h_res = 800,
.v_res = 640,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.dma_burst_size = 64,
.byte_swap_en = false,
.bk_buffer_dis = true,
.pin_dont_init = true,
@@ -96,7 +96,7 @@ TEST_CASE("TEST DVP driver intern/extern init", "[DVP]")
.h_res = 800,
.v_res = 640,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.dma_burst_size = 64,
.byte_swap_en = false,
.external_xtal = true,
};
@@ -128,7 +128,7 @@ TEST_CASE("TEST DVP driver intern/extern generate xclk", "[DVP]")
.h_res = 800,
.v_res = 640,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.dma_burst_size = 64,
.byte_swap_en = false,
.external_xtal = true,
};

View File

@@ -14,3 +14,14 @@ from pytest_embedded_idf.utils import idf_parametrize
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
def test_dvp(dut: Dut) -> None:
dut.run_all_single_board_cases()
@pytest.mark.octal_psram
@pytest.mark.parametrize(
'config',
['cache_safe', 'release', 'pm_enable'],
indirect=True,
)
@idf_parametrize('target', ['esp32s3'], indirect=['target'])
def test_dvp_octal(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@@ -4,6 +4,4 @@ CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_HAL_ASSERTION_SILENT=y
CONFIG_CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE=y
CONFIG_CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE=y
CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE=y

View File

@@ -2,4 +2,3 @@
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
#
CONFIG_ESP_TASK_WDT_EN=n
CONFIG_FREERTOS_HZ=1000

View File

@@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_OCT=y

View File

@@ -199,7 +199,9 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
(uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev),
LCD_LL_EVENT_TRANS_DONE, i80_lcd_default_isr_handler, bus, &bus->intr);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
PERIPH_RCC_ATOMIC() {
lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, false); // disable all interrupts
}
lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
bus->max_transfer_bytes = bus_config->max_transfer_bytes;
@@ -215,8 +217,10 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
lcd_ll_set_swizzle_mode(bus->hal.dev, LCD_LL_SWIZZLE_AB2BA);
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(bus->hal.dev, true);
PERIPH_RCC_ATOMIC() {
// enable trans done interrupt
lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, true);
}
// trigger a quick "trans done" event, and wait for the interrupt line goes active
// this could ensure we go into ISR handler next time we call `esp_intr_enable`
lcd_periph_trigger_quick_trans_done_event(bus);

View File

@@ -336,7 +336,9 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
(uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev),
LCD_LL_EVENT_VSYNC_END, rgb_lcd_default_isr_handler, rgb_panel, &rgb_panel->intr);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
PERIPH_RCC_ATOMIC() {
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, false); // disable all interrupts
}
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
@@ -573,8 +575,10 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
// in stream mode, after finish one frame, the LCD controller will ask for data automatically from the DMA
// DMA should prepare the next frame data within porch region
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
PERIPH_RCC_ATOMIC() {
// trigger interrupt on the end of frame
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true);
}
// enable intr
esp_intr_enable(rgb_panel->intr);
// start transmission

View File

@@ -611,6 +611,12 @@ static inline void cam_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bo
dev->lc_dma_int_ena.val &= ~(mask & 0x0c);
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define cam_ll_enable_interrupt(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
cam_ll_enable_interrupt(__VA_ARGS__); \
} while(0)
/**
* @brief Get interrupt status value

View File

@@ -749,6 +749,12 @@ static inline void lcd_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bo
dev->lc_dma_int_ena.val &= ~(mask & 0x03);
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define lcd_ll_enable_interrupt(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
lcd_ll_enable_interrupt(__VA_ARGS__); \
} while(0)
/**
* @brief Get interrupt status value

View File

@@ -0,0 +1,621 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "hal/misc.h"
#include "hal/assert.h"
#include "soc/lcd_cam_struct.h"
#include "soc/system_struct.h"
#include "hal/cam_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define CAM_LL_GET_HW(id) (((id) == 0) ? (&LCD_CAM) : NULL)
#define CAM_LL_CLK_FRAC_DIV_N_MAX 256 // CAM_CLK = CAM_CLK_S / (N + b/a), the N register is 8 bit-width
#define CAM_LL_CLK_FRAC_DIV_AB_MAX 64 // CAM_CLK = CAM_CLK_S / (N + b/a), the a/b register is 6 bit-width
/**
* @brief Enable the bus clock for CAM module
*
* @param group_id Group ID
* @param enable true to enable, false to disable
*/
static inline void cam_ll_enable_bus_clock(int group_id, bool en)
{
(void)group_id;
SYSTEM.perip_clk_en1.lcd_cam_clk_en = en;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_RC_ATOMIC_ENV variable in advance
#define cam_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_RC_ATOMIC_ENV; \
cam_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the CAM module
*
* @param group_id Group ID
*/
static inline void cam_ll_reset_register(int group_id)
{
(void)group_id;
SYSTEM.perip_rst_en1.lcd_cam_rst = 1;
SYSTEM.perip_rst_en1.lcd_cam_rst = 0;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_RC_ATOMIC_ENV variable in advance
#define cam_ll_reset_register(...) do { \
(void)__DECLARE_RCC_RC_ATOMIC_ENV; \
cam_ll_reset_register(__VA_ARGS__); \
} while(0)
/**
* @brief Enable clock gating
*
* @param group_id Group ID
* @param en True to enable, False to disable
*/
static inline void cam_ll_enable_clk(int group_id, bool en)
{
(void)group_id;
(void)en;
//For compatibility
}
/**
* @brief Select clock source for CAM peripheral
*
* @param group_id Group ID
* @param src Clock source
*/
static inline void cam_ll_select_clk_src(int group_id, cam_clock_source_t src)
{
switch (src) {
case CAM_CLK_SRC_XTAL:
LCD_CAM.cam_ctrl.cam_clk_sel = 1;
break;
case CAM_CLK_SRC_PLL240M:
LCD_CAM.cam_ctrl.cam_clk_sel = 2;
break;
case CAM_CLK_SRC_PLL160M:
LCD_CAM.cam_ctrl.cam_clk_sel = 3;
break;
default:
// disable LCD clock source
LCD_CAM.cam_ctrl.cam_clk_sel = 0;
HAL_ASSERT(false);
break;
}
}
/**
* @brief Get the CAM source clock type
*
* @param dev CAM register base address
* @param src The pointer to accept the CAM source clock type
*/
static inline void cam_ll_get_clk_src(lcd_cam_dev_t *dev, cam_clock_source_t *src)
{
switch (LCD_CAM.cam_ctrl.cam_clk_sel) {
case 1:
*src = CAM_CLK_SRC_XTAL;
break;
case 2:
*src = CAM_CLK_SRC_PLL240M;
break;
case 3:
*src = CAM_CLK_SRC_PLL160M;
break;
default:
HAL_ASSERT(false);
break;
}
}
/**
* @brief Set clock coefficient of CAM peripheral
*
* @param group_id Group ID
* @param div_num Integer part of the divider
* @param div_a denominator of the divider
* @param div_b numerator of the divider
*/
__attribute__((always_inline))
static inline void cam_ll_set_group_clock_coeff(int group_id, int div_num, int div_a, int div_b)
{
HAL_ASSERT(div_num >= 2 && div_num < CAM_LL_CLK_FRAC_DIV_N_MAX);
LCD_CAM.cam_ctrl.cam_clkm_div_num = div_num;
LCD_CAM.cam_ctrl.cam_clkm_div_a = div_a;
LCD_CAM.cam_ctrl.cam_clkm_div_b = div_b;
}
/**
* @brief Enable stop signal for CAM peripheral
*
* @param dev CAM register base address
* @param en True to stop when GDMA Rx FIFO is full, False to not stop
*/
static inline void cam_ll_enable_stop_signal(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_stop_en = en;
}
/**
* @brief Set vsync filter threshold value
*
* @param dev CAM register base address
* @param value Filter threshold value for CAM_VSYNC_SIGNAL, range [0, 7]
*/
static inline void cam_ll_set_vsync_filter_thres(lcd_cam_dev_t *dev, uint32_t value)
{
dev->cam_ctrl.cam_vsync_filter_thres = value;
}
/**
* @brief Enable to generate LCD_CAM_CAM_HS_INT
*
* @param dev CAM register base address
* @param en True to enable to generate LCD_CAM_CAM_HS_INT, False to disable
*/
static inline void cam_ll_enable_hs_line_int(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_line_int_en = en;
}
/**
* @brief Enable CAM_VSYNC to generate in_suc_eof
*
* @param dev CAM register base address
* @param en True to enable CAM_VSYNC to generate in_suc_eof, False to use LCD_CAM_CAM_REC_DATA_BYTELEN to control in_suc_eof
*/
static inline void cam_ll_enable_vsync_generate_eof(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_vs_eof_en = en;
}
/**
* @brief Enable to swap every two 8-bit input data
*
* @param dev CAM register base address
* @param en True to enable invert, False to disable invert
*/
static inline void cam_ll_enable_8bits_data_invert(lcd_cam_dev_t *dev, bool en)
{
dev->cam_rgb_yuv.cam_conv_8bits_data_inv = en;
}
/**
* @brief Enable YUV-RGB converter
*
* @param dev CAM register base address
* @param en True to enable converter, False to disable converter
*/
static inline void cam_ll_enable_rgb_yuv_convert(lcd_cam_dev_t *dev, bool en)
{
dev->cam_rgb_yuv.cam_conv_bypass = en;
}
/**
* @brief Set convert data line width
*
* @param dev CAM register base address
* @param width data line width (8 or 16)
*/
static inline void cam_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width)
{
HAL_ASSERT(width == 8 || width == 16);
dev->cam_rgb_yuv.cam_conv_mode_8bits_on = (width == 8) ? 1 : 0;
}
/**
* @brief Set the color range of input data
*
* @param dev CAM register base address
* @param range Color range
*/
static inline void cam_ll_set_input_color_range(lcd_cam_dev_t *dev, color_range_t range)
{
if (range == COLOR_RANGE_LIMIT) {
dev->cam_rgb_yuv.cam_conv_data_in_mode = 0;
} else if (range == COLOR_RANGE_FULL) {
dev->cam_rgb_yuv.cam_conv_data_in_mode = 1;
}
}
/**
* @brief Set the color range of output data
*
* @param dev CAM register base address
* @param range Color range
*/
static inline void cam_ll_set_output_color_range(lcd_cam_dev_t *dev, color_range_t range)
{
if (range == COLOR_RANGE_LIMIT) {
dev->cam_rgb_yuv.cam_conv_data_out_mode = 0;
} else if (range == COLOR_RANGE_FULL) {
dev->cam_rgb_yuv.cam_conv_data_out_mode = 1;
}
}
/**
* @brief Set YUV conversion standard
*
* @param dev CAM register base address
* @param std YUV conversion standard
*/
static inline void cam_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, color_conv_std_rgb_yuv_t std)
{
if (std == COLOR_CONV_STD_RGB_YUV_BT601) {
dev->cam_rgb_yuv.cam_conv_protocol_mode = 0;
} else if (std == COLOR_CONV_STD_RGB_YUV_BT709) {
dev->cam_rgb_yuv.cam_conv_protocol_mode = 1;
}
}
/**
* @brief Set the converter mode: RGB565 to YUV
*
* @param dev CAM register base address
* @param yuv_sample YUV sample mode
*/
static inline void cam_ll_set_convert_mode_rgb_to_yuv(lcd_cam_dev_t *dev, color_pixel_yuv_format_t yuv_sample)
{
dev->cam_rgb_yuv.cam_conv_trans_mode = 1;
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 3;
switch (yuv_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 2;
break;
default:
abort();
}
}
/**
* @brief Set the converter mode: YUV to RGB565
*
* @param dev CAM register base address
* @param yuv_sample YUV sample mode
*/
static inline void cam_ll_set_convert_mode_yuv_to_rgb(lcd_cam_dev_t *dev, color_pixel_yuv_format_t yuv_sample)
{
dev->cam_rgb_yuv.cam_conv_trans_mode = 0;
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 3;
switch (yuv_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 2;
break;
default:
abort();
}
}
/**
* @brief Set the converter mode: YUV to YUV
*
* @param dev CAM register base address
* @param src_sample Source YUV sample mode
* @param dst_sample Destination YUV sample mode
*/
static inline void cam_ll_set_convert_mode_yuv_to_yuv(lcd_cam_dev_t *dev, color_pixel_yuv_format_t src_sample, color_pixel_yuv_format_t dst_sample)
{
HAL_ASSERT(src_sample != dst_sample);
dev->cam_rgb_yuv.cam_conv_trans_mode = 1;
switch (src_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv_mode = 2;
break;
default:
abort();
}
switch (dst_sample) {
case COLOR_PIXEL_YUV422:
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 0;
break;
case COLOR_PIXEL_YUV420:
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 1;
break;
case COLOR_PIXEL_YUV411:
dev->cam_rgb_yuv.cam_conv_yuv2yuv_mode = 2;
break;
default:
abort();
}
}
/**
* @brief Set camera received data byte length
*
* @param dev CAM register base address
* @param length received data byte length, range [0, 0xFFFF]
*/
static inline void cam_ll_set_recv_data_bytelen(lcd_cam_dev_t *dev, uint32_t length)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(dev->cam_ctrl1, cam_rec_data_bytelen, length);
}
/**
* @brief Set line number to trigger interrupt
*
* @param dev CAM register base address
* @param number line number to trigger hs interrupt, range [0, 0x3F]
*/
static inline void cam_ll_set_line_int_num(lcd_cam_dev_t *dev, uint32_t number)
{
dev->cam_ctrl1.cam_line_int_num = number;
}
/**
* @brief Whether to invert the input signal CAM_PCLK
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_pclk(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_clk_inv = en;
}
/**
* @brief Enable CAM_VSYNC filter function
*
* @param dev CAM register base address
* @param en True to enable, False to bypass
*/
static inline void cam_ll_enable_vsync_filter(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_vsync_filter_en = en;
}
/**
* @brief Set CAM input data width
*
* @param dev CAM register base address
* @param stride 16: The bit number of input data is 9~16. 8: The bit number of input data is 0~8.
*/
static inline void cam_ll_set_input_data_width(lcd_cam_dev_t *dev, uint32_t stride)
{
switch (stride) {
case 8:
dev->cam_ctrl1.cam_2byte_en = 0;
break;
case 16:
dev->cam_ctrl1.cam_2byte_en = 1;
break;
default:
HAL_ASSERT(false);
break;
}
}
/**
* @brief Whether to invert CAM_DE
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_de(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_de_inv = en;
}
/**
* @brief Whether to invert CAM_HSYNC
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_hsync(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_hsync_inv = en;
}
/**
* @brief Whether to invert CAM_VSYNC
*
* @param dev CAM register base address
* @param en True to invert, False to not invert
*/
static inline void cam_ll_enable_invert_vsync(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl1.cam_vsync_inv = en;
}
/**
* @brief Enable the mode to control the input control signals
*
* @param dev CAM register base address
* @param mode 1: Input control signals are CAM_DE, CAM_HSYNC and CAM_VSYNC;
* 0: Input control signals are CAM_DE and CAM_VSYNC. CAM_HSYNC and CAM_DE are all 1 the the same time.
*/
static inline void cam_ll_set_vh_de_mode(lcd_cam_dev_t *dev, bool enable)
{
dev->cam_ctrl1.cam_vh_de_mode_en = enable;
}
/**
* @brief Get the mode of input control signals
*
* @param dev CAM register base address
* @param en The pointer to accept the vh_de mode status. 1: Input control signals are CAM_DE, CAM_HSYNC and CAM_VSYNC;
* 0: Input control signals are CAM_DE and CAM_VSYNC. CAM_HSYNC and CAM_DE are all 1 the the same time.
*/
static inline void cam_ll_get_vh_de_mode(lcd_cam_dev_t *dev, bool *en)
{
*en = dev->cam_ctrl1.cam_vh_de_mode_en;
}
/**
* @brief Set the wire width of CAM output
*
* @param dev CAM register base address
* @param width CAM output wire width
*/
static inline void cam_ll_set_data_wire_width(lcd_cam_dev_t *dev, uint32_t width)
{
// data line width is same as data stride that set in `cam_ll_set_input_data_width`
}
/**
* @brief Start the CAM transaction
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_start(lcd_cam_dev_t *dev)
{
dev->cam_ctrl.cam_update = 1;
dev->cam_ctrl1.cam_start = 1;
}
/**
* @brief Stop the CAM transaction
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_stop(lcd_cam_dev_t *dev)
{
dev->cam_ctrl1.cam_start = 0;
dev->cam_ctrl.cam_update = 1; // self clear
}
/**
* @brief Whether to reverse the data bit order
*
* @note It acts before the YUV-RGB converter
*
* @param dev CAM register base address
* @param en True to reverse, False to not reverse
*/
__attribute__((always_inline))
static inline void cam_ll_reverse_dma_data_bit_order(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_bit_order = en;
}
/**
* @brief Whether to swap adjacent two bytes
*
* @note This acts before the YUV-RGB converter, mainly to change the data endian.
* {B1,B0},{B3,B2} => {B0,B1}{B2,B3}
*
* @param dev CAM register base address
* @param en True to swap the byte order, False to not swap
*/
__attribute__((always_inline))
static inline void cam_ll_swap_dma_data_byte_order(lcd_cam_dev_t *dev, bool en)
{
dev->cam_ctrl.cam_byte_order = en;
}
/**
* @brief Reset camera module
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_reset(lcd_cam_dev_t *dev)
{
dev->cam_ctrl1.cam_reset = 1; // self clear
}
/**
* @brief Reset Async RX FIFO
*
* @param dev CAM register base address
*/
__attribute__((always_inline))
static inline void cam_ll_fifo_reset(lcd_cam_dev_t *dev)
{
dev->cam_ctrl1.cam_afifo_reset = 1; // self clear
}
/**
* @brief Enable/disable interrupt by mask
*
* @param dev CAM register base address
* @param mask Interrupt mask
* @param en True to enable interrupt, False to disable interrupt
*/
static inline void cam_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bool en)
{
if (en) {
dev->lc_dma_int_ena.val |= mask & 0x0c;
} else {
dev->lc_dma_int_ena.val &= ~(mask & 0x0c);
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define cam_ll_enable_interrupt(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
cam_ll_enable_interrupt(__VA_ARGS__); \
} while(0)
/**
* @brief Get interrupt status value
*
* @param dev CAM register base address
* @return Interrupt status value
*/
__attribute__((always_inline))
static inline uint32_t cam_ll_get_interrupt_status(lcd_cam_dev_t *dev)
{
return dev->lc_dma_int_st.val & 0x0c;
}
/**
* @brief Clear interrupt status by mask
*
* @param dev CAM register base address
* @param mask Interrupt status mask
*/
__attribute__((always_inline))
static inline void cam_ll_clear_interrupt_status(lcd_cam_dev_t *dev, uint32_t mask)
{
dev->lc_dma_int_clr.val = mask & 0x0c;
}
/**
* @brief Get address of interrupt status register address
*
* @param dev CAM register base address
* @return Interrupt status register address
*/
static inline volatile void *cam_ll_get_interrupt_status_reg(lcd_cam_dev_t *dev)
{
return &dev->lc_dma_int_st;
}
#ifdef __cplusplus
}
#endif

View File

@@ -723,6 +723,12 @@ static inline void lcd_ll_enable_interrupt(lcd_cam_dev_t *dev, uint32_t mask, bo
dev->lc_dma_int_ena.val &= ~(mask & 0x03);
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define lcd_ll_enable_interrupt(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
lcd_ll_enable_interrupt(__VA_ARGS__); \
} while(0)
/**
* @brief Get interrupt status value

View File

@@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/gpio_sig_map.h"
#include "soc/cam_periph.h"
const cam_signal_conn_t cam_periph_signals = {
.buses = {
[0] = {
.module = PERIPH_LCD_CAM_MODULE,
.irq_id = ETS_LCD_CAM_INTR_SOURCE,
.data_sigs = {
CAM_DATA_IN0_IDX,
CAM_DATA_IN1_IDX,
CAM_DATA_IN2_IDX,
CAM_DATA_IN3_IDX,
CAM_DATA_IN4_IDX,
CAM_DATA_IN5_IDX,
CAM_DATA_IN6_IDX,
CAM_DATA_IN7_IDX,
CAM_DATA_IN8_IDX,
CAM_DATA_IN9_IDX,
CAM_DATA_IN10_IDX,
CAM_DATA_IN11_IDX,
CAM_DATA_IN12_IDX,
CAM_DATA_IN13_IDX,
CAM_DATA_IN14_IDX,
CAM_DATA_IN15_IDX
},
.hsync_sig = CAM_H_SYNC_IDX,
.vsync_sig = CAM_V_SYNC_IDX,
.pclk_sig = CAM_PCLK_IDX,
.de_sig = CAM_H_ENABLE_IDX,
.clk_sig = CAM_CLK_IDX
}
}
};

View File

@@ -47,6 +47,10 @@ config SOC_LCDCAM_SUPPORTED
bool
default y
config SOC_LCDCAM_CAM_SUPPORTED
bool
default y
config SOC_LCDCAM_I80_LCD_SUPPORTED
bool
default y
@@ -1538,3 +1542,15 @@ config SOC_ULP_HAS_ADC
config SOC_PHY_COMBO_MODULE
bool
default y
config SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV
bool
default y
config SOC_LCDCAM_CAM_PERIPH_NUM
int
default 1
config SOC_LCDCAM_CAM_DATA_WIDTH_MAX
int
default 16

View File

@@ -197,6 +197,23 @@ typedef enum {
LCD_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the default choice */
} soc_periph_lcd_clk_src_t;
//////////////////////////////////////////////////LCD///////////////////////////////////////////////////////////////////
/**
* @brief Array initializer for all supported clock sources of CAM
*/
#define SOC_CAM_CLKS {SOC_MOD_CLK_PLL_F160M, SOC_MOD_CLK_PLL_D2, SOC_MOD_CLK_XTAL}
/**
* @brief Type of CAM clock source
*/
typedef enum {
CAM_CLK_SRC_PLL160M = SOC_MOD_CLK_PLL_F160M, /*!< Select PLL_F160M as the source clock */
CAM_CLK_SRC_PLL240M = SOC_MOD_CLK_PLL_D2, /*!< Select PLL_D2 as the source clock */
CAM_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the source clock */
CAM_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_D2, /*!< Select PLL_D2 as the default choice */
} soc_periph_cam_clk_src_t;
//////////////////////////////////////////////////RMT///////////////////////////////////////////////////////////////////
/**

View File

@@ -33,6 +33,7 @@
#define SOC_AHB_GDMA_SUPPORTED 1
#define SOC_GPTIMER_SUPPORTED 1
#define SOC_LCDCAM_SUPPORTED 1
#define SOC_LCDCAM_CAM_SUPPORTED 1 // support the camera driver based on the LCD_CAM peripheral
#define SOC_LCDCAM_I80_LCD_SUPPORTED 1
#define SOC_LCDCAM_RGB_LCD_SUPPORTED 1
#define SOC_MCPWM_SUPPORTED 1
@@ -605,3 +606,8 @@
/*------------------------------------- PHY CAPS -------------------------------------*/
#define SOC_PHY_COMBO_MODULE (1) /*!< Support Wi-Fi and BLE*/
/*--------------------------- CAM ---------------------------------*/
#define SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV (1)
#define SOC_LCDCAM_CAM_PERIPH_NUM (1U)
#define SOC_LCDCAM_CAM_DATA_WIDTH_MAX (16U)

View File

@@ -58,6 +58,17 @@ examples/peripherals/camera/dvp_isp_dsi:
- esp_lcd
- esp_driver_cam
examples/peripherals/camera/dvp_spi_lcd:
disable:
- if: SOC_LCDCAM_CAM_SUPPORTED != 1
disable_test:
- if: IDF_TARGET == "esp32s3"
temporary: true
reason: lack of runners
depends_components:
- esp_lcd
- esp_driver_cam
examples/peripherals/camera/mipi_isp_dsi:
disable:
- if: SOC_MIPI_CSI_SUPPORTED != 1 or SOC_MIPI_DSI_SUPPORTED != 1

View File

@@ -1,4 +1,4 @@
dependencies:
espressif/esp_cam_sensor: "^0.6.1"
espressif/esp_cam_sensor: "^1.1.0"
idf:
version: ">=5.3.0"

View File

@@ -0,0 +1,8 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(dvp_spi_lcd)

View File

@@ -0,0 +1,139 @@
| Supported Targets | ESP32-P4 | ESP32-S3 |
| ----------------- | -------- | -------- |
# DVP Camera display via LCD example
## Overview
This example demonstrates how to use the esp_driver_cam component to capture DVP camera sensor signals and display it via LCD interface. This example will auto-detect camera sensors via [ESP camera sensor driver](https://components.espressif.com/components/espressif/esp_cam_sensor) and capture camera sensor signals via DVP interface and display it via LCD interface.
## Usage
The subsections below give only absolutely necessary information. For full steps to configure ESP-IDF and use it to build and run projects, see [ESP-IDF Getting Started](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#get-started).
### Hardware Required
- ESP32S3 devkit with OV2640 camera sensor and ST7789 LCD screen
- or an ESP32S3-EYE dev-kit
You can also connect camera sensors and LCD screens from other vendors to the ESP chip, you can find corresponding camera or LCD drivers from [ESP Component Registry](https://components.espressif.com), or design your own customized drivers.
GND GND
┌────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────┐
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ┌───────────────┴─────────────┴──────────────────┐ │
│ │ │ ┌──────────┴───────────┐
│ │ │ LCD MOSI │ │
│ │ ├───────────────────────────┤ │
┌───────────┴─────────┐ │ │ │ │
│ │ │ │ LCD CLK │ │
│ │ │ ├───────────────────────────┤ │
│ │ XCLK │ ESP_CHIP │ │ │
│ DVP Camera ├──────────────────────┤ │ LCD CS │ LCD Screen │
│ │ │ ├───────────────────────────┤ │
│ │ D0~7 │ │ │ │
│ ├──────────────────────┤ │ LCD DC │ │
│ │ │ ├───────────────────────────┤ │
│ │ PCLK │ │ │ │
│ ├──────────────────────┤ │ LCD BACKLIGHT │ │
│ │ │ ├───────────────────────────┤ │
│ │ VSYNC │ │ │ │
│ ├──────────────────────┤ │ │ │
│ │ │ │ │ │
│ │ DE (HREF) │ │ │ │
│ ├──────────────────────┤ │ └──────────────────────┘
│ │ │ │
└───────┬──┬──────────┘ │ │
│ │ I2C SCL │ │
│ └─────────────────────────────────┤ │
│ I2C SDA │ │
└────────────────────────────────────┤ │
└────────────────────────────────────────────────┘
### Set Chip Target
First of all, your target must be supported by both:
- **By your ESP-IDF version**: For the full list of supported targets, run:
```
idf.py --list-targets
```
- **By this example**: For the full list of supported targets, refer to the supported targets table at the top of this README.
After you make sure that your target is supported, go to your example project directory and [set the chip target](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tools/idf-py.html#select-the-target-chip-set-target):
```
idf.py set-target <target>
```
For example, to set esp32-S3 as the chip target, run:
```
idf.py set-target esp32s3
```
### Configure the Project
For information about Kconfig options, see [Project Configuration](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html) > _Name of relevant section(s)_.
To conveniently check or modify Kconfig options for this example in a project configuration menu, run:
```
idf.py menuconfig
```
```
Set CONFIG_CAMERA_OV2640 to y
```
Available options for the camera sensor output horizontal/vertical resolution can be seen in ``menuconfig`` > ``Example Configuration``.
### Build and Flash
Execute the following command to build the project, flash it to your development board, and run the monitor tool to view the serial output:
```
idf.py build flash monitor
```
This command can be reduced to `idf.py flash monitor`.
If the above command fails, check the log on the serial monitor which usually provides information on the possible cause of the issue.
To exit the serial monitor, use `Ctrl` + `]`.
## Example Output
If you see the following console output, your example should be running correctly:
```
I (1481) main_task: Calling app_main()
I (278) dvp_spi_lcd: Init SPI bus
I (278) dvp_spi_lcd: New panel IO SPI
I (278) dvp_spi_lcd: New ST7789 panel
I (278) dvp_spi_lcd: Reset and init panel
I (408) dvp_spi_lcd: Turn on display
I (408) dvp_spi_lcd: Screen lit up now!
```
## Reference
- Link to the ESP-IDF feature's API reference, for example [ESP-IDF: Camera Controller Driver](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/camera_driver.html)
- [ESP-IDF Getting Started](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#get-started)
- [Project Configuration](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html) (Kconfig Options)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "dvp_spi_lcd_main.c"
INCLUDE_DIRS "."
REQUIRES esp_mm esp_driver_cam esp_driver_i2c esp_lcd sensor_init
)

View File

@@ -0,0 +1,33 @@
menu "Example Configuration"
choice EXAMPLE_CAM_HRES
bool "Set camera horizontal resolution"
default EXAMPLE_CAM_HRES_240
config EXAMPLE_CAM_HRES_640
bool "640"
config EXAMPLE_CAM_HRES_240
bool "240"
endchoice
config EXAMPLE_CAM_HRES
int
default 640 if EXAMPLE_CAM_HRES_640
default 240 if EXAMPLE_CAM_HRES_240
choice EXAMPLE_CAM_VRES
bool "Set camera vertical resolution"
default EXAMPLE_CAM_VRES_480 if EXAMPLE_CAM_HRES_640
default EXAMPLE_CAM_VRES_240 if EXAMPLE_CAM_HRES_240
config EXAMPLE_CAM_VRES_480
bool "480"
config EXAMPLE_CAM_VRES_240
bool "240"
endchoice
config EXAMPLE_CAM_VRES
int
default 480 if EXAMPLE_CAM_VRES_480
default 240 if EXAMPLE_CAM_VRES_240
endmenu

View File

@@ -0,0 +1,226 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "esp_lcd_panel_ops.h"
#include "esp_cache.h"
#include "driver/i2c_master.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_dvp.h"
#include "example_config.h"
#include "driver/ledc.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "example_sensor_init.h"
static const char *TAG = "dvp_spi_lcd";
#define BUFFER_SIZE (CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8)
typedef struct {
esp_lcd_panel_handle_t panel_hdl;
esp_cam_ctlr_trans_t cam_trans;
} example_cam_context_t;
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);
static void lcd_display_init(esp_lcd_panel_handle_t *lcd_panel_hdl, esp_lcd_panel_io_handle_t lcd_io_hdl)
{
esp_lcd_panel_handle_t panel_handle = NULL;
//----------LEDC initialization------------//
const ledc_timer_config_t lcd_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = EXAMPLE_LEDC_LCD_BACKLIGHT,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&lcd_timer));
const ledc_channel_config_t lcd_channel = {
.gpio_num = EXAMPLE_LCD_BACKLIGHT,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = EXAMPLE_LEDC_LCD_BACKLIGHT,
.intr_type = LEDC_INTR_DISABLE,
.duty = 0,
.hpoint = 0,
.flags.output_invert = true,
};
ESP_ERROR_CHECK(ledc_channel_config(&lcd_channel));
//----------SPI initialization------------//
ESP_LOGI(TAG, "Init SPI bus");
const spi_bus_config_t bus_cfg = {
.sclk_io_num = EXAMPLE_LCD_SPI_CLK,
.mosi_io_num = EXAMPLE_LCD_SPI_MOSI,
.miso_io_num = GPIO_NUM_NC,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = BUFFER_SIZE,
};
ESP_ERROR_CHECK(spi_bus_initialize(EXAMPLE_LCD_SPI_NUM, &bus_cfg, SPI_DMA_CH_AUTO));
//----------Panel IO initialization------------//
ESP_LOGI(TAG, "New panel IO SPI");
const esp_lcd_panel_io_spi_config_t io_cfg = {
.dc_gpio_num = EXAMPLE_LCD_DC,
.cs_gpio_num = EXAMPLE_LCD_SPI_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
.spi_mode = 2,
.trans_queue_depth = 10,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(
(esp_lcd_spi_bus_handle_t)EXAMPLE_LCD_SPI_NUM,
&io_cfg,
&lcd_io_hdl
));
//----------ST7789 Panel initialization------------//
ESP_LOGI(TAG, "New ST7789 panel");
const esp_lcd_panel_dev_config_t panel_dev_cfg = {
.reset_gpio_num = EXAMPLE_LCD_RST,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = EXAMPLE_RGB565_BITS_PER_PIXEL,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(lcd_io_hdl, &panel_dev_cfg, &panel_handle));
ESP_LOGI(TAG, "Reset and init panel");
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_invert_color(panel_handle, true);
ESP_LOGI(TAG, "Turn on display");
esp_lcd_panel_disp_on_off(panel_handle, true);
const int brightness = 100;
uint32_t duty = (1023 * brightness) / 100;
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
*lcd_panel_hdl = panel_handle;
}
void app_main(void)
{
esp_err_t ret = ESP_FAIL;
esp_lcd_panel_handle_t lcd_panel_hdl = NULL;
esp_lcd_panel_io_handle_t lcd_io_hdl = NULL;
size_t cam_buffer_size = CONFIG_EXAMPLE_CAM_HRES * CONFIG_EXAMPLE_CAM_VRES * EXAMPLE_RGB565_BITS_PER_PIXEL / 8;
void *cam_buffer = heap_caps_malloc(cam_buffer_size, EXAMPLE_DVP_CAM_BUF_ALLOC_CAPS);
if (!cam_buffer) {
ESP_LOGE(TAG, "no mem for cam_buffer");
return;
}
lcd_display_init(&lcd_panel_hdl, lcd_io_hdl);
//----------CAM Controller Init------------//
esp_cam_ctlr_handle_t cam_handle = NULL;
esp_cam_ctlr_dvp_pin_config_t pin_cfg = {
.data_width = EXAMPLE_DVP_CAM_DATA_WIDTH,
.data_io = {
EXAMPLE_DVP_CAM_D0_IO,
EXAMPLE_DVP_CAM_D1_IO,
EXAMPLE_DVP_CAM_D2_IO,
EXAMPLE_DVP_CAM_D3_IO,
EXAMPLE_DVP_CAM_D4_IO,
EXAMPLE_DVP_CAM_D5_IO,
EXAMPLE_DVP_CAM_D6_IO,
EXAMPLE_DVP_CAM_D7_IO,
},
.vsync_io = EXAMPLE_DVP_CAM_VSYNC_IO,
.de_io = EXAMPLE_DVP_CAM_DE_IO,
.pclk_io = EXAMPLE_DVP_CAM_PCLK_IO,
.xclk_io = EXAMPLE_DVP_CAM_XCLK_IO,
};
esp_cam_ctlr_dvp_config_t dvp_config = {
.ctlr_id = 0,
.clk_src = CAM_CLK_SRC_DEFAULT,
.h_res = CONFIG_EXAMPLE_CAM_HRES,
.v_res = CONFIG_EXAMPLE_CAM_VRES,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 64,
.pin = &pin_cfg,
.bk_buffer_dis = 1,
.xclk_freq = EXAMPLE_DVP_CAM_XCLK_FREQ_HZ,
};
ret = esp_cam_new_dvp_ctlr(&dvp_config, &cam_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "dvp init fail[%d]", ret);
return;
}
//--------Camera Sensor and SCCB Init-----------//
example_sensor_config_t cam_sensor_config = {
.i2c_port_num = I2C_NUM_0,
.i2c_sda_io_num = EXAMPLE_DVP_CAM_SCCB_SDA_IO,
.i2c_scl_io_num = EXAMPLE_DVP_CAM_SCCB_SCL_IO,
.port = ESP_CAM_SENSOR_DVP,
.format_name = EXAMPLE_CAM_FORMAT,
};
example_sensor_handle_t sensor_handle = {
.sccb_handle = NULL,
.i2c_bus_handle = NULL,
};
example_sensor_init(&cam_sensor_config, &sensor_handle);
//--------Register Camera Callbacks----------//
example_cam_context_t cam_ctx = {
.panel_hdl = lcd_panel_hdl,
.cam_trans = {
.buffer = cam_buffer,
.buflen = cam_buffer_size,
}
};
esp_cam_ctlr_evt_cbs_t cbs = {
.on_get_new_trans = s_camera_get_new_vb,
.on_trans_finished = s_camera_get_finished_trans,
};
if (esp_cam_ctlr_register_event_callbacks(cam_handle, &cbs, &cam_ctx) != ESP_OK) {
ESP_LOGE(TAG, "ops register fail");
return;
}
//--------Enable and start Camera Controller----------//
ESP_ERROR_CHECK(esp_cam_ctlr_enable(cam_handle));
if (esp_cam_ctlr_start(cam_handle) != ESP_OK) {
ESP_LOGE(TAG, "Driver start fail");
return;
}
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
static bool s_camera_get_new_vb(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data)
{
example_cam_context_t *ctx = (example_cam_context_t *)user_data;
*trans = ctx->cam_trans;
return false;
}
static bool s_camera_get_finished_trans(esp_cam_ctlr_handle_t handle, esp_cam_ctlr_trans_t *trans, void *user_data)
{
example_cam_context_t *ctx = (example_cam_context_t *)user_data;
esp_lcd_panel_draw_bitmap(ctx->panel_hdl, 0, 0, CONFIG_EXAMPLE_CAM_HRES, CONFIG_EXAMPLE_CAM_VRES, trans->buffer);
return false;
}

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
//----------CAM Config------------//
#define EXAMPLE_RGB565_BITS_PER_PIXEL 16
#define EXAMPLE_DVP_CAM_SCCB_SCL_IO (5)
#define EXAMPLE_DVP_CAM_SCCB_SDA_IO (4)
#define EXAMPLE_DVP_CAM_XCLK_FREQ_HZ (20000000)
#define EXAMPLE_DVP_CAM_DATA_WIDTH (8)
#define EXAMPLE_DVP_CAM_D0_IO (11)
#define EXAMPLE_DVP_CAM_D1_IO (9)
#define EXAMPLE_DVP_CAM_D2_IO (8)
#define EXAMPLE_DVP_CAM_D3_IO (10)
#define EXAMPLE_DVP_CAM_D4_IO (12)
#define EXAMPLE_DVP_CAM_D5_IO (18)
#define EXAMPLE_DVP_CAM_D6_IO (17)
#define EXAMPLE_DVP_CAM_D7_IO (16)
#define EXAMPLE_DVP_CAM_XCLK_IO (15)
#define EXAMPLE_DVP_CAM_PCLK_IO (13)
#define EXAMPLE_DVP_CAM_DE_IO (7)
#define EXAMPLE_DVP_CAM_VSYNC_IO (6)
#define EXAMPLE_DVP_CAM_HSYNC_IO (-1)
#if CONFIG_SPIRAM
#define EXAMPLE_DVP_CAM_BUF_ALLOC_CAPS (MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)
#else
#define EXAMPLE_DVP_CAM_BUF_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA)
#endif
#define EXAMPLE_CAM_FORMAT "DVP_8bit_20Minput_RGB565_240x240_25fps" // ov2640
#ifndef EXAMPLE_CAM_FORMAT
#error "Unsupported camera format! Please adjust EXAMPLE_CAM_HRES and EXAMPLE_CAM_VRES in menuconfig"
#endif
//----------LCD Config------------//
#define EXAMPLE_LEDC_DVP_XCLK (LEDC_TIMER_0)
#define EXAMPLE_LEDC_LCD_BACKLIGHT (LEDC_TIMER_1)
#define EXAMPLE_LCD_SPI_NUM (SPI3_HOST)
#define EXAMPLE_LCD_CMD_BITS (8)
#define EXAMPLE_LCD_PARAM_BITS (8)
/* LCD Display */
#define EXAMPLE_LCD_SPI_MOSI (GPIO_NUM_47)
#define EXAMPLE_LCD_SPI_CLK (GPIO_NUM_21)
#define EXAMPLE_LCD_SPI_CS (GPIO_NUM_44)
#define EXAMPLE_LCD_DC (GPIO_NUM_43)
#define EXAMPLE_LCD_RST (GPIO_NUM_NC)
#define EXAMPLE_LCD_BACKLIGHT (GPIO_NUM_48)
#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (80 * 1000 * 1000)
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,3 @@
dependencies:
sensor_init:
path: ${IDF_PATH}/examples/peripherals/camera/common_components/sensor_init

View File

@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
def test_dvp_spi_lcd_p4(dut: Dut) -> None:
dut.expect_exact('Calling app_main()')

View File

@@ -0,0 +1 @@
CONFIG_CAMERA_OV2640=y

View File

@@ -0,0 +1,2 @@
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM=y

View File

@@ -0,0 +1,2 @@
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_OCT=y