完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第四十三章 摄像头实验 ALIENTEK 阿波罗 STM32F767 开发板具有 DCMI 接口,并板载了一个摄像头接口(P7), 该接口可以用来连接 ALIENTEK OV5640/OV2640 等摄像头模块。本章,我们将使用 STM32 驱动 ALIENTEK OV5640 摄像头模块,实现摄像头功能。本章分为如下几个部分: 43.1 OV5640&DCMI 简介 43.2 硬件设计 43.3 软件设计 43.4 下载验证 43.1 OV5640&DCMI 简介 本节将分为两个部分,分别介绍 OV5640 和 STM32F767 的 DCMI 接口。另外,所有 OV5640 的相关资料,都在光盘:A 盘7,硬件资料OV5640 资料 文件夹里面。 43.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 的功能框图图如图 43.1.1.1 所示: 图 43.1.1.1 OV5640 功能框图 其中 image array 部分的尺寸,OV5640 的官方数据并没有给出具体的数字,其最大的有效 输出尺寸为:2592*1944,即 500W 像素,我们根据官方提供的一些应用文档,发现其设置的 image array 最大为:2632*1951,所以,在接下来的介绍,我们设定其 image array 最大为 2632*1951。 1,DVP 接口说明 OV5640 支持数字视频接口(DVP)和 MIPI 接口,因为我们的 STM32F767 使用的 DCMI 接口,仅支持 DVP 接口,所以,OV5640 必须使用 DVP 输出接口,才可以连接我们的阿波罗 STM32 开发板。 OV5640提供一个 10 位 DVP 接口(支持 8 位接法),其MSB 和 LSB 可以程序设置先后顺序, ALIENTEK OV5640 模块采用默认的 8 位连接方式,如图 43.1.1.2 所示: 图 43.1.1.2 OV5640 默认 8 位连接方式 OV5640 的寄存器通过 SCCB 时序访问并设置,SCCB 时序和 IIC 时序十分类似,在本章我 们不做介绍,请大家参考光盘《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》这个文档。 2,窗口设置说明 接下来,我们介绍一下 OV5640 的:ISP(Image Signal Processor)输入窗口设置、预缩放 窗口设置和输出大小窗口设置,这几个设置与我们的正常使用密切相关,有必要了解一下。他 们的设置关系,如图 43.1.1.3 所示: 图 43.1.4 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(正常)。 图 43.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 的控制 下进行的。首先看看行输出时序,如图 43.1.1.5 所示: 图 43.1.1.5 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 模式),如图 43.1.6 所示: 图 43.1.1.6 OV5640 帧时序 上图清楚的表示了 OV5640 在 QSXGA 模式下的数据输出。我们按照这个时序去读取 OV5640 的数据,就可以得到图像数据。 4,自动对焦(Auto Focus)说明 OV5640 由内置微型控制器完成自动对焦,并且 VCM(Voice Coil Motor,即音圈马达)驱 动器也已集成在传感器内部。微型控制器的控制固件(firmware)从主机下载。当固件运行后, 内置微型控制器从 OV5640 传感器读得自动对焦所需的信息,计算并驱动 VCM 马达带动镜头 到达正确的对焦位置。主机可以通过 IIC 命令控制微型控制器的各种功能。 OV5640 的自动对焦命令(通过 SCCB 总线发送),如表 43.1.1.1 所示: 表 43.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。 43.1.2 STM32F767 DCMI 接口简介 STM32F767 自带了一个数字摄像头(DCMI)接口,该接口是一个同步并行接口,能够接 收外部 8 位、10 位、12 位或 14 位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据 格式:YCbCr4:2:2/RGB565 逐行视频和压缩数据 (JPEG)。 STM32F767 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 接口是一个同步并行接口,可接收高速(可达 54 MB/s)数据流。该接口包含多达 14 条数据线(D13-D0)和一条像素时钟线(PIXCLK)。像素时钟的极性可以编程,因此可以在像素 时钟的上升沿或下降沿捕获数据。 DCMI 接收到的摄像头数据被放到一个 32 位数据寄存器(DCMI_DR)中,然后通过通用 DMA 进行传输。图像缓冲区由 DMA 管理,而不是由摄像头接口管理。 从摄像头接收的数据可以按行/帧来组织(原始 YUV/RGB/拜尔模式),也可以是一系列 JPEG 图像。要使能 JPEG 图像接收,必须将 JPEG 位(DCMI_CR 寄存器的位 3)置 1。 数据流可由可选的 HSYNC(水平同步)信号和 VSYNC(垂直同步)信号硬件同步,或 者通 过数据流中嵌入的同步码同步。 STM32F767 DCMI 接口的框图如图 43.1.2.1 所示: 图 43.1.2.1 DCMI 接口框图 DCMI 接口的数据与 PIXCLK(即 PCLK)保持同步,并根据像素时钟的极性在像素时钟 上升沿/下降沿发生变化。HSYNC(HREF)信号指示行的开始/结束,VSYNC 信号指示帧的开 始/结束。DCMI 信号波形如图 43.1.2.2 所示: 图 43.1.2.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 位字中的排布如表 43.1.2.1 所示: 表 43.1.2.1 8 位捕获数据在 32 位字中的排布 从表 43.1.2.1 可以看出,STM32F767 的 DCMI 接口,接收的数据是低字节在前,高字节在 后的,所以,要求摄像头输出数据也是低字节在前,高字节在后才可以,否则就还得程序上处 理字节顺序,会比较麻烦。 DCMI 接口支持 DMA 传输,当 DCMI_CR 寄存器中的 CAPTURE 位置 1 时,激活 DMA 接口。摄像头接口每次在其寄存器中收到一个完整的 32 位数据块时,都将触发一个DMA 请求。 DCMI 接口支持两种同步方式:内嵌码同步和硬件(HSYNC 和 VSYNC)同步。我们简单 介绍下硬件同步,详细介绍请参考《STM32F7 中文参考手册》第 17.5.3 节。 硬件同步模式下将使用两个同步信号 (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 接口的其他特性,我们这里就不再介绍了,请大家参考《STM32F7 中文参考手 册》第 17 章相关内容。 本章,我们将使用 STM32F767IGT6 的 DCMI 接口连接 ALIENTEK OV5640 摄像头模块, 该模块采用 8 位数据输出接口,自带 24M 有源晶振,无需外部提供时钟,模组支持自动对焦功 能,且支持闪光灯,整个模块只需提供 3.3V 供电即可正常使用。 ALIENTEK OV5640 摄像头模块外观如图 43.1.2.3 所示: 图 43.1.2.3 ALIENTEK OV5640 摄像头模块外观图 模块原理图如图 43.1.2.4 所示: 图 43.1.2.4 ALIENTEK OV5640 摄像头模块原理图 从上图可以看出,ALIENTEK OV5640 摄像头模块自带了有源晶振,用于产生 24M 时钟作 为 OV5640 的 XCLK 输入,模块的闪光灯(LED1&LED2)由 OV5640 的 STROBE 脚控制(可 编程控制)。同时自带了稳压芯片,用于提供 OV5640 稳定的 2.8V 和 1.5V 工作电压,模块通过 一个 2*9 的双排排针(P1)与外部通信,与外部的通信信号如表 43.1.2.2 所示: 表 43.1.2.2 OV5640 模块信号及其作用描述 本章,我们将 OV5640 默认配置为 WXGA 输出,也就是 1280*800 的分辨率,输出信号设 置为:VSYNC 高电平有效,HREF 高电平有效,输出数据在 PCLK 的下降沿输出(即上升沿的 时候,MCU 才可以采集)。这样,STM32F767 的 DCMI 接口就必须设置为:VSYNC 低电平有 效、HSYNC 低电平有效和 PIXCLK 上升沿有效,这些设置都是通过 DCMI_CR 寄存器控制的, 该寄存器描述如图 43.1.2.5 所示: 图 43.1.2.5 DCMI_CR 寄存器各位描述 ENABLE,该位用于设置是否使能 DCMI,不过,在使能之前,必须将其他配置设置好。 FCRC[1:0],这两个位用于帧率控制,我们捕获所有帧,所以设置为 00 即可。 VSPOL,该位用于设置垂直同步极性,也就是 VSYNC 引脚上面,数据无效时的电平状态, 根据前面说所,我们应该设置为 0。 HSPOL,该位用于设置水平同步极性,也就是 HSYNC 引脚上面,数据无效时的电平状态, 同样应该设置为 0。 PCKPOL,该位用于设置像素时钟极性,我们用上升沿捕获,所以设置为 1。 CM,该位用于设置捕获模式,我们用连续采集模式,所以设置为 0 即可。 CAPTURE,该位用于使能捕获,我们设置为 1。该位使能后,将激活 DMA,DCMI 等待 第一帧开始,然后生成 DMA 请求将收到的数据传输到目标存储器中。注意:该位必须在 DCMI 的其他配置(包括 DMA)都设置好了之后,才设置!! DCMI_CR 寄存器的其他位,我们就不介绍了,另外 DCMI 的其他寄存器这里也不再介绍, 请大家参考《STM32F7 中文参考手册》第 17.8 节。 最后,我们来看下用 DCMI 驱动 OV5640 的步骤。HAL 库中 DCMI 接口相关的库函数分布 在源文件 stm32f7xx_hal_dcmi.c/stm32f7xx_hal_dcmi_ex.c 以及头文件 stm32f7xx_hal_dcmi.h 中。 1)配置 OV5640 控制引脚,并配置 OV5640 工作模式。 在启动 DCMI 之前,我们先设置好 OV5640。OV5640 通过 OV_SCL 和 OV_SDA 进行寄存 器配置,同时还有 OV_PWDN/OV_RESET 等信号,我们也需要配置对应 IO 状态,先设置 OV_PWDN 为 0,退出掉电模式,然后拉低 OV_RESET 复位 OV5640,之后再设置 OV_RESET 为 1,结束复位,然后就是对 OV5640 的大把寄存器进行配置了。然后,可以根据我们的需要, 设置成 RGB565 输出模式,还是 JPEG 输出模式。 2)配置相关引脚的模式和复用功能(AF13),使能时钟。 OV5640 配置好之后,再设置 DCMI 接口与摄像头模块连接的 IO 口,使能 IO 和 DCMI 时 钟,然后设置相关 IO 口为复用功能模式,复用功能选择 AF13(DCMI 复用)。 DCMI 时钟使能方法: __HAL_RCC_DCMI_CLK_ENABLE(); //使能 DCMI 时钟 引脚模式配置就是通过 HAL_GPIO_Init 函数来配置,这里就不多说了。 3)配置 DCMI 相关设置,初始化 DCMI 接口。 这一步,主要通过 DCMI_CR 寄存器设置,包括 VSPOL/HSPOL/PCKPOL/数据宽度等重要 参数,都在这一步设置。HAL 库提供了 DCMI 初始化函数 HAL_DCMI_Init,函数声明如下: HAL_StatusTypeDef HAL_DCMI_Init(DCMI_HandleTypeDef *hdcmi); 结构体 DCMI_HandleTypeDef 定义为: typedef struct { DCMI_TypeDef *Instance; DCMI_InitTypeDef Init; HAL_LockTypeDef Lock; __IO HAL_DCMI_StateTypeDef State; __IO uint32_t XferCount; __IO uint32_t XferSize; uint32_t XferTransferNumber; uint32_t pBuffPtr; DMA_HandleTypeDef *DMA_Handle; __IO uint32_t ErrorCode; }DCMI_HandleTypeDef; 该结构体第一个成员变量 Instance 用来指向寄存器基地址,设置为 DCMI 即可。 成员变量 XferCount,XferSize,XferTransferNumber,pBuffPtr 和 DMA_Handle 是与 HAL 库中 DMA 处理相关中间变量,由于使用 HAL 库配置的 DCMI DMA 会非常复杂,而且灵活性 不高,所以本实验我们是自由独立配置的 DMA。 成员变量 Init 是 DCMI_InitTypeDef 结构体类型,该结构体定义为: typedef struct { uint32_t SynchroMode; //同步方式为硬件同步还是内嵌码同步 uint32_t PCKPolarity; //像素极性 uint32_t VSPolarity; //垂直同步极性 uint32_t HSPolarity; //水平同步极性 uint32_t CaptureRate; //帧捕获率 uint32_t ExtendedDataMode; //扩展数据模式 DCMI_CodesInitTypeDef SyncroCode;//分隔符设置 uint32_t JPEGMode; //JPEG 模式选择 uint32_t ByteSelectMode; //设置字节选项模式 uint32_t ByteSelectStart; //字节选择开始:奇数/偶数字节选择 uint32_t LineSelectMode;//行选择模式 uint32_t LineSelectStart; }DCMI_InitTypeDef; 成员变量 SynchroMode 用来选择同步方式为硬件同步还是内嵌码同步。如果选择硬件同步 值 DCMI_SYNCHRO_HARDWARE,那么数据捕获由 HSYNC/VSYNC 信号同步,如果选择内 嵌码同步方式值 DCMI_SYNCHRO_EMBEDDED,那么数据捕获由数据流中嵌入的同步码同步。 成员变量 PCKPolarity 用来设置像素时钟极性为上升沿有效还是下降沿有效。我们实验使 用的是上升沿有效,所以值为 DCMI_PCKPOLARITY_RISING。 成员变量 VSPolarity 用来设置垂直同步极性 VSYNC 为低电平有效还是高电平有效。也就 是 VSYNC 引脚上面,数据无效时的电平状态。我们设置为 VSYNC 低电平有效。所以值为 DCMI_VSPOLARITY_LOW。 成员变量 HSPolarity 用来设置水平同步极性为高电平有效还是低电平有效,也就是 HSYNC 引 脚 上 面 , 数 据 无 效 时 的 电 平 状 态 。 我 们 设 置 为 HSYNC 低 电 平 有 效 。 所 以 值 为 DCMI_HSPOLARITY_LOW。 成员变量 CaptureRate 用来设置帧捕获率。如果设置为值 DCMI_CR_ALL_FRAME,也就 是全帧捕获,设置为 DCMI_CR_ALTERNATE_2_FRAME,也就 2 帧捕获一帧,设置为 DCMI_CR_ALTERNATE_4_FRAME,也就是 4 帧捕获一帧。 成员变量 ExtendedDataMode 用来设置扩展数据模式。可以设置为每个像素时钟捕获 8 位, 10 位,12 位以及 14 位数据。这里我们设置为 8 位值 DCMI_EXTEND_DATA_8B。 成员变量 SyncroCode 用来设置分隔码,包括:帧结束分隔码,行结束分隔码,行开始分隔 码以及帧开始分隔码。 成员变量 DCMI_CaptureMode 是用来设置捕获模式为连续捕获模式还是快照模式。我们实 验采取的是连续捕获模式值 DCMI_CaptureMode_Continuous,也就是通过 DMA 连续传输数据 到目标存储区。 成员变量 JPEGMode 用来设置 JPEG 格式使能。 成员变量 ByteSelectMode 用来设置字节选项模式,也就是接口对接收到的数据每隔多少个 字节捕获一个字节,取值为:DCMI_BSM_ALL(捕获所有字节),DCMI_BSM_OTHER(每隔一 个 字 节 进 行 捕 获 ) , DCMI_BSM_ALTERNATE_4 ( 每 四 个 字 节 捕 获 一 个 字 节 ) 和 DCMI_BSM_ALTERNATE_2(每四个字节捕获两个字节)。 成员变量 ByteSelectStart 是奇数偶数字节选择开始。也就是接口从帧/行开始捕获第一个数 据同时丢弃第二个字节(DCMI_OEBS_ODD)或者捕获第二个数据同时丢弃第一个字节 (DCMI_OEBS_EVEN)。 成员变量 LineSelectMode 用来配置行选择模式,也就是选择接口捕获所有接受到的行 (DCMI_LSM_ALL)还是每两行捕获一行(DCMI_LSM_ALTERNATE_2)。 成员变量 LineSelectStart 用来配置奇数偶数行选择开始。也就是接口在帧开始后捕获第一 行丢弃第二行( DCMI_OELS_ODD )或者在帧开始后捕获第二行同时丢弃第一行 (DCMI_OELS_EVEN)。 各个成员变量含义就给大家讲解到这里,函数 HAL_DCMI_Init 初始化实例为: DCMI_HandleTypeDef DCMI_Handler; //DCMI 句柄 DCMI_Handler.Instance=DCMI; DCMI_Handler.Init.SynchroMode=DCMI_SYNCHRO_HARDWARE;//硬件同步 DCMI_Handler.Init.PCKPolarity=DCMI_PCKPOLARITY_RISING; //PCLK 上升沿有效 DCMI_Handler.Init.VSPolarity=DCMI_VSPOLARITY_LOW; //VSYNC 低电平有效 DCMI_Handler.Init.HSPolarity=DCMI_HSPOLARITY_LOW; //HSYNC 低电平有效 DCMI_Handler.Init.CaptureRate=DCMI_CR_ALL_FRAME; //全帧捕获 DCMI_Handler.Init.ExtendedDataMode=DCMI_EXTEND_DATA_8B; //8 位数据格式 HAL_DCMI_Init(&DCMI_Handler); //初始化 DCMI 接口 同样,HAL 库也提供了 DCMI 接口的 MSP 初始化回调函数: void HAL_DCMI_MspInit(DCMI_HandleTypeDef* hdcmi); 一般情况下,该函数内部编写时钟使能,IO 初始化以及 NVIC 相关程序。 4)配置 DMA。 本章采用连续模式采集,并将采集到的数据输出到 LCD(RGB565 模式)或内存(JPEG 模式),所以源地址都是 DCMI_DR,而目的地址可能是 LCD->RAM 或者 SRAM 的地址。DCMI 的 DMA 传输采用的是 DMA2 数据流 1 的通道 1 来实现的,关于 DMA 的介绍,请大家参考前 面的 DMA 实验章节。这里我们列出本章我们的 DMA 配置源码如下: __HAL_RCC_DMA2_CLK_ENABLE(); //使能 DMA2 时钟 HAL_LINKDMA(&DCMI_Handler,DMA_Handle,DMADMCI_Handler); //将 DMA 与 DCMI 联系起来 DMADMCI_Handler.Instance=DMA2_Stream1; //DMA2 数据流 1 DMADMCI_Handler.Init.Channel=DMA_CHANNEL_1; //通道 1 DMADMCI_Handler.Init.Direction=DMA_PERIPH_TO_MEMORY; //外设到存储器 DMADMCI_Handler.Init.PeriphInc=DMA_PINC_DISABLE; /外设非增量模式 DMADMCI_Handler.Init.MemInc=meminc; //存储器增量模式 DMADMCI_Handler.Init.PeriphDataAlignment= DMA_PDATAALIGN_WORD; //外设数据长度:32 位 DMADMCI_Handler.Init.MemDataAlignment= DMA_MDATAALIGN_WORD; //存储器数据长度: 32 位 DMADMCI_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式 DMADMCI_Handler.Init.Priority=DMA_PRIORITY_HIGH; //高优先级 DMADMCI_Handler.Init.FIFOMode=DMA_FIFOMODE_ENABLE; //使能 FIFO DMADMCI_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_HALFFULL; //使用 1/2 的 FIFO DMADMCI_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发传输 DMADMCI_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输 HAL_DMA_DeInit(&DMADMCI_Handler); //先清除以前的设置 HAL_DMA_Init(&DMADMCI_Handler); //初始化 DMA5)设置 OV5640 的图像输出大小,使能 DCMI 捕获。 图像输出大小设置,分两种情况:在 RGB565 模式下,我们根据 LCD 的尺寸,设置输出 图像大小,以实现全屏显示(图像可能因缩放而变形);在 JPEG 模式下,我们可以自由设置输 出图像大小(可不缩放);最后,开启 DCMI 捕获,即可正常工作了。 43.2 硬件设计 本章实验功能简介:开机后,初始化摄像头模块(OV5640),如果初始化成功,则提示 选择模式:RGB565 模式,或者 JPEG 模式。KEY0 用于选择 RGB565 模式,KEY1 用于选择 JPEG 模式。 当使用 RGB565 时,输出图像(固定为:WXGA)将经过缩放处理(完全由 OV5640 的 DSP 控制),显示在 LCD 上面(默认开启连续自动对焦)。我们可以通过 KEY_UP 按键选择: 1:1 显示,即不缩放,图片不变形,但是显示区域小(液晶分辨率大小),或者缩放显示,即将 1280*800 的图像压缩到液晶分辨率尺寸显示,图片变形,但是显示了整个图片内容。通过 KEY0 按键,可以设置对比度;KEY1 按键,可以启动单次自动对焦;KEY2 按键,可以设置特效。 当使用 JPEG 模式时,图像可以设置任意尺寸(QSXGA~QQVGA),采集到的 JPEG 数据 将先存放到 STM32F767 的 SDRAM 内存里面,每当采集到一帧数据,就会关闭 DMA 传输,然 后将采集到的数据发送到串口 2(此时可以通过上位机软件(ATK-CAM.exe)接收,并显示图 片),之后再重新启动 DMA 传输。我们可以通过 KEY_UP 设置输出图片的尺寸(QSXGA ~QQVGA)。通过 KEY0 按键,可以设置对比度;KEY1 按键,可以启动单次自动对焦;KEY2 按键,可以设置特效。 同时时可以通过串口 1,借助 USMART 设置/读取 OV5640 的寄存器,方便大家调试。DS0 指示程序运行状态,DS1 用于指示帧中断。 本实验用到的硬件资源有: 1) 指示灯 DS0 和 DS1 2) 4 个按键 3) 串口 1 和串口 2 4) LCD 模块 5) PCF8574T 6) OV5640 摄像头模块 这些资源,基本上都介绍过了,这里我们用到串口 2 来传输 JPEG 数据给上位机,其配置 同串口 1 几乎一模一样,只是串口 2 的时钟来自 APB1,频率为 54Mhz。开发板板载的摄像头 模块接口与 MCU 的连接如图 43.2.1 所示: 图 43.2.1 摄像头模块接口与 STM32 连接图 图中 P7 就是摄像头模块/OLED 模块共用接口,在第十六章,我们曾简单介绍过这个接口, 它在开发板的左下角,是一个 2*9 的排座(P7)。本章,我们只需要将 ALIENTEK OV5640 摄 像头模块插入这个接口即可。 从图 43.2.1 可以看出,OV5640 摄像头模块的各信号脚与 STM32 的连接关系为: DCMI_VSYNC 接 PB7; DCMI_HREF 接 PH8; DCMI_PCLK 接 PA6; DCMI_SCL 接 PB4; DCMI_SDA 接 PB3; DCMI_RESET 接 PA15; DCMI_PWDN 接 PCF8574T 的 P2 脚; DCMI_XCLK 接 PA8(本章未用到); DCMI_D[7:0]接 PB9/PB8/PD3/PC11/PC9/PC8/PC7/PC6; 这些线的连接,阿波罗 STM32F767 开发板的内部已经连接好了,我们只需要将 OV5640 摄像头模块插上去就好了。特别注意:DCMI 摄像头接口和 SDIO 以及红外接收头有冲突,使 用的时候,必须分时复用才可以,不可同时使用。另外,DCMI_PWDN 连接在 PCF8574T 的 P2 脚上,所以本章必须使用 PCF8574T,来间接控制 DCMI_PWDN。 实物连接如图 43.2.2 所示: 图 43.2.2 OV5640 摄像头模块与开发板连接实物图 43.3 软件设计 打开本章实验工程可以看到,因为本实验要使用定时器和串口 2,所以我们添加了 timer.c 和 usart2.c 文件。同时新建了 dcmi.c/dcmi.h,sccb.c/sccb.h 以及 ov5540.c/ov5540.h 等文件。 由于本实验代码比较多,我们就不一一列出了,仅挑几个重要的地方进行讲解。 首先,我们来看 ov5640.c 里面的 OV5640_Init 函数,该函数代码如下: //初始化 OV5640 //配置完以后,默认输出是 1600*1200 尺寸的图片!! //返回值:0,成功 // 其他,错误代码 u8 OV5640_Init(void) { u16 i=0; u16 reg; //设置 IO GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟 GPIO_Initure.Pin=GPIO_PIN_15; //PA15 GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速 HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 PCF8574_Init(); //初始化 PCF8574 OV5640_RST(0); //必须先拉低 OV5640 的 RST 脚,再上电 delay_ms(20); OV5640_PWDN_Set(0); //POWER ON delay_ms(5); OV5640_RST(1); //结束复位 delay_ms(20); SCCB_Init(); //初始化 SCCB 的 IO 口 delay_ms(5); reg=OV5640_RD_Reg(OV5640_CHIPIDH); //读取 ID 高八位 reg<<=8; reg|=OV5640_RD_Reg(OV5640_CHIPIDL); //读取 ID 低八位 if(reg!=OV5640_ID) { printf("ID:%drn",reg); return 1; } OV5640_WR_Reg(0x3103,0X11); //system clock from pad, bit[1] OV5640_WR_Reg(0X3008,0X82); //软复位 delay_ms(10); //初始化 OV5640,采用 SXGA 分辨率(1600*1200) for(i=0;i OV5640_WR_Reg(ov5640_uxga_init_reg_tbl[0],ov5640_uxga_init_reg_tbl[1]); } //检查闪光灯是否正常 OV5640_Flash_Ctrl(1);//打开闪光灯 delay_ms(50); OV5640_Flash_Ctrl(0);//关闭闪光灯 return 0x00; //ok }此部分代码先初始化 OV5640 相关的 IO 口(包括 PCF8574_Init 和 SCCB_Init),然后最主要的 是完成 OV5640 的寄存器序列初始化。OV5640 的寄存器特多(百几十个),配置特麻烦,幸好 厂家有提供参考配置序列(详见《OV5640_camera_module_software_application_notes_1.3 _Sonix.pdf》),本章我们用到的配置序列,存放在 ov5640_ init_reg_tbl 这个数组里面,该数组是 一个 2 维数组,存储初始化序列寄存器及其对应的值,该数组存放在 ov5640cfg.h 里面。 另外,在 ov5640.c 里面,还有几个函数比较重要,这里不贴代码了,只介绍功能: OV5640_ImageWin_Set 函数,该函数用于设置 ISP 输入窗口; OV5640_OutSize_Set 函数,用于设置预缩放窗口和输出大小窗口; OV5640_Focus_Init 函数,用于初始化自动对焦功能; OV5640_Focus_Single 函数,用于实现一次自动对焦; OV5640_Focus_Constant 函数,用于开启持续自动对焦功能; OV5640_ImageWin_Set 和 OV5640_OutSize_Set 这就是我们在 43.1.1 节所介绍的 3 个窗口 的设置,他们共同决定了图像的输出。 接下来,我们看看 ov5640cfg.h 里面 ov5640_init_reg_tbl 的内容,ov5640cfg.h 文件的代码如 下: //JPEG 配置.7.5 帧 //最大支持 2592*1944 的 JPEG 图像输出 const u16 OV5640_jpeg_reg_tbl[][2]= { 0x4300, 0x30, // YUV 422, YUYV ……//省略部分代码 0x3503, 0x00, // AEC/AGC on }; //RGB565 配置.15 帧 //最大支持 1280*800 的 RGB565 图像输出 const u16 ov5640_rgb565_reg_tbl[][2]= { 0x4300, 0X6F, ……//省略部分代码 0x3503, 0x00, // AEC/AGC on }; //OV5640 初始化寄存器序列表 const u16 ov5640_init_reg_tbl[][2]= { // 24MHz input clock, 24MHz PCLK 0x3008, 0x42, // software power down, bit[6] ……//省略部分代码 0x4740, 0X21, //VSYNC 高有效 }; 以上代码,我们省略了很多(全部贴出来太长了),里面总共有 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 模式下使用。 接下来,我们看看 dcmi.c 里面的代码,如下: DCMI_HandleTypeDef DCMI_Handler; //DCMI 句柄 DMA_HandleTypeDef DMADMCI_Handler; //DMA 句柄 u8 ov_frame=0; //帧率 extern void jpeg_data_process(void); //JPEG 数据处理函数 //DCMI 初始化 void DCMI_Init(void) { DCMI_Handler.Instance=DCMI; DCMI_Handler.Init.SynchroMode=DCMI_SYNCHRO_HARDWARE;//硬件同步 DCMI_Handler.Init.PCKPolarity=DCMI_PCKPOLARITY_RISING; //PCLK 上升沿有效 DCMI_Handler.Init.VSPolarity=DCMI_VSPOLARITY_LOW; //VSYNC 低电平有效 DCMI_Handler.Init.HSPolarity=DCMI_HSPOLARITY_LOW; //HSYNC 低电平有效 DCMI_Handler.Init.CaptureRate=DCMI_CR_ALL_FRAME; //全帧捕获 DCMI_Handler.Init.ExtendedDataMode=DCMI_EXTEND_DATA_8B; //8 位数据格式 HAL_DCMI_Init(&DCMI_Handler); //初始化 DCMI,此函数会开启帧中断 //关闭行中断、VSYNC 中断、同步错误中断和溢出中断 __HAL_DCMI_DISABLE_IT(&DCMI_Handler,DCMI_IT_LINE| DCMI_IT_VSYNC|DCMI_IT_ERR|DCMI_IT_OVR); __HAL_DCMI_ENABLE_IT(&DCMI_Handler,DCMI_IT_FRAME); //使能帧中断 __HAL_DCMI_ENABLE(&DCMI_Handler); //使能 DCMI } //DCMI 底层驱动,引脚配置,时钟使能,中断配置 //此函数会被 HAL_DCMI_Init()调用 //hdcmi:DCMI 句柄 void HAL_DCMI_MspInit(DCMI_HandleTypeDef* hdcmi) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_DCMI_CLK_ENABLE(); //使能 DCMI 时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); //使能 GPIOA 时钟 ……//省略部分时钟使能代码 //初始化 PA6 GPIO_Initure.Pin=GPIO_PIN_6; GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速 GPIO_Initure.Alternate=GPIO_AF13_DCMI; //复用为 DCMI HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化 ……//省略部分 IO 配置代码 HAL_NVIC_SetPriority(DCMI_IRQn,2,2); //抢占优先级 1,子优先级 2 HAL_NVIC_EnableIRQ(DCMI_IRQn); //使能 DCMI 中断 } //DCMI DMA 配置 //mem0addr:存储器地址 0 将要存储摄像头数据的内存地址(也可以是外设地址) //mem1addr:存储器地址 1 当只使用 mem0addr 的时候,该值必须为 0 //memblen:存储器位宽,可以为:DMA_MDATAALIGN_BYTE/ //DMA_MDATAALIGN_HALFWORD/DMA_MDATAALIGN_WORD //meminc:存储器增长方式,可以为:DMA_MINC_ENABLE/DMA_MINC_DISABLE void DCMI_DMA_Init(u32 mem0addr,u32 mem1addr,u16 memsize,u32 memblen,u32 meminc) { __HAL_RCC_DMA2_CLK_ENABLE();//使能 DMA2 时钟 __HAL_LINKDMA(&DCMI_Handler,DMA_Handle,DMADMCI_Handler); //将 DMA 与 DCMI 联系起来 __HAL_DMA_DISABLE_IT(&DMADMCI_Handler,DMA_IT_TC); //先关闭 DMA 传输完成中断(否则在使用 MCU 屏的时候会出现花屏的情况) DMADMCI_Handler.Instance=DMA2_Stream1; //DMA2 数据流 1 DMADMCI_Handler.Init.Channel=DMA_CHANNEL_1; //通道 1 DMADMCI_Handler.Init.Direction=DMA_PERIPH_TO_MEMORY; //外设到存储器 DMADMCI_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式 DMADMCI_Handler.Init.MemInc=meminc; //存储器增量模式 DMADMCI_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_WORD; //外设数据长度:32 位 DMADMCI_Handler.Init.MemDataAlignment=memblen; //存储器数据长度:8/16/32 位 DMADMCI_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式 DMADMCI_Handler.Init.Priority=DMA_PRIORITY_HIGH; //高优先级 DMADMCI_Handler.Init.FIFOMode=DMA_FIFOMODE_ENABLE; //使能 FIFO DMADMCI_Handler.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_HALFFULL; //使用 1/2 的 FIFO DMADMCI_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器突发传输 DMADMCI_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设突发单次传输 HAL_DMA_DeInit(&DMADMCI_Handler); //先清除以前的设置 HAL_DMA_Init(&DMADMCI_Handler); //初始化 DMA //在开启 DMA 之前先使用__HAL_UNLOCK()解锁一次 DMA __HAL_UNLOCK(&DMADMCI_Handler); if(mem1addr==0) //开启 DMA,不使用双缓冲 { HAL_DMA_Start(&DMADMCI_Handler,(u32)&DCMI->DR,mem0addr,memsize); } else //使用双缓冲 { HAL_DMAEx_MultiBufferStart(&DMADMCI_Handler,(u32)&DCMI->DR, mem0addr,mem1addr,memsize);//开启双缓冲 __HAL_DMA_ENABLE_IT(&DMADMCI_Handler,DMA_IT_TC);//开启传输完成中断 HAL_NVIC_SetPriority(DMA2_Stream1_IRQn,2,3); //DMA 中断优先级 HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn); } } //DCMI,启动传输 void DCMI_Start(void) { LCD_SetCursor(0,0); LCD_WriteRAM_Prepare(); //开始写入 GRAM __HAL_DMA_ENABLE(&DMADMCI_Handler); //使能 DMA DCMI->CR|=DCMI_CR_CAPTURE; //DCMI 捕获使能 } //DCMI,关闭传输 void DCMI_Stop(void) { DCMI->CR&=~(DCMI_CR_CAPTURE); //关闭捕获 while(DCMI->CR&0X01); //等待传输完成 __HAL_DMA_DISABLE(&DMADMCI_Handler);//关闭 DMA } //DCMI 中断服务函数 void DCMI_IRQHandler(void) { HAL_DCMI_IRQHandler(&DCMI_Handler); } //捕获到一帧图像处理函数 //hdcmi:DCMI 句柄 void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) { jpeg_data_process();//jpeg 数据处理 LED1_Toggle; ov_frame++; //重新使能帧中断,因为 HAL_DCMI_IRQHandler()函数会关闭帧中断 __HAL_DCMI_ENABLE_IT(&DCMI_Handler,DCMI_IT_FRAME); } void (*dcmi_rx_callback)(void);//DCMI DMA 接收回调函数 //DMA2 数据流 1 中断服务函数 void DMA2_Stream1_IRQHandler(void) { if(__HAL_DMA_GET_FLAG(&DMADMCI_Handler,DMA_FLAG_TCIF1_5)!=RESET) //DMA 传输完成 { __HAL_DMA_CLEAR_FLAG(&DMADMCI_Handler,DMA_FLAG_TCIF1_5); //清除 DMA 传输完成中断标志位 dcmi_rx_callback(); //执行摄像头接收回调函数,读取数据等操作在这里面处理 } } //////////////////////////////////////////////////////////////////////////////// //以下两个函数,供 usmart 调用,用于调试代码 //DCMI 设置显示窗口 //sx,sy;LCD 的起始坐标 //width,height:LCD 显示范围. void DCMI_Set_Window(u16 sx,u16 sy,u16 width,u16 height) { DCMI_Stop(); LCD_Clear(WHITE); LCD_Set_Window(sx,sy,width,height); OV5640_OutSize_Set(0,0,width,height); LCD_SetCursor(0,0); LCD_WriteRAM_Prepare(); //开始写入 GRAM __HAL_DMA_ENABLE(&DMADMCI_Handler); //开启 DMA2,Stream1 DCMI->CR|=DCMI_CR_CAPTURE; //DCMI 捕获使能 } //通过 usmart 调试,辅助测试用. //pclk/hsync/vsync:三个信号的有限电平设置 void DCMI_CR_Set(u8 pclk,u8 hsync,u8 vsync) { HAL_DCMI_DeInit(&DCMI_Handler);//清除原来的设置 DCMI_Handler.Instance=DCMI; DCMI_Handler.Init.SynchroMode=DCMI_SYNCHRO_HARDWARE;//硬件同步 DCMI_Handler.Init.PCKPolarity=pclk<<5; //PCLK 上升沿有效 DCMI_Handler.Init.VSPolarity=vsync<<7; //VSYNC 低电平有效 DCMI_Handler.Init.HSPolarity=hsync<<6; //HSYNC 低电平有效 DCMI_Handler.Init.CaptureRate=DCMI_CR_ALL_FRAME; //全帧捕获 DCMI_Handler.Init.ExtendedDataMode=DCMI_EXTEND_DATA_8B;//8 位数据格式 HAL_DCMI_Init(&DCMI_Handler); //初始化 DCMI DCMI_Handler.Instance->CR|=DCMI_MODE_CONTINUOUS; //持续模式 }其中:DCMI_IRQHandler 函数,用于处理帧中断,可以实现帧率统计(需要定时器支持) 和 JPEG 数据处理等 ,实际上当捕获到一帧数据后,调用的是 HAL 库 回 调 函 数 HAL_DCMI_FrameEventCallback 进行处理,关于中断处理回调函数前面实验多次讲解,这里我 们就不重复讲解处理过程了。DCMI_DMA_Init 函数,则用于配置 DCMI 的 DMA 传输,其外 设地址固定为:DCMI->DR,而存储器地址可变(LCD 或者 SRAM)。DMA 被配置为循环模式, 一旦开启,DMA 将不停的循环传输数据。DMA2_Stream1_IRQHandler 函数,用于在使用 RGB 屏的时候,双缓冲存储时,数据的搬运处理(通过 dcmi_rx_callback 函数实现)。DCMI_Init 函 数用于初始化 STM32F7 的 DCMI 接口,这是根据在 43.1.2 节提到的配置步骤进行配置的。最 后,DCMI_Start 和 DCMI_Stop 两个函数,用于开启或停止 DCMI 接口。 其他部分代码我们就不再细说了,请大家参考光盘本例程源码(实验 38 摄像头实验)。 最后,打开 main.c 文件,代码如下: u8 ovx_mode=0; //bit0:0,RGB565 模式;1,JPEG 模式 u16 curline=0; //摄像头输出数据,当前行编号 u16 yoffset=0; //y 方向的偏移量 #define jpeg_buf_size 30*1024*1024//定义 JPEG 数据缓存 jpeg_buf 的大小(1*4M 字节) #define jpeg_line_size 2*1024 //定义 DMA 接收数据时,一行数据的最大值 u32 dcmi_line_buf[2][jpeg_line_size]; //RGB 屏时,摄像头采用一行一行读取,定义行缓存 u32 jpeg_data_buf[jpeg_buf_size] __attribute__((at(0XC0000000+1280*800*2))); //JPEG 数据缓存 buf,定义在 LCD 帧缓存之后 volatile u32 jpeg_data_len=0; //buf 中的 JPEG 有效数据长度 volatile u8 jpeg_data_ok=0; //JPEG 数据采集完成标志 //0,数据没有采集完; //1,数据采集完了,但是还没处理; //2,数据已经处理完成了,可以开始下一帧接收 //JPEG 尺寸支持列表 const u16 jpeg_img_size_tbl[][2]= { 160,120, //QQVGA ……//省略部分代码 2592,1944, //500W }; const u8*EFFECTS_TBL[7]={"Normal","Cool","Warm","B&W","Yellowish ","Inverse", "Greenish"}; //7 种特效 const u8*JPEG_SIZE_TBL[12]={"QQVGA","QVGA","VGA","SVGA","XGA","WXGA", "WXGA+","SXGA","UXGA","1080P","QXGA","500W"};//JPEG 图片 12 种尺寸 //处理 JPEG 数据 //当采集完一帧 JPEG 数据后,调用此函数,切换 JPEG BUF.开始下一帧采集. void jpeg_data_process(void) { u16 i; u16 rlen; //剩余数据长度 u32 *pbuf; curline=yoffset; //行数复位 if(ovx_mode&0X01) //只有在 JPEG 格式下,才需要做处理. { if(jpeg_data_ok==0) //jpeg 数据还未采集完? { __HAL_DMA_DISABLE(&DMADMCI_Handler);//关闭 DMA rlen=jpeg_line_size-__HAL_DMA_GET_COUNTER(&DMADMCI_Handler); //得到剩余数据长度 pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾,继续添加 if(DMADMCI_Handler.Instance->CR&(1<<19))for(i=0;i dcmi_line_buf[1];//读取 buf1 里面的剩余数据 else for(i=0;i jpeg_data_len+=rlen; //加上剩余长度 jpeg_data_ok=1; //标记 JPEG 数据采集完成,等待其他函数处理 } if(jpeg_data_ok==2) //上一次的 jpeg 数据已经被处理了 { __HAL_DMA_SET_COUNTER(&DMADMCI_Handler,jpeg_line_size); //传输长度为 jpeg_buf_size*4 字节 __HAL_DMA_ENABLE(&DMADMCI_Handler); //打开 DMA jpeg_data_ok=0; //标记数据未采集 jpeg_data_len=0; //数据重新开始 } }else { LCD_SetCursor(0,0); LCD_WriteRAM_Prepare(); //开始写入 GRAM } } //jpeg 数据接收回调函数 void jpeg_dcmi_rx_callback(void) { u16 i; u32 *pbuf; pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾 if(DMADMCI_Handler.Instance->CR&(1<<19))//buf0 已满,正常处理 buf1 { for(i=0;i jpeg_data_len+=jpeg_line_size;//偏移 }else //buf1 已满,正常处理 buf0 { for(i=0;i jpeg_data_len+=jpeg_line_size;//偏移 } SCB_CleanInvalidateDCache(); //清除无效化 DCache } //JPEG 测试 //JPEG 数据,通过串口 2 发送给电脑. void jpeg_test(void) { u32 i,jpgstart,jpglen; u8 *p; u8 key,headok=0; u8 effect=0,contrast=2; u8 size=2; //默认是 QVGA 320*240 尺寸 u8 msgbuf[15]; //消息缓存区 ……//省略部分代码 LCD_ShowString(30,180,200,16,16,msgbuf);//显示当前 JPEG 分辨率 //自动对焦初始化 OV5640_RGB565_Mode(); //RGB565 模式 OV5640_Focus_Init(); ……//省略部分代码 OV5640_Focus_Constant();//启动持续对焦 DCMI_Init(); //DCMI 配置 dcmi_rx_callback=jpeg_dcmi_rx_callback;//JPEG 接收数据回调函数 DCMI_DMA_Init((u32)&dcmi_line_buf[0],(u32)&dcmi_line_buf[1], jpeg_line_size,DMA_MDATAALIGN_WORD,DMA_MINC_ENABLE); OV5640_OutSize_Set(4,0,jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]); //设置输出尺寸 DCMI_Start(); //启动传输 while(1) { if(jpeg_data_ok==1) //已经采集完一帧图像了 { p=(u8*)jpeg_data_buf; printf("jpeg_data_len:%drn",jpeg_data_len*4);//打印帧率 LCD_ShowString(30,210,210,16,16,"Sending JPEG data..."); //提示正在传输数据 jpglen=0; //设置 jpg 文件大小为 0 headok=0; //清除 jpg 头标记 for(i=0;i if((p==0XFF)&&(p[i+1]==0XD8))//找到 FF D8 { jpgstart=i; headok=1; //标记找到 jpg 头(FF D8) } if((p==0XFF)&&(p[i+1]==0XD9)&&headok)//找到头以后,再找 FF D9 { jpglen=i-jpgstart+2; break; } } if(jpglen) //正常的 jpeg 数据 { p+=jpgstart; //偏移到 0XFF,0XD8 处 for(i=0;i USART2->TDR=p; while((USART2->ISR&0X40)==0); //循环发送,直到发送完毕 key=KEY_Scan(0); if(key)break; } } if(key) //有按键按下,需要处理 { ……//省略部分代码 }else LCD_ShowString(30,210,210,16,16,"Send data complete!!");//提示结束 jpeg_data_ok=2; //标记 jpeg 数据处理完了,可以让 DMA 去采集下一帧了. } } } //RGB 屏数据接收回调函数 void rgblcd_dcmi_rx_callback(void) { u16 *pbuf; if(DMA2_Stream1->CR&(1<<19))//DMA 使用 buf1,读取 buf0 { pbuf=(u16*)dcmi_line_buf[0]; }else //DMA 使用 buf0,读取 buf1 { pbuf=(u16*)dcmi_line_buf[1]; } LTDC_Color_Fill(0,curline,lcddev.width-1,curline,pbuf);//DM2D 填充 if(curline //RGB565 测试 //RGB 数据直接显示在 LCD 上面 void rgb565_test(void) { u8 key; u8 effect=0,contrast=2,fac; u8 scale=1; //默认是全尺寸缩放 u8 msgbuf[15]; //消息缓存区 u16 outputheight=0; ……//省略部分代码 LCD_ShowString(30,160,200,16,16,"KEY_UP:FullSize/Scale"); //1:1 尺寸 //自动对焦初始化 OV5640_RGB565_Mode(); //RGB565 模式 OV5640_Focus_Init(); ……//省略部分代码 OV5640_Focus_Constant();//启动持续对焦 DCMI_Init(); //DCMI 配置 if(lcdltdc.pwidth!=0) //RGB 屏 { dcmi_rx_callback=rgblcd_dcmi_rx_callback;//RGB 屏接收数据回调函数 DCMI_DMA_Init((u32)dcmi_line_buf[0],(u32)dcmi_line_buf[1],lcddev.width/2, DMA_MDATAALIGN_HALFWORD,DMA_MINC_ENABLE); }else //MCU 屏 { DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,1, DMA_MDATAALIGN_HALFWORD,DMA_MINC_DISABLE); } TIM3->CR1&=~(0x01); //关闭定时器 3,关闭帧率统计(如果打开,RGB 屏会抖) if(lcddev.height>800) { yoffset=(lcddev.height-800)/2; outputheight=800; OV5640_WR_Reg(0x3035,0X51);//降低输出帧率,否则可能抖动 }else { yoffset=0; outputheight=lcddev.height; } curline=yoffset; //行数复位 OV5640_OutSize_Set(4,0,lcddev.width,outputheight); //满屏缩放显示 DCMI_Start(); //启动传输 LCD_Clear(BLACK); while(1) { key=KEY_Scan(0); if(key) { ……//省略部分代码 } delay_ms(10); } } int main(void) { u8 key; u8 t; Cache_Enable(); //打开 L1-Cache MPU_Memory_Protection(); //保护相关存储区域 HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz ……//省略部分代码 usart2_init(921600); //初始化 USART2 TIM3_Init(10000-1,10800-1); //10Khz 计数,1 秒钟中断一次 while(OV5640_Init())//初始化 OV5640 { ……//省略部分代码 } LCD_ShowString(30,130,200,16,16,"OV5640 OK"); while(1) { key=KEY_Scan(0); if(key==KEY0_PRES){ovx_mode=0;break;} //RGB565 模式 else if(key==KEY1_PRES){ovx_mode=1;break;} //JPEG 模式 t++; if(t==100)LCD_ShowString(30,150,230,16,16,"KEY0:RGB565 KEY1:JPEG"); //闪烁显示提示信息 if(t==200) { LCD_Fill(30,150,210,150+16,WHITE); t=0; LED0_Toggle; } delay_ms(5); } if(ovx_mode==1)jpeg_test(); else rgb565_test(); }这部分代码比较长,我们省略了一些内容。详细的代码,请大家参考光盘本例程源码。注 意,这里定义了一个非常大的数组 jpeg_data_buf(4MB),用来存储 JPEG 数据,因为 2592*1944 大小的 jpeg 图片,有可能大于 3MB,所以必须将这个数组尽量设置大一点。这个数组,我们 定义在 SDRAM,由__attribute__关键字,指定数组地址,紧跟 LTDC GRAM 后的地址存放。 在 main.c 里面,总共有 6 个函数,我们接下来分别介绍。 1,jpeg_data_process 函数 该函数用于处理 JPEG 数据的接收,在 DCMI_IRQHandler 函数(在 dcmi.c 里面)里面被 调用,它与 jpeg_dcmi_rx_callback 函数和 jpeg_test 函数共同控制 JPEG 的数据传送。JPEG 数据 的接收,采用 DMA 双缓冲机制,缓冲数组为:dcmi_line_buf(u32 类型,RGB 屏接收 RGB565 数据时,也是用这个数组);数组大小为:jpeg_line_size,我们定义的是 2*1024,即数组大小 为 8K 字节(数组大小不能小于存储摄像头一行输出数据的大小);JPEG 数据接收处理流程如 图 43.3.1 所示: 图 43.3.1 JPEG 数据流 DMA 双缓冲接收流程 JPEG 数据采集流程:当 JPEG 数据流传输给 MCU 的时候,首先由 M0AR 存储,此时如果 M1AR 有数据,则可以读取 M1AR 里面的数据,当 M0AR 数据满时,由 M1AR 存储,此时程 序可以读取 M0AR 里面所存储的数据,当 M1AR 数据满时,由 M0AR 存储……。这个存储数 据的操作,绝大部分是由 DMA 传输完成中断服务函数,调用 jpeg_dcmi_rx_callback 函数实现 的,当一帧数据传输完成时,会进入 DCMI 帧中断服务函数,调用 jpeg_data_process 函数,对 最后的剩余数据进行存储,完成一帧 JPEG 数据的采集。 2,jpeg_dcmi_rx_callback 函数 这是 jpeg 数据接收的主要函数,通过判断 DMA2_Stream1->CR 寄存器,读取不同 buf 里面 的数据,存储到 SDRAM 里面(jpeg_data_buf)。该函数由 DMA 的传输完成中断服务函数: DMA2_Stream1_IRQHandler 调用。 3,jpeg_test 函数 该函数将 OV5640 设置为 JPEG 模式,并开启持续自动对焦,该函数实现 OV5640 的 JPEG 数据接收,并通过串口 2 发送给上位机软件。 4,rgblcd_dcmi_rx_callback 函数 该函数仅在使用 RGB 屏,且使用 RGB565 模式的时候用到。当使用 RGB 屏的时候,我们 每接收一行数据,就使用 DMA2D 填充到 RGB 屏的 GRAM,这里同样是使用 DMA 的双缓冲 机制来接收 RGB565 数据,原理参照图 43.3.1。该函数由 DMA 传输完成中断服务函数调用。 5,rgb565_test 函数 该函数将 OV5640 设置为 RGB565 模式,并将接收到的数据,传送给 LCD。当使用 MCU 屏的时候,完全由硬件 DMA 传输给 LCD,CPU 不用处理;当使用 RGB 屏的时候,数据先由 DMA 接收到双缓存里面,然后在 DMA 传输完成中断服务函数里面,调用函数: rgblcd_dcmi_rx_callback,将接收到的数据,用 DMA2D 填充到 RGB LCD,显示到屏幕上。 6,main 函数 该函数完成对各相关硬件的初始化,然后检测 OV5640,最后通过按键选择来调用 jpeg_test 还是 rgb565_test,实现 JPEG 测试和 RGB565 测试。 前面提到,我们要用 USMART 来设置摄像头的参数,我们只需要在 usmart_nametab 里面 添加 OV5640_WR_Reg 和 OV5640_RD_Reg 等相关函数,就可以轻松调试摄像头了。 43.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK 阿波罗 STM32 开发板上,在 OV5640 初始化成功后,屏幕提示选择模式,此时我们可以按 KEY0,进入 RGB565 模式测试, 也可以按 KEY1,进入 JPEG 模式测试。 当按 KEY0 后,选择 RGB565 模式,LCD 满屏显示压缩放后的图像(有变形),如图 43.4.1 所示: 图 43.4.1 RGB565 模式测试图片 此时,可以按 KEY_UP 切换为 1:1 显示(不变形)。同时还可以通过 KEY0 按键,设置对 比度;KEY1 按键,执行一次自动对焦;KEY2 按键,设置特效。 当按 KEY1 后,选择 JPEG 模式,此时屏幕显示 JPEG 数据传输进程,如图 43.4.2 所示: 图 43.4.2 JPEG 模式测试图 默认条件下,图像分辨率是 VGA(640*480)的,硬件上:我们需要一根 RS232 串口线连接 开发板的 COM2(注意要用跳线帽将 P8 的:COM2_RX 连接在 PA2(TX))。如果没有 RS232 线, 也可以借助我们开发板板载的 USB 转串口实现(有 2 个办法:1,改代码,将串口 2 输出改到 串口 1;2,杜邦线连接 P8 的 PA2(TX)和 P4 的 RXD)。 我们打开上位机软件:ATK-CAM.exe(路径:光盘6,软件资料软件串口&网络摄像 头软件ATK-CAM.exe),选择正确的串口,然后波特率设置为 921600,打开即可收到下位机 传过来的图片了,如图 43.4.3 所示: 图 43.4.3 ATK-CAM 软件接收并显示 JPEG 图片 我们可以通过 KEY_UP 设置输出图像的尺寸(QQVGA~QSXGA)。通过 KEY0 按键,设置 对比度;KEY1 按键,执行一次自动对焦;KEY2 按键,设置特效。 同时,你还可以在串口(开发板的串口 1),通过 USMART 调用 SCCB_WR_Reg 等函数, 来设置 OV5640 的各寄存器,达到调试测试 OV5640 的目的,如图 43.4.4 所示: 图 43.4.4 USMART 调试 OV5640 从上图还可以看出,帧率为 7/8 帧(实际上是 7.5 帧),每张 JPEG 图片的大小是 33KB 左右(分辨率为:640*480 的时候)。 |
|
相关推荐
|
|
1020 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
996 浏览 2 评论
2102 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1202 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1621 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-23 22:21 , Processed in 0.674198 second(s), Total 66, Slave 48 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号