OV5640(3):图像数据处理

一、OV5640输出图像处理

  前面说过,选择 RGB565 格式输出,2 个RAW像素合成 1 个 RGB565 像素,但摄像头出来的 RGB565 像素不是单个的,而仍然1个RGB565像素用 2 个字节表示。因此,需要我们手动将两个字节拼接成一个RGB565像素。

  时序设计如下:

  用 ov5640_href 来生成 byte_flag,再用 byte_flag 来拼接像素和生成 vld 信号。

  此外,在 OV5640 数据手册没有写明一个事情,但在 OV7725 数据手册中提到过,摄像头出来的前 10 帧数据不稳定,需要丢弃,因此在上述的时序上,还得计数前 10 帧图像数据,并丢弃它。

 1 module ov5640_data
 2 //========================< 端口 >==========================================
 3 (
 4 //system --------------------------------------------
 5 input   wire                rst_n                   ,  
 6 //OV5640 --------------------------------------------
 7 input   wire                ov5640_pclk             ,       
 8 input   wire                ov5640_href             ,       
 9 input   wire                ov5640_vsync            ,       
10 input   wire    [ 7:0]      ov5640_data             ,       
11 //User ----------------------------------------------
12 output  reg     [15:0]      rgb_data                ,
13 output  reg                 rgb_vld                   
14 );
15 //========================< 信号 >==========================================
16 reg                         byte_flag               ; // 0: first byte, 1: second byte
17 wire                        ov5640_vsync_pos        ;
18 reg                         ov5640_vsync_r          ;
19 reg     [ 3:0]              frame_cnt               ;
20 wire                        frame_vaild             ;
21 
22 //==========================================================================
23 //==    RGB564信号产生,两个byte合成一个rgb像素
24 //==========================================================================
25 //字节指示
26 //---------------------------------------------------
27 always  @(posedge ov5640_pclk or negedge rst_n) begin
28     if(!rst_n) begin
29         byte_flag <= 1'b0;
30     end
31     else if(ov5640_href) begin
32         byte_flag <= ~byte_flag;
33     end
34     else begin
35         byte_flag <= 1'b0;
36     end
37 end
38 
39 //rgb_data
40 //---------------------------------------------------
41 always  @(posedge ov5640_pclk or negedge rst_n) begin
42     if(!rst_n) begin
43         rgb_data <= 'h0;
44     end
45     else if(byte_flag == 1'b0) begin                    //first byte
46         rgb_data <= {ov5640_data, rgb_data[7:0]};
47     end
48     else if(byte_flag == 1'b1) begin                    //second byte
49         rgb_data <= {rgb_data[15:8], ov5640_data};
50     end
51 end
52 
53 //==========================================================================
54 //==    丢弃前10帧图像
55 //==========================================================================
56 //ov_5640 vsync 上升沿检测
57 //---------------------------------------------------
58 always  @(posedge ov5640_pclk) begin
59      ov5640_vsync_r <= ov5640_vsync;
60 end
61 
62 assign ov5640_vsync_pos = ov5640_vsync & ~ov5640_vsync_r;
63 
64 //帧有效信号,去除前10帧
65 //---------------------------------------------------
66 always @(posedge ov5640_pclk or negedge rst_n) begin
67     if(!rst_n) begin
68         frame_cnt <= 'd0;
69     end
70     else if(frame_vaild==1'b0 && ov5640_vsync_pos) begin
71         frame_cnt <= frame_cnt + 1'b1;
72     end
73 end
74 
75 assign frame_vaild = (frame_cnt >= 'd10) ? 1'b1 : 1'b0;
76 
77 //==========================================================================
78 //==    rgb_vld
79 //==========================================================================
80 always  @(posedge ov5640_pclk or negedge rst_n) begin
81     if(!rst_n) begin
82         rgb_vld <= 1'b0;
83     end
84     else if(frame_vaild && byte_flag) begin
85         rgb_vld <= 1'b1;
86     end
87     else begin
88         rgb_vld <= 1'b0;
89     end
90 end
91 
92 
93 
94 
95 endmodule

二、SDRAM缓存处理

  先假设 OV5640 和 VGA 直接相连,中间不采用任何缓存器件,那会出现什么情况呢?

  OV5640的帧率为 30fps, VGA 的帧率为 60fps,且时钟也完全不一样。假设分辨率是 3x2 = 6,数据为:1,2,3,4,5,6。OV5640 帧率小,生成一个像素的时间假设是2秒,而 VGA 帧率大,需要一个像素的时间假设是 1s。在 6s 的时间里,VGA就请求了一帧数据,可这个时间里 OV5640 只生成了1,2,3共三个像素,VGA那边就出现了空数据的情况。在 12s 的时间里,VGA 请求了两帧数据,可这个时间里 OV5640 只生成了一帧数据,怎么来说都是错的。上述假设是简化了时间点,实际情况是 OV5640 生成像素和 VGA 请求像素的时间不一致,导致VGA总是要不到一帧图像正确的数据,也要不全一帧图像正确的数据。因此,OV5640 和 VGA 之间需要加入一个缓存器件,用于缓存一帧的数据,这样 VGA 每次要一帧数据都可以被满足。加入缓存器件的根本原因就是两端时钟不一致,导致读写速率不一致。

  那采用什么缓存器件呢?FIFO 和 RAM 是我们最常用的,但是这里一帧的图像数据往往是 640x480、1280x768、1920x1280,每个像素是 16bit,数据量大,FPGA的片内资源无法满足。因此需要引入外部缓存器件,如 SDRAM、DDR2、DDR3等。

三、乒乓操作

1、无乒乓操作

  上面的那种情况显然考虑不周到,除非一帧图像是瞬间产生完,OV5640帧率30fps,VGA帧率60fps,仿佛刚好写一帧可以读两帧。但是,一帧图像实际情况是一行行的生成和读取的,所以会出现 VGA 从SDRAM处读的上半帧是新帧,由于而SDRAM缓存的下半帧还没有被 OV5640 写完,VGA 从SDRAM处读的下半帧还是老帧!示意图如下所示,红线为写,黑线为读。

  将上述 5 帧图像生成的时间点编号为 t1、t2、t3、t4、t5。在 t1、t3、t5 时刻图像都是残缺帧(新老帧参半),在 t2、t4 时刻图像才是完整的一帧,而 VGA 那边可不管,每个时间点都会要一帧图像,这就是错帧现象。而解决错帧现象的方法则是乒乓操作。

2、乒乓操作

  乒乓操作,使用两个缓存区,写缓存区 1 时读缓存区 2,写缓存区 2 时读缓存区 1,读写交替。示意图如下,红线为写,黑线为读。

  由示意图可以看出,乒乓操作中,每次读的(黑线)都是完整的帧,每一帧读 2 次,这样便没有出现读残缺帧的现象,解决了错帧问题。

3、乒乓操作设计

  在上述设计中,由于写读速率为1:2,所以写一帧读两帧即可,但是很多时候写读速率的比例是其他数值,那怎么办呢?

  (1)写端在缓存区1写完一帧数据就切换到缓存区2写下一帧,写完后又切换回缓存区1写再下一帧,如此反复。

  (2)读端在缓存区2读完一帧:

    ①如果写端仍然在缓存区1,则读端不切换缓存区,而是继续在缓存区2重读一帧。

    ②如果写端离开了缓存区1,即切换到缓存区2了,说明写端已经在缓存区1写完了,则读端可以切换到缓存区1。这样就能保证读端每次读的都是完整的帧,就算有一小段时间是读写端都在同一个缓存区,但是由于写慢读快,写是不会追上读的,读还是能读完旧帧,之后旧帧才被新帧覆盖。

  写慢读快这样设计就行,那如果写快读慢呢?其实也是一样的,无论读写谁快谁慢,都是快端照顾慢端,即慢端一帧结束就离开自身缓存区到另一缓存区。快端则一帧结束后,先检测慢端是否离开自身缓存区,是则切换过去,否则不切换过去,重新在本缓存区再工作一帧。示意图如下所示:

4、SDRAM乒乓操作

  用两片SDRAM来进行乒乓操作有些太浪费了,可以直接利用SDRAM里面的bank进行乒乓操作即可。虽然读写只有一根总线,但是SDRAM时钟高,读写足够快,可以满足乒乓操作的需求。

参考资料:[1]开源骚客《SDRAM那些事儿》

        [2]威三学院FPGA教程

猜你喜欢

转载自www.cnblogs.com/xianyufpga/p/12283590.html