1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#
第四十六章 摄像头实验
正点原子战舰STM32开发板板载了一个摄像头接口(P6),该接口可以用来连接正点原子 OV7725摄像头模块。本章,我们将使用STM32驱动正点原子 OV7725摄像头模块,实现摄像头功能。
本章分为如下几个部分:
46.1 OV7725简介
46.2 硬件设计
46.3 软件设计
46.4 下载验证
46.1 OV7725模块简介
本部分将从正点原子的OV7725模块的原理和驱动方法讲述摄像头模块的使用方法。
46.1.1 正点原子OV7725模块
正点原子OV7725模块是正点原子推出的一款高性能30W像素高清摄像头模块。该模块采用OmniVision公司生产的一颗1/4英寸CMOS VGA(640*480)图像传感器:OV7725。正点原子的OV7725模块采用该OV7725传感器作为核心部件,集成有源晶振和和FIFO(AL422B),可以调整缓存摄像头的图像数据,任意一款MCU都可控制该模块和读取图像。
它的外形如图46.1.1.1所示:
图46.1.1.1 正点原子OV7725模块
正点原子OV7725模块的特点如下:
● 高灵敏度、低电压适合嵌入式应用
● 标准的SCCB接口,兼容IIC接口
● 支持RawRGB、RGB(GBR4:2:2,RGB565/RGB555/RGB444),YUV(4:2:2)和YCbCr(4:2:2)输出格式
● 支持VGA、QVGA,和从CIF到40*30的各种尺寸输出
● 自动图像控制功能:自动曝光(AEC)、自动白平衡(AWB)、自动消除灯光条纹、自动黑电平校准(ABLC)和自动带通滤波器(ABF)
● 支持图像质量控制:色饱和度调节、色调调节、gamma校准、锐度和镜头校准等
● 支持图像缩放、平移和窗口设置
● 集成有源晶振,无需外部提供时钟
● 集成FIFO芯片(AL422B),方便MCU读取图像
● 自带嵌入式微处理器
OV7725的功能框图如图46.1.1.2所示:
图46.1.1.2 OV7725功能框图
由上图可知,感光阵列(image array)在XCLK时钟的驱动下进行图像采样,输出640*480阵列的模拟数据;接着模拟信号处理器在时序发生器(video timing generator)的控制下对模拟数据进行算法处理(analog processing);模拟数据处理完成后分成G(绿色)和R/B(红色/蓝色)两路通道经过AD转换器后转换成数字信号,并且通过DSP进行相关图像处理,最终输出所配置格式的10位视频数据流。模拟信号处理以及DSP等都可以通过寄存器(register)来配置,配置寄存器的接口就是SCCB接口。
正点原子OV7725模块原理图如下图46.1.1.3所示:
图46.1.1.3 正点原子OV7725摄像头模块原理图
从上图可以看出,正点原子OV7725摄像头模块自带了有源晶振,用于产生12M时钟作为OV7725传感器的XCLK输入;带有一个FIFO芯片(AL422B),该FIFO芯片的容量是384K字节,足够存储2帧QVGA的图像数据。当驱动好OV7725模块,图像数据就被存放到FIFO中,获取图像数据就是对FIFO进行读取,这个过程需要用到的引脚就是上图中的29双排座。模块就是通过一个29的双排排针(P1)与外部通信,与外部通信信号如表46.1.1.1所示:
46.1.2 串行摄像头控制总线(SCCB)简介
正点原子OV7725摄像头模块的所有配置,包括图像数据格式、分辨率以及图像处理参数等,都是通过SCCB总线来实现的。
SCCB全称是:Serial Camera Control Bus即串行摄像头控制总线,是由OV(OmniVision的简称)公司定义和发展的三线式串行总线。不过,OV公司为了减少传感器引脚的封装,现在SCCB总线大多采用两线式接口总线。
OV7725使用的两线式接口总线,由两条数据线组成:一条是用于传输时钟信号的SIO_C(即OV_SCL),另一条是用于传输数据信号的SIO_D(即OV_SDA),这两条数据线类型IIC协议中的SCL和SDA信号线。在前面提及到SCCB协议兼容IIC协议,是因为SCCB协议和IIC协议非常相似,有关IIC协议的详细介绍请大家参考前面的“IIC实验”章节。
SCCB包括三种传输周期(也就是协议),即3相写传输周期,2相写传输周期和2相读传输周期。3相写传输周期相当于写操作,而读操作是符合的需要2相写传输周期和2相读传输周期进行结合。这里的相指的是传输的单位,一字节称为一个相。
SCCB的写传输协议如下图46.1.2.1所示:
图46.1.2.1 SCCB写传输协议
上图中就是三相写传输周期,第一个相就是ID Address,由7位器件地址和1位读写控制位构成(0:写 1:读),而OV7725器件地址为0x21,所以在写传输时序中,ID Address(W)为0x42(器件地址左移1位,低位补0);第二个相就是Sub-address,即8位寄存器地址,在OV7725的数据手册中定义了0x00~0xAC共173个寄存器,有些寄存器是可写的,有些是只读的,只有可写的寄存器才能正确写入;第三个相就是Write Data,即要写入寄存器的8位配置数据。而上图中的第9位X表示Don’t Care(不必关心位),该位是由从机(此处指OV7725)发出应答信号来响应主机表示当前ID Adress、Sub-address和Write Data是否传输完成,但是从机有可能不发出应答信号,因此主机(此处指STM32)可不用判断此处是否有应答,直接默认当前传输完成即可。
SCCB和IIC写传输协议是极为相似,只是在SCCB写传输时序中,第9位为不必关心位,而IIC写传输协议中为应答位。SCCB的读传输时序和IIC有些差异,在IIC读传输协议中,写完寄存器地址后,会有一个restart即重复开始的操作;而SCCB读传输协议中没有重复开始的概念,在写完寄存器地址后,发起总线停止信号。
SCCB读传输协议如下图46.1.2.2所示。
图46.1.2.2 SCCB读传输协议
SCCB读传输协议由两个部分组成:二相写传输周期和二相读传输周期。跟IIC的读操作是相似的,都是复合的过程。第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针向虚写操作中寄存器地址的位置。第二部分就是读器件地址和读数据,此时读取到的数据才是寄存器地址对应的数据,这里的读器件地址为0x43(器件地址左移1位,低位补1)。上图中的NA位是由主机(这里指STM32)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。
关于SCCB的详细介绍,请大家参考正点原子OV7725摄像头模块资料里《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification.pdf》这个文档。
OV7725的初始化,需要配置大量的寄存器,这里我们就不给大家多做介绍了,请大家参考正点原子OV7725摄像头模块资料里《OV7725 Software Application Note.pdf》。
46.1.3 输出时序说明
当使用SCCB总线对OV7725进行寄存器配置后,就会输出图像数据。通过查看输出时序图就可以知道如何进行图像数据的获取。由于OV7725支持多种尺寸(分辨率)输出,所以在这里简单介绍一下这些尺寸定义:
VGA,即分辨率为640 * 480的输出模式;
QVGA,即分辨率为320 * 240的输出模式;
QQVGA,即分辨率为160 * 120的输出模式;
这里,我们就以VGA模式为例子,即OV7725输出的图像分辨率为640*480,下面分析一下图46.1.3.1所示的帧时序图。
图46.1.3.1 VGA模式帧时序图
在分析时序图前,我们先了解几个基本的概念。
VSYNC:场同步信号,也叫帧信号,由摄像头输出,用于标志一帧图像数据的开始与结束。上图中VSYNC的高电平作为一帧的同步信号,在低电平时输出的数据有效。场同步的极性可以通过寄存器0x15去设置的,本实验使用的是和上图一致的默认设置。
HREF/HSYNC:行同步信号,由摄像头输出,用来标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样。HREF上升沿就马上输出图像数据,而HSYNC会等待一段时间再输出图像数据,如果行中断里需要处理事情再开始采集,显然用HREF上升沿是不容易采集到第一个像素数据的。而我们本实验使用的是HREF格式输出,当HREF为高电平时,图像数据马上输出并有效。该引脚的极性也是可以通过寄存器0x15进行设置。
D[9:0]:数据信号,由摄像头输出,在RGB格式输出中,只用到8个数据引脚,即高8位D[9:2]是有效的。
tPCLK:一个像素时钟周期。
tp:单个数据周期,这里我们需要注意上图中左下角的Note1和2描述,在RGB模式中,tp代表两个tPCLK(像素时钟)。以RGB565数据格式为例,RGB565采用16bit数据表示一个像素点,而OV7725在一个像素周期(tPCLK)内只能传输8bit数据,因此需要两个时钟周期才能输出一个RGB565数据。
tLine:摄像头输出一行数据的时间,共784个tp,包含640tp个高电平和144tp个低电平,其中,640tp为有效像素数据输出的时间。以RGB565数据格式为例,640tp实际上就是6402=1280个tPCLK。
由图46.1.3.1可知,VSYNC的上升沿作为一帧的开始,高电平同步脉冲时间为4tLine,紧接着等待18tLine时间后,HREF开始拉高,此时OV7725输出一行有效图像数据,这里是一行数据即640个像素点(VGA模式);HREF由640tp个高电平和144tp个低电平构成;输出480行数据之后等待8tLine时间会产生一个VSYNC上升沿标志一帧数据传输结束。所以输出一帧图像的时间实际上是tFrame = (4 + 18 + 480 + 8) * tLine = 510 tLine。
利用以上的公式,结合摄像头的输出时钟fPCLK得到tPCLK,便可算出摄像头输出帧率:
摄像头输出帧率:1s / tFrame = 1s / (510 * 784 * 2 tPCLK)
OV7725模块的输入时钟为12MHz,通过OV7725初始化的配置(主要查看0x0D和0x11寄存器),输出时钟为24MHz(周期为42ns),所以代入以上公式,帧率达到30Hz。这里要跟LCD的刷新率进行区分,由于我们本实验用到的是带FIFO的OV7725模块,MCU不是直接去接收传感器输出的图像数据,而是通过从FIFO里进行获得,所以说这个刷新率是比摄像头的输出帧率要低很多。
从帧时序图中,可以清楚知道OV7725是如何输出图像数据,但是不清楚像素数据的情况,所以接下来,我们来看一下图46.1.3.2所示的输出RGB565时序图。
图46.1.3.2 OV7725 RGB565输出时序
从上图可看出,OV7725的图像数据通过D[9:2]输出一个字节,first byte和second byte组成一个16位RGB565数据。在时序上,HREF为高时开始传输一行数据,1个PCLK传输1个字节,传输完一行数据最后一个字节(last byte)后HREF则变为低。
46.1.4 图像数据存储和读取说明
由于OV7725的像素时钟(PCLK)最高可达24Mhz,我们用STM32F103的IO口直接抓取,会十分消耗CPU(可以通过降低PCLK输出频率,来实现IO口抓取,但是不推荐)。所以,我们并不是采取直接抓取OV7725输出的图像数据,而是通过FIFO读取。正点原子 OV7725摄像头模块自带了一个FIFO芯片,用于暂存图像数据,有了这个芯片,我们就可以很方便的获取图像数据了,而不再要求单片机具有高速IO,也不会耗费多少CPU,可以说,只要是个单片机,都可以通过正点原子OV7725摄像头模块实现拍照的功能。
FIFO芯片,型号是AL422B,本质是一种RAM存储器,容量为393216字节,不足以存放一帧VGA分辨率RGB格式的图像数据(6404802),但是能存放2帧QVGA分辨率RGB格式的图像数据(3202402)。由于AL422B写操作相关引脚和读操作相关引脚都是独立开来的,其引脚图如图46.1.4.1所示,所以支持同时写入和读出数据。
图46.1.4.1 AL422B引脚图
写操作相关引脚:WCK、WRST、DI7~0、WE
读操作相关引脚:RCK、RRST、DO7~0、RE、OE
以上引脚在前面表46.1.1.1已经做了介绍,接下来,我们看一下FIFO写时序和读FIFO时序,这两个时序图对应的就是“如何存储图像数据”和“如何读取图像数据”问题的解决。
首先,我们来看一下图46.1.4.2 FIFO写时序图,了解一下如何存储图像数据。
图46.1.4.2 FIFO写时序图
上图中WCK是写FIFO时钟,它与OV_PCLK相连,也就是OV7725的PCLK时钟信号直接提供写FIFO时钟。WRST是FIFO写指针复位引脚,由MCU控制,低电平时,写指针会复位到FIFO的0地址处。WE是FIFO写使能引脚即FIFO_WE,低电平时,FIFO允许写入,但该引脚的电平是通过一个与非门进行决定,详看图46.1.1.3模块原理图中的SN74LVC1G00部分(FIFO_WEN和OV_HREF都为高电平,FIFO_WE才为低电平),具体使用:当OV_HREF为高电平就是一行图像数据到来,而FIFO_WEN引脚是引出来的,所以我们只需要在此刻拉高FIFO_WEN引脚,WE引脚就输出低电平,允许图像数据写入到FIFO。而DI70是直接与OV7725传感器的数据引脚D2D9相连。
总的来说,写FIFO时序就是:OV7725输出有效的行图像数据时(HREF高电平),我们需要保持写使能引脚为低电平(FIFO_WEN拉高)。在复位写指针(拉低后又重新拉高)后,需要一定的复位周期,然后才开始往FIFO的0地址处写数据,且数据会按地址递增方式存入FIFO。
通常,我们会根据帧同步信号进而以上操作,这个存储图像数据的工程为:
- 等待OV7725帧同步信号;
- FIFO写指针复位;
- FIFO写使能;
- 等待第二个OV7725帧同步信号;
- FIFO写禁止。
通过以上5个步骤,我们就可以完成1帧图像数据在AL422B的存储。注意:FIFO写禁止操作不是必须的,只有当你想将一帧图片数据存储在FIFO,并在外部MCU读取完这帧图片数据之前,不再采集新的图片数据的时候,才需要进行FIFO写禁止。
接下来,继续看图46.1.4.2所示的读FIFO时序图,了解一下如何读取图像数据。
图46.1.4.2 读FIFO时序图
上图中的RCK是读FIFO时钟,由MCU控制;RRST是FIFO读指针复位引脚,由MCU控制,低电平时,读指针会复位到FIFO的0地址处;RE是读使能引脚,硬件设计直接接地;OE是输出使能,由MCU控制,要保持低电平才能使能FIFO数据输出;DO7~0是数据引脚,获取图像数据。
在存储完一帧图像以后,我们就可以开始读取图像数据了。读取过程如下:
- FIFO读指针复位(RRST);
- 给FIFO读时钟(FIFO_RCLK);
- 读取第一个像素高字节(D[7:0]);
- 给FIFO读时钟(FIFO_RCLK);
- 读取第一个像素低字节(D[7:0]);
- 给FIFO读时钟(FIFO_RCLK);
- 读取第二个像素高字节(D[7:0]);
- 循环读取剩余像素;
- 结束。
可以看出,摄像头模块数据的读取也是十分简单,比如QVGA模式,RGB565格式,我们总共循环读取3202402次,就可以读取1帧图像数据,把这些数据写入LCD模块,我们就可以看到摄像头捕捉到的画面了。
注意:如果摄像头要使用VGA模式输出,由于FIFO是没办法缓存一帧的VGA图像,就需要在FIFO写满之前开始读FIFO数据,否则数据可能被覆盖。OV7725还可以对输出图像进行各种设置,数据手册和应用笔记详见光盘《OV7725_datasheet.pdf》和《OV7725 Software Application Note.pdf》。对AL422B的操作时序,请大家参考AL422B的数据手册。以上资料,大家可以通过正点原子的资料下载中心获取《正点原子OV7725摄像头模块资料》。
46.2 硬件设计
- 例程功能
开机后,检测和初始化OV7725摄像头模块。初始化成功后需要先通过KEY0和KEY1选择为QVGA或VGA输出模式,然后LCD才会显示拍摄到的画面。
正常显示拍摄画面后,我们可以通过KEY0设置光照模式、通过KEY1设置色饱和度,通过KEY2设置亮度,通过KEY_UP设置对比度,通过TPAD设置特效(总共7种特效)。通过串口,我们可以查看当前的帧率(这里是指LCD显示的帧率,而不是指OV7725的输出帧率),同时可以借助USMART设置OV7725的寄存器,方便大家调试。LED0指示程序运行状态。 - 硬件资源
1)LED灯
LED0 – PB5
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)独立按键: KEY0 – PE4、KEY1 – PE3、KEY2 – PE2、WK_UP - PA0
5)电容按键: PA1,用于控制触摸按键TPAD。
6)外部中断8(连接PA8,用于检测OV7725的帧信号)
7)定时器6(用于打印摄像头帧率等信息)
8)正点原子OV7725摄像头模块,通讯线的连接关系为:
OV7725模块 STM32开发板 OV7725模块 STM32开发板
OV_D0~D7 PC0~7 FIFO_OE PG15
OV_SCL PD3 FIFO_WRST PD6
OV_SDA PG13 FIFO_WEN PB3
OV_VSYNC PA8 FIFO_RCLK PB4
FIFO_RRST PG14
表46.2.1 OV7725模块与开发板连接关系
这部分的连线,模块与开发板上的P6座子已经对应好了,安装时直接把模块镜头背离开发板的方向安装即可(建议安装所有模块的时候断电安装)。
图46.2.1 开发板上连接OV7725模块的座子
46.3 程序设计
OV7725模块驱动步骤
1)初始化OV7725
这里的初始化工作包括:初始化用到的IO口以及SCCB接口、读取传感器ID以及执行初始化序列(配置参数)。
2)存储图像数据
依照OV7725帧时序和FIFO写时序进行操作,详细过程参考前面对时序的分析。
3)读取图像数据
依照FIFO读时序进行,详细过程参考前面对时序的分析。
46.3.1 程序流程图
图46.3.1.1 摄像头实验程序流程图
46.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SCCB驱动源码包括两个文件:sccb.c和sccb.h。OV7725驱动源码包括五个文件:ov7725.c、ov7725.h、ov7725cfg.h、exti.c和exti.h。
- SCCB驱动代码
我们已经介绍过OV7725是通过SCCB协议进行驱动的了,所以在实现OV7725驱动前,需要先加定义好SCCB对应IO的初始化、读写函数,这部分代码我们在sccb.c/h中实现。由于sccb与I2C的实现类似,但是根据sccb协议在数据收/发周期时必须实现第九位的传输。
我们直接复制myiic.c/h的代码,把它们改成sccb驱动,除了读写函数存在一点区别外,其他基本上没有太大的变化。这里看一下读写函数,代码如下:
/**
* @brief SCCB 发送一个字节
* @param data: 要发送的数据
* @retval 无
*/
uint8_t sccb_send_byte(uint8_t data)
{
uint8_t t, res;
for (t = 0; t < 8; t++)
{
SCCB_SDA((data & 0x80) >> 7); /* 高位先发送 */
sccb_delay();
SCCB_SCL(1);
sccb_delay();
SCCB_SCL(0);
data <<= 1; /* 左移1位,用于下一次发送 */
}
SCCB_SDA(1); /* 发送完成, 主机释放SDA线 */
sccb_delay();
SCCB_SCL(1); /* 接收第九位,以判断是否发送成功 */
sccb_delay();
if (SCCB_READ_SDA)
{
res = 1; /* SDA=1发送失败,返回1 */
}
else
{
res = 0; /* SDA=0发送成功,返回0 */
}
SCCB_SCL(0);
return res;
}
/**
* @brief SCCB 读取一个字节
* @param 无
* @retval 读取到的数据
*/
uint8_t sccb_read_byte(void)
{
uint8_t i, receive = 0;
for (i = 0; i < 8; i++ ) /* 接收1个字节数据 */
{
receive <<= 1; /* 高位先输出,所以先收到的数据位要左移 */
SCCB_SCL(1);
sccb_delay();
if (SCCB_READ_SDA)
{
receive++;
}
SCCB_SCL(0);
sccb_delay();
}
return receive;
}
- OV7725驱动代码
OV7725驱动代码实现对OV7725摄像头模块的操作。
首先看一下基于SCCB基本接口的OV7725读寄存器函数和写寄存器函数,代码如下:
/**
* @brief OV7725 读寄存器
* @param reg : 寄存器地址
* @retval 读到的寄存器值
*/
uint8_t ov7725_read_reg(uint16_t reg)
{
uint8_t data = 0;
sccb_start(); /* 起始信号 */
sccb_send_byte(OV7725_ADDR); /* 写通讯地址 */
sccb_send_byte(reg); /* 寄存器地址 */
sccb_stop(); /* 停止信号 */
/* 设置寄存器地址后,才是读 */
sccb_start(); /* 起始信号 */
sccb_send_byte(OV7725_ADDR | 0X01); /* 读通讯地址 */
data = sccb_read_byte(); /* 读取数据 */
sccb_nack(); /* 非应答信号 */
sccb_stop(); /* 停止信号 */
return data;
}
/**
* @brief OV7725 写寄存器
* @param reg : 寄存器地址
* @param data: 要写入寄存器的值
* @retval 0, 成功; 1, 失败;
*/
uint8_t ov7725_write_reg(uint8_t reg, uint8_t data)
{
uint8_t res = 0;
sccb_start(); /* 起始信号 */
if (sccb_send_byte(OV7725_ADDR)) res = 1; /* 写通讯地址 */
if (sccb_send_byte(reg)) res = 1; /* 寄存器地址 */
if (sccb_send_byte(data)) res = 1; /* 写数据 */
sccb_stop(); /* 停止信号 */
return res;
}
按厂商建议的初始化序列,我们封装了需要进行初始化的寄存器,我们再结合AL422B的读写特性操作相关IO。实现的初始化函数如下:
/**
* @brief 初始化 OV7725
* @param 无
* @retval 0, 成功; 1, 失败;
*/
uint8_t ov7725_init(void)
{
uint16_t i = 0;
uint16_t reg = 0;
/* .......这里删去IO和时钟初始化部分代码........ */
__HAL_RCC_AFIO_CLK_ENABLE();
/* 禁止JTAG,使能SWD,释放PB3,PB4两个引脚做普通IO用 */
__HAL_AFIO_REMAP_SWJ_NOJTAG();
OV7725_WRST(1); /* WRST = 1 */
OV7725_RRST(1); /* RRST = 1 */
OV7725_OE(1); /* OE = 1 */
OV7725_RCLK(1); /* RCLK = 1 */
OV7725_WEN(1); /* WEN = 1 */
sccb_init(); /* 初始化SCCB 的IO口 */
if (ov7725_write_reg(0x12, 0x80)) /* 软件复位 */
{
return 1;
}
delay_ms(50);
reg = ov7725_read_reg(0X1c); /* 读取厂家ID 高八位 */
reg <<= 8;
reg |= ov7725_read_reg(0X1d); /* 读取厂家ID 低八位 */
if ((reg != OV7725_MID) && (reg != OV7725_MID1)) /* MID不正确 ? */
{
printf("MID:%d\r\n", reg);
return 1;
}
reg = ov7725_read_reg(0X0a); /* 读取厂家ID 高八位 */
reg <<= 8;
reg |= ov7725_read_reg(0X0b); /* 读取厂家ID 低八位 */
if (reg != OV7725_PID) /* PID不正确 ? */
{
printf("HID:%d\r\n", reg);
return 2;
}
/* 初始化 OV7725,采用QVGA分辨率(320*240) */
for (i=0; i<sizeof(ov7725_init_reg_tb1)/sizeof(ov7725_init_reg_tb1[0]); i++)
{
ov7725_write_reg(ov7725_init_reg_tb1[i][0], ov7725_init_reg_tb1[i][1]);
}
return 0; /* ok */
}
通过ov7725_init函数就完成了OV7725的基本配置,OV7725就会以配置的QVGA模式输出图像数据。每当OV7725输出一个帧信号VSYNC,就代表一帧图像数据就要通过数据引脚进行输出了,因此我们可以利用STM32的外部中断来捕获这个信号,进而在中断服务函数里,把图像数据写入到FIFO中。这里过程的实现,详看exti.c新添加的函数exti_ov7725_vsync_init和中断服务函数,源码如下:
/**
* @brief OV7725 VSYNC外部中断初始化程序
* @param 无
* @retval 无
*/
void exti_ov7725_vsync_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin = OV7725_VSYNC_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_IT_RISING; /* 上升沿触发 */
HAL_GPIO_Init(OV7725_VSYNC_GPIO_PORT, &gpio_init_struct);
HAL_NVIC_SetPriority(OV7725_VSYNC_INT_IRQn, 0, 0); /* 抢占0,子优先级0 */
HAL_NVIC_EnableIRQ(OV7725_VSYNC_INT_IRQn); /* 使能中断线8 */
}
/**
* @brief OV7725 VSYNC 外部中断服务程序
* @param 无
* @retval 无
*/
void OV7725_VSYNC_INT_IRQHandler(void)
{ /* 是OV7725_VSYNC_GPIO_PIN 线的中断? */
if (__HAL_GPIO_EXTI_GET_IT(OV7725_VSYNC_GPIO_PIN))
{
if (g_ov7725_vsta == 0) /* 上一帧数据已经处理了? */
{
OV7725_WRST(0); /* 复位写指针 */
OV7725_WRST(1); /* 结束复位 */
OV7725_WEN(1); /* 允许写入FIFO */
g_ov7725_vsta = 1; /* 标记帧中断 */
}
else
{
OV7725_WEN(0); /* 禁止写入FIFO */
}
}
/* 清除OV7725_VSYNC_GPIO_PIN上的中断标志位 */
__HAL_GPIO_EXTI_CLEAR_IT(OV7725_VSYNC_GPIO_PIN);
}
因为OV7725的帧同步信号(OV_VSYNC)接在PA8,所以用到的是EXTI9_5_IRQHandler。在中断服务函数中,需要先判断中断是不是来自中断线8再进行处理。
中断处理的部分流程:每当帧中断到来后,先判断g_ov7725_vsta的值是否为0,如果是0,说明可以往FIFO里面写入数据,执行复位FIFO写指针,并允许FIFO写入。此时,AL422B将从地址0开始,存储新一帧的图像数据。然后设置g_ov7725_vsta为1,标记新的一帧数据正在存储中。如果g_ov7725_vsta不为0,说明之前存储在FIFO里面的一帧数据还未被读取过,直接禁止FIFO写入,等待MCU读取FIFO数据,以免数据覆盖。
然而,STM32只需要判断g_ov7725_vsta是否为1,来读取FIFO里面的数据,读完一帧后,设置g_ov7725_vsta为0,以免重复读取,同时还可以使能FIFO新一帧数据的写入。
2. main.c代码
我们在实现main函数前,定义了一个ov7725_camera_refresh()函数,用于读取摄像头模块自带FIFO里面的数据并显示在LCD:
uint16_t g_ov7725_wwidth = 320; /* 默认窗口宽度为320 */
uint16_t g_ov7725_wheight = 240; /* 默认窗口高度为240 */
/**
* @brief 更新LCD显示
* @note 该函数将OV7725模块FIFO里面的数据拷贝到LCD屏幕上
* @param 无
* @retval 无
*/
void ov7725_camera_refresh(void)
{
uint32_t i, j;
uint16_t color;
if (g_ov7725_vsta) /* 有帧中断更新 */
{
lcd_scan_dir(U2D_L2R); /* 从上到下, 从左到右 */
lcd_set_window((lcddev.width - g_ov7725_wwidth) / 2,
(lcddev.height - g_ov7725_wheight) / 2, g_ov7725_wwidth, g_ov7725_wheight); /* 将显示区域设置到屏幕中央 */
lcd_write_ram_prepare(); /* 开始写入GRAM */
OV7725_RRST(0); /* 开始复位读指针 */
OV7725_RCLK(0);
OV7725_RCLK(1);
OV7725_RCLK(0);
OV7725_RRST(1); /* 复位读指针结束 */
OV7725_RCLK(1);
for (i = 0; i < g_ov7725_wheight; i++)
{
for (j = 0; j < g_ov7725_wwidth; j++)
{
OV7725_RCLK(0);
color = OV7725_DATA; /* 读数据 */
OV7725_RCLK(1);
color <<= 8;
OV7725_RCLK(0);
color |= OV7725_DATA; /* 读数据 */
OV7725_RCLK(1);
LCD->LCD_RAM = color;
}
}
g_ov7725_vsta = 0; /* 清零帧中断标记 */
g_ov7725_frame++;
lcd_scan_dir(DFT_SCAN_DIR); /* 恢复默认扫描方向 */
}
}
对于OV7725,我们可以通过g_ov7725_wwidth和g_ov7725_wheight两个全局变量设置图像窗口输出的大小。对于分辨率大于320*240的屏幕,则通过开窗函数(lcd_set_window)将显示区域开窗在屏幕的正中央。注意,为了提高FIFO读取速度,我们将OV7725_RCLK的控制,采用快速IO控制,关键代码如下(在ov7725.h里面):
#define OV7725_RCLK(x) x ?(OV7725_RCLK_GPIO_PORT->BSRR = OV7725_RCLK_GPIO_PIN): (OV7725_RCLK_GPIO_PORT->BRR = OV7725_RCLK_GPIO_PIN)
控制OV7725_RCLK输出高电平或者低电平就用到BSRR和BRR两个寄存器,以实现快速IO设置,从而提高读取速度。
最后介绍的是main函数,其定义如下:
const char *LMODE_TBL[6] = {"Auto", "Sunny", "Cloudy", "Office",
"Home", "Night"}; /* 6种光照模式 */
const char *EFFECTS_TBL[7] = {"Normal", "Negative", "B&W", "Redish",
"Greenish", "Bluish", "Antique"};/* 7种特效 */
int main(void)
{
uint8_t key;
uint8_t i = 0;
char msgbuf[15]; /* 消息缓存区 */
uint8_t tm = 0;
uint8_t lightmode = 0, effect = 0;
uint8_t saturation = 4, brightness = 4, contrast = 4;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(72); /* 初始化USMART */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
tpad_init(6); /* TPAD 初始化 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "OV7725 TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY0:Light Mode", RED);
lcd_show_string(30, 130, 200, 16, 16, "KEY1:Saturation", RED);
lcd_show_string(30, 150, 200, 16, 16, "KEY2:Brightness", RED);
lcd_show_string(30, 170, 200, 16, 16, "KEY_UP:Contrast", RED);
lcd_show_string(30, 190, 200, 16, 16, "TPAD:Effects", RED);
lcd_show_string(30, 210, 200, 16, 16, "OV7725 Init...", RED);
while (1)
{
if (ov7725_init() == 0) /* 初始化OV7725 */
{
lcd_show_string(30, 210, 200, 16, 16, "OV7725 Init OK ", RED);
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES)
{ /* QVGA模式输出 */
g_ov7725_wwidth = 320; /* 默认窗口宽度为320 */
g_ov7725_wheight = 240; /* 默认窗口高度为240 */
ov7725_window_set(g_ov7725_wwidth, g_ov7725_wheight, 0);
break;
}
else if (key == KEY1_PRES)
{ /* VGA模式输出 */
g_ov7725_wwidth = 320; /* 默认窗口宽度为320 */
g_ov7725_wheight = 240; /* 默认窗口高度为240 */
ov7725_window_set(g_ov7725_wwidth, g_ov7725_wheight, 1);
break;
}
i++;
if (i == 100)
lcd_show_string(30,230,210,16,16, "KEY0:QVGA KEY1:VGA", RED);
if (i == 200)
{
lcd_fill(30, 230, 210, 250 + 16, WHITE);
i = 0;
}
delay_ms(5);
}
ov7725_light_mode(lightmode);
ov7725_color_saturation(saturation);
ov7725_brightness(brightness);
ov7725_contrast(contrast);
ov7725_special_effects(effect);
OV7725_OE(0); /* 使能OV7725 FIFO数据输出 */
break;
}
else
{
lcd_show_string(30, 190, 200, 16, 16, "OV7725 Error!!", RED);
delay_ms(200);
lcd_fill(30, 190, 239, 246, WHITE);
delay_ms(200);
}
}
btim_timx_int_init(10000, 7200 - 1); /* 10Khz计数频率,1秒钟中断 */
exti_ov7725_vsync_init(); /* 使能OV7725 VSYNC外部中断 */
lcd_clear(BLACK);
while (1)
{
key = key_scan(0); /* 不支持连按 */
if (key)
{
tm = 20;
switch (key)
{
case KEY0_PRES: /* 灯光模式Light Mode */
lightmode++;
if (lightmode > 5) lightmode = 0;
ov7725_light_mode(lightmode);
sprintf((char *)msgbuf, "%s", LMODE_TBL[lightmode]);
break;
case KEY1_PRES: /* 饱和度Saturation */
saturation++;
if (saturation > 8) saturation = 0;
ov7725_color_saturation(saturation);
sprintf((char *)msgbuf, "Saturation:%d", saturation);
break;
case KEY2_PRES: /* 饱和度Saturation */
brightness++;
if (brightness > 8) brightness = 0;
ov7725_brightness(brightness);
sprintf((char *)msgbuf, "Brightness:%d", brightness);
break;
case WKUP_PRES: /* 对比度Contrast */
contrast++;
if (contrast > 8) contrast = 0;
ov7725_contrast(contrast);
sprintf((char *)msgbuf, "Contrast:%d", contrast);
break;
}
}
if (tpad_scan(0)) /* 检测到触摸按键 */
{
effect++;
if (effect > 6) effect = 0;
ov7725_special_effects(effect); /* 设置特效 */
sprintf((char *)msgbuf, "%s", EFFECTS_TBL[effect]);
tm = 20;
}
ov7725_camera_refresh(); /* 更新显示 */
if (tm)
{
lcd_show_string((lcddev.width - 240) / 2 + 30,
(lcddev.height-320)/2 + 60,200,16,16, msgbuf,BLUE);
tm--;
}
i++;
if (i >= 15)
{
i = 0;
LED0_TOGGLE(); /* LED0闪烁 */
}
}
}
main函数的具体流程可以参考程序流程图,代码比较简单,这里我们就不展开说明了。
到此摄像头的使用过程我们讲解完了,大家可以参考光盘中的源码进行测试和修改,也可以在USMART中加入OV7725的测试接口ov7725_write_reg和ov7725_read_reg,轻松调试摄像头。
最后,为了得到最快的显示速度,我们可以把MDK的代码优化等级设置为-O2级别(在C/C++选项卡设置),这样OV7725的显示帧率可达23帧。注意:因为tpad_scan扫描会占用比较多的时间,所以帧率比较慢,屏蔽该函数,也可以提高帧率。
46.4 下载验证
在代码编译成功之后,下载代码到正点原子战舰STM32F103开发板上,得到如图46.4.1所示界面:
图46.4.1程序运行效果图
随后,我们通过KEY0和KEY1选择模式。当选择QVGA模式时,OV7725直接输出320240分辨率图像数据,该模式相对VGA模式视角较广但画面没有那么清晰细腻。当选择VGA模式时,实质是将640480窗口截取中间320*240的图像输出,该模式拍出的图像较为清晰细腻但视角较小。
我们可以按不同的按键(KEY0~KEY2、KEY_UP、TPAD等),来设置摄像头的相关参数和模式,得到不同的成像效果。同时,我们可以通过USMART调用ov7725_write_reg等函数,来设置OV7725的各寄存器等,达到调试测试的目的,具体如图46.4.2所示:
文章来源:https://www.toymoban.com/news/detail-763246.html
图46.4.2 USMART调试OV7725
从上图还可以看出,LCD显示帧率为10帧左右(没有代码优化),而实际上OV77225的输出速度是30帧。图中,我们是可以通过USMART发送ov7725_write_reg(0x66, 0x20)设置OV7725输出彩条,方便测试。
摄像头的实验我们就讲解到这里。文章来源地址https://www.toymoban.com/news/detail-763246.html
到了这里,关于【正点原子STM32连载】 第四十六章 摄像头实验 摘自【正点原子】STM32F103 战舰开发指南V1.2的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!