ESP-IDF 相机控制器驱动(esp_driver_cam)开发指南:MIPI CSI / ISP DVP / LCD_CAM DVP 统一接口实战
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
本指南以 ESP-IDF 官方文档 Camera Controller Driver 为主体,系统讲解esp_driver_cam组件中相机控制器驱动的完整使用方法:如何在 MIPI CSI、ISP DVP 与 LCD_CAM DVP 三种硬件通路之间做资源分配、驱动使能、启停、收帧与事件回调,并给出缓存关闭(Cache-Safe / IRAM-Safe)场景下的配置建议。读完本文,你将能够基于esp_cam_ctlr_*系列 API 快速搭建一条"相机传感器 → 控制器 → 应用缓冲区"的完整数据通路,并理解其底层接口抽象与 SOC 能力约束。
概述:三种硬件通路与一个统一驱动接口
ESP32 系列芯片为连接外部相机传感器设计了三种硬件通路,在 components/esp_driver_cam 组件中分别由三个工厂函数承载:
- MIPI CSI(
SOC_MIPI_CSI_SUPPORTED):MIPI Camera Serial Interface,面向 MIPI CSI 协议相机,对应esp_cam_new_csi_ctlr; - ISP DVP(
SOC_ISP_DVP_SUPPORTED):经 ISP 模块接入的 Digital Video Port,对应esp_cam_new_isp_dvp_ctlr; - LCD_CAM DVP(
SOC_LCDCAM_CAM_SUPPORTED):经 LCD_CAM 模块实现的 DVP 端口,对应esp_cam_new_lcd_cam_ctlr。
这些能力宏定义在各芯片的soc_caps.h中,从仓库源码可以确认当前的支持矩阵:esp32p4 同时使能了SOC_LCDCAM_CAM_SUPPORTED、SOC_MIPI_CSI_SUPPORTED与SOC_ISP_DVP_SUPPORTED;esp32s3 与 esp32s31 使能了SOC_LCDCAM_CAM_SUPPORTED。
无论底层走哪条通路,驱动最终都返回统一的esp_cam_ctlr_handle_t句柄。这一统一抽象定义在 esp_cam_ctlr_interface.h 中:struct esp_cam_ctlr_t是一张纯函数指针表,包含enable、start、stop、disable、receive、del、register_event_callbacks、get_internal_buffer、get_buffer_len、alloc_buffer、format_conversion等操作。公共 API 层 esp_cam_ctlr.c 只是对这些函数指针做参数校验(如空指针检查、能力未实现检查)后转发,因此上层业务代码可以完全与具体硬件通路解耦。
资源分配:创建相机控制器实例
三种工厂函数
创建控制器实例有三种方式,按 SOC 能力选择其一:
esp_cam_new_csi_ctlr(MIPI CSI,配置结构体esp_cam_ctlr_csi_config_t)esp_cam_new_isp_dvp_ctlr(ISP DVP,配置结构体esp_cam_ctlr_isp_dvp_cfg_t,需先创建 ISP 处理器句柄)esp_cam_new_lcd_cam_ctlr(LCD_CAM DVP,配置结构体esp_cam_ctlr_dvp_config_t)
创建成功后返回控制器句柄;失败时返回错误码。从 esp_cam_ctlr_csi.h 的注释可以看到典型返回值:ESP_OK、ESP_ERR_INVALID_ARG、ESP_ERR_NO_MEM、ESP_ERR_NOT_SUPPORTED,以及ESP_ERR_NOT_FOUND(表示该通路控制器已被注册)。
方式一:MIPI CSI 控制器
以下示例创建一个 2-lane MIPI CSI 控制器,输入 RAW8、输出 RGB565:
#include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" #include "esp_cam_ctlr_csi.h" void app_main(void) { esp_cam_ctlr_csi_config_t csi_config = { .ctlr_id = 0, .h_res = MIPI_CSI_DISP_HSIZE, .v_res = MIPI_CSI_DISP_VSIZE_640P, .lane_bit_rate_mbps = MIPI_CSI_LANE_BITRATE_MBPS, .input_data_color_type = CAM_CTLR_COLOR_RAW8, .output_data_color_type = CAM_CTLR_COLOR_RGB565, .data_lane_num = 2, .byte_swap_en = false, .queue_items = 1, }; esp_cam_ctlr_handle_t handle = NULL; ESP_ERROR_CHECK(esp_cam_new_csi_ctlr(&csi_config, &handle)); }esp_cam_ctlr_csi_config_t的完整字段见 esp_cam_ctlr_csi.h,要点如下:
| 字段 | 含义 | 备注 |
|---|---|---|
ctlr_id | CSI 控制器 ID | 通常取 0 |
clk_src | CSI PHY 时钟源 | mipi_csi_phy_clock_source_t类型 |
h_res/v_res | 输入水平 / 垂直分辨率 | 单位分别为像素/行、行/帧 |
data_lane_num | 数据 lane 数 | 如 1、2、4 |
lane_bit_rate_mbps | 单 lane 比特率 | 单位 Mbps |
input_data_color_type/output_data_color_type | 输入 / 输出颜色格式 | 见下方cam_ctlr_color_t |
data_type | 自定义数据类型 | 默认置 0 即可支持 embedded data 与标准数据类型 |
queue_items | 队列项数 | 内部缓冲队列深度 |
input_8bit_swap_en | 输入 8bit 位交换 | 使能后[31:24][23:16][15:8][7:0]→[7:0][15:8][23:16][31:24] |
input_16bit_swap_en | 输入 16bit 位交换 | 使能后[31:16][15:0]→[15:0][31:16] |
byte_swap_en | 输出字节交换 | 调节输出字节序 |
bk_buffer_dis | 禁用备用缓冲区 | 置 1 时驱动不再分配内部 backup buffer |
MIPI CSI 供电要求:CSI 外设在芯片内需要稳定的 2.5 V 电源,使用前请对照原理图确认电源引脚已接 2.5 V。在支持内部可调 LDO(SOC_GP_LDO_SUPPORTED)的芯片上,可以将 LDO 通道输出引脚接到 CSI 供电引脚,在初始化 CSI 驱动前调用 ldo_regulator 的 API 将 LDO 配置为输出 2.5 V。
方式二:ISP DVP 控制器
使用 ISP DVP 通路时,必须先通过esp_isp_new_processor创建 ISP 处理器句柄,再以该句柄创建 DVP 控制器:
#include "esp_err.h" #include "esp_cam_ctlr.h" #include "esp_cam_ctlr_isp_dvp.h" #include "driver/isp.h" #define MIPI_CSI_DISP_HSIZE 800 // example value, replace with actual resolution #define MIPI_CSI_DISP_VSIZE 600 // example value, replace with actual resolution void app_main(void) { isp_proc_handle_t isp_proc = NULL; esp_isp_processor_cfg_t isp_config = { .clk_hz = 120 * 1000 * 1000, .input_data_source = ISP_INPUT_DATA_SOURCE_DVP, .input_data_color_type = ISP_COLOR_RAW8, .output_data_color_type = ISP_COLOR_RGB565, .has_line_start_packet = false, .has_line_end_packet = false, .h_res = MIPI_CSI_DISP_HSIZE, .v_res = MIPI_CSI_DISP_VSIZE, }; ESP_ERROR_CHECK(esp_isp_new_processor(&isp_config, &isp_proc)); esp_cam_ctlr_handle_t cam_handle = NULL; esp_cam_ctlr_isp_dvp_cfg_t dvp_ctlr_config = { .data_width = 8, .data_io = {53, 54, 52, 0, 1, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1}, .pclk_io = 21, .hsync_io = 5, .vsync_io = 23, .de_io = 22, .io_flags.vsync_invert = 1, .queue_items = 10, }; ESP_ERROR_CHECK(esp_cam_new_isp_dvp_ctlr(isp_proc, &dvp_ctlr_config, &cam_handle)); }esp_cam_ctlr_isp_dvp_cfg_t的关键字段(esp_cam_ctlr_isp_dvp.h):
data_width:数据线宽度(cam_ctlr_data_width_t,可取值 8/10/12/16,见 cam_ctlr_types.h);data_io[16]:DVP 数据输入 IO,最多 16 路,未用引脚填-1(ESP_CAM_CTLR_ISP_DVP_DATA_SIG_NUM_MAX定义为 16);pclk_io/hsync_io/vsync_io/de_io:像素时钟、行同步、场同步、数据使能引脚;io_flags:pclk_invert、hsync_invert(低有效)、vsync_invert(高有效)、de_invert(低有效),用于适配不同传感器极性;queue_items:队列项数。
方式三:LCD_CAM DVP 控制器
LCD_CAM DVP 通路先配置引脚(esp_cam_ctlr_dvp_pin_config_t),再配置控制器(esp_cam_ctlr_dvp_config_t):
#include "esp_err.h" #include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" #include "esp_cam_ctlr_dvp.h" void app_main(void) { 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, // Set XCLK pin to generate XCLK signal }; 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, .output_data_color_type = CAM_CTLR_COLOR_RGB565, .dma_burst_size = 128, .pin = &pin_cfg, .bk_buffer_dis = 1, .xclk_freq = EXAMPLE_DVP_CAM_XCLK_FREQ_HZ, }; ESP_ERROR_CHECK(esp_cam_new_dvp_ctlr(&dvp_config, &cam_handle)); }esp_cam_ctlr_dvp_config_t的完整字段见 esp_cam_ctlr_dvp.h,要点:
external_xtal:置 1 时使用外部晶振产生 XCLK,此时xclk_io与驱动内部生成的输出时钟都会被忽略;否则由驱动内部生成 XCLK;cam_data_width:数据位宽(8/16/24 位,默认 8);bit_swap_en/byte_swap_en:位交换 / 字节交换开关,其组合效果可参考头文件内附的 GDMA 数据字节序表(以输入B0,B1,B2,B3,B4,B5、地址由低到高为例):
| cam_data_width | bit_swap_en | byte_swap_en | 阶段 1 输出数据序列 |
|---|---|---|---|
| 8-bit | 0 | 0 | {B0}{B1}{B2}{B3}{B4}{B5} |
| 8-bit | 0 | 1 | {B1,B0}{B3,B2}{B5,B4} |
| 8-bit | 1 | 0 | {B0'}{B1'}{B2'}{B3'}{B4'}{B5'} |
| 8-bit | 1 | 1 | {B1',B0'}{B3',B2'}{B5',B4'} |
| 16-bit | 0 | 0 | {B1,B0}{B3,B2}{B5,B4} |
| 16-bit | 0 | 1 | {B0,B1}{B2,B3}{B4,B5} |
| 24-bit | 0 | 0 | {B2,B1,B0}{B5,B4,B3} |
| 24-bit | 0 | 1 | {B0,B1,B2}{B3,B4,B5} |
其中B0'表示按位取反序(Bn'[7:0] = Bn[0:7]),{}内为大端并行数据,{}之间为串行输出、从左到右。
bk_buffer_dis:禁用备用缓冲区;pin_dont_init:若此前已调用过esp_cam_ctlr_dvp_init,可跳过引脚初始化;pic_format_jpeg:输入为 JPEG 时置位,此时input_data_color_type会被忽略;dma_burst_size:DMA 突发传输块大小,置 0 表示禁用突发,其他值必须为 2 的幂(如 4/8/16/32/64/128);xclk_freq:DVP 输出时钟频率(Hz)。
创建 DVP 控制器后,需要分配满足对齐约束的相机缓冲区,可自行分配,也可调用esp_cam_ctlr_alloc_buffer自动分配(该函数必须在esp_cam_new_*_ctlr之后调用,buf_caps支持MALLOC_CAP_SPIRAM || MALLOC_CAP_DMA或MALLOC_CAP_INTERNAL || MALLOC_CAP_DMA,见 esp_cam_ctlr.h)。
颜色格式枚举:三种通路共用的cam_ctlr_color_t定义于 cam_ctlr_types.h,采用 FourCC 编码,包括CAM_CTLR_COLOR_RAW8/RAW10/RAW12、CAM_CTLR_COLOR_RGB565、CAM_CTLR_COLOR_RGB888、CAM_CTLR_COLOR_YUV420、四种 YUV422 变体(YVYU/YUYV/UYVY/VYUY)与CAM_CTLR_COLOR_GRAY8。
格式转换
LCD_CAM DVP 通路支持调用esp_cam_ctlr_format_conversion配置格式转换(配置结构体cam_ctlr_format_conv_config_t,包含src_format、dst_format、conv_std、data_width、input_range、output_range,见 cam_ctlr_types.h)。驱动支持以下转换类型与色域范围:
- 转换类型:YUV → RGB、RGB → YUV、YUV → YUV;
- 色域范围:Full range(RGB 与 YUV 均为 0-255);Limited range(RGB 16-240,YUV Y 为 16-240、U-V 为 16-235)。
PCLK 频率限制(来自文档注意事项):
cam_data_width = 8时:建议 CAM_PCLK 频率低于 80 MHz;若同时启用 YUV-RGB 格式转换,建议低于 60 MHz;cam_data_width = 16时:建议 CAM_PCLK 频率低于 40 MHz;若同时启用 YUV-RGB 格式转换,建议低于 30 MHz。
卸载驱动
当不再需要相机控制器时,调用esp_cam_ctlr_del释放底层硬件资源。公共层实现位于 esp_cam_ctlr.c,同样会先做空指针校验再转发到对应通路的del实现。
使能与禁用:init ↔ enable
对控制器执行任何操作前,先调用esp_cam_ctlr_enable将驱动状态从init切换到enable:
#include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" #include "esp_err.h" void app_main(void) { esp_cam_ctlr_handle_t handle; ESP_ERROR_CHECK(esp_cam_ctlr_enable(handle)); }需要回到init状态时调用esp_cam_ctlr_disable:
#include "esp_err.h" #include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" void app_main(void) { esp_cam_ctlr_handle_t handle; ESP_ERROR_CHECK(esp_cam_ctlr_disable(handle)); }两个 API 的返回值均为ESP_OK、ESP_ERR_INVALID_ARG(参数非法)或ESP_ERR_INVALID_STATE(状态非法),见 esp_cam_ctlr.h。
启动与停止:enable ↔ start
开始接收相机信号前,调用esp_cam_ctlr_start将驱动状态从enable切换到start:
#include "esp_err.h" #include "esp_log.h" #include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" void app_main(void) { esp_cam_ctlr_handle_t handle = NULL; ESP_ERROR_CHECK(esp_cam_ctlr_start(handle)); ESP_LOGI("CAM", "Camera controller started successfully"); }停止接收并回到enable状态则调用esp_cam_ctlr_stop:
#include "esp_err.h" #include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" void app_main(void) { esp_cam_ctlr_handle_t handle = NULL; ESP_ERROR_CHECK(esp_cam_ctlr_stop(handle)); }由此可归纳驱动的生命周期状态机:init → enable → start,逆向依次为start → enable → init,每一层都由一对 API 完成单向切换。
接收相机数据
通过esp_cam_ctlr_receive接收来自相机传感器的数据。事务描述符类型esp_cam_ctlr_trans_t包含三个字段(esp_cam_ctlr_types.h):buffer(事务缓冲区指针)、buflen(缓冲区长度)、received_size(实际接收长度,由驱动回填):
#include "esp_err.h" #include "esp_cam_ctlr.h" #include "esp_cam_ctlr_types.h" ESP_ERROR_CHECK(esp_cam_ctlr_receive(handle, &my_trans, ESP_CAM_CTLR_MAX_DELAY));其中ESP_CAM_CTLR_MAX_DELAY定义为UINT32_MAX(esp_cam_ctlr_types.h),表示不设超时上限;timeout_ms以毫秒为单位,可传入任意有限超时值。
注册事件回调
驱动开始接收数据后可能动态产生事件。通过esp_cam_ctlr_register_event_callbacks注册用户回调,回调集合定义于esp_cam_ctlr_evt_cbs_t(esp_cam_ctlr_types.h),包含三个回调:
on_get_new_trans:驱动完成一个事务并尝试获取新的事务描述符时被调用;在 CSI 通路的s_ctlr_csi_start中也会被调用。若该回调未提供新的事务描述符,且bk_buffer_dis标志未置位,驱动会使用内部备用缓冲区;on_trans_finished:驱动完成一个事务时被调用。该函数运行在 ISR 上下文中,必须避免阻塞,例如只能使用带ISR后缀的 FreeRTOS API;on_error:检测到错误时被调用,同样运行在 ISR 上下文,禁止调用阻塞 API。当控制器的 ISR Cache-Safe 选项使能时,该回调及其 user data 必须放在内部 RAM 中。错误事件数据esp_cam_ctlr_error_event_data_t携带csi_host_err_evts位掩码,可组合ESP_CAM_CTLR_CSI_HOST_ERR_PHY(PHY 错误)、ESP_CAM_CTLR_CSI_HOST_ERR_PACKET(数据包错误)、ESP_CAM_CTLR_CSI_HOST_ERR_FRAME(帧边界/序列错误)、ESP_CAM_CTLR_CSI_HOST_ERR_CRC(帧或负载 CRC 错误)、ESP_CAM_CTLR_CSI_HOST_ERR_DATA_ID(无法识别或不受支持的数据类型)等标志,具体枚举见 esp_cam_ctlr_types.h。
此外,若启用了内部备用缓冲区,可调用esp_cam_ctlr_get_frame_buffer/esp_cam_ctlr_get_frame_buffer_len获取内部缓冲区地址与长度——内部缓冲区的数据通常在on_trans_finished事件发生时已就绪(见 esp_cam_ctlr.h)。
线程安全
以下工厂函数是线程安全的,可以从不同的 RTOS 任务中直接调用而无需额外加锁:
esp_cam_new_csi_ctlr(SOC_MIPI_CSI_SUPPORTED)esp_cam_new_isp_dvp_ctlr(SOC_ISP_DVP_SUPPORTED)esp_cam_ctlr_del
Kconfig 配置选项
相机控制器驱动提供三个 Kconfig 选项,均位于 components/esp_driver_cam/Kconfig,用于控制缓存禁用场景下的中断行为:
| 选项 | 依赖 | 默认值 | 作用 |
|---|---|---|---|
CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE | SOC_MIPI_CSI_SUPPORTED | n | 确保 CSI 驱动 ISR 在缓存关闭时仍可用 |
CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE | SOC_ISP_DVP_SUPPORTED | n | 确保 ISP DVP 驱动 ISR 在缓存关闭时仍可用 |
CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE | SOC_LCDCAM_CAM_SUPPORTED | n | 确保 DVP 驱动 ISR 在缓存关闭时仍可用 |
使能 CSI/ISP_DVP 的 Cache-Safe 选项后,Kconfig 会联动选择 DW_GDMA 的ISR_IRAM_SAFE、CTRL_FUNC_IN_IRAM、SETTER_FUNC_IN_IRAM、GETTER_FUNC_IN_IRAM;使能 DVP 选项后,会联动选择 GDMA 的ISR_HANDLER_IN_IRAM、CTRL_FUNC_IN_IRAM(在SOC_GDMA_SUPPORTED时生效)。
IRAM Safety:缓存禁用时的中断可靠性
默认情况下,在 Flash 写入或擦除等缓存被禁用的操作期间,CSI 中断会被延迟,直到缓存重新使能后才被处理。启用上述 Cache-Safe Kconfig 选项会带来以下效果:
- 即使缓存被禁用,中断也能得到及时服务;
- ISR 用到的所有函数被放入 IRAM;
- 驱动对象被放入 DRAM(避免映射到 PSRAM)。
代价是 IRAM 占用增加。在缓存关闭期间,必须确保用户回调及其相关代码/数据同样是 IRAM-Safe 或 DRAM-Safe(例如使用IRAM_ATTR修饰回调函数、将数据放在内部 RAM),否则仍可能出现取指/取数异常。
应用示例
仓库提供了三个开箱即用的完整示例(均位于 examples/peripherals/camera),演示了从采集到显示的完整链路:
- mipi_isp_dsi:使用
esp_driver_cam组件,经 ISP 模块采集 MIPI CSI 相机信号,并通过 DSI 接口在 LCD 上显示; - dvp_isp_dsi:使用
esp_driver_cam组件,经 ISP 模块采集 DVP 相机信号,并通过 DSI 接口在 LCD 上显示; - dvp_dsi:使用
esp_driver_cam组件采集 DVP 相机数据,并在 MIPI DSI LCD 上显示。
配套的自动化测试位于 components/esp_driver_cam/test_apps(csi / dvp / isp_dvp 三个子目录),测试代码覆盖了驱动创建、使能、收帧等核心路径,例如test_csi_ov5647.c、test_dvp_ov2640.c、test_isp_dvp_driver.c,并配有 cache_safe、pm_enable、release 等不同sdkconfig.ci.*构建配置,可帮助理解不同场景下的正确用法。
小结
相机控制器驱动通过统一的esp_cam_ctlr_handle_t句柄屏蔽了 MIPI CSI、ISP DVP、LCD_CAM DVP 三种硬件通路的差异:资源分配阶段按 SOC 能力选择对应的工厂函数并配置各自的配置结构体,运行阶段则使用同一套 enable/start/receive/stop/disable 状态机 API。需要特别留意三点:MIPI CSI 的 2.5 V 供电(必要时用内部 LDO 提供)、DVP 通路的 PCLK 频率上限(尤其开启格式转换后)、以及缓存关闭场景下通过 Cache-Safe Kconfig 选项配合 IRAM/DRAM 安全的回调代码。对上述细节的完整实现参考,可继续阅读 esp_cam_ctlr.h、esp_cam_ctlr_types.h 及各通路头文件。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考