54. DCMI—OV5640摄像头¶
本章参考资料:《STM32F4xx参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。
关于开发板配套的OV5640摄像头参数可查阅《ov5640datasheet》配套资料获知。
STM32F4芯片具有浮点运算单元,适合对图像信息使用DSP进行基本的图像处理,其处理速度比传统的8、16位机快得多, 而且它还具有与摄像头通讯的专用DCMI接口,所以使用它驱动摄像头采集图像信息并进行基本的加工处理非常适合。 本章讲解如何使用STM32驱动OV5640型号的摄像头。
54.1. 摄像头简介¶
在各类信息中,图像含有最丰富的信息,作为机器视觉领域的核心部件,摄像头被广泛地应用在安防、探险以及车牌检测等场合。 摄像头按输出信号的类型来看可以分为数字摄像头和模拟摄像头,按照摄像头图像传感器材料构成来看可以分为CCD和CMOS。 现在智能手机的摄像头绝大部分都是CMOS类型的数字摄像头。
54.1.1. 数字摄像头跟模拟摄像头区别¶
输出信号类型
数字摄像头输出信号为数字信号,模拟摄像头输出信号为标准的模拟信号。
接口类型
数字摄像头有USB接口(比如常见的PC端免驱摄像头)、IEE1394火线接口(由苹果公司领导的开发联盟开发的一种高速度传送接口, 数据传输率高达800Mbps)、千兆网接口(网络摄像头)。模拟摄像头多采用AV视频端子(信号线+地线)或S-VIDEO(即莲花头–SUPER VIDEO, 是一种五芯的接口,由两路视频亮度信号、两路视频色度信号和一路公共屏蔽地线共五条芯线组成)。
分辨率
模拟摄像头的感光器件,其像素指标一般维持在752(H)*582(V)左右的水平,像素数一般情况下维持在41万左右。 现在的数字摄像头分辨率一般从数十万到数千万。但这并不能说明数字摄像头的成像分辨率就比模拟摄像头的高, 原因在于模拟摄像头输出的是模拟视频信号,一般直接输入至电视或监视器,其感光器件的分辨率与电视信号的扫描数呈一定的换算关系, 图像的显示介质已经确定,因此模拟摄像头的感光器件分辨率不是不能做高,而是依据于实际情况没必要做这么高。
54.1.2. CCD与CMOS的区别¶
摄像头的图像传感器CCD与CMOS传感器主要区别如下:
成像材料
CCD与CMOS的名称跟它们成像使用的材料有关,CCD是“电荷耦合器件”(Charge Coupled Device)的简称, 而CMOS是“互补金属氧化物半导体”(Complementary Metal Oxide Semiconductor)的简称。
功耗
由于CCD的像素由MOS电容构成,读取电荷信号时需使用电压相当大(至少12V)的二相或三相或四相时序脉冲信号,才能有效地传输电荷。 因此CCD的取像系统除了要有多个电源外,其外设电路也会消耗相当大的功率。有的CCD取像系统需消耗2~5W的功率。 而CMOS光电传感器件只需使用一个单电源5V或3V,耗电量非常小,仅为CCD的1/8~1/10,有的CMOS取像系统只消耗20~50mW的功率。
成像质量
CCD传感器件制作技术起步早,技术成熟,采用PN结或二氧化硅(sio2)隔离层隔离噪声,所以噪声低,成像质量好。与CCD相比, CMOS的主要缺点是噪声高及灵敏度低,不过现在随着CMOS电路消噪技术的不断发展,为生产高密度优质的CMOS传感器件提供了良好的条件, 现在的CMOS传感器已经占领了大部分的市场,主流的单反相机、智能手机都已普遍采用CMOS传感器。
54.2. OV5640摄像头¶
本章主要讲解实验板配套的摄像头,它的实物见图 实验板配套的OV5640摄像头 ,该摄像头主要由镜头、图像传感器、板载电路及下方的信号引脚组成。
镜头部件包含一个镜头座和一个可旋转调节距离的凸透镜,通过旋转可以调节焦距,正常使用时,镜头座覆盖在电路板上遮光, 光线只能经过镜头传输到正中央的图像传感器,它采集光线信号,然后把采集得的数据通过下方的信号引脚输出数据到外部器件。
54.2.1. OV5640传感器简介¶
图像传感器是摄像头的核心部件,上述摄像头中的图像传感器是一款型号为OV5640的CMOS类型数字图像传感器。该传感器支持输出最大为500万像素的图像 (2592x1944分辨率), 支持使用VGA时序输出图像数据,输出图像的数据格式支持YUV(422/420)、YCbCr422、RGB565以及JPEG格式,若直接输出JPEG格式的图像时可大大减少数据量, 方便网络传输。它还可以对采集得的图像进行补偿,支持伽玛曲线、白平衡、饱和度、色度等基础处理。根据不同的分辨率配置, 传感器输出图像数据的帧率从15-60帧可调,工作时功率在150mW-200mW之间。
54.2.2. OV5640引脚及功能框图¶
OV5640模组带有自动对焦功能,引脚的定义见图 OV5640传感器引脚分布图
信号引脚介绍如下表 OV5640管脚 。
下面我们配合图 OV5640功能框图 中的OV5640功能框图讲解这些信号引脚。
控制寄存器
标号处的是OV5640的控制寄存器,它根据这些寄存器配置的参数来运行,而这些参数是由外部控制器通过SIO_C和SIO_D引脚写入的, SIO_C与SIO_D使用的通讯协议跟I2C十分类似,在STM32中我们完全可以直接用I2C硬件外设来控制。
通信、控制信号及时钟
标号处包含了OV5640的通信、控制信号及外部时钟,其中PCLK、HREF及VSYNC分别是像素同步时钟、行同步信号以及帧同步信号, 这与液晶屏控制中的信号是很类似的。RESETB引脚为低电平时,用于复位整个传感器芯片,PWDN用于控制芯片进入低功耗模式。 注意最后的一个XCLK引脚,它跟PCLK是完全不同的,XCLK是用于驱动整个传感器芯片的时钟信号,是外部输入到OV5640的信号; 而PCLK是OV5640输出数据时的同步信号,它是由OV5640输出的信号。XCLK可以外接晶振或由外部控制器提供, 若要类比XCLK之于OV5640就相当于HSE时钟输入引脚与STM32芯片的关系,PCLK引脚可类比STM32的I2C外设的SCL引脚。
感光矩阵
标号处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。
数据输出信号
标号处包含了DSP处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元, 转换出的数据最终通过Y0-Y9引脚输出,一般来说我们使用8根据数据线来传输,这时仅使用Y2-Y9引脚, OV5640与外部器件的连接方式见图 8位数据线接法 。
数据输出信号
标号⑤处为VCM处理单元,他会通过图像分析来实现图像的自动对焦功能。要实现自动对焦还需要下载自动对焦固件到模组,后面摄像头实验详细介绍这个功能。
54.2.3. SCCB时序¶
外部控制器对OV5640寄存器的配置参数是通过SCCB总线传输过去的,而SCCB总线跟I2C十分类似,所以在STM32驱动中我们直接使用片上I2C外设与它通讯。 SCCB与标准的I2C协议的区别是它每次传输只能写入或读取一个字节的数据,而I2C协议是支持突发读写的, 即在一次传输中可以写入多个字节的数据(EEPROM中的页写入时序即突发写)。关于SCCB协议的完整内容可查看配套资料里的《SCCB协议》文档,下面我们简单介绍下。
SCCB的起始、停止信号及数据有效性
SCCB的起始信号、停止信号及数据有效性与I2C完全一样,见图 SCCB停止信号 及图 SCCB的数据有效性 。
起始信号:在SIO_C为高电平时,SIO_D出现一个下降沿,则SCCB开始传输。
停止信号:在SIO_C为高电平时,SIO_D出现一个上升沿,则SCCB停止传输。
数据有效性:除了开始和停止状态,在数据传输过程中,当SIO_C为高电平时,必须保证SIO_D上的数据稳定, 也就是说,SIO_D上的电平变换只能发生在SIO_C为低电平的时候,SIO_D的信号在SIO_C为高电平时被采集。
SCCB数据读写过程
在SCCB协议中定义的读写操作与I2C也是一样的,只是换了一种说法。它定义了两种写操作,即三步写操作和两步写操作。 三步写操作可向从设备的一个目的寄存器中写入数据,见图 SCCB的三步写操作 。在三步写操作中, 第一阶段发送从设备的ID地址+W标志(等于I2C的设备地址:7位设备地址+读写方向标志),第二阶段发送从设备目标寄存器的8位地址, 第三阶段发送要写入寄存器的8位数据。图中的“X”数据位可写入1或0,对通讯无影响。
而两步写操作没有第三阶段,即只向从器件传输了设备ID+W标志和目的寄存器的地址,见图 SCCB的两步写操作 。 两步写操作是用来配合后面的读寄存器数据操作的,它与读操作一起使用,实现I2C的复合过程。
两步读操作,它用于读取从设备目的寄存器中的数据,见图 SCCB的两步读操作 。在第一阶段中发送从设备的设备ID+R标志(设备地址+读方向标志)和自由位, 在第二阶段中读取寄存器中的8位数据和写NA位(非应答信号)。由于两步读操作没有确定目的寄存器的地址, 所以在读操作前,必需有一个两步写操作,以提供读操作中的寄存器地址。
可以看到,以上介绍的SCCB特性都与I2C无区别,而I2C比SCCB还多出了突发读写的功能, 所以SCCB可以看作是I2C的子集,我们完全可以使用STM32的I2C外设来与OV5640进行SCCB通讯。
54.2.4. OV5640的寄存器¶
控制OV5640涉及到它很多的寄存器,可直接查询《ov5640datasheet》了解,通过这些寄存器的配置,可以控制它输出图像的分辨率大小、图像格式及图像方向等。要注意的是OV5640寄存器地址为16位。
官方还提供了一个《OV5640_自动对焦照相模组应用指南(DVP_接口)__R2.13C.pdf》的文档,它针对不同的配置需求,提供了配置范例,见图 调节帧率的寄存器配置范例 。 其中write_SCCB是一个利用SCCB向寄存器写入数据的函数,第一个参数为要写入的寄存器的地址,第二个参数为要写入的内容。
54.2.5. 像素数据输出时序¶
对OV5640时采用SCCB协议进行控制,而它输出图像时则使用VGA时序(还可用SVGA、UXGA,这些时序都差不多), 这跟控制液晶屏输入图像时很类似。OV5640输出图像时,一帧帧地输出,在帧内的数据一般从左到右,从上到下, 一个像素一个像素地输出(也可通过寄存器修改方向),见图 摄像头数据输出 。
例如,见图 DVP接口时序 ,若我们使用Y2-Y9数据线,图像格式设置为RGB565, 进行数据输出时,Y2-Y9数据线会在1个像素同步时钟PCLK的驱动下发送1字节的数据信号,所以2个PCLK时钟可发送1个RGB565格式的像素数据。 像素数据依次传输,每传输完一行数据时,行同步信号HREF会输出一个电平跳变信号,每传输完一帧图像时,VSYNC会输出一个电平跳变信号。
54.3. STM32的DCMI接口简介¶
STM32F4系列的控制器包含了DCMI数字摄像头接口(Digital cameraInterface), 它支持使用上述类似VGA的时序获取图像数据流,支持原始的按行、帧格式来组织的图像数据,如YUV、RGB, 也支持接收JPEG格式压缩的数据流。接收数据时,主要使用HSYNC及VSYNC信号来同步。
54.3.1. DCMI整体框图¶
STM32的DCMI接口整体框图见图 DCMI接口整体框图 。
外部接口及时序
上图标号处的是DCMI向外部引出的信号线。DCMI提供的外部接口的方向都是输入的,接口的各个信号线说明见表 DCMI的信号线说明 。
其中DCMI_D数据线的数量可选8、10、12或14位,各个同步信号的有效极性都可编程控制。 它使用的通讯时序与OV5640的图像数据输出接口时序一致,见图 DCMI时序图 。
内部信号及PIXCLK的时钟频率
图 DCMI接口整体框图 的标号处表示DCMI与内部的信号线。在STM32的内部,使用HCLK作为时钟源提供给DCMI外设。 从DCMI引出有DCMI_IT信号至中断控制器,并可通过DMA_REQ信号发送DMA请求。
DCMI从外部接收数据时,在HCLK的上升沿时对PIXCLK同步的信号进行采样, 它限制了PIXCLK的最小时钟周期要大于2.5个HCLK时钟周期,即最高频率为HCLK的1/4。
54.3.2. DCMI接口内部结构¶
DCMI接口的内部结构见图 DCMI接口内部结构 。
(1) 同步器
同步器主要用于管理DCMI接收数据的时序,它根据外部的信号提取输入的数据。
(2) FIFO/数据格式化器
为了对数据传输加以管理,STM32在DCMI接口上实现了 4 个字(32bit x4)深度的 FIFO,用以缓冲接收到的数据。
(3) AHB接口
DCMI接口挂载在AHB总线上,在AHB总线中有一个DCMI接口的数据寄存器,当我们读取该寄存器时,它会从FIFO中获取数据, 并且FIFO中的数据指针会自动进行偏移,使得我们每次读取该寄存器都可获得一个新的数据。
(4) 控制/状态寄存器
DCMI的控制寄存器协调图中的各个结构运行,程序中可通过检测状态寄存器来获DCMI的当前运行状态。
(5) DMA接口
由于DCMI采集的数据量很大,我们一般使用DMA来把采集得的数据搬运至内存。
54.3.3. 同步方式¶
DCMI接口支持硬件同步或内嵌码同步方式,硬件同步方式即使用HSYNC和VSYNC作为同步信号的方式,OV5640就是使用这种同步时序。
而内嵌码同步的方式是使用数据信号线传输中的特定编码来表示同步信息,由于需要用0x00和0xFF来表示编码, 所以表示图像的数据中不能包含有这两个值。利用这两个值,它扩展到4个字节,定义出了2种模式的同步码,每种模式包含4个编码, 编码格式为0xFF0000XY,其中XY的值可通过寄存器设置。当DCMI接收到这样的编码时,它不会把这些当成图像数据, 而是按照表 两种模式的内嵌码 中的编码来解释,作为同步信号。
54.3.4. 捕获模式及捕获率¶
DCMI还支持两种数据捕获模式,分别为快照模式和连续采集模式。快照模式时只采集一帧的图像数据,连续采集模式会一直采集多个帧的数据, 并且可以通过配置捕获率来控制采集多少数据,如可配置为采集所有数据或隔1帧采集一次数据或隔3帧采集一次数据。
54.4. DCMI初始化结构体¶
与其它外设一样,STM32的DCMI外设也可以使用库函数来控制,其中最主要的配置项都封装到了DCMI_InitTypeDef结构体, 来这些内容都定义在库文件“stm32f4xx_dcmi.h”及“stm32f4xx_ dcmi.c”中,编程时我们可以结合这两个文件内的注释使用或参考库帮助文档。
DCMI_InitTypeDef初始化结构体的内容见 代码清单:OV5640-1 。
1 2 3 4 5 6 7 8 9 10 11 12 13 | /**
* @brief DCMI 初始化结构体
*/
typedef struct
{
uint16_t DCMI_CaptureMode; /*选择连续模式或拍照模式 */
uint16_t DCMI_SynchroMode; /*选择硬件同步模式还是内嵌码模式 */
uint16_t DCMI_PCKPolarity; /*设置像素时钟的有效边沿*/
uint16_t DCMI_VSPolarity; /*设置VSYNC的有效电平*/
uint16_t DCMI_HSPolarity; /*设置HSYNC的有效边沿*/
uint16_t DCMI_CaptureRate; /*设置图像的采集间隔 */
uint16_t DCMI_ExtendedDataMode; /*设置数据线的宽度 */
} DCMI_InitTypeDef;
|
这些结构体成员说明如下,其中括号内的文字是对应参数在STM32标准库中定义的宏:
(1) DCMI_CaptureMode
本成员设置DCMI的捕获模式, 可以选择为连续摄像(DCMI_CaptureMode_Continuous)或单张拍照DCMI_CaptureMode_SnapShot。
(2) DCMI_SynchroMode
本成员设置DCMI数据的同步模式, 可以选择为硬件同步方式(DCMI_SynchroMode_Hardware)或内嵌码方式(DCMI_SynchroMode_Embedded)。
(3) DCMI_PCKPolarity
本成员用于配置DCMI接口像素时钟的有效边沿,即在该时钟边沿时,DCMI会对数据线上的信号进行采样, 它可以被设置为上升沿有效(DCMI_PCKPolarity_Rising)或下降沿有效(DCMI_PCKPolarity_Falling)。
(4) DCMI_VSPolarity
本成员用于设置VSYNC的有效电平,当VSYNC信号线表示为有效电平时,表示新的一帧数据传输完成, 它可以被设置为高电平有效(DCMI_VSPolarity_High)或低电平有效(DCMI_VSPolarity_Low)。
(5) DCMI_HSPolarity
类似地,本成员用于设置HSYNC的有效电平,当HSYNC信号线表示为有效电平时,表示新的一行数据传输完成, 它可以被设置为高电平有效(DCMI_HSPolarity_High)或低电平有效(DCMI_HSPolarity_Low)。
(6) DCMI_CaptureRate
本成员可以用于设置DCMI捕获数据的频率,可以设置为全采集、半采集或1/4采集(DCMI_CaptureRate_All_Frame/ 1of2_Frame/ 1of4_Frame), 在间隔采集的情况下,STM32的DCMI外设会直接按间隔丢弃数据。
(7) DCMI_ExtendedDataMode
本成员用于设置DCMI的数据线宽度,可配置为8/10/12及14位数据线宽(DCMI_ExtendedDataMode_8b/10b/12b/14b)。
配置完这些结构体成员后,我们调用库函数DCMI_Init即可把这些参数写入到DCMI的控制寄存器中,实现DCMI的初始化。
54.5. DCMI—OV5640摄像头实验¶
本小节讲解如何使用DCMI接口从OV5640摄像头输出的RGB565格式的图像数据,并把这些数据实时显示到液晶屏上。
学习本小节内容时,请打开配套的“DCMI—OV5640摄像头”工程配合阅读。
54.5.1. 硬件设计¶
摄像头原理图
本实验采用的OV5640摄像头实物见图 实验板配套的OV5640摄像头 ,其原理图见图 OV5640摄像头原理图 。
图 OV5640摄像头原理图 标号①处的是OV5640模组接口电路,在这部分中已对SCCB使用的信号线接了上拉电阻,外部电路可以省略上拉; 标号②处的是一个24MHz的有源晶振,它为OV5640提供系统时钟,如果不想使用外部晶振提供时钟源,可以参考图中的R6处贴上0欧电阻, XCLK引脚引出至外部,由外部控制器提供时钟;标号③处的是电源转换模块,可以从5V转2.8V和1.5V供给模组使用;标号④处的是摄像头引脚集中引出的排针接口, 使用它可以方便地与STM32实验板中的排母连接。标号⑤处的是电源指示灯。
摄像头与实验板的连接
通过排母,OV5640与STM32引脚的连接关系见图 STM32实验板引出的DCMI接口 。
以上原理图可查阅《ov5640—黑白原理图》及《野火F407开发板底板原理图》文档获知,若您使用的摄像头或实验板不一样,请根据实际连接的引脚修改程序。
54.5.2. 软件设计¶
为了使工程更加有条理,我们把摄像头控制相关的代码独立分开存储,方便以后移植。在“液晶显示”工程的基础上新建“bsp_ov5640.c”及“bsp_ov5640.h”文件, 这些文件也可根据您的喜好命名,它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。
54.5.2.1. 编程要点¶
初始化DCMI时钟,I2C时钟;
使用I2C接口向OV5640写入寄存器配置;
初始化DCMI工作模式;
初始化DMA,用于搬运DCMI的数据到显存空间进行显示;
编写测试程序,控制采集图像数据并显示到液晶屏。
54.5.2.2. 代码分析¶
摄像头硬件相关宏定义
我们把摄像头控制硬件相关的配置都以宏的形式定义到 “bsp_ov5640.h”文件中,其中包括I2C及DCMI接口的,见 代码清单:OV5640-2 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /*摄像头接口 */
//IIC SCCB
#define CAMERA_I2C I2C1
#define CAMERA_I2C_CLK RCC_APB1Periph_I2C1
#define CAMERA_I2C_SCL_PIN GPIO_Pin_8
#define CAMERA_I2C_SCL_GPIO_PORT GPIOB
#define CAMERA_I2C_SCL_GPIO_CLK RCC_AHB1Periph_GPIOB
#define CAMERA_I2C_SCL_SOURCE GPIO_PinSource8
#define CAMERA_I2C_SCL_AF GPIO_AF_I2C1
#define CAMERA_I2C_SDA_PIN GPIO_Pin_9
#define CAMERA_I2C_SDA_GPIO_PORT GPIOB
#define CAMERA_I2C_SDA_GPIO_CLK RCC_AHB1Periph_GPIOB
#define CAMERA_I2C_SDA_SOURCE GPIO_PinSource9
#define CAMERA_I2C_SDA_AF GPIO_AF_I2C1
//VSYNC
#define DCMI_VSYNC_GPIO_PORT GPIOB
#define DCMI_VSYNC_GPIO_CLK RCC_AHB1Periph_GPIOB
#define DCMI_VSYNC_GPIO_PIN GPIO_Pin_7
#define DCMI_VSYNC_PINSOURCE GPIO_PinSource7
#define DCMI_VSYNC_AF GPIO_AF_DCMI
// HSYNC
#define DCMI_HSYNC_GPIO_PORT GPIOA
#define DCMI_HSYNC_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DCMI_HSYNC_GPIO_PIN GPIO_Pin_4
#define DCMI_HSYNC_PINSOURCE GPIO_PinSource4
#define DCMI_HSYNC_AF GPIO_AF_DCMI
//PIXCLK
#define DCMI_PIXCLK_GPIO_PORT GPIOA
#define DCMI_PIXCLK_GPIO_CLK RCC_AHB1Periph_GPIOA
#define DCMI_PIXCLK_GPIO_PIN GPIO_Pin_6
#define DCMI_PIXCLK_PINSOURCE GPIO_PinSource6
#define DCMI_PIXCLK_AF GPIO_AF_DCMI
//PWDN
#define DCMI_PWDN_GPIO_PORT GPIOG
#define DCMI_PWDN_GPIO_CLK RCC_AHB1Periph_GPIOG
#define DCMI_PWDN_GPIO_PIN GPIO_Pin_3
//数据信号线
#define DCMI_D0_GPIO_PORT GPIOC
#define DCMI_D0_GPIO_CLK RCC_AHB1Periph_GPIOC
#define DCMI_D0_GPIO_PIN GPIO_Pin_0
#define DCMI_D0_PINSOURCE GPIO_PinSource0
#define DCMI_D0_AF GPIO_AF_DCMI
/*....省略部分数据线*/
|
以上代码根据硬件的连接,把与DCMI、I2C接口与摄像头通讯使用的引脚号、引脚源以及复用功能映射都以宏封装起来。
初始化DCMI的 GPIO及I2C
利用上面的宏,初始化DCMI的GPIO引脚及I2C,见 代码清单:OV5640-3 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | /**
* @brief 初始化控制摄像头使用的GPIO(I2C/DCMI)
* @param None
* @retval None
*/
void OV5640_HW_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
I2C_InitTypeDef I2C_InitStruct;
/***DCMI引脚配置***/
/* 使能DCMI时钟 */
RCC_AHB1PeriphClockCmd(DCMI_PWDN_GPIO_CLK|DCMI_RST_GPIO_CLK|DCMI_VS
YNC_GPIO_CLK | DCMI_HSYNC_GPIO_CLK |
DCMI_PIXCLK_GPIO_CLK|
DCMI_D0_GPIO_CLK| DCMI_D1_GPIO_CLK|
DCMI_D2_GPIO_CLK| DCMI_D3_GPIO_CLK|
DCMI_D4_GPIO_CLK| DCMI_D5_GPIO_CLK|
DCMI_D6_GPIO_CLK| DCMI_D7_GPIO_CLK, ENABLE);
/*控制/同步信号线*/
GPIO_InitStructure.GPIO_Pin = DCMI_VSYNC_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(DCMI_VSYNC_GPIO_PORT, &GPIO_InitStructure);
GPIO_PinAFConfig(DCMI_VSYNC_GPIO_PORT, DCMI_VSYNC_PINSOURCE,
DCMI_VSYNC_AF);
GPIO_InitStructure.GPIO_Pin = DCMI_HSYNC_GPIO_PIN ;
GPIO_Init(DCMI_HSYNC_GPIO_PORT, &GPIO_InitStructure);
GPIO_PinAFConfig(DCMI_HSYNC_GPIO_PORT, DCMI_HSYNC_PINSOURCE,
DCMI_HSYNC_AF);
GPIO_InitStructure.GPIO_Pin = DCMI_PIXCLK_GPIO_PIN ;
GPIO_Init(DCMI_PIXCLK_GPIO_PORT, &GPIO_InitStructure);
GPIO_PinAFConfig(DCMI_PIXCLK_GPIO_PORT, DCMI_PIXCLK_PINSOURCE,
DCMI_PIXCLK_AF);
/*数据信号*/
GPIO_InitStructure.GPIO_Pin = DCMI_D0_GPIO_PIN ;
GPIO_Init(DCMI_D0_GPIO_PORT, &GPIO_InitStructure);
GPIO_PinAFConfig(DCMI_D0_GPIO_PORT, DCMI_D0_PINSOURCE, DCMI_D0_AF);
/*...省略部分数据信号线*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Pin = DCMI_PWDN_GPIO_PIN ;
GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = DCMI_RST_GPIO_PIN ;
GPIO_Init(DCMI_RST_GPIO_PORT, &GPIO_InitStructure);
/*PWDN引脚,高电平关闭电源,低电平供电*/
GPIO_ResetBits(DCMI_RST_GPIO_PORT,DCMI_RST_GPIO_PIN);
GPIO_SetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN);
Delay(10);//延时10ms
GPIO_ResetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN);
Delay(10);//延时10ms
GPIO_SetBits(DCMI_RST_GPIO_PORT,DCMI_RST_GPIO_PIN);
/****** 配置I2C,使用I2C与摄像头的SCCB接口通讯*****/
/* 使能I2C时钟 */
RCC_APB1PeriphClockCmd(CAMERA_I2C_CLK, ENABLE);
/* 使能I2C使用的GPIO时钟 */
RCC_AHB1PeriphClockCmd(CAMERA_I2C_SCL_GPIO_CLK|CAMERA_I2C_SDA_GPIO_
CLK, ENABLE);
/* 配置引脚源 */
GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE,
CAMERA_I2C_SCL_AF);
GPIO_PinAFConfig(CAMERA_I2C_SDA_GPIO_PORT, CAMERA_I2C_SDA_SOURCE,
CAMERA_I2C_SDA_AF);
/* 初始化GPIO */
GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SCL_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(CAMERA_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE,
CAMERA_I2C_SCL_AF);
GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SDA_PIN ;
GPIO_Init(CAMERA_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);
/*初始化I2C模式 */
I2C_DeInit(CAMERA_I2C);
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0xFE;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7b
it;
I2C_InitStruct.I2C_ClockSpeed = 400000;
/* 写入配置 */
I2C_Init(CAMERA_I2C, &I2C_InitStruct);
/* 使能I2C */
I2C_Cmd(CAMERA_I2C, ENABLE);
Delay(50);//延时50ms
}
|
与所有使用到GPIO的外设一样,都要先把使用到的GPIO引脚模式初始化,以上代码把DCMI接口的信号线全都初始化为DCMI复用功能, 而这里需要特别注意的地方是:OV5640的上电时序比较特殊,我们初始化PWDN则被和RST应该特别小心,先初始化成普通的推挽输出模式, 并且在初始化完毕后直接控制它RST为低电平,PWDN为高电平,使能给摄像头供电处于待机模式,延时10ms后控制PWDN为低电平, 再延时10ms后控制RST为高电平,OV5640模组启动。
函数中还包含了I2C的初始化配置,使用I2C与OV2640OV5640的SCCB接口通讯,这里的I2C模式配置与标准的I2C无异。 特别注意:I2C初始化完必须延时50ms,再进行对OV5640寄存器的读写操作。
配置DCMI的模式
接下来需要配置DCMI的工作模式,我们通过编写OV5640_Init函数完成该功能,见 代码清单:OV5640-4 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #define FSMC_LCD_ADDRESS FSMC_Addr_ILI9806G_DATA
/**
* @brief 配置 DCMI/DMA 以捕获摄像头数据
* @param None
* @retval None
*/
void OV5640_Init(void)
{
DCMI_InitTypeDef DCMI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/*** 配置DCMI接口 ***/
/* 使能DCMI时钟 */
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI, ENABLE);
/* DCMI 配置*/
DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_Continuous;
DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;
DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising;
DCMI_InitStructure.DCMI_VSPolarity = DCMI_VSPolarity_Low;
DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low;
DCMI_InitStructure.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame;
DCMI_InitStructure.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b;
DCMI_Init(&DCMI_InitStructure);
//配置DMA传输,直接配置循环传输即可
OV5640_DMA_Config(FSMC_LCD_ADDRESS,1);
/* 配置中断 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置帧中断,接收到帧同步信号就进入中断 */
NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn ; //帧中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DCMI_ITConfig (DCMI_IT_FRAME,ENABLE);
}
|
该函数的执行流程如下:
(1) 使能DCMI外设的时钟, 它是挂载在AHB2总线上的;
(2) 根据摄像头的时序和硬件连接的要求,配置DCMI工作模式为:使用硬件同步, 连续采集所有帧数据,采集时使用8根数据线,PIXCLK被设置为上升沿有效,VSYNC被设置成高电平有效, HSYNC被设置成低电平有效;
(3) 调用OV5640_DMA_Config函数开始DMA数据传输,每传输完一行数据需要调用一次, 它包含本次传输的目的首地址及传输的数据量,后面我们再详细解释 ;
(4) 配置DCMI的帧传输中断,可以用于统计帧率。另外, 为了防止有时DMA出现传输错误或传输速度跟不上导致数据错位、偏移等问题,每次DCMI接收到摄像头的一帧数据, 得到新的帧同步信号后(VSYNC),就进入中断,重新设置液晶屏显示窗口,可使它重新开始一帧的数据传输(本工程没有实现这个功能)。
配置DMA数据传输
上面的DCMI配置函数中调用了OV5640_DMA_Config函数开始了DMA传输,该函数的定义见 代码清单:OV5640-5 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /**
* @brief 配置 DCMI/DMA 以捕获摄像头数据
* @param DMA_Memory0BaseAddr:本次传输的目的首地址
* @param DMA_BufferSize:本次传输的数据量(单位为字,即4字节)
*/
void OV5640_DMA_Config(uint32_t DMA_Memory0BaseAddr,uint16_t DMA_BufferSize)
{
DMA_InitTypeDef DMA_InitStructure;
/* 配置DMA从DCMI中获取数据*/
/* 使能DMA*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_Cmd(DMA2_Stream1,DISABLE);
while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE) {}
DMA_InitStructure.DMA_Channel = DMA_Channel_1;
DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS; //DCMI数据寄存器地址
//DMA传输的目的地址(传入的参数)
DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize =DMA_BufferSize; //传输的数据大小(传入的参数)
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//液晶数据地址,不自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC8;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
/*DMA初始化 */
DMA_Init(DMA2_Stream1, &DMA_InitStructure);
DMA_Cmd(DMA2_Stream1,ENABLE);
while (DMA_GetCmdStatus(DMA2_Stream1) != ENABLE) {}
}
|
该函数跟普通的DMA配置无异,它把DCMI接收到的数据从它的数据寄存器搬运到液晶显示器上。 它包含2个输入参数:DMA_Memory0BaseAddr和DMA_BufferSize,其中DMA_Memory0BaseAddr用于设置本次DMA传输的目的首地址, 该参数会被赋值到结构体成员DMA_InitStructure.DMA_Memory0BaseAddr中。
根据前面的函数OV5640_Init对它的调用,DMA_Memory0BaseAddr的值为ILI9806G液晶屏接收显存数据的地址,当STM32往该地址写入数据时, FSMC会自动产生时序把该数据写入到液晶屏,从而可以使用液晶屏显示摄像头采集得的图像。
另一个参数DMA_BufferSize则用于指示本次DMA传输的数据量,它会被赋值到结构体成员DMA_InitStructure.DMA_BufferSize中, 要注意它的单位是一个字,即4字节,是DCMI数据寄存器的单位大小。
根据前面的函数OV5640_Init对它的调用,DMA_BufferSize的值被直接赋为1,而DMA又被配置为循环模式,所以当开始传输时, STM32会把摄像头采集到DCMI数据寄存器里的4字节数据使用DMA分2次写入到液晶屏数据地址,然后不断循环该过程。
若液晶屏提前调用ILI9806G_OpenWindow设置好了数据的显示窗口,那么这些数据就会一个一个(16位的数据)地显示在对应的位置,从而实时显示摄像头采集得到的数据。
DCMI帧中断
OV5640_Init函数初始化了DCMI,使能了帧中断,当摄像头采集到一帧图像的,会进入该中断,见 代码清单:OV5640-6 中的DCMI_IRQHandler。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | extern uint8_t fps;
//使用帧中断重置line_num,可防止有时掉数据的时候DMA传送行数出现偏移
void DCMI_IRQHandler(void)
{
if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET ) {
/*传输完一帧,计数复位*/
fps++; //帧率计数
DCMI_ClearITPendingBit(DCMI_IT_FRAME);
}
}
|
在本工程中的帧中断服务函数中,仅仅是使用了一个变量用于统计帧数,以便配合定时器计算帧率。实际上, 为了避免有时STM32内部DMA传输受到阻塞而跟不上外部摄像头信号导致的数据错误(即液晶显示摄像头的数据错位), 可以在本断服务函数中重新调用液晶屏的ILI9806G_OpenWindow复位液晶显示的起始位置,达到重新同步的目的, 由于本工程实测并没有错位的问题,所以这里省略了,当您了解了整个工程的原理后,可以自行添加防错位的处理代码。
使能DCMI采集
以上是我们使用DCMI的传输配置,但它还没有使能DCMI采集,在实际使用中还需要调用下面两个库函数开始采集数据。
//使能DCMI采集数据
DCMI_Cmd(ENABLE);
DCMI_CaptureCmd(ENABLE);
读取OV5640芯片ID
配置完了STM32的DCMI,还需要控制摄像头,它有很多寄存器用于配置工作模式。利用STM32的I2C接口,可向OV5640的寄存器写入控制参数, 我们先写个读取芯片ID的函数测试一下,见 代码清单:OV5640-7 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | //存储摄像头ID的结构体
typedef struct {
uint8_t PIDH;
uint8_t PIDL;
} OV5640_IDTypeDef;
#define OV5640_SENSOR_PIDH 0x300A
#define OV5640_SENSOR_PIDL 0x300B
/**
* @brief 读取摄像头的ID.
* @param OV5640ID: 存储ID的结构体
* @retval None
*/
void OV5640_ReadID(OV5640_IDTypeDef *OV5640ID)
{
/*读取寄存芯片ID*/
OV5640ID->PIDH = OV5640_ReadReg(OV5640_SENSOR_PIDH);
OV5640ID->PIDL = OV5640_ReadReg(OV5640_SENSOR_PIDL);
}
/**
* @brief 从OV5640寄存器中读取一个字节的数据
* @param Addr: 寄存器地址
* @retval 返回读取得的数据
*/
uint8_t OV5640_ReadReg(uint16_t Addr)
{
uint32_t timeout = DCMI_TIMEOUT_MAX;
uint8_t Data = 0;
/* Generate the Start Condition */
I2C_GenerateSTART(CAMERA_I2C, ENABLE);
/* Test on CAMERA_I2C EV5 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Send DCMI selcted device slave Address for write */
I2C_Send7bitAddress(CAMERA_I2C, OV5640_DEVICE_ADDRESS,
I2C_Direction_Transmitter);
/* Test on I2C1 EV6 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Send I2C1 location address MSB */
I2C_SendData( CAMERA_I2C, (uint8_t)((Addr>>8) & 0xFF) );
/* Test on I2C1 EV8 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C,
I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Clear AF flag if arised */
CAMERA_I2C->SR1 |= (uint16_t)0x0400;
//--------------------------------------------------------
/* Send I2C1 location address LSB */
I2C_SendData( CAMERA_I2C, (uint8_t)(Addr & 0xFF) );
/* Test on I2C1 EV8 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C,
I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Clear AF flag if arised */
CAMERA_I2C->SR1 |= (uint16_t)0x0400;
//--------------------------------------------------------
/* Generate the Start Condition */
I2C_GenerateSTART(CAMERA_I2C, ENABLE);
/* Test on I2C1 EV6 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Send DCMI selcted device slave Address for write */
I2C_Send7bitAddress(CAMERA_I2C, OV5640_DEVICE_ADDRESS,
I2C_Direction_Receiver);
/* Test on I2C1 EV6 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Prepare an NACK for the next data received */
I2C_AcknowledgeConfig(CAMERA_I2C, DISABLE);
/* Test on I2C1 EV7 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED))
{
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Prepare Stop after receiving data */
I2C_GenerateSTOP(CAMERA_I2C, ENABLE);
/* Receive the Data */
Data = I2C_ReceiveData(CAMERA_I2C);
/* return the read data */
return Data;
}
|
在OV5640的PIDH及PIDL寄存器存储了产品ID,PIDH的默认值为0x56,PIDL的默认值为0x40。在代码中我们定义了一个结构体OV5640_IDTypeDef专门存储这些读取得的ID信息。
OV5640_ReadID函数中使用的OV5640_ReadReg函数是使用STM32的I2C外设向某寄存器读写单个字节数据的底层函数, 它与我们前面章节中用到的I2C函数差异是OV5640的寄存器地址是16位的。程序中是先发高8位地址接着发低8位地址,再读取寄存器的值。
向OV5640写入寄存器配置
检测到OV5640的存在后,向它写入配置参数,见 代码清单:OV5640-8 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | unsigned short RGB565_Init[][2]= {
//15fps VGA RGB565 output
// 24MHz input clock, 24MHz PCLK
0x3103, 0x11, // system clock from pad, bit[1]
0x3008, 0x82, // software reset, bit[7]
// delay 5ms
0x3008, 0x42, // software power down, bit[6]
0x3103, 0x03, // system clock from PLL, bit[1]
0x3017, 0xff, // FREX, Vsync, HREF, PCLK, D[9:6] output enable
0x3018, 0xff, // D[5:0], GPIO[1:0] output enable
0x3034, 0x1a, // MIPI 10-bit
0x3037, 0x13, // PLL root divider, bit[4], PLL pre-divider, bit[3:0]
/*以下配置省略*/
}
unsigned short sensor_reg[(sizeof(RGB565_Init)/4)];
/**
* @brief 配置 OV5640为 BMP 模式.
* @retval None
*/
void OV5640_RGB565_Default_Config(void)
{
uint32_t i;
/*摄像头复位*/
OV5640_Reset();
Delay(50);
/* 写入寄存器配置RGB565模式 */
for (i=0; i<(sizeof(RGB565_Init)/4); i++) {
OV5640_WriteReg(RGB565_Init[i][0], RGB565_Init[i][1]);
Delay(5);
sensor_reg[i] = OV5640_ReadReg(RGB565_Init[i][0]);
CAMERA_DEBUG("sensor_reg[0x%x]:%x-%x\n",RGB565_Init[i][0],RGB565_Init[i][1],sensor_reg[i]);
}
Delay(100);
//以默认模式初始化
for (i=0; i<(sizeof(RGB565_WVGA)/2); i++) {
OV5640_WriteReg(RGB565_WVGA[i][0], RGB565_WVGA[i][1]);
}
}
/**
* @brief 写一字节数据到OV5640寄存器
* @param Addr: OV5640 的寄存器地址
* @param Data: 要写入的数据
* @retval 返回0表示写入正常,0xFF表示错误
*/
uint8_t OV5640_WriteReg(uint16_t Addr, uint8_t Data)
{
uint32_t timeout = DCMI_TIMEOUT_MAX;
/* Generate the Start Condition */
I2C_GenerateSTART(CAMERA_I2C, ENABLE);
/* Test on CAMERA_I2C EV5 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Send DCMI selcted device slave Address for write */
I2C_Send7bitAddress(CAMERA_I2C, OV5640_DEVICE_ADDRESS, I2C_Direction_Transmitter);
/* Test on CAMERA_I2C EV6 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Send CAMERA_I2C location address MSB */
I2C_SendData(CAMERA_I2C, (uint8_t)( (Addr >> 8) & 0xFF) );
/* Test on CAMERA_I2C EV8 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
//--------------------------------------------------------
/* Send I2C1 location address LSB */
I2C_SendData( CAMERA_I2C, (uint8_t)(Addr & 0xFF) );
/* Test on I2C1 EV8 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
//--------------------------------------------------------
/* Send Data */
I2C_SendData(CAMERA_I2C, Data);
/* Test on CAMERA_I2C EV8 and clear it */
timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */
while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
/* If the timeout delay is exeeded, exit with error code */
if ((timeout--) == 0) return 0xFF;
}
/* Send I2C1 STOP Condition */
I2C_GenerateSTOP(CAMERA_I2C, ENABLE);
/* If operation is OK, return 0 */
return 0;
}
|
这个OV5640_RGB565Config函数直接把一个初始化的二维数组RGB565_Init和一个分辨率设置的二维数组RGB565_WVGA使用I2C传输到OV5640中, 二维数组的第一维存储的是寄存器地址,第二维存储的是对应寄存器要写入的控制参数。OV5640_WriteReg 函数中,因为OV5640的寄存器地址为16位, 所以写寄存器的时候会先写入高8位的地址接着写入低8位的地址,然后再写入寄存器的值,这个是有别于普通的I2C设备的写入方式,需要特别注意。
如果您对这些寄存器配置感兴趣,可以一个个对着OV5640的寄存器说明来阅读,这些配置主要是把OV5640配置成了WVGA时序模式, 并使用8根数据线输出格式为RGB565的图像数据。我们参考《OV5640_自动对焦照相模组应用指南(DVP_接口)__R2.13C.pdf》文档中第20页4.1.3节的800x480(本配置为854*480, 与说明的类似)预览的寄存器参数进行配置。使摄像头输出为WVGA模式。
初始化OV5640自动对焦功能
写入OV5640的配置参数后,需要向它写入自动对焦固件,初始化自动对焦功能,才能使用自动对焦功能,见 代码清单:OV5640-9 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | void OV5640_AUTO_FOCUS(void)
{
OV5640_FOCUS_AD5820_Init();
OV5640_FOCUS_AD5820_Constant_Focus();
}
static void OV5640_FOCUS_AD5820_Init(void)
{
u8 state=0x8F;
u32 iteration = 100;
u16 totalCnt = 0;
CAMERA_DEBUG("OV5640_FOCUS_AD5820_Init\n");
OV5640_WriteReg(0x3000, 0x20);
totalCnt = sizeof(OV5640_AF_FW);
CAMERA_DEBUG("Total Count = %d\n", totalCnt);
// 写入自动对焦固件 Brust mode
OV5640_WriteFW(OV5640_AF_FW,totalCnt);
OV5640_WriteReg(0x3022, 0x00);
OV5640_WriteReg(0x3023, 0x00);
OV5640_WriteReg(0x3024, 0x00);
OV5640_WriteReg(0x3025, 0x00);
OV5640_WriteReg(0x3026, 0x00);
OV5640_WriteReg(0x3027, 0x00);
OV5640_WriteReg(0x3028, 0x00);
OV5640_WriteReg(0x3029, 0xFF);
OV5640_WriteReg(0x3000, 0x00);
OV5640_WriteReg(0x3004, 0xFF);
OV5640_WriteReg(0x0000, 0x00);
OV5640_WriteReg(0x0000, 0x00);
OV5640_WriteReg(0x0000, 0x00);
OV5640_WriteReg(0x0000, 0x00);
do {
state = (u8)OV5640_ReadReg(0x3029);
CAMERA_DEBUG("when init af, state=0x%x\n",state);
Delay(10);
if (iteration-- == 0) {
CAMERA_DEBUG("[OV5640]STA_FOCUS state check ERROR!!,state=0x%x\n",state);
break;
}
} while (state!=0x70);
OV5640_FOCUS_AD5820_Check_MCU();
return;
} /* OV5640_FOCUS_AD5820_Init */
//set constant focus
void OV5640_FOCUS_AD5820_Constant_Focus(void)
{
u8 state = 0x8F;
u32 iteration = 300;
//send constant focus mode command to firmware
OV5640_WriteReg(0x3023,0x01);
OV5640_WriteReg(0x3022,0x04);
iteration = 5000;
do {
state = (u8)OV5640_ReadReg(0x3023);
if (iteration-- == 0) {
CAMERA_DEBUG("[OV5640]AD5820_Single_Focus time out !!%x\n",state);
return ;
}
Delay(10);
} while (state!=0x00); //0x0 : focused 0x01: is focusing
return;
}
|
OV5640_AUTO_FOCUS函数调用了OV5640_FOCUS_AD5820_Init函数和OV5640_FOCUS_AD5820_Constant_Focus函数,我们先来介绍OV5640_FOCUS_AD5820_Init函数, 首先复位OV5640内部的MCU,然后通过I2C的突发模式写入自动对焦固件,突发模式就是只需要写入首地址,接着就一直写数据,这个过程地址会自增, 直接写完数据位置,对于连续地址写入相当方便。写入固件之后OV5640内部MCU开始初始化,最后检查初始化完成的状态是否为0x70,如果是就代表固件已经写入成功, 并初始化成功。接着,我们需要OV5640_FOCUS_AD5820_Constant_Focus函数来调用自动对焦固件中的持续对焦指令,完成以上步骤后,摄像头就已经初始化完毕。
OV5640的其它模式配置
以上是最基本的摄像头采集过程,而提供的工程在以上基础还增加了一些摄像头的配置,包括分辨率、光照度、饱和度、对比度及特殊模式等, 如OV5640_OutSize_Set、OV5640_BrightnessConfig、OV5640_Color_Saturation、OV5640_ContrastConfig和OV5640_SpecialEffects等函数, 这些函数的本质都是根据函数的输入参数,转化成对应的配置写入到OV5640摄像头的寄存器中,完成相应的配置,下面仅以OV5640_SpecialEffects函数为例进行介绍, 见 代码清单:OV5640-10 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | //特效设置参数表
const static uint8_t OV5640_Effect_reg[][4]= {
0X06,0x40,0X10,0X08,//正常
0X1E,0xA0,0X40,0X08,//冷色
0X1E,0x80,0XC0,0X08,//暖色
0X1E,0x80,0X80,0X08,//黑白
0X1E,0x40,0XA0,0X08,//泛黄
0X40,0x40,0X10,0X08,//反色
0X1E,0x60,0X60,0X08,//偏绿
0X1E,0xF0,0XF0,0X08,//过曝
0X06,0x40,0X10,0X09,//正负片叠加
};
/**
* @brief 特殊效果
* @param 参数用于选择光线模式
* 0x01 正常
* 0x02 冷色
* 0x03 暖色
* 0x04 黑白
* 0x05 泛黄
* 0x06 反色
* 0x07 偏绿
* 0x08 过曝
* 0x09 正负片叠加
* @retval None
*/
void OV5640_SpecialEffects(uint8_t mode)
{
OV5640_WriteReg(0x3212, 0x03); // start group 3
OV5640_WriteReg(0x5580, OV5640_Effect_reg[mode][0]);
OV5640_WriteReg(0x5583, OV5640_Effect_reg[mode][1]); // sat U
OV5640_WriteReg(0x5584, OV5640_Effect_reg[mode][2]); // sat V
OV5640_WriteReg(0x5003, OV5640_Effect_reg[mode][3]);
OV5640_WriteReg(0x3212, 0x13); // end group 3
OV5640_WriteReg(0x3212, 0xa3); // launch group 3
}
|
从代码中可了解到,函数支持1~9作为输入参数,分别对应不同的模式,在函数内部根据不同的输入对寄存器写入相应配置。
设置液晶屏显示窗口
初始化摄像头后,即可以使能DCMI采集,并且设置液晶显示窗口,然后DMA会把数据传输至液晶屏上,见 代码清单:OV5640-11 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /**
* @brief 液晶屏开窗,使能摄像头数据采集
* @param 无
* @retval 无
*/
void ImagDisp(void)
{
//扫描模式,横屏
ILI9806G_GramScan(cam_mode.lcd_scan);
LCD_SetFont(&Font16x32);
LCD_SetColors(RED,BLACK);
ILI9806G_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH); /* 清屏,显示全黑 */
/*DMA会把数据传输到液晶屏,开窗后数据按窗口排列 */
ILI9806G_OpenWindow(cam_mode.lcd_sx,
cam_mode.lcd_sy,
cam_mode.cam_out_width,
cam_mode.cam_out_height);
OV5640_Capture_Control(ENABLE);
}
|
在本函数中,先调用了ILI9806G_GramScan函数把液晶屏设置成横屏显示模式,然后使用ILI9806G_OpenWindow设置一个起始位置为lcd_sx, lcd_sy宽cam_out_width高cam_out_height的窗口(它们是结构体cam_mode的成员,稍后介绍), 根据液晶驱动FSMC的配置,开窗后只要我们往地址FSMC_LCD_ADDRESS写入的数据会一个一个地添加到该窗口中进行显示, 而前面的代码已经配置了DMA搬运摄像头数据的工作,只要执行完本函数最后的OV5640_Capture_Control使能DCMI采集, 一切就会开始正常运转:摄像头根据寄存器的配置输出RGB565的图像,STM32的DCMI外设把接收到的图像数据缓存到DCMI数据寄存器中, 当接收到2个像素数据后,DMA开始传输,把这两个数据搬运到内存地址FSMC_LCD_ADDRESS,往该地址写入数据时触发FSMC产生时序写入到液晶屏, 最终液晶屏把数据显示到窗口上,如此循环往复,完成一帧数据的采集和显示,不断循环,即可实现连续的实时采集与显示。
摄像头配置结构体
由于分辨率、光照度、饱和度、对比度及特殊模式等摄像头配置涉及众多内容,特别是关于分辨率的配置,需要与液晶扫描方向匹配, 否则容易出现显示错误的现象,为了方便使用,工程中定义了一个结构体类型专门用于设置摄像头的这些配置,在初始化摄像头或想修改配置的时候, 修改该变量的内容,然后把它作为参数输入到各种配置函数调用即可。该摄像头配置结构体类型见 代码清单:OV5640-12 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /*摄像头配置结构体*/
typedef struct {
uint8_t frame_rate; //输出帧率
uint16_t cam_isp_sx; //摄像头ISP X起始位置
uint16_t cam_isp_sy; //摄像头ISP Y起始位置
uint16_t cam_isp_width; //摄像头ISP 宽
uint16_t cam_isp_height; //摄像头ISP 高
uint8_t scaling; //是否使用自动缩放,推荐使用,1:使用,0:不使用
uint16_t cam_out_sx; //摄像头输出窗口X起始位置
uint16_t cam_out_sy; //摄像头输出窗口Y起始位置
uint16_t cam_out_width;//输出图像分辨率,宽
uint16_t cam_out_height;//输出图像分辨率,高
uint16_t lcd_sx;//图像显示在液晶屏的X起始位置
uint16_t lcd_sy;//图像显示在液晶屏的Y起始位置
uint8_t lcd_scan;//液晶屏的扫描模式(0-7)
uint8_t light_mode;//光照模式,参数范围[0~4]
int8_t saturation;//饱和度,参数范围[-3 ~ +3]
int8_t brightness;//光照度,参数范围[-4~+4]
int8_t contrast;//对比度,参数范围[-3~+3]
uint8_t effect; //特殊效果,参数范围[0~9]:
int8_t exposure;//曝光补偿,参数范围[-3~+3]
uint8_t auto_focus;//是否使用自动对焦功能 1:使用,0:不使用
} OV5640_MODE_PARAM;
|
结构体类型定义中的代码注释有注明各种配置参数的范围,如其中的frame_rate可以配置摄像头的输出帧率,可以赋值为FRAME_RATE_15FPS或FRAME_RATE_30FPS的宏, 分辨率高时可能会出现图像花屏的现象。
在工程中,提供了三组摄像头及显示配置范例,实验时可以亲自尝试一下来了解,见 代码清单:OV5640-13 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | //摄像头初始化配置
//注意:使用这种方式初始化结构体,要在c/c++选项中选择 C99 mode
OV5640_MODE_PARAM cam_mode = {
/*以下包含几组摄像头配置,可自行测试,保留一组,把其余配置注释掉即可*/
/************配置1***854*480******横屏显示*************/
.frame_rate = FRAME_RATE_15FPS,
//ISP窗口
.cam_isp_sx = 0,
.cam_isp_sy = 0,
.cam_isp_width = 1920,
.cam_isp_height = 1080,
//输出窗口
.scaling = 1, //使能自动缩放
.cam_out_sx = 16, //使能自动缩放后,一般配置成16即可
.cam_out_sy = 4, //使能自动缩放后,一般配置成4即可
.cam_out_width = 854,
.cam_out_height = 480,
//LCD位置
.lcd_sx = 0,
.lcd_sy = 0,
.lcd_scan = 5, //LCD扫描模式,本横屏配置可用1、3、5、7模式
//以下可根据自己的需要调整,参数范围见结构体类型定义
.light_mode = 0,//自动光照模式
.saturation = 0,
.brightness = 0,
.contrast = 0,
.effect = 0, //正常模式
.exposure = 0,
.auto_focus = 1,
/**********配置2*****240*320****竖屏显示****************************/
// .frame_rate = FRAME_RATE_30FPS,
//
// //ISP窗口
// .cam_isp_sx = 0,
// .cam_isp_sy = 0,
//
// .cam_isp_width = 1920,
// .cam_isp_height = 1080,
//
// //输出窗口
// .scaling = 1, //使能自动缩放
// .cam_out_sx = 16, //使能自动缩放后,一般配置成16即可
// .cam_out_sy = 4, //使能自动缩放后,一般配置成4即可
// .cam_out_width = 240,
// .cam_out_height = 320,
//
// //LCD位置
// .lcd_sx = 120,
// .lcd_sy = 267,
// .lcd_scan = 6, //LCD扫描模式,
//
// //以下可根据自己的需要调整,参数范围见结构体类型定义
// .light_mode = 0,//自动光照模式
// .saturation = 0,
// .brightness = 0,
// .contrast = 0,
// .effect = 0, //正常模式
// .exposure = 0,
// .auto_focus = 1,//自动对焦
/*******配置3********640*480****小分辨率****************************/
// .frame_rate = FRAME_RATE_30FPS,
//
// //ISP窗口
// .cam_isp_sx = 0,
// .cam_isp_sy = 0,
//
// .cam_isp_width = 1920,
// .cam_isp_height = 1080,
//
// //输出窗口
// .scaling = 0, //使能自动缩放
// .cam_out_sx = 16, //使能自动缩放后,一般配置成16即可
// .cam_out_sy = 4, //使能自动缩放后,一般配置成4即可
// .cam_out_width = 640,
// .cam_out_height = 480,
//
// //LCD位置
// .lcd_sx = 100,
// .lcd_sy = 0,
// .lcd_scan = 5, //LCD扫描模式,
//
// //以下可根据自己的需要调整,参数范围见结构体类型定义
// .light_mode = 0,//自动光照模式
// .saturation = 0,
// .brightness = 0,
// .contrast = 0,
// .effect = 0, //正常模式
// .exposure = 0,
// .auto_focus = 1,//自动对焦
};
|
代码中使用OV5640_MODE_PARAM类型定义了一个cam_mode全局变量并对其结构体成员赋予了初始值。本工程默认使用以上第一组配置, 采集854*480的图像在液晶屏上横屏显示;而第二组配置是240*320的VGA图像在液晶屏上竖屏显示,注意两组配置中cam_out_width、 cam_out_height和lcd_mode变量值的区别;第三组配置是640*480的分辨率,帧率为30帧。你可亲自尝试以上各组配置,使用时注释掉其余两组即可, 也可以在范例的基础上,自己进行修改测试。
使用摄像头配置结构体时,在初始化摄像头时要调用相应的函数对寄存器进行赋值,本工程中定义了一个函数专门用于把cam_mode的所有配置更新到摄像头, 见 代码清单:OV5640-14 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /**
* @brief 使用cammode参数初始化各项配置
* @param None
* @retval None
*/
void OV5640_USER_Config(void)
{
OV5640_FrameRate_Set(cam_mode.frame_rate);
Delay(100);
OV5640_ISPSize_Set(cam_mode.cam_isp_sx,
cam_mode.cam_isp_sy,
cam_mode.cam_isp_width,
cam_mode.cam_isp_height);
Delay(100);
OV5640_OutSize_Set(cam_mode.scaling,
cam_mode.cam_out_sx,
cam_mode.cam_out_sy,
cam_mode.cam_out_width,
cam_mode.cam_out_height);
Delay(100);
OV5640_BrightnessConfig(cam_mode.brightness);
Delay(100);
OV5640_Color_Saturation(cam_mode.saturation);
Delay(100);
OV5640_ContrastConfig(cam_mode.contrast);
Delay(100);
OV5640_Exposure(cam_mode.exposure);
Delay(100);
OV5640_LightMode(cam_mode.light_mode);
Delay(100);
OV5640_SpecialEffects(cam_mode.effect);
Delay(100);
// Delay(500);
}
|
本函数根据全局变量cam_mode的值,依次调用对应的摄像头配置函数,把cam_mode的配置更新到摄像头寄存器中,所以,当程序运行时需要修改摄像头配置时, 修改全局变量cam_mode的值后,调用OV5640_USER_Config函数即可。
main函数
最后我们来编写main函数,利用前面讲解的函数,控制采集图像,见 代码清单:OV5640-15 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | //修改cam_mode参数,修改后需要重新调用配置函数才生效
void Camera_Mode_Reconfig(void)
{
cam_mode.frame_rate = FRAME_RATE_30FPS,
//ISP窗口
cam_mode.cam_isp_sx = 0;
cam_mode.cam_isp_sy = 0;
cam_mode.cam_isp_width = 1920;
cam_mode.cam_isp_height = 1080;
//输出窗口
cam_mode.scaling = 1; //使能自动缩放
cam_mode.cam_out_sx = 16; //使能自动缩放后,一般配置成16即可
cam_mode.cam_out_sy = 4; //使能自动缩放后,一般配置成4即可
cam_mode.cam_out_width = 320;
cam_mode.cam_out_height = 240;
//LCD位置
cam_mode.lcd_sx = 270;
cam_mode.lcd_sy = 120;
cam_mode.lcd_scan = 5; //LCD扫描模式,
//以下可根据自己的需要调整,参数范围见结构体类型定义
cam_mode.light_mode = 0;//自动光照模式
cam_mode.saturation = 0;
cam_mode.brightness = 0;
cam_mode.contrast = 0;
cam_mode.effect = 0; //正常模式
cam_mode.exposure = 0;
cam_mode.auto_focus = 1;//自动对焦
}
//【*】注意事项:
//本程序暂时不支持摄像头延长线!!!
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint8_t focus_status = 0;
ILI9806G_Init (); //LCD 初始化
LCD_SetFont(&Font16x32);
LCD_SetColors(RED,BLACK);
ILI9806G_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH); /* 清屏,显示全黑 */
Debug_USART_Config();
/* 配置SysTick 为1ms中断一次,时间到后触发定时中断,
*进入stm32fxx_it.c文件的SysTick_Handler处理,通过数中断次数计时
*/
SysTick_Init();
Key_GPIO_Config();
//液晶扫描方向
ILI9806G_GramScan(5);
CAMERA_DEBUG("STM32F407 DCMI 驱动OV5640例程");
/* 初始化摄像头GPIO及IIC */
OV5640_HW_Init();
/* 读取摄像头芯片ID,确定摄像头正常连接 */
OV5640_ReadID(&OV5640_Camera_ID);
if (OV5640_Camera_ID.PIDH == 0x56) {
sprintf((char*)dispBuf, "OV5640 Camera ID:0x%x,initializing... ", OV5640_Camera_ID.PIDH);
ILI9806G_DispStringLine_EN(LINE(0),dispBuf);
CAMERA_DEBUG("检测到摄像头 %x %x",OV5640_Camera_ID.PIDH ,OV5640_Camera_ID.PIDL);
} else {
LCD_SetTextColor(RED);
ILI9806G_DispString_EN(10,10, "Can not detect OV5640 module,please check the connection!");
CAMERA_DEBUG("没有检测到OV5640摄像头,请重新检查连接。");
while (1);
}
OV5640_Init();
OV5640_RGB565_Default_Config();
OV5640_USER_Config();
OV5640_FOCUS_AD5820_Init();
if (cam_mode.auto_focus ==1) {
OV5640_FOCUS_AD5820_Constant_Focus();
focus_status = 1;
}
/*DMA直接传输摄像头数据到LCD屏幕显示*/
ImagDisp();
while (1) {
//运行时动态修改摄像头参数示例
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON ) {
//关闭采集
OV5640_Capture_Control(DISABLE);
//修改Cam mode 参数
Camera_Mode_Reconfig();
OV5640_USER_Config();
if (cam_mode.auto_focus ==1) {
OV5640_FOCUS_AD5820_Constant_Focus();
focus_status = 1;
}
/*DMA直接传输摄像头数据到LCD屏幕显示*/
ImagDisp();
}
//反转对焦状态,若原来在持续对焦则暂停,否则开启
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON ) {
if (focus_status == 1) {
//暂停对焦
OV5640_FOCUS_AD5820_Pause_Focus();
focus_status = 0 ;
} else {
//自动对焦
OV5640_FOCUS_AD5820_Constant_Focus();
focus_status = 1 ;
}
}
//使用串口输出帧率
if (Task_Delay[0]==0) {
/*输出帧率*/
CAMERA_DEBUG("\r\n帧率:%.1f/s \r\n", (double)fps/5.0);
//重置
fps =0;
Task_Delay[0]=5000; //此值每1ms会减1,减到0才可以重新进来这里
}
}
}
|
阅读本代码时,先跳过前面的Camera_Mode_Reconfig函数。先来看在main函数,它初始化了液晶屏、串口、按键和定时器等外设。
摄像头控制部分,首先调用了OV5640_HW_Init函数初始化DCMI的GPIO及I2C,然后调用OV5640_ReadID函数检测摄像头与实验板是否正常连接, 若连接正常则调用OV5640_Init函数初始化DCMI的工作模式及DMA,再调用OV5640_RGB565_Default_Config函数向OV5640写入默认的寄存器配置, 调用OV5640_USER_Config把全局变量cam_mode的值写入摄像头,调用OV5640_FOCUS_AD5820_Init函数设置自动对焦,最后, 一定要记得调用ImagDisp使能DCMI开始捕获数据,这样才能正常开始工作。
在main函数的while循环里,循环检测按键,若按下key1按键,它会调用开头部分的Camera_Mode_Reconfig函数设置新的cam_mode值, 然后调用OV5640_USER_Config更新摄像头配置,它演示了如何在程序运行时利用cam_mode更新摄像头配置。
在while循环中,还有一个条件判断,它使用定时器每5秒统计一次帧率,并使用串口输出。
54.5.2.3. 下载验证¶
把OV5640接到实验板的摄像头接口中,用USB线连接开发板,编译程序下载到实验板,并上电复位,液晶屏会显示摄像头采集得的图像,摄像头会自动调焦。