基于STM32F407摄像头实验(有代码)

1.OV2640 简介

OV2640 是 OV(OmniVision)公司生产的一颗 1/4 寸的 CMOS UXGA(16321232)图像 传感器。该传感器体积小、工作电压低,提供单片 UXGA 摄像头和影像处理器的所有功能。通 过 SCCB 总线控制,可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率 8/10 位影像 数据。该产品 UXGA 图像最高达到 15 帧/秒(SVGA 可达 30 帧,CIF可达 60 帧)。用户可以完 全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、对比 度、色度等都可以通过 SCCB 接口编程。OmmiVision 图像传感器应用独有的传感器技术,通 过减少或消除光学或电子缺陷如固定图案噪声、拖尾、浮散等,提高图像质量,得到清晰的稳 定的彩色图像。 OV2640 的特点有:
 高灵敏度、低电压适合嵌入式应用
 标准的 SCCB 接口,兼容 IIC 接口
 支持 RawRGB、RGB(RGB565/RGB555)、GRB422、YUV(422/420)YCbCr(422) 输出格式
 支持 UXGA、SXGA、SVGA 以及按比例缩小到从 SXGA 到 40
30 的任何尺寸
 支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校 准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。
 支持闪光灯
 支持图像缩放、平移和窗口设置
 支持图像压缩,即可输出 JPEG 图像数据  自带嵌入式微处理器
其功能框图如图:
在这里插入图片描述
OV2640 传感器包括如下一些功能模块。
1.感光整列(Image Array) OV2640 总共有 16321232 个像素,最大输出尺寸为 UXGA(16001200), 即 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 信号的过程,并控制一些图像质量:  边缘锐化(二维高通滤波器)  颜色空间转换(原始信号到 RGB 或者 YUV/YCbYCr)  RGB 色彩矩阵以消除串扰  色相和饱和度的控制  黑/白点补偿  降噪  镜头补偿  可编程的伽玛  十位到八位数据转换
5.输出格式模块(Output Formatter) 该模块按设定优先级控制图像的所有输出数据及其格式。
6.压缩引擎(Compression Engine) 压缩引擎框图如图 所示:
在这里插入图片描述
从图可以看出,压缩引擎主要包括三部分: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 位连接方式,如图 所示:
在这里插入图片描述
OV2640 的寄存器通过 SCCB 时序访问并设置,SCCB 时序和 IIC 时序十分类似, 接下来,我们介绍一下 OV2640 的传感器窗口设置、图像尺寸设置、图像窗口设置和图像 输出大小设置,这几个设置与我们的正常使用密切相关,有必要了解一下。其中,除了传感器 窗口设置是直接针对传感器阵列的设置,其他都是 DSP 部分的设置了,接下来我们一个个介绍。

传感器窗口设置,该功能允许用户设置整个传感器区域(16321220)的感兴趣部分,也就 是在传感器里面开窗,开窗范围从 22~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 的调试经验,以及相关文档总结出来的,不保证百分比正确,如有错误,还请 大家指正。 以上几个设置,光看文字可能不太清楚,这里我们画一个简图有助于大家理解,如图所示:
在这里插入图片描述
上图,最终红色框所示的图像输出大小,才是 OV2640 输出给外部的图像尺寸,也就是显 示在 LCD 上面的图像大小。当图像输出大小与图像窗口不等时,会进行缩放处理,在 LCD 上 面看到的图像将会变形。 最后,我们介绍一下 OV2640 的图像数据输出格式。首先我们简单介绍一些定义: UXGA,即分辨率位 16001200 的输出格式,类似的还有:SXGA(12801024)、 WXGA+(1440900)、XVGA(1280960)、WXGA(1280800)、XGA(1024768)、SVGA(800600)、 VGA(640480)、CIF(352288)、WQVGA(400240)、QCIF(176144)和 QQVGA(160120)等。 PCLK,即像素时钟,一个 PCLK 时钟,输出一个像素(或半个像素)。 VSYNC,即帧同步信号。 HREF /HSYNC,即行同步信号。 OV2640 的图像数据输出(通过 Y[9:0])就是在 PCLK,VSYNC 和 HREF/ HSYNC 的控制 下进行的。首先看看行输出时序,
在这里插入图片描述
从上图可以看出,图像数据在 HREF 为高的时候输出,当 HREF 变高后,每一个 PCLK 时 钟,输出一个8位/10位数据。我们采用8位接口,所以每个PCLK输出1个字节,且在RGB/YUV 输出格式下,每个 tp=2 个 Tpclk,如果是 Raw 格式,则一个 tp=1 个 Tpclk。比如我们采用 UXGA 时序,RGB565 格式输出,每 2 个字节组成一个像素的颜色(高低字节顺序可通过 0XDA 寄存 器设置),这样每行输出总共有 16002 个 PCLK 周期,输出 16002 个字节。

再来看看帧时序(UXGA 模式)
在这里插入图片描述
上图清楚的表示了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 我们就介绍到这。

2.STM32F4 DCMI 接口简介

STM32F4 自带了一个数字摄像头(DCMI)接口,该接口是一个同步并行接口,能够接收 外部 8 位、10 位、12 位或 14 位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据格 式:YCbCr4:2:2/RGB565 逐行视频和压缩数据 (JPEG)。

STM32F4 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]),用于接摄像头的数据输出,接 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(垂直同步)信号硬件同步,或 者通 过数据流中嵌入的同步码同步。

STM32F4 DCMI 接口的框图如图 :
在这里插入图片描述
DCMI 接口的数据与 PIXCLK(即 PCLK)保持同步,并根据像素时钟的极性在像素时钟 上升沿/下降沿发生变化。HSYNC(HREF)信号指示行的开始/结束,VSYNC 信号指示帧的开 始/结束。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 位字中的排布如表:

在这里插入图片描述
从表中,STM32F4 的 DCMI 接口,接收的数据是低字节在前,高字节在后 的,所以,要求摄像头输出数据也是低字节在前,高字节在后才可以,否则就还得程序上处理 字节顺序,会比较麻烦。 DCMI 接口支持 DMA 传输,当 DCMI_CR 寄存器中的 CAPTURE 位置 1 时,激活 DMA 接口。摄像头接口每次在其寄存器中收到一个完整的32位数据块时,都将触发一个DMA请求。

DCMI 接口支持两种同步方式:内嵌码同步和硬件(HSYNC 和 VSYNC)同步。我们简单 介绍下硬件同步,详细介绍请参考《STM32F4xx 中文数据手册》第 13.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 接口将等待新的数据帧开始。

摄像头如图所示:
在这里插入图片描述
原理图:

在这里插入图片描述

从上图可以看出,ALIENTEK OV2640 摄像头模块自带了有源晶振,用于产生 24M 时钟作 为 OV2640 的 XVCLK 输入。同时自带了稳压芯片,用于提供 OV2640 稳定的 2.8V 和 1.3V 工 作电压,模块通过一个 2*9 的双排排针(P1)与外部通信,与外部的通信信号如表 :
在这里插入图片描述

,我们将 OV2640 默认配置为 UXGA 输出,也就是 1600*1200 的分辨率,输出信号设 置为:VSYNC 高电平有效,HREF 高电平有效,输出数据在 PCLK 的下降沿输出(即上升沿的 时候,MCU 才可以采集)。这样,STM32F4 的 DCMI 接口就必须设置为:VSYNC 低电平有效、 HSYNC 低电平有效和 PIXCLK 上升沿有效,这些设置都是通过 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)都设置好了之后,才设置!!

3. DCMI 驱动 OV2640 的步骤

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 的大把寄存器进行配置了,这里我们配置成 UXGA 输出。 然后,可以根据我们的需要,设置成 RGB565 输出模式,还是 JPEG 输出模式。 学习到这里,怎么使能相关 IO 口时钟以及配置相关模式这里我们就不再讲解,大家可以 打开我们实验源码查看,OV2640 的初始化配置以及相关操作函数在我们实验的 ov2640.c 源文 件中,其中初始化在 OV2640_Init 函数中,大家可以打开看看相关步骤。

2)配置相关引脚的模式和复用功能(AF13),使能时钟。 OV2640 配置好之后,再设置 DCMI 接口与摄像头模块连接的 IO 口,使能 IO 和 DCMI 时钟,然后设置相关 IO 口为复用功能模式,复用功能选择 AF13(DCMI 复用)。 使能 DCMI 时钟的方法为:

RCC _AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE);//使能 DCMI 时钟

关于相关IO 口设置复用功能的方法之前已经多次讲解,这里我们就只贴出关键代码片段:

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能输出 

接下来我们就是设置复用映射,方法为:

GPIO_PinAFConfig(GPIOA,GPIO_PinSource4,GPIO_AF_DCMI);//PA4,DCMI_HSYNC 
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_DCMI); //PA6, DCMI_PCLK   
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_DCMI); //PB7, DCMI_VSYNC  ……//省略部分 IO 映射代码 
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_DCMI); //PB6,DCMI_D5  
GPIO_PinAFConfig(GPIOE,GPIO_PinSource5,GPIO_AF_DCMI); //PE5,DCMI_D6 
GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI); //PE6,DCMI_D7 

3)配置 DCMI 相关设置。 这一步,主要通过 DCMI_CR 寄存器设置,包括 VSPOL/HSPOL/PCKPOL/数据宽度等重要 参数,都在这一步设置,同时我们也开启帧中断,编写 DCMI 中断服务函数,方便进行数据处 理(尤其是 JPEG 模式的时候)。不过对于 CAPTURE 位,我们等待 DMA 配置好之后再设置, 另外对于 OV2640 输出的 JPEG 数据,我们也不使用 DCMI 的 JPEG 数据模式(实测设置不设 置都一样),而是采用正常模式,直接采集。 DCMI 相关寄存器的配置是通过函数 DCMI_Init 来实现的。接下来我们看看函数申明:

void DCMI_Init(DCMI_InitTypeDef* DCMI_InitStruct); 

同样,我们来看看结构体 DCMI_InitTypeDef 的定义:

typedef struct {   
uint16_t DCMI_CaptureMode;        
uint16_t DCMI_SynchroMode;      
uint16_t DCMI_PCKPolarity;        
uint16_t DCMI_VSPolarity;         
uint16_t DCMI_HSPolarity;         
uint16_t DCMI_CaptureRate;        
uint16_t DCMI_ExtendedDataMode;  
  } DCMI_InitTypeDef; 

结构体 DCMI_InitTypeDef 一共有 7 个成员变量,接下来我们来看看每个成员变量的含义:
第一个参数 DCMI_CaptureMode 是用来设置捕获模式为连续捕获模式还是快照模式。我们 实验采取的是连续捕获模式值 DCMI_CaptureMode_Continuous,也就是通过 DMA 连续传输数 据到目标存储区。
第二个参数 DCMI_SynchroMode 用来选择同步方式为硬件同步还是内嵌码同步。如果选择 硬件同步值 DCMI_SynchroMode_Hardware,那么数据捕获由 HSYNC/VSYNC 信号同步,如果 选择内嵌码同步方式值 DCMI_SynchroMode_Embedded,那么数据捕获由数据流中嵌入的同步 码同步。
第三个参数 DCMI_PCKPolarity 用来设置像素时钟极性为上升沿有效还是下降沿有效。我 们实验使用的是上升沿有效,所以值为 DCMI_PCKPolarity_Rising。
第四个参数 DCMI_VSPolarity 用来设置垂直同步极性 VSYNC 为低电平有效还是高电平有效。也就是 VSYNC 引脚上面,数据无效时的电平状态。我们设置为 VSYNC 低电平有效。所 以值为 DCMI_VSPolarity_Low。
第五个参数 DCMI_HSPolarity用来设置水平同步极性为高电平有效还是低电平有效,也就 是 HSYNC 引脚上面,数据无效时的电平状态。我们设置为 HSYNC 低电平有效。所以值为 DCMI_HSPolarity_Low。
第六个参数 DCMI_CaptureRate 用来设置帧捕获率。如果设置为值 DCMI_CaptureRate_All_Frame,也就是全帧捕获,设置为 DCMI_CaptureRate_1of2_Frame,也 就 2 帧捕获一帧,设置为 DCMI_CaptureRate_1of4_Frame,也就是 4 帧捕获一帧。 第七个参数 DCMI_ExtendedDataMode 用来设置扩展数据模式。可以设置为每个像素时钟 捕获 8 位,10 位,12 位以及 14 位数据。这里我们设置为 8 位值 DCMI_ExtendedDataMode_8b。 DCMI 初始化实例如下:


DCMI_InitTypeDef DCMI_InitStructure; 
DCMI_InitStructure.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;//连续模式 
DCMI_InitStructure.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;//全帧捕获 
DCMI_InitStructure.DCMI_ExtendedDataMode= DCMI_ExtendedDataMode_8b;//8 位格式   
DCMI_InitStructure.DCMI_HSPolarity= DCMI_HSPolarity_Low;//HSYNC 低电平有效 
DCMI_InitStructure.DCMI_PCKPolarity= DCMI_PCKPolarity_Rising;//PCLK 上升沿有效 
DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步 
DCMI_InitStructure.DCMI_VSPolarity=DCMI_VSPolarity_Low;//VSYNC 低电平有效 
DCMI_Init(&DCMI_InitStructure);//初始化 DCMI 4

4)配置 DMA。

本章采用连续模式采集,并将采集到的数据输出到 LCD(RGB565 模式)或内存(JPEG 模式),所以源地址都是 DCMI_DR,而目的地址可能是 LCD->RAM 或者 SRAM 的地址。DCMI 的 DMA 传输采用的是 DMA2 数据流 1 的通道 1 来实现的,关于 DMA 的介绍,请大家参考前 面的 DMA 实验章节。这里我们列出本章我们的 DMA 配置源码如下:


DMA_InitTypeDef  DMA_InitStructure; DMA_InitStructure.DMA_Channel = DMA_Channel_1;  //通道 1 DCMI 通道 
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址为:DCMI->DR 
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&LCD->LCD_RAM;//存储器 0 地址 
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式 
DMA_InitStructure.DMA_BufferSize = 1;//数据传输量  
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式 
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式 
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式  
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级 
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Enable; //FIFO 模式         
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;//使用全 FIFO  
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;//外设突发单次传输 
DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;  DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化 DMA Stream 

5)设置 OV2640 的图像输出大小,使能 DCMI 捕获。
图像输出大小设置,分两种情况:在 RGB565 模式下,我们根据 LCD 的尺寸,设置输出 图像大小,以实现全屏显示(图像可能因缩放而变形);在 JPEG 模式下,我们可以自由设置输 出图像大小(可不缩放);最后,开启 DCMI 捕获,即可正常工作了。开启 DCMI 捕获的方法 为:

 DCMI_CaptureCmd(ENABLE);//DCMI 捕获使能 

4.硬件设计

并可选择 RGB565 和 JPEG 两种输出格式,当使用 RGB565 时,输出图像将经过缩放处理 (完全由 OV2640 的 DSP 控制),显示在 LCD 上面。当使用 JPEG 数据输出的时候,我们将收 到的 JPEG 数据,通过串口 2(115200 波特率),送给电脑,并利用电脑端上位机软件,显示接 收到的图片,。 本章实验功能简介:开机后,初始化摄像头模块(OV2640),如果初始化成功,则提示 选择模式:RGB565 模式,或者 JPEG模式。KEY0 用于选择 RGB565 模式,KEY1 用于选择 JPEG 模式。 当使用 RGB565 时,输出图像(固定为:UXGA)将经过缩放处理(完全由 OV2640 的 DSP 控制),显示在 LCD 上面。我们可以通过 KEY_UP 按键选择:1:1 显示,即不缩放,图片不变 形,但是显示区域小(液晶分辨率大小),或者缩放显示,即将 1600*1200 的图像压缩到液晶分 辨率尺寸显示,图片变形,但是显示了整个图片内容。通过 KE0Y 按键,可以设置对比度;KEY1 按键,可以设置饱和度;KEY2 按键,可以设置特效。 当使用 JPEG 模式时,图像可以设置任意尺寸(QQVGA~UXGA),采集到的 JPEG 数据将 先存放到 STM32F4 的内存里面,每当采集到一帧数据,就会关闭 DMA 传输,然后将采集到的 数据发送到串口 2(此时可以通过上位机软件(串口摄像头.exe)接收,并显示图片),之后再 重新启动 DMA 传输。我们可以通过 KEY_UP 设置输出图片的尺寸(QQVGA~UXGA)。通过 KEY0 按键,可以设置对比度;KEY1 按键,可以设置饱和度;KEY2 按键,可以设置特效。 同时时可以通过串口 1,借助 USMART 设置/读取 OV2640 的寄存器,方便大家调试。DS0 指示程序运行状态,DS1 用于指示帧中断。 本实验用到的硬件资源有:
1) 指示灯 DS0 和 DS1
2) 4 个按键
3) 串口 1 和串口 2
4) TFTLCD 模块
5) OV2640 摄像头模块 这些资源,基本上都介绍过了,这里我们用到串口 2 来传输 JPEG 数据给上位机,其配置 同串口 1 几乎一模一样,支持串口 2 的时钟来自 APB1,频率为 42Mhz。

我们重点介绍下探索者 STM32F4 开发板的摄像头接口与 ALIENTEK OV2640 摄像头模块 的连接。在开发板的左下角的 2*9 的 P8 排座,是摄像头模块/OLED 模块共用接口,在第十七 章,我们曾简单介绍过这个接口。本章,我们只需要将 ALIENTEK OV2640 摄像头模块插入这 个接口即可,该接口与 STM32 的连接关系如图
在这里插入图片描述

5.软件设计

打开本章实验工程目录,我们在 HARDWARE 文件夹下新建了 OV2640、DCMI 和 USART2 三个文件夹。在 OV2640 文件夹下新建了 ov2640.c、sccb.c、ov2640.h、sccb.h、ov2640cfg.h 等 5 个文件,并同时在工程中将这个文件夹加入头文件包含路径。在 DCMI 文件夹新建了 dcmi.c 和dcmi.h两个文件,也将这个文件夹加入头文件包含路径。 在USART2文件夹下新建了:usart2.c 和 usart2.h 两个文件,同时将这个文件夹加入头文件包含路径。 DCMI 接口相关的库函数分布在 stm32f4xx_dcmi.c 文件以及对应的头文件 stm32f4xx_dcmi.h 中。我们工程引入了这两个文件。 本章总共新增了 9 个文件,代码比较多,我们就不一一列出了,仅挑几个重要的地方进行 讲解。首先,我们来看 ov2640.c 里面的 OV2640_Init 函数,该函数代码如下:

//初始化 OV2640  
//配置完以后,默认输出是 1600*1200 尺寸的图片!!  
//返回值:0,成功   其他,错误代码 
u8 OV2640_Init(void) {  
u16 i=0, reg;  //设置 IO              
GPIO_InitTypeDef  GPIO_InitStructure;     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);     //GPIOG9,GPIOG15 初始化设置     
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_15;//PG9,15 推挽输出     
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出     
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出     
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz     
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉     
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化    
OV2640_PWDN=0; delay_ms(10); //POWER ON   
OV2640_RST=0; delay_ms(10);  //复位 OV2640   
OV2640_RST=1; //结束复位     
SCCB_Init();      //初始化 SCCB 的 IO 口    
SCCB_WR_Reg(OV2640_DSP_RA_DLMT, 0x01); //操作 sensor 寄存器   
SCCB_WR_Reg(OV2640_SENSOR_COM7, 0x80); //软复位 OV2640  
delay_ms(50);   
reg=SCCB_RD_Reg(OV2640_SENSOR_MIDH); //读取厂家 ID 高八位  
reg<<=8;  
reg|=SCCB_RD_Reg(OV2640_SENSOR_MIDL); //读取厂家 ID 低八位  
if(reg!=OV2640_MID)  
{  
 printf("MID:%d\r\n",reg);   
 return 1; 
  } 
   //读取厂家 ID 高八位 
 reg<<=8;  reg|=SCCB_RD_Reg(OV2640_SENSOR_PIDL); //读取厂家 ID 低八位  
 if(reg!=OV2640_PID) 
  {  
   printf("HID:%d\r\n",reg); 
    }      //初始化 OV2640,采用 SXGA 分辨率(1600*1200)    
    for(i=0;i<sizeof(ov2640_uxga_init_reg_tbl)/2;i++)  
    {      
    SCCB_WR_Reg(ov2640_uxga_init_reg_tbl[i][0],ov2640_uxga_init_reg_tbl[i][1]);  
     }     
     return 0x00;  //ok }

此部分代码先初始化OV2640相关的IO口(包括SCCB_Init),然后最主要的是完成OV2640 的寄存器序列初始化。OV2640 的寄存器特多(百几十个),配置特麻烦,幸好厂家有提供参考 配置序列(详见《OV2640 Software Application Notes 1.03》),本章我们用到的配置序列,存放 在 ov2640_uxga_init_reg_tbl 这个数组里面,该数组是一个 2 维数组,存储初始化序列寄存器及 其对应的值,该数组存放在 ov2640cfg.h 里面。 另外,在 ov2640.c 里面,还有几个函数比较重要,这里贴代码了,只介绍功能: OV2640_Window_Set 函数,该函数用于设置传感器输出窗口; OV2640_ImageSize_Set 函数,用于设置图像尺寸; OV2640_ImageWin_Set 函数,用于设置图像窗口大小; OV2640_OutSize_Set 函数,用于设置图像输出大小; 这就是我们在 40.1.1 节所介绍的 4 个设置,他们共同决定了图像的输出。接下来,我们看 看 ov2640cfg.h 里面 ov2640_uxga_init_reg_tbl 的内容,ov2640cfg.h 文件的代码如下:

//OV2640 UXGA 初始化寄存器序列表 
//此模式下帧率为 15 帧 
//UXGA(1600*1200)  
const u8 ov2640_uxga_init_reg_tbl[][2]=  {     0xff, 0x00,   ……
//省略部分代码    0x05, 0x00, };   
//OV2640 
SVGA 初始化寄存器序列表 
//此模式下,帧率可以达到 30 帧 
//SVGA 800*600 
const u8 ov2640_svga_init_reg_tbl[][2]=  {      0xff, 0x00,    ……//省略部分代码    0x05, 0x00, };    
const u8 ov2640_yuv422_reg_tbl[][2]=  {  0xFF, 0x00,    ……//省略部分代码    0x00, 0x00, }; 
const u8 ov2640_jpeg_reg_tbl[][2]= {  0xff, 0x01,    ……//省略部分代码    0xe0, 0x00,  }; 
const u8 ov2640_rgb565_reg_tbl[][2]= {  0xFF, 0x00,   ……//省略部分代码    0xe0, 0x00,   
}; 

以上代码,我们省略了很多(全部贴出来太长了),里面总共有 5 个数组。我们大概了解下 数组结构,每个数组条目的第一个字节为寄存器号(也就是寄存器地址),第二个字节为要设置 的值,比如{0xff, 0x01},就表示在 0Xff 地址,写入 0X01 这个值。 五个数组里面 ov2640_uxga_init_reg_tbl 和 ov2640_svga_init_reg_tbl,分别用于配置 OV2640 输出 UXGA 和 SVGA 分辨率的图像,我们只用了 ov2640_uxga_init_reg_tbl 这个数组,完成对 OV2640 的初始化(设置为 UXGA)。最后 OV2640 要输出数据是 RGB565 还是 JPEG,就得通过 其他数组设置,输出 RGB565 时,通过一个数组:ov2640_rgb565_reg_tbl 设置即可;输出 JPEG 时,则要通过 ov2640_yuv422_reg_tbl 和 ov2640_jpeg_reg_tbl 两个数组设置。 接下来,我们看看 dcmi.c 里面的代码,如下:

u8 ov_frame=0;       //帧率 
extern void jpeg_data_process(void); //JPEG 数据处理函数 
//DCMI 中断服务函数 
void DCMI_IRQHandler(void) 
{  
if(DCMI_GetITStatus(
DCMI_IT_FRAME)==SET)//捕获到一帧图像  {   
jpeg_data_process();  //jpeg 数据处理    
DCMI_ClearITPendingBit(DCMI_IT_FRAME);//清除帧中断   
LED1=!LED1;   
ov_frame++;    
} 
}  //DMA_Memory0BaseAddr:存储器地址 将要存储摄像头数据的内存地址 //DMA_BufferSize:存储器长度    0~65535 

//DMA_MemoryDataSize:存储器位宽   
//DMA_MemoryInc:存储器增长方式  @defgroup DMA_memory_incremented_mode  void 
DCMI_DMA_Init(u32 DMA_Memory0BaseAddr, u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
 {   DMA_InitTypeDef  DMA_InitStructure;       RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2 时钟使能   
 DMA_DeInit(DMA2_Stream1); 
  while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待 DMA 可配置      /* 配置 DMA Stream */   
  DMA_InitStructure.DMA_Channel = DMA_Channel_1;  //通道 1 DCMI 通道      
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址   
  DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式     
  DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;//数据传输量      
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量   
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式     
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;// 32 位     
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;//存储器数据长度      
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式      
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级     
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO 模式             
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全 FIFO      
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;   
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;   
  DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化 DMA Stream }  //DCMI 初始化 
  void My_DCMI_Init (void) {  GPIO_InitTypeDef  GPIO_InitStructure;  NVIC_InitTypeDef NVIC_InitStructure; 
 
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB |RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOE, ENABLE);//使能 PABCE 时钟  
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE);//使能 DCMI 时钟     //PA4/6 初始化设置     
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;//PA4/6 复用功能输出     
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能输出     
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出     
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz     
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 
 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化       
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6;// PB6/7   复用功能输出     
 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化     
 GPIO_InitStructure.GPIO_Pin =   GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_11;//复用功能输出     
 GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化  
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;//PE5/6  复用功能输出      
    GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化  
 
 
 GPIO_PinAFConfig(GPIOA,GPIO_PinSource4,GPIO_AF_DCMI);//PA4, DCMI_HSYNC  
 GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_DCMI);//PA6, DCMI_PCLK     
 GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_DCMI);//PB7,DCMI_VSYNC    
 GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_DCMI);//PC6, DCMI_D0     
 GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_DCMI);//PC7, DCMI_D1   
 GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_DCMI);//PC8,DCMI_D2  
 GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI);//PC9, DCMI_D3  
 GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_DCMI);//PC11,DCMI_D4   
 GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_DCMI);//PB6,DCMI_D5   
 GPIO_PinAFConfig(GPIOE,GPIO_PinSource5,GPIO_AF_DCMI);//PE5, DCMI_D6  
 GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI);//PE6, DCMI_D7 
 
 DCMI_DeInit();//清除原来的设置      
 DCMI_InitStructure.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;//连续模式  
 DCMI_InitStructure.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;//全帧捕获  
 DCMI_InitStructure.DCMI_ExtendedDataMode= DCMI_ExtendedDataMode_8b;//8 位  
 DCMI_InitStructure.DCMI_HSPolarity= DCMI_HSPolarity_Low;//HSYNC 低电平有效  
 DCMI_InitStructure.DCMI_PCKPolarity= DCMI_PCKPolarity_Rising;//PCL 上升沿有效  
 DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步  
 DCMI_InitStructure.DCMI_VSPolarity=DCMI_VSPolarity_Low;//VSYNC 低电平有效  
 DCMI_Init(&DCMI_InitStructure);//DCMI 初始化 
 
 
 DCMI_ITConfig(DCMI_IT_FRAME,ENABLE);//开启帧中断   DCMI_Cmd(ENABLE); //DCMI 使能 
 
  NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn;  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级 1  
  NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;  //响应优先级 3  
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ 通道使能  
  NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器、}  //DCMI,启动传输 
void DCMI_Start(void) {      
 LCD_SetCursor(0,0);    
 LCD_WriteRAM_Prepare();  //开始写入 GRAM  
 DMA_Cmd(DMA2_Stream1, ENABLE);//开启 DMA2,Stream1   
 DCMI_CaptureCmd(ENABLE);//DCMI 捕获使能   
  } //DCMI,关闭传输
   void DCMI_Stop(void) {    
    DCMI_CaptureCmd(DISABLE);//DCMI 捕获使关闭   
    while(DCMI->CR&0X01);  //等待传输结束   
    DMA_Cmd(DMA2_Stream1,DISABLE);//关闭 DMA2,Stream1 
    }

其中:DCMI_IRQHandler 函数,用于处理帧中断,可以实现帧率统计(需要定时器支持) 和 JPEG 数据处理等。DCMI_DMA_Init 函数,则用于配置 DCMI 的 DMA 传输,其外设地址固 定为:DCMI->DR,而存储器地址可变(LCD 或者 SRAM)。 DMA 被配置为循环模式,一旦开 启,DMA 将不停的循环传输数据。My_DCMI_Init 函数用于初始化 STM32F4 的 DCMI 接口, 这是根据在 40.1.2 节提到的配置步骤进行配置的。最后,DCMI_Start 和 DCMI_Stop 两个函数, 用于开启或停止 DCMI 接口。 其他部分代码我们就不再细说了,请大家参考光盘本例程源码库函数版本(实验 35 摄像 头实验)。 最后,打开 main.c 文件,代码如下:

u8 ov2640_mode=0;     
//工作模式:0,RGB565 模式;1,JPEG 模式 
#define jpeg_buf_size 31*1024     //定义 JPEG 数据缓存 jpeg_buf 的大小(*4 字节) 
__align(4) u32 jpeg_buf[jpeg_buf_size]; //JPEG 数据缓存 buf 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  ……//省略部分代码  1600,1200, //UXGA  }; 
   const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish", "Antique"}; 
   //7 种特效  
   const u8*JPEG_SIZE_TBL[13]={"QQVGA","QCIF","QVGA","WQVGA","CIF","VGA", "SVGA","XGA","WXGA","XVGA","WXGA+","SXGA","UXGA"};//JPEG 图片 13 种尺寸  //处理 JPEG 数据 
   //当采集完一帧 JPEG 数据后,调用此函数,切换 JPEG BUF.开始下一帧采集. 
   void jpeg_data_process(void) {  
   if(ov2640_mode)//只有在 JPEG 格式下,才需要做处理.  
   {   
   if(jpeg_data_ok==0) //jpeg 数据还未采集完?  
    {    
    DMA2_Stream1->CR&=~(1<<0);  //停止当前传输    
    while(DMA2_Stream1->CR&0X01); //等待 DMA2_Stream1 可配置       
    jpeg_data_len=jpeg_buf_size-DMA2_Stream1->NDTR;//得到此次数据的长度    
    jpeg_data_ok=1; //标记 JPEG 数据采集完按成,等待其他函数处理   }   
    if(jpeg_data_ok==2) //上一次的 jpeg 数据已经被处理了   {    
    DMA2_Stream1->NDTR=jpeg_buf_size; //传输长度为 jpeg_buf_size*4 字节    
    DMA2_Stream1->CR|=1<<0;   //重新传输    
    jpeg_data_ok=0;      //标记数据未采集   
    }  
    } 
    }  
    //JPEG 测试 
    //JPEG 数据,通过串口 2 发送给电脑. 
    void jpeg_test(void) {  
    u32 i; u8 *p; u8 key;  
     u8 effect=0,saturation=2,contrast=2; 
      u8 size=2;   //默认是 QVGA 320*240 尺寸  
      u8 msgbuf[15];  //消息缓存区   ……//省略部分代码   
      LCD_ShowString(30,180,200,16,16,msgbuf);//显示当前 JPEG 分辨率 
      OV2640_JPEG_Mode();//JPEG 模式  DCMI_Init();   //DCMI 配置  
      DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,2,1);//DCMI DMA 配置      
      OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置尺寸   
      DCMI_Start();   //启动传输  
      while(1)  {   
      if(jpeg_data_ok==1)//已经采集完一帧图像了   
      {     
       p=(u8*)jpeg_buf;    
       LCD_ShowString(30,210,210,16,16,"Sending JPEG data..."); //提示在传输数据    
       for(i=0;i<jpeg_data_len*4;i++)  //dma 传输 1 次等于 4 字节,所以乘以 4.   
        { 
        while((USART2->SR&0X40)==0);//循环发送,直到发送完毕        
        USART2->DR=p[i];       
        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 去采集下一帧了.   
           }   
            }     
            }  //RGB565 测试 //RGB 数据直接显示在 LCD 上面 
            void rgb565_test(void) {  
             u8 key; 
             u8 effect=0,saturation=2,contrast=2;   
             u8 scale=1;  //默认是全尺寸缩放  
             u8 msgbuf[15]; //消息缓存区   ……//省略部分代码  
             LCD_ShowString(30,170,200,16,16,"KEY_UP:FullSize/Scale");    
             OV2640_RGB565_Mode(); //RGB565 模式  
             DCMI_Init();   //DCMI 配置  
             DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,1,0);//DCMI DMA 配置     
             OV2640_OutSize_Set(lcddev.width,lcddev.height);   
             DCMI_Start();   //启动传输  
             while(1)  {    
             key=KEY_Scan(0);    
             if(key)  
              {     ……//省略部分代码   }     
               delay_ms(10); 
               }  
               int main(void) {          
               u8 key; 
               u8 t; 
               NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组 2  
               delay_init(168);  //初始化延时函数  
               uart_init(115200);  //初始化串口波特率为 115200  
               usart2_init(42,115200);  //初始化串口 2 波特率为 115200 
               LED_Init();     //初始化 LED   
                LCD_Init();     //LCD 初始化    
                 KEY_Init();     //按键初始化   
                 TIM3_Int_Init(10000-1,8400-1);//10Khz 计数,1 秒钟中断一次   
                 POINT_COLOR=RED;//设置字体为红色   
                 LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");   
                 LCD_ShowString(30,70,200,16,16,"OV2640 TEST");   
                 LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");  
                 LCD_ShowString(30,110,200,16,16,"2014/5/14");      
                 while(OV2640_Init())//初始化 OV2640  
                 {   
                 LCD_ShowString(30,130,240,16,16,"OV2640 ERROR"); 
                 delay_ms(200);      
                  LCD_Fill(30,130,239,170,WHITE); 
                  delay_ms(200);    
                  LED0=!LED0; 
                   }  
                   LCD_ShowString(30,130,200,16,16,"OV2640 OK");        
                   while(1) 
                    {    
                    key=KEY_Scan(0);  
                     if(key==KEY0_PRES){ov2640_mode=0; 
                     break;
                     }//RGB565 模式   
                     else if(key==KEY1_PRES) 
                     {ov2640_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=!LED0;
}   
delay_ms(5);     
}  
if(ov2640_mode)jpeg_test();//JPEG 测试  
else rgb565_test(); //RGB565 测试 
} 

这部分代码比较长,我们省略了一些内容。详细的代码,请大家参考光盘本例程源码。注 意,这里定义了一个非常大的数组 jpeg_buf(124KB),用来存储 JPEG 数据,因为 1600*1200 大小的 jpeg 图片,有可能大于 120K,所以必须将这个数组尽量设置大一点。 在 main.c 里面,总共有 4 个函数:jpeg_data_process、jpeg_test、rgb565_test 和 main 函数。 其中,jpeg_data_process 函数用于处理 JPEG 数据的接收,在 DCMI_IRQHandler 函数里面被调 用,通过一个 jpeg_data_ok 变量与 jpeg_test 函数共同控制 JPEG 的数据传送。jpeg_test 函数将 OV2640 设置为 JPEG 模式,该函数接收 OV2640 的 JPEG 数据,并通过串口 2 发送给上位机。 rgb565_test 函数将 OV2640 设置为 RGB565 模式,并将接收到的数据,直接传送给 LCD,处理 过程完全由硬件实现,CPU 完全不用理会。最后,main 函数就相对简单了,大家看代码即可。 前面提到,我们要用 USMART 来设置摄像头的参数,我们只需要在 usmart_nametab 里面 添加 SCCB_WR_Reg 和 SCCB_RD_Reg 等相关函数,就可以轻松调试摄像头了。

下载验证

在代码编译成功之后,我们通过下载代码到 ALIENTEK 探索者 STM32F4 开发板上,在OV2640 初始化成功后,屏幕提示选择模式,此时我们可以按 KEY0,进入 RGB565 模式测试, 也可以按 KEY1,进入 JPEG 模式测试。 当按 KEY0 后,选择 RGB565 模式,LCD 满屏显示压缩放后的图像(有变形),如图 所示:
在这里插入图片描述
此时,可以按 KEY_UP 切换为 1:1 显示(不变形)。同时还可以通过 KEY0 按键,设置对 比度;KEY1 按键,设置饱和度;KEY2,可以设置特效。 当按 KEY1 后,选择 JPEG 模式,此时屏幕显示 JPEG 数据传输进程,如图 :
在这里插入图片描述
默认条件下,图像分辨率是 QVGA(320*240)的,硬件上:我们需要一根 RS232 串口线连 接开发板的 COM2(注意要用跳线帽将 P9 的:COM2_RX 连接在 PA2(TX))。如果没有 RS232 线,也可以借助我们开发板板载的 USB 转串口实现(有 2 个办法:1,改代码;2,杜邦线连接 P9 的 PA2(TX)和 P6 的 RX)。 我们打开上位机软件:串口摄像头.exe(路径:光盘\6,软件资料软件串口摄像头软 件串口摄像头.exe),选择正确的串口,然后波特率设置为 115200,打开即可收到下位机传过 来的图片了,如图
在这里插入图片描述

我们可以通过 KEY_UP 设置输出图像的尺寸(QQVGA~UXGA)。通过 KEY0 按键,设置 对比度;KEY1 按键,设置饱和度;KEY2 按键,设置特效。 同时,你还可以在串口,通过 USMART 调用 SCCB_WR_Reg 等函数,来设置 OV2640 的 各寄存器,达到调试测试 OV2640 的目的,
在这里插入图片描述

发布了160 篇原创文章 · 获赞 316 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/ShawnWang1994/article/details/104505170