当前位置: 首页 > news >正文

《DNESP32P4开发指南_V1.0》第三十七章 MIPICAMERA实验

第三十七章 MIPICAMERA实验

本章,我们将介绍MIPI摄像头OV5645,学习如何在ESP32-P4上驱动MIPI摄像头,实现摄像头图像数据采集并进行显示。
本章分为如下几个小节:
37.1 OV5645摄像头介绍及ESP32-P4的CAM相关外设介绍
37.2 硬件设计
37.3 程序设计
37.4 下载验证

37.1 OV5645摄像头介绍及ESP32-P4的MIPI_CSI介绍
37.1.1 OV5645摄像头介绍
OV5645是正点原子推出的一款高性能超清2K摄像头模块。该模块具有一颗1/4英寸CMOS 2592x1944图像传感器OV5645。同时,模块集成了有源晶振和LDO芯片。模块接口有间距2mm的2*11pin排针和间距为0.5mm的22pin的FPC接口,可供用户根据不同的场景选择使用。
模块实物图如下图所示。

image001

image003

图37.1.1.1 OV5645摄像头实物图

OV5645模块具有以下特点:
● 像素大小:1.4微米 x 1.4微米
● 500万像素(2592x1944)
● 可编程控制帧速率,镜像和翻转,裁剪和窗口
● 支持图像大小:2K(2560x1440)、1080P(1920x1080)、720P(1280x720)等
● 双线串行总线控制(SCCB)
● 双通道MIPI串行输出
● 集成有源晶振,无需外部提供时钟
● 集成LDO,仅需提供3.3V电源即可正常工作
● 自动图像控制功能:自动曝光控制(AEC)、自动白平衡(AWB)、自动带通滤波器(ABF)、自动黑电平校准(ABLC)、自动50/60Hz亮度检测
● 可编程控制:帧率、图像大小、水平反射镜、垂直翻转、裁剪和平移
● 支持RawRGB、RGB565、RGB555、RGB444、YUV422、YUV420、YCbCr422和压缩图像(JPEG)输出格式
● 电源核心:1.5±5%;模拟量:2.6V ~ 3.0V;输入/输出:1.7V ~ 3.0V
● 工作温度范围:-30℃ ~ 70℃,稳定成像温度范围:0 ~ 50℃
OV5645的内部结构如下图所示:

image005

图 OV5645内部结构图

由上图可知,时序发生器timing generator控制着感光阵列image array、放大器AMP、AD转换以及输出外部时序信号(VSYNC、HREF和PCLK),外部时钟XVCLK经过PLL锁相环后输出的时钟作为系统的控制时钟;感光阵列将光信号转化成模拟信号,经过增益放大器之后进入10位AD转换器;AD 转换器将模拟信号转化成数字信号,并且经过ISP进行相关图像处理,最终输出所配置格式视频数据流。增益放大器控制以及 ISP 等都可以通过寄存器(registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容IIC协议。
SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OmniVision的简称)公司定义和发展的三线式串行总线,该总线控制着摄像头大部分的功能,包括图像数据格式、分辨率以及图像处理参数等。OV公司为了减少传感器引脚的封装,现在 SCCB 总线大多采用两线式接口总线。
OV5645使用的是两线式SCCB接口总线,该接口总线包括SIO_C串行时钟输入线和SIO_D 串行双向数据线,分别相当于IIC协议的SCL信号线和SDA信号线。我们在前面提到过SCCB协议兼容IIC协议,是因为SCCB协议和IIC协议非常相似,有关IIC协议的详细介绍请大家参考“IIC_EXIO”章节。
OV5645是用16位(两个字节)表示寄存器地址。OV5645 SCCB的写传输协议如下图所示:

image007

图 SCCB 写传输协议

上图中的ID Address(W)由7位器件地址和1位读写控制位构成(0:写;1:读),而OV5645的器件地址为0x3C,所以在写传输协议中,ID Address(W)= 0x78(器件地址左移1位,低位补0);Sub-address(H)为高8位寄存器地址,Sub-address(L)为低 8 位寄存器地址,在OV5645众多寄存器中,有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入;Write Data为8位写数据,每一个寄存器地址对应8位的配置数据。上图中的第9位X表示Don’t Care(不必关心位),该位是由从机(此处指OV5645)发出应答信号来响应主机表示当前ID Address、Sub-address和Write Data是否传输完成,但是从机有可能不发出应答信号,因此主机可不用判断此处是否有应答,直接默认当前传输完成即可。
我们可以发现,SCCB和IIC写传输协议是极为相似的,只是在SCCB写传输协议中,第9位为不必关心位,而IIC写传输协议为应答位。SCCB的读传输协议和IIC有些差异,在IIC读传输协议中,写完寄存器地址后会有restart即重复开始的操作;而SCCB读传输协议中没有重复开始的概念,在写完寄存器地址后,发起总线停止信号,下图为SCCB的读传输协议。

image009

图 SCCB 读传输协议

SCCB读传输协议分为两个部分。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。第二部分是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,注意ID Address(R) = 0x43(器件地址左移1位,低位补1)。上图中的NA位由主机产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。
在OV5645正常工作之前,必须先对传感器进行初始化,即通过配置寄存器使其工作在预期的工作模式,以及得到较好画质的图像。因为SCCB的写传输协议和IIC几乎相同,因此我们可以直接使用IIC的驱动程序来配置摄像头。当然这么多寄存器也并非都需要配置,很多寄存器可以采用默认的值。下表是本程序用到的关键寄存器的配置说明。
由于OV5645摄像头的寄存器非常多,并且大多数的寄存器都可以使用默认设置,因此上表只列出了一些关键寄存器,更多寄存器设置大家可以参考官方数据手册。

QQ截图20260715104233

表37.1.1.1 OV5645寄存器表

由于OV5645摄像头的寄存器非常多,并且大多数的寄存器都可以使用默认设置,因此上表只列出了一些关键寄存器,更多寄存器设置大家可以参考官方数据手册。

时钟配置
在上表中列出了0x3035、0x3036和0x3037三个时钟配置寄存器,这三个寄存器在整个时钟链路当中的作用如下图所示:

image011

图 时钟路径

从图中可以看到OV5645外部输入时钟范围是627Mhz(板载24Mhz),外部输入时钟进入摄像头内部PLL,先经过pre-divider,根据0x3037寄存器bit0bit3的值将时钟降为427Mhz(本节实验降低到8Mhz);之后再经过multiplier,判断寄存器0x3036的bit7位是否为1,如果是1则将时钟倍频到0x3036[7:1]*2倍的值,如果不为1则倍频到0x3036[6:0]倍的值(本节实验倍频到0x3036[6:0]倍的值,即112倍,最终输出值为896Mhz),最终输出时钟范围是5001000Mhz;再之后时钟会经过SYS divider0和MIPI divider,前者是判断0x3035寄存器bit4bit7的值是否为0,如果为0则时钟分频16倍,否则分频0x3035[7:4]倍,后者是判断0x3035寄存器bit0bit3的值是否为0,如果为0则时钟分频16倍,否则分频0x3035[3:0]倍(本节实验先分频2倍,再分频1倍,最终输出448Mhz);最后时钟到达MIPI PHY,在这个模块之中直接对时钟进行2分频,最终输出MIPI CLK(本节实验最终的MIPI CLK为224Mhz)。

图像输出开窗设置
OV5645摄像头输出的图像大小是可以调整的,整体的开窗设置如下图所示:

image013

图 开窗示意图

从上图中可以看出OV5645的开窗大小是用(“开窗水平结束位置”减去“开窗水平起始位置”)*(“开窗垂直结束位置”减去“开窗垂直起始位置”),通过寄存器0x3800~0x3807来控制。
开完窗后还可以通过水平方向和垂直方向的偏移寄存器(0x3810~0x3813)来进一步调整大小,最终输出的图像就上图中白色区域部分。这里大家需要注意一下,在整体图像分辨率水平:垂直=4:3的情况下按上图去配置是可以的,如果是其他尺寸比例那么按上图配置不一定能成功,具体的配置需要原厂协助,我们也没有详细的配置方法或者尺寸计算公式。
OV5645输出图像大小不仅可以通过开窗来调整,还可以通过压缩功能来调整,如下图所示:

image015

图 图像压缩

当寄存器0x5001的bit5压缩使能打开(0关闭,1开启),就可以在开窗的基础上输出压缩后的图像,压缩图像等于开窗图像减去2倍水平垂直偏移量。

输出像素格式
从寄存器的配置表中就可以看到OV5645摄像头支持的像素格式非常多,可以通过配置寄存器0x4300,让摄像头输出不同格式的像素数据。本节实验使用的是RAW格式数据,所以接下来只介绍RAW格式的像素数据输出,如下图所示:

image017

图 原始像素格式

从上图中可以看到整个传感器拥有水平2624垂直1964共计5153536个感光像素点,其中有效感光像素点是水平2592垂直1944共计5038848个像素,也就是图中active区域。在有效显示区域中像素数据是按照Bayer阵列排布的。所谓的Bayer阵列其实就是在上图active区域中像素按照2*2的方格排列,如下图所示:

image019

图 Byer阵列

上图中红色方框框出来的就是一个2*2的Byer阵列,可以看到它的组合方式是BGGR,这是摄像头的原始配置,当寄存器0x4300配置为0x00时输出的就是这种组合,第一行数据全部是BGBG,第二行数据全部是GRGR,组成Byer阵列就是BGGR。当然也可以通过改变寄存器0x4300的配置值来改变输出的像素格式,具体的格式配置在错误!未找到引用源。中已经列出。这种Byer阵列数据可以通过插值法来进一步转换成RGB数据,最终在显示屏幕上显示出来。

彩条测试模式
图像传感器配置成彩条测试模式后,会输出彩色的条纹,方便测试图像传感器是否正常工作,通过配置寄存器 0x503d 的Bit[7]位打开和关闭彩条模式。当需要打开彩条模式时,寄存器 0x503d 配置成 0x80,关闭时配置成0x00,下图为打开彩条模式后图像输出的条纹(8个颜色条)。

image021

图 测试彩条

注意彩条最左边是一个白色长条,颜色与背景一致,所以看不清楚。
关于OV5645的基础介绍,我们就介绍到这。OV5645的详细资料请参考光盘:7,硬件资料2,芯片资料 OV5645_CSP3_DS_2.01.pdf这个文档,供大家参考学习。

37.1.2 ESP32-P4的CAMERA模块介绍
ESP32-P4的LCD_CAM控制器包括独立的LCD控制模块和Camera(摄像头)控制模块。在前面已经介绍过了LCD模块,本章节主要介绍Camera模块。
首先通过分析LCD_CAM控制器的结构框图,了解一下其工作过程。结构框图如下图所示。

image007

图37.1.2.1 LCD_CAM控制器结构框图

Camera控制模块(红色虚线以上)包括独立的接收控制单元(Camera_Ctrl)、接收异步FIFO(Async RX FIFO)、时钟生成模块(CAM_Clock Generator)和格式转换模块(RGB/YUV Converter)。
接收控制单元:用于控制CAM数据的接收。CAM数据从上图可知,可由GDMA取自内部或外部存储器。
接收异步FIFO:用于与外部设备进行交互。
CAM时钟生成模块:用于生成CAM_CLK时钟。时钟源经过时钟生成模块处理后,生成Camera模块所需要的时钟CAM_CLK,这一过程如下图所示。

image025

图37.1.2.2 CAM模块时钟生成过程

Camera模块时钟由三个时钟源提供,分别是XTAL_CLK、PLL_F160M_CLK和APLL_CLK。可由HP_SYS_CLKRST_PERI_CLK_CTRL19_REG的HP_SYS_CLKRST_CAM_CLK_SRC_SEL位决定,0选择使用XTAL_CLK,1选择使用PLL_F160M_CLK,2选择使用APLL_CLK。
选择好时钟源后(即得到CAM_CLK_SRC),经过降频得到CAM_CLK,后面再经过分频最终得到CAM_CLK,这三者的关系如下:

image027

Note:
① 第一条公式中的N、b和a都是通过HP_SYS_CLKRST_PERI_CLK_CTRL120_REG寄存器进行配置的。N的取值范围为2≤N≤256,通过HP_SYS_CLKRST_CAM_CLK_DIV_NUM位进行配置。b是通过HP_SYS_CLKRST_CAM_CLK_DIV_NUMERATOR位进行配置,而a是通过HP_SYS_CLKRST_CAM_CLK_DIV_DENONMINATOR位进行配置。
格式转换模块:用于各种格式的视频数据互相转换。在上图中,该部分是虚线框,也就是可不使用该模块。
Camera-LCD控制器的CAM和LCD接口都可通过GPIO交换矩阵灵活配置使用任意GPIO引脚。对于CAM模块的信号,即图37.1.2.1右上角部分,在这里也简单介绍一下,如下表所示。

QQ截图20260715104256

表37.1.2.1 CAM模块信号描述

37.1.3 ESP32-P4 MIPI_CSI接口介绍
ESP32-P4带有一个MIPI CSI接口,用于连接MIPI接口的摄像头,具有如下特性:
● 符合MIPI CSI-2协议。
● 使用DPHY v1.1版本。
● 2-lane x 1.5 Gbps。
● 输入格式支持RGB888、RGB666、RGB565、YUV422、YUV420、RAW8、RAW10、RAW12。
MIPI CSI接口使用专用数字管脚,管脚序号为42~48。

37.1.4 图像信号处理器ISP介绍
ESP32-P4带有一个图像信号处理器(ISP),具有如下特性:
最大分辨率 1920 x 1080
三个输入通道:MIPI CSI、DVP、DW-GDMA
输入格式:RAW8、RAW10、RAW12
输出格式:RAW8、RGB888、RGB565、YUV422、YUV420
pipeline结构:拜尔域降噪(BF)、去马赛克(Demosaic)、色彩矩阵(CCM)、伽马矫正(Gamma Correction)、锐化(Edge)、对比度色相饱和度亮度调节(Contrast/Hue/Saturation/Brightness)、自动曝光统计(AE)、自动对焦统计(AF)、白平衡统计(AWB)、直方图统计(HIST)。

37.1.5 像素处理加速器PPA外设介绍
ESP32-P4带有像素处理加速器(PPA),可实现SRM与BLEND两大功能,具有如下特性:
SRM可实现图像旋转、缩放、镜像
- 输入格式支持ARGB8888、RGB888、RGB565、YUV420
- 输出格式支持ARGB8888、RGB888、RGB565、YUV420
- 支持逆时针90°、180°、270°旋转
- 支持水平、垂直方向缩放,缩放系数的整数部分为8-bit,小数部分为4-bit
- 支持水平、垂直方向镜像
BLEND可实现两个相同尺寸的图层叠加
- 输入格式支持ARGB8888、RGB888、RGB565、L4、L8、A4、A8
- 输出格式支持ARGB8888、RGB888、RGB565
- 支持两个图层基于Alpha通道叠加。若图层不包含Alpha通道信息,通过寄存器配置补全
- 通过设置前景以及背景color-key范围实现特殊颜色过滤

37.1.6 CAMERA驱动框架介绍
CAMERA驱动框架主要用到的是V4L2框架,即Video for Linux 2,是Linux系统中用于处理视频设备的内核驱动程序接口。
V4L2提供了一种标准化的方式,使用户空间程序能够与视频设备(如摄像头、视频采集卡等)进行通信和交互。它屏蔽了底层摄像头的不同驱动实现,为用户空间提供了统一的接口调用。

37.2 硬件设计
37.2.1 例程功能
开机的时候,初始化OV5645摄像头。初始化成功后直接在LCD上输出图像。 LED0用来指示程序正在运行。

37.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)
3)MIPICAMERA
CSI_D0_P(固定引脚) CSI_D0_N(固定引脚)
CSI_D1_P(固定引脚) CSI_D1_N(固定引脚)
CSI_CK_P(固定引脚) CSI_CK_N(固定引脚)
CSI_RST - IO34 CSI_PWDN - IO54
IIC_SCL - IO32 IIC_SDA - IO33

37.2.3 原理图
CAMERA相关原理图,如下图所示。

image029

图37.2.3.1 摄像头实验原理图

这里说明一下,与OV5645通信的引脚都是1.8V的,所以通过CJ3134芯片进行转换。

37.3 程序设计
37.3.1 CAMERA的IDF驱动
esp_video、esp_sccb_intf、esp_cam_sensor组件驱动位于ESP-IDF在线组件注册表中。如果需要将该组件添加到项目工程中,可按照以下步骤操作:
1)打开ESP-IDF注册表。
2)搜索 “esp_video”、“esp_sccb_intf”、“esp_cam_sensor”组件。
3)将组件安装到项目中。
组件安装完成后,系统会自动更新main文件夹中的特殊组件清单文件idf_component.yml在项目编译时,系统会根据清单文件从注册表中下载并集成该组件到工程中。关于上述操作流程,可参考本书籍第八章的内容。
注意:在进行ESP32摄像头模块(如OV5645)开发时,必须对电源管理进行一些关键的调整。首先,在esp_cam_sensor组件下的ov5645.c文件中的ov5645_power_on函数中,我们需要确保pwdn(Power Down)管脚在初始化时先拉低再拉高。这一修改是根据OV5645摄像头的数据手册要求的,因为不按照这个时序操作,摄像头可能无法正常启动或工作不稳定。接着,在esp_video组件中的esp_video_csi_device.c文件里,我们将CSI_LDO_CFG_VOL_MV的电压值设置为1900mV。这是因为LDO3的电压在LCD初始化时已经被定义为1900mV,为了确保系统电源的一致性,必须将此电压值与LCD初始化时的配置保持一致。若电压不一致,可能会导致系统不稳定或出现运行错误。通过这些调整,我们可以确保摄像头模块和视频系统的电源管理符合硬件要求,并提高系统的稳定性,避免因电压不匹配或初始化顺序错误导致的潜在问题。这里的组件过多嵌套,暂时不做说明,请参考应用层代码即可。

37.3.2 程序流程图

image032

图37.3.2.1 摄像头实验程序流程图

37.3.3 程序解析
在28_mipicamera例程中,作者在28_mipicamera\main\APP路径下新建了1个文件夹MIPI_CAM,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. MIPI_CAM驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。MIPI_CAM驱动源码包括四个文件:mipi_cam.c、mipi_cam.h、app_video.c和app_video.h。
其中mipi_cam.c和mipi_cam.h主要是MIPI摄像头的驱动文件,而app_video.c和app_video.h主要是关于MIPI摄像头调用相对下层接口实现。
在mipi_cam.h中主要是函数声明,大家自行查看源码即可。
下面我们再解析mipi_cam.c的程序,首先先来看一下初始化函数mipi_cam_init,代码如下:

/*** @brief     	mipi_cam初始化* @param     	无* @retval    	ESP_OK:开启成功; ESP_FAIL:开启失败*/
esp_err_t mipi_cam_init(void)
{esp_err_t ret = ESP_OK;ret = app_video_main(bus_handle);			/* 初始化摄像头硬件和软件接口 */if (ret != ESP_OK){ESP_LOGE(mipi_cam_tag, "video main init failed with error 0x%x", ret);return ESP_FAIL;}int video_cam_fd0 = app_video_open(0);  	/* 打开摄像头设备 */if (video_cam_fd0 < 0){ESP_LOGE(mipi_cam_tag, "video cam open failed");return ESP_FAIL;}/* 初始化设备并设置捕获视频格式 */ret = app_video_init(video_cam_fd0, APP_VIDEO_FMT_RGB565);  if (ret != ESP_OK){ESP_LOGE(mipi_cam_tag, "Video cam init failed with error 0x%x", ret);return ESP_FAIL;}xTaskCreatePinnedToCore(lcd_cam_task, "lcd cam display", 4096, &video_cam_fd0, 4, NULL, 0);	/* 新建摄像头显示任务 */return ESP_OK;
}

在MIPI摄像头初始化函数中,首先调用app_video_main函数初始化摄像头硬件和软件接口接口,然后调用app_video_open函数打开摄像头设备,随后调用app_video_init函数初始化设备并设置捕获视频格式。最后新建摄像头显示任务lcd_cam_task。
接下来,看一下前面提及到的摄像头显示任务函数lcd_cam_task,代码如下。

/*** @brief    	摄像头显示任务* @param     	arg: 传入参数(未用到)* @retval    	无*/
static void lcd_cam_task(void *arg)
{int video_fd = *((int *)arg);       /* 任务参数 */int res = 0;struct v4l2_buffer buf;             /* 视频缓冲信息 */int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;struct v4l2_format format = {0};    /* 数据流格式 */format.type = type;                 /* 获取LCD BUF(双buffer) */void *lcd_buffer[2];void *draw_buffer = NULL;size_t data_cache_line_size = 0;if (lcddev.id <= 0x7084)        /* RGB屏触摸屏 */{ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(lcddev.lcd_panel_handle, 2, &lcd_buffer[0], &lcd_buffer[1]));}else{ESP_ERROR_CHECK(esp_lcd_dpi_panel_get_frame_buffer(lcddev.lcd_panel_handle, 2, &lcd_buffer[0], &lcd_buffer[1]));}draw_buffer = lcd_buffer[1];
ESP_ERROR_CHECK(esp_cache_get_alignment(MALLOC_CAP_SPIRAM,&data_cache_line_size));/*  CAM BUF设置两个,使用MMAP */void *camera_outbuf[2];ESP_ERROR_CHECK(camera_set_bufs(video_fd, 2, NULL));		/* 设置帧缓存数量 */ESP_ERROR_CHECK(camera_get_bufs(2, &camera_outbuf[0]));   	/* 获取帧缓冲 *//*---------------PPA的设置-SRM------------------*/ppa_client_handle_t ppa_srm_handle = NULL;ppa_client_config_t ppa_srm_config = {         	/* 设置客户端的属性 */.oper_type             = PPA_OPERATION_SRM,	/* PPA操作类型 */.max_pending_trans_num = 1, 	/* 客户端可以持有的最大PPA事务挂起数量 */
};/* 注册PPA客户端 */ESP_ERROR_CHECK(ppa_register_client(&ppa_srm_config, &ppa_srm_handle)); ESP_ERROR_CHECK(camera_stream_start(video_fd));         /* 开启视频流 */#if FPS_FUNC_ON == 1 		/* FPS_FUNC_ON该宏 打开帧率显示功能 */int fps_count = 0;int64_t start_time = esp_timer_get_time();
#endifif (ioctl(video_fd, VIDIOC_G_FMT, &format) != 0)	/* 获取设置支持的视频格式 */{ESP_LOGE(mipi_cam_tag, "get fmt failed");}while (1)
{   /* 双缓冲情况下,切换buffer */draw_buffer = lcd_buffer[0] == draw_buffer ?lcd_buffer[1]:lcd_buffer[0];#if FPS_FUNC_ON == 1	/* FPS_FUNC_ON该宏 打开帧率显示功能 */fps_count++;if (fps_count == 50) {int64_t end_time = esp_timer_get_time();ESP_LOGI(mipi_cam_tag, "fps: %f", 1000000.0 / 
((end_time - start_time) / 50.0));start_time = end_time;fps_count = 0;}
#endifmemset(&buf, 0, sizeof(buf));buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE; 	/* 与前面format.type要一致 */buf.memory = V4L2_MEMORY_MMAP;            	/* 内存使用mmap方式 *//* 将已经捕获好视频的内存拉出已捕获视频的队列 */res = ioctl(video_fd, VIDIOC_DQBUF, &buf);	if (res != 0){ESP_LOGE(mipi_cam_tag, "failed to receive video frame");}if (mipidev.id == 0x8399)   /* 1080p */{ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],	.in.pic_w           = format.fmt.pix.width,        .in.pic_h           = format.fmt.pix.height,        .in.block_w         = format.fmt.pix.width,         .in.block_h         = format.fmt.pix.height,        .in.block_offset_x  = 0,                            .in.block_offset_y  = 0,                            .in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,    .out.buffer         = draw_buffer,                  .out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width *16 / 8, data_cache_line_size), .out.pic_w          = lcddev.width,                 .out.pic_h          = lcddev.height,                .out.block_offset_x = 60,                           .out.block_offset_y = 0,                            .out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,    .rotation_angle     = PPA_SRM_ROTATION_ANGLE_90,    .scale_x            = 1.5,                          .scale_y            = 1,                            .mirror_x           = true,                        .mode               = PPA_TRANS_MODE_BLOCKING,      };ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }else if (mipidev.id == 0x8394)  /* 720p */{ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],.in.pic_w           = format.fmt.pix.width,   .in.pic_h           = format.fmt.pix.height,.in.block_w         = format.fmt.pix.width / 2,.in.block_h         = format.fmt.pix.height / 2,.in.block_offset_x  = 0,.in.block_offset_y  = 0,.in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,.out.buffer         = draw_buffer,.out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width * 
16 / 8, data_cache_line_size),.out.pic_w          = lcddev.width,.out.pic_h          = lcddev.height,.out.block_offset_x = 0,.out.block_offset_y = 0,.out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,.rotation_angle     = PPA_SRM_ROTATION_ANGLE_90,.scale_x            = 2,.scale_y            = 1.5,.mirror_x           = true,.mode               = PPA_TRANS_MODE_BLOCKING,};ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }else if (mipidev.id == 0x9881)  /* 800p */{ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],.in.pic_w           = format.fmt.pix.width,   .in.pic_h           = format.fmt.pix.height,.in.block_w         = format.fmt.pix.width,.in.block_h         = format.fmt.pix.height / 2,.in.block_offset_x  = 0,.in.block_offset_y  = 0,.in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,.out.buffer         = draw_buffer,.out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width *16 / 8, data_cache_line_size),.out.pic_w          = lcddev.width,.out.pic_h          = lcddev.height,.out.block_offset_x = 40,.out.block_offset_y = 0,.out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,.rotation_angle     = PPA_SRM_ROTATION_ANGLE_90,.scale_x            = 1,.scale_y            = 1.5,.mirror_x           = true,.mode               = PPA_TRANS_MODE_BLOCKING,};ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }else if (lcddev.id == 0X4342){ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],.in.pic_w           = format.fmt.pix.width,   .in.pic_h           = format.fmt.pix.height,.in.block_w         = format.fmt.pix.width / 4,.in.block_h         = format.fmt.pix.height / 4, .in.block_offset_x  = 0,.in.block_offset_y  = 0,.in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,.out.buffer         = draw_buffer,.out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width *16 / 8, data_cache_line_size),.out.pic_w          = lcddev.width,.out.pic_h          = lcddev.height,.out.block_offset_x = 0,.out.block_offset_y = 16,.out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,.rotation_angle     = PPA_SRM_ROTATION_ANGLE_0,.scale_x            = 1.5,.scale_y            = 1,.mirror_x           = true,.mirror_y           = true,.mode               = PPA_TRANS_MODE_BLOCKING,};ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }else if (lcddev.id == 0X4384)       /* PCLK时钟要调整为20MHz  */{ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],.in.pic_w           = format.fmt.pix.width,   .in.pic_h           = format.fmt.pix.height,.in.block_w         = format.fmt.pix.width / 2,.in.block_h         = format.fmt.pix.height / 2,.in.block_offset_x  = 0,.in.block_offset_y  = 0,.in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,.out.buffer         = draw_buffer,.out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width * 
16 / 8, data_cache_line_size),.out.pic_w          = lcddev.width,.out.pic_h          = lcddev.height,.out.block_offset_x = 0,.out.block_offset_y = 0,.out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,.rotation_angle     = PPA_SRM_ROTATION_ANGLE_0,.scale_x            = 1.25,.scale_y            = 1,.mirror_x           = true,.mirror_y           = true,.mode               = PPA_TRANS_MODE_BLOCKING,};ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }else if (lcddev.id == 0X7084)       /* PCLK时钟要调整为20MHz  */{ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],.in.pic_w           = format.fmt.pix.width,   .in.pic_h           = format.fmt.pix.height,.in.block_w         = format.fmt.pix.width / 2,.in.block_h         = format.fmt.pix.height / 2,.in.block_offset_x  = 0,.in.block_offset_y  = 0,.in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,.out.buffer         = draw_buffer,.out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width * 
16 / 8, data_cache_line_size),.out.pic_w          = lcddev.width,.out.pic_h          = lcddev.height,.out.block_offset_x = 0,.out.block_offset_y = 0,.out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,.rotation_angle     = PPA_SRM_ROTATION_ANGLE_0,.scale_x            = 1.25,.scale_y            = 1,.mirror_x           = true,.mirror_y           = true,.mode               = PPA_TRANS_MODE_BLOCKING,};ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }else if (lcddev.id == 0X7016)       /* PCLK时钟要调整为18MHz  */{ppa_srm_oper_config_t oper_config = {.in.buffer          = camera_outbuf[buf.index],.in.pic_w           = format.fmt.pix.width,   .in.pic_h           = format.fmt.pix.height,.in.block_w         = format.fmt.pix.width / 2,.in.block_h         = format.fmt.pix.height / 2,.in.block_offset_x  = 0,.in.block_offset_y  = 0,.in.srm_cm          = PPA_SRM_COLOR_MODE_RGB565,.out.buffer         = draw_buffer,.out.buffer_size    = ALIGN_UP_BY(lcddev.height * lcddev.width * 
16 / 8, data_cache_line_size),.out.pic_w          = lcddev.width,.out.pic_h          = lcddev.height,.out.block_offset_x = 32,.out.block_offset_y = 12,.out.srm_cm         = PPA_SRM_COLOR_MODE_RGB565,.rotation_angle     = PPA_SRM_ROTATION_ANGLE_0,.scale_x            = 1.5,.scale_y            = 1.2,.mirror_x           = true,.mirror_y           = true,.mode               = PPA_TRANS_MODE_BLOCKING,};ppa_do_scale_rotate_mirror(ppa_srm_handle, &oper_config);   }
/* 将空闲的内存加入可捕获视频的队列 */if (ioctl(video_fd, VIDIOC_QBUF, &buf) != 0)    {ESP_LOGE(mipi_cam_tag, "failed to free video frame");}}
}

该函数的实现,主要先把LCD的缓冲区获取到,然后设置CAM的缓冲区,图像数据直接传输到LCD的缓冲区中。由于摄像头的分辨率跟屏幕分辨率不一致问题,所以得使用PPA外设进行裁剪,具体操作如上代码所示。在while循环中,便可通过调用ppa_do_scale_rotate_mirror函数去把图像直接显示出来。特别注意:若使用的是RGB屏,其PCLK需要调整,才能正常运行起来。

2. CMakeLists.txt文件
本例程的功能实现主要依靠MIPI驱动。要在main函数中,成功调用MIPI_CAM文件中的内容,就得需要修改main文件夹下的CMakeLists.txt文件,修改如下:

idf_component_register(SRC_DIRS ".""APP/MIPI_CAM" INCLUDE_DIRS ".""APP/MIPI_CAM" )

3. main.c驱动代码
在main.c里面编写如下代码。

void app_main(void)
{esp_err_t ret;ret = nvs_flash_init();		/* 初始化NVS */if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}led_init();         		/* LED初始化 */myiic_init();       		/* MYIIC初始化 */lcd_init();         		/* LCD屏初始化 */lcd_show_string(30, 50,  200, 16, 16, "ESP32-P4", RED);lcd_show_string(30, 70,  200, 16, 16, "CAMERA TEST", RED);lcd_show_string(30, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);vTaskDelay(pdMS_TO_TICKS(500));lcd_clear(WHITE);mipi_cam_init();    		/* 摄像头初始化 */while (1){LED0_TOGGLE();vTaskDelay(pdMS_TO_TICKS(500));}
}

app_main函数比较简单了,主要就是初始化所需外设后,直接调用mipi_cam_init函数,该函数内部会新建一个摄像头显示任务,直接就可以把摄像头数据显示在LCD上了。

37.4 下载验证
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如下图所示:

image034

图37.4.1 摄像头实验程序运行效果图

屏幕显示了当前摄像头的图像数据。
摄像头实验我们就讲解到这里。

http://www.jsqmd.com/news/1227404/

相关文章:

  • 驾照翻译公证在哪办理?办理驾照公证需要多少钱? - 叮咚办真方便
  • 2026广州正规财税公司推荐榜:税务咨询选哪家最省心? - 热点速览
  • 2026年重庆三维精工字怎么选不踩雷?5家厂家实测对比与源头采购指南 - 中国品牌价值观察网
  • 从零实现C++椭圆曲线加密:深入理解ECC算法与工程实践
  • 苏州黄金回收哪家靠谱?认准奢二网|全城直营无套路、高价极速变现 - 生活时报
  • 2026年嘉兴高强度紧固件如何选不踩雷?5家实测对比与非标定制避坑指南 - 中国华商产业观察网
  • 7月杭州本地人才知道的卖lv包捷径:不去闲鱼,不找中古店,直接约这家 - 逸程奢侈品回收中心
  • 2026 年现阶段广州专业的钩臂车可下地库工厂有哪些,地库空间告急?这台“钩臂车”如何瞬间解放你的车位! - 实业推荐官【官方】
  • 解锁QQ音乐加密格式:QMCDecode让您的音乐收藏真正自由
  • 杭州 2026 名包变现门店排行,奢二网甄选无套路商家 - 每日生活报
  • 黄金回收不再【如堕烟海】!2026西安避坑全攻略,识破高价引流与隐形扣费陷阱 - 一日一测评
  • 2026年积家 中国区售后服务网络更新优化 全国60+门店地址及电话汇总 - 亨得利腕表服务中心
  • QiLink OS 失败数据共享平台对产业创新联盟的六大具体落地启示
  • 构建专业游戏卡片脚本:3个核心技巧掌握ygopro-scripts开发
  • 2026西安装修公司实测参考:覆盖毛坯整装、旧房改造、大宅全案 - 装修新知
  • 2026上海工厂设计推荐 行业主流服务资源汇总 - 奔跑123
  • 2026年7月,Gelfioor 地板哪家适合办公室地面改造 - 热点速览
  • 暑期必备!线上才艺展演投票活动,一键创建~ - 投票评选活动
  • 重百世纪卡怎么回收?四种主流渠道实测对比,高效盘活闲置卡券 - 可可收公众号
  • C++图结构实现与算法详解:从邻接表到最短路径
  • 2026苏州市文武学校怎么选?认准正规办学!虹坤文武学校优势全解析 - 全国文武学校招生
  • 别急着卖大润发卡!亲测4家回收平台,这份避坑清单请收好 - 京质回收
  • 微信视频投票怎么设置?海投票2026发起图文视频混合投票教程 - 微信投票小程序
  • 2026 年 7 月卡地亚中国官方授权售后网点完整名录|官方搬迁、新店启用统一公示公告 - 亨得利腕表服务中心
  • 肇庆封开黄金回收正当时:认准本地正规连锁,卖金避险不踩坑 - 得天独厚
  • 2026 渭南各区县黄金回收正规门店汇总|便民卖金避坑完整手册 - 不晚生活号
  • 2026 永泰黄金回收全攻略:本地卖金避坑干货 + 三家正规门店实测对比 - 铂衡汇黄金珠宝
  • C++20协程内存优化实战:从自定义分配到对象池与无栈设计
  • 7 月多品类收金防坑指南,足金 18K 金铂金分开估价不亏损 - 讯息早知道
  • 苏州二手黄金回收避雷,低价引流门店分辨小技巧 - 奢侈品回收评测