OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 28|回复: 0

《STM32H562开发指南 V1.0 》第四十一章 摄像头实验

[复制链接]

1417

主题

1433

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
6045
金钱
6045
注册时间
2019-5-8
在线时间
1726 小时
发表于 3 小时前 | 显示全部楼层 |阅读模式
第四十一章 摄像头实验

1)实验平台:正点原子Mini STM32H562开发板

2)章节摘自【正点原子】STM32H562开发指南 V1.0

3)购买链接: https://detail.tmall.com/item.htm?id=753606659612

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h562_mini.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:414052738


2.jpg

3.png

STM32H562具有DCMI接口,所以我们的STM32H562开发板板载了一个摄像头接口(P4),该接口可以用来连接ALIENTEK OV5640/OV2640/OV7725等摄像头模块。本章,我们将使用STM32驱动ALIENTEK OV5640摄像头模块,实现摄像头功能。
本章分为如下几个小节:
41.1 OV5640和DCMI简介
41.2 硬件设计
41.3 程序设计
41.4 下载验证


41.1 OV5640和DCMI简介
本节将分为两个部分,分别介绍OV5640简介和STM32H562 DCMI接口简介。另外,所有OV5640的相关资料,都在光盘:A盘→7,硬件资料→OV5640资料 文件夹里面。

41.1.1 OV5640简介
OV5640是OV(OmniVision)公司生产的一颗1/4寸的CMOS QSXGA(2592*1944)图像传感器,提供了一个完整的500W像素摄像头解决方案,并且集成了自动对焦(AF)功能,具有非常高的性价比。
该传感器体积小、工作电压低,提供单片QSXGA摄像头和影像处理器的所有功能。通过SCCB 总线控制,可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率8/10位影像数据。该产品QSXGA图像最高达到15帧/秒(1080P图像可达30帧,720P图像可达60帧,QVGA分辨率时可达120帧)。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、对比度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、拖尾、浮散等,提高图像质量,得到清晰稳定的彩色图像。
OV5640的特点有:
采用1.4μm*1.4μm像素大小,并且使用OmniBSI技术以达到更高性能(高灵敏度、低串扰和低噪声)
自动图像控制功能:自动曝光(AEC)、自动白平衡(AWB)、自动消除灯光条纹、自动黑电平校准(ABLC)和自动带通滤波器(ABF)等。
支持图像质量控制:色饱和度调节、色调调节、gamma校准、锐度和镜头校准等
标准的SCCB接口,兼容IIC接口
支持RawRGB、RGB(RGB565/RGB555/RGB444)、CCIR656、YUV(422/420)、YCbCr(422)和压缩图像(JPEG)输出格式
支持QSXGA(500W)图像尺寸输出,以及按比例缩小到其他任何尺寸
支持闪光灯
支持图像缩放、平移和窗口设置
支持图像压缩,即可输出JPEG图像数据
支持数字视频接口(DVP)和MIPI接口
支持自动对焦
自带嵌入式微处理器
OV5640的功能框图图如图41.1.1.1所示:


第四十一章 摄像头实验1152.png
图41.1.1.1 OV5640功能框图

其中image array部分的尺寸,OV5640的官方数据并没有给出具体的数字,其最大的有效输出尺寸为:2592*1944,即500W像素,我们根据官方提供的一些应用文档,发现其设置的image array最大为:2632*1951,所以,在接下来的介绍,我们设定其image array最大为2632*1951。

1、DVP接口说明
OV5640支持数字视频接口(DVP)和MIPI接口,因为我们的STM32H562使用的DCMI接口,仅支持DVP接口,所以,OV5640必须使用DVP输出接口,才可以连接我们的阿波罗STM32开发板。
OV5640提供一个10位DVP接口(支持8位接法),其MSB和LSB可以程序设置先后顺序,正点原子OV5640模块采用默认的8位连接方式,如图41.1.1.2所示:


第四十一章 摄像头实验1530.png
图41.1.1.2 OV5640默认8位连接方式

OV5640的寄存器通过SCCB时序访问并设置,SCCB时序和IIC时序十分类似。SCCB 与标准的 I2C 协议的区别是它每次传输只能写入或读取一个字节的数据,而 I2C协议是支持突发读写的,即在一次传输中可以写入多个字节的数据(EEPROM中的页写入时序即突发写)。在本章我们不做介绍,请大家参考光盘《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》这个文档。

2、窗口设置说明
接下来,我们介绍一下OV5640的:ISP(Image Signal Processor)输入窗口设置、预缩放窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下。他们的设置关系,如图41.1.1.3所示:


第四十一章 摄像头实验1911.png
图41.1.1.3 OV5640各窗口设置关系

ISP输入窗口设置(ISP input size)
该设置允许用户设置整个传感器区域(physical pixel size ,2632*1951)的感兴趣部分,也就是在传感器里面开窗(X_ADDR_ST、Y_ADDR_ST、X_ADDR_END和Y_ADDR_END),开窗范围从0*0~2632*1951都可以设置,该窗口所设置的范围,将输入ISP进行处理。
ISP输入窗口,通过:0X3800~0X3807等8个寄存器进行设置,这些寄存器的定义请看:OV5640_CSP3_DS_2.01_Ruisipusheng.pdf 这个文档(下同)。
预缩放窗口设置(pre-scaling size)
该设置允许用户在ISP输入窗口的基础上,再次设置将要用于缩放的窗口大小。该设置仅在ISP输入窗口内进行x/y方向的偏移(X_OFFSET/Y_OFFSET)。通过:0X3810~0X3813等4个寄存器进行设置。
输出大小窗口设置(data output size)
该窗口是以预缩放窗口为原始大小,经过内部DSP进行缩放处理后,输出给外部的图像窗口大小。它控制最终的图像输出尺寸(X_OUTPUT_SIZE/Y_OUTPUT_SIZE)。通过:0X3808~0X380B等4个寄存器进行设置。注意:当输出大小窗口与预缩放窗口比例不一致时,图像将进行缩放处理(会变形),仅当两者比例一致时,输出比例才是1:1(正常)。  
图41.1.1.4中,右侧data output size区域,才是OV5640输出给外部的图像尺寸,也就是显示在LCD上面的图像大小。输出大小窗口与预缩放窗口比例不一致时,会进行缩放处理,在LCD上面看到的图像将会变形。

3、输出时序说明
接下来,我们介绍一下OV5640的图像数据输出时序。首先我们简单介绍一些定义:
QSXGA,这里指:分辨率为2592*1944的输出格式,类似的还有:QXGA(2048*1536)、UXGA(1600*1200)、SXGA(1280*1024)、WXGA+(1440*900)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、QVGA(320*240)和QQVGA(160*120)等。
PCLK,即像素时钟,一个PCLK时钟,输出一个像素(或半个像素)。
VSYNC,即帧同步信号。
HREF /HSYNC,即行同步信号。
OV5640的图像数据输出(通过Y[9:0])就是在PCLK,VSYNC和HREF/ HSYNC的控制下进行的。首先看看行输出时序,如图41.1.1.4所示:


第四十一章 摄像头实验3049.png
图41.1.1.4 OV5640行输出时序

从上图可以看出,图像数据在HREF为高的时候输出,当HREF变高后,每一个PCLK时钟,输出一个8位/10位数据。我们采用8位接口,所以每个PCLK输出1个字节,且在RGB/YUV输出格式下,每个tp=2个Tpclk,如果是Raw格式,则一个tp=1个Tpclk。比如我们采用QSXGA时序,RGB565格式输出,每2个字节组成一个像素的颜色(低字节在前,高字节在后),这样每行输出总共有2592*2个PCLK周期,输出2592*2个字节。
再来看看帧时序(QSXGA模式),如图41.1.1.5所示:


第四十一章 摄像头实验3325.png
图41.1.1.5 OV5640帧时序

上图清楚的表示了OV5640在QSXGA模式下的数据输出。我们按照这个时序去读取OV5640的数据,就可以得到图像数据。

4、自动对焦(Auto Focus)说明
OV5640由内置微型控制器完成自动对焦,并且VCM(Voice Coil Motor,即音圈马达)驱动器也已集成在传感器内部。微型控制器的控制固件(firmware)从主机下载。当固件运行后,内置微型控制器从OV5640传感器读得自动对焦所需的信息,计算并驱动VCM马达带动镜头到达正确的对焦位置。主机可以通过IIC命令控制微型控制器的各种功能。
OV5640的自动对焦命令(通过SCCB总线发送),如表41.1.1.1所示:


1.png
表41.1.1.1 OV5640自动对焦命令

OV5640内部的微控制器收到自动对焦命令后会自动将CMD_MAIN(0X3022)寄存器数据清零,当命令完成后会将CMD_ACK(0X3023)寄存器数据清零。
自动对焦(AF)过程
①在第一次进入图像预览的时候(图像可以正常输出时),下载固件(firmware)。
②拍照前,自动对焦,对焦完成后,拍照。
③拍照完毕,释放马达到初始状态。
接下来,我们分别说明。
①下载固件
OV5640初始化完成后,就可以下载AF自动对焦固件了,其操作和下载初始化参数类似,AF固件下载地址为:0X8000,初始化数组由厂家提供(本例程该数组保存在ov5640af.h里面),下载固件完成后,通过检查0X3029寄存器的值,来判断固件状态(等于0X70,说明正常)。
②自动对焦
OV5640支持单次自动对焦和持续自动对焦,通过0X3022寄存器控制。单次自动对焦过程如下:
1,将0X3022寄存器写为0X03,开始单点对焦过程。
2,读取寄存器0X3029,如果返回值为0X10,代表对焦已完成。
3,写寄存器0X3022为0X06,暂停对焦过程,使镜头将保持在此对焦位置。
其中,前两步是必须的,第三步,可以不要,因为单次自动对焦完成以后,就不会继续自动对焦了,镜头也就不会动了。
持续自动对焦过程如下:
1,将0X22寄存器写为0X08,释放马达到初始位置(对焦无穷远)。
2,将0X3022寄存器写为0X04,启动持续自动对焦过程。
3,读取寄存器0X3023,等待命令完成。
4,当OV5640每次检测到失焦时,就会自动进行对焦(一直检测)。
③释放马达,结束自动对焦
最后,在拍照完成,或者需要结束自动对焦的时候,我们对在寄存器0X3022写入0X08,即可释放马达,结束自动对焦。
最后说一下OV5640的图像数据格式,我们一般用2种输出方式:RGB565和JPEG。当输出RGB565格式数据的时候,时序完全就是上面两幅图介绍的关系。以满足不同需要。而当输出数据是JPEG数据的时候,同样也是这种方式输出(所以数据读取方法一模一样),不过PCLK数目大大减少了,且不连续,输出的数据是压缩后的JPEG数据,输出的JPEG数据以:0XFF,0XD8开头,以0XFF,0XD9结尾,且在0XFF,0XD8之前,或者0XFF,0XD9之后,会有不定数量的其他数据存在(一般是0),这些数据我们直接忽略即可,将得到的0XFF,0XD8~0XFF,0XD9之间的数据,保存为.jpg/.jpeg文件,就可以直接在电脑上打开看到图像了。
OV5640自带的JPEG输出功能,大大减少了图像的数据量,使得其在网络摄像头、无线视频传输等方面具有很大的优势。OV5640我们就介绍到这,关于OV5640更详细的介绍,请大家参考:A盘→7,硬件资料→OV5640资料→OV5640_CSP3_DS_2.01_Ruisipusheng.pdf。
ALIENTEK OV5640摄像头模块
本实验,我们将使用MiniPRO STM32H562开发板的DCMI接口连接ALIENTEK OV5640摄像头模块,该模块采用8位数据输出接口,自带24M有源晶振,无需外部提供时钟,模组支持自动对焦功能,且支持闪光灯,整个模块只需提供3.3V 供电即可正常使用。
ALIENTEK OV5640摄像头模块外观如图41.1.1.6所示:


第四十一章 摄像头实验5348.png
图41.1.1.6 ALIENTEK OV5640摄像头模块外观图

模块原理图如图41.1.1.7所示:

第四十一章 摄像头实验5403.png
图41.1.1.7 ALIENTEK OV5640摄像头模块原理图

从上图可以看出,ALIENTEK OV5640摄像头模块自带了有源晶振,用于产生24M时钟作为OV5640的XCLK输入,模块的闪光灯(LED1&LED2)由OV5640的STROBE脚控制(可编程控制)。同时自带了稳压芯片,用于提供OV5640稳定的2.8V和1.5V工作电压,模块通过一个2*9的双排排针(P1)与外部通信,与外部的通信信号如表41.1.1.2所示:

2.png
表41.1.1.2 OV5640模块信号及其作用描述

41.1.2 STM32H562 DCMI接口简介
STM32H562自带了一个数字摄像头(DCMI)接口,该接口是一个同步并行接口,能够接收外部8位、10位、12位或 14位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据格式:YCbCr4:2:2/RGB565逐行视频和压缩数据 (JPEG)。
STM32H562 DCM接口特点:
8 位、10 位、12 位或 14 位并行接口
内嵌码/外部行同步和帧同步
连续模式或快照模式
裁剪功能
支持以下数据格式:
1,8/10/12/14 位逐行视频:单色或原始拜尔(Bayer)格式
2,YCbCr 4:2:2逐行视频
3,RGB 565 逐行视频
4,压缩数据:JPEG
DCMI接口包括如下一些信号:
1,数据输入(D[0:13]),用于接摄像头的数据输出,接OV5640我们只用了8位数据。
2,水平同步(行同步)输入(HSYNC),用于接摄像头的HSYNC/HREF信号。
3,垂直同步(场同步)输入(VSYNC),用于接摄像头的VSYNC信号。
4,像素时钟输入(PIXCLK),用于接摄像头的PCLK信号。
DCMI接口是一个同步并行接口,可接收高速(可达140 MB/s)数据流。该接口包含多达14条数据线(D13-D0)和一条像素时钟线(PIXCLK)。像素时钟的极性可以编程,因此可以在像素时钟的上升沿或下降沿捕获数据。
DCMI接收到的摄像头数据被放到一个32位数据寄存器(DCMI_DR)中,然后通过通用 DMA进行传输。图像缓冲区由 DMA 管理,而不是由摄像头接口管理。
从摄像头接收的数据可以按行/帧来组织(原始YUV/RGB/拜尔模式),也可以是一系列 JPEG图像。要使能 JPEG 图像接收,必须将 JPEG 位(DCMI_CR 寄存器的位 3)置 1。
数据流可由可选的 HSYNC(水平同步)信号和 VSYNC(垂直同步)信号硬件同步,或者通 过数据流中嵌入的同步码同步。


41.1.2.1 DCMI接口功能概述
STM32H562 DCMI接口的框图如图41.1.2.1.1所示:


第四十一章 摄像头实验6727.png
图41.1.2.1.1 DCMI接口框图

上图左边可以看到的是DCMI的内部信号,如下表所示:

3.png
表41.1.2.1.1 DCMI内部信号

dcmi_dma,DCMI DMA请求信号。当DCMI_CR寄存器中的 CAPTURE位置1时,激活DMA接口。摄像头接口每次在其寄存器中收到一个完整的32位数据块时,都将触发一个DMA请求。
dcmi_it,DCMI中断请求信号。DCMI有五种中断源,所有中断都可通过软件屏蔽。全局中断 (dcmi_it) 是所有单个中断的逻辑或运算所得的结果。
DCMI的五种中断源,如下表所示:


4.png
表41.1.2.1.2 DCMI中断源

dcmi_hclk,DCMI 接口时钟,时钟源自来于HCLK2(我们设置为250MHz)。
上图中间部分就是DCMI内核。
上图右边的DCMI物理接口,具体如下表所示:


5.png
表41.1.2.1.3 DCMI物理接口

该接口由11/13/15/17个输入信号组成。仅支持从模式。根据 DCMI_CR 寄存器中 EDM[1:0] 位的设置,摄像头接口可以捕获8位、10位、12位或14位数据。如果使用的位数少于14,则必须将未使用的输入引脚接地。
DCMI数据与DCMI_PIXCLK(即PCLK)保持同步,并根据像素时钟的极性在像素时钟上升沿/下降沿发生变化。
DCMI_HSYNC(HREF)信号指示行的开始/结束。
DCMI_VSYNC 信号指示帧的开始/结束。
DCMI信号波形,如图41.1.2.1.2所示:


第四十一章 摄像头实验7757.png
图41.1.2.1.2 DCMI信号波形

上图中,对应设置为:DCMI_PIXCLK的捕获沿为下降沿,DCMI_HSYNC和DCMI_VSYNC的有效状态为1,注意,这里的有效状态实际上对应的是指示数据在并行接口上无效时,HSYNC/VSYNC引脚上面的引脚电平。
本实验我们用到DCMI的8位数据宽度,通过设置DCMI_CR中的EDM[1:0]=00设置。此时DCMI_D0~D7有效,DCMI_D8~D13上的数据则忽略,这个时候,每次需要4个像素时钟来捕获一个32位数据。捕获的第一个数据存放在32位字的LSB位置,第四个数据存放在32位字的MSB位置 ,捕获数据字节在32位字中的排布如表41.1.2.1.4所示:


第四十一章 摄像头实验8073.png
表41.1.2.1.4 8位捕获数据在32位字中的排布

从表41.1.2.1.4可以看出,STM32H562的DCMI接口,接收的数据是低字节在前,高字节在后的,所以,要求摄像头输出数据也是低字节在前,高字节在后才可以,否则就还得程序上处理字节顺序,会比较麻烦。
DCMI接口支持两种同步方式:内嵌码同步和硬件(HSYNC和VSYNC)同步。我们使用硬件同步,硬件同步模式下将使用两个同步信号 (HSYNC/VSYNC)。根据摄像头模块/模式的不同,可能在水平/垂直同步期间内发送数据。由于系统会忽略HSYNC/VSYNC信号有效电平期间内接收的所有数据,HSYNC/VSYNC 信号相当于消隐信号。
为了正确地将图像传输到 DMA/RAM 缓冲区,数据传输将与VSYNC信号同步。选择硬件同步模式并启用捕获(DCMI_CR中的CAPTURE位置1)时,数据传输将与VSYNC信号的 无效电平同步(开始下一帧时)。之后传输便可以连续执行,由DMA将连续帧传输到多个连续的缓冲区或一个具有循环特性的缓冲区。为了允许DMA管理连续帧,每一帧结束时都将激活VSIF(垂直同步中断标志,即帧中断),我们可以利用这个帧中断来判断是否有一帧数据采集完成,方便处理数据。
DCMI接口的捕获模式支持:快照模式和连续采集模式。一般我们使用连续采集模式,通过DCMI_CR中的CM位设置。另外,DCMI接口还支持实现了4个字深度的 FIFO,配有一个简单的FIFO控制器,每次摄像头接口从AHB读取数据时读指针递增,每次摄像头接口向FIFO写入数据时写指针递增。因为没有溢出保护,如果数据传输率超过AHB接口能够承受的速率,FIFO中的数据就会被覆盖。如果同步信号出错,或者 FIFO 发生溢出,FIFO 将复位,DCMI 接口将等待新的数据帧开始。
关于DCMI接口的其它特性,我们这里就不再介绍了,请大家参考《STM32H5xx参考手册_V1(英文版).pdf》第30章相关内容。

41.1.2.2 DCMI寄存器
本实验,我们将OV5640默认配置为WXGA输出,也就是1280*800的分辨率,输出信号设置为:VSYNC高电平有效,HREF高电平有效,输出数据在PCLK的下降沿输出(即上升沿的时候,MCU才可以采集)。这样,STM32H562的DCMI接口就必须设置为:VSYNC低电平有效、HSYNC低电平有效和PIXCLK上升沿有效,这些设置都是通过DCMI_CR寄存器控制。
DCMI控制寄存器(DCMI_CR)
DCMI控制寄存器描述如图41.1.2.2.1所示:


第四十一章 摄像头实验9159.png
图41.1.2.2.1 DCMI_CR寄存器

ENABLE,该位用于设置是否使能DCMI,不过,在使能之前,必须将其它配置设置好。
EDM[1:0],设置扩展数据模式,选择00:接口每个像素时钟捕获8位数据。
FCRC[1:0],这两个位用于帧率控制,我们捕获所有帧,所以设置为00即可。
VSPOL,该位用于设置垂直同步极性,也就是VSYNC引脚上面,数据无效时的电平状态,根据前面说所,我们应该设置为0。
HSPOL,该位用于设置水平同步极性,也就是HSYNC引脚上面,数据无效时的电平状态,同样应该设置为0。
PCKPOL,该位用于设置像素时钟极性,我们用上升沿捕获,所以设置为1。
ESS,内嵌码同步选择,我们选择硬件同步,默认置0。
CM,该位用于设置捕获模式,我们用连续采集模式,所以设置为0即可。
CAPTURE,该位用于使能捕获,我们设置为1。该位使能后,将激活DMA,DCMI等待第一帧开始,然后生成DMA请求将收到的数据传输到目标存储器中。注意:该位必须在DCMI的其他配置(包括DMA)都设置好了之后,才设置!!
DCMI_CR寄存器的其它位,我们就不介绍了,另外DCMI的其它寄存器这里也不再介绍,请大家参考《STM32H5xx参考手册_V1(英文版).pdf》第30.5小节。关于DMA的寄存器,这里就不再赘述,请回顾前面的实验。


41.2 硬件设计

1. 例程功能
1、初始化摄像头后,可通过按下KEY0按键或KEY1按键进入RGB565或JPEG测试模式。
2、RGB565测试模式下,LCD显示DCMI捕获的摄像头画面,可通过按键修改画面对焦、特效或缩放。
3、JPEG测试模式下,USART2输出DCMI捕获的JPEG数据,可通过按键对画面对焦、特效或尺寸进行设置。
4、串口1输出DCMI接收摄像头数据帧率。
5、LED1闪烁,提示DCMI接收到一帧数据。

2. 硬件资源
1)LED灯
       LED0 - PB4
       LED1 - PB5
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)独立按键 :KEY0 - PA1、KEY1 - PA15、WK_UP - PA0
5)串口2 (PA2,外接串口线/用杜邦线连接到CH340上面)
6)DCMI接口(用于驱动OV5640摄像头模块)
7)定时器6(用于打印摄像头帧率等信息)
8)正点原子 OV5640摄像头模块,连接关系为:
       OV5640模块 -----------   STM32开发板
       OV_D0~D7  ------------   PC6/PC7/PC8/PC9/PC11/PD3/PB8/PB9
       OV_SCL    ------------   PB10
       OV_SDA    ------------   PB3
       OV_VSYNC  ------------  PB7
       OV_HREF   ------------   PA4
       OV_RESET  ------------   PC1
       OV_PCLK   ------------   PA6
       OV_PWDN  ------------   PC0

3. 原理图
开发板板载的摄像头模块接口与MCU的连接关系,如下图所示:


第四十一章 摄像头实验10613.png
图41.2.1摄像头端子原理图

这些GPIO口的线都在开发板上连接到P4端口,所以我们只需要将OV5640摄像头模块插上开发板的连接座子就好了(摄像头模块正面往外插)。

第四十一章 摄像头实验10700.png
图41.2.2 OV5640摄像头模块与开发板的连接座子

41.3 程序设计

41.3.1 DCMI的HAL库驱动
DCMI在HAL库中的驱动代码在stm32h5xx_hal_dcmi.c文件(及其头文件)中。

1. HAL_DCMI_Init函数
DCMI初始化函数,其声明如下:
HAL_StatusTypeDef HAL_DCMI_Init(DCMI_HandleTypeDef *hdcmi);
函数描述:
用于初始化DCMI。
函数形参:
形参1是DCMI_HandleTypeDef结构体类型指针变量,其定义如下:

  1. typedef struct
  2. {
  3.   DCMI_TypeDef                   *Instance;                   /* DCMI 外设寄存器基地址 */
  4.   DCMI_InitTypeDef              Init;                         /* DCMI 初始化结构体 */
  5.   HAL_LockTypeDef               Lock;                         /* 锁对象 */
  6.   __IO HAL_DCMI_StateTypeDef  State;                        /* DCMI 工作状态 */
  7.   __IO uint32_t                  XferCount;                   /* DMA 传输计数器 */
  8.   __IO uint32_t                  XferSize;                    /* DMA 传输数据的大小 */
  9.   uint32_t                        XferTransferNumber;        /* DMA 数据的个数 */
  10.   uint32_t                        pBuffPtr;                    /* DMA输出缓冲区地址 */
  11.   DMA_HandleTypeDef             *DMA_Handle;                /* DMA配置结构体指针 */
  12.   __IO uint32_t                  ErrorCode;                  /* DCMI 错误代码 */
  13. }DCMI_HandleTypeDef;
复制代码
下面重点介绍DCMI_InitTypeDef结构体,其定义如下:
  1. typedef struct {
  2. uint32_t SynchroMode;                      /* 同步方式选择硬件同步模式还是内嵌码模式 */
  3. uint32_t PCKPolarity;                      /* 设置像素时钟的有效边沿 */
  4. uint32_t VSPolarity;                       /* 设置垂直同步的有效电平 */
  5. uint32_t HSPolarity;                       /* 设置水平同步的有效边沿 */
  6. uint32_t CaptureRate;                      /* 设置图像的帧捕获率 */
  7. uint32_t ExtendedDataMode;                /* 设置数据线的宽度(扩展数据模式) */
  8. DCMI_CodesInitTypeDef SyncroCode;        /* 分隔符设置 */
  9. uint32_t JPEGMode;                          /* JPEG 模式选择 */
  10. uint32_t ByteSelectMode;                   /* 配置字节选项模式 */
  11. uint32_t ByteSelectStart;                  /* 字节选择开始 */
  12. uint32_t LineSelectMode;                   /* 行选择模式 */
  13. uint32_t LineSelectStart;                  /* 指定数据行是奇数还是偶数 */
  14. } DCMI_InitTypeDef;
复制代码
1) SynchroMode:用于设置 DCMI数据的同步模式,可以选择为硬件同步方式或内嵌码方式。
如果选择硬件同步值DCMI_SYNCHRO_HARDWARE,那么数据捕获由HSYNC/VSYNC信号同步,如果选择内嵌码同步方式值DCMI_SYNCHRO_EMBEDDED,那么数据捕获由数据流中嵌入的同步码同步。
2) PCKPolarity:用来设置像素时钟极性为上升沿有效还是下降沿有效。
3) VSPolarity:用于设置VSYNC的有效电平,当VSYNC信号线表示为有效电平时,表示新
的一帧数据传输完成,它可以被设置为高电平有效或低电平有效。
4) HSPolarity:用于设置HSYNC的有效电平,当 HSYNC 信号线表示为有效电平时,表示新的一行数据传输完成,它可以被设置为高电平有效或低电平有效。
5) CaptureRate:用于设置帧捕获率,可以设置为全采集、半采集或 1/4 采集。设置的值为DCMI_CR_ALL_FRAME(表示全帧捕获),设置为DCMI_CR_ALTERNATE_2_FRAME(表示2帧捕获一帧),设置为DCMI_CR_ALTERNATE_4_FRAME(表示4帧捕获一帧)。
6) ExtendedDataMode:用于设置扩展数据模式,可设置为8/10/12或者14位数据宽度。
7) SyncroCode:用于设置分隔码,包括:帧结束分隔码,行结束分隔码,行开始分隔码以及帧开始分隔码。
8) JPEGMode:用于设置JPEG格式使能或禁止。
9)ByteSelectMode用于设置字节选项模式,也就是接口对接收到的数据每隔多少个字节捕获一个字节,取值为:DCMI_BSM_ALL(捕获所有字节),DCMI_BSM_OTHER(每隔一个字节进行捕获),DCMI_BSM_ALTERNATE_4(每四个字节捕获一个字节)和DCMI_BSM_ALTERNATE_2(每四个字节捕获两个字节)。
10)ByteSelectStart用于设置奇数偶数字节选择开始,也就是接口从帧/行开始捕获第一个数据同时丢弃第二个字节(DCMI_OEBS_ODD)或者捕获第二个数据同时丢弃第一个字节(DCMI_OEBS_EVEN)。
11)LineSelectMode用于配置行选择模式,也就是选择接口捕获所有接受到的行(DCMI_LSM_ALL)还是每两行捕获一行(DCMI_LSM_ALTERNATE_2)。
12)LineSelectStart用于配置奇数偶数行选择开始。也就是接口在帧开始后捕获第一行丢弃第二行(DCMI_OELS_ODD)或者在帧开始后捕获第二行同时丢弃第一行(DCMI_OELS_EVEN)。
函数返回值:
HAL_StatusTypeDef枚举类型的值。


41.3.2 程序流程图

第四十一章 摄像头实验13640.png
图41.3.2.1 摄像头实验程序流程图

41.3.3 程序解析

1. DCMI驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DCMI驱动源码包括两个文件:dcmi.c和dcmi.h。
dcmi.h头文件里面,定义了DMA_NODE_NUM宏定义的值为2,用于定义DMA链表节点数量。
下面直接开始介绍dcmi.c文件,首先是DCMI初始化函数,其定义如下:

  1. /**
  2. * @brief       DCMI 初始化
  3. *   [url=home.php?mod=space&uid=60778]@note[/url]      IO对应关系如下:
  4. *               摄像头模块 ------------  STM32开发板
  5. *               OV_D0~D7  ------------  PC6/PC7/PC8/PC9/PC11/PD3/PB8/PB9
  6. *               OV_SCL    ------------  PB10
  7. *               OV_SDA    ------------  PB3
  8. *               OV_VSYNC  ------------  PB7
  9. *               OV_HREF   ------------  PA4
  10. *               OV_RESET  ------------  PC1
  11. *               OV_PCLK   ------------  PA6
  12. *               OV_PWDN   ------------  PC0
  13. *               本函数仅初始化OV_D0~D7/OV_VSYNC/OV_HREF/OV_PCLK等信号(11个).
  14. * [url=home.php?mod=space&uid=271674]@param[/url]       无
  15. * @retval      无
  16. */
  17. void dcmi_init(void)
  18. {
  19.     /* 初始化DCMI */
  20.     g_dcmi_handle.Instance = DCMI;
  21.     g_dcmi_handle.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE;
  22.     g_dcmi_handle.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING;
  23.     g_dcmi_handle.Init.VSPolarity = DCMI_VSPOLARITY_LOW;
  24.     g_dcmi_handle.Init.HSPolarity = DCMI_HSPOLARITY_LOW;
  25.     g_dcmi_handle.Init.CaptureRate = DCMI_CR_ALL_FRAME;
  26.     g_dcmi_handle.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B;
  27.     g_dcmi_handle.Init.JPEGMode = DCMI_JPEG_DISABLE;
  28.     g_dcmi_handle.Init.ByteSelectMode = DCMI_BSM_ALL;
  29.     g_dcmi_handle.Init.ByteSelectStart = DCMI_OEBS_ODD;
  30.     g_dcmi_handle.Init.LineSelectMode = DCMI_LSM_ALL;
  31.     g_dcmi_handle.Init.LineSelectStart = DCMI_OELS_ODD;
  32.     HAL_DCMI_Init(&g_dcmi_handle);

  33.     /* 使能DCMI及其帧捕获完成中断 */
  34. __HAL_DCMI_DISABLE_IT(&g_dcmi_handle, DCMI_IT_FRAME | DCMI_IT_OVR
  35. | DCMI_IT_ERR | DCMI_IT_VSYNC | DCMI_IT_LINE);
  36.     __HAL_DCMI_ENABLE_IT(&g_dcmi_handle, DCMI_IT_FRAME);
  37.     __HAL_DCMI_ENABLE(&g_dcmi_handle);
  38. }
复制代码
该函数主要对DCMI_HandleTypeDef结构体成员赋值并初始化,最后关闭所有中断,只开启帧中断,使能DCMI。而DCMI接口的GPIO口的初始化是在HAL_DCMI_MspInit回调函数中完成,其定义如下:
  1. /**
  2. * @brief   HAL库DCMI初始化MSP回调函数
  3. * @param   hdcmi: DCMI句柄指针
  4. * @retval  无
  5. */
  6. void HAL_DCMI_MspInit(DCMI_HandleTypeDef *hdcmi)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct = {0};
  9.    
  10.     if (hdcmi->Instance == DCMI)
  11.     {
  12.         /* 使能时钟 */
  13.         __HAL_RCC_DCMI_CLK_ENABLE();
  14.         __HAL_RCC_GPIOA_CLK_ENABLE();
  15.         __HAL_RCC_GPIOC_CLK_ENABLE();
  16.         __HAL_RCC_GPIOD_CLK_ENABLE();
  17.         __HAL_RCC_GPIOB_CLK_ENABLE();
  18.         __HAL_RCC_GPDMA1_CLK_ENABLE();
  19.         
  20.         /* 初始化引脚 */
  21.         gpio_init_struct.Pin = GPIO_PIN_4 | GPIO_PIN_6;
  22.         gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  23.         gpio_init_struct.Pull = GPIO_PULLUP;
  24.         gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  25.         gpio_init_struct.Alternate = GPIO_AF13_DCMI;
  26.         HAL_GPIO_Init(GPIOA, &gpio_init_struct);
  27.         
  28.         gpio_init_struct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
  29.         HAL_GPIO_Init(GPIOB, &gpio_init_struct);
  30.         
  31.         gpio_init_struct.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8
  32. | GPIO_PIN_9 | GPIO_PIN_11;
  33.         HAL_GPIO_Init(GPIOC, &gpio_init_struct);
  34.         
  35.         gpio_init_struct.Pin = GPIO_PIN_3;
  36.         HAL_GPIO_Init(GPIOD, &gpio_init_struct);
  37.         
  38.         /* 配置中断 */
  39.         HAL_NVIC_SetPriority(DCMI_PSSI_IRQn, 0, 0);
  40.         HAL_NVIC_EnableIRQ(DCMI_PSSI_IRQn);
  41.         HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 0, 0);
  42.         HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
  43.     }
  44. }
复制代码
接下来介绍DCMI DMA配置初始化函数,其定义如下:
  1. /**
  2. * @brief       DCMI DMA配置
  3. * @param       *memaddr: 存储器地址      指向存储器地址的指针
  4. * @param       memsize : 存储器长度      0~65535,单位是:字
  5. * @param       memnum :  存储器数据段个数  0~256,用一个链表节点控制一个存储器数据段的
  6. *                           传输,可以实现多缓冲区功能
  7. * @param       memblen : 存储器位宽      DMA_DEST_DATAWIDTH_BYTE,8位;
  8. *                   DMA_DEST_DATAWIDTH_HALFWORD,16位;DMA_DEST_DATAWIDTH_WORD,32位
  9. * @param       meminc : 存储器增长方式 DMA_DINC_FIXED,不增长;
  10. *                          DMA_DINC_INCREMENTED,增长
  11. * @retval      无
  12. */
  13. void dcmi_dma_init(uint32_t *memaddr, uint16_t memsize, uint8_t memnum,
  14. uint32_t memblen, uint32_t meminc)
  15. {
  16.     DMA_NodeConfTypeDef dma_node_conf_struct = {0};
  17.     uint8_t i;
  18.    
  19.     if (memnum > (sizeof(g_dma_node_struct) / sizeof(g_dma_node_struct[0])))
  20.     {
  21.         node_used = sizeof(g_dma_node_struct) / sizeof(g_dma_node_struct[0]);
  22.     }
  23.     else
  24.     {
  25.         node_used = memnum;
  26.     }
  27.    
  28.     /* 复位链表 */
  29.     HAL_DMAEx_List_ResetQ(&g_dma_qlist_struct);
  30.    
  31.     /* 构建链表节点 */
  32.     dma_node_conf_struct.NodeType = DMA_GPDMA_LINEAR_NODE;
  33.     dma_node_conf_struct.Init.Request = GPDMA1_REQUEST_DCMI;
  34.     dma_node_conf_struct.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
  35.     dma_node_conf_struct.Init.Direction = DMA_PERIPH_TO_MEMORY;
  36.     dma_node_conf_struct.Init.SrcInc = DMA_SINC_FIXED;
  37.     dma_node_conf_struct.Init.DestInc = meminc;
  38.     dma_node_conf_struct.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
  39.     dma_node_conf_struct.Init.DestDataWidth = memblen;
  40.     dma_node_conf_struct.Init.SrcBurstLength = 1;
  41.     dma_node_conf_struct.Init.DestBurstLength = 1;
  42. dma_node_conf_struct.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0
  43. | DMA_DEST_ALLOCATED_PORT1;
  44. dma_node_conf_struct.Init.TransferEventMode =
  45. DMA_TCEM_EACH_LL_ITEM_TRANSFER;
  46.     dma_node_conf_struct.Init.Mode = DMA_NORMAL;
  47.     dma_node_conf_struct.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
  48.     dma_node_conf_struct.DataHandlingConfig.DataAlignment = DMA_DATA_UNPACK;
  49. dma_node_conf_struct.TriggerConfig.TriggerPolarity =
  50. DMA_TRIG_POLARITY_MASKED;
  51.     dma_node_conf_struct.SrcAddress = (uint32_t)&g_dcmi_handle.Instance->DR;
  52.     dma_node_conf_struct.DataSize = memsize * sizeof(uint32_t);
  53.     for (i = 0; i < node_used; i++)
  54.     {
  55.         dma_node_conf_struct.DstAddress = memaddr[i];
  56.         HAL_DMAEx_List_BuildNode(&dma_node_conf_struct, &g_dma_node_struct[i]);
  57.         if (i == 0)
  58.         {
  59.             HAL_DMAEx_List_InsertNode(&g_dma_qlist_struct, NULL,
  60. &g_dma_node_struct[i]);
  61.         }
  62.         else
  63.         {
  64.             HAL_DMAEx_List_InsertNode(&g_dma_qlist_struct,
  65. &g_dma_node_struct[i - 1], &g_dma_node_struct[i]);
  66.         }
  67.     }
  68.    
  69.     /* 循环链表 */
  70.     HAL_DMAEx_List_SetCircularMode(&g_dma_qlist_struct);
  71.    
  72.     /* 初始化DMA通道为链表模式 */
  73.     g_dcmi_dma_handle.Instance = GPDMA1_Channel0;
  74.     g_dcmi_dma_handle.InitLinkedList.Priority = DMA_HIGH_PRIORITY;
  75.     g_dcmi_dma_handle.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
  76. g_dcmi_dma_handle.InitLinkedList.LinkAllocatedPort =
  77. DMA_LINK_ALLOCATED_PORT1;
  78. g_dcmi_dma_handle.InitLinkedList.TransferEventMode =
  79. DMA_TCEM_EACH_LL_ITEM_TRANSFER;
  80.     g_dcmi_dma_handle.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
  81.     HAL_DMAEx_List_DeInit(&g_dcmi_dma_handle);
  82.     HAL_DMAEx_List_Init(&g_dcmi_dma_handle);
  83.    
  84.     /* 链接链表到DMA通道 */
  85.     HAL_DMAEx_List_LinkQ(&g_dcmi_dma_handle, &g_dma_qlist_struct);
  86.    
  87.     /* 链接到外设 */
  88.     __HAL_LINKDMA(&g_dcmi_handle, DMA_Handle, g_dcmi_dma_handle);
  89.    
  90.     /* 配置DMA通道的安全和特权属性 */
  91.     HAL_DMA_ConfigChannelAttributes(&g_dcmi_dma_handle, DMA_CHANNEL_NPRIV);
  92.    
  93.     /* 注册DMA传输完成回调函数 */
  94. HAL_DMA_RegisterCallback(&g_dcmi_dma_handle, HAL_DMA_XFER_CPLT_CB_ID,
  95. dcmi_dma_transfer_complete_cb);
  96.    
  97.     HAL_DMAEx_List_Start_IT(&g_dcmi_dma_handle);
  98. }
复制代码
该函数用于配置DCMI的DMA传输,其源地址固定为:DCMI->DR,而目的地址可变(LCD或者SRAM)。DMA被配置为循环模式,一旦开启,DMA将不停的循环传输数据。
下面介绍的是DCMI启动传输和关闭传输函数,它们的定义分别如下:

  1. /**
  2. * @brief   启动DCMI传输
  3. * @param   无
  4. * @retval  无
  5. */
  6. void dcmi_start(void)
  7. {
  8.     g_dcmi_handle.Instance->CR |= DCMI_CR_CAPTURE;
  9. }

  10. /**
  11. * @brief   停止DCMI传输
  12. * @param   无
  13. * @retval  无
  14. */
  15. void dcmi_stop(void)
  16. {
  17.     g_dcmi_handle.Instance->CR &= ~(DCMI_CR_CAPTURE);
  18.     while ((g_dcmi_handle.Instance->CR & DCMI_CR_CAPTURE) == DCMI_CR_CAPTURE);
  19. }
复制代码
下面介绍的是DCMI中断服务函数(及相关回调函数)、GPDMA中断服务函数和DMA传输完成回调函数,它们的定义分别如下:
  1. /**
  2. * @brief   DCMI中断服务函数
  3. * @param   无
  4. * @retval  无
  5. */
  6. void DCMI_PSSI_IRQHandler(void)
  7. {
  8.     HAL_DCMI_IRQHandler(&g_dcmi_handle);
  9. }

  10. /**
  11. * @brief   HAL库DCMI帧捕获完成回调函数
  12. * @param   hdcmi: DCMI句柄指针
  13. * @retval  无
  14. */
  15. void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
  16. {
  17.     uint32_t node_addr;
  18.     uint8_t node;
  19.     uint8_t i;
  20.    
  21.     if (hdcmi->Instance == DCMI)
  22.     {
  23.         node_addr = (hdcmi->DMA_Handle->Instance->CLBAR & DMA_CLBAR_LBA)
  24. | (hdcmi->DMA_Handle->Instance->CLLR & DMA_CLLR_LA);
  25.         for (i=0; i<node_used; i++)
  26.         {
  27.             if (node_addr == (uint32_t)&g_dma_node_struct[i])
  28.             {
  29.                 node = (i != 0) ? (i - 1) : (node_used - 1);
  30.                 break;
  31.             }
  32.         }
  33.         
  34.         dcmi_frame_transfer_complete_user_cb(hdcmi, node);
  35.         
  36.         __HAL_DCMI_ENABLE_IT(hdcmi, DCMI_IT_FRAME);
  37.     }
  38. }

  39. /**
  40. * @brief   DCMI帧捕获完成回调函数
  41. * @param   hdcmi: DCMI句柄指针
  42. * @param   node: 正在传输的DMA链表节点编号
  43. * @retval  无
  44. */
  45. __weak void dcmi_frame_transfer_complete_user_cb(DCMI_HandleTypeDef *hdcmi, uint8_t node)
  46. {
  47.     UNUSED(hdcmi);
  48.     UNUSED(node);
  49. }

  50. /**
  51. * @brief   GPDMA1通道0中断服务函数
  52. * @param   无
  53. * @retval  无
  54. */
  55. void GPDMA1_Channel0_IRQHandler(void)
  56. {
  57.     HAL_DMA_IRQHandler(&g_dcmi_dma_handle);
  58. }

  59. /**
  60. * @brief   DMA传输完成回调函数
  61. * @param   dma_handle: DMA句柄指针
  62. * @retval  无
  63. */
  64. static void dcmi_dma_transfer_complete_cb(DMA_HandleTypeDef *const dma_handle)
  65. {
  66.     uint32_t node_addr;
  67.     uint8_t node;
  68.     uint8_t i;
  69.    
  70. node_addr = (dma_handle->Instance->CLBAR & DMA_CLBAR_LBA)
  71. | (dma_handle->Instance->CLLR & DMA_CLLR_LA);
  72.     for (i=0; i<node_used; i++)
  73.     {
  74.         if (node_addr == (uint32_t)&g_dma_node_struct[i])
  75.         {
  76.             node = (i != 0) ? (i - 1) : (node_used - 1);
  77.             break;
  78.         }
  79.     }
  80.    
  81.     dcmi_dma_transfer_complete_user_cb(node);
  82. }

  83. /**
  84. * @brief   DMA传输完成用户回调函数
  85. * @param   node: 传输完成的DMA链表节点编号
  86. * @retval  无
  87. */
  88. __weak void dcmi_dma_transfer_complete_user_cb(uint8_t node)
  89. {
  90.     UNUSED(node);
  91. }
复制代码
其中:DCMI_PSSI_IRQHandler函数(及相关回调函数),用于处理帧捕获完成中断,可以实现帧率统计(需要定时器支持)和JPEG数据处理等。实际上当捕获到一帧数据后,调用的是HAL库回调函数HAL_DCMI_FrameEventCallback进行处理,然后在该函数里面又调用了我们自己定义的回调函数dcmi_frame_transfer_complete_user_cb。
当发生DMA传输完成中断后,会进入GPDMA1_Channel0_IRQHandler函数,然后调用dcmi_dma_transfer_complete_cb() DMA传输完成回调函数。该函数里面又调用了我们自己定义的回调函数dcmi_dma_transfer_complete_user_cb,用于更新采集到的JPEG数据量。
DCMI驱动代码就介绍到这里。


2. OV5640驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。OV5640驱动源码包括六个文件:ov5640.c、ov5640.h、ov5640af.h、ov5640cfg.h、sccb.c和sccb.h。
其中sccb.c和sccb.h是SCCB通信接口的驱动代码。OV5640的寄存器通过SCCB时序访问并设置,SCCB时序和IIC时序十分类似,这里我们也是用软件模拟SCCB时序。
下面,首先介绍sccb.h头文件中SCCB的IO口宏定义,其定义情况如下:

  1. /* 引脚 定义 */
  2. #define SCCB_SCL_GPIO_PORT                  GPIOB
  3. #define SCCB_SCL_GPIO_PIN                   GPIO_PIN_10
  4. #define SCCB_SCL_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOB_CLK_ENABLE(); } while(0)

  5. #define SCCB_SDA_GPIO_PORT                 GPIOB
  6. #define SCCB_SDA_GPIO_PIN                       GPIO_PIN_3
  7. #define SCCB_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); } while(0)

  8. /* IO操作函数 */
  9. #define SCCB_SCL(x)    do{ x ? \
  10.    HAL_GPIO_WritePin(SCCB_SCL_GPIO_PORT, SCCB_SCL_GPIO_PIN, GPIO_PIN_SET) : \
  11.    HAL_GPIO_WritePin(SCCB_SCL_GPIO_PORT, SCCB_SCL_GPIO_PIN, GPIO_PIN_RESET); \
  12.                                    }while(0)       /* SCL */

  13. #define SCCB_SDA(x)   do{ x ? \
  14.    HAL_GPIO_WritePin(SCCB_SDA_GPIO_PORT, SCCB_SDA_GPIO_PIN, GPIO_PIN_SET) : \
  15.    HAL_GPIO_WritePin(SCCB_SDA_GPIO_PORT, SCCB_SDA_GPIO_PIN, GPIO_PIN_RESET); \
  16.                             }while(0)       /* SDA */

  17. #define SCCB_READ_SDA    HAL_GPIO_ReadPin(SCCB_SDA_GPIO_PORT, SCCB_SDA_GPIO_PIN)
复制代码
SCCB时序有两根信号线(SCCB_SCL和SCCB_SDA),所以这里定义了两个IO口(PB10和PB3)来控制。IO操作函数有三个,包括SCCB_SCL用于控制时钟,SCCB_SDA是写IO口输出的值为逻辑1或者逻辑0,SCCB_READ_SDA是读取IO口的值,逻辑1或者逻辑0。
接下来介绍sccb.c文件的代码,首先是SCCB接口初始化函数,该函数主要就是初始化PB10和PB3两个IO口,其定义如下:

  1. /**
  2. * @brief   初始化SCCB
  3. * @param   无
  4. * @retval  无
  5. */
  6. void sccb_init(void)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct = {0};
  9.    
  10.     /* 时钟使能 */
  11.     SCCB_SCL_GPIO_CLK_ENABLE();
  12.     SCCB_SDA_GPIO_CLK_ENABLE();
  13.    
  14.     /* 配置SCL引脚 */
  15.     gpio_init_struct.Pin = SCCB_SCL_GPIO_PIN;
  16.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
  17.     gpio_init_struct.Pull = GPIO_PULLUP;
  18.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  19.     HAL_GPIO_Init(SCCB_SCL_GPIO_PORT, &gpio_init_struct);
  20.    
  21.     /* 配置SDA引脚 */
  22.     gpio_init_struct.Pin = SCCB_SDA_GPIO_PIN;
  23.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;
  24.     gpio_init_struct.Pull = GPIO_PULLUP;
  25.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  26.     HAL_GPIO_Init(SCCB_SDA_GPIO_PORT, &gpio_init_struct);
  27.    
  28.     sccb_stop();
  29. }
复制代码
这里的PB3(SDA)引脚设置为带上拉的开漏输出,这样设置的好处是:SDA引脚不用再设置IO口方向了,因为开漏输出模式下,STM32的IO口可以读取外部信号的高低电平,这个在前面介绍GPIO外设的时候已经讲解过。初始化IO口后,调用sccb_stop函数停止总线上的所有设备,防止误操作。
sccb.c文件的其它函数,这里就不再介绍了,主要都是IO口模拟SCCB时序的相关函数,详细请看源码。
ov5640.c、ov5640.h、ov5640af.h和ov5640cfg.h这四个文件主要就是用于OV5640摄像头的初始化,当然也要使用到SCCB的驱动代码。
ov5640cfg.h文件存放的是OV5640初始化数组,总共有3个数组。我们大概了解下数组结构,每个数组条目的第一个字节为寄存器号(也就是寄存器地址),第二个字节为要设置的值,比如{0x4300, 0x30},就表示在0x4300地址,写入0X30这个值。
三个数组中,ov5640_init_reg_tbl数组,用于初始化OV5640,该数组必须最先进行配置;ov5640_rgb565_reg_tbl数组,用于设置OV5640的输出格式为RGB565,分辨率为1280*800,帧率为15帧,在RGB模式下使用;OV5640_jpeg_reg_tbl数组用于设置OV5640的输出格式为JPEG,分辨率为2592*1944,帧率为7.5帧,在JPEG模式下使用。由于数组的篇幅过长,就不列出来了,请对照源码理解。
ov5640af.h文件存放的是OV5640自动对焦配置数组,前面介绍过。
下面开始介绍ov5640.h文件,主要是OV5640的PWDN和RESET引脚定义和控制函数,以及OV5640的ID、访问地址和相关寄存器定义,其定义如下:

  1. /* PWDN和RESET 引脚 定义 */
  2. #define OV_PWDN_GPIO_PORT                   GPIOC
  3. #define OV_PWDN_GPIO_PIN                  GPIO_PIN_1
  4. #define OV_PWDN_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   

  5. #define OV_RESET_GPIO_PORT                GPIOC
  6. #define OV_RESET_GPIO_PIN                  GPIO_PIN_0
  7. #define OV_RESET_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
  8.    
  9. /* IO控制函数 */
  10. #define OV5640_PWDN(x)    do{ x ? \
  11.             HAL_GPIO_WritePin(OV_PWDN_GPIO_PORT, OV_PWDN_GPIO_PIN, GPIO_PIN_SET) : \
  12.           HAL_GPIO_WritePin(OV_PWDN_GPIO_PORT, OV_PWDN_GPIO_PIN, GPIO_PIN_RESET); \
  13.                                           }while(0)             /* POWER DOWN控制信号 */

  14. #define OV5640_RST(x)     do{ x ? \
  15.            HAL_GPIO_WritePin(OV_RESET_GPIO_PORT, OV_RESET_GPIO_PIN, GPIO_PIN_SET) : \
  16.      HAL_GPIO_WritePin(OV_RESET_GPIO_PORT, OV_RESET_GPIO_PIN, GPIO_PIN_RESET); \
  17.                                           }while(0)             /* 复位控制信号 */

  18. /* OV5640的ID和访问地址 */
  19. #define OV5640_ID           0X5640              /* OV5640的芯片ID */
  20. #define OV5640_ADDR         0X3C                /* OV5640的IIC地址 */
复制代码
下面开始介绍ov5640.c文件,首先是OV5640初始化函数,其定义如下:
  1. /**
  2. * @brief   初始化OV5640
  3. * @param   无
  4. * @retval  初始化结果
  5. * @arg     0: 成功
  6. * @arg     1: 失败
  7. */
  8. uint8_t ov5640_init(void)
  9. {
  10.     GPIO_InitTypeDef gpio_init_struct = {0};
  11.     uint16_t id;
  12.     uint16_t i;
  13.    
  14.     /* 使能时钟 */
  15.     OV_RESET_GPIO_CLK_ENABLE();
  16.    
  17.     /* 初始化RESET引脚 */
  18.     gpio_init_struct.Pin = OV_RESET_GPIO_PIN;
  19.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
  20.     gpio_init_struct.Pull = GPIO_PULLUP;
  21.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  22.     HAL_GPIO_Init(OV_RESET_GPIO_PORT, &gpio_init_struct);
  23.    
  24.     /* 硬件复位 */
  25.     OV5640_RST(0);
  26.     delay_ms(20);
  27.     OV5640_PWDN(0);
  28.     delay_ms(5);
  29.     OV5640_RST(1);
  30.     delay_ms(20);
  31.    
  32.     /* 初始化SCCB */
  33.     sccb_init();
  34.    
  35.     /* 校验ID */
  36.     id = ov5640_read_reg(0x300A) << 8;
  37.     id |= ov5640_read_reg(0x300B);
  38.     if (id != OV5640_ID)
  39.     {
  40.         return 1;
  41.     }
  42.    
  43.     /* 软件复位 */
  44.     ov5640_write_reg(0x3103, 0x11);
  45.     ov5640_write_reg(0x3008, 0x82);
  46.     delay_ms(10);
  47.    
  48.     /* 初始化OV5640寄存器 */
  49.     for (i=0; i<(sizeof(ov5640_init_reg_tbl) / sizeof(ov5640_init_reg_tbl[0])); i++)
  50.     {
  51.         ov5640_write_reg(ov5640_init_reg_tbl[i][0], ov5640_init_reg_tbl[i][1]);
  52.     }
  53.    
  54.     /* 检查闪光灯 */
  55.     ov5640_flash_ctrl(1);
  56.     delay_ms(50);
  57.     ov5640_flash_ctrl(0);
  58.    
  59.     return 0;
  60. }
复制代码
该函数除了初始化OV5640相关的IO口,最主要的是完成OV5640的寄存器序列初始化。OV5640的寄存器特多(百几十个),配置特麻烦,幸好厂家有提供参考配置序列,就存放在上面介绍的ov5640_ init_reg_tbl这个数组里面。
另外,在ov5640.c里面,还有几个函数比较重要,这里不贴代码了,只介绍功能:
ov5640_outsize_set函数,用于设置图像输出大小;
ov5640_image_window_set函数,用于设置图像开窗大小(ISP大小);
ov5640_focus_init函数,用于初始化自动对焦功能;
ov5640_focus_single函数,用于实现一次自动对焦;
ov5640_focus_constant函数,用于开启持续自动对焦功能;
其中,ov5640_outsize_set和ov5640_image_window_set函数就是前面在41.1.1节所介绍的3个窗口的设置,它们共同决定了图像的输出。
其它的函数就不列出来了,请大家查看源码。


3. TIMER驱动代码
TIMER驱动代码我们主要就是用到定时器6,在定时器6更新中断回调函数中打印帧率,更新中断回调函数定义如下:

  1. /**
  2. * @brief       定时器更新中断回调函数
  3. * @param       htim:定时器句柄指针
  4. * @retval      无
  5. */
  6. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  7. {
  8.     if (htim->Instance == BTIM_TIMX_INT)
  9.     {
  10.         printf("Frame: %d\r\n", g_ov_frame);
  11.         g_ov_frame = 0;
  12.     }
  13. }
复制代码
g_ov_frame变量在main.c中被定义,用于存放帧率。TIMER的其它驱动代码在介绍定时器的时候已经介绍过,请看源码。

4. USART2驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。USART2驱动源码包括两个文件:usart2.c和usart2.h。
usart2.h头文件对usart2的IO口做了宏定义,具体如下:

  1. /* USART2相关定义 */
  2. #define USART2_UX                                   USART2
  3. #define USART2_UX_CLK_ENABLE()   do { __HAL_RCC_USART2_CLK_ENABLE(); } while (0)
  4. #define USART2_UX_TX_GPIO_PORT                 GPIOA
  5. #define USART2_UX_TX_GPIO_PIN                   GPIO_PIN_2
  6. #define USART2_UX_TX_GPIO_AF                    GPIO_AF7_USART2
  7. #define USART2_UX_TX_GPIO_CLK_ENABLE()  
  8. do { __HAL_RCC_GPIOA_CLK_ENABLE(); } while (0)
复制代码
PA2复用为串口2的发送引脚。
下面介绍usart2.c文件的串口2初始化函数,其定义如下:

  1. /**
  2. * @brief   初始化USART2
  3. * @param   baudrate: 通信波特率
  4. * @retval  无
  5. */
  6. void usart2_init(uint32_t baudrate)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct = {0};
  9.    
  10.     /* 使能时钟 */
  11.     USART2_UX_CLK_ENABLE();
  12.     USART2_UX_TX_GPIO_CLK_ENABLE();
  13.    
  14.     /* 初始化TX引脚 */
  15.     gpio_init_struct.Pin = USART2_UX_TX_GPIO_PIN;
  16.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  17.     gpio_init_struct.Pull = GPIO_PULLUP;
  18.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  19.     gpio_init_struct.Alternate = USART2_UX_TX_GPIO_AF;
  20.     HAL_GPIO_Init(USART2_UX_TX_GPIO_PORT, &gpio_init_struct);

  21.     /* 初始化USART2 */
  22.     g_usart2_handle.Instance = USART2_UX;
  23.     g_usart2_handle.Init.BaudRate = baudrate;
  24.     g_usart2_handle.Init.WordLength = UART_WORDLENGTH_8B;
  25.     g_usart2_handle.Init.StopBits = UART_STOPBITS_1;
  26.     g_usart2_handle.Init.Parity = UART_PARITY_NONE;
  27.     g_usart2_handle.Init.Mode = UART_MODE_TX;
  28.     g_usart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  29.     g_usart2_handle.Init.OverSampling = UART_OVERSAMPLING_16;
  30.     g_usart2_handle.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  31.     g_usart2_handle.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  32.     HAL_UART_Init(&g_usart2_handle);
  33. }
复制代码
该部分代码比较简单,基本usart_init函数是一样的。

5. main.c代码
main.c前面定义了一些变量和数组,具体如下:

  1. static uint8_t g_ov_mode; /* OV5640输出数据格式
  2.                                      * 0: RGB565格式
  3.                                      * 1: JPEG格式
  4.                                      */
  5. uint8_t g_ov_frame = 0;    /* 帧数 */

  6. /* JPEG相关数据缓存定义 */
  7. /* JPEG数据接收缓冲区区域大小(最大65535字节),单位:字 */
  8. #define JPEG_BUF_REGION_SIZE    (16306)
  9. #define JPEG_BUF_REGION_NUM     (DMA_NODE_NUM)     /* JPEG数据接收缓冲区区域数量 */
  10. static uint32_t g_jpeg_data_buf[JPEG_BUF_REGION_NUM][JPEG_BUF_REGION_SIZE]
  11. __attribute__((aligned(4))); /* JPEG数据缓冲区 */
  12. static uint32_t g_jpeg_data_recv_len = 0;      /* JPEG数据缓冲区中有效数据的长度 */

  13. /* JPEG数据状态
  14. * 0: 未采集完成
  15. * 1: 采集完成
  16. */
  17. static uint8_t g_jpeg_data_sta = 0;   

  18. /* OV5640特效名称定义 */
  19. static const char *effects_tbl[] = {"Normal", "Cool", "Warm", "B&W",
  20. "Yellowish ", "Inverse", "Greenish"};

  21. /* JPEG图片尺寸名称定义 */
  22. static const char *jpeg_size_tbl[] = {"QQVGA", "QVGA", "VGA", "SVGA", "XGA",
  23. "WXGA", "WXGA+", "SXGA", "UXGA", "1080P", "QXGA", "500W"};

  24. /* JPEG图片尺寸参数定义 */
  25. static const uint16_t jpeg_size_param_tbl[][2] = {
  26.     { 160,  120},   /* QQVGA */
  27.     { 320,  240},   /* QVGA */
  28.     { 640,  480},   /* VGA */
  29.     { 800,  600},   /* SVGA */
  30.     {1024,  768},   /* XGA */
  31.     {1280,  800},   /* WXGA */
  32.     {1440,  900},   /* WXGA+ */
  33.     {1280, 1024},   /* SXGA */
  34.     {1600, 1200},   /* UXGA */
  35.     {1920, 1080},   /* 1080P */
  36.     {2048, 1536},   /* QXGA */
  37.     {2592, 1944}    /* 500W */
  38. };
复制代码
在main.c里面,总共有5个函数,我们接下来分别介绍。首先是DCMI帧捕获完成回调函数,其定义如下:
  1. /**
  2. * @brief   DCMI帧捕获完成回调函数
  3. * @param   hdcmi: DCMI句柄指针
  4. * @param   node: 正在传输的DMA链表节点编号
  5. * @retval  无
  6. */
  7. void dcmi_frame_transfer_complete_user_cb(DCMI_HandleTypeDef *hdcmi,
  8. uint8_t node)
  9. {
  10.     UNUSED(node);
  11.    
  12.     LED1_TOGGLE();
  13.     g_ov_frame++;
  14.     if (g_ov_mode != 0)
  15.     {
  16.         /* 停止DCMI捕获 */
  17.         dcmi_stop();
  18.         
  19.         /* 更新采集到的JPEG数据量 */
  20.         g_jpeg_data_recv_len += (JPEG_BUF_REGION_SIZE –
  21. (__HAL_DMA_GET_COUNTER(hdcmi->DMA_Handle) / sizeof(uint32_t)));
  22.         
  23.         /* 标记JPEG数据采集完成 */
  24.         g_jpeg_data_sta = 1;
  25.     }
  26.     else
  27.     {
  28.         /* 更新LCD显示位置 */
  29.         lcd_set_cursor(0, 0);
  30.         lcd_write_ram_prepare();
  31.     }
  32. }
复制代码
该函数用于DCMI帧捕获完成相关代码,包括:LED1翻转;帧数变量g_ov_frame++;如果是JPEG格式,停止DCMI捕获、更新采集到的JPEG数据量、标记JPEG数据采集完成;如果是RGB565格式,更新LCD显示位置。
接下来介绍的是DMA传输完成用户回调函数,其定义如下:

  1. /**
  2. * @brief   DMA传输完成用户回调函数
  3. * @param   node: 传输完成的DMA链表节点编号
  4. * @retval  无
  5. */
  6. void dcmi_dma_transfer_complete_user_cb(uint8_t node)
  7. {
  8.     if (g_ov_mode != 0)
  9.     {
  10.         /* 更新采集到的JPEG数据量 */
  11.         g_jpeg_data_recv_len += JPEG_BUF_REGION_SIZE;
  12.     }
  13. }
复制代码
该函数用于更新采集到的JPEG数据量。
接下来介绍的是JPEG测试函数,其定义如下:

  1. /**
  2. * @brief   JPEG测试
  3. * @param   无
  4. * @retval  无
  5. */
  6. void jpeg_test(void)
  7. {
  8.     char msgbuf[15];
  9.     uint8_t effect = 0;
  10.     uint8_t size = 0;
  11.     uint8_t *pbuf;
  12.     uint32_t i;
  13.     uint32_t jpeg_start_index;
  14.     uint32_t jpeg_data_len;
  15.     uint32_t memaddr[JPEG_BUF_REGION_NUM];
  16.     uint8_t key;

  17.     lcd_clear(WHITE);
  18.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  19.     lcd_show_string(30, 70, 200, 16, 16, "OV5640 JPEG Mode", RED);
  20.     lcd_show_string(30, 90, 200, 16, 16, "KEY0: Auto Focus", RED);
  21.     lcd_show_string(30, 110, 200, 16, 16, "KEY1: Effects", RED);
  22.     lcd_show_string(30, 130, 200, 16, 16, "KEY_UP: Size", RED);
  23.     sprintf(msgbuf, "Effect: %s", effects_tbl[effect]);
  24.     lcd_show_string(30, 150, 200, 16, 16, msgbuf, RED);
  25.     sprintf(msgbuf, "JPEG Size: %s", jpeg_size_tbl[size]);
  26.     lcd_show_string(30, 170, 200, 16, 16, msgbuf, RED);

  27.     /* 配置OV5640 */
  28.     ov5640_rgb565_mode();
  29.     ov5640_focus_init();
  30.     ov5640_jpeg_mode();
  31.     ov5640_light_mode(0);
  32.     ov5640_color_saturation(3);
  33.     ov5640_brightness(4);
  34.     ov5640_contrast(3);
  35.     ov5640_sharpness(33);
  36.     ov5640_focus_constant();
  37.     ov5640_special_effects(effect);
  38. ov5640_outsize_set(4, 0, jpeg_size_param_tbl[size][0],
  39. jpeg_size_param_tbl[size][1]);
  40.    
  41.     lcd_set_cursor(0, 0);
  42.     lcd_write_ram_prepare();
  43.    
  44.     /* 配置DCMI */
  45.     dcmi_init();
  46.     for (i=0; i<(sizeof(memaddr) / sizeof(memaddr[0])); i++)
  47.     {
  48.         memaddr[i] = (uint32_t)g_jpeg_data_buf[i];
  49.     }
  50. dcmi_dma_init(memaddr, JPEG_BUF_REGION_SIZE, sizeof(memaddr)
  51. / sizeof(memaddr[0]), DMA_DEST_DATAWIDTH_WORD, DMA_DINC_INCREMENTED);
  52.     dcmi_start();
  53.    
  54.     while (1)
  55.     {
  56.         if (g_jpeg_data_sta == 1)
  57.         {
  58.             printf("Receive JPEG Data Size: %d\r\n",
  59. g_jpeg_data_recv_len * sizeof(uint32_t));
  60.             
  61.             /* 寻找JPEG数据的开头(0xFF 0xD8)和结尾(0xFF 0xD9) */
  62.             pbuf = (uint8_t *)g_jpeg_data_buf;
  63.             jpeg_start_index = JPEG_BUF_REGION_NUM * JPEG_BUF_REGION_SIZE
  64. * sizeof(uint32_t);
  65.             jpeg_data_len = 0;
  66.             for (i=1; i<(g_jpeg_data_recv_len * sizeof(uint32_t)); i++)
  67.             {
  68.                 if (jpeg_start_index == JPEG_BUF_REGION_NUM
  69. * JPEG_BUF_REGION_SIZE * sizeof(uint32_t))
  70.                 {
  71.                     if ((pbuf[i - 1] == 0xFF) && (pbuf[i] == 0xD8))
  72.                     {
  73.                         jpeg_start_index = i - 1;
  74.                     }
  75.                 }
  76.                 else
  77.                 {
  78.                     if ((pbuf[i - 1] == 0xFF) && (pbuf[i] == 0xD9))
  79.                     {
  80.                         jpeg_data_len = i - 1 - jpeg_start_index + 2;
  81.                         break;
  82.                     }
  83.                 }
  84.             }
  85.             
  86.             /* 通过串口输出有效的JPEG数据 */
  87.             if (jpeg_data_len != 0)
  88.             {
  89.                 printf("JPEG Data Size: %d\r\n", jpeg_data_len);
  90.                
  91.                 for (i=0; i<jpeg_data_len; i++)
  92.                 {
  93.                     key = key_scan(0);
  94.                     if (key)
  95.                     {
  96.                         break;
  97.                     }
  98.                     
  99.                     while ((USART2->ISR & USART_ISR_TC) != USART_ISR_TC);
  100.                     USART2->TDR = pbuf[jpeg_start_index + i];
  101.                 }
  102.             }
  103.             
  104.             /* 处理按键 */
  105.             if (key)
  106.             {
  107.                 if (key == KEY0_PRES)
  108.                 {
  109.                     /* 单次自动对焦 */
  110.                     ov5640_focus_single();
  111.                 }
  112.                 else if (key == KEY1_PRES)
  113.                 {
  114.                     /* 特效切换 */
  115.                     if (++effect == 7)
  116.                     {
  117.                         effect = 0;
  118.                     }
  119.                     ov5640_special_effects(effect);
  120.                     sprintf(msgbuf, "Effect: %s", effects_tbl[effect]);
  121.                     lcd_fill(30, 150, 239, 150 + 16, WHITE);
  122.                     lcd_show_string(30, 150, 200, 16, 16, msgbuf, RED);
  123.                 }
  124.                 else if (key == WKUP_PRES)
  125.                 {
  126.                     /* 尺寸切换 */
  127.                     if (++size == 9)
  128.                     {
  129.                         size = 0;
  130.                     }
  131.                     ov5640_outsize_set(4, 0, jpeg_size_param_tbl[size][0],
  132. jpeg_size_param_tbl[size][1]);
  133.                     sprintf(msgbuf, "JPEG Size: %s", jpeg_size_tbl[size]);
  134.                     lcd_fill(30, 170, 239, 170 + 16, WHITE);
  135.                     lcd_show_string(30, 170, 200, 16, 16, msgbuf, RED);
  136.                 }
  137.                 key = 0;
  138.             }
  139.             
  140.             /* 开启下一帧JPEG数据采集 */
  141.             g_jpeg_data_sta = 0;
  142.             g_jpeg_data_recv_len = 0;
  143.             dcmi_dma_init(memaddr, JPEG_BUF_REGION_SIZE, sizeof(memaddr)
  144. / sizeof(memaddr[0]), DMA_DEST_DATAWIDTH_WORD, DMA_DINC_INCREMENTED);
  145.             dcmi_start();
  146.         }
  147.     }
  148. }
复制代码
该函数将OV5640设置为JPEG模式,并开启持续自动对焦、实现OV5640的JPEG数据接收,通过串口2发送给上位机软件。
接下来介绍的是RGB565测试函数,其定义如下:

  1. /**
  2. * @brief   RGB565测试
  3. * @param   无
  4. * @retval  无
  5. */
  6. void rgb565_test(void)
  7. {
  8.     uint8_t key;
  9.     float fac = 0;
  10.     uint8_t effect = 0, contrast = 2;
  11.     uint8_t scale = 1;
  12.     uint8_t msgbuf[15];
  13.     uint16_t outputheight = 0;
  14.     uint32_t memaddr;
  15.     uint8_t i;
  16.    
  17.     lcd_clear(WHITE);
  18.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  19.     lcd_show_string(30, 70, 200, 16, 16, "OV5640 RGB565 Mode", RED);
  20.     lcd_show_string(30, 90, 200, 16, 16, "KEY0: Auto Focus", RED);
  21.     lcd_show_string(30, 110, 200, 16, 16, "KEY1: Effects", RED);
  22.     lcd_show_string(30, 130, 200, 16, 16, "KEY_UP: Fullsize/Scale", RED);
  23.    
  24.     /* 配置OV5640 */
  25.     ov5640_rgb565_mode();
  26.     ov5640_focus_init();
  27.     ov5640_light_mode(0);
  28.     ov5640_color_saturation(3);
  29.     ov5640_brightness(4);
  30.     ov5640_contrast(3);
  31.     ov5640_sharpness(33);
  32.     ov5640_focus_constant();
  33.     if (lcddev.height == 1024)
  34.     {
  35.         outputheight = 800;
  36.         ov5640_write_reg(0x3035, 0x51);
  37.     }
  38.     else if (lcddev.height == 1280)
  39.     {
  40.         outputheight = 600;
  41.         ov5640_write_reg(0x3035, 0x51);
  42.     }
  43.     else
  44.     {
  45.         outputheight = lcddev.height;
  46.     }
  47.     ov5640_outsize_set(4, 0, lcddev.width, outputheight);
  48.    
  49.     lcd_clear(WHITE);
  50.     lcd_set_cursor(0, 0);
  51.     lcd_write_ram_prepare();
  52.    
  53.     /* 配置DCMI */
  54.     dcmi_init();
  55.     memaddr = (uint32_t)&LCD->LCD_RAM;
  56. dcmi_dma_init(&memaddr, UINT16_MAX, 1, DMA_DEST_DATAWIDTH_HALFWORD,
  57. DMA_DINC_FIXED);
  58.     dcmi_start();
  59.    
  60.     while (1)
  61.     {
  62.         key = key_scan(0);

  63.         if (key)
  64.         {
  65.             if (key != KEY0_PRES)
  66.             {
  67.                 dcmi_stop();    /* 非KEY1按下,停止显示 */
  68.             }
  69.             
  70.             switch (key)
  71.             {
  72.                 case KEY0_PRES: /* 执行一次自动对焦 */
  73.                     ov5640_focus_single();
  74.                     break;

  75.                 case KEY1_PRES: /* 特效设置 */
  76.                     effect++;

  77.                     if (effect > 6)effect = 0;

  78.                     ov5640_special_effects(effect);         /* 设置特效 */
  79.                     sprintf((char *)msgbuf, "%s", effects_tbl[effect]);
  80.                     break;

  81.                 case WKUP_PRES: /* 1:1尺寸(显示真实尺寸)/缩放 */
  82.                     scale = !scale;

  83.                     if (scale == 0)
  84.                     {
  85.                         fac = (float)800 / outputheight;    /* 得到比例因子 */
  86.                         ov5640_outsize_set((1280 - fac * lcddev.width) / 2,
  87. (800 - fac * outputheight) / 2, lcddev.width, outputheight);
  88.                         sprintf((char *)msgbuf, "Full Size 1:1");
  89.                     }
  90.                     else
  91.                     {
  92.                         ov5640_outsize_set(4, 0, lcddev.width, outputheight);
  93.                         sprintf((char *)msgbuf, "Scale");
  94.                     }

  95.                     break;
  96.             }

  97.             if (key != KEY0_PRES)              /* 非KEY0按下 */
  98.             {
  99.                 /* 显示提示内容 */
  100.                 lcd_show_string(30, 50, 210, 16, 16, (char*)msgbuf, RED);
  101.                 delay_ms(800);
  102.                 lcd_set_cursor(0, 0);
  103.                 lcd_write_ram_prepare();
  104.                 dcmi_start();                    /* 重新开始传输 */
  105.             }
  106.         }

  107.         delay_ms(10);
  108.     }
  109. }
复制代码
该函数将OV5640设置为RGB565模式,并将接收到的数据,传送给LCD。当使用MCU屏的时候,完全由硬件DMA传输给LCD,CPU不用处理。
接下来介绍的是main函数,其定义如下:

  1. int main(void)
  2. {
  3.     uint8_t t = 0;
  4.     uint8_t key;

  5.     HAL_Init();                                                 /* HAL库初始化 */
  6.     HAL_ICACHE_Enable();                                /* 使能指令缓存 */
  7.     sys_stm32_clock_init(250, 4, 2, 2);                 /* 配置时钟,250MHz */
  8.     delay_init(250);                                            /* 初始化延时 */
  9.     usart_init(115200);                                 /* 初始化串口 */
  10.     led_init();                                                 /* 初始化LED */
  11.     key_init();                                                 /* 初始化按键 */
  12.     btim_timx_int_init(10000 - 1, 25000 - 1);   /* 初始化基本定时器 */
  13.     lcd_init();                                                 /* 初始化LCD */
  14.     usart2_init(1500000);                               /* 初始化USART2 */
  15.    
  16.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  17.     lcd_show_string(30, 70, 200, 16, 16, "OV5640 TEST", RED);
  18.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

  19.     while (ov5640_init() != 0)                          /* 初始化OV5640 */
  20.     {
  21.         lcd_show_string(30, 110, 200, 16, 16, "OV5640 Error!", RED);
  22.         delay_ms(500);
  23.         lcd_show_string(30, 110, 200, 16, 16, "Please Check! ", RED);
  24.         delay_ms(500);
  25.         LED0_TOGGLE();
  26.     }
  27.     lcd_show_string(30, 110, 200, 16, 16, "OV5640 OK!   ", RED);
  28.    
  29.     /* 选择测试模式 */
  30.     lcd_show_string(30, 130, 200, 16, 16, "KEY0: RGB565 Mode", BLUE);
  31.     lcd_show_string(30, 150, 200, 16, 16, "KEY1: JPEG Mode", BLUE);
  32.     while (1)
  33.     {
  34.         key = key_scan(0);
  35.         if (key == KEY0_PRES)
  36.         {
  37.             g_ov_mode = 0;
  38.             break;
  39.         }
  40.         else if (key == KEY1_PRES)
  41.         {
  42.             g_ov_mode = 1;
  43.             break;
  44.         }
  45.         
  46.         if (++t == 20)
  47.         {
  48.             t = 0;
  49.             LED0_TOGGLE();
  50.         }
  51.         
  52.         delay_ms(10);
  53.     }
  54.    
  55.     /* 进入测试 */
  56.     if (g_ov_mode == 0)
  57.     {
  58.         rgb565_test();
  59.     }
  60.     else
  61.     {
  62.         jpeg_test();
  63.     }
  64. }
复制代码
该函数完成对各相关硬件的初始化,然后检测OV5640,最后通过按键选择来调用jpeg_test还是rgb565_test,实现JPEG测试和RGB565测试。

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

第四十一章 摄像头实验44406.png
图41.4.1 摄像头实验程序运行效果图

上图是摄像头已经正确的插上开发板上的运行结果,如果没有插上摄像头,会有错误的报错信息。在OV5640初始化成功后,屏幕提示选择测试模式,此时我们可以按KEY0,进入RGB565模式测试,也可以按KEY1,进入JPEG模式测试。
按下KEY0后,选择RGB565模式,LCD满屏显示压缩放后的图像(有变形),如图41.4.1所示:


第四十一章 摄像头实验44595.png
图41.4.1 RGB565模式测试图片

此时,可以按KEY_UP切换为1:1显示(不变形)。同时还可以通过KEY0按键,执行一次自动对焦;KEY1按键,设置特效。
当按KEY1后,选择JPEG模式,此时屏幕显示JPEG数据传输进程,如图41.4.2所示:


第四十一章 摄像头实验44726.png
图41.4.2 JPEG模式测试图

默认条件下,图像分辨率是QQVGA (160*120)的,硬件上:我们需要用一根杜邦线连接开发板的PA2排针到RXD排针端。
打开上位机软件:XCAM V1.0.exe(路径:光盘→6,软件资料→软件→串口&网络摄像头软件→XCAM V1.0.exe),选择正确的串口,然后波特率设置为1500000,打开即可收到下位机传过来的图片了。
可以通过KEY_UP设置输出图像的尺寸(QQVGA~ 500W)。由于串口传输速率性能等的限制,当图像的尺寸大于SVGA (800*600)时,就基本无法显示了,如图41.4.3所示的是图像分辨率是VGA (640*480)的效果。


第四十一章 摄像头实验45033.png
图41.4.3 XCAM V1.0.exe软件接收并显示JPEG图片

KEY0按键,执行一次自动对焦;KEY1按键,设置特效。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-10-9 14:03

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表