目录
在前面介绍了使用 VDMA + VTC + Video Out 搭建的显示通路,后端接转码 TMDS 差分信号输出到 HDMI 上;这里不再使用静态图片,使用 OV5640 摄像头作为 Video 输入,最终将视频信息输出到 HDMI 屏上;数据的输出部分,我们沿用之前的 VDMA + VTC + Dynamic Clock + rgb2dvi 那套通路;数据的输入部分需要研究一下 OV5640 的时序,以及如何将数据抓到我们内部并进行传输;
这里有几个点需要进行分析:
1、如何配置 OV5640 让其输出视频;
2、如何将 OV5640 的数据,怼到我们内部;
3、之前 VDMA 只有将数据从内存搬运出来,这里增加了数据源的输入通路,如何处理竞争;
1、OV5640
介绍 OV5640 的资料很多很多,这里简单的叙述一下即可,他是一颗 CMOS 摄像头,其感光阵列达到2592*1944(即500W像素),能实现最快15fps QSXVGA(2592*1944)或者90fps VGA(640*480)分辨率的图像采集。传感器采用OmniVision推出的OmniBSI(背面照度)技术,使传感器达到更高的性能,如高灵敏度、低串扰和低噪声。传感器内部集成了图像处理的功能,包括自动曝光控制(AEC)、自动白平衡(AWB)等。同时该传感器支持LED补光、MIPI(移动产业处理器接口)输出接口和 DVP(数字视频并行)输出接口选择、ISP(图像信号处理)以及AFC(自动聚焦控制)等功能;
它的内部框图如下所示:
由上图可知,时序发生器(timing generator)控制着感光阵列(image array)、放大器(AMP)、AD转换以及输出外部时序信号(VSYNC、HREF 和 PCLK),外部时钟XVCLK经过PLL锁相环后输出的时钟作为系统的控制时钟;感光阵列将光信号转化成模拟信号,经过增益放大器之后进入10位AD转换器;AD转换器将模拟信号转化成数字信号,并且经过ISP进行相关图像处理,最终输出所配置格式的10位视频数据流。增益放大器控制以及ISP等都可以通过寄存器(Registers)来配置,配置寄存器的接口就是SCCB接口,该接口协议兼容 IIC 协议;
1.1、OV5640 Input Signals
我们整理一个简单的 OV5640 的输入输出信号:
XVCLK 输入时钟,SCCB 用于对 OV5640 进行配置,配置的内容可以参考 OV5640 的datasheet 和《OV5640_自动对焦照相模组应用指南(DVP_接口)》;
SCCB 的协议具体描述,参考文档:《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》,网上很多介绍,这里我们不再多说;
SCCB 可以配置很多 OV5640 的关键特性,包括 XVCLK 输入后的 PLL,输出图像的格式(YUV/RGB),输出方式 MIPI/DVP,是否需要使用 ISP 进行处理,等等,有兴趣可以研究一下 datasheet 并进行尝试;
1.2、OV5640 Output Signals
通过 SCCB 配置完 OV5640 后,那么 OV5640 便开始输出图像数据了,它支持 1080p,720p 等等,以及不同分辨率,这些都囊括在 SCCB 配置中;同样分辨率下,不同帧率的输出,主要体现在 pclk 信号上,pclk 信号是输出的时钟,提频导致输出更多的数据,也就是帧率的含义了;
打个比如:
VSYNC:场同步信号,由摄像头输出,用于标志一帧数据的开始与结束。上图中VSYNC的高电平作为一帧的同步信号,在低电平时输出的数据有效。需要注意的是场同步信号是可以通过设置寄存器位进行取反的,即低电平同步高电平有效,这里和上图一致的默认设置;
HREF/HSYNC:行同步信号,由摄像头输出,用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样。本次实验使用的是HREF格式输出,当HREF为高电平时,图像输出有效,可以通过寄存器进行配置。这里使用的是HREF格式输出;
D[9:0]:数据信号,由摄像头输出,在RGB格式输出中,只有高8位D[9:2]是有效的;
tPCLK:一个像素时钟周期;
tp:单个数据周期,这里需要注意的是上图中左下角红框标注的部分,在RGB模式中,tp代表两个tPCLK(像素时钟)。以RGB565数据格式为例,RGB565采用16bit数据表示一个像素点,而OV5640在一个像素周期(tPCLK)内只能传输8bit数据,因此需要两个时钟周期才能输出一个RGB565数据;
这里使用 DVP + RGB565 方式进行输出,每两个 PCLK 传输一个像素,它的定义为:
2、Video In to AXI4-Stream
上面描述了如何配置和使用 OV5460 进行输出图像数据,它输出的数据是 8bit + 8bits 的图像数据,这里我们需要将这个数据怼到我们的系统中(内存中),为何要放到内存中呢,因为 VDMA 的输出是从 DDR framebuffer 去拿数据,所以呢,我们要想办法将视频数据仍到 DDR 中,让 VDMA 去拿;
VDMA 管理的到内存的写入是通过 S2MM 接口进行的,输入端是 AXI-Stream 的数据流,输出端是 AXI HP 到 memory controller,所以呢,这里,我们需要将图像数据转成为 AXI-Stream 数据,并接到 VDMA 的 S2MM 接口;
这里我们写一个 IP,来对 OV5640 的数据进行抓取,同时将数据怼到 Video In to AXI4-Stream IP ,利用 Video In to AXI4-Stream 进行输出 AXI-Stream 到 VDMA,在到 DDR;既然要用到 Video In to AXI4-Stream,那么我们大概分析一下他的输入,这样我们才能够去设计一个采集 OV5640 的 IP,将数据灌入到 DDR;
这里我们为了将数据输入到 Video In to AXI4-Stream,我们需要了解 Video In to AXI4-Stream 输入的数据和时序:
Video In to AXI4-Stream
为了简化设计,我们这里设计的 IP 包含以下几个信号,并且直接输入到 Video In to AXI4-Stream:
1、clk:时钟信号;
2、clk_en:时钟有效信号;
3、vsync:一帧的起始;
4、active:图像有效信号;
5、data[23:0]:RGB888 的数据信号;
这里需注意的一点:我们采集到数据是 2 个 pclk 产生一个像素,像素是 16bits 的 RGB565,我们需要将 RGB565 -> RGB888,并输入到 Video In to AXI4-Stream,我们的设计文件为:
`timescale 1ns / 1ps
//
// Company : Stephen Zhou Tech
// Engineer : StephenZhou
//
// Create Date : 2021/05/20 14:57:44
// Design Name :
// Module Name : Steph_OVSensorDVPCaptureV2
// Project Name :
// Target Devices : Zynq-7000
// Tool Versions :
// Description : Capture the OV5640 Image data
//
// Dependencies :
//
// Revision:
// Revision 0.02 - File Created
// Additional Comments:
//
//
module Steph_OVSensorDVPCaptureV2(
// enable
input ov_en_i ,
// OV5640 Signals
input ov_pclk_i ,
input ov_vsync_i ,
input ov_href_i ,
input [9:0] ov_data_i ,
// Output to Video In AXI Stream
output vid_clk_o ,
output vid_clk_en_o ,
output vid_vsync_o ,
output vid_active_o ,
output [23:0] vid_data_o
);
// Wair for video valid counter
localparam VIDEO_STABLE_FRAME_CNT = 4'd15;
reg vsync_d0 ;
reg vsync_d1 ;
wire vsync_pos ;
reg [3:0] vsync_cnt ;
reg wait_done ;
reg ov_href_d0 ;
reg ov_href_d1 ;
reg [7:0] data_in_d0 ;
reg [15:0] data_16bits ;
reg data_merge ;
reg data_merge_d0 ;
assign vsync_pos = (~vsync_d1) & vsync_d0;
assign vid_clk_o = ov_pclk_i;
assign vid_vsync_o = wait_done ? (vsync_d1) : 1'b0;
assign vid_active_o = wait_done ? (ov_href_d1) : 1'b0;
assign vid_clk_en_o = wait_done ? ((data_merge_d0 & vid_active_o) || (!vid_active_o)) : 1'b0;
assign vid_data_o = wait_done ? { data_16bits[15:11],3'd0 , data_16bits[10:5],2'd0 , data_16bits[4:0],3'd0 } : 24'd0;
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
vsync_d0 <= 1'b0;
vsync_d1 <= 1'b0;
end
else begin
vsync_d0 <= ov_vsync_i;
vsync_d1 <= vsync_d0;
end
end
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
vsync_cnt <= 4'd0;
end
else if (vsync_pos && (vsync_cnt < VIDEO_STABLE_FRAME_CNT)) begin
vsync_cnt <= vsync_cnt + 1'b1;
end
else begin
vsync_cnt <= vsync_cnt;
end
end
// Generate wait done signal
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
wait_done <= 4'd0;
end
else if (vsync_pos && (vsync_cnt == VIDEO_STABLE_FRAME_CNT)) begin
wait_done <= 1'b1;
end
else begin
wait_done <= wait_done;
end
end
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
ov_href_d0 <= 4'd0;
ov_href_d1 <= 4'd0;
end
else begin
ov_href_d0 <= ov_href_i;
ov_href_d1 <= ov_href_d0;
end
end
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
data_in_d0 <= 8'd0;
end
else if (ov_href_i) begin
data_in_d0 <= ov_data_i[9:2];
end
else begin
data_in_d0 <= 8'd0;
end
end
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
data_merge <= 1'b0;
end
else if (ov_href_i) begin
data_merge <= ~data_merge;
end
else begin
data_merge <= 1'b0;
end
end
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
data_16bits <= 1'b0;
end
else if (ov_href_i && data_merge) begin
data_16bits <= {data_in_d0, ov_data_i[9:2]};
end
else begin
data_16bits <= data_16bits;
end
end
always @ (posedge ov_pclk_i) begin
if(~ov_en_i) begin
data_merge_d0 <= 1'b0;
end
else begin
data_merge_d0 <= data_merge;
end
end
endmodule
这部分设计完毕后,设计流程就成为如下:
在之前的 《Zynq-PS-SDK(12) 之 VDMA+VTC+AXI4S-VideoOut 视频通路硬件搭建》我们的 VDMA 只处理一帧数据,现在情况比较复杂了,又有输入,又有输出,如果不处理这种冲突的话,势必导致对同一帧数据,同时进行读写的情况,导致图像撕裂;我们这里整成 3 块 framebuffer,同时开启 VDMA 的 Dynamic GenLock,避免出现同时读写一帧的缓存;
配置完毕后,Generate Output Product,然后进行生成 Bitstream;
BD 的设计图中,抓取摄像头数据导入 Video In AXI-Stream 部分的连线如下所示:
整个 BD 的设计如下所示: