STM32F4_OV2640&DCMI 摄像头模块
KEY0 按键设置对比度;,图像可以设置任何尺寸(QQVGA~UXGA),采集到的 JPEG 数据将先存放到STM32F4的内存里面,每采集到一帧数据,就会关闭DMA传输,然后将采集到的数据发送到串口 2(通过上位机软件串口摄像头.exe接收,显示图片),之后在重新启动DMA传输;来控制采集多少的数据,比如说采集40帧的数据,但是CPU无法同时处理40帧数据,这是可以通过设置捕获率来设置采集数目的
目录
前言
STM32F4 开发板具有 DCMI 接口,并且板载了摄像头接口,该接口可以用来连接 OV2640摄像头模块。接下来我们将去学习 DCMI 接口以及摄像头 OV2640 模块。
1. 摄像头简介
在各种各样的信息中,图像含有最丰富的信息,作为机器视觉领域的核心部件,摄像头被广泛的应用在生活中的各种场合。
摄像头按照输出信号的类型来看可以分为数字摄像头和模拟摄像头,按照摄像头图像传感器材料构成可以分为CCD(电荷耦合器件)和CMOS(互补金属氧化物半导体 )。
1.1 OV2640简介
OV2640是 OV 公司(OmniVision)公司生产的一颗 1/4 寸的 CMOS UXGA(1632*1232)图像传感器。该传感器体积小、工作电压低,提供单片 UXGA 摄像头和影像处理器的所有功能。通过 SCCB 总线控制,可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率 8/10 位影像数据。所有图像处理功能包括伽玛曲线、白平衡、对比度、色度等都可以通过 SCCB 接口编程。
2. OV2640简介
2.1 OV2640引脚及功能框图
OV2640传感器不同于其他芯片的封装,可以明显的看到其引脚分布;OV2640传感器采用的是BGA封装,前端是采光窗口(拆开OV2640的前盖,可以看到一个透光的窗口,这个窗口就是OV2640的前端),引脚都在背面引出。
引脚:
其中没有标出的是电源部分,标出的是主要的信号引脚。
- SIO_C:输入引脚,SCCB总线协议的时钟线,可类比于IIC协议的SCL串行时钟总线
- SIO_D:I/O输入输出引脚,SCCB总线协议的数据线,可类比于IIC协议的SDA串行数据总线
- RESETB:输入引脚,系统复位管脚,低电平有效
- PWDN:输入引脚,掉电/省电模式,高电平有效
- HREF:输出引脚,行同步信号 (行信号就是发送一张图片,假设分辨率是480,那么发送图片时是从上到下,从左到右发送的,那么每发送完一行就有一个高低电平变化)
- VSYNC:输出引脚,帧同步信号 (帧就是每发送完一张图片,就会有一个高低电平变化的信号提醒;而不再是每发送完一行就会有一个电平变化)
- PCLK:输出引脚,像素同步时钟输出信号
- XCLK:输入引脚,外部时钟输入端口,可接外部晶振
- Y0……Y9:输出引脚,像素数据输出端口
功能框图:
模拟信号处理(图中的模拟电路):
模拟信号处理所有模拟功能,并包括:模拟放大(AMP)、增益控制、通道平衡和平衡控制等。
输出格式模块:
该模块按设定优先级控制图像的所有输出数据及其格式。
压缩引擎(对应图中DSP模块中的conpression engine):
压缩引擎主要包括:DCT、QZ和entropy encoder(熵编译码),将原始的数据流压缩成 jpeg 数据输出。
微处理器(Microcontroller):
OV2640自带了一个8位微处理器,该处理器有 512 字节SRAM,4KB 的ROM,他提供了一个灵活的主机到控制系统的接口指令,同时也具有细调图像质量的功能。
数字视频接口(Gigital Video Port):(对应图中DSP模块中video port模块)
OV2640拥有一个10位数字视频接口(支持8位接法),其MSB和LSB可以程序设置先后顺序;
也就是说:OV2640模块采用默认的8位连接方式,Y0对应于Y2;Y7对应于Y9;
2.2 OV2640设置
2.2.1 OV2640传感器窗口设置
传感器窗口功能允许用户设置整个传感器区域(1632*1220)的感兴趣部分;简单来说就是:摄像头采集的不是整个区域,而是我们想要看到的某一个确定的区域,这个区域比完整的区域要小;也可以说是在传感器里面开窗,开窗范围从2*2~1632*1220都可以设置,不过设置的窗口必须大于随后设置的图像的窗口(否则图像是无法呈现的,这个很好理解)。传感器窗口设置,通过:0X03/0X19/0X1A/0X07/0X17/0X18 等寄存器设置;
2.2.2 OV2640图像尺寸设置
图像尺寸设置就是DSP输出到LCD上图像的最大尺寸,该尺寸要小于等于传感器窗口设置的窗口尺寸(这个也好理解,就是要显示图片的尺寸一定得小于窗口的尺寸,这个现实中也比较好理解);图像尺寸通过:0XC0/0XC1/0X8C 等寄存器设置。
2.2.3 OV2640图像窗口设置
图像窗口设置刚开始是和传感器窗口设置相同的,只是这个窗口是我们设置的图像尺寸里面,再一次设置的时候必须比前一次设置的窗口要小。该窗口设置后的图像范围,将用于输出到外部。图像窗口设置通过:0X51/0X52/0X53/0X54/0X55/0X57等寄存器设置。
2.2.4 OV2640图像输出大小设置
图像输出大小就是最终输出到外部的图像尺寸。该设置将我们图像窗口设置的大小,通过内部DSP处理,缩放成我们输出到外部的图像大小;(如果我们设置的窗口大小和输出到外部的图像大小不一致,那么就要进行缩放处理,保证输出比例是1:1)
2.3 OV2640的图像数据输出格式
UXGA:分辨率为1600*1200的输出格式,类似的还有:SXGA(1280*1024)、WXGA+(1440*900)、XVGA(1280*960)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)
PCLK:像素时钟,一个PCLK时钟,输出一个像素或者半个像素
VSYNC:帧同步信号
HREF/HSYNC:行同步信号
OV2640的图像数据输出(通过Y[9:0]就是在PCLK,VSYNC和HREF/HSYNC的控制下进行的)。
OV2640的图像数据格式一般是 RGB565 和 JPEG 两种。不过 JPEG 输出方式下,PCLK 的数量大大减少了,输出的数据是压缩后的 JPEG 数据;输出的数据以 0xFF,0xD8 开头,以0xFF,0xD9 结尾,最终我们将得到 0XFF,0XD8~0XFF,0XD9 之间的数据,保存为 .jpg/.jpeg 文件,此时就可以直接在电脑上看到图像了。
2.4 OV2640模块初始化
初始化 OV2640 对应的 I/O 口 ---> 上电,并复位 ---> 读取传感器 ID ---> 执行初始化序列 ---> 完成初始化
//初始化OV2640
//配置完成后,默认输出是1600*1200尺寸的图片
//返回值:0,成功
// 其他,错误代码
u8 OV2640_Init(void)
{
u16 i=0;
u16 Register;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOG时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //设置为推挽输出
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_15; //PG9 PG15
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure);
OV2640_PWDN=0; //掉电模式关闭
delay_ms(10);
OV2640_RST=0; //复位OV2640
delay_ms(10);
OV2640_RST=1; //结束复位
SCCB_Init(); //初始化SCCB的IO口
SCCB_Write_Register(OV2640_DSP_RA_DLMT,0x01); //操作Sensor寄存器
SCCB_Write_Register(OV2640_SENSOR_COM7,0x80); //软复位OV2640
delay_ms(50);
Register=SCCB_Read_Register(OV2640_SENSOR_MIDH); //读取厂家ID高8位
Register=Register<<8; //Register是16位寄存器
Register=Register|SCCB_Read_Register(OV2640_SENSOR_MIDL); //读取厂家ID低8位
if(Register!=OV2640_MID)
{
printf("MID:%d\r\n",Register);
return 1;
}
Register=SCCB_Read_Register(OV2640_SENSOR_PIDH); //读取厂家ID高8位
Register=Register<<8; //Register是16位寄存器
Register=Register|SCCB_Read_Register(OV2640_SENSOR_PIDL); //读取厂家ID低8位
if(Register!=OV2640_PID)
{
printf("HID:%d\r\n",Register);
//return 2;
}
//初始化OV2640,采用SXGA分辨率 1600*1200
for(i=0;i<sizeof(ov2640_sxga_init_reg_tbl)/2;i++)
{
SCCB_Write_Register(ov2640_sxga_init_reg_tbl[i][0],ov2640_sxga_init_reg_tbl[i][1]);
}
return 0x00;
}
2.5 MPU读取OV2640模块
等待帧同步(VSYNC) ---> 等待 HREF (行同步信号)为高电平 ---> 等待第一个PCLK上升沿 ---> 读取第一个像素低字节 ---> 等待第二个PCLK上升沿 ---> 读取第一个像素高字节 ---> 等待第三个PCLK上升沿 ---> 读取第二个像素低字节 ---> 循环读取剩余像素……
注:之所以读一次高电平,读一次低电平是因为两帧信号代表一个字节。
3. SCCB时序
外部控制器(STM32就属于外部控制器)对 OV2640 寄存器的配置参数就是通过 SCCB总线 传输过去的,而 SCCB 总线跟 IIC 总线十分类似,所以在 STM32 驱动中可以直接使用片上的IIC 外设与它通讯。
SCCB和标准的IIC协议的区别是:SCCB时序每次传输只能写入或读取一个字节的数据,而 IIC 协议是支持突发读写的,也就是说在一次传输中可以写入多个字节的数据。
其中:
SCCB_E是总线使能引脚;
SIO_C是时钟总线;
SIO_D是数据总线;
OV2640寄存器:
需要注意的是,OV2640有两组寄存器,这两组寄存器有部分地址重合,通过设置RA_DLMT寄存器可以切换寄存器组。
当 RA_DLMT 寄存器为 0 时,通过 SCCB 发送的寄存器地址在 DSP 相关的寄存器组寻址
当 RA_DLMT 寄存器为 1 时,在 Sensor 相关的寄存器组寻址
注:有两组寄存器的意思是说OV2640从寄存器地址 0x00 到 0xFF 有两组寄存器组,每组的地址都是 0x00-0xFF,地址虽然重复,但是对应的是不同的寄存器组,通过设置寄存器地址为0xFF的寄存器RA_DLMT可以切换寄存器组;(具体寄存器的信息可以查阅OV2640寄存器的手册)
像素数据输出时序:
主控器(主控器在本节其实就是STM32)控制OV2640时采用 SCCB 时序(STM32可以采用IIC时序,和SCCB时序非常类似)读写其寄存器,而它输出图像时则使用 VGA 时序(还可以用SVGA、UXGA,这些时序都差不多,只是分辨率不同),这跟控制液晶屏输入图像时很类似。
OV2640输出图像时,一帧一帧地输出,在帧内的数据一般是从左到右,从上到下,一个像素一个像素地输出(也可以通过寄存器修改方向)。
若使用 Y2-Y9 数据线,图像格式设置为 RGB565,进行数据传输时,Y2-Y9 数据线会在 1 个像素同步时钟 PCLK 的驱动下发送1字节的数据信号,2个 PCLK 时钟可发送1个 RGB565 格式的像素数据(1个RGB565占用2个字节,2个字节一个时钟,2个字节一个像素点)。像素数据依次传输,每传输完一行数据时,行同步信号HREF会输出一个电平跳变信号,每传输完一帧图像时,VSYNC会输出一个电平跳变信号。
4. DCMI 数字摄像头接口
STM32F4 系列的控制器包含了 DCMI 数字摄像头接口(Digital camera Interface),该接口是一个同步并行接口;它支持使用上述类似 VGA 的时序获取图像数据流,支持原始的按行、帧格式来组织的图像数据,如 YUV、RGB,也支持接收 JPEG 格式压缩的数据流。接收数据时,主要使用 HSYNC 及 VSYNC 信号来同步。
HCLK是STM32内部的时钟频率:168MHz
DCMI_IT是中断控制器
DMA_REQ是为了减少CPU的负担,对于某些不是很重要的程序处理、计算,可以使用DMA直接存储器去进行处理
标号1:
DCMI向外部引出的信号线(DCMI提供的外部接口的方向都是输入的):
DCMI_D[0:13]:数据线
DCMI_PIXCLK:像素同步时钟
DCMI_HSYNC:行同步信号(水平同步信号)
DCMI_VSYNC:帧同步信号(垂直同步信号)
STM32F4的 DCMI 接口包括的信号(DCMI提供的外部接口的方向都是输入的):
1. 数据输入(D[0:13]),接摄像头的数据输出
2. 水平同步(行同步)输入(HSYNC),接摄像头的HSYNC/HREF信号
3. 垂直同步(场同步)输入(VSYNC),接摄像头的VSYNC信号
4. 像素时钟输入(PIXCLK),接摄像头的PCLK信号
标号2:
DCMI 与内部的信号线。在STM32的内部,使用 HCLK 作为时钟源提供给 DCMI 外设。从 DCMI 引出有 DCMI_IT 信号至中断控制器,并可通过 DMA_REQ 信号发送 DMA 请求。
DCMI 从外部接收数据时,在HCLK的上升沿时对 PIXCLK 像素同步信号进行采样,它限制了 PIXCLK 的最小时钟周期要大于2.5个 HCLK 时钟周期,即最高频率为 HCLK 的1/4(168/4 MHz)。
DCMI 内部框图:
同步器:
同步器主要用于管理 DCMI 接收数据的时序,它根据外部信号(外部信号主要就是像素同步时钟、数据线、行同步信号以及帧同步信号)提取输入的数据
FIFO/数据格式化器:
为了对数据传输加以管理,STM32在 DCMI 接口上实现了4个字(32bit*4)深度的FIFO,用以缓冲接收到的数据(所谓的 FIFO,实际上就是缓冲区)
AHB接口:
DCMI 接口挂载在AHB总线上,在AHB总线上有一个DCMI 接口的数据寄存器,当我们读取该寄存器时,它会从FIFO中获取数据,并且FIFO中的数据指针会自动进行偏移,使得我们每次读取该寄存器都可获得一个新的数据
控制/状态寄存器:
DCMI 的控制寄存器协调图中的各个结构运行,程序中可通过检测状态寄存器来获得 DCMI 的当前运行状态
DMA接口:
由于DCMI 采集的数据量很大,我们一般使用 DMA 来把采集的数据搬运至内存
同步方式:
DCMI 接口支持硬件同步或内嵌码同步方式,硬件同步方式即使用HSYNC和VSYNC作为同步信号的方式,OV2640就是使用这种同步时序。
而内嵌码同步的方式是使用数据信号线传输中的特定编码来表示同步信息,由于需要用0x00和0xFF来表示编码,所以表示图像的数据中不能包含有这两个值。
捕获模式及捕获率:
DCMI 还支持两种数据捕获模式,分别是快照模式和连续采集模式。快照模式时只采集一帧的图像数据;连续采集模式会一直采集多个帧的数据;
(简单的来说:一帧就是一个静态的图片,我们看到的电视剧乃至动画片中,1秒大概就是24、25帧的图片,也就是说,1秒是由25张左右的静态图片合成的;这就是帧的描述)
可以通过配置捕获率来控制采集多少的数据,比如说采集40帧的数据,但是CPU无法同时处理40帧数据,这时可以通过设置捕获率来设置采集数目的多少,可以采集20帧数据,也就是说,在原本采集40帧的基础上,每采集一帧紧接着就丢弃一帧,这样下来会采集一半的帧数。
5. DCMI结构体
typedef struct
{
uint16_t DCMI_CaptureMode; //选择连续模式或快拍模式
//设置DCMI的捕获模式,
//可以选择连续摄像:DCMI_CaptureMode_Continuous
//单张拍照:DCMI_CaptureMode_SnapShot
uint16_t DCMI_SynchroMode; //选择硬件同步模式还是内嵌码模式
//设置同步模式
//硬件同步模式:DCMI_SynchroMode_Hardware
//内嵌码方式:DCMI_SynchroMode_Embedded
uint16_t DCMI_PCKPolarity; //设置像素时钟的有效边沿
//配置像素时钟的有效边沿,在该边沿,DCMI会对信号进行采样
//上升沿有效:DCMI_PCKPolarity_Rising
//下降沿有效:DCMI_PCKPolarity_Falling
uint16_t DCMI_VSPolarity; //设置VSYNC的有效电平
//当VSYNC信号线表示有效电平时,表示新的一帧数据传输完成
//高电平有效:DCMI_VSPolarity_High
//低电平有效:DCMI_VSPolarity_Low
uint16_t DCMI_HSPolarity; //设置HSYNC的有效边沿
//当HSYNC信号线表示为有效电平时,表示新的一行数据传输完成
//高电平有效:DCMI_HSPolarity_High
//低电平有效:DCMI_HSPolarity_Low
uint16_t DCMI_CaptureRate; //设置图像的采样间隔,在间隔采集的情况下,会直接按间隔丢弃数据
//可以设置为全采集、半采集或1/4采集
//DCMI_CaptureRate_All_Frame/1of2_Frame/1of4_Frame
uint16_t DCMI_ExtendedDataMode; //设置数据线的宽度
//可配置为8/10/12及14位数据线宽
//DCMI_ExtendedDataMode_8b/10b/12b/14b
}DCMI_InitTypeDef;
//配置该结构体,其本质是配置DCMI控制寄存器的各个位,实现DCMI初始化
6. 硬件设计
OV2640摄像头模块自带了有源晶振,用于产生24M时钟作为OV2640的XVCLK输入。自带稳压芯片XC6219B132MR和RT9193-2.8,可以将 3.3V电压转换成2.8V和1.3V工作电压;
整个OV2640模块通过P1双排插针与外部通讯,在这里就是和STM32进行通讯,OV2640遵循SCCB时序,而STM32通过 IIC 协议和 DCMI 接口进行通讯。(具体是开发板上板载了DCMI接口,位于STM32F4的左下角,和OLED屏是一个接口,之前已经介绍过了,该接口和OV2640模块的P1双排插针相连,实现STM32F4和OV2640模块的通讯,实现摄像头功能)
注意:
假设我们将OV2640默认设置为UXGA输出,也就是1600*1200的分辨率;
输出信号设置为:VSYNC帧同步信号高电平有效,HREF行同步信号高电平有效,输出数据在PCLK的下降沿输出(即上升沿的时候,MCU才可以采集)
这样,STM32F4的 DCMI 接口就必须设置为:VSYNC帧同步信号低电平有效,HSYNC行同步信号低电平有效和PIXCLK上升沿有效。
也就是说:STM32通过DCMI接口和OV2640进行通讯时,OV2640负责输出采集到的帧,也就是图片,STM32负责采集;所以双方的设置应该恰好相反,OV2640设置PCLK下降沿输出,那么与此同时STM32就必须设置上升沿捕获。
以上的这些引脚都是通过 DCMI_CR控制寄存器 配置的:
位14 ENABLE:设置是否使能DCMI,不过在使能之前,必须把其他的配置都设置好
位[9:8] FCRC:帧捕获率控制
- 00:捕获所有帧
- 01:每隔一帧捕获一次
- 10:每隔三帧捕获一次
- 11:保留
位7 VSPOL:垂直同步极性 也就是VSYNC帧同步信号引脚上面,数据无效时的电平状态,我们设置为0
- 0:VSYNC低电平有效
- 1:VSYNC高电平有效
位6 HSPOL:水平同步极性 也就是HSYNC引脚上面,数据无效时的电平状态,同样设置为0
- 0:HSYNC低电平有效
- 1:HSYNC高电平有效
位5 PCKPOL:像素时钟极性
- 0:下降沿有效
- 1:上升沿有效
位1 CM:捕获模式
- 0:连续采集模式 也就是有多少帧采集多少帧——收到的数据将通过DMA传输到目标存储区
- 1:快照模式(单帧采集)
位0 CAPTURE:使能捕获 摄像头接口等待第一帧开始,然后生成一个DMA请求以将收到的数据传输到目标存储器中
- 0:禁止捕获
- 1:使能捕获
在快照模式下,收到的第一帧结束时将自动是CAPTURE位清零
在连续采集模式下,如果在执行捕获操作时通过软件将此位清零,则帧结束后此位的清零才会生效
DCMI 支持DMA传输,当DCMI_CR寄存器中的CAPTURE位置1时,激活DMA接口;摄像头接口每次在其寄存器DCMI_DR数据寄存器中收到一个完整的32位数据块时,都将发送一个DMA请求,由DMA将DCMI_DR数据寄存器中的值搬运到目的地(在LCD上显示或者保存到STM32内部SRAM)
DCMI 的 DMA 请求是映射在 DMA2 通道 1 的数据流 1 上面的,所以配置 DMA 时应该配置这个;
如果直接DCMI ---> DMA ---> LCD的传输方式,因为LCD是16位宽(RGB565),而DCMI_DR数据寄存器是32位宽,所以,一次 DCMI 引起的DMA传输,将引发往 LCD 写两次数据。
STM32F4的 DCMI 接口和 MCU 引脚连接图:
- DCMI_VSYNC 接 PB7;
- DCMI_HREF 接 PA4;
- DCMI_PCLK 接 PA6;
- DCMI_SCL 接 PD6;
- DCMI_SDA 接 PD7;
- DCMI_RESET 接 PG15;
- DCMI_PWDN 接 PG9;
- DCMI_XCLK 接 PA8(本章未用到);
- DCMI_D[7:0]接 PE6/PE5/PB6/PC11/PC9/PC8/PC7/PC6;
注:当引脚有多功能时,使用某单一功能时,必须分时复用,不可以同时使用。
7. 库函数实现 DCMI 驱动OV2640
1. 配置OV2640控制引脚,并配置OV2640工作模式
配置OV_SCL和OV_SDA以及OV_PWDN、OV_RESET等IO状态,先设置OV_PWDN=0,退出掉电模式,然后拉低OV_RESET复位OV_2640,之后设置OV_RESET为1,结束复位。
紧接着配置OV2640的工作模式:UXGA输出、RGB565输出模式或者JPEG输出模式。
2. 配置相关引脚的模式和复用功能(AF13),使能时钟
RCC _AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE); //使能 DCMI 时钟
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能输出 (IO口模式设置为复用)
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
typedef struct
{
uint16_t DCMI_CaptureMode; //选择连续模式或快拍模式
//设置DCMI的捕获模式,
//可以选择连续摄像:DCMI_CaptureMode_Continuous
//单张拍照:DCMI_CaptureMode_SnapShot
uint16_t DCMI_SynchroMode; //选择硬件同步模式还是内嵌码模式
//设置同步模式
//硬件同步模式:DCMI_SynchroMode_Hardware
//内嵌码方式:DCMI_SynchroMode_Embedded
uint16_t DCMI_PCKPolarity; //设置像素时钟的有效边沿
//配置像素时钟的有效边沿,在该边沿,DCMI会对信号进行采样
//上升沿有效:DCMI_PCKPolarity_Rising
//下降沿有效:DCMI_PCKPolarity_Falling
uint16_t DCMI_VSPolarity; //设置VSYNC的有效电平
//当VSYNC信号线表示有效电平时,表示新的一帧数据传输完成
//高电平有效:DCMI_VSPolarity_High
//低电平有效:DCMI_VSPolarity_Low
uint16_t DCMI_HSPolarity; //设置HSYNC的有效边沿
//当HSYNC信号线表示为有效电平时,表示新的一行数据传输完成
//高电平有效:DCMI_HSPolarity_High
//低电平有效:DCMI_HSPolarity_Low
uint16_t DCMI_CaptureRate; //设置图像的采样间隔,在间隔采集的情况下,会直接按间隔丢弃数据
//可以设置为全采集、半采集或1/4采集
//DCMI_CaptureRate_All_Frame/1of2_Frame/1of4_Frame
uint16_t DCMI_ExtendedDataMode; //设置数据线的宽度
//可配置为8/10/12及14位数据线宽
//DCMI_ExtendedDataMode_8b/10b/12b/14b
}DCMI_InitTypeDef;
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. 配置DMA
本章采用连续模式进行采集,也就是有多少帧数据就采集多少次,并且将采集到的数据输出到RGB565模式或者JPEG模式,不管是采用的什么模式,数据的来源都是DCMI数据寄存器DCMI_DR。DCMI 的 DMA 传输采用的是 DMA2 数据流 1 的通道 1 来实现的。
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 捕获
OV2640的图像输出大小设置:在RGB565模式下,根据LCD尺寸,设置输出图像的大小,以实现全屏显示;在JPEG模式下,我们可以自由设置输出图像的大小;
最后,开启DCMI 捕获;
DCMI_CaptureCmd(ENABLE); //DCMI 捕获使能
8. 实验程序
本节中,我们可以选择 RGB565 和 JPEG 两种输出格式;当使用 RGB565 时,输出的图像将经过缩放处理(完全由OV2640的DSP数字输出信号控制),显示在LCD上面。当使用 JPEG 数据输出的时候,我们将收到的 JPEG 数据,通过串口 2(115200波特率)发送给电脑,并利用电脑端上位机软件,显示接收到的图片。
本章实验功能简介:
开机后,初始化摄像头模块(OV2640),如果初始化成功(也就是检测OV2640模块是否已经插上了),则提示选择模式:RGB565模式或者JPEG模式。KEY0 选择RGB565模式,KEY1 选择JPEG模式。
当使用RGB565时,输出图像(固定为:UXGA)将经过缩放处理显示在LCD上(此过程完全由OV2640的DSP数字输出模块控制)。我们可以通过KEY_UP按键选择:1:1不缩放显示,图片不变形;或者缩放显示(将1600*1200的图像压缩到液晶分辨率尺寸显示,在图片变形的基础上显示了整个图片的内容);KEY0 按键设置对比度;KEY1 按键设置饱和度;KEY2 按键设置特效;
当使用JPEG模式时,图像可以设置任何尺寸(QQVGA~UXGA),采集到的 JPEG 数据将先存放到STM32F4的内存里面,每采集到一帧数据,就会关闭DMA传输,然后将采集到的数据发送到串口 2(通过上位机软件串口摄像头.exe接收,显示图片),之后在重新启动DMA传输;KEY0 按键设置对比度;KEY1 按键设置饱和度;KEY2 按键设置特效;
8.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "USART2.h"
#include "Timer.h"
#include "OV2640.h"
#include "DCMI.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
u8 HSYNC_INT=0; //帧中断标志
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]=
{
176,144, //QCIF
160,120, //QQVGA
352,288, //CIF
320,240, //QVGA
640,480, //VGA
800,600, //SVGA
1024,768, //XGA
1280,1024, //SXGA
1600,1200, //UXGA
};
const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"}; //7种特效
const u8*JPEG_SIZE_TBL[9]={"QCIF","QQVGA","CIF","QVGA","VGA","SVGA","XGA","SXGA","UXGA"}; //JPEG图片 9种尺寸
//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{
if(OV2640_Mode)//只有在JPEG格式下,才需要做处理.
{
if(jpeg_data_ok==0) //jpeg数据还未采集完?
{
DMA_Cmd(DMA2_Stream1, DISABLE);//停止当前传输
while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置
jpeg_data_len=jpeg_buf_size-DMA_GetCurrDataCounter(DMA2_Stream1);//得到此次数据传输的长度
jpeg_data_ok=1; //标记JPEG数据采集完成,等待其他函数处理
}
if(jpeg_data_ok==2) //上一次的jpeg数据已经被处理了
{
DMA2_Stream1->NDTR=jpeg_buf_size;
DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_buf_size);//传输长度为jpeg_buf_size*4字节
DMA_Cmd(DMA2_Stream1, ENABLE); //重新传输
jpeg_data_ok=0; //标记数据未采集
}
}
else
{
LCD_SetCursor(0, 0);
LCD_WriteRAM_Prepare(); //开始写入GRAM
HSYNC_INT = 1;
}
}
//JPEG测试
//JPEG数据,通过串口2发送给电脑.
void jpeg_test(void)
{
u32 i;
u8 *p;
u8 key;
u8 effect=0,saturation=2,contrast=2;
u8 size=3; //默认是QVGA 320*240尺寸
u8 msgbuf[15]; //消息缓存区
LCD_Clear(WHITE);
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
LCD_ShowString(30,70,200,16,16,"OV2640 JPEG Mode");
LCD_ShowString(30,100,200,16,16,"KEY0:Contrast"); //对比度
LCD_ShowString(30,120,200,16,16,"KEY1:Saturation"); //色彩饱和度
LCD_ShowString(30,140,200,16,16,"KEY2:Effects"); //特效
LCD_ShowString(30,160,200,16,16,"KEY_UP:Size"); //分辨率设置
sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
LCD_ShowString(30,180,200,16,16,msgbuf); //显示当前JPEG分辨率
OV2640_JPEG_Mode(); //JPEG模式
My_DCMI_Init(); //DCMI配置
DCMI_DMA_Init((u32)&jpeg_buf,jpeg_buf_size,DMA_MemoryDataSize_Word,DMA_MemoryInc_Enable);//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(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
USART_SendData(USART2,p[i]);
key=KEY_Scan(0);
if(key)break;
}
if(key) //有按键按下,需要处理
{
LCD_ShowString(30,210,210,16,16,"Quit Sending data ");//提示退出数据传输
switch(key)
{
case KEY0_PRESS: //对比度设置
contrast++;
if(contrast>4)contrast=0;
OV2640_Contrast(contrast);
sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
break;
case KEY1_PRESS: //饱和度Saturation
saturation++;
if(saturation>4)saturation=0;
OV2640_Color_Saturation(saturation);
sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
break;
case KEY2_PRESS: //特效设置
effect++;
if(effect>6)effect=0;
OV2640_Special_Effects(effect);//设置特效
sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
break;
case KEY_UP_PRESS: //JPEG输出尺寸设置
size++;
if(size>8)size=0;
OV2640_OutSize_Set(jpeg_img_size_tbl[size][0],jpeg_img_size_tbl[size][1]);//设置输出尺寸
sprintf((char*)msgbuf,"JPEG Size:%s",JPEG_SIZE_TBL[size]);
break;
}
LCD_Fill(30,180,239,190+16,WHITE);
LCD_ShowString(30,180,210,16,16,msgbuf);//显示提示内容
delay_ms(800);
}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_Clear(WHITE);
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"ALIENTEK STM32F4");
LCD_ShowString(30,70,200,16,16,"OV2640 RGB565 Mode");
LCD_ShowString(30,100,200,16,16,"KEY0:Contrast"); //对比度
LCD_ShowString(30,130,200,16,16,"KEY1:Saturation"); //色彩饱和度
LCD_ShowString(30,150,200,16,16,"KEY2:Effects"); //特效
LCD_ShowString(30,170,200,16,16,"KEY_UP:FullSize/Scale"); //1:1尺寸(显示真实尺寸)/全尺寸缩放
OV2640_RGB565_Mode(); //RGB565模式
My_DCMI_Init(); //DCMI配置
DCMI_DMA_Init((u32)&LCD->LCD_RAM,1,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置
OV2640_OutSize_Set(lcddev.width,lcddev.height);
DCMI_Start(); //启动传输
while(1)
{
key=KEY_Scan(0);
if(key)
{
DCMI_Stop(); //停止显示
switch(key)
{
case KEY0_PRESS: //对比度设置
contrast++;
if(contrast>4)contrast=0;
OV2640_Contrast(contrast);
sprintf((char*)msgbuf,"Contrast:%d",(signed char)contrast-2);
break;
case KEY1_PRESS: //饱和度Saturation
saturation++;
if(saturation>4)saturation=0;
OV2640_Color_Saturation(saturation);
sprintf((char*)msgbuf,"Saturation:%d",(signed char)saturation-2);
break;
case KEY2_PRESS: //特效设置
effect++;
if(effect>6)effect=0;
OV2640_Special_Effects(effect);//设置特效
sprintf((char*)msgbuf,"%s",EFFECTS_TBL[effect]);
break;
case KEY_UP_PRESS: //1:1尺寸(显示真实尺寸)/缩放
scale=!scale;
if(scale==0)
{
if(lcddev.id == 0X5510)
{
SCCB_Write_Register(0xd3,0x02);
}
OV2640_ImageWin_Set((1600-lcddev.width)/2,(1200-lcddev.height)/2,lcddev.width,lcddev.height);//1:1真实尺寸
OV2640_OutSize_Set(lcddev.width,lcddev.height);
sprintf((char*)msgbuf,"Full Size 1:1");
}else
{
OV2640_ImageWin_Set(0,0,1600,1200); //全尺寸缩放
OV2640_OutSize_Set(lcddev.width,lcddev.height);
sprintf((char*)msgbuf,"Scale");
}
break;
}
LCD_ShowString(30,50,210,16,16,msgbuf);//显示提示内容
delay_ms(800);
DCMI_Start();//重新开始传输
}
if (HSYNC_INT) //刚刚产生帧中断,可以延时
{
delay_ms(10);
HSYNC_INT = 0;
}
}
}
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秒钟中断一次
usmart_dev.init(84); //初始化USMART
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 ERR");
delay_ms(200);
LCD_Fill(30,130,239,170,WHITE);
delay_ms(200);
}
LCD_ShowString(30,130,200,16,16,"OV2640 OK");
while(1)
{
key=KEY_Scan(0);
if(key==1) //RGB565模式
{
OV2640_Mode=0;
break;
}else if(key==2) //JPEG模式
{
OV2640_Mode=1;
break;
}
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;
}
delay_ms(5);
}
if(OV2640_Mode)jpeg_test();
else rgb565_test();
}
8.2 OV2640.c
#include "stm32f4xx.h"
#include "OV2640.h"
#include "SCCB.h"
#include "Timer.h"
#include "delay.h"
#include "usart.h"
#include "OV2640Config.h"
//初始化OV2640
//配置完成后,默认输出是1600*1200尺寸的图片
//返回值:0,成功
// 其他,错误代码
u8 OV2640_Init(void)
{
u16 i=0;
u16 Register;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE); //使能GPIOG时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //设置为推挽输出
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽模式
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_15; //PG9 PG15
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOG,&GPIO_InitStructure);
OV2640_PWDN=0; //掉电模式关闭
delay_ms(10);
OV2640_RST=0; //复位OV2640
delay_ms(10);
OV2640_RST=1; //结束复位
SCCB_Init(); //初始化SCCB的IO口
SCCB_Write_Register(OV2640_DSP_RA_DLMT,0x01); //操作Sensor寄存器
SCCB_Write_Register(OV2640_SENSOR_COM7,0x80); //软复位OV2640
delay_ms(50);
Register=SCCB_Read_Register(OV2640_SENSOR_MIDH); //读取厂家ID高8位
Register=Register<<8; //Register是16位寄存器
Register=Register|SCCB_Read_Register(OV2640_SENSOR_MIDL); //读取厂家ID低8位
if(Register!=OV2640_MID)
{
printf("MID:%d\r\n",Register);
return 1;
}
Register=SCCB_Read_Register(OV2640_SENSOR_PIDH); //读取厂家ID高8位
Register=Register<<8; //Register是16位寄存器
Register=Register|SCCB_Read_Register(OV2640_SENSOR_PIDL); //读取厂家ID低8位
if(Register!=OV2640_PID)
{
printf("HID:%d\r\n",Register);
//return 2;
}
//初始化OV2640,采用SXGA分辨率 1600*1200
for(i=0;i<sizeof(ov2640_sxga_init_reg_tbl)/2;i++)
{
SCCB_Write_Register(ov2640_sxga_init_reg_tbl[i][0],ov2640_sxga_init_reg_tbl[i][1]);
}
return 0x00;
}
//OV2640切换为JPEG模式
void OV2640_JPEG_Mode(void)
{
u16 i=0;
//设置:YUV422格式
for(i=0;i<(sizeof(ov2640_yuv422_reg_tbl)/2);i++)
{
SCCB_Write_Register(ov2640_yuv422_reg_tbl[i][0],ov2640_yuv422_reg_tbl[i][1]);
}
//设置:输出JPEG数据
for(i=0;i<(sizeof(ov2640_jpeg_reg_tbl)/2);i++)
{
SCCB_Write_Register(ov2640_jpeg_reg_tbl[i][0],ov2640_jpeg_reg_tbl[i][1]);
}
}
//OV2640切换为RGB565模式
void OV2640_RGB565_Mode(void)
{
u16 i=0;
//设置:RGB565输出
for(i=0;i<(sizeof(ov2640_rgb565_reg_tbl)/2);i++)
{
SCCB_Write_Register(ov2640_rgb565_reg_tbl[i][0],ov2640_rgb565_reg_tbl[i][1]);
}
}
//自动曝光设置参数表,支持5个等级
const static u8 OV2640_AUTOEXPOSURE_LEVEL[5][8]=
{
{
0xFF,0x01,
0x24,0x20,
0x25,0x18,
0x26,0x60,
},
{
0xFF,0x01,
0x24,0x34,
0x25,0x1c,
0x26,0x00,
},
{
0xFF,0x01,
0x24,0x3e,
0x25,0x38,
0x26,0x81,
},
{
0xFF,0x01,
0x24,0x48,
0x25,0x40,
0x26,0x81,
},
{
0xFF,0x01,
0x24,0x58,
0x25,0x50,
0x26,0x92,
},
};
//OV2640自动曝光等级设置
//level:0~4
void OV2640_Auto_Exposure(u8 level)
{
u8 i;
u8 *p=(u8*)OV2640_AUTOEXPOSURE_LEVEL[level];
for(i=0;i<4;i++)
{
SCCB_Write_Register(p[i*2],p[i*2+1]);
}
}
//白平衡设置
//0:自动
//1:太阳sunny
//2,阴天cloudy
//3,办公室office
//4,家里home
void OV2640_Light_Mode(u8 mode)
{
u8 regccval=0X5E;//Sunny
u8 regcdval=0X41;
u8 regceval=0X54;
switch(mode)
{
case 0://auto
SCCB_Write_Register(0XFF,0X00);
SCCB_Write_Register(0XC7,0X10);//AWB ON
return;
case 2://cloudy
regccval=0X65;
regcdval=0X41;
regceval=0X4F;
break;
case 3://office
regccval=0X52;
regcdval=0X41;
regceval=0X66;
break;
case 4://home
regccval=0X42;
regcdval=0X3F;
regceval=0X71;
break;
}
SCCB_Write_Register(0XFF,0X00);
SCCB_Write_Register(0XC7,0X40); //AWB OFF
SCCB_Write_Register(0XCC,regccval);
SCCB_Write_Register(0XCD,regcdval);
SCCB_Write_Register(0XCE,regceval);
}
//色度设置
//0:-2
//1:-1
//2,0
//3,+1
//4,+2
void OV2640_Color_Saturation(u8 sat)
{
u8 reg7dval=((sat+2)<<4)|0X08;
SCCB_Write_Register(0XFF,0X00);
SCCB_Write_Register(0X7C,0X00);
SCCB_Write_Register(0X7D,0X02);
SCCB_Write_Register(0X7C,0X03);
SCCB_Write_Register(0X7D,reg7dval);
SCCB_Write_Register(0X7D,reg7dval);
}
//亮度设置
//0:(0X00)-2
//1:(0X10)-1
//2,(0X20) 0
//3,(0X30)+1
//4,(0X40)+2
void OV2640_Brightness(u8 bright)
{
SCCB_Write_Register(0xff, 0x00);
SCCB_Write_Register(0x7c, 0x00);
SCCB_Write_Register(0x7d, 0x04);
SCCB_Write_Register(0x7c, 0x09);
SCCB_Write_Register(0x7d, bright<<4);
SCCB_Write_Register(0x7d, 0x00);
}
//对比度设置
//0:-2
//1:-1
//2,0
//3,+1
//4,+2
void OV2640_Contrast(u8 contrast)
{
u8 reg7d0val=0X20;//默认为普通模式
u8 reg7d1val=0X20;
switch(contrast)
{
case 0://-2
reg7d0val=0X18;
reg7d1val=0X34;
break;
case 1://-1
reg7d0val=0X1C;
reg7d1val=0X2A;
break;
case 3://1
reg7d0val=0X24;
reg7d1val=0X16;
break;
case 4://2
reg7d0val=0X28;
reg7d1val=0X0C;
break;
}
SCCB_Write_Register(0xff,0x00);
SCCB_Write_Register(0x7c,0x00);
SCCB_Write_Register(0x7d,0x04);
SCCB_Write_Register(0x7c,0x07);
SCCB_Write_Register(0x7d,0x20);
SCCB_Write_Register(0x7d,reg7d0val);
SCCB_Write_Register(0x7d,reg7d1val);
SCCB_Write_Register(0x7d,0x06);
}
//特效设置
//0:普通模式
//1,负片
//2,黑白
//3,偏红色
//4,偏绿色
//5,偏蓝色
//6,复古
void OV2640_Special_Effects(u8 eft)
{
u8 reg7d0val=0X00;//默认为普通模式
u8 reg7d1val=0X80;
u8 reg7d2val=0X80;
switch(eft)
{
case 1://负片
reg7d0val=0X40;
break;
case 2://黑白
reg7d0val=0X18;
break;
case 3://偏红色
reg7d0val=0X18;
reg7d1val=0X40;
reg7d2val=0XC0;
break;
case 4://偏绿色
reg7d0val=0X18;
reg7d1val=0X40;
reg7d2val=0X40;
break;
case 5://偏蓝色
reg7d0val=0X18;
reg7d1val=0XA0;
reg7d2val=0X40;
break;
case 6://复古
reg7d0val=0X18;
reg7d1val=0X40;
reg7d2val=0XA6;
break;
}
SCCB_Write_Register(0xff,0x00);
SCCB_Write_Register(0x7c,0x00);
SCCB_Write_Register(0x7d,reg7d0val);
SCCB_Write_Register(0x7c,0x05);
SCCB_Write_Register(0x7d,reg7d1val);
SCCB_Write_Register(0x7d,reg7d2val);
}
//彩条测试
//sw:0,关闭彩条
// 1,开启彩条(注意OV2640的彩条是叠加在图像上面的)
void OV2640_Color_Bar(u8 sw)
{
u8 reg;
SCCB_Write_Register(0XFF,0X01);
reg=SCCB_Read_Register(0X12);
reg&=~(1<<1);
if(sw)reg|=1<<1;
SCCB_Write_Register(0X12,reg);
}
//设置图像输出窗口
//sx,sy,起始地址
//width,height:宽度(对应:horizontal)和高度(对应:vertical)
void OV2640_Window_Set(u16 sx,u16 sy,u16 width,u16 height)
{
u16 endx;
u16 endy;
u8 temp;
endx=sx+width/2; //V*2
endy=sy+height/2;
SCCB_Write_Register(0XFF,0X01);
temp=SCCB_Read_Register(0X03); //读取Vref之前的值
temp&=0XF0;
temp|=((endy&0X03)<<2)|(sy&0X03);
SCCB_Write_Register(0X03,temp); //设置Vref的start和end的最低2位
SCCB_Write_Register(0X19,sy>>2); //设置Vref的start高8位
SCCB_Write_Register(0X1A,endy>>2); //设置Vref的end的高8位
temp=SCCB_Read_Register(0X32); //读取Href之前的值
temp&=0XC0;
temp|=((endx&0X07)<<3)|(sx&0X07);
SCCB_Write_Register(0X32,temp); //设置Href的start和end的最低3位
SCCB_Write_Register(0X17,sx>>3); //设置Href的start高8位
SCCB_Write_Register(0X18,endx>>3); //设置Href的end的高8位
}
//设置图像输出大小
//OV2640输出图像的大小(分辨率),完全由改函数确定
//width,height:宽度(对应:horizontal)和高度(对应:vertical),width和height必须是4的倍数
//返回值:0,设置成功
// 其他,设置失败
u8 OV2640_OutSize_Set(u16 width,u16 height)
{
u16 outh;
u16 outw;
u8 temp;
if(width%4)return 1;
if(height%4)return 2;
outw=width/4;
outh=height/4;
SCCB_Write_Register(0XFF,0X00);
SCCB_Write_Register(0XE0,0X04);
SCCB_Write_Register(0X5A,outw&0XFF); //设置OUTW的低八位
SCCB_Write_Register(0X5B,outh&0XFF); //设置OUTH的低八位
temp=(outw>>8)&0X03;
temp|=(outh>>6)&0X04;
SCCB_Write_Register(0X5C,temp); //设置OUTH/OUTW的高位
SCCB_Write_Register(0XE0,0X00);
return 0;
}
//设置图像开窗大小
//由:OV2640_ImageSize_Set确定传感器输出分辨率从大小.
//该函数则在这个范围上面进行开窗,用于OV2640_OutSize_Set的输出
//注意:本函数的宽度和高度,必须大于等于OV2640_OutSize_Set函数的宽度和高度
// OV2640_OutSize_Set设置的宽度和高度,根据本函数设置的宽度和高度,由DSP
// 自动计算缩放比例,输出给外部设备.
//width,height:宽度(对应:horizontal)和高度(对应:vertical),width和height必须是4的倍数
//返回值:0,设置成功
// 其他,设置失败
u8 OV2640_ImageWin_Set(u16 offx,u16 offy,u16 width,u16 height)
{
u16 hsize;
u16 vsize;
u8 temp;
if(width%4)return 1;
if(height%4)return 2;
hsize=width/4;
vsize=height/4;
SCCB_Write_Register(0XFF,0X00);
SCCB_Write_Register(0XE0,0X04);
SCCB_Write_Register(0X51,hsize&0XFF); //设置H_SIZE的低八位
SCCB_Write_Register(0X52,vsize&0XFF); //设置V_SIZE的低八位
SCCB_Write_Register(0X53,offx&0XFF); //设置offx的低八位
SCCB_Write_Register(0X54,offy&0XFF); //设置offy的低八位
temp=(vsize>>1)&0X80;
temp|=(offy>>4)&0X70;
temp|=(hsize>>5)&0X08;
temp|=(offx>>8)&0X07;
SCCB_Write_Register(0X55,temp); //设置H_SIZE/V_SIZE/OFFX,OFFY的高位
SCCB_Write_Register(0X57,(hsize>>2)&0X80); //设置H_SIZE/V_SIZE/OFFX,OFFY的高位
SCCB_Write_Register(0XE0,0X00);
return 0;
}
//该函数设置图像尺寸大小,也就是所选格式的输出分辨率
//UXGA:1600*1200,SVGA:800*600,CIF:352*288
//width,height:图像宽度和图像高度
//返回值:0,设置成功
// 其他,设置失败
u8 OV2640_ImageSize_Set(u16 width,u16 height)
{
u8 temp;
SCCB_Write_Register(0XFF,0X00);
SCCB_Write_Register(0XE0,0X04);
SCCB_Write_Register(0XC0,(width)>>3&0XFF); //设置HSIZE的10:3位
SCCB_Write_Register(0XC1,(height)>>3&0XFF); //设置VSIZE的10:3位
temp=(width&0X07)<<3;
temp|=height&0X07;
temp|=(width>>4)&0X80;
SCCB_Write_Register(0X8C,temp);
SCCB_Write_Register(0XE0,0X00);
return 0;
}
8.3 OV2640.h
#ifndef _OV2640__H_
#define _OV2640__H_
#include "sys.h"
#include "SCCB.h"
//
#define OV2640_PWDN PGout(9) //POWER DOWN控制信号
#define OV2640_RST PGout(15) //复位控制信号
//
#define OV2640_MID 0X7FA2
#define OV2640_PID 0X2642
//当选择DSP地址(0XFF=0X00)时,OV2640的DSP寄存器地址映射表
#define OV2640_DSP_R_BYPASS 0x05
#define OV2640_DSP_Qs 0x44
#define OV2640_DSP_CTRL 0x50
#define OV2640_DSP_HSIZE1 0x51
#define OV2640_DSP_VSIZE1 0x52
#define OV2640_DSP_XOFFL 0x53
#define OV2640_DSP_YOFFL 0x54
#define OV2640_DSP_VHYX 0x55
#define OV2640_DSP_DPRP 0x56
#define OV2640_DSP_TEST 0x57
#define OV2640_DSP_ZMOW 0x5A
#define OV2640_DSP_ZMOH 0x5B
#define OV2640_DSP_ZMHH 0x5C
#define OV2640_DSP_BPADDR 0x7C
#define OV2640_DSP_BPDATA 0x7D
#define OV2640_DSP_CTRL2 0x86
#define OV2640_DSP_CTRL3 0x87
#define OV2640_DSP_SIZEL 0x8C
#define OV2640_DSP_HSIZE2 0xC0
#define OV2640_DSP_VSIZE2 0xC1
#define OV2640_DSP_CTRL0 0xC2
#define OV2640_DSP_CTRL1 0xC3
#define OV2640_DSP_R_DVP_SP 0xD3
#define OV2640_DSP_IMAGE_MODE 0xDA
#define OV2640_DSP_RESET 0xE0
#define OV2640_DSP_MS_SP 0xF0
#define OV2640_DSP_SS_ID 0x7F
#define OV2640_DSP_SS_CTRL 0xF8
#define OV2640_DSP_MC_BIST 0xF9
#define OV2640_DSP_MC_AL 0xFA
#define OV2640_DSP_MC_AH 0xFB
#define OV2640_DSP_MC_D 0xFC
#define OV2640_DSP_P_STATUS 0xFE
#define OV2640_DSP_RA_DLMT 0xFF
//当选择传感器地址(0XFF=0X01)时,OV2640的DSP寄存器地址映射表
#define OV2640_SENSOR_GAIN 0x00
#define OV2640_SENSOR_COM1 0x03
#define OV2640_SENSOR_REG04 0x04
#define OV2640_SENSOR_REG08 0x08
#define OV2640_SENSOR_COM2 0x09
#define OV2640_SENSOR_PIDH 0x0A
#define OV2640_SENSOR_PIDL 0x0B
#define OV2640_SENSOR_COM3 0x0C
#define OV2640_SENSOR_COM4 0x0D
#define OV2640_SENSOR_AEC 0x10
#define OV2640_SENSOR_CLKRC 0x11
#define OV2640_SENSOR_COM7 0x12
#define OV2640_SENSOR_COM8 0x13
#define OV2640_SENSOR_COM9 0x14
#define OV2640_SENSOR_COM10 0x15
#define OV2640_SENSOR_HREFST 0x17
#define OV2640_SENSOR_HREFEND 0x18
#define OV2640_SENSOR_VSTART 0x19
#define OV2640_SENSOR_VEND 0x1A
#define OV2640_SENSOR_MIDH 0x1C
#define OV2640_SENSOR_MIDL 0x1D
#define OV2640_SENSOR_AEW 0x24
#define OV2640_SENSOR_AEB 0x25
#define OV2640_SENSOR_W 0x26
#define OV2640_SENSOR_REG2A 0x2A
#define OV2640_SENSOR_FRARL 0x2B
#define OV2640_SENSOR_ADDVSL 0x2D
#define OV2640_SENSOR_ADDVHS 0x2E
#define OV2640_SENSOR_YAVG 0x2F
#define OV2640_SENSOR_REG32 0x32
#define OV2640_SENSOR_ARCOM2 0x34
#define OV2640_SENSOR_REG45 0x45
#define OV2640_SENSOR_FLL 0x46
#define OV2640_SENSOR_FLH 0x47
#define OV2640_SENSOR_COM19 0x48
#define OV2640_SENSOR_ZOOMS 0x49
#define OV2640_SENSOR_COM22 0x4B
#define OV2640_SENSOR_COM25 0x4E
#define OV2640_SENSOR_BD50 0x4F
#define OV2640_SENSOR_BD60 0x50
#define OV2640_SENSOR_REG5D 0x5D
#define OV2640_SENSOR_REG5E 0x5E
#define OV2640_SENSOR_REG5F 0x5F
#define OV2640_SENSOR_REG60 0x60
#define OV2640_SENSOR_HISTO_LOW 0x61
#define OV2640_SENSOR_HISTO_HIGH 0x62
u8 OV2640_Init(void);
void OV2640_JPEG_Mode(void);
void OV2640_RGB565_Mode(void);
void OV2640_Auto_Exposure(u8 level);
void OV2640_Light_Mode(u8 mode);
void OV2640_Color_Saturation(u8 sat);
void OV2640_Brightness(u8 bright);
void OV2640_Contrast(u8 contrast);
void OV2640_Special_Effects(u8 eft);
void OV2640_Color_Bar(u8 sw);
void OV2640_Window_Set(u16 sx,u16 sy,u16 width,u16 height);
u8 OV2640_OutSize_Set(u16 width,u16 height);
u8 OV2640_ImageWin_Set(u16 offx,u16 offy,u16 width,u16 height);
u8 OV2640_ImageSize_Set(u16 width,u16 height);
#endif
8.4 OV2640Config.h
#ifndef _OV2640Config__H_
#define _OV2640Config__H_
#include "OV2640.h"
//OV2640 SXGA初始化寄存器序列表
//此模式下帧率为15帧
//SXGA(1600*1200)
const u8 ov2640_sxga_init_reg_tbl[][2]=
{
0xff, 0x00,
0x2c, 0xff,
0x2e, 0xdf,
0xff, 0x01,
0x3c, 0x32,
//
0x11, 0x00,
0x09, 0x02,
0x04, 0xD8,//水平镜像,垂直翻转
0x13, 0xe5,
0x14, 0x48,
0x2c, 0x0c,
0x33, 0x78,
0x3a, 0x33,
0x3b, 0xfB,
//
0x3e, 0x00,
0x43, 0x11,
0x16, 0x10,
//
0x39, 0x92,
//
0x35, 0xda,
0x22, 0x1a,
0x37, 0xc3,
0x23, 0x00,
0x34, 0xc0,
0x36, 0x1a,
0x06, 0x88,
0x07, 0xc0,
0x0d, 0x87,
0x0e, 0x41,
0x4c, 0x00,
0x48, 0x00,
0x5B, 0x00,
0x42, 0x03,
//
0x4a, 0x81,
0x21, 0x99,
//
0x24, 0x40,
0x25, 0x38,
0x26, 0x82,
0x5c, 0x00,
0x63, 0x00,
0x46, 0x00,
0x0c, 0x3c,
//
0x61, 0x70,
0x62, 0x80,
0x7c, 0x05,
//
0x20, 0x80,
0x28, 0x30,
0x6c, 0x00,
0x6d, 0x80,
0x6e, 0x00,
0x70, 0x02,
0x71, 0x94,
0x73, 0xc1,
0x3d, 0x34,
0x5a, 0x57,
//
0x12, 0x00,//UXGA 1600*1200
0x17, 0x11,
0x18, 0x75,
0x19, 0x01,
0x1a, 0x97,
0x32, 0x36,
0x03, 0x0f,
0x37, 0x40,
//
0x4f, 0xca,
0x50, 0xa8,
0x5a, 0x23,
0x6d, 0x00,
0x6d, 0x38,
//
0xff, 0x00,
0xe5, 0x7f,
0xf9, 0xc0,
0x41, 0x24,
0xe0, 0x14,
0x76, 0xff,
0x33, 0xa0,
0x42, 0x20,
0x43, 0x18,
0x4c, 0x00,
0x87, 0xd5,
0x88, 0x3f,
0xd7, 0x03,
0xd9, 0x10,
0xd3, 0x82,
//
0xc8, 0x08,
0xc9, 0x80,
//
0x7c, 0x00,
0x7d, 0x00,
0x7c, 0x03,
0x7d, 0x48,
0x7d, 0x48,
0x7c, 0x08,
0x7d, 0x20,
0x7d, 0x10,
0x7d, 0x0e,
//
0x90, 0x00,
0x91, 0x0e,
0x91, 0x1a,
0x91, 0x31,
0x91, 0x5a,
0x91, 0x69,
0x91, 0x75,
0x91, 0x7e,
0x91, 0x88,
0x91, 0x8f,
0x91, 0x96,
0x91, 0xa3,
0x91, 0xaf,
0x91, 0xc4,
0x91, 0xd7,
0x91, 0xe8,
0x91, 0x20,
//
0x92, 0x00,
0x93, 0x06,
0x93, 0xe3,
0x93, 0x05,
0x93, 0x05,
0x93, 0x00,
0x93, 0x04,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
//
0x96, 0x00,
0x97, 0x08,
0x97, 0x19,
0x97, 0x02,
0x97, 0x0c,
0x97, 0x24,
0x97, 0x30,
0x97, 0x28,
0x97, 0x26,
0x97, 0x02,
0x97, 0x98,
0x97, 0x80,
0x97, 0x00,
0x97, 0x00,
//
0xc3, 0xef,
0xa4, 0x00,
0xa8, 0x00,
0xc5, 0x11,
0xc6, 0x51,
0xbf, 0x80,
0xc7, 0x10,
0xb6, 0x66,
0xb8, 0xA5,
0xb7, 0x64,
0xb9, 0x7C,
0xb3, 0xaf,
0xb4, 0x97,
0xb5, 0xFF,
0xb0, 0xC5,
0xb1, 0x94,
0xb2, 0x0f,
0xc4, 0x5c,
//
0xc0, 0xc8,
0xc1, 0x96,
0x8c, 0x00,
0x86, 0x3d,
0x50, 0x00,
0x51, 0x90,
0x52, 0x2c,
0x53, 0x00,
0x54, 0x00,
0x55, 0x88,
0x5a, 0x90,
0x5b, 0x2C,
0x5c, 0x05,
0xd3, 0x05,//auto设置要小心
//
0xc3, 0xed,
0x7f, 0x00,
0xda, 0x09,
0xe5, 0x1f,
0xe1, 0x67,
0xe0, 0x00,
0xdd, 0x7f,
0x05, 0x00,
};
//OV2640 SVGA初始化寄存器序列表
//此模式下,帧率可以达到30帧
//SVGA 800*600
const u8 ov2640_svga_init_reg_tbl[][2]=
{
0xff, 0x00,
0x2c, 0xff,
0x2e, 0xdf,
0xff, 0x01,
0x3c, 0x32,
//
0x11, 0x00,
0x09, 0x02,
0x04, 0xD8,//水平镜像,垂直翻转
0x13, 0xe5,
0x14, 0x48,
0x2c, 0x0c,
0x33, 0x78,
0x3a, 0x33,
0x3b, 0xfB,
//
0x3e, 0x00,
0x43, 0x11,
0x16, 0x10,
//
0x39, 0x92,
//
0x35, 0xda,
0x22, 0x1a,
0x37, 0xc3,
0x23, 0x00,
0x34, 0xc0,
0x36, 0x1a,
0x06, 0x88,
0x07, 0xc0,
0x0d, 0x87,
0x0e, 0x41,
0x4c, 0x00,
0x48, 0x00,
0x5B, 0x00,
0x42, 0x03,
//
0x4a, 0x81,
0x21, 0x99,
//
0x24, 0x40,
0x25, 0x38,
0x26, 0x82,
0x5c, 0x00,
0x63, 0x00,
0x46, 0x22,
0x0c, 0x3c,
//
0x61, 0x70,
0x62, 0x80,
0x7c, 0x05,
//
0x20, 0x80,
0x28, 0x30,
0x6c, 0x00,
0x6d, 0x80,
0x6e, 0x00,
0x70, 0x02,
0x71, 0x94,
0x73, 0xc1,
0x3d, 0x34,
0x5a, 0x57,
//根据分辨率不同而设置
0x12, 0x40,//SVGA 800*600
0x17, 0x11,
0x18, 0x43,
0x19, 0x00,
0x1a, 0x4b,
0x32, 0x09,
0x37, 0xc0,
//
0x4f, 0xca,
0x50, 0xa8,
0x5a, 0x23,
0x6d, 0x00,
0x3d, 0x38,
//
0xff, 0x00,
0xe5, 0x7f,
0xf9, 0xc0,
0x41, 0x24,
0xe0, 0x14,
0x76, 0xff,
0x33, 0xa0,
0x42, 0x20,
0x43, 0x18,
0x4c, 0x00,
0x87, 0xd5,
0x88, 0x3f,
0xd7, 0x03,
0xd9, 0x10,
0xd3, 0x82,
//
0xc8, 0x08,
0xc9, 0x80,
//
0x7c, 0x00,
0x7d, 0x00,
0x7c, 0x03,
0x7d, 0x48,
0x7d, 0x48,
0x7c, 0x08,
0x7d, 0x20,
0x7d, 0x10,
0x7d, 0x0e,
//
0x90, 0x00,
0x91, 0x0e,
0x91, 0x1a,
0x91, 0x31,
0x91, 0x5a,
0x91, 0x69,
0x91, 0x75,
0x91, 0x7e,
0x91, 0x88,
0x91, 0x8f,
0x91, 0x96,
0x91, 0xa3,
0x91, 0xaf,
0x91, 0xc4,
0x91, 0xd7,
0x91, 0xe8,
0x91, 0x20,
//
0x92, 0x00,
0x93, 0x06,
0x93, 0xe3,
0x93, 0x05,
0x93, 0x05,
0x93, 0x00,
0x93, 0x04,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
0x93, 0x00,
//
0x96, 0x00,
0x97, 0x08,
0x97, 0x19,
0x97, 0x02,
0x97, 0x0c,
0x97, 0x24,
0x97, 0x30,
0x97, 0x28,
0x97, 0x26,
0x97, 0x02,
0x97, 0x98,
0x97, 0x80,
0x97, 0x00,
0x97, 0x00,
//
0xc3, 0xed,
0xa4, 0x00,
0xa8, 0x00,
0xc5, 0x11,
0xc6, 0x51,
0xbf, 0x80,
0xc7, 0x10,
0xb6, 0x66,
0xb8, 0xA5,
0xb7, 0x64,
0xb9, 0x7C,
0xb3, 0xaf,
0xb4, 0x97,
0xb5, 0xFF,
0xb0, 0xC5,
0xb1, 0x94,
0xb2, 0x0f,
0xc4, 0x5c,
//根据分辨率不同而设置
0xc0, 0x64,
0xc1, 0x4B,
0x8c, 0x00,
0x86, 0x3D,
0x50, 0x00,
0x51, 0xC8,
0x52, 0x96,
0x53, 0x00,
0x54, 0x00,
0x55, 0x00,
0x5a, 0xC8,
0x5b, 0x96,
0x5c, 0x00,
0xd3, 0x02,//auto设置要小心
//
0xc3, 0xed,
0x7f, 0x00,
0xda, 0x09,
0xe5, 0x1f,
0xe1, 0x67,
0xe0, 0x00,
0xdd, 0x7f,
0x05, 0x00,
};
const u8 ov2640_jpeg_reg_tbl[][2]=
{
0xff, 0x01,
0xe0, 0x14,
0xe1, 0x77,
0xe5, 0x1f,
0xd7, 0x03,
0xda, 0x10,
0xe0, 0x00,
};
const u8 ov2640_rgb565_reg_tbl[][2]=
{
0xFF, 0x00,
0xDA, 0x09,
0xD7, 0x03,
0xDF, 0x02,
0x33, 0xa0,
0x3C, 0x00,
0xe1, 0x67,
0xff, 0x01,
0xe0, 0x00,
0xe1, 0x00,
0xe5, 0x00,
0xd7, 0x00,
0xda, 0x00,
0xe0, 0x00,
};
const u8 ov2640_yuv422_reg_tbl[][2]=
{
0xFF, 0x00,
0xDA, 0x10,
0xD7, 0x03,
0xDF, 0x00,
0x33, 0x80,
0x3C, 0x40,
0xe1, 0x77,
0x00, 0x00,
};
#endif
8.5 SCCB.c
#include "stm32f4xx.h"
#include "SCCB.h"
#include "delay.h"
//初始化SCCB接口
//SCCB时序和IIC时序是非常相似的,这里和配置IIC时序的程序相类似,DCMI的SCL和SDA总线分别接在MCU的PD6和PD7上
void SCCB_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE); //使能GPIOD时钟
//PD6 PD7 引脚设置
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //IIC引脚推挽输出
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStructure); //初始化GPIO
GPIO_SetBits(GPIOD,GPIO_Pin_6|GPIO_Pin_7); //IIC拉高两个引脚SCL SDA,IIC呈现空闲状态
SCCB_SDA_OUT(); //初始化设置输出模式,为发送数据做准备
}
//SCCB起始信号
//当时钟为高电平时,数据线由高变低表示起始信号
//在激活状态下,SDA和SCL均为低电平
void SCCB_Start(void)
{
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(50);
SCCB_SDA=0;
delay_us(50);
SCCB_SCL=0;
}
//SCCB停止信号
//当时钟总线为高电平时,数据线由低电平变为高电平表示SCCB停止信号
//空闲状态下,SCL和SDA均为高电平
void SCCB_Stop(void)
{
SCCB_SDA=0;
delay_us(50);
SCCB_SCL=1;
delay_us(50);
SCCB_SDA=1;
delay_us(50);
}
//产生非应答NAck信号
void SCCB_No_Ack(void)
{
delay_us(50);
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(50);
SCCB_SCL=0;
delay_us(50);
SCCB_SDA=0;
delay_us(50);
}
//SCCB写入一个字节
//返回值:0,成功;1,失败
u8 SCCB_Write_Byte(u8 data)
{
u8 i,res;
for(i=0;i<8;i++) //循环8次发送数据
{
if(data&0x80) //IIC高位在前
SCCB_SDA=1; //判断是否高位在前,如果高位在前,就把这一位置1
else
SCCB_SDA=0;
data=data<<1; //每循环一次,就左移一位,保证每次发送的数据都是最高位
delay_us(50);
SCCB_SCL=1; //SCL高电平期间写入数据
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_IN(); //SCCB写字节,那么对应的MCU引脚设置为输入
delay_us(50);
SCCB_SCL=1; //接收第九位,以判断是否发送成功,第九位是从机给的应答位
delay_us(50);
if(SCCB_READ_SDA) //SDA=1,发送失败 也就是IIC时序中是否收到应答
res=1;
else //SDA=0,发送成功
res=0; //返回0
SCCB_SCL=0;
SCCB_SDA_OUT(); //设置SDA为输出,为下次写字节做准备
return res;
}
//SCCB读取一个字节
//在SCL上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_Read_Byte(void)
{
u8 temp=0;
u8 j;
SCCB_SDA_IN(); //设置SDA为输入 SCCB读取一个字节,每一个读取到的数据都是要存到自己的寄存器里的
for(j=8;j>0;j--)
{
delay_us(50);
SCCB_SCL=1;
temp=temp<<1; //这两句代码的意思是:每次循环都让temp左移一位
if(SCCB_READ_SDA)//读到的数据为真,表示读取到数据了,temp++,8位temp每次++,每次++都是从最低位开始++的,所以并不会影响到最低位
temp++;
//这两句代码的总体作用会使得每次循环读取的数据依次放在上一次读取的下一位上,也就是第一次读取放在最低位0000 0001
//然后左移一位0000 0010,再次读取如果为真会变成0000 0011,这样一来会保证第一次接收到的数据最终8位循环以后会放到最高位
//反之,如果不进入if判断语句中,那么直接就是逻辑0,直接左移,就代表接收到了低电平
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_OUT(); //设置SDA为输出,为下次读取字节做准备
return temp;
}
//写寄存器
//返回值:0,成功;1,失败
u8 SCCB_Write_Register(u8 Register,u8 Data)
{
//IIC时序规定:写寄存器时首先写器件地址,(把器件地址的最低位改为1,即表示在写的基础上改为了读)然后写寄存器地址,然后写数据
u8 res=0;
SCCB_Start(); //开始SCCB传输
if(SCCB_Write_Byte(SCCB_ID)) //写器件地址,SCCB_Write_Byte函数返回值如果为1则表示失败
res=1;
delay_us(100);
if(SCCB_Write_Byte(Register)) //写寄存器地址,如果不进入if判断语句,就意味着寄存器地址写入成功了
res=1;
delay_us(100);
if(SCCB_Write_Byte(Data)) //写数据
res=1;
SCCB_Stop(); //停止SCCB传输
return res;
}
//读寄存器
//返回值:读到的寄存器值
u8 SCCB_Read_Register(u8 Register)
{
u8 Value=0;
SCCB_Start(); //开始SCCB传输
SCCB_Write_Byte(SCCB_ID); //写器件地址
delay_us(100);
SCCB_Write_Byte(Register); //写寄存器地址
delay_us(100);
SCCB_Stop();
delay_us(100);
SCCB_Start();
SCCB_Write_Byte(SCCB_ID|0x01); //发送读命令
delay_us(100);
Value=SCCB_Read_Byte(); //读取数据赋值给Value
SCCB_No_Ack();
SCCB_Stop();
return Value;
}
8.6 SCCB.h
#ifndef _SCCB__H_
#define _SCCB__H_
#include "sys.h"
//IO方向设置
//还是通过位段的方式进行设置,首先将引脚对应的位清空,然后再将对应位填上,00表示输入,01表示输出
#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 输出
//IO操作函数
#define SCCB_SCL PDout(6) //SCL
#define SCCB_SDA PDout(7) //SDA
#define SCCB_READ_SDA PDin(7) //输入SDA
#define SCCB_ID 0X60 //OV2640的ID
void SCCB_Init(void);
void SCCB_Start(void);
void SCCB_Stop(void);
void SCCB_No_Ack(void);
u8 SCCB_Write_Byte(u8 data);
u8 SCCB_Read_Byte(void);
u8 SCCB_Write_Register(u8 Register,u8 Data);
u8 SCCB_Read_Register(u8 Register);
#endif
8.7 DCMI.c
#include "stm32f4xx.h"
#include "DCMI.h"
#include "LCD.h"
#include "LED.h"
#include "OV2640.h"
u8 ov_frame=0; //帧率
extern void jpeg_data_process(void); //JPEG数据处理函数
DCMI_InitTypeDef DCMI_InitStructure;
//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++; //捕获一帧图像,帧率++
}
}
//DCMI DMA配置
//DMA_MemoryBaseAddress:存储器地址 用来存储摄像头数据的内存地址,也可以是外设地址
//DMA_BufferSize:存储器长度 0~65535
//DMA_MemoryDataSize:存储器位宽
//DMA_MemoryInc:存储器增长方式
void DCMI_DMA_Init(u32 DMA_MemoryBaseAddress,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE); //DMA2时钟使能
DMA_DeInit(DMA2_Stream1); //初始化DMA2数据流1通道
while(DMA_GetCmdStatus(DMA2_Stream1)!=DISABLE); //等待待DMA2_Stream1可配置
//配置DMA Stream
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_BufferSize=DMA_BufferSize; //数据传输量
DMA_InitStructure.DMA_Channel=DMA_Channel_1; //通道1 DCMI通道
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralToMemory; //外设到存储器模式
DMA_InitStructure.DMA_FIFOMode=DMA_FIFOMode_Enable; //FIFO模式
DMA_InitStructure.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;//使用全FIFO
DMA_InitStructure.DMA_Memory0BaseAddr=DMA_MemoryBaseAddress;
DMA_InitStructure.DMA_MemoryBurst=DMA_MemoryBurst_Single;//外设突发单次传输
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc;
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;// 使用循环模式
DMA_InitStructure.DMA_PeripheralBaseAddr=(u32)&DCMI->DR;//外设地址为:DCMI->DR
DMA_InitStructure.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;//存储器突发单次传输
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;//外设数据长度:32位
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_Priority=DMA_Priority_High;//高优先级
DMA_Init(DMA2_Stream1,&DMA_InitStructure);
}
//DCMI初始化
void My_DCMI_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOA B C E 时钟
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE); //使能DCMI时钟
//PA4/6初始化设置
GPIO_InitTypeDef GPIO_InitStructure;
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;//PC6/7/8/9/11 复用功能输出
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;//PE5/6 复用功能输出
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化
//引脚复用为DCMI
GPIO_PinAFConfig(GPIOA,GPIO_PinSource4,GPIO_AF_DCMI); //PA4,AF13 DCMI_HSYNC
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_DCMI); //PA6,AF13 DCMI_PCLK
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_DCMI); //PB7,AF13 DCMI_VSYNC
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_DCMI); //PC6,AF13 DCMI_D0
GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_DCMI); //PC7,AF13 DCMI_D1
GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_DCMI); //PC8,AF13 DCMI_D2
GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI); //PC9,AF13 DCMI_D3
GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_DCMI);//PC11,AF13 DCMI_D4
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_DCMI); //PB6,AF13 DCMI_D5
GPIO_PinAFConfig(GPIOE,GPIO_PinSource5,GPIO_AF_DCMI); //PE5,AF13 DCMI_D6
GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI); //PE6,AF13 DCMI_D7
DCMI_DeInit(); //清除原来的设置,重新设置DCMI的工作模式
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;//硬件同步HSYNC,VSYNC
DCMI_InitStructure.DCMI_VSPolarity=DCMI_VSPolarity_Low;//VSYNC 低电平有效
DCMI_Init(&DCMI_InitStructure);
DCMI_ITConfig(DCMI_IT_FRAME,ENABLE); //开启帧中断
DCMI_Cmd(ENABLE); //DCMI使能
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=DCMI_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; //子优先级0
NVIC_Init(&NVIC_InitStructure);
}
//DCMI启动传输
void DCMI_Start(void)
{
LCD_SetCursor(0,0); //设置光标位置(0,0)
LCD_WriteRAM_Prepare(); //开始写入GRAM
DMA_Cmd(DMA2_Stream1,ENABLE); //开启DAM2,数据流1
DCMI_CaptureCmd(ENABLE); //DCMI捕获使能
}
//DCMI关闭传输
void DCMI_Stop(void)
{
DCMI_CaptureCmd(DISABLE); //DCMI捕获使能关闭
while(DCMI->CR&0x01); //等待传输结束 DCMI控制寄存器的最低位控制开启捕获或者禁止捕获,1:使能 0:禁止
//只要在使能,就在while空循环中等待,一旦跳出while循环,意味着禁止使能了
//关闭传输的指令发出后,会等待所有传输结束后,才会禁止捕获
DMA_Cmd(DMA2_Stream1,DISABLE); //关闭DAM2,数据流1
}
//以下两个函数,供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);
OV2640_OutSize_Set(width,height);
LCD_SetCursor(0,0);
LCD_WriteRAM_Prepare(); //开始写入GRAM
DMA_Cmd(DMA2_Stream1,ENABLE); //开启DMA2,Stream1
DCMI_CaptureCmd(ENABLE);//DCMI捕获使能
}
//通过usmart调试,辅助测试用.
//pclk/hsync/vsync:三个信号的有限电平设置
void DCMI_CR_Set(u8 pclk,u8 hsync,u8 vsync)
{
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= hsync<<6;//HSYNC 低电平有效
DCMI_InitStructure.DCMI_PCKPolarity= pclk<<5;//PCLK 上升沿有效
DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步HSYNC,VSYNC
DCMI_InitStructure.DCMI_VSPolarity=vsync<<7;//VSYNC 低电平有效
DCMI_Init(&DCMI_InitStructure);
DCMI_CaptureCmd(ENABLE);//DCMI捕获使能
DCMI_Cmd(ENABLE); //DCMI使能
}
8.8 DCMI.h
#ifndef _DCMI__H_
#define _DCMI__H_
#include "sys.h"
void DCMI_IRQHandler(void);
void DCMI_DMA_Init(u32 DMA_MemoryBaseAddress,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc);
void My_DCMI_Init(void);
void DCMI_Start(void);
void DCMI_Stop(void);
void DCMI_Set_Window(u16 sx,u16 sy,u16 width,u16 height);
void DCMI_CR_Set(u8 pclk,u8 hsync,u8 vsync);
#endif
8.9 USART2.c
#include "stm32f4xx.h"
#include "USART2.h"
//初始化串口2
//PCLK1:PCLK1时钟频率(MHz)
//Bound:波特率
void Usart2_Init(u32 PCLK1,u32 Bound)
{
//串口2连接MCU芯片PA2和PA3
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //使能USART2时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF; //模式设置为复用,因为该引脚不只是串口2一个功能
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //PA2复用为串口2
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //PA3复用为串口2
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate=Bound;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //无硬件数据流控制
USART_InitStructure.USART_Mode=USART_Mode_Tx; //发送模式
USART_InitStructure.USART_Parity=USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_StopBits=USART_StopBits_1; //1个停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b; //字长为8位的数据长度
USART_Init(USART2,&USART_InitStructure);
USART_Cmd(USART2,ENABLE); //使能串口
}
8.10 USART2.h
#ifndef _USART2__H_
#define _USART2__H_
#include "sys.h"
#include "stdio.h"
void Usart2_Init(u32 PCLK1,u32 Bound);
#endif
更多推荐
所有评论(0)