请选择 进入手机版 | 继续访问电脑版
[X]关闭
0

(非AXI4)S02-CH04-OV5640-HDMI(4 片 DDR 三缓存)

摘要: 在前面的章节,笔者非常详细的提供了一种数据缓存的构架,并且提供了完善的测试例子,对 构架的设计做了充分的测试验证。前面章节也完成了 OV5640 的图像缓存显示方案,总得来说没什么 难度,但是由于 OV5640 的输入 ...

4.1 概述

      在前面的章节,笔者非常详细的提供了一种数据缓存的构架,并且提供了完善的测试例子,对 构架的设计做了充分的测试验证。前面章节也完成了 OV5640 的图像缓存显示方案,总得来说没什么 难度,但是由于 OV5640 的输入信号是 720P@30HZ,而 HDMI输出是 720P@60HZ 所以本课程的三缓存设计可以最大程度上保证图像的输出结果。(7725的例子采用了相同的构架)。

4.2 构架设计

       对于稍微复杂一点的工程项目必须要有优秀的构架设计,优秀的设计构架的意义比具体写代码 更有价值,所以笔者建议读者在以后自己的项目中要多学习和思考构架的设计。笔者设计了一种基于 消息缓存模式的构架,可以广泛用于图像缓存,数据采集缓存,通信数据缓存等项目中。如下图所示,

       摄像头数据经过sensor_decode 模块解码摄像头数据后将RGB时序图像数据输入到MIG_BURTS_IMAGE, 然后再流出来到 vga_lcd_driver 模块,vga_lcd_driver 模块输出的是 RGB 时序,进过 HDMI IP模块 后接到 HDMI显示器显示输出。 

1)、OV5640摄像头接口:是连接到开发板的物理接口,摄像头模块安装FEP转CEP扩展卡上,默认安装CEP1接口。

2) 、HDMI 接口:是图形的输出接口,图像数据从 HDMI 接口输出 

3) 、sensor_decode模块:解码 OV5640 摄像头的图像数据,然后转为 R/G/B 数据,同时需要产生 HS 信号、VS信号、de 信号提供给后续模块使用。如下面一段代码 

assign rgb_o = {rgb565[15:11],3'd0 ,rgb565[10:5] ,2'd0,rgb565[4:0],3'd0};  

//assign rgb_o = {grid_data_2,grid_data_2,grid_data_2};  

assign clk_ce =out_en? byte_flag_r0:1'b0;  

assign vs_o = out_en ? vsync_d[1] : 1'b0;  

assign hs_o = out_en ? href_d[1] : 1'b0;


4)、vga_lcd_driver 模块:产生 RGB(VGA)输出时序 

5)、MIG_BURST_IMAGE 模块:管理图像数据和内存管理,MIG_BURST_IMAGE 模块中包括了 CH0_FIFO 模 块、CH6_FIFO 模块、MIG_DDR 控制器、MSG_FIFO 消息盒、M_S 内存管理状态机,此外还包括 CH0_FIFO 的读请求,以及 CH6_FIFO 的写请求。

4.3 主要模块源码分析

4.3.1 sensor_decode.v

module sensor_decode(  

 input cmos_clk_i,//cmos senseor clock.  

 input rst_n_i,//system reset.active low.  

 input cmos_pclk_i,//input pixel clock.  

 input cmos_href_i,//input pixel hs signal.  

 input cmos_vsync_i,//input pixel vs signal.  

 input  [7:0]cmos_data_i,//data.  

 output cmos_xclk_o,//output clock to cmos sensor.  

 output hs_o,//hs signal.  

 output vs_o,//vs signal.  

 output [23:0] rgb_o,//data output  

 output clk_ce

    );


assign cmos_xclk_o = cmos_clk_i;      

parameter[5:0]CMOS_FRAME_WAITCNT = 4'd15;      

reg[4:0] rst_n_reg = 5'd0;      

reg cmos_href_r;

reg cmos_vsync_r;

reg [7:0]cmos_data_r;


always@(posedge cmos_pclk_i)

begin  

       cmos_data_r <= cmos_data_i;  

       cmos_href_r <= cmos_href_i;  

       cmos_vsync_r<= ~cmos_vsync_i;  

end     

//reset signal deal with.  

always@(posedge cmos_clk_i)

begin  

 rst_n_reg <= {rst_n_reg[3:0],rst_n_i};  

End


reg[1:0]vsync_d;

reg[1:0]href_d;

wire vsync_start;

wire vsync_end;

//vs signal deal with.  

always@(posedge cmos_pclk_i)

begin  

 vsync_d <= {vsync_d[0],cmos_vsync_r};  

 href_d  <= {href_d[0],cmos_href_r};  

end


assign vsync_end  =  vsync_d[1]&(!vsync_d[0]);  assign vsync_start  = (!vsync_d[1])&vsync_d[0];


reg[6:0]cmos_fps;

//frame count.  

always@(posedge cmos_pclk_i)

begin

 if(!rst_n_reg[4])

  begin

  cmos_fps <= 7'd0;

  end

 else if(vsync_start)

  begin  

  cmos_fps <= cmos_fps + 7'd1;  

  end  

 else if(cmos_fps >= CMOS_FRAME_WAITCNT)  

  begin  

  cmos_fps <= CMOS_FRAME_WAITCNT;  

  end

end

//wait frames and output enable.  

reg out_en;

always@(posedge cmos_pclk_i)

begin

 if(!rst_n_reg[4])

  begin

  out_en <= 1'b0;

  end  

 else if(cmos_fps >= CMOS_FRAME_WAITCNT)  

  begin

  out_en <= 1'b1;

end

 else

  begin  

  out_en <= out_en;  

  end

end


//output data 8bit changed into 16bit in rgb565.  

reg [7:0] cmos_data_d0;  

reg [15:0]cmos_rgb565_d0;

reg byte_flag;


always@(posedge cmos_pclk_i)  

begin

 if(!rst_n_reg[4])  

  byte_flag <= 0;  

 else if(cmos_href_r)  

  byte_flag <= ~byte_flag;  

 else

  byte_flag <= 0;

end


reg byte_flag_r0;  

always@(posedge cmos_pclk_i)  

begin

 if(!rst_n_reg[4])  

  byte_flag_r0 <= 0;  

 else   

  byte_flag_r0 <= byte_flag;  

end


always@(posedge cmos_pclk_i)

begin

 if(!rst_n_reg[4])

  cmos_data_d0 <= 8'd0;  

 else if(cmos_href_r)  

  cmos_data_d0 <= cmos_data_r; //MSB -> LSB  

 else if(~cmos_href_r)   

  cmos_data_d0 <= 8'd0;

end


reg [15:0] rgb565;

always@(posedge cmos_pclk_i)

begin

 if(!rst_n_reg[4])

  rgb565 <= 16'd0;

 else if(cmos_href_r&byte_flag)  

  rgb565 <= {cmos_data_d0,cmos_data_r}; //MSB -> LSB

else if(~cmos_href_r)  

  rgb565 <= 8'd0;

End

//以下代码是利用摄像头输出的时序,产生自动定义的测试数据//   

reg [7:0]cnt_test;

reg [8:0]cnt_temp;

reg [11:0]vcounter;

reg [15:0]vcnt=10'd0;

always@(posedge cmos_pclk_i) begin

if(!rst_n_reg[4]) begin

    vcnt<=16'd0;

end else begin

    if(vsync_start)  

            vcounter <=12'd0;

            if(!vcnt[15])vcnt <= vcnt+1'b1;  

     else if({href_d[0],cmos_href_r}==2'b01)  

             vcounter <= vcounter + 12'd1;

 end

end

reg [11:0]hcounter;

always@(posedge cmos_pclk_i) begin

  if(!cmos_href_r)  

    hcounter <=12'd0;

  else  

    hcounter <= hcounter + 12'd1;

end

reg[7:0] grid_data_1;

reg[7:0] grid_data_2;

always @(posedge cmos_pclk_i)//格子图像 

begin

 if((hcounter[2]==1'b1)^(vcounter[4]==1'b1))

 grid_data_1 <= 8'h00;

 else

 grid_data_1 <= 8'hff;

 if((hcounter[6]==1'b1)^(vcounter[6]==1'b1))

 grid_data_2 <= 8'h00;

 else

 grid_data_2 <= 8'hff;

End

assign rgb_o = {grid_data_2,grid_data_2,grid_data_2};  

//以上代码是利用摄像头输出的时序,产生自动定义的测试数据//   

//assign rgb_o = {rgb565[15:11],3'd0 ,rgb565[10:5] ,2'd0,rgb565[4:0],3'd0};  assign clk_ce =out_en? byte_flag_r0:1'b0;  

assign vs_o = out_en ? vsync_d[1] : 1'b0;  

assign hs_o = out_en ? href_d[1] : 1'b0;

/*

ila_2 sensor_sg (

 .clk(cmos_pclk_i), // input wire clk

.probe0(vs_o), // input wire [0:0]  probe0    

 .probe1(hs_o), // input wire [0:0]  probe1   

 .probe2(clk_ce), // input wire [0:0]  probe2   

 .probe3(vcounter[8:0]), // input wire [0:0]  probe3   

 .probe4(grid_data_2) // input wire [0:0]  probe4   );*/


      OV5640输入的图像是 1280X720分辨率的图形,为了观察数据的方便,未来观察行场信号是否正 确,笔者先利用上一课中用到的测试图形方案数据代码OV5640产生的 RGB 数据,而行场型号继续使 用OV5640产生的行场信号。这么做的目的,主要是我们在对OV5640解码的时候可能采样的颜色时序 会出问题,但是一开始又不能定位问题,这样可以循序渐进式解决问题。 

4.3.2 vga_lcd_driver.v

module vga_lcd_driver(

    input  clk,

    input  [7:0]  r_i,

    input  [7:0]  g_i,

    input  [7:0]  b_i,

    output [7:0]  r_o,

    output [7:0]  g_o,

    output [7:0]  b_o,

    output de,

    output vsync,

    output hsync

    );


   reg  [11:0] hcounter;

   reg  [11:0] vcounter;

    

// Colours converted using The RGB


// -- Set the video mode to 1920x1080x60Hz (150MHz pixel clock needed)

 /*    parameter hVisible  = 1920;

   parameter hStartSync = 1920+88;

   parameter hEndSync   = 1920+88+44;

   parameter hMax       = 1920+88+44+148; //2200

   

   parameter vVisible    = 1080;

   parameter vStartSync  = 1080+4;

   parameter vEndSync    = 1080+4+5;

   parameter vMax        = 1080+4+5+36; //1125

*/


// -- Set the video mode to 1440x900x60Hz (106.47MHz pixel clock needed)

/*   parameter hVisible   = 1440;

   parameter hStartSync = 1440+80;

   parameter hEndSync   = 1440+80+152;

   parameter hMax       = 1440+80+152+232; //1904

   

   parameter vVisible    = 900;

   parameter vStartSync  = 900+1;

   parameter vEndSync    = 900+1+3;

   parameter vMax        = 900+1+3+28; //932

   */

  

// -- Set the video mode to 1280x720x60Hz (75MHz pixel clock needed)

   parameter hVisible   = 1280;

   parameter hStartSync = 1280+72;

   parameter hEndSync   = 1280+72+80;

   parameter hMax       = 1280+72+80+216; //1647

   

   parameter vVisible    = 720;

   parameter vStartSync  = 720+3;

   parameter vEndSync    = 720+3+5;

   parameter vMax        = 720+3+5+22; //749

/*

// -- Set the video mode to 800x600x60Hz (40MHz pixel clock needed)

  parameter hVisible   = 800;

   parameter hStartSync = 840; //800+40

   parameter hEndSync   = 968; //800+40+128

   parameter hMax       = 1056; //800+40+128+88

   

   parameter vVisible    = 600;

   parameter vStartSync  = 601; //600+1

   parameter vEndSync    = 605; //600+1+4

   parameter vMax        = 628; //600+1+4+23      



// -- Set the video mode to 640x480x60Hz (25MHz pixel clock needed)


   parameter hVisible   = 640;

   parameter hStartSync = 656; //640+16

   parameter hEndSync   = 752; //640+16+96

   parameter hMax       = 800; //640+16+96+48

   

   parameter vVisible    = 480;

   parameter vStartSync  = 490; //480+10

   parameter vEndSync    = 492; //480+10+2

   parameter vMax        = 525; //480+10+2+33

*/


//------------------------------------------

//v_sync counter & generator


always@(posedge clk) begin

if(hcounter < hMax - 12'd1)        //line over

hcounter <= hcounter + 12'd1;

else

hcounter <= 12'd0;

end


always@(posedge clk) begin

if(hcounter == hMax - 12'd1) begin

if(vcounter < vMax - 12'd1)  //frame over

vcounter <= vcounter + 12'd1;

else

vcounter <= 12'd0;

end

end


assign hsync = ((hcounter >= (hStartSync - 2'd2))&&(hcounter < (hEndSync - 2'd2)))? 1'b0:1'b1;  //Generate the hSync Pulses

assign vsync = ((vcounter >= (vStartSync - 1'b1))&&(vcounter < (vEndSync - 1'b1)))? 1'b0:1'b1; //Generate the vSync Pulses

  

  assign de = (vcounter >= vVisible || hcounter >= hVisible) ? 1'b0 : 1'b1;

  assign r_o = r_i;

  assign g_o = g_i;

  assign b_o = b_i;  

endmodule

     vga_lcd_driver模块的作用是产生 1280X720 分辨率的RGB使出时序,RGB 的数据来源来自 CH6_FIFO。读者可以修改不同分辨率输出不同的测试图形。 

4.3.3 CH0_FIFO FIFO IP 设置

Step1:设置 Native 和 Independent Clocks Block RAM

Step2:设置 FIFO 采用 Firt Word Fall Through 模式,FIFO 的写是 64bit 2048 深度 FIFO 的读是 512bit 256 深度

Step3:默认设置

Step4:设置计数器


   wire CH0_empty;

    CH0_FIFO CH0_FIFO_INST (

    .rst(CH0_FIFO_RST),  // input wire rst

    .wr_clk(CH0_wclk_i), // input wire wr_clk

    .rd_clk(CH0_rclk_i),// input wire rd_clk

    .din(CH0_data_i), // input wire [63 : 0] din

    .wr_en(CH0_wren_i), // input wire wr_en

    .rd_en(CH0_rden_i), // input wire rd_en

    .dout(CH0_data_o),  // output wire [512 : 0] dout

    .full(),  // output wire full

    .empty(CH0_empty), // output wire empty

    .rd_data_count(CH0_rusdw_o)  // output wire [7 : 0] rd_data_count

    );

4.3.4 CH6_FIFO FIFO IP 设置

Step1:设置 Native 和 Common Clocks Block RAM

Step2:设置 FIFO 采用 Firt Word Fall Through 模式,FIFO 的写是 32bit 32 深度 FIFO 的读是 64bit 16 深度

Step3:这一页设置默认

Step4:设置读FIFO 的 counter 计数器宽度是6bit

    //----------------CH6 fifo接口--------------------//

    CH6_FIFO CH6_FIFO_INST (

    .rst(CH6_FIFO_RST),   // input wire rst

    .wr_clk(CH6_wclk_i),   // input wire wr_clk

    .rd_clk(CH6_rclk_i),   // input wire rd_clk

    .din(CH6_data_i),      // input wire [512 : 0] din

    .wr_en(CH6_wren_i),    // input wire wr_en

    .rd_en(CH6_rden_i),    // input wire rd_en

    .dout(CH6_data_o),     // output wire [64 : 0] dout

    .full(),              // output wire full

    .empty(),            // output wire empty

    .rd_data_count(),  // output wire [10 : 0] rd_data_count

    .wr_data_count(CH6_wusdw_o)  // output wire [7 : 0] wr_data_count

    );

前面讲到 FIFO不支持从512BIT 转换为32bit所以需要添加以下代码将64Bit转换为32bit

always@(posedge CH6_rclk_i)begin

    CH6_64_32_rden <= CH6_rden_i ? CH6_64_32_rden + 1'b1 : 1'b0;

end

assign  CH6_data_o =  CH6_64_32_rden ? CH6_64_32_data_64[31:0] : CH6_64_32_data_64[63:32];

4.3.5 MSG_FIFO 模块

MSG_FIFO MSG_FIFO_INST (  

     .clk(MSG_FIFO_CLK),    // input wire clk  

     .din(MSG_FIFO_WRDATA), // input wire [7 : 0] din  

     .wr_en(MSG_FIFO_WREN), // input wire wr_en  

     .rd_en(MSG_FIFO_RDEN), // input wire rd_en  

     .dout(MSG_FIFO_RDDATA),// output wire [7 : 0] dout  

     .full(MSG_FIFO_FULL),  // output wire full  

     .empty(empty),         // output wire empty  

     .data_count(MSG_FIFO_USEDW) // output wire [4 : 0] data_count  

    );


MSG_FIFO 模块的作用是设计了一个消息的缓存,所有读写请求都会先保存在这消息队列里面然后再 按照先后顺序出列。M_S 模块会对 MSG_FIFO里面的消息进行处理。 

MSG_FIFO_CLK :MSG_FIFO 的时钟信号 这里就是系统本模块的系统时钟和 MIG 控制器时钟一致。  MSG_FIFO_WRDATA:长度为 8 代表同时可以一次性存储 8 个不同信号  

MSG_FIFO_WREN:写消息使能,当读 image_data_gen 和 vga_lcd_driver 模块的场信号过来的时候会 使能写 MSG_FIFO,把消息的状态进入 MSG_FIFO

MSG_FIFO_RDDATA:读消息使能,M_S 状态机会逐条取出 MSG_FIFO 队列中的消息。  MSG_FIFO_FULL:消息满信号,不应该让此信号满,如果满必然发出消息溢出  

MSG_FIFO_USEDW:记录目前已经写入的消息条数,这里最多记录 32 条消息 

CH6_FIFO 的配置界面: 

Step1:设置 Native 和 Common Clocks Block RAM

Step2:设置 FIFO采用 standard 模式,FIFO 的写是 8bit 32 深度读 FIFO 是 8bit 32 深度。

Step3:这一页默认不用设置

Step4:设置写FIFO 的 counter 计数器宽度5bit

4.3.6 CH0_FIFO_REQ/CH6_FIFO_REQ 模块

        //-------------------------数据缓存FIFO满状态标识-------------------------//

        always@(posedge ui_clk)

        begin

            CH0_REQ    <= (CH0_rusdw_o   >=PIXEL_H);//--大于等于一行可以读FIFO 写入DDR 这个参数可以调整         

            CH6_REQ    <= (CH6_wusdw_o   <=PIXEL_H);//--不满一行开始读DDR写入FIFO 这个参数可以调整--//

        end


CH0_REQ:这个模块中,信号在CH0_FIFO 缓存一行数据后有效  

CH6_REQ:这个模块中,当CH6_FIFO的数据小于一行开始读DDR并且写入。 

4.3.7 M_S 内存管理状态机

      //--包含sdram读测试/sdram写测试/sdram突发读/sdram突发写 模块--//

        always@(posedge ui_clk)

        begin

        if(!rst_tem[4])

                begin

                M_S           <=M_S_MSG_FIFO0;

                MSG_FIFO_RDEN <=1'd0;

                CH0_FIFO_RST  <=1'd0;

                CH6_FIFO_RST  <=1'd0;

                rst_FIFO_cnt  <=8'd0;

                count_rden    <=8'd0;

                count_wren    <=8'd0;    

                CH0_PTR       <=21'd0;

                CH6_PTR       <=21'd0;   

                CH0_Fbuf      <=7'd0;

                CH6_Fbuf      <=7'd0;

                end

        else case(M_S)

        //------------------------读取FIFO的控制信号-------------------------//

        M_S_MSG_FIFO0://--FIFO有数据就读取--//

            begin

            M_S             <=({MSG_FIFO_FULL,MSG_FIFO_USEDW}!=5'd0)?M_S_MSG_FIFO1:M_S_IDLE;

            MSG_FIFO_RDEN   <=({MSG_FIFO_FULL,MSG_FIFO_USEDW}!=5'd0);

            CH0_FIFO_RST    <=1'd0;

            CH6_FIFO_RST    <=1'd0;

            rst_FIFO_cnt    <=8'd0;

            count_rden      <=8'd0;

            count_wren      <=8'd0;

            end

            

        M_S_MSG_FIFO1://--延迟1个时钟--//

            begin

            M_S              <=M_S_MSG_FIFO2;

            MSG_FIFO_RDEN    <=1'd0;

            end

          

        M_S_MSG_FIFO2://--相对地址处理--//

            begin

            M_S                <=M_S_RST_FIFO0;

             //三缓存设计

              if(MSG_FIFO_RDDATA[7])begin

                 if(CH0_Fbuf == 2) begin

                     CH0_Fbuf <= 0;

                 end

                 else begin

                     CH0_Fbuf <= CH0_Fbuf + 1'b1;

                 end

                 CH0_PTR <= 21'd0;

              end

     

              if(MSG_FIFO_RDDATA[6])begin

                 if(CH0_Fbuf == 0) begin

                     CH6_Fbuf <= 2;

                 end

                 else begin

                     CH6_Fbuf <= CH0_Fbuf - 1'b1;

                 end

                 CH6_PTR <= 21'd0;

              end

               

            end

            

        //------------------------读取FIFO的控制信号-------------------------//

        M_S_RST_FIFO0:        

            begin

            M_S             <=(rst_FIFO_cnt>=8'd60)?M_S_IDLE:M_S;

            CH6_FIFO_RST    <=(rst_FIFO_cnt<=8'd20)&&MSG_FIFO_RDDATA[6];

            CH0_FIFO_RST    <=(rst_FIFO_cnt<=8'd20)&&MSG_FIFO_RDDATA[7];

            rst_FIFO_cnt    <=rst_FIFO_cnt+8'd1;

            end

            

        //-------------------------状态机空闲状态--------------------------//

        M_S_IDLE:            

            begin    

            count_rden        <=8'd0;

            count_wren        <=8'd0;

            casex    ({//-读写通道仲裁-//

                    CH0_REQ&&1'd1,//ch0_输入信道

                   &nb

2

路过

雷人

握手

鲜花

鸡蛋

刚表态过的朋友 (2 人)

最新评论

本文作者
2019-10-17 16:47
  • 1
    粉丝
  • 3024
    阅读
  • 0
    回复

米联客UIsrc

独家课程 硬核科技

销售电话:18921033576
EMAIL:tjy@uisrc.com
地址:常州溧阳市天目云谷3号楼北楼
热门评论
排行榜