48. DCMI—OV2640摄像头

本章参考资料:《STM32F4xx参考手册》、《STM32F4xx规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。

关于开发板配套的OV2640摄像头参数可查阅《ov2640datasheet》配套资料获知。

STM32F4芯片具有浮点运算单元,适合对图像信息使用DSP进行基本的图像处理,其处理速度比传统的8、16位机快得多, 而且它还具有与摄像头通讯的专用DCMI接口,所以使用它驱动摄像头采集图像信息并进行基本的加工处理非常适合。 本章讲解如何使用STM32驱动OV2640型号的摄像头。

48.1. 摄像头简介

在各类信息中,图像含有最丰富的信息,作为机器视觉领域的核心部件,摄像头被广泛地应用在安防、探险以及车牌检测等场合。 摄像头按输出信号的类型来看可以分为数字摄像头和模拟摄像头,按照摄像头图像传感器材料构成来看可以分为CCD和CMOS。 现在智能手机的摄像头绝大部分都是CMOS类型的数字摄像头。

48.1.1. 数字摄像头跟模拟摄像头区别

  • 输出信号类型

数字摄像头输出信号为数字信号,模拟摄像头输出信号为标准的模拟信号。

  • 接口类型

数字摄像头有USB接口(比如常见的PC端免驱摄像头)、IEE1394火线接口(由苹果公司领导的开发联盟开发的一种高速度传送接口, 数据传输率高达800Mbps)、千兆网接口(网络摄像头)。模拟摄像头多采用AV视频端子(信号线+地线)或S-VIDEO(即莲花头–SUPER VIDEO, 是一种五芯的接口,由两路视频亮度信号、两路视频色度信号和一路公共屏蔽地线共五条芯线组成)。

  • 分辨率

模拟摄像头的感光器件,其像素指标一般维持在752(H)*582(V)左右的水平,像素数一般情况下维持在41万左右。 现在的数字摄像头分辨率一般从数十万到数千万。但这并不能说明数字摄像头的成像分辨率就比模拟摄像头的高, 原因在于模拟摄像头输出的是模拟视频信号,一般直接输入至电视或监视器,其感光器件的分辨率与电视信号的扫描数呈一定的换算关系, 图像的显示介质已经确定,因此模拟摄像头的感光器件分辨率不是不能做高,而是依据于实际情况没必要做这么高。

48.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传感器。

48.2. OV2640摄像头

本章主要讲解实验板配套的摄像头,它的实物见图 实验板配套的OV2640摄像头 ,该摄像头主要由镜头、图像传感器、板载电路及下方的信号引脚组成。

实验板配套的OV2640摄像头

镜头部件包含一个镜头座和一个可旋转调节距离的凸透镜,通过旋转可以调节焦距,正常使用时,镜头座覆盖在电路板上遮光, 光线只能经过镜头传输到正中央的图像传感器,它采集光线信号,然后把采集得的数据通过下方的信号引脚输出数据到外部器件。

48.2.1. OV2640传感器简介

图像传感器是摄像头的核心部件,上述摄像头中的图像传感器是一款型号为OV2640的CMOS类型数字图像传感器。该传感器支持输出最大为200万像素的图像 (1600x1200分辨率), 支持使用VGA时序输出图像数据,输出图像的数据格式支持YUV(422/420)、YCbCr422、RGB565以及JPEG格式,若直接输出JPEG格式的图像时可大大减少数据量, 方便网络传输。它还可以对采集得的图像进行补偿,支持伽玛曲线、白平衡、饱和度、色度等基础处理。根据不同的分辨率配置, 传感器输出图像数据的帧率从15-60帧可调,工作时功率在125mW-140mW之间。

48.2.2. OV2640引脚及功能框图

OV2640传感器采用BGA封装,它的前端是采光窗口,引脚都在背面引出,引脚的分布见图 OV2640传感器引脚分布图

OV2640传感器引脚分布图

图中的非彩色部分是电源相关的引脚,彩色部分是主要的信号引脚,介绍如下表 OV2640管脚

OV2640管脚

下面我们配合图 OV2640功能框图 中的OV2640功能框图讲解这些信号引脚。

OV2640功能框图
  1. 控制寄存器

标号处的是OV2640的控制寄存器,它根据这些寄存器配置的参数来运行,而这些参数是由外部控制器通过SIO_C和SIO_D引脚写入的, SIO_C与SIO_D使用的通讯协议SCCB跟I2C十分类似,在STM32中我们完全可以直接用I2C硬件外设来控制。

  1. 通信、控制信号及时钟

标号处包含了OV2640的通信、控制信号及外部时钟,其中PCLK、HREF及VSYNC分别是像素同步时钟、行同步信号以及帧同步信号, 这与液晶屏控制中的信号是很类似的。RESETB引脚为低电平时,用于复位整个传感器芯片,PWDN用于控制芯片进入低功耗模式。 注意最后的一个XCLK引脚,它跟PCLK是完全不同的,XCLK是用于驱动整个传感器芯片的时钟信号,是外部输入到OV2640的信号; 而PCLK是OV2640输出数据时的同步信号,它是由OV2640输出的信号。XCLK可以外接晶振或由外部控制器提供, 若要类比XCLK之于OV2640就相当于HSE时钟输入引脚与STM32芯片的关系,PCLK引脚可类比STM32的I2C外设的SCL引脚。

  1. 感光矩阵

标号处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。

  1. 数据输出信号

标号处包含了DSP处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元, 转换出的数据最终通过Y0-Y9引脚输出,一般来说我们使用8根据数据线来传输,这时仅使用Y2-Y9引脚, OV2640与外部器件的连接方式见图 8位数据线接法

8位数据线接法

48.2.3. SCCB时序

外部控制器对OV2640寄存器的配置参数是通过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的数据有效性

SCCB数据读写过程

在SCCB协议中定义的读写操作与I2C也是一样的,只是换了一种说法。它定义了两种写操作,即三步写操作和两步写操作。 三步写操作可向从设备的一个目的寄存器中写入数据,见图 SCCB的三步写操作 。在三步写操作中, 第一阶段发送从设备的ID地址+W标志(等于I2C的设备地址:7位设备地址+读写方向标志),第二阶段发送从设备目标寄存器的8位地址, 第三阶段发送要写入寄存器的8位数据。图中的“X”数据位可写入1或0,对通讯无影响。

SCCB的三步写操作

而两步写操作没有第三阶段,即只向从器件传输了设备ID+W标志和目的寄存器的地址,见图 SCCB的两步写操作 。 两步写操作是用来配合后面的读寄存器数据操作的,它与读操作一起使用,实现I2C的复合过程。

SCCB的两步写操作

两步读操作,它用于读取从设备目的寄存器中的数据,见图 SCCB的两步读操作 。在第一阶段中发送从设备的设备ID+R标志(设备地址+读方向标志)和自由位, 在第二阶段中读取寄存器中的8位数据和写NA位(非应答信号)。由于两步读操作没有确定目的寄存器的地址, 所以在读操作前,必需有一个两步写操作,以提供读操作中的寄存器地址。

SCCB的两步读操作

可以看到,以上介绍的SCCB特性都与I2C无区别,而I2C比SCCB还多出了突发读写的功能, 所以SCCB可以看作是I2C的子集,我们完全可以使用STM32的I2C外设来与OV2640进行SCCB通讯。

48.2.4. OV2640的寄存器

控制OV2640涉及到它很多的寄存器,可直接查询《ov2640datasheet》了解,通过这些寄存器的配置,可以控制它输出图像的分辨率大小、 图像格式及图像方向等。要注意的是OV2640有两组寄存器,这两组寄存器有部分地址重合,通过设置地址为0xFF的RA_DLMT寄存器可以切换寄存器组, 当RA_DLMT寄存器为0时,通过SCCB发送的寄存器地址在DSP相关的寄存器组寻址,见图 DSP相关寄存器说明 ; RA_DLMT寄存器为1时,在Sensor相关的寄存器组寻址,图 Sensor相关寄存器说明

DSP相关寄存器说明 Sensor相关寄存器说明

官方还提供了一个《OV2640_Camera_app》的文档,它针对不同的配置需求,提供了配置范例,见图 调节帧率的寄存器配置范例 。 其中write_SCCB是一个利用SCCB向寄存器写入数据的函数,第一个参数为要写入的寄存器的地址,第二个参数为要写入的内容。

调节帧率的寄存器配置范例

48.2.5. 像素数据输出时序

主控器控制OV2640时采用SCCB协议读写其寄存器,而它输出图像时则使用VGA时序(还可用SVGA、UXGA,这些时序都差不多), 这跟控制液晶屏输入图像时很类似。OV2640输出图像时,一帧帧地输出,在帧内的数据一般从左到右,从上到下, 一个像素一个像素地输出(也可通过寄存器修改方向),见图 摄像头数据输出

摄像头数据输出

例如,见图 像素同步时序 和图 帧图像同步时序 ,若我们使用Y2-Y9数据线,图像格式设置为RGB565, 进行数据输出时,Y2-Y9数据线会在1个像素同步时钟PCLK的驱动下发送1字节的数据信号,所以2个PCLK时钟可发送1个RGB565格式的像素数据。 像素数据依次传输,每传输完一行数据时,行同步信号HREF会输出一个电平跳变信号,每传输完一帧图像时,VSYNC会输出一个电平跳变信号。

像素同步时序 帧图像同步时序

48.3. STM32的DCMI接口简介

STM32F4系列的控制器包含了DCMI数字摄像头接口(Digital cameraInterface), 它支持使用上述类似VGA的时序获取图像数据流,支持原始的按行、帧格式来组织的图像数据,如YUV、RGB, 也支持接收JPEG格式压缩的数据流。接收数据时,主要使用HSYNC及VSYNC信号来同步。

48.3.1. DCMI整体框图

STM32的DCMI接口整体框图见图 DCMI接口整体框图

DCMI接口整体框图

外部接口及时序

上图标号处的是DCMI向外部引出的信号线。DCMI提供的外部接口的方向都是输入的,接口的各个信号线说明见表 DCMI的信号线说明

DCMI的信号线说明

其中DCMI_D数据线的数量可选8、10、12或14位,各个同步信号的有效极性都可编程控制。 它使用的通讯时序与OV2640的图像数据输出接口时序一致,见图 DCMI时序图

DCMI时序图

内部信号及PIXCLK的时钟频率

DCMI接口整体框图 的标号处表示DCMI与内部的信号线。在STM32的内部,使用HCLK作为时钟源提供给DCMI外设。 从DCMI引出有DCMI_IT信号至中断控制器,并可通过DMA_REQ信号发送DMA请求。

DCMI从外部接收数据时,在HCLK的上升沿时对PIXCLK同步的信号进行采样, 它限制了PIXCLK的最小时钟周期要大于2.5个HCLK时钟周期,即最高频率为HCLK的1/4。

48.3.2. DCMI接口内部结构

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来把采集得的数据搬运至内存。

48.3.3. 同步方式

DCMI接口支持硬件同步或内嵌码同步方式,硬件同步方式即使用HSYNC和VSYNC作为同步信号的方式,OV2640就是使用这种同步时序。

而内嵌码同步的方式是使用数据信号线传输中的特定编码来表示同步信息,由于需要用0x00和0xFF来表示编码, 所以表示图像的数据中不能包含有这两个值。利用这两个值,它扩展到4个字节,定义出了2种模式的同步码,每种模式包含4个编码, 编码格式为0xFF0000XY,其中XY的值可通过寄存器设置。当DCMI接收到这样的编码时,它不会把这些当成图像数据, 而是按照表 两种模式的内嵌码 中的编码来解释,作为同步信号。

两种模式的内嵌码

48.3.4. 捕获模式及捕获率

DCMI还支持两种数据捕获模式,分别为快照模式和连续采集模式。快照模式时只采集一帧的图像数据,连续采集模式会一直采集多个帧的数据, 并且可以通过配置捕获率来控制采集多少数据,如可配置为采集所有数据或隔1帧采集一次数据或隔3帧采集一次数据。

48.4. DCMI初始化结构体

与其它外设一样,STM32的DCMI外设也可以使用库函数来控制,其中最主要的配置项都封装到了DCMI_InitTypeDef结构体, 来这些内容都定义在库文件“stm32f4xx_dcmi.h”及“stm32f4xx_ dcmi.c”中,编程时我们可以结合这两个文件内的注释使用或参考库帮助文档。

DCMI_InitTypeDef初始化结构体的内容见 代码清单:OV2640-1

代码清单:OV2640-1 DCMI初始化结构体
 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的初始化。

48.5. DCMI—OV2640摄像头实验

本小节讲解如何使用DCMI接口从OV2640摄像头输出的RGB565格式的图像数据,并把这些数据实时显示到液晶屏上。

学习本小节内容时,请打开配套的“DCMI—OV2640摄像头”工程配合阅读。

48.5.1. 硬件设计

摄像头原理图

本实验采用的OV2640摄像头实物见图 实验板配套的OV2640摄像头 ,其原理图见图 OV2640摄像头原理图

OV2640摄像头原理图

图 28‑18标号处的是OV2640芯片的主电路,在这部分中已对SCCB使用的信号线接了上拉电阻,外部电路可以省略上拉; 标号处的是一个24MHz的有源晶振,它为OV2640提供系统时钟,如果不想使用外部晶振提供时钟源,可以参考图中的R6处贴上0欧电阻, XCLK引脚引出至外部,由外部控制器提供时钟;标号处的是摄像头引脚集中引出的排针接口,使用它可以方便地与STM32实验板中的排母连接。

摄像头与实验板的连接

通过排母,OV2640与STM32引脚的连接关系见图 STM32实验板引出的DCMI接口 。控制摄像头的部分引脚与实验板上的RGB彩灯共用,使用时会互相影响。

STM32实验板引出的DCMI接口

以上原理图可查阅《ov2640—黑白原理图》及《F429开发板黑白原理图》文档获知,若您使用的摄像头或实验板不一样,请根据实际连接的引脚修改程序。

48.5.2. 软件设计

为了使工程更加有条理,我们把摄像头控制相关的代码独立分开存储,方便以后移植。在“LTDC—液晶显示”工程的基础上新建“bsp_ov2640.c”及“bsp_ov2640.h”文件, 这些文件也可根据您的喜好命名,它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。

48.5.2.1. 编程要点

  1. 初始化DCMI时钟,I2C时钟;

  2. 使用I2C接口向OV2640写入寄存器配置;

  3. 初始化DCMI工作模式;

  4. 初始化DMA,用于搬运DCMI的数据到显存空间进行显示;

  5. 编写测试程序,控制采集图像数据并显示到液晶屏。

48.5.2.2. 代码分析

摄像头硬件相关宏定义

我们把摄像头控制硬件相关的配置都以宏的形式定义到 “bsp_ov2640.h”文件中,其中包括I2C及DCMI接口的,见 代码清单:OV2640-2

代码清单:OV2640-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_6
#define CAMERA_I2C_SCL_GPIO_PORT            GPIOB
#define CAMERA_I2C_SCL_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define CAMERA_I2C_SCL_SOURCE               GPIO_PinSource6
#define CAMERA_I2C_SCL_AF                   GPIO_AF_I2C1

#define CAMERA_I2C_SDA_PIN                  GPIO_Pin_7
#define CAMERA_I2C_SDA_GPIO_PORT            GPIOB
#define CAMERA_I2C_SDA_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define CAMERA_I2C_SDA_SOURCE               GPIO_PinSource7
#define CAMERA_I2C_SDA_AF                   GPIO_AF_I2C1

//VSYNC
#define DCMI_VSYNC_GPIO_PORT            GPIOI
#define DCMI_VSYNC_GPIO_CLK             RCC_AHB1Periph_GPIOI
#define DCMI_VSYNC_GPIO_PIN             GPIO_Pin_5
#define DCMI_VSYNC_PINSOURCE            GPIO_PinSource5
#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           GPIOH
#define DCMI_D0_GPIO_CLK            RCC_AHB1Periph_GPIOH
#define DCMI_D0_GPIO_PIN            GPIO_Pin_9
#define DCMI_D0_PINSOURCE           GPIO_PinSource9
#define DCMI_D0_AF                    GPIO_AF_DCMI
/*....省略部分数据线*/

以上代码根据硬件的连接,把与DCMI、I2C接口与摄像头通讯使用的引脚号、引脚源以及复用功能映射都以宏封装起来。

初始化DCMI的 GPIO及I2C

利用上面的宏,初始化DCMI的GPIO引脚及I2C,见 代码清单:OV2640-3

代码清单:OV2640-3 初始化DCMI的GPIO及I2C(省略了部分数据线)
 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
/**
* @brief  初始化控制摄像头使用的GPIO(I2C/DCMI)
* @param  None
* @retval None
*/
void OV2640_HW_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef  I2C_InitStruct;

    /***DCMI引脚配置***/
    /* 使能DCMI时钟 */
    RCC_AHB1PeriphClockCmd(DCMI_PWDN_GPIO_CLK|DCMI_VSYNC_GPIO_CLK |
                        DCMI_HSYNC_GPIO_CLK | DCMI_PIXCLK_GPIO_CLK|
                        DCMI_D0_GPIO_CLK| DCMI_D1_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_PWDN_GPIO_PIN ;
    GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure);
    /*PWDN引脚,高电平关闭电源,低电平供电*/
    GPIO_ResetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN);

    /*数据信号*/
    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);
    /*...省略部分数据信号线*/

    /****** 配置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_7bit;
    I2C_InitStruct.I2C_ClockSpeed = 40000;

    /* 写入配置 */
    I2C_Init(CAMERA_I2C, &I2C_InitStruct);

    /* 使能I2C */
    I2C_Cmd(CAMERA_I2C, ENABLE);
}

与所有使用到GPIO的外设一样,都要先把使用到的GPIO引脚模式初始化,以上代码把DCMI接口的信号线全都初始化为DCMI复用功能, 而PWDN则被初始化成普通的推挽输出模式,并且在初始化完毕后直接控制它为低电平,使能给摄像头供电。

函数中还包含了I2C的初始化配置,使用I2C与OV2640的SCCB接口通讯,这里的I2C模式配置与标准的I2C无异。

配置DCMI的模式

接下来需要配置DCMI的工作模式,我们通过编写OV2640_Init函数完成该功能,见 代码清单:OV2640-4

代码清单:OV2640-4 配置DCMI的模式(bsp_ov2640.c文件)
 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
#define FSMC_LCD_ADDRESS     ((uint32_t)0xD0000000)

/*液晶屏的分辨率,用来计算地址偏移*/
uint16_t lcd_width=800, lcd_height=480;

/*摄像头采集图像的大小,改变这两个值可以改变数据量,
但不会加快采集速度,要加快采集速度需要改成SVGA模式*/
uint16_t img_width=800, img_height=480;
/**
* @brief  配置 DCMI/DMA 以捕获摄像头数据
* @param  None
* @retval None
*/
void OV2640_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_memory 以16位数据为单位, dma_bufsize以32位数据为单位(即像素个数/2)
    OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_height-1)*(lcd_width)*2,img_width*2/4);

    /* 配置中断 */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

    /* 配置中断源 */
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn ;//DMA数据流中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE);

    /* 配置帧中断,接收到帧同步信号就进入中断 */
    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) 调用OV2640_DMA_Config函数开始DMA数据传输, 每传输完一行数据需要调用一次,它包含本次传输的目的首地址及传输的数据量,后面我们再详细解释 ;

(4) 配置DMA中断,DMA每次传输完毕会引起中断, 以便我们在中断服务函数配置DMA传输下一行数据;

(5) 配置DCMI的帧传输中断,为了防止有时DMA出现传输错误或传输速度跟不上导致数据错位、 偏移等问题,每次DCMI接收到摄像头的一帧数据,得到新的帧同步信号后(VSYNC),就进入中断, 复位DMA,使它重新开始一帧的数据传输。

配置DMA数据传输

上面的DCMI配置函数中调用了OV2640_DMA_Config函数开始了DMA传输,该函数的定义见 代码清单:OV2640-5

代码清单:OV2640-5 配置DMA数据传输(bsp_ov2640.c文件)
 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
/**
* @brief  配置 DCMI/DMA 以捕获摄像头数据
* @param  DMA_Memory0BaseAddr:本次传输的目的首地址
* @param DMA_BufferSize:本次传输的数据量(单位为字,即4字节)
*/
void OV2640_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_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_Enable; //寄存器地址自增
    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接收到的数据从它的数据寄存器搬运到SDRAM显存中,从而直接使用液晶屏显示摄像头采集得的图像。 它包含2个输入参数DMA_Memory0BaseAddr和DMA_BufferSize,其中DMA_Memory0BaseAddr用于设置本次DMA传输的目的首地址, 该参数会被赋值到结构体成员DMA_InitStructure.DMA_Memory0BaseAddr中。DMA_BufferSize则用于指示本次DMA传输的数据量, 它会被赋值到结构体成员DMA_InitStructure.DMA_BufferSize中,要注意它的单位是一个字,即4字节,如我们要传输60字节的数据时, 它应配置为15。在前面的OV2640_Init函数中,对这个函数有如下调用:

#define FSMC_LCD_ADDRESS     ((uint32_t)0xD0000000)

/*液晶屏的分辨率,用来计算地址偏移*/
uint16_t lcd_width=800, lcd_height=480;

/*摄像头采集图像的大小,改变这两个值可以改变数据量,
但不会加快采集速度,要加快采集速度需要改成SVGA械*/
uint16_t img_width=800, img_height=480;


//开始传输,从后面开始一行行扫描上来,实现数据翻转
//dma_memory 以16位数据为单位, dma_bufsize以32位数据为单位(即像素个数/2)
OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_height-1)*(lcd_width)*2,img_width*2/4);

其中的lcd_width和lcd_height是液晶屏的分辨率,img_width和img_heigh表示摄像头输出的图像的分辨率,FSMC_LCD_ADDRESS是液晶层的首个显存地址。 另外,本工程中显示摄像头数据的这个液晶层采用RGB565的像素格式,每个像素点占据2个字节。

所以在上面的函数调用中,第一个输入参数:

【FSMC_LCD_ADDRESS+(lcd_height-1)*(lcd_width)*2】

它表示的是液晶屏最后一行的第一个像素的地址。

而第二个输入参数:

【img_width*2/4】

它表示表示摄像头一行图像的数据量,单位为字,即用一行图像数据的像素个数除以2即可。注意这里使用的变量是“img_width”而不是的“lcd_width”。

由于这里配置的是第一次DMA传输,它把DCMI接收到的第一行摄像头数据传输至液晶屏的最后一行,见图 DMA传输过程 , 再配合在后面分析的中断函数里的多次DMA配置,摄像头输出的数据会一行一行地“由下至上”显示到液晶屏上。

DMA传输过程

把摄像头输出的第一行数据显示到液晶屏的最后一行,是因为摄像头输出的原图像是颠倒的,这样处理可方便观看实验现象(实际上OV2640可配置寄存器来直接输出镜像数据, 但不知为何配置该功能后采集得的图像失真,所以我们就采用这样颠倒的方法处理了)。

DMA传输完成中断及帧中断

OV2640_Init函数初始化了DCMI,使能了帧中断、DMA传输完成中断,并使能了第一次DMA传输,当这一行数据传输完成时, 会进入DMA中断服务函数,见 代码清单:OV2640-6 中的DMA2_Stream1_IRQHandler。

代码清单:OV2640-6 DMA传输完成中断与帧中断(stm32f4xx_it.c文件)
 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
extern uint16_t lcd_width, lcd_height;
extern uint16_t img_width, img_height;

//记录传输了多少行
static uint16_t line_num =0;
//DMA传输完成中断服务函数
void DMA2_Stream1_IRQHandler(void)
{
    if (  DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1) == SET )
    {
        /*行计数*/
        line_num++;
        if (line_num==img_height)
        {
            /*传输完一帧,计数复位*/
            line_num=0;
        }
        /*DMA 一行一行传输*/
        OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_width*2*(lcd_height-line_num-1)),img_width*2/4);
        DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1);
    }
}


//帧中断服务函数,使用帧中断重置line_num,可防止有时掉数据的时候DMA传送行数出现偏移
void DCMI_IRQHandler(void)
{
    if (  DCMI_GetITStatus (DCMI_IT_FRAME) == SET )
    {
        /*传输完一帧,计数复位*/
        line_num=0;
        DCMI_ClearITPendingBit(DCMI_IT_FRAME);
    }
}

DMA中断服务函数中主要是使用了一个静态变量line_num来记录已传输了多少行数据,每进一次DMA中断时自加1,由于进入一次中断就代表传输完一行数据, 所以line_num的值等于lcd_height时(摄像头输出的数据行数),表示传输完一帧图像,line_num复位为0,开始另一帧数据的传输。 line_num计数完毕后利用前面定义的OV2640_DMA_Config函数配置新的一行DMA数据传输,它利用line_num变量计算显存地址的行偏移, 控制DCMI数据被传送到正确的位置,每次传输的都是一行像素的数据量。

当DCMI接口检测到摄像头传输的帧同步信号时,会进入DCMI_IRQHandler中断服务函数,在这个函数中不管line_num原来的值是什么, 它都把line_num直接复位为0,这样下次再进入DMA中断服务函数的时候,它会开始新一帧数据的传输。这样可以利用DCMI的硬件同步信号, 而不只是依靠DMA自己的传输计数,这样可以避免有时STM32内部DMA传输受到阻塞而跟不上外部摄像头信号导致的数据错误。

使能DCMI采集

以上是我们使用DCMI的传输配置,但它还没有使能DCMI采集,在实际使用中还需要调用下面两个库函数开始采集数据。

//使能DCMI采集数据
DCMI_Cmd(ENABLE);
DCMI_CaptureCmd(ENABLE);

读取OV2640芯片ID

配置完了STM32的DCMI,还需要控制摄像头,它有很多寄存器用于配置工作模式。利用STM32的I2C接口,可向OV2640的寄存器写入控制参数, 我们先写个读取芯片ID的函数测试一下,见 代码清单:OV2640-7

代码清单:OV2640-7 读取OV2640的芯片ID(bsp_ov2640.c文件)
 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
//存储摄像头ID的结构体
typedef struct
{
    uint8_t Manufacturer_ID1;
    uint8_t Manufacturer_ID2;
    uint8_t PIDH;
    uint8_t PIDL;
} OV2640_IDTypeDef;
/*寄存器地址*/
#define OV2640_DSP_RA_DLMT       0xFF
#define OV2640_SENSOR_MIDH       0x1C
#define OV2640_SENSOR_MIDL       0x1D
#define OV2640_SENSOR_PIDH       0x0A
#define OV2640_SENSOR_PIDL       0x0B
/**
* @brief  读取摄像头的ID.
* @param  OV2640ID: 存储ID的结构体
* @retval None
*/
void OV2640_ReadID(OV2640_IDTypeDef *OV2640ID)
{
    /*OV2640有两组寄存器,设置0xFF寄存器的值为0或为1时可选择使用不同组的寄存器*/
    OV2640_WriteReg(OV2640_DSP_RA_DLMT, 0x01);

    /*读取寄存芯片ID*/
    OV2640ID->Manufacturer_ID1 = OV2640_ReadReg(OV2640_SENSOR_MIDH);
    OV2640ID->Manufacturer_ID2 = OV2640_ReadReg(OV2640_SENSOR_MIDL);
    OV2640ID->PIDH = OV2640_ReadReg(OV2640_SENSOR_PIDH);
    OV2640ID->PIDL = OV2640_ReadReg(OV2640_SENSOR_PIDL);
}

在OV2640的MIDH及MIDL寄存器中存储了它的厂商ID,默认值为0x7F和0xA2;而PIDH及PIDL寄存器存储了产品ID,PIDH的默认值为0x26, PIDL的默认值不定,可能的值为0x40、0x41及0x42。在代码中我们定义了一个结构体OV2640_IDTypeDef专门存储这些读取得的ID信息。

由于这些寄存器都是属于Sensor组的,所以在读取这些寄存器的内容前,需要先把OV2640中0xFF地址的DLMT寄存器值设置为1。

OV2640_ReadID函数中使用的OV2640_ReadReg及OV2640_WriteReg函数是使用STM32的I2C外设向某寄存器读写单个字节数据的底层函数, 它与我们前面章节中用到的I2C函数大同小异,就不展开分析了。

向OV2640写入寄存器配置

检测到OV2640的存在后,向它写入配置参数,见 代码清单:OV2640-8

代码清单:OV2640-8向OV2640写入寄存器配置
 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
/* OV2640的 SVGA是 600列*800行的,在800列*480行的液晶屏上不能全屏*/
/* 所以直接用 UXGA 模式,再根据所需的图像窗口裁剪 */
const unsigned char OV2640_UXGA[][2]=
{
    0xff, 0x00,
    0x2c, 0xff,
    0x2e, 0xdf,
    0xff, 0x01,
    0x3c, 0x32,
    0x11, 0x00,
    0x09, 0x02,
    0x04, 0x20|0x80,    //水平翻转
    0x13, 0xe5,

    /*....以下省略*/
}


/**
* @brief  配置OV2640为UXGA模式,并设置输出图像大小
* @param  None
* @retval None
*/
void OV2640_UXGAConfig(void)
{
    uint32_t i;

    /* 写入寄存器配置 */
    for (i=0; i<(sizeof(OV2640_UXGA)/2); i++)
    {
        OV2640_WriteReg(OV2640_UXGA[i][0], OV2640_UXGA[i][1]);
    }
    /*设置输出的图像大小*/
    OV2640_OutSize_Set(img_width,img_height);
}

这个OV2640_UXGAConfig函数简单粗暴,它直接把一个二维数组OV2640_UXGA使用I2C传输到OV2640中,该二维数组的第一维存储的是寄存器地址, 第二维存储的是对应寄存器要写入的控制参数。

如果您对这些寄存器配置感兴趣,可以一个个对着OV2640的寄存器说明来阅读,阅读时要注意区分DLMT寄存器(地址0xFF)为1或为0时的寄存器组。 总的来部,这些配置主要是把OV2640配置成了UXGA时序模式,并使用8根数据线输出格式为RGB565的图像数据。

其中UXGA时序是指它最大可输出1600x1200分辨率的图像,OV2640还支持使用SVGA时序输出最大分辨率为800x600的图像, 相对于UXGA,它可使用更高的帧率输出,但由于它规定好了数据是800行、600列,而我们的液晶屏在横屏状态下,无法直接全屏显示(800列、480行), 所以就把OV2640配置成UXGA模式了。通过修改COM7寄存器(地址0x12)可改变时序模式。

main函数

最后我们来编写main函数,利用前面讲解的函数,控制采集图像,见 代码清单:OV2640-9

代码清单:OV2640-9 main函数
 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
/**
* @brief  主函数
* @param  无
* @retval 无
*/
int main(void)
{

    /*摄像头与RGB LED灯共用引脚,不要同时使用LED和摄像头*/
    Debug_USART_Config();

    /*初始化液晶屏*/
    LCD_Init();
    LCD_LayerInit();
    LTDC_Cmd(ENABLE);

    /*把背景层刷黑色*/
    LCD_SetLayer(LCD_BACKGROUND_LAYER);
    LCD_SetTransparency(0xFF);
    LCD_Clear(LCD_COLOR_BLACK);

    /*初始化后默认使用前景层*/
    LCD_SetLayer(LCD_FOREGROUND_LAYER);
    /*默认设置不透明 ,该函数参数为不透明度,范围 0-0xff ,
    0为全透明,0xff为不透明*/
    LCD_SetTransparency(0xFF);
    LCD_Clear(TRANSPARENCY);

    LCD_SetColors(LCD_COLOR_RED,TRANSPARENCY);

    LCD_ClearLine(LINE(18));
    LCD_DisplayStringLine_EN_CH(LINE(18),(uint8_t* )" 模式:UXGA 800x480");

    CAMERA_DEBUG("STM32F429 DCMI 驱动OV2640例程");

    /* 初始化摄像头GPIO及IIC */
    OV2640_HW_Init();

    /* 读取摄像头芯片ID,确定摄像头正常连接 */
    OV2640_ReadID(&OV2640_Camera_ID);

    if (OV2640_Camera_ID.PIDH  == 0x26)
    {
        CAMERA_DEBUG("%x %x",OV2640_Camera_ID.Manufacturer_ID1 ,
                    OV2640_Camera_ID.Manufacturer_ID2);
    }
    else
    {
        LCD_SetTextColor(LCD_COLOR_RED);
    LCD_DisplayStringLine_EN_CH(LINE(0),(uint8_t*) "没有检测到OV2640,请重新检查连接。");
        CAMERA_DEBUG("没有检测到OV2640摄像头,请重新检查连接。");
        while (1);
    }

    OV2640_Init();
    OV2640_UXGAConfig();

    //使能DCMI采集数据
    DCMI_Cmd(ENABLE);
    DCMI_CaptureCmd(ENABLE);

    /*DMA直接传输摄像头数据到LCD屏幕显示*/
    while (1)
    {
    }
}

在main函数中,首先初始化了液晶屏,注意它是把摄像头使用的液晶层初始化成RGB565格式了,可直接在工程的液晶底层驱动解这方面的内容。

摄像头控制部分,首先调用了OV2640_HW_Init函数初始化DCMI及I2C,然后调用OV2640_ReadID函数检测摄像头与实验板是否正常连接, 若连接正常则调用OV2640_Init函数初始化DCMI的工作模式及DMA,再调用OV2640_UXGAConfig函数向OV2640写入寄存器配置,最后, 一定要记住调用库函数DCMI_Cmd及DCMI_CaptureCmd函数使能DCMI开始捕获数据,这样才能正常开始工作。

48.5.2.3. 下载验证

把OV2640接到实验板的摄像头接口中,用USB线连接开发板,编译程序下载到实验板,并上电复位,液晶屏会显示摄像头采集得的图像,通过旋转镜头可以调焦。