【FPGA】摄像头模块OV5640
本篇文章包含的内容
- 一、OV5640简介
- 1.1 基本概述
- 1.2 工作时序
- 1.2.1 DVP Timing(数据传输时序)
- 1.2.2 帧曝光工作模式
- 1.3 OV5640 闪光灯工作模式
- 1.3.1 Xenon Flash(氙灯闪烁)模式
- 1.3.2 LED 1&2 模式
- 1.3.3 LED 3模式
- 1.3.4 手动开启闪光灯
- 1.4 模块硬件设计
- 二、SCCB通信协议
- 2.1 SCCB读写时序
- 2.2 SCCB与I2C的区别
- 三、代码设计
- 3.1 设置输出像素大小
- 3.1.1 设置 ISP input size(X/Y_ADDR)
- 3.1.2 设置 pre-scaling size (OFFSET)
- 3.1.3 设置 data output size(OUTPUT)
- 3.2 读取摄像头输出像素数据
- 开发板:正点原子的达芬奇开发板(或MicroPhase的Z7-Lite 7020开发板)
- FPGA型号:XC7A35TFGG484-2(或XC7Z020CLG400-2)
- Vivado版本:2020.2
- 参考课程链接:正点原子手把手教你学FPGA-基于达芬奇开发板 A7
- OV5640模块:正点原子ATK-OV5640
一、OV5640简介
1.1 基本概述
OV5640是OV(OMNIVISION)公司设计的一款CMOS图像传感器,最高输出500万像素的图像,最高分辨篇格式为QSXVGA(2592×1944),数据接口采用DVP,控制接口为SCCB。可以输出RGB565/RGB555/RGB444、YUV(422/420)、YCbCr422和JPEG格式,可以对图像进行白平衡、饱和度、色度、锐度、Gamma曲线等调节。图像分辨率、帧率可调。
OV5640支持LED补光、MIPI(移动产业处理器接口,常用于手机摄像头)输出接口和 DVP(数字视频并行,本次实验使用这个接口) 输出接口选择、ISP(图像信号处理)以及AFC(自动聚焦控制)等功能。
控制OV5640的核心在于了解它的时序电路和数据输出之间的关系,在FPGA端需要注意以下标黄的端口,它们都是OV5640的控制输入引脚。下面对时序生成和系统逻辑控制电路的引脚作一些说明:
PWDN
:休眠控制RESETB
:复位信号FREX
:帧曝光控制GPIO[3:0]
:和自动聚焦和防震动相关配置有关(本次实验没有用到)PCLK
:像素同步时钟(由模块板载晶振提供)HREF
:行同步信号,可以理解为像素数据有效信号VSYNC
:场同步信号STROBE
:闪光灯控制(连接到模块板载LED)
OV5640支持2592×1944(QSXVGA)及以下任意分辨率图像的输出。它是通过缩放和修改相关像素定位寄存器实现的。相关配置寄存器的配置地址及意义由下图给出。
X/Y_ADDR_ST/END
:输出像素的开始位置和结束位置。X/Y_OFFSET
:缩放前输出场的偏移。X/Y_OUTPUT_SIZE
:输出像素大小。
1.2 工作时序
1.2.1 DVP Timing(数据传输时序)
上图表示了OV5640在DVP模式下的数据输出时序,同时列出了以VGA(640×480)分辨率为例的不同区间的时间。
tp
:在RGB565格式下,一个 t p tp tp等于两倍的 t P C L K t_{PCLK} tPCLK,因为一个像素的有效数据需要两个时钟周期传输。
1.2.2 帧曝光工作模式
OV5640拥有三种曝光模式,在模式0下,帧曝光控制引脚FREQ
作输入,曝光脉冲请求通过FPGA发出;在模式1下,曝光请求通过I2C(SCCB)总线发出,帧曝光控制引脚FREQ
作输出,通知外部主机(FPGA)将要开始曝光,LED功能只有在模式0和模式1下才会工作;滚动曝光模式下曝光控制功能失效。
1.3 OV5640 闪光灯工作模式
在了解闪光灯信号strobe
信号之前,需要注意,闪光灯的所有工作模式只在帧曝光模式0和模式1下有效。滚动帧曝光下闪光灯不工作。
1.3.1 Xenon Flash(氙灯闪烁)模式
"Xenon"是指氙灯(xenon flash)模式。氙灯是一种基于氙气放电的强光源,当电流通过充满氙气的灯管时,氙气被激发产生亮白色的光。在摄影和摄像中,氙灯闪光灯被广泛用于提供短时间内的高强度照明,以便在拍摄照片或视频时,尤其是在低光环境下,获得更好的曝光效果。在脉冲请求strobe request
信号到来后,经历三帧时间,strobe pulse
将输出一个极短的脉冲。该功能需要通过寄存器配置。上电后默认工作在该模式下。
1.3.2 LED 1&2 模式
LED1和LED2模式均为LED闪烁模式,在LED曝光期间的帧数据会跳过输出。该功能需要寄存器配置。
1.3.3 LED 3模式
在LED3模式下,LED灯会保持常亮。
1.3.4 手动开启闪光灯
与LED工作模式无关,我们可以通过SCCB总线发送指令修改寄存器的值,手动开启或关闭闪光灯。通过依次配置寄存器0x3016
、0x301C
、0x3019
的Bit[1]
即可打开或关闭闪光灯。
1.4 模块硬件设计
二、SCCB通信协议
SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OMNIVISION)公司定义和发展的两线/三线式串行总线。该总线控制OV系列摄像头大部分的功能,包括图像数据格式、分辨篇以及图像处理参数等。两线SCCB只能实现“一主一从”的控制,而三线结构可以实现对多个从机进行控制。OV公司为了减少传感器引脚的封装,现在SCCB大多采用两线式接口总线。
SIO_C/SCL
:只能由主机(FPGA)配置;SIO_D/SDA
:上面有一个三态门,可以实现双向数据传输,可以由主机控制,也可以由从机控制
SCCB协议与I2C协议十分相似,甚至在有些驱动历程中直接将I2C的驱动程序拿来使用。了解I2C协议之前有必要熟悉I2C协议的相关使用,在这里不作过多赘述。
2.1 SCCB读写时序
SCCB 总线跟 I2C 十分类似,起始信号、停止信号与 I2C 一样,SCCB 定义数据传输的基本单元为相(phase),每个相传输一个字节数据。SCCB 只包含三种传输周期:三相写周期、两相写周期和两相读周期。
-
三相写周期:依次为(开始信号)设备地址(写命令)、寄存器地址、数据(结束信号)。
-
两相写周期:依次为(开始信号)设备地址(写命令)、寄存器地址(结束信号)。
-
两相读周期:依次为(开始信号)设备地址(读命令)、数据(结束信号)。
在写入数据时,只执行三相写周期;在读出数据时,先执行两相写周期(虚写),再执行两相读周期。在SCCB协议中,每一相后有一个X
信号,表示不需要关心这一位信号,与I2C协议不同,SCCB协议不用等待从器件响应(拉低)。两相读周期最后的NA
信号表示不响应,即主机不会拉低信号线给从机发送响应。
2.2 SCCB与I2C的区别
- SCCB第九位为不关心位,而I2C传输协议中为应答位。
- SCCB每次传输不超过三相(Phase),即不能连续读写;I2C可以连续读写。
- SCCB读传输协议中没有重复开始的操作,在虚写完成之后,必须先发出一次停止信号,再重新发出开始信号;I2C协议中不需要在虚写之后添加停止,只需要再发送一次开始信号并执行后续操作即可。
三、代码设计
3.1 设置输出像素大小
OV5640通过SCCB总线写入对应位置寄存器设置输出像素大小。实际上I2C的驱动程序完全可以兼容SCCB通信,所以重点在于OV5640初始化工作中对寄存器值的操作,在正点原子的历程中要配置250个寄存器,还是相当繁琐的。其中首先要注意的就是输出像素大小的定义。
3.1.1 设置 ISP input size(X/Y_ADDR)
/* 部分设置代码…… */
// 设置感光区域的“开窗大小”,开窗区域不是最终的显示区域
8'd212:
i2c_data <= {16'h3800,8'h00};
8'd213:
i2c_data <= {16'h3801,8'h00}; // 起始点x坐标 16'h0000
8'd214:
i2c_data <= {16'h3802,8'h00};
8'd215:
i2c_data <= {16'h3803,8'h04}; // 起始点x坐标 16'h0004
8'd216:
i2c_data <= {16'h3804,8'h0a};
8'd217:
i2c_data <= {16'h3805,8'h3f}; // 终止点x坐标 16'h0a3f = 16'd2623
8'd218:
i2c_data <= {16'h3806,8'h07};
8'd219:
i2c_data <= {16'h3807,8'h9b}; // 终止点y坐标 16'h079b = 16'd1947
3.1.2 设置 pre-scaling size (OFFSET)
/* 部分设置代码…… */
// 设定画幅OFFSET,在这里X_OFFSET = 0x0010 (16)
8'd46 :
i2c_data <= {16'h3810,8'h00}; // Timing Hoffset[11:8]
8'd47 :
i2c_data <= {16'h3811,8'h10}; // Timing Hoffset[7:0]
8'd48 :
i2c_data <= {16'h3812,8'h00}; // Timing Voffset[10:8]
/* 部分设置代码…… */
// OFFSET设置,Y_OFFSET = 0x0006(6)
8'd228:
i2c_data <= {16'h3813,8'h06}; // Timing Voffset[7:0]
3.1.3 设置 data output size(OUTPUT)
OV5640内部的ISP算法可以直接将压缩前获得的图像通过算法压缩成定义的像素大小,只需要在输入中给出像素大小即可。
/* 部分设置代码…… */
//设置输出像素个数(ISP压缩到800*480)
//DVP 输出水平像素点数高4位
8'd220:
i2c_data <= {16'h3808,{4'd0,cmos_h_pixel[11:8]}};
//DVP 输出水平像素点数低8位
8'd221:
i2c_data <= {16'h3809,cmos_h_pixel[7:0]};
//DVP 输出垂直像素点数高3位
8'd222:
i2c_data <= {16'h380a,{5'd0,cmos_v_pixel[10:8]}};
//DVP 输出垂直像素点数低8位
8'd223:
i2c_data <= {16'h380b,cmos_v_pixel[7:0]};
//水平总像素大小高5位
8'd224:
i2c_data <= {16'h380c,{3'd0,total_h_pixel[12:8]}};
//水平总像素大小低8位
8'd225:
i2c_data <= {16'h380d,total_h_pixel[7:0]};
//垂直总像素大小高5位
8'd226:
i2c_data <= {16'h380e,{3'd0,total_v_pixel[12:8]}};
//垂直总像素大小低8位
8'd227:
i2c_data <= {16'h380f,total_v_pixel[7:0]};
3.2 读取摄像头输出像素数据
cmos_capture_data.v
module cmos_capture_data(
input rst_n, // 复位信号
input cam_pclk, // coms 数据像素同步时钟
input cam_vsync, // coms 场同步信号
input cam_href, // cmos 行有效信号(数据输出有效)
input [7:0] cam_data,
output cmos_frame_vsync,
output cmos_frame_href,
output cmos_frame_valid,
output [15:0] cmos_frame_data
);
// 寄存器全部配置完成后,先等待10帧的时间
// 待寄存器配置生效后再开始采集图像
parameter WAIT_FRAME = 4'd10; // 寄存器等待稳定的帧个数
// reg define
reg cam_vsync_d0;
reg cam_vsync_d1;
reg cam_href_d0;
reg cam_href_d1;
reg [3:0] cmos_ps_cnt; // 等待帧数稳定计数器
reg [7:0] cam_data_d0;
reg [15:0] cmos_data_t; // 8位转16位临时寄存器
reg byte_flag; // 16位RGB数据转换完成的标志信号
reg byte_flag_d0;
reg frame_val_flag; // 帧有效标志
wire pos_vsync; // 采输入场同步信号的上升沿
// 采输入场同步信号的上升沿
assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0;
// 输出帧有效信号和行有效信号
assign cmos_frame_vsync = frame_val_flag ? cam_vsync_d1 : 1'b0;
assign cmos_frame_href = frame_val_flag ? cam_href_d1 : 1'b0;
// 输出数据使能有效信号
assign cmos_frame_valid = frame_val_flag ? byte_flag_d0 : 1'b0;
// 输出像素数据
assign cmos_frame_data = frame_val_flag ? cmos_data_t : 1'b0;
// 对摄像头的场同步信号和行同步信号进行打拍获取上升沿
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n) begin
cam_vsync_d0 <= 1'b0;
cam_vsync_d1 <= 1'b0;
cam_href_d0 <= 1'b0;
cam_href_d1 <= 1'b0;
end
else begin
cam_vsync_d0 <= cam_vsync;
cam_vsync_d1 <= cam_vsync_d0;
cam_href_d0 <= cam_href;
cam_href_d1 <= cam_href_d0;
end
end
// 对帧数进行计数
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n)
cmos_ps_cnt <= 4'd0;
else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
end
// 帧有效标志
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n)
frame_val_flag <= 1'b0;
else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
frame_val_flag <= 1'b1;
end
// 8位数据转16位RGB565数据
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n) begin
cmos_data_t <= 16'd0;
cam_data_d0 <= 8'd0;
byte_flag <= 1'b0;
end
else if(cam_href) begin // 当数据有效信号
byte_flag <= ~byte_flag;
cam_data_d0 <= cam_data; // 打拍暂存上一个pclk的字节数据
if(byte_flag)
cmos_data_t <= {cam_data_d0, cam_data};
end
else begin
byte_flag <= 1'b0;
cam_data_d0 <= 8'b0;
end
end
// 打一拍产生输出数据有效信号 (cmos_frame_valid)
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n)
byte_flag_d0 <= 1'b0;
else
byte_flag_d0 <= byte_flag;
end
endmodule
持续不定期更新完善中……
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。