本帖最后由 正点原子运营 于 2023-8-26 16:05 编辑
第四十七章 摄像头实验
1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
6)STM32技术交流QQ群:151941872
STM32F407具有DCMI接口,所以我们的探索者STM32F407开发板板载了一个摄像头接口(P3),该接口可以用来连接ALIENTEK OV5640/OV2640/OV7725等摄像头模块。本章,我们将使用STM32驱动ALIENTEK OV2640摄像头模块,实现摄像头功能。 本章分为如下几个小节: 47.1 OV2640和DCMI简介 47.2 硬件设计 47.3 程序设计 47.4 下载验证
47.1 OV2640和DCMI简介本节将分为两个部分,分别介绍OV2640简介和STM32F407 DCMI接口简介。另外,所有OV2640的相关资料,都在光盘:A盘à7,硬件资料àOV2640资料 文件夹里面。
47.1.1 OV2640简介OV2640是OV(OmniVision)公司生产的一颗1/4寸的CMOS UXGA(1632*1232)图像传感器。该传感器体积小、工作电压低,提供单片UXGA摄像头和影像处理器的所有功能。通过SCCB 总线控制,可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率8/10位影像数据。该产品UXGA图像最高达到15帧/秒(SVGA可达30帧,CIF可达60帧)。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、对比度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、拖尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。
OV2640的特点有: l 高灵敏度、低电压适合嵌入式应用 l 标准的SCCB接口,兼容IIC接口 l 支持RawRGB、RGB(RGB565/RGB555)、GRB422、YUV(422/420)和YCbCr(422)输出格式 l 支持UXGA、SXGA、SVGA以及按比例缩小到从SXGA到40*30的任何尺寸 l 支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。 l 支持闪光灯 l 支持图像缩放、平移和窗口设置 l 支持图像压缩,即可输出JPEG图像数据 l 自带嵌入式微处理器
OV2640的功能框图如图47.1.1.1所示: OV2640传感器包括如下一些功能模块。
1.感光整列(Image Array) OV2640总共有1632*1232个像素,最大输出尺寸为UXGA(1600*1200),即200W像素。
2.模拟信号处理(Analog Processing) 模拟信号处理所有模拟功能,并包括:模拟放大(AMP)、增益控制、通道平衡和平衡控制等。
3.10位A/D 转换(A/D) 原始的信号经过模拟放大后,分G和BR两路进入一个10 位的A/D 转换器,A/D 转换器工作频率高达20M,与像素频率完全同步(转换的频率和帧率有关)。除A/D转换器外,该模块还有黑电平校正(BLC)功能。
4.数字信号处理器(DSP) 这个部分控制由原始信号插值到RGB信号的过程,并控制一些图像质量: l 边缘锐化(二维高通滤波器) l 颜色空间转换(原始信号到RGB或者YUV/YCbYCr) l RGB色彩矩阵以消除串扰 l 色相和饱和度的控制 l 黑/白点补偿 l 降噪 l 镜头补偿 l 可编程的伽玛 l 十位到八位数据转换
5.输出格式模块(Output Formatter) 该模块按设定优先级控制图像的所有输出数据及其格式。
6.压缩引擎(Compression Engine) 压缩引擎框图如图47.1.1.2所示: 从图可以看出,压缩引擎主要包括三部分:DCT、QZ和entropy encoder(熵编码器),将原始的数据流,压缩成jpeg数据输出。
7.微处理器(Microcontroller) OV2640自带了一个8位微处理器,该处理器有512字节SRAM,4KB的ROM,它提供一个灵活的主机到控制系统的指令接口,同时也具有细调图像质量的功能。
8.SCCB接口(SCCB Interface) SCCB接口控制图像传感器芯片的运行,详细使用方法参照光盘的《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》这个文档
9.数字视频接口(Digital Video Port) OV2640拥有一个10位数字视频接口(支持8位接法),其MSB和LSB可以程序设置先后顺序,ALIENTEK OV2640模块采用默认的8位连接方式,如图47.1.1.3所示: OV2640的寄存器通过SCCB时序访问并设置,SCCB时序和IIC时序十分类似,在本章我们不做介绍,请大家参考光盘《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》这个文档。
接下来,我们介绍一下OV2640的传感器窗口设置、图像尺寸设置、图像窗口设置和图像输出大小设置,这几个设置与我们的正常使用密切相关,有必要了解一下。其中,除了传感器窗口设置是直接针对传感器阵列的设置,其他都是DSP部分的设置了,接下来我们一个个介绍。
传感器窗口设置,该功能允许用户设置整个传感器区域(1632*1220)的感兴趣部分,也就是在传感器里面开窗,开窗范围从2*2~1632*1220都可以设置,不过要求这个窗口必须大于等于随后设置的图像尺寸。传感器窗口设置,通过:0X03/0X19/0X1A/0X07/0X17/0X18等寄存器设置,寄存器定义请看OV2640_DS(1.6).pdf这个文档(下同)。
图像尺寸设置,也就是DSP输出(最终输出到LCD的)图像的最大尺寸,该尺寸要小于等于前面我们传感器窗口设置所设定的窗口尺寸。图像尺寸通过:0XC0/0XC1/0X8C等寄存器设置。
图像窗口设置,这里起始和前面的传感器窗口设置类似,只是这个窗口是在我们前面设置的图像尺寸里面,再一次设置窗口大小,该窗口必须小于等于前面设置的图像尺寸。该窗口设置后的图像范围,将用于输出到外部。图像窗口设置通过:0X51/0X52/0X53/0X54/0X55/0X57等寄存器设置。
图像输出大小设置,这是最终输出到外部的图像尺寸。该设置将图像窗口设置所决定的窗口大小,通过内部DSP处理,缩放成我们输出到外部的图像大小。该设置将会对图像进行缩放处理,如果设置的图像输出大小不等于图像窗口设置图像大小,那么图像就会被缩放处理,只有这两者设置一样大的时候,输出比例才是1 : 1的。
因为OmniVision 公司公开的文档,对这些设置实在是没有详细介绍。只能从他们提供的初始化代码(还得去linux源码里面移植过来)里面去分析规律,所以,这几个设置,都是作者根据OV2640的调试经验,以及相关文档总结出来的,不保证百分比正确,如有错误,还请大家指正。
以上几个设置,光看文字可能不太清楚,这里我们画一个简图有助于大家理解,如图47.1.1.4所示: 上图,最终红色框所示的图像输出大小,才是OV2640输出给外部的图像尺寸,也就是显示在LCD上面的图像大小。当图像输出大小与图像窗口不等时,会进行缩放处理,在LCD上面看到的图像将会变形。
最后,我们介绍一下OV2640的图像数据输出格式。首先我们简单介绍一些定义: UXGA,即分辨率位1600*1200的输出格式,类似的还有:SXGA(1280*1024)、WXGA+(1440*900)、XVGA(1280*960)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、CIF(352*288)、WQVGA(400*240)、QCIF(176*144)和QQVGA(160*120)等。
PCLK,即像素时钟,一个PCLK时钟,输出一个像素(或半个像素)。
VSYNC,即帧同步信号。
HREF /HSYNC,即行同步信号。
OV2640的图像数据输出(通过Y[9:0])就是在PCLK,VSYNC和HREF/ HSYNC的控制下进行的。首先看看行输出时序,如图47.1.1.5所示: 从上图可以看出,图像数据在HREF为高的时候输出,当HREF变高后,每一个PCLK时钟,输出一个8位/10位数据。我们采用8位接口,所以每个PCLK输出1个字节,且在RGB/YUV输出格式下,每个tp=2个Tpclk,如果是Raw格式,则一个tp=1个Tpclk。比如我们采用UXGA时序,RGB565格式输出,每2个字节组成一个像素的颜色(高低字节顺序可通过0XDA寄存器设置),这样每行输出总共有1600*2个PCLK周期,输出1600*2个字节。
再来看看帧时序(UXGA模式),如图47.1.1.6所示: 上图清楚的表示了OV2640在UXGA模式下的数据输出。我们按照这个时序去读取OV2640的数据,就可以得到图像数据。
最后说一下OV2640的图像数据格式,我们一般用2种输出方式:RGB565和JPEG。当输出RGB565格式数据的时候,时序完全就是上面两幅图介绍的关系。以满足不同需要。而当输出数据是JPEG数据的时候,同样也是这种方式输出(所以数据读取方法一模一样),不过PCLK数目大大减少了,且不连续,输出的数据是压缩后的JPEG数据,输出的JPEG数据以:0XFF,0XD8开头,以0XFF,0XD9结尾,且在0XFF,0XD8之前,或者0XFF,0XD9之后,会有不定数量的其他数据存在(一般是0),这些数据我们直接忽略即可,将得到的0XFF,0XD8~0XFF,0XD9之间的数据,保存为.jpg/.jpeg文件,就可以直接在电脑上打开看到图像了。
OV2640自带的JPEG输出功能,大大减少了图像的数据量,使得其在网络摄像头、无线视频传输等方面具有很大的优势。OV2640我们就介绍到这,关于OV2640更详细的介绍,请参考:A盘à7,硬件资料à4,OV2640资料à OV2640_DS(1.6).pdf。
ALIENTEKOV2640摄像头模块 本实验,我们将使用探索者STM32F407开发板的DCMI接口连接ALIENTEK OV2640摄像头模块,该模块采用8位数据输出接口,自带24M有源晶振,无需外部提供时钟,百万高清镜头,且支持闪光灯,整个模块只需提供3.3V 供电即可正常使用。
ALIENTEK OV2640摄像头模块外观如图47.1.1.6所示:
图47.1.1.6 ALIENTEK OV2640摄像头模块外观图 模块原理图如图47.1.1.7所示: 图47.1.1.7 ALIENTEK OV2640摄像头模块原理图 从上图可以看出,ATK-OV2640摄像头模块自带了有源晶振,用于产生24M时钟作为OV2640的XVCLK输入,模块的闪光灯(LED1&LED2)可由OV2640的STROBE脚控制(可编程控制)或外部引脚控制,只需焊接R2或R3的电阻进行切换控制。同时自带了稳压芯片,用于提供OV2640稳定的2.8V和1.3V两个电压。模块通过一个2*9的双排排针(P1)与外部通信,与外部的通信信号如表43.1.1.2所示: 表43.1.1.2 OV2640模块信号及其作用描述 注1:当OV2640硬件R2电阻焊接时,OV_FLASH引脚控制才有效。默认R2电阻没有焊接,R3电阻焊接,闪光灯默认通过STROBE脚编程控制。
47.1.2 STM32F407 DCMI接口简介STM32F407自带了一个数字摄像头(DCMI)接口,该接口是一个同步并行接口,能够接收外部8位、10位、12位或 14位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据格式:YCbCr4:2:2/RGB565逐行视频和压缩数据 (JPEG)。
STM32F407 DCM接口特点: l 8 位、10 位、12 位或 14 位并行接口 l 内嵌码/外部行同步和帧同步 l 连续模式或快照模式 l 裁剪功能 l 支持以下数据格式: 1,8/10/12/14 位逐行视频:单色或原始拜尔(Bayer)格式 2,YCbCr 4:2:2逐行视频 3,RGB 565 逐行视频 4,压缩数据:JPEG
DCMI接口包括如下一些信号: 1, 数据输入(D[0:13]),用于接摄像头的数据输出,接OV2640我们只用了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(垂直同步)信号硬件同步,或者通过数据流中嵌入的同步码同步。
47.1.2.1 DCMI接口功能概述STM32F407 DCMI接口的框图如图47.1.2.1.1所示: DCMI接口的数据与PIXCLK(即PCLK)保持同步,并根据像素时钟的极性在像素时钟上升沿/下降沿发生变化。HSYNC(HREF)信号指示行的开始/结束,VSYNC信号指示帧的开始/结束。DCMI信号波形如图47.1.2.1.2所示: 上图中,对应设置为: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位字中的排布如表47.1.2.1.4所示: 表47.1.2.1.4 8位捕获数据在32位字中的排布 从表47.1.2.1.4可以看出,STM32F407的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接口的其他特性,我们这里就不再介绍了,请大家参考《STM32F4xx参考手册_V4(中文版).pdf》第13章相关内容。
47.1.2.2 DCMI寄存器本实验,我们将OV2640默认配置为UXGA输出,也就是1600*1200的分辨率,输出信号设置为:VSYNC高电平有效,HREF高电平有效,输出数据在PCLK的下降沿输出(即上升沿的时候,MCU才可以采集)。这样,STM32F407的DCMI接口就必须设置为:VSYNC低电平有效、HSYNC低电平有效和PIXCLK上升沿有效,这些设置都是通过DCMI_CR寄存器控制。
l DCMI控制寄存器(DCMI_CR) DCMI控制寄存器描述如图47.1.2.2.1所示: 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的其他寄存器这里也不再介绍,请大家参考《STM32F4xx参考手册_V4(中文版).pdf》第13.8小节。关于DMA的寄存器,这里就不再赘述,请回顾前面的实验。
47.2 硬件设计
1. 例程功能1、本实验开机后,初始化摄像头模块(OV2640),如果初始化成功,则提示选择模式:RGB565模式或者JPEG模式。KEY0用于选择RGB565模式,KEY1用于选择JPEG模式。 2、当使用RGB565时,输出图像(固定为:UXGA)将经过缩放处理(完全由OV5640的DSP控制),显示在LCD上面(默认开启连续自动对焦)。我们可以通过KEY_UP按键选择:1:1显示,即不缩放,图片不变形,但是显示区域小(液晶分辨率大小),或者缩放显示,即将1280*800的图像压缩到液晶分辨率尺寸显示,图片变形,但是显示了整个图片内容。通过按键KEY_UP设置输出图片的尺寸、按键KEY0设置对比度、按键KEY1设置饱和度,按键KEY2设置特效。 3、当使用JPEG模式时,图像可以设置默认是QVGA 320*240尺寸,采集到的JPEG数据将先存放到STM32的RAM内存里面,每当采集到一帧数据,就会关闭DMA传输,然后将采集到的数据发送到串口2(此时可以通过上位机软件(ATK-CAM.exe)接收,并显示图片),之后再重新启动DMA传输。通过按键KEY_UP设置输出图片的尺寸、按键KEY0设置对比度、按键KEY1设置饱和度,按键KEY2设置特效。 4、同时可以通过串口1,借助USMART设置/读取OV2640的寄存器,方便大家调试。 5、LED0闪烁,提示程序运行。LED1用于指示帧中断。。
2. 硬件资源1)RGB灯 LED0 – PF9 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 KEY0 – PE4 KEY1 – PE3 KEY2 – PE2 KEY_UP – PA0 5)串口2 (PA2/PA3,通过USB转232线连接在板载232上) 6)DCMI接口(用于驱动OV5640摄像头模块) 7)定时器6(用于打印摄像头帧率等信息) 8)ALIENTEK OV2640摄像头模块,连接关系为: OV2640模块----------- STM32开发板 OV_D0~D7 ------------ PC6/PC7/PC8/PC9/PC11/PB6/PE5/PE6 OV_SCL ------------ PD6 OV_SDA ------------ PD7 OV_VSYNC ------------ PB7 OV_HREF ------------ PA4 OV_PCLK ------------ PA6 OV_PWDN ------------ PG9 OV_RESET ------------ PG15 OV_XCLK ------------ PA8
3. 原理图开发板板载的摄像头模块接口与MCU的连接关系,如下图所示: 图47.2.1 OV2640摄像头与STM32F407连接示意图 这些GPIO口的线都在开发板上连接到P3端口,所以我们只需要将OV2640摄像头模块插上开发板的连接座子就好了(摄像头模块正面往外插)。 图47.2.2 OV2640摄像头模块与开发板的连接座子
47.3 程序设计
47.3.1 DCMI的HAL库驱动DCMI在HAL库中的驱动代码在stm32f4xx_hal_dcmi.c文件(及其头文件)中。
1.HAL_DCMI_Init函数 DCMI初始化函数,其声明如下: - HAL_StatusTypeDefHAL_DCMI_Init(DCMI_HandleTypeDef *hdcmi);
复制代码l 函数描述: 用于初始化DCMI。
l 函数形参: 形参1是DCMI_HandleTypeDef结构体类型指针变量,其定义如下: - typedef struct
- {
- DCMI_TypeDef *Instance; /* DCMI 外设寄存器基地址 */
- DCMI_InitTypeDef Init; /* DCMI 初始化结构体 */
- HAL_LockTypeDef Lock; /* 锁对象 */
- __IOHAL_DCMI_StateTypeDef State; /* DCMI 工作状态 */
- __IO uint32_t XferCount; /* DMA 传输计数器 */
- __IO uint32_t XferSize; /* DMA 传输数据的大小 */
- uint32_t XferTransferNumber; /* DMA 数据的个数 */
- uint32_t pBuffPtr; /* DMA输出缓冲区地址 */
- DMA_HandleTypeDef *DMA_Handle; /* DMA配置结构体指针 */
- __IO uint32_t ErrorCode; /* DCMI 错误代码 */
- }DCMI_HandleTypeDef;
复制代码下面重点介绍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 模式选择 */
- }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格式使能或禁止。
l 函数返回值: HAL_StatusTypeDef枚举类型的值。
以DMA方式传输DCMI数据配置步骤 1)配置OV2640控制引脚,并配置OV2640工作模式。 在启动DCMI之前,我们先设置好OV2640。OV2640通过OV_SCL和OV_SDA进行寄存器配置,同时还有OV_PWDN/OV_RESET等信号,我们也需要配置对应IO状态,先设置OV_PWDN为0,退出掉电模式,然后拉低OV_RESET复位OV2640,之后再设置OV_RESET为1,结束复位,然后就是对OV2640的寄存器进行配置了。最后,可以根据我们的需要,设置成RGB565输出模式,还是JPEG输出模式。
2)配置相关引脚的模式和复用功能(AF13),使能时钟。 OV2640配置好之后,再设置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);
复制代码该结构体第一个成员变量Instance用来指向寄存器基地址,设置为DCMI即可。 成员变量XferCount,XferSize,XferTransferNumber,pBuffPtr和DMA_Handle是与HAL库中DMA处理相关中间变量,由于使用HAL库配置的DCMI DMA会非常复杂,而且灵活性不高,所以本实验我们是独立配置的DMA。 同样,HAL库也提供了DCMI接口的MSP初始化回调函数: - voidHAL_DCMI_MspInit(DCMI_HandleTypeDef* hdcmi);
复制代码一般情况下,该函数内部编写时钟使能,IO初始化以及NVIC相关程序。
4)配置DMA。 本实验我们采用连续模式采集,并将采集到的数据输出到LCD(RGB565模式)或内存(JPEG模式),所以源地址都是DCMI_DR,而目的地址可能是LCD->RAM或者SDRAM的地址。DCMI的DMA传输采用的是DMA1数据流1的通道1来实现的,关于DMA的介绍,请大家参考前面的DMA实验章节。
5)设置OV2640的图像输出大小,使能DCMI捕获。 图像输出大小设置,分两种情况:在RGB565模式下,我们根据LCD的尺寸,设置输出图像大小,以实现全屏显示(图像可能因缩放而变形);在JPEG模式下,我们可以自由设置输出图像大小(可不缩放);最后,开启DCMI捕获,即可正常工作了。
47.3.2 程序流程图
47.3.3 程序解析
1. DCMI驱动代码这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。DCMI驱动源码包括两个文件:dcmi.c和dcmi.h。
dcmi.h头文件只是一些声明,下面直接开始介绍dcmi.c文件,首先是DCMI初始化函数,其定义如下: - /**
- *@brief DCMI 初始化
- * @note IO对应关系如下:
- * 摄像头模块 ------------ STM32开发板
- * OV_D0~D7 ------------ PC6/PC7/PC8/PC9/PC11/PB6/PE5/PE6
- * OV_SCL ------------ PD6
- * OV_SDA ------------ PD7
- * OV_VSYNC ------------ PB7
- * OV_HREF ------------ PA4
- * OV_PCLK ------------ PA6
- * OV_PWDN ------------ PG9
- * OV_RESET ------------ PG15
- * OV_XCLK ------------ PA8
- * 本函数仅初始化OV_D0~D7/OV_VSYNC/OV_HREF/OV_PCLK等信号(11个).
- * @param 无
- *@retval 无
- */
- void dcmi_init(void)
- {
- g_dcmi_handle.Instance = DCMI;
- g_dcmi_handle.Init.SynchroMode =DCMI_SYNCHRO_HARDWARE;/*硬件同步HSYNC,VSYNC*/
- g_dcmi_handle.Init.PCKPolarity =DCMI_PCKPOLARITY_RISING;/* PCLK 上升沿有效 */
- g_dcmi_handle.Init.VSPolarity =DCMI_VSPOLARITY_LOW; /* VSYNC 低电平有效 */
- g_dcmi_handle.Init.HSPolarity =DCMI_HSPOLARITY_LOW; /* HSYNC 低电平有效 */
- g_dcmi_handle.Init.CaptureRate = DCMI_CR_ALL_FRAME; /* 全帧捕获 */
- g_dcmi_handle.Init.ExtendedDataMode=DCMI_EXTEND_DATA_8B;/* 8位数据格式 */
- HAL_DCMI_Init(&g_dcmi_handle); /* 初始化DCMI,此函数会开启帧中断 */
- /* 关闭行中断、VSYNC中断、同步错误中断和溢出中断 */
- //__HAL_DCMI_DISABLE_IT(&g_dcmi_handle,DCMI_IT_LINE|DCMI_IT_VSYNC
- |DCMI_IT_ERR|DCMI_IT_OVR);
- /* 关闭所有中断,函数HAL_DCMI_Init()会默认打开很多中断,开启这些中断
- 以后我们就需要对这些中断做相应的处理,否则的话就会导致各种各样的问题,
- 但是这些中断很多都不需要,所以这里将其全部关闭掉,也就是将IER寄存器清零。
- 关闭完所有中断以后再根据自己的实际需求来使能相应的中断 */
- DCMI->IER=0x0;
- __HAL_DCMI_ENABLE_IT(&g_dcmi_handle,DCMI_IT_FRAME); /* 使能帧中断 */
- __HAL_DCMI_ENABLE(&g_dcmi_handle); /* 使能DCMI */
- }
复制代码该函数主要对DCMI_HandleTypeDef结构体成员赋值并初始化,最后关闭所有中断,只开启帧中断,使能DCMI。而DCMI接口的GPIO口的初始化是在HAL_DCMI_MspInit回调函数中完成,其定义如下: - /**
- *@brief DCMI底层驱动,引脚配置,时钟使能,中断配置
- *@param hdcmi:DCMI句柄
- *@note 此函数会被HAL_DCMI_Init()调用
- *@retval 无
- */
- voidHAL_DCMI_MspInit(DCMI_HandleTypeDef* hdcmi)
- {
- GPIO_InitTypeDef gpio_init_struct;
- __HAL_RCC_DCMI_CLK_ENABLE(); /* 使能DCMI时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能GPIOA时钟 */
- __HAL_RCC_GPIOB_CLK_ENABLE(); /* 使能GPIOB时钟 */
- __HAL_RCC_GPIOC_CLK_ENABLE(); /* 使能GPIOC时钟 */
- __HAL_RCC_GPIOE_CLK_ENABLE(); /* 使能GPIOE时钟 */
- gpio_init_struct.Pin = GPIO_PIN_4 | GPIO_PIN_6;
- gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
- gpio_init_struct.Alternate = GPIO_AF13_DCMI; /* 复用为DCMI */
- HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 初始化PA4,6引脚 */
- gpio_init_struct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
- HAL_GPIO_Init(GPIOB, &gpio_init_struct); /* 初始化PB6,7引脚 */
- gpio_init_struct.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8
- | GPIO_PIN_9 | GPIO_PIN_11;
- HAL_GPIO_Init(GPIOC, &gpio_init_struct); /* 初始化PC6,7,8,9,11引脚 */
- gpio_init_struct.Pin = GPIO_PIN_5 | GPIO_PIN_6;
- HAL_GPIO_Init(GPIOE, &gpio_init_struct); /* 初始化PE5,6引脚 */
- HAL_NVIC_SetPriority(DCMI_IRQn, 2, 2); /* 抢占优先级2,子优先级2 */
- HAL_NVIC_EnableIRQ(DCMI_IRQn); /* 使能DCMI中断 */
- }
复制代码DCMI接口的GPIO口前面都介绍过了,该函数最后设置了DCMI中断抢占优先级为2,子优先级为2,并且使能DCMI中断。
接下来介绍DCMI DMA配置初始化函数,其定义如下: - /**
- *@brief DCMI DMA配置
- *@param mem0addr: 存储器地址0 将要存储摄像头数据的内存地址(也可以是外设地址)
- *@param mem1addr: 存储器地址1 当只使用mem0addr的时候,该值必须为0
- *@param memsize : 存储器长度 0~65535
- *@param memblen : 存储器位宽 DMA_MDATAALIGN_BYTE,8位;
- DMA_MDATAALIGN_HALFWORD,16位;DMA_MDATAALIGN_WORD,32位
- *@param meminc : 存储器增长方式 DMA_MINC_DISABLE,不增长;DMA_MINC_ENABLE,增长
- *@retval 无
- */
- voiddcmi_dma_init(uint32_t mem0addr,uint32_t mem1addr,
- uint16_t memsize,uint32_t memblen,uint32_t meminc)
- {
- __HAL_RCC_DMA1_CLK_ENABLE(); /* 使能DMA1时钟 */
- /* 将DMA与DCMI联系起来 */
- __HAL_LINKDMA(&g_dcmi_handle, DMA_Handle, g_dma_dcmi_handle);
- /* 先关闭DMA传输完成中断(否则在使用MCU屏的时候会出现花屏的情况) */
- __HAL_DMA_DISABLE_IT(&g_dma_dcmi_handle, DMA_IT_TC);
- g_dma_dcmi_handle.Instance = DMA1_Stream1; /* DMA1数据流1 */
- g_dma_dcmi_handle.Init. Channel = DMA_CHANNEL_1; /* DCMI的DMA请求 */
- g_dma_dcmi_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 外设到存储器 */
- g_dma_dcmi_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
- g_dma_dcmi_handle.Init.MemInc = meminc; /* 存储器增量模式 */
- /* 外设数据长度:32位 */
- g_dma_dcmi_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
- g_dma_dcmi_handle.Init.MemDataAlignment = memblen;/*存储器数据长度:8/16/32位*/
- g_dma_dcmi_handle.Init.Mode = DMA_CIRCULAR; /* 使用循环模式 */
- g_dma_dcmi_handle.Init.Priority = DMA_PRIORITY_HIGH; /* 高优先级 */
- g_dma_dcmi_handle.Init.FIFOMode = DMA_FIFOMODE_ENABLE; /* 使能FIFO */
- /* 使用1/2的FIFO */
- g_dma_dcmi_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;
- g_dma_dcmi_handle.Init.MemBurst = DMA_MBURST_SINGLE; /* 存储器突发传输 */
- g_dma_dcmi_handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 外设突发单次传输 */
- HAL_DMA_DeInit(&g_dma_dcmi_handle); /* 先清除以前的设置 */
- HAL_DMA_Init(&g_dma_dcmi_handle); /* 初始化DMA */
-
- /* 在开启DMA之前先使用__HAL_UNLOCK()解锁一次DMA,因为HAL_DMA_Statrt()
- HAL_DMAEx_MultiBufferStart()这两个函数一开始要先使用__HAL_LOCK()锁定DMA,
- 而函数__HAL_LOCK()会判断当前的DMA状态是否为锁定状态,如果是锁定状态的话就直接
- 返回HAL_BUSY,这样会导致函数HAL_DMA_Statrt()和HAL_DMAEx_MultiBufferStart()
- 后续的DMA配置程序直接被跳过!DMA也就不能正常工作,为了避免这种现象,所以在启动
- DMA之前先调用__HAL_UNLOC()先解锁一次DMA。 */
-
- __HAL_UNLOCK(&g_dma_dcmi_handle);
- if (mem1addr == 0) /* 开启DMA,不使用双缓冲 */
- {
- HAL_DMA_Start(&g_dma_dcmi_handle, (uint32_t)&DCMI->DR,
- mem0addr, memsize);
- }
- else /* 使用双缓冲 */
- {
- HAL_DMAEx_MultiBufferStart(&g_dma_dcmi_handle, (uint32_t)&DCMI->DR,
- mem0addr, mem1addr, memsize); /* 开启双缓冲 */
- __HAL_DMA_ENABLE_IT(&g_dma_dcmi_handle, DMA_IT_TC); /* 开启传输完成中断 */
- HAL_NVIC_SetPriority(DMA2_Stream1_IRQn, 2, 3); /* DMA中断优先级 */
- HAL_NVIC_EnableIRQ(DMA2_Stream1_IRQn);
- }
- }
复制代码该函数用于配置DCMI的DMA传输,其外设地址固定为:DCMI->DR,而存储器地址可变(LCD或者SRAM)。DMA被配置为循环模式,一旦开启,DMA将不停的循环传输数据。
下面介绍的是DCMI启动传输和关闭传输函数,它们的定义分别如下: - /**
- *@brief DCMI,启动传输
- *@param 无
- *@retval 无
- */
- void dcmi_start(void)
- {
- lcd_set_cursor(0,0); /* 设置坐标到原点 */
- lcd_write_ram_prepare(); /* 开始写入GRAM */
- __HAL_DMA_ENABLE(&g_dma_dcmi_handle); /* 使能DMA */
- DCMI->CR |= DCMI_CR_CAPTURE; /* DCMI捕获使能 */
- }
- /**
- *@brief DCMI,关闭传输
- *@param 无
- *@retval 无
- */
- void dcmi_stop(void)
- {
- DCMI->CR &= ~(DCMI_CR_CAPTURE); /* DCMI捕获关闭 */
- while (DCMI->CR & 0X01); /* 等待传输结束 */
- __HAL_DMA_DISABLE(&g_dma_dcmi_handle); /* 关闭DMA */
- }
复制代码下面介绍的是DCMI中断服务函数(及其回调函数)、DCMI DMA接收回调函数和DMA2数据流1中断服务函数,它们的定义分别如下: - /**
- *@brief DCMI中断服务函数
- *@param 无
- *@retval 无
- */
- voidDCMI_IRQHandler(void)
- {
- HAL_DCMI_IRQHandler(&g_dcmi_handle);
- }
- /**
- *@brief DCMI中断回调服务函数
- *@param hdcmi:DCMI句柄
- *@note 捕获到一帧图像处理
- *@retval 无
- */
- voidHAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi)
- {
- jpeg_data_process(); /* jpeg数据处理 */
- LED1_TOGGLE(); /* LED1闪烁 */
- g_ov_frame++;
- /* 重新使能帧中断,因为HAL_DCMI_IRQHandler()函数会关闭帧中断 */
- __HAL_DCMI_ENABLE_IT(&g_dcmi_handle, DCMI_IT_FRAME);
- }
- void (*dcmi_rx_callback)(void); /* DCMI DMA接收回调函数 */
- /**
- *@brief DMA2数据流1中断服务函数(仅双缓冲模式会用到)
- *@param 无
- *@retval 无
- */
- voidDMA2_Stream1_IRQHandler(void)
- {
- /* DMA传输完成 */
- if (__HAL_DMA_GET_FLAG(&g_dma_dcmi_handle, DMA_FLAG_TCIF1_5) != RESET)
- {
- /* 清除DMA传输完成中断标志位 */
- __HAL_DMA_CLEAR_FLAG(&g_dma_dcmi_handle, DMA_FLAG_TCIF1_5);
- dcmi_rx_callback(); /* 执行摄像头接收回调函数,读取数据等操作在这里面处理 */
- }
- }
复制代码其中:DCMI_IRQHandler函数,用于处理帧中断,可以实现帧率统计(需要定时器支持)和JPEG数据处理等,实际上当捕获到一帧数据后,调用的是HAL库回调函数HAL_DCMI_FrameEventCallback进行处理。DMA2_Stream1_IRQHandler函数,仅用于在使用RGB屏的时候,双缓冲存储时,数据的搬运处理(通过dcmi_rx_callback函数实现)。
最后还定义两个可以通过usmart调试、辅助测试使用的函数dcmi_set_window和dcmi_cr_set函数。dcmi_set_window函数用于调节屏幕显示的范围,本实验LCD的起始坐标要设置为(0,0),LCD显示范围要设置为屏幕最大像素点范围内。dcmi_cr_set函数用于设置pclk/hsync/vsync 这三个信号的有效电平。
DCMI驱动代码就介绍到这里。
2. OV2640驱动代码这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。OV2640驱动源码包括六个文件:ov2640.c、ov2640.h、ov2640af.h、ov2640cfg.h、sccb.c和sccb.h。
其中sccb.c和sccb.h是SCCB通信接口的驱动代码。OV2640的寄存器通过SCCB时序访问并设置,SCCB时序和IIC时序十分类似,这里我们也是用软件模拟SCCB时序。
下面,首先介绍sccb.h头文件中SCCB的IO口宏定义,其定义情况如下: - /*****************************************************************************/
- /* 引脚 定义 */
- #define SCCB_SCL_GPIO_PORT GPIOD
- #define SCCB_SCL_GPIO_PIN GPIO_PIN_6
- #define SCCB_SCL_GPIO_CLK_ENABLE()
- do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0) /* PD口时钟使能 */
- #define SCCB_SDA_GPIO_PORT GPIOD
- #define SCCB_SDA_GPIO_PIN GPIO_PIN_7
- #define SCCB_SDA_GPIO_CLK_ENABLE()
- do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0) /* PD口时钟使能 */
- /* 如果不用开漏模式或SCCB上无上拉电阻,我们需要推挽和输入切换的方式 */
- #define OV_SCCB_TYPE_NOD 1
- #if OV_SCCB_TYPE_NOD
- #define SCCB_SDA_IN() { GPIOD->MODER &= ~(3 << (7 *2)); GPIOD->MODER |= \
- 0 << (7 * 2); } /* PD7 输入 */
- #define SCCB_SDA_OUT() { GPIOD->MODER&= ~(3 << (7 * 2)); GPIOD->MODER |= \
- 1 << (7 * 2); } /* PD7 输出 */
- #endif
- /*****************************************************************************/
- /* IO操作函数 */
- #define SCCB_SCL(x) do{ x ? \
- HAL_GPIO_WritePin(SCCB_SCL_GPIO_PORT,SCCB_SCL_GPIO_PIN, GPIO_PIN_SET) : \
- HAL_GPIO_WritePin(SCCB_SCL_GPIO_PORT, SCCB_SCL_GPIO_PIN,GPIO_PIN_RESET); \
- }while(0) /* SCL */
- #define SCCB_SDA(x) do{ x ? \
- HAL_GPIO_WritePin(SCCB_SDA_GPIO_PORT,SCCB_SDA_GPIO_PIN, GPIO_PIN_SET) : \
- HAL_GPIO_WritePin(SCCB_SDA_GPIO_PORT, SCCB_SDA_GPIO_PIN,GPIO_PIN_RESET); \
- }while(0) /* SDA */
- #define SCCB_READ_SDA HAL_GPIO_ReadPin(SCCB_SDA_GPIO_PORT, \
- SCCB_SDA_GPIO_PIN) /* 读取SDA */
复制代码SCCB时序有两根信号线(SCCB_SCL和SCCB_SDA),所以这里定义了两个IO口(PD6和PD7)来控制。IO操作函数有三个,包括SCCB_SCL用于控制时钟,SCCB_SDA是写IO口输出的值为逻辑1或者逻辑0,SCCB_READ_SDA是读取IO口的值,逻辑1或者逻辑0。
接下来介绍sccb.c文件的代码,首先是SCCB接口初始化函数,该函数主要就是初始化PD6和PD7两个IO口,其定义如下: - /**
- *@brief 初始化SCCB
- *@param 无
- *@retval 无
- */
- void sccb_init(void)
- {
- GPIO_InitTypeDef gpio_init_struct;
- SCCB_SCL_GPIO_CLK_ENABLE(); /* SCL引脚时钟使能 */
- SCCB_SDA_GPIO_CLK_ENABLE(); /* SDA引脚时钟使能 */
- gpio_init_struct.Pin = SCCB_SCL_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
- HAL_GPIO_Init(SCCB_SCL_GPIO_PORT, &gpio_init_struct); /* 初始化SCL引脚 */
- /* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了,
- 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
- gpio_init_struct.Pin = SCCB_SDA_GPIO_PIN;
- gpio_initure.Mode = GPIO_MODE_OUTPUT_OD;
- HAL_GPIO_Init(SCCB_SDA_GPIO_PORT, &gpio_init_struct); /* 初始化SDA引脚 */
- sccb_stop(); /* 停止总线上所有设备 */
- }
复制代码PD6和PD7都设置为带上拉的开漏输出,这样设置的好处是:SDA引脚不用再设置IO口方向了,因为开漏输出模式下,STM32的IO口可以读取外部信号的高低电平,这个在前面介绍GPIO外设的时候已经讲解过。初始化IO口后,调用sccb_stop函数停止总线上的所有设备,防止误操作。
sccb.c文件的其他函数,这里就不再介绍了,主要都是IO口模拟SCCB时序的相关函数,详细请看源码。
ov2640.c、ov2640.h和ov2640cfg.h这三个个文件主要就是用于OV2640摄像头的初始化,当然也要使用到SCCB的驱动代码。
ov2640cfg.h文件存放的是OV2640初始化数组,总共有五个数组。我们大概了解下数组结构,每个数组条目的第一个字节为寄存器号(也就是寄存器地址),第二个字节为要设置的值,比如{0x04, 0xA8},就表示在0x04地址,写入0xA8这个值。
五个数组中,ov2640_uxga_init_reg_tbl和ov2640_svga_init_reg_tb1数组,分别用于配置OV2640输出UXGA和SVGA分辨率的图像,我们只用了ov2640_uxga_init_reg_tb1,完成对OV2640的初始化(设置为UXGA)。最后OV2640要输出数据是RGB565还是JPEG,就得通过其他数组设置,输出RGB565时,通过数组:ov2640_rgb565_reg_tbl设置即可;输出JPEG时,则要通过ov2640_yuv422_reg_tbl和ov2640_jpeg_reg_tbl两个数组设置。
下面开始介绍ov2640.h文件,主要是OV2640的PWDN和RESET引脚定义和控制函数,以及OV2640的ID、访问地址,其定义如下: - /*****************************************************************************/
- /* PWDN 引脚定义 */
- #define OV_PWDN_GPIO_PORT GPIOG
- #define OV_PWDN_GPIO_PIN GPIO_PIN_9
- /* PG口时钟使能 */
- #define OV_PWDN_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOG_CLK_ENABLE(); }while(0)
- /* RESET 引脚定义 */
- #define OV_RESET_GPIO_PORT GPIOG
- #define OV_RESET_GPIO_PIN GPIO_PIN_15、
- /* PG口时钟使能 */
- #define OV_RESET_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOG_CLK_ENABLE(); }while(0)
- /* FLASH 引脚定义 */
- #define OV_FLASH_GPIO_PORT GPIOA
- #define OV_FLASH_GPIO_PIN GPIO_PIN_8
- /* PA口时钟使能 */
- #define OV_FLASH_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
- /*****************************************************************************/
- /* IO控制函数 */
- #define OV2640_PWDN(x) do{ x ? \
- HAL_GPIO_WritePin(OV_PWDN_GPIO_PORT,OV_PWDN_GPIO_PIN, PIO_PIN_SET) : \
- HAL_GPIO_WritePin(OV_PWDN_GPIO_PORT,OV_PWDN_GPIO_PIN, GPIO_PIN_RESET); \
- }while(0) /* POWER DOWN控制信号 */
- #define OV2640_RST(x) do{ x ? \
- HAL_GPIO_WritePin(OV_RESET_GPIO_PORT,OV_RESET_GPIO_PIN, GPIO_PIN_SET) : \
- HAL_GPIO_WritePin(OV_RESET_GPIO_PORT, OV_RESET_GPIO_PIN, GPIO_PIN_RESET); \
- }while(0) /* 复位控制信号 */
- #define OV2640_FLASH(x) do{ x ? \
- HAL_GPIO_WritePin(OV_FLASH_GPIO_PORT,OV_FLASH_GPIO_PIN, GPIO_PIN_SET) : \
- HAL_GPIO_WritePin(OV_FLASH_GPIO_PORT, OV_FLASH_GPIO_PIN,GPIO_PIN_RESET); \
- }while(0) /* 闪光灯控制信号 */
- /* 图像垂直翻转使能 1;使能 0:失能(使用ATK-OV2640模组版本必须使能) */
- #define Image_FlipVer 1
- /* OV2640的ID和访问地址 */
- #define OV2640_MID 0x7FA2
- #define OV2640_PID 0x2642
- #define OV2640_ADDR 0x60 /* OV2640的IIC地址 */
复制代码OV2640其他相关寄存器定义较多,这里不过多介绍,请参考例程源码。下面开始介绍ov2640.c文件,首先是OV2640初始化函数,其定义如下: - /**
- *@brief OV2640 初始化
- *@param 无
- *@retval 0, 成功; 1, 失败;
- */
- uint8_t ov2640_init(void)
- {
- uint16_t i = 0;
- uint16_t reg;
- GPIO_InitTypeDef gpio_init_struct;
- OV_PWDN_GPIO_CLK_ENABLE(); /* 使能OV_PWDN脚时钟 */
- OV_RESET_GPIO_CLK_ENABLE(); /* 使能OV_RESET脚时钟 */
- gpio_init_struct.Pin = OV_PWDN_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
- HAL_GPIO_Init(OV_PWDN_GPIO_PORT, &gpio_init_struct); /* 初始化OV_PWDN引脚 */
- gpio_init_struct.Pin = OV_RESET_GPIO_PIN;
- HAL_GPIO_Init(OV_RESET_GPIO_PORT, &gpio_init_struct);/* 初始化OV_RESET引脚 */
- OV2640_PWDN(0); /* POWER ON */
- delay_ms(10);
- OV2640_RST(0); /* 必须先拉低OV2640的RST脚,再上电 */
- delay_ms(20);
- OV2640_RST(1); /* 结束复位 */
- delay_ms(20);
-
- sccb_init(); /* 初始化SCCB 的IO口 */
- delay_ms(5);
- ov2640_write_reg(OV2640_DSP_RA_DLMT, 0x01); /* 操作sensor寄存器 */
- ov2640_write_reg(OV2640_SENSOR_COM7, 0x80); /* 软复位OV2640 */
- delay_ms(50);
- reg=ov2640_read_reg(OV2640_SENSOR_MIDH); /* 读取厂家ID 高八位 */
- reg<<= 8;
- reg|=ov2640_read_reg(OV2640_SENSOR_MIDL); /* 读取厂家ID 低八位 */
- if (reg != OV2640_MID) /* ID 是否正常 */
- {
- printf("MID:%d\r\n", reg);
- return 1; /* 失败 */
- }
-
- reg=ov2640_read_reg(OV2640_SENSOR_PIDH); /* 读取厂家ID 高八位 */
- reg<<= 8;
- reg|=ov2640_read_reg(OV2640_SENSOR_PIDL); /* 读取厂家ID 低八位 */
- if (reg != OV2640_PID) /* ID是否正常 */
- {
- printf("HID:%d\r\n", reg);
- return 1; /* 失败 */
- }
- /* 初始化 OV2640 */
- for (i = 0; i < sizeof(ov2640_uxga_init_reg_tbl) / 2; i++)
- {
- ov2640_write_reg(ov2640_uxga_init_reg_tbl[0],
- ov2640_uxga_init_reg_tbl[1]);
- }
- return 0; /* OV2640初始化完成 */
- }
复制代码该函数除了初始化OV2640相关的IO口,最主要的是完成OV2640的寄存器序列初始化。OV2640的寄存器很多,配置繁琐,通过厂家提供的参考配置序列可以更方便的完成OV2640寄存器初始化,配置序列存放在上面介绍的ov2640_uxga_init_reg_tbl和ov2640_svag_init_reg_tb1这个两个数组里面。
另外,在ov2640.c里面,还有几个函数比较重要,这里不贴代码了,只介绍功能: ov2640_outsize_set函数,用于设置图像输出大小; ov2640_window_set函数,用于设置传感器输出窗口; ov2640_image_win_set函数,用于设置图像开窗大小; ov2640_imagesize_set函数,用于设置图像尺寸大小;
其中,ov2640_outsize_set和ov2640_image_win_set函数就是前面在47.1.1节所介绍的3个窗口的设置,他们共同决定了图像的输出。
其他的函数就不列出来了,请大家查看源码。
3. TIMER驱动代码TIMER驱动代码我们主要就是用到定时器6,在定时器6更新中断回调函数中打印帧率,更新中断回调函数定义如下: - /**
- *@brief 定时器更新中断回调函数
- *@param htim:定时器句柄指针
- *@retval 无
- */
- voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- if (htim == (&g_timx_handle))
- {
- printf("frame:%d\r\n", g_ov_frame);
- g_ov_frame = 0;
- }
- }
复制代码g_ov_frame变量在dcmi.c中被定义,用于存放帧率。TIMER的其他驱动代码在介绍定时器的时候已经介绍过,请看例程源码。
4. USART2驱动代码这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。USART2驱动源码包括两个文件:usart2.c和usart2.h。
usart2.h头文件对usart2的IO口做了宏定义,具体如下: - /*****************************************************************************/
- /* 串口2 引脚 定义 */
- #define USART2_TX_GPIO_PORT GPIOA
- #define USART2_TX_GPIO_PIN GPIO_PIN_2
- #define USART2_TX_GPIO_AF GPIO_AF7_USART2 /* AF功能选择 */
- /* PA口时钟使能 */
- #define USART2_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
- #define USART2_RX_GPIO_PORT GPIOA
- #define USART2_RX_GPIO_PIN GPIO_PIN_3
- #define USART2_RX_GPIO_AF GPIO_AF7_USART2 /* AF功能选择 */
- /* PA口时钟使能 */
- #define USART2_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
- /*****************************************************************************/
复制代码PA2和PA3复用为串口2的发送和接收引脚。
下面介绍usart2.c文件的串口2初始化函数,其定义如下: - /**
- *@brief 串口2初始化函数
- *@note 注意:串口2的时钟源频率在sys已经设置过了
- *@param baudrate : 波特率, 根据自己需要设置波特率值
- */
- void usart2_init(uint32_t baudrate)
- {
- GPIO_InitTypeDef gpio_init_struct;
- _USART2_UX_CLK_ENABLE(); /* USART2时钟使能 */
- USART2_TX_GPIO_CLK_ENABLE(); /* 串口TX脚时钟使能 */
- USART2_RX_GPIO_CLK_ENABLE(); /* 串口RX脚时钟使能 */
- gpio_init_struct.Pin = USART2_TX_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
- gpio_init_struct.Alternate = USART2_TX_GPIO_AF; /* 复用为USART2 */
- HAL_GPIO_Init(USART2_TX_GPIO_PORT, &gpio_init_struct); /* 初始化串口TX引脚 */
- gpio_init_struct.Pin = USART2_RX_GPIO_PIN;
- gpio_init_struct.Alternate = USART2_RX_GPIO_AF; /* 复用为USART2 */
- HAL_GPIO_Init(USART2_RX_GPIO_PORT, &gpio_init_struct); /* 初始化串口RX引脚 */
- g_uart2_handle.Instance = USART2; /* USART2 */
- g_uart2_handle.Init.BaudRate = baudrate; /* 波特率 */
- g_uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
- g_uart2_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
- g_uart2_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
- g_uart2_handle.Init.HwFlowCtl =UART_HWCONTROL_NONE; /* 无硬件流控 */
- g_uart2_handle.Init.Mode = UART_MODE_TX; /* 发模式 */
- HAL_UART_Init(&g_uart2_handle); /* 使能USART2 */
- }
复制代码这里需要注意的是,串口2的时钟源频率在sys_stm32_clock_init函数中已经设置了。其他的代码基本usart_init函数是一样的。
5. main.c代码main.c前面定义了一些变量和数组,具体如下: - uint8_t g_ov_mode = 0; /* bit0: 0, RGB565模式; 1, JPEG模式 */
- #define jpeg_buf_size 29*1024 /* 定义JPEG数据缓存jpeg_buf的大小(*4字节) */
- #define jpeg_line_size 1*1024 /* 定义DMA接收数据时,一行数据的最大值 */
- __ALIGNED(4) uint32_t g_jpeg_data_buf[jpeg_buf_size]; /* JPEG数据缓存buf */
- /* JPEG数据 DMA双缓存buf */
- __ALIGNED(4) uint32_t g_dcmi_line_buf[2][jpeg_line_size];
- volatile uint32_t g_jpeg_data_len = 0; /* buf中的JPEG有效数据长度 */
- /**
- * 0,数据没有采集完;
- * 1,数据采集完了,但是还没处理;
- * 2,数据已经处理完成了,可以开始下一帧接收
- */
- volatile uint8_t g_jpeg_data_ok = 0; /* JPEG数据采集完成标志 */
- /* JPEG尺寸支持列表 */
- const uint16_t jpeg_img_size_tbl[][2] =
- {
- 160, 120, /* QQVGA */
- 176, 144, /* QCIF */
- 320, 240, /* QVGA */
- 400,240, /* WGVGA */
- 352,288, /* CIF */
- 640, 480, /* VGA */
- 800, 600, /* SVGA */
- 1024, 768, /* XGA */
- 1280, 800, /* WXGA */
- 1280, 960, /* XVGA */
- 1440, 900, /* WXGA+ */
- 1280, 1024, /* SXGA */
- 1600, 1200, /* UXGA */
- };
- const char *EFFECTS_TBL[7] = {"Normal", "Negative", "B&W", "Redish",
- "Greenish", "Bluish", "Antique"}; /* 7种特效 */
- const char *JPEG_SIZE_TBL[13] = {"QQVGA", "QCIF", "QVGA", "WGVGA", "CIF",
- "VGA", "SVGA", "XGA", "WXGA", "SVGA", "WXGA+",
- "SXGA", "UXGA"}; /* JPEG图片 13种尺寸 */
复制代码其中,定义的g_jpeg_data_buf数组大小为116KB字节,用来存储JPEG数据。
在main.c里面,总共有5个函数,我们接下来分别介绍。首先是处理JPEG数据函数,其定义如下: - /**
- *@brief 处理JPEG数据
- * @ntoe 在DCMI_IRQHandler中断服务函数里面被调用
- * 当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
- *
- *@param 无
- *@retval 无
- */
- voidjpeg_data_process(void)
- {
- uint16_t i;
- uint16_t rlen; /* 剩余数据长度 */
- uint32_t *pbuf;
- if (g_ov_mode) /* 只有在JPEG格式下,才需要做处理. */
- {
- if (g_jpeg_data_ok == 0) /* jpeg数据还未采集完? */
- {
- __HAL_DMA_DISABLE(&g_dma_dcmi_handle); /* 关闭DMA */
- while(DMA2_Stream1->CR & 0x01); /* 等待DMA2_Stream1可配置 */
- /* 得到剩余数据长度 */
- rlen =jpeg_line_size -__HAL_DMA_GET_COUNTER(&g_dma_dcmi_handle);
- pbuf =g_jpeg_data_buf + g_jpeg_data_len;/* 偏移到有效数据末尾,继续添加 */
- if (DMA2_Stream1->CR & (1 << 19))
- {
- for (i = 0; i < rlen; i++)
- {
- pbuf = g_dcmi_line_buf[1]; /* 读取buf1里面的剩余数据 */
- }
- }
- else
- {
- for (i = 0; i < rlen; i++)
- {
- pbuf = g_dcmi_line_buf[0]; /* 读取buf0里面的剩余数据 */
- }
- }
- g_jpeg_data_len += rlen; /* 加上剩余长度 */
- g_jpeg_data_ok = 1; /* 标记JPEG数据采集完成,等待其他函数处理 */
- }
- if (g_jpeg_data_ok == 2) /* 上一次的jpeg数据已经被处理了 */
- {
- /* 传输长度为jpeg_buf_size*4字节 */
- __HAL_DMA_SET_COUNTER(&g_dma_dcmi_handle, jpeg_line_size);
- __HAL_DMA_ENABLE(&g_dma_dcmi_handle); /* 重新传输 */
- g_jpeg_data_ok = 0; /* 标记数据未采集 */
- g_jpeg_data_len = 0; /* 数据重新开始 */
- }
- }
- else
- {
- lcd_set_cursor(0, 0);
- lcd_write_ram_prepare(); /* 开始写入GRAM */
- }
- }
复制代码该函数用于处理JPEG数据的接收,在DCMI_IRQHandler函数(在dcmi.c里面)里面被调用,它与jpeg_dcmi_rx_callback函数和jpeg_test函数共同控制JPEG的数据传送。JPEG数据的接收,采用DMA双缓冲机制,缓冲数组为:dcmi_line_buf(u32类型,RGB屏接收RGB565数据时,也是用这个数组);数组大小为:jpeg_line_size,我们定义的是1*1024,即数组大小为4K字节(数组大小不能小于存储摄像头一行输出数据的大小);JPEG数据接收处理流程如图47.3.3.1所示: 图47.3.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数据的采集。
接下来介绍的是JPEG数据接收回调函数,其定义如下: - /**
- *@brief JPEG数据接收回调函数
- * @ntoe 在DMA1_Stream1_IRQHandler中断服务函数里面被调用
- *
- *@param 无
- *@retval 无
- */
- voidjpeg_dcmi_rx_callback(void)
- {
- uint16_t i;
- volatile uint32_t *pbuf;
- pbuf =g_jpeg_data_buf + g_jpeg_data_len; /* 偏移到有效数据末尾 */
- if (DMA2_Stream1->CR & (1 << 19)) /* buf0已满,正常处理buf1 */
- {
- for (i = 0; i < jpeg_line_size; i++)
- {
- pbuf = g_dcmi_line_buf[0]; /* 读取buf0里面的数据 */
- }
-
- g_jpeg_data_len += jpeg_line_size; /* 偏移 */
- }
- else /* buf1已满,正常处理buf0 */
- {
- for (i = 0; i < jpeg_line_size; i++)
- {
- pbuf = g_dcmi_line_buf[1]; /* 读取buf1里面的数据 */
- }
-
- g_jpeg_data_len += jpeg_line_size; /* 偏移 */
- }
- }
复制代码该函数是jpeg数据接收的主要函数,通过判断DMA2_Stream1->CR寄存器,读取不同buf里面的数据,存储到g_jpeg_data_buf里面。该函数由DMA的传输完成中断服务函数:DMA2_Stream1_IRQHandler调用。
接下来介绍的是JPEG测试函数,其定义如下: - /**
- *@brief JPEG测试
- * @ntoe JPEG数据,通过串口2发送给电脑.
- *
- *@param 无
- *@retval 无
- */
- void jpeg_test(void)
- {
- uint32_t i, jpgstart, jpglen;
- uint8_t *p;
- uint8_t key, headok = 0;
- uint8_t effect = 0, saturation = 2, contrast = 2;
- uint8_t size = 2; /* 默认是QVGA 320*240尺寸 */
- uint8_t msgbuf[15]; /* 消息缓存区 */
- lcd_clear(WHITE);
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "OV2640 JPEGMode", RED);
- lcd_show_string(30, 100, 200, 16, 16, "KEY0:Contrast", RED); /* 对比度 */
- lcd_show_string(30, 120, 200, 16, 16, "KEY1:Saturation", RED);/* 色彩饱和度 */
- lcd_show_string(30, 140, 200, 16, 16, "KEY2:Effect", RED); /* 特效 */
- lcd_show_string(30, 160, 200, 16, 16, "KEY_UP:Size", RED); /* 分辨率设置 */
- sprintf((char *)msgbuf, "JPEGSize:%s",JPEG_SIZE_TBL[size]);
- /*显示当前JPEG分辨率 */
- lcd_show_string(30, 180, 200, 16, 16, (char*)msgbuf, RED);
- ov2640_jpeg_mode(); /* JPEG模式 */
- dcmi_init(); /* DCMI配置 */
- dcmi_rx_callback = jpeg_dcmi_rx_callback; /* JPEG接收数据回调函数 */
- dcmi_dma_init((uint32_t)&g_dcmi_line_buf[0], (uint32_t)&g_dcmi_line_buf[1],
- jpeg_line_size, DMA_MDATAALIGN_WORD, DMA_MINC_ENABLE); /* DCMI DMA配置 */
- /* 设置输出尺寸 */
- ov2640_outsize_set(jpeg_img_size_tbl[size][0], jpeg_img_size_tbl[size][1]);
- dcmi_start(); /* 启动传输 */
- while (1)
- {
- if (g_jpeg_data_ok == 1) /* 已经采集完一帧图像了 */
- {
- p = (uint8_t *)g_jpeg_data_buf;
- /* 打印数据长度 */
- printf("g_jpeg_data_len:%d\r\n", g_jpeg_data_len * 4);
- /* 提示正在传输数据 */
- lcd_show_string(30, 210, 210, 16, 16, "SendingJPEG data...", RED);
- jpglen = 0; /* 设置jpg文件大小为0 */
- headok = 0; /* 清除jpg头标记 */
- /* 查找0XFF,0XD8和0XFF,0XD9,获取jpg文件大小 */
- for (i = 0; i < g_jpeg_data_len * 4; i++)
- {
- if ((p == 0XFF) && (p[i + 1] == 0XD8)) /* 找到FF D8 */
- {
- jpgstart = i;
- headok = 1; /* 标记找到jpg头(FF D8) */
- }
- /* 找到头以后,再找FF D9 */
- if ((p == 0XFF) && (p[i + 1] == 0XD9) && headok)
- {
- jpglen = i - jpgstart + 2;
- break;
- }
- }
- if (jpglen) /* 正常的jpeg数据 */
- {
- p += jpgstart; /* 偏移到0XFF,0XD8处 */
- for (i = 0; i < jpglen; i++) /* 发送整个jpg文件 */
- {
- USART2->DR = p;
- while ((USART2->SR & 0X40) == 0); /* 循环发送,直到发送完毕 */
- key = key_scan(0);
- if (key)break;
- }
- }
- if (key != 0) /* 有按键按下,需要处理 */
- {
- /* 提示退出数据传输 */
- lcd_show_string(30, 210, 210, 16, 16, "QuitSending data ", RED);
- switch (key)
- {
- case KEY0_PRES: /* 对比度设置 */
- contrast++;
- if( contrast > 4)contrast = 0;
- ov2640_contrast(contrast);
- sprintf((char*)msgbuf, "Constrast:%d", (signed char)
- contrast - 2);
- break;
- case KEY1_PRES: /* 饱和度设置 */
- saturation++;
- if (saturation > 4)saturation = 0;
- ov2640_color_saturation(saturation);
- sprintf((char *)msgbuf, "Saturation:%d", saturation);
- break;
-
- case KEY2_PRES: /* 特效设置 */
- effect++;
- if (effect > 6) effect = 0;
- ov2640_special_effects(effect);
- sprintf((char *)msgbuf, "Effect:%s", EFFECTS_TBL[effect]);
- break;
-
- case WKUP_PRES: /* 特效设置 */
- size++;
- /* 最大只支持WXGA的jpeg数据保存,再大分辨率就不够内存用了 */
- if (size > 12)size = 0;
- ov2640_outsize_set(jpeg_img_size_tbl[size][0],
- jpeg_img_size_tbl[size][1]); /* 设置输出尺寸 */
- sprintf((char *)msgbuf, "JPEGSize:%s",
- JPEG_SIZE_TBL[size]);
- break;
- default : break;
- }
- lcd_fill(30, 180, 239, 190 + 16, WHITE);
- /* 显示提示内容 */
- lcd_show_string(30, 180, 210, 16, 16, (char*)msgbuf, RED);
- delay_ms(800);
- }
- else
- {
- /* 提示传输结束设置 */
- lcd_show_string(30, 210, 210, 16, 16,"Send datacomplete!!", RED);
- }
-
- g_jpeg_data_ok = 2; /* 标记jpeg数据处理完了,可以让DMA去采集下一帧了. */
- }
- }
- }
复制代码该函数将OV2640设置为JPEG模式,并开启持续自动对焦、实现OV2640的JPEG数据接收,通过串口2发送给上位机软件。
接下来介绍的是RGB565测试函数,其定义如下: - /**
- *@brief RGB565测试
- * @ntoe RGB数据直接显示在LCD上面
- *
- *@param 无
- *@retval 无
- */
- void rgb565_test(void)
- {
- uint8_t key;
- uint8_t effect = 0, saturation = 3, contrast = 2;
- uint8_t scale = 1; /* 默认是全尺寸缩放 */
- uint8_t msgbuf[15]; /* 消息缓存区 */
- lcd_clear(WHITE);
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "OV2640RGB565 Mode", RED);
- lcd_show_string(30, 100, 200, 16, 16, "KEY0:Contrast", RED); /* 对比度 */
- /*执行自动对焦*/
- lcd_show_string(30, 120, 200, 16, 16, "KEY1:Saturation", RED);
- lcd_show_string(30, 140, 200, 16, 16, "KEY2:Effects", RED); /* 特效 */
- /* 1:1尺寸(显示真实尺寸)/全尺寸缩放 */
- lcd_show_string(30, 160, 200, 16, 16, "KEY_UP:FullSize/Scale", RED);
- ov2640_rgb565_mode(); /* RGB565模式 */
- dcmi_init(); /* DCMI配置 */
- dcmi_dma_init((uint32_t)&LCD->LCD_RAM, 0, 1,DMA_MDATAALIGN_HALFWORD,
- DMA_MINC_DISABLE); /* DCMI DMA配置,MCU屏,竖屏 */
- ov2640_outsize_set(lcddev.width, lcddev.height); /* 满屏缩放显示 */
- dcmi_start(); /* 启动传输 */
-
- while (1)
- {
- key = key_scan(0);
- if (key)
- {
- dcmi_stop(); /* 停止显示 */
- switch (key)
- {
- case KEY0_PRES: /* 对比度设置 */
- contrast++;
- if (contrast > 4)contrast = 0;
- ov2640_contrast(contrast);
- sprintf((char*)msgbuf, "Contrast:%d", (signed char)contrast-2);
- break;
- case KEY1_PRES: /* 饱和度设置 */
- saturation++;
- if (saturation > 4)saturation = 0;
- ov2640_color_saturation(saturation); /* 饱和度设置 */
- sprintf((char *)msgbuf, "Saturation:%d", (signed char)
- saturation - 2);
- break;
-
- case KEY2_PRES: /* 特效设置 */
- effect++;
- if (effect > 6)effect = 0;
- ov2640_special_effects(effect); /* 设置特效 */
- sprintf((char *)msgbuf, "%s", EFFECTS_TBL[effect]);
- break;
- case WKUP_PRES: /* 1:1尺寸(显示真实尺寸)/缩放 */
- scale = !scale;
- if (scale == 0)
- {
- ov2640_image_win_set((1600 - lcddev.width) / 2, (1200 –
- lcddev.height) / 2, lcddev.width, lcddev.height);
- ov2640_outsize_set(lcddev.width, lcddev.height);
- sprintf((char *)msgbuf, "Full Size1:1");
- }
- else
- {
- ov2640_image_win_set(0, 0, 1600, 1200);
- ov2640_outsize_set(lcddev.width, lcddev.height);
- sprintf((char *)msgbuf, "Scale");
- }
-
- break;
- default : break;
- }
- /* 显示提示内容 */
- lcd_show_string(30, 50, 210, 16, 16, (char*)msgbuf, RED);
- delay_ms(800);
- dcmi_start(); /* 重新开始传输 */
- }
- delay_ms(10);
- }
- }
复制代码该函数将OV2640设置为RGB565模式,并将接收到的数据,传送给LCD。当使用MCU屏的时候,完全由硬件DMA传输给LCD,CPU不用处理。
接下来介绍的是main函数,其定义如下: - int main(void)
- {
- uint8_t key, t;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- usmart_dev.init(84); /* 初始化USMART */
- usart2_init(912600); /* 初始化串口2波特率为912600 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- /*10Khz计数,1秒钟中断一次,用于统计帧率*/
- btim_timx_int_init(10000 - 1, 8400 - 1);
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "OV2640TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- while (ov2640_init()) /* 初始化OV2640 */
- {
- lcd_show_string(30, 130, 240, 16, 16, "OV2640ERROR", RED);
- delay_ms(200);
- lcd_fill(30, 130, 239, 170, WHITE);
- delay_ms(200);
- LED0_TOGGLE();
- }
- lcd_show_string(30, 130, 200, 16, 16, "OV2640OK", RED);
- ov2640_flash_intctrl(); /* 闪光灯控制 */
- while (1)
- {
- key = key_scan(0);
- if (key == KEY0_PRES)
- {
- g_ov_mode = 0; /* RGB565模式 */
- break;
- }
- else if (key == KEY1_PRES)
- {
- g_ov_mode = 1; /* JPEG模式 */
- break;
- }
- t++;
- if (t == 100)lcd_show_string(30, 150, 230, 16, 16,
- "KEY0:RGB565 KEY1:JPEG", RED); /* 闪烁显示提示信息 */
- if (t == 200)
- {
- lcd_fill(30, 150, 210, 150 + 16, WHITE);
- t = 0;
- LED0_TOGGLE();
- }
- delay_ms(5);
- }
- if (g_ov_mode == 1)
- {
- jpeg_test();
- }
- else
- {
- rgb565_test();
- }
- }
复制代码该函数完成对各相关硬件的初始化,然后检测OV2640,最后通过按键选择来调用jpeg_test还是rgb565_test,实现JPEG测试和RGB565测试。
前面提到,我们要用USMART来设置摄像头的参数,我们只需要在usmart_nametab里面添加ov2640_write_reg和ov2640_read_reg等相关函数,就可以轻松调试摄像头了。
47.4 下载验证将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图47.4.1所示: 上图是摄像头已经正确的插上开发板上的运行结果,如果没有插上摄像头,会有错误的报错信息。在OV2640初始化成功后,屏幕提示选择测试模式,此时我们可以按KEY0,进入RGB565模式测试,也可以按KEY1,进入JPEG模式测试。
当按KEY0后,选择RGB565模式,LCD满屏显示压缩放后的图像(有变形),如图47.4.1所示: 此时,可以按KEY_UP切换为1:1显示(不变形)。同时还可以通过KEY0按键,可以设置对比度;通过KEY1按键,可以设置饱和度;通过KEY2按键,可以设置特效。
当按KEY1后,选择JPEG模式,此时屏幕显示JPEG数据传输进程,如图47.4.2所示: 默认条件下,图像分辨率是QVGA (320*240)的,硬件上:我们需要用一根杜邦线连接开发板的PA2排针到RXD排针端。
打开上位机软件:XCAM V1.3.exe(路径:光盘à6,软件资料à软件à串口&网络摄像头软件à XCAM V1.3.exe),选择正确的串口,然后波特率设置为921600,打开即可收到下位机传过来的图片了,如图47.4.3所示: 图47.4.3 XCAM V1.3.exe软件接收并显示JPEG图片 我们可以通过KEY_UP设置输出图像的尺寸(QQVGA~ WXGA)。同时还可以通过KEY0按键,可以设置对比度;通过KEY1按键,可以设置饱和度;通过KEY2按键,可以设置特效。
你还可以在串口(开发板的串口1),通过USMART调用ov2640_write_reg等函数,来设置OV2640的各寄存器,达到调试测试OV2640的目的,如图47.4.4所示: 从图47.4.3可以看出,在QVGA模式下,帧率为5帧,传输速度31.1KB/s。从图47.4.4可以看出在UXGA模式,帧率为15帧,并且可以通过USMART调试OV2640。 |