zoukankan      html  css  js  c++  java
  • S03_CH04_AXI_DMA_OV5640摄像头采集系统

    S03_CH04_AXI_DMA_OV5640摄像头采集系统

    4.1概述

    本课程讲解如何搭建基于DMA的图形系统,方案原理和搭建7725的一样,只是OV5640显示的分辨率是1280X720如下,只是购买了OV5640摄像头的用户可以直接从本章开始学习。

    摄像头采样图像数据后通过DMA送入到DDR,在PS部分产生DMA接收中断,在接收中断里面再把DDR里面保持的图形数据DMA发送出去。在FPGA的接收端口部分产生VID OUT时序驱动VGA显示器显示图形。MIZ701N没有VGA接口,可以跳过直接看《S03_CH05_AXI_DMA_HDMI图像输出》或者大家不想看本章的也可以直接跳到《S03_CH05_AXI_DMA_HDMI图像输出》这两节课的核心教学内容一样。

    《S03_CH03_AXI_DMA_OV7725摄像头采集系统》、《S03_CH04_AXI_DMA_OV5640摄像头采集系统》、《S03_CH05_AXI_DMA_HDMI图像输出》。读者可以根据自己需求情况而阅读,请知悉。

    4.2系统构架

    4.2.1构架方案图

    wpsF559.tmp

    摄像头接口采集的摄像头数据,进过vid in视频输入 IP后,还需要通过用户FPGA逻辑编程,和DMA IP之间实现握手协议,实现把数据通过DMA写入到DDR。每次写入一副图像的数据后,产生一次接收中断,接收中断函数,会把数据三缓存后,在通过DMA发出去,DMA发送完成后产生中断,在中断中,把缓存好的图像发送出去。DMA发送的数据需要发送到vid out 视频输出IP。同理,DMA和vid out  IP之间也许需要增加FPGA用户代码实现接口的握手协议。数据进入vid out 后,会随同vtc IP 输出符合VGA时序的图像信号。vid out 的输出就可以直接定义成VGA信号输出。

    4.2.2构BLOCK模块化设计方案图

    wpsF56A.tmp

    4.3 vid in IP介绍

    4.3.1 OV_Sensor_ML 自定义 IP模块

    wpsF56B.tmp

    外部信号接口说明:

    CLK_i :为输入时钟,通常接24MHZ 或者25MHZ

    Cmos_xclk_o:摄像头工作,通常直接把CLK_i连接到cmos_xclk_o

    Cmos_vsyns_i:摄像头场同步输入 上升沿代表场同步开始

    Cmos_href_i:摄像头行同步输入 高电平代表行数据有效

    Cmos_data[7:0]:摄像头数据输入

    Hs_o:采集 OV_Sensor_ML IP 输出的行数据有效

    Vs_o:采集 OV_Sensor_ML IP 输出的场同步信号

    Vid_clk_ce:此信号用于和vid_in IP的时钟同步(由于OV_Sensor_ML IP 是每两个时钟输出一次rgb[23:0]的图像数据,因此需要通过Vid_clk_ce对时钟频率进行同步,有了这个信号,可以解决输入采集IP和vid_in IP数据接口之间的同步问题).

    OV_Sensor_ML IP 包含3个源程序文件,分别为OV_Sensor_ML.v、cmos_decode_v1.v、count_reset_v1.v文件。

    OV_Sensor_ML.v程序中,对cmos_data_i、cmos_href_i、cmos_vsync_i做了一次寄存器,笔者发现图像效果有所改观。笔者分析,是因为寄存后有利于去除一些毛刺信号,提高了数据的稳定性。

    表3-3-1-1 OV_Sensor_ML 源码OV_Sensor_ML.v

    `timescale 1ns / 1ps

    //////////////////////////////////////////////////////////////////////////////////

    // Company: milinker

    // Engineer:tangjinyuan

    //

    // Create Date:    15:54:59 11/21/2015

    // Design Name:

    // Module Name:    OV7725_IP_ML

    // Project Name: OV7725_IP_ML

    // Target Devices: ZYNQ

    // Tool versions:

    // Description:

    //

    // Dependencies:

    //

    // Revision:

    // Revision 0.01 - File Created

    // Additional Comments:

    //

    //////////////////////////////////////////////////////////////////////////////////

    module OV_Sensor_ML(

        input CLK_i,

    //---------------------------- CMOS sensor hardware interface --------------------------/

    input cmos_vsync_i, //cmos vsync

    input cmos_href_i, //cmos hsync refrence

    input cmos_pclk_i, //cmos pxiel clock

    output cmos_xclk_o, //cmos externl clock

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

    output hs_o,//hs signal.

        output vs_o,//vs signal.

       // output de_o,//data enable.

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

        output vid_clk_ce

    );

    //----------------------视频输出解码模块---------------------------//

    wire  [15:0]rgb_o_r;

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

    reg [7:0]cmos_data_r;

    reg cmos_href_r;

    reg cmos_vsync_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

    //assign rgb_o = 24'b11111111_00000000_11111111;

    cmos_decode cmos_decode_u0(

    //system signal.

    .cmos_clk_i(CLK_i),//cmos senseor clock.

    .rst_n_i(RESETn_i2c),//system reset.active low.

    //cmos sensor hardware interface.

    .cmos_pclk_i(cmos_pclk_i),//(cmos_pclk),//input pixel clock.

    .cmos_href_i(cmos_href_r),//(cmos_href),//input pixel hs signal.

    .cmos_vsync_i(cmos_vsync_r),//(cmos_vsync),//input pixel vs signal.

    .cmos_data_i(cmos_data_r),//(cmos_data),//data.

    .cmos_xclk_o(cmos_xclk_o),//(cmos_xclk),//output clock to cmos sensor.

    //user interface.

    .hs_o(hs_o),//hs signal.

    .vs_o(vs_o),//vs signal.

    // .de_o(de_o),//data enable.

    .rgb565_o(rgb_o_r),//data output

    .vid_clk_ce(vid_clk_ce)

        );

    count_reset_v1#(

            .num(20'hffff0)

        )(

            .clk_i(CLK_i),

            .rst_o(RESETn_i2c)

        );    

    endmodule

    1 cmos_decode_v1.v 是本模块的关键部分,实现了RGB565 的解码输出以及vid_clk_ce实现了此模块和vid_in IP直接时序匹配的关系。

    表3-3-1-2 OV_Sensor_ML 源码cmos_decode_v1.v

    `timescale 1ns / 1ps

    //////////////////////////////////////////////////////////////////////////////////

    // Company: milinker corperation

    // WEB:www.milinker.com

    // BBS:www.osrc.cn

    // Engineer:.

    // Create Date:    07:28:50 09/04/2015

    // Design Name:  cmos_decode_v1

    // Module Name:    cmos_decode_v1

    // Project Name:  cmos_decode_v1

    // Target Devices:

    // Tool versions:  

    // Description:  cmos_decode_v1.

    // Revision:  V1.0

    // Additional Comments:

    //1) _i PIN input  

    //2) _o PIN output

    //3) _n PIN active low

    //4) _dg debug signal

    //5) _r  reg delay

    //6) _s state machine

    //////////////////////////////////////////////////////////////////////////////

    module cmos_decode(

    //system signal.

    input cmos_clk_i,//cmos senseor clock.

    input rst_n_i,//system reset.active low.

    //cmos sensor hardware interface.

    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.

    //user interface.

    output hs_o,//hs signal.

    output vs_o,//vs signal.

    output reg [15:0] rgb565_o,//data output

    output vid_clk_ce

        );

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

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

    //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_i};

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

    end

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

    assign vsync_end   = (!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_i)

    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_i)

    cmos_data_d0 <= cmos_data_i; //MSB -> LSB

    else if(~cmos_href_i)

    cmos_data_d0 <= 8'd0;

    end

    always@(posedge cmos_pclk_i)

    begin

    if(!rst_n_reg[4])

    rgb565_o <= 16'd0;

    else if(cmos_href_i&byte_flag)

    rgb565_o <= {cmos_data_d0,cmos_data_i}; //MSB -> LSB

    else if(~cmos_href_i)

    rgb565_o <= 8'd0;

    end

    assign vid_clk_ce = out_en ? (byte_flag_r0&hs_o)||(!hs_o) : 1'b0;

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

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

    assign cmos_xclk_o = cmos_clk_i;

    endmodule

    count_reset_v1.v 源文件实现了信号的延迟复位。

    表3-3-1-1 count_reset_v1.v

    `timescale 1ns / 1ps

    //////////////////////////////////////////////////////////////////////////////////

    // Company: milinker corperation

    // WEB:www.milinker.com

    // BBS:www.osrc.cn

    // Engineer:sanliuyaoling.

    // Create Date:    07:28:50 12/04/2015

    // Design Name:    count_reset_v1

    // Module Name:    count_reset_v1

    // Project Name:  count_reset_v1

    // Target Devices: XC7Z020-CLG484-1I

    // Tool versions:  vivado2015.4

    // Description:  count_reset_v1

    // Revision:  V1.0

    // Additional Comments:

    //1) _i PIN input  

    //2) _o PIN output

    //3) _n PIN active low

    //4) _dg debug signal

    //5) _r  reg delay

    //6) _s state machine

    //////////////////////////////////////////////////////////////////////////////

    module count_reset_v1#

    (

    parameter[19:0]num = 20'hffff0

    )(

    input clk_i,

    output rst_o

        );

    reg[19:0] cnt = 20'd0;

    reg rst_d0;

    /*count for clock*/

    always@(posedge clk_i)

    begin

    cnt <= ( cnt <= num)?( cnt + 20'd1 ):num;

    end

    /*generate output signal*/

    always@(posedge clk_i)

    begin

    rst_d0 <= ( cnt >= num)?1'b1:1'b0;

    end

    assign rst_o = rst_d0;

    endmodule

    表3-3-1-2

    4.3.2 vid in IP模块

    wpsF57B.tmp

    wpsF57C.tmp

    • Pixels Per Clock: 设置每个时钟输出的像素个数,可以是1、2、4

    • Video Format: 视频格式

    • Input Component Width: 输入像素的宽度,这个参数影响TDATA的位宽

    • Output Component Width:输出像素的宽度

    • FIFO Depth: FIFO深度

    • Clock Mode:时钟的模式,可以选择独立时钟,或者共享时钟

    4.3.2 VID_IN IP接口信号的定义

    wpsF58D.tmp

    Common Interface wpsF58E.tmpwpsF58F.tmp

    Video Timing Interface

    wpsF59F.tmp

    Video Input Interface

    wpsF5A0.tmp

    使用到的信号有:

    Vid in IP输入端信号:

    Vid_data:视频数据输入

    Vid_active_video:视频数据有效

    Vid_hsync:视频行同步信号(非常关键信号,下面重点分析对象)

    Vid_vsync:视频场同步信号

    Vid_io_in_ce:数据输入有效(非常关键信号,下面重点分析对象)

    vid_io_in_clk:这是时钟信号和摄像头时钟同步

    Vid_io_in_reset:这个信号,高电平的时候复位

    有很多读者会问笔者,这些官方的IP如何使用,这么没有详细的技术手册。还别说,官方就是没有非常详细的技术手册,有时候笔者也是使出浑身分析vid_in IP 内部信号时序,掌握OV_Sensor_ML 自定义IP 时序接口设计。

    打开 v_vid_in_axi4s_v4_0_1_formatter.v这个文件

    下面对其关键的部分进行说明。

    表3-3-2-1 v_vid_in_axi4s_v4_0_1_formatter.v

    `timescale 1ps/1ps

    `default_nettype none

    (* DowngradeIPIdentifiedWarnings="yes" *)

    module v_vid_in_axi4s_v4_0_1_formatter #(

      parameter  C_NATIVE_DATA_WIDTH = 24

    ) (

      // System signals

      input  wire VID_IN_CLK,           // Native video clock

      input  wire VID_RESET,            // Native video reset

      input  wire VID_CE,               // Native video clock enable

      // Video input signals

      input  wire VID_ACTIVE_VIDEO,     // Native video input data enable

      input  wire VID_VBLANK,           // Native video input vertical blank

      input  wire VID_HBLANK,           // Native video input horizontal blank

      input  wire VID_VSYNC,            // Native video input vertical sync

      input  wire VID_HSYNC,            // Native video input horizontal sync

      input  wire VID_FIELD_ID,         // Native video input field-id

      input  wire [C_NATIVE_DATA_WIDTH-1:0] VID_DATA, // Native video input data

      // Video timing detector signals

      output wire VTD_ACTIVE_VIDEO,     // Native video output data enable

      output wire VTD_VBLANK,           // Native video output vertical blank

      output wire VTD_HBLANK,           // Native video output horizontal blank

      output wire VTD_VSYNC,            // Native video output vertical sync

      output wire VTD_HSYNC,            // Native video output horizontal sync

      output wire VTD_FIELD_ID,         // Native video output field-id

      input  wire VTD_LOCKED,           // Native video locked signal from VTD

      // FIFO write signals

      output wire [C_NATIVE_DATA_WIDTH+2:0] FIFO_WR_DATA, // FIFO write data

      output wire FIFO_WR_EN            // FIFO write enable

    );

      // Wire and register declarations

      reg  de_1 = 0;         

      reg  vblank_1 = 0;

      reg  hblank_1 = 0;

      reg  vsync_1 = 0;  

      reg  hsync_1 = 0;

      reg  [C_NATIVE_DATA_WIDTH -1:0] data_1 = 0;  

      reg  de_2 = 0;  

      reg  v_blank_sync_2 = 0;  

      reg  [C_NATIVE_DATA_WIDTH -1:0] data_2 = 0;  

      reg  de_3 = 0;  // DE output register

      reg  [C_NATIVE_DATA_WIDTH -1:0] data_3 = 0;  // data output register

      reg  vert_blanking_intvl = 0; // SR, reset by DE rising

      reg  field_id_1 = 0;

      reg  field_id_2 = 0;

      reg  field_id_3 = 0;

      wire v_blank_sync_1;  // vblank or vsync

      wire de_rising;                   

      wire de_falling;      

      wire vsync_rising;

      reg  sof;

      reg  sof_1;

      reg  eol;   

      reg  vtd_locked;

      wire sof_rising;

      // Assignments

      assign FIFO_WR_DATA     = {field_id_3,sof_1,eol,data_3};

      assign FIFO_WR_EN       = de_3 & ~VID_RESET & vtd_locked;

      assign VTD_ACTIVE_VIDEO = de_1;

      assign VTD_VBLANK       = vblank_1;

      assign VTD_HBLANK       = hblank_1;

      assign VTD_VSYNC        = vsync_1;

      assign VTD_HSYNC        = hsync_1;

      assign VTD_FIELD_ID     = field_id_1;

      assign v_blank_sync_1 = vblank_1 || vsync_1;

      assign de_rising  = de_1  && !de_2;  

      assign de_falling = !de_1 && de_2;  

      assign vsync_rising = v_blank_sync_1 && !v_blank_sync_2;    

      assign sof_rising = sof & ~sof_1;

      // VTD locked process

      always @(posedge VID_IN_CLK) begin

        if(VID_RESET | ~VTD_LOCKED) begin

          vtd_locked <= 1'b0;

        end else if(VID_CE) begin

          vtd_locked <= (sof_rising & VTD_LOCKED) ? 1'b1 : vtd_locked;

        end

      end

      // input, output, and delay registers

      always @ (posedge VID_IN_CLK) begin

        if(VID_RESET) begin

          de_1           <= 1'b0;  

          de_2           <= 1'b0;

          de_3           <= 1'b0;

          vblank_1       <= 1'b0;

          hblank_1       <= 1'b0;

          vsync_1        <= 1'b0;

          hsync_1        <= 1'b0;

          field_id_1     <= 1'b0;

          field_id_2     <= 1'b0;

          field_id_3     <= 1'b0;

          data_1         <= {C_NATIVE_DATA_WIDTH{1'b0}};

          data_2         <= {C_NATIVE_DATA_WIDTH{1'b0}};

          data_3         <= {C_NATIVE_DATA_WIDTH{1'b0}};

          v_blank_sync_2 <= 1'b0;

          eol            <= 1'b0;

          sof            <= 1'b0;

          sof_1          <= 1'b0;

        end else if(VID_CE) begin

          de_1           <= VID_ACTIVE_VIDEO;

          de_2           <= de_1;    

          de_3           <= de_2;    

          vblank_1       <= VID_VBLANK;

          hblank_1       <= VID_HBLANK;

          vsync_1        <= VID_VSYNC;

          hsync_1        <= VID_HSYNC;

          field_id_1     <= VID_FIELD_ID;

          field_id_2     <= field_id_1;

          field_id_3     <= field_id_2;

          data_1         <= VID_DATA;

          data_2         <= data_1;

          data_3         <= data_2;

          v_blank_sync_2 <= v_blank_sync_1;

          eol            <= de_falling;

          sof            <= de_rising && vert_blanking_intvl;

          sof_1          <= sof;

        end      

      end

      // Vertical back porch SR register

      always @ (posedge VID_IN_CLK) begin

        if (VID_CE) begin

          if (vsync_rising)   // falling edge of vsync

            vert_blanking_intvl <= 1;

          else if (de_rising)        // rising edge of data enable

            vert_blanking_intvl <= 0;

        end

      end

    endmodule

    在上面代码中,

    eol            <= de_falling;

    sof            <= de_rising && vert_blanking_intvl;

    eol 实际就是tlast信号,而sof就是tuser信号。tlast信号代表每行图像数据的最后一个数据,tuser代表每场数据的第一个数据。

    所有非常关键的信号都和de_falling 和vert_blanking_intvl有关系。

    hs_o和vid_in IP的连接关系。

    wpsF5A1.tmp

    上图中,被红色圈起来的hs_o信号,同时接到了vid_in ip的vid_active_video和vid_hsync信号接口。因此,de信号就是hs_o信号,而vid_hsync 我们发现没有任何作用,也就是说不hs_o不连接到vid_hsync也不影响这里的程序工作。

    VID_CE这个参数就是前面的vid_io_in_ce信号,可以看出这个芯片有效的时候相对应的时序电路才会执行。在本工程中,摄像头每2个pclk输出1个有效的数据,而vid_in IP如果VID_CE为1则数据输入会每个时钟输入1个就错了。因此官方的IP设计的还是很不错考虑周到,通过VID_CE这个条件,控制时钟同步。

    ...

    else if(VID_CE) begin

    ...

    end

    ...

    现在回到OV_Sensor_ML的cmos_decode_v1.v文件中有一段红色的代码如下:

    assign vid_clk_ce = out_en ? (byte_flag_r0&hs_o)||(!hs_o) : 1'b0;

    这段代码控制了vid_clk_ce的正确输出,关键部分是(byte_flag_r0&hs_o)||(!hs_o)。当hs_o有效的时候,vid_in的VID_CE信号就有效,当hs_o=0的时候VID_CE必须仍然有效,这样才能检测到vsync_rising信号了,检测到了vsync_rising才能有ert_blanking_intvl为1,才有tuser信号。

    好了罗嗦了半天,终于解释完了,如果有不清楚的,找我们技术支持吧。

    4.4 VTC IP的分析

    4.4.1 VTC IP的参数介绍

    wpsF5B2.tmp

    wpsF5B3.tmp

    这个IP就是一个时序发生器,产生显示器输出所需要的时序信号。

    这个页面中,incluse AXI-lite interface可以不勾选,不勾选就只能采用默认设置,无法在C语言中灵活配置了,所以笔者这里建议大家勾选吧。max clocks per line 和 max_lines per frame 需要设置下,当设置到4096的时候可以支持分辨率到最大,当然消耗的资源也更多。笔者这里太奢侈了设置了4096。实际上设置到2048就够用了。本页面的其他信号可以采取默认设置。

    Enable Generation:

    支持产生时序,这个肯定是必须勾选的。

    Enable Detection:

    支持时序扑捉,这个不是必须的,根据需要而定,如果设置了这个选项,就可以先扑捉输入的时序,然后再设置输出的时序,实现输入和输出一致的效果。

    wpsF5B4.tmp

    在这个页面中,只要选择需要支持的分辨率就可以了,当然不设置也没关系的,因为我们在C代码综合那个会进一步设置的。

    4.4.2 VTC IP接口信号的定义

    wpsF5C5.tmp
    红色方框内的绝大部分信号需要我们手动联系,所以下面重点是讲解红色方框内的信号作用,至于AXI4-LITE接口主要是用来设置参数的。

    Common Port Descriptions

    wpsF5C6.tmpwpsF5C7.tmp

    wpsF5D7.tmpwpsF5D8.tmpwpsF5D9.tmp

    本例子中没有使用到输入时序的扑捉,因此笔者下面只对用到的信号做一些介绍。

    hsync_out

    产生行同步输出

    hsync_out

    产生行消影

    vsync_out:

    产生场同步输出

    vblank_out:

    产生场消影

    active_video_out:

    有效数据输出


    4.4.3 VTC IP配置寄存器

    shows the start of the horizontal front porch (Hblank Start), synchronization

    (Hsync Start), back porch (Hsync End) and active video (SAV). It also shows the start of the

    vertical front porch (Vblank Start), synchronization (Vsync Start), back porch (Vsync End)

    and active video (SAV). The total number of horizontal clock cycles is HSIZE and the total

    number of lines is the VSIZE.

    wpsF5EA.tmp

    Generator Active Size Register (Address Offset 0x0060)

    wpsF5EB.tmp

    这是重要的寄存器用来设置有效的行数量和场数量

    Generator Timing Status Register (Address Offset 0x0064)

    wpsF5EC.tmp

    GEN_ACTIVE_VIDEO:当第一帧图像输出时候置1

    GEN_VBLANK:第一帧有效图像的blank信号输出的时候置1

    Generator Encoding Register (Address Offset 0x0068)

    wpsF5ED.tmp

    CHROMA_PARITY:奇偶色度(读者没明白)

    FIELD_ID_PARITY:奇偶场标志

    INTERLACED:视频格式是渐进式还是各行扫描

    VIDEO_FORMAT:视频格设置,有YUV422  YUV444  YUV420  RGB

    Generator Polarity Register (Address Offset 0x006C)

    wpsF5FD.tmp

    wpsF5FE.tmp这个寄存器设置相应的场输出极性和色度输出极性。

    Generator Horizontal Frame Size Register (Address Offset 0x0070)

    wpsF5FF.tmp

    一副图像的一行的大小,包括了消隐和有效数据阶段。

    Generator Vertical Frame Size Register (Address Offset 0x0074)

    wpsF610.tmp

    一副图像的一场的大小,包括了消隐和有效数据阶段。


    Generator Horizontal Sync Register (Address Offset 0x0078)

    wpsF611.tmp

    设置行的水平同步结束和同步开始

    Generator Frame/Field 0 Vertical Blank Cycle Register (Address Offset 0x007C)

    wpsF612.tmp

    设置Fram/Field0的水平消隐结束和开始

    Generator Frame/Field 0 Vertical Sync Line Register (Address Offset 0x0080)

    wpsF623.tmp

    设置Fram/Field0的垂直同步垂直结束和开始
    Generator Frame/Field 0 Vertical Sync Cycle Register (Address Offset 0x0084)

    wpsF624.tmp

    设置Fram/Field0的垂直同步水平结束和开始

    Generator Field 1 Vertical Blank Cycle Register (Address Offset 0x0088)

    wpsF625.tmp

    设置Field1的水平消隐结束和开始

    Generator Field 1 Vertical Sync Line Register (Address Offset 0x008C)

    wpsF626.tmp

    设置Field1的垂直同步垂直结束和开始

    Generator Field 1 Vertical Sync Cycle Register (Address Offset 0x0090)

    wpsF636.tmp

    设置Field1的垂直同步水平结束和开始

    Frame Sync 015 Configuration Registers (Address Offsets 0x0100 0x013C)

    wpsF637.tmp

    Generator Global Delay Register (Address Offset 0x140)

    wpsF638.tmpwpsF649.tmp

    4.4.5设置VTC IP

    讲了这么多实际上我们用的时候很简单,所以只要这么简单。

    wpsF64A.tmp

    由于不使用动态配置,并且只使用了视频时序产生,所以只要勾选如下复选框。

    wpsF64B.tmp

    由于OV5640分辨率是1280X720因此直接选择720P即可。
    wpsF64C.tmp

    4.6 PLL时钟设置

    由于这里的分辨率是1280X720因此提供给VTC IP 和VID OUTIP的时钟74.5M就可以了

    wpsF65C.tmp

    4.7 VID_OUT IP的分析

    4.7.1 VID_OUT 的参数介绍

    wpsF65D.tmp

    wpsF65E.tmp

    这些参数和前面的V_TPG参数类似

    • Pixels Per Clock: 设置每个时钟输出的像素个数,可以是1、2、4

    • Input Component Width: 输入像素的宽度,这个参数影响TDATA的位宽

    • Output Component Width:输出像素的宽度

    • Clock Mode:时钟的模式,可以选择独立时钟,或者共享时钟

    • Video Format: 视频格式

    • FIFO Depth: FIFO深度

    • Hysteresis Level: 滞后输出

    4.7.2 VID_OUT IP接口信号的定义

    wpsF66F.tmp

    wpsF670.tmp

    Video Timing Interface

    wpsF671.tmp

    AXI4‐Stream Interface

    wpsF672.tmp

    对于s_axis_video_tdata(TDATA)需要注意一些事情,一般情况下我们的RGB888 输出,但是,如果s_axis_video_tdata是32bit 那么VID_OUT IP会自动截取到24bit。由于技术手册只给出了12bit 到 8bit 的截取方式,也就是RGB 12:12:12 到RGB 8:8:8如下图:

    wpsF683.tmp

    这种截取比较简单,把每个色度的低4bit截取就可以了。但是如果是RGB10:10:10 ,官方并没有给出截取方式,但是可以通过纯色输出来进行测试。

    因此最简单的办法是无需任何截取了,如果s_axis_video_tdata是RGB8:8:8 那就无需任何截取,笔者设计的时候由于AXI 总线是32bit 因此数据的低24bit为RGB 8:8:8只要去掉高24-31bit就可以取得RGB8:8:8,这样最省事。

    以下时序图是在SOF是一帧图像的开始,当VALID 和 READY有效的时候开始传输数据。

    wpsF684.tmp

    EOL代表每一行的最后一个数据,SOF代表前一帧的最后一行的结束,下一帧第一行的开始。SOF为1个PLUS有效(pg044_v_axis_out.pdf 没有描述清楚,而且有错误)。

    wpsF685.tmpExample Horizontal Generation Register Inputs

    wpsF695.tmp

    设置水平输出的相关寄存器

    wpsF696.tmp

    水平输出时序图

    Example Vertical Generation Register Inputs

    wpsF697.tmp

    设置垂直输出的相关寄存器

    wpsF698.tmp

    垂直输出时序图

    4.8 FPGA 实现的用户逻辑代码

    4.8.1关键信号1

    assign s_axis_s2mm_tlast = m_axis_video_tvalid & s_axis_s2mm_tready & m_axis_video_tlast &(vid_in_v_cnt == VID_IN_VS);// dma in last signal

    m_axis_video_tvalid:此信号是vid in IP输出的,代表输出数据有效

    s_axis_s2mm_tready:此信号是DMA IP 输出的,代表DMA可以接收数据

    m_axis_video_tlast:这是每一行图像数据的最后一个像素的信号标志

    vid_in_v_cnt == VID_IN_VS:表示一副图像的最后一个像素输出。

    s_axis_s2mm_tlast:所有这些信号有效的时候代表DMA的最后一个数据s_axis_s2mm_tlast信号有效。

    4.8.2关键信号2

    assign s_axis_video_tuser = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == 11'd0) & (vid_out_v_cnt == 11'd0); //vid out user

    m_axis_mm2s_tvalid:是M_AXIS_MM2S接口(读DMA接口)的数据有效标志。

    s_axis_video_tready:vid out IP 准备好了,可以接收数据

    (vid_out_h_cnt == 11'd0) & (vid_out_v_cnt == 11'd0);行计数器为0场计数器也为0说明要么这副图像已经结束,也可以理解为下一副图像开始前。这样结合s_axis_video_tready,m_axis_mm2s_tvalid为1,基于FPGA时序,下一个时钟输出s_axis_video_tuser为1正好是一副图像的第一个像素。

    s_axis_video_tuser:因此s_axis_video_tuser代表了每一副图像开始的第一个像素。

    4.8.3关键信号3

    assign s_axis_video_tlast = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == VID_OUT_HS);//vid out last signal

    m_axis_mm2s_tvalid:是M_AXIS_MM2S接口(读DMA接口)的数据有效标志。

    s_axis_video_tready:vid out IP 准备好了,可以接收数据

    vid_out_h_cnt == VID_OUT_HS):图像一行数据的最后一个像素。

    4.8.4 部分关键代码

    表3-6-4-1

    reg [10:0] vid_out_v_cnt;

    reg [10:0] vid_out_h_cnt;

    reg [10:0] vid_in_v_cnt;

    parameter VID_OUT_HS = 11'd1279;//图像输出行分辨率

    parameter VID_OUT_VS = 11'd719;//图像输出场分辨率

    parameter VID_IN_VS = 11'd719;

    always@(posedge FCLK_CLK0)

    begin

       if(!gpio_rtl_tri_o_0)

         vid_out_v_cnt <= 11'd0;

       else

          if(m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == VID_OUT_HS))

         if(vid_out_v_cnt != VID_OUT_VS)

            vid_out_v_cnt <= vid_out_v_cnt + 1'b1;

    else

        vid_out_v_cnt <= 11'd0;

      else

         vid_out_v_cnt <= vid_out_v_cnt;

    end

    always@(posedge FCLK_CLK0)

    begin

       if(!gpio_rtl_tri_o_0)

         vid_out_h_cnt <= 11'd0;

       else

          if(m_axis_mm2s_tvalid & s_axis_video_tready)

         if(vid_out_h_cnt != VID_OUT_HS)

            vid_out_h_cnt <= vid_out_h_cnt + 1'b1;

    else

        vid_out_h_cnt <= 11'd0;

      else

         vid_out_h_cnt <= vid_out_h_cnt;

    end

    always@(posedge FCLK_CLK0)

    begin

       if(!gpio_rtl_tri_o_0)

         vid_in_v_cnt <= 11'd0;

       else

          if(m_axis_video_tvalid & s_axis_s2mm_tready & m_axis_video_tlast)

         if(vid_in_v_cnt != VID_IN_VS)

            vid_in_v_cnt <= vid_in_v_cnt + 1'b1;

    else

        vid_in_v_cnt <= 11'd0;

      else

            vid_in_v_cnt <= vid_in_v_cnt;

    end

    assign s_axis_video_tuser = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == 11'd0) & (vid_out_v_cnt == 11'd0); //vid out user

    assign s_axis_video_tlast = m_axis_mm2s_tvalid & s_axis_video_tready & (vid_out_h_cnt == VID_OUT_HS);//vid out last signal

    assign s_axis_s2mm_tlast = m_axis_video_tvalid & s_axis_s2mm_tready & m_axis_video_tlast &(vid_in_v_cnt == VID_IN_VS);// dma in last signal

    4.9 PS部分

    4.9.1 DMA中断函数部分分析

    为了让图像输出高品质效果,PS部分设计了3缓存处理机制。3缓存处理机制在大量图像缓冲处理方法是最有效的办法之一。

    在DMA_intr.h文件中,定义3段内存空间用于保存三副最新的图像。

    #define BUFFER0_BASE (MEM_BASE_ADDR )

    #define BUFFER1_BASE (MEM_BASE_ADDR + IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

    #define BUFFER2_BASE (MEM_BASE_ADDR + 2 * IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

    在DMA_intr.h文件中,还定义一下2个变量1个指针数组。tx_buffer_index;指示了当前的发送缓存序号,rx_buffer_index;指示了当前的接收缓存序号。*BufferPtr[3]会被制定到对应的内存地址空间。

    extern volatile u8 tx_buffer_index;

    extern volatile u8 rx_buffer_index;

    extern u32 *BufferPtr[3];

    在main函数里面有这么一段实现了指针数组指向内存地址空间。

    BufferPtr[0] = (u32 *)BUFFER0_BASE;

    BufferPtr[1] = (u32 *)BUFFER1_BASE;

    BufferPtr[2] = (u32 *)BUFFER2_BASE;

    下面给出dma_intr.h的完整代码

    表3-7-1-1 dma_intr.h

    /*

    *

    * www.osrc.cn

    * www.milinker.com

    * copyright by nan jin mi lian dian zi www.osrc.cn

    */

    #ifndef DMA_INTR_H

    #define DMA_INTR_H

    #include "xaxidma.h"

    #include "xparameters.h"

    #include "xil_exception.h"

    #include "xdebug.h"

    #include "xscugic.h"

    /************************** Constant Definitions *****************************/

    /*

    * Device hardware build related constants.

    */

    #define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID

    #define MEM_BASE_ADDR 0x10000000

    #define RX_INTR_ID XPAR_FABRIC_AXI_DMA_0_S2MM_INTROUT_INTR

    #define TX_INTR_ID XPAR_FABRIC_AXI_DMA_0_MM2S_INTROUT_INTR

    #define IMAGE_WIDTH     1280

    #define IMAGE_HEIGHT 720

    #define BYTES_PER_PIXEL 4

    #define BUFFER_NUM     2

    #define MEM_BASE_ADDR 0x10000000

    #define BUFFER0_BASE (MEM_BASE_ADDR )

    #define BUFFER1_BASE (MEM_BASE_ADDR +     IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

    #define BUFFER2_BASE (MEM_BASE_ADDR + 2 * IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

    /* Timeout loop counter for reset

    */

    #define RESET_TIMEOUT_COUNTER 10000

    /* test start value

    */

    #define TEST_START_VALUE 0xC

    /*

    * Buffer and Buffer Descriptor related constant definition

    */

    #define MAX_PKT_LEN (IMAGE_WIDTH * IMAGE_HEIGHT * BYTES_PER_PIXEL)

    /*

    * transfer times

    */

    #define NUMBER_OF_TRANSFERS 100000

    extern volatile int TxDone;

    extern volatile int RxDone;

    extern volatile int Error;

    extern  volatile u8 tx_buffer_index;

    extern  volatile u8 rx_buffer_index;

    extern  u32 *BufferPtr[3];

    int  DMA_CheckData(int Length, u8 StartValue);

    int  DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId);

    int  DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr);

    int  DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId);

    #endif

    每次一整副图像通过DMA进入DDR后,会产生DMA中断请求,在DMA中断请求中,会指定下一次DMA接收的buffer位置。

    表3-7-1-2 DMA_RxIntrHandler函数

    /*****************************************************************************/

    /*

    *

    * This is the DMA RX interrupt handler function

    *

    * It gets the interrupt status from the hardware, acknowledges it, and if any

    * error happens, it resets the hardware. Otherwise, if a completion interrupt

    * is present, then it sets the RxDone flag.

    *

    * @param Callback is a pointer to RX channel of the DMA engine.

    *

    * @return None.

    *

    * @note None.

    *

    ******************************************************************************/

    static void DMA_RxIntrHandler(void *Callback)

    {

    u32 IrqStatus;

    u32 Status;

    int TimeOut;

    XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

    /* Read pending interrupts */

    IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

    /* Acknowledge pending interrupts */

    XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

    /*

    * If no interrupt is asserted, we do not do anything

    */

    if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

    return;

    }

    /*

    * If error interrupt is asserted, raise error flag, reset the

    * hardware to recover from the error, and return with no further

    * processing.

    */

    if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

    xil_printf("rx error! ");

    return;

    }

    /*

    * If completion interrupt is asserted, then set RxDone flag

    */

    if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

    RxDone++;

    }

    if(rx_buffer_index == 2)

        rx_buffer_index = 0;

    else

    rx_buffer_index++;

    Status = XAxiDma_SimpleTransfer(AxiDmaInst, (u32)BufferPtr[rx_buffer_index],

    MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

    if (Status != XST_SUCCESS) {

    xil_printf("rx axi dma failed! 0 %d ", Status);

    return;

    }

    }

    发送函数通过tx_buffer_index标记需要发送的缓存部分,并且确保发送的是最新保存的一副图像。

    表3-7-3 DMA_TxIntrHandler

    /*****************************************************************************/

    /*

    *

    * This is the DMA TX Interrupt handler function.

    *

    * It gets the interrupt status from the hardware, acknowledges it, and if any

    * error happens, it resets the hardware. Otherwise, if a completion interrupt

    * is present, then sets the TxDone.flag

    *

    * @param Callback is a pointer to TX channel of the DMA engine.

    *

    * @return None.

    *

    * @note None.

    *

    ******************************************************************************/

    static void DMA_TxIntrHandler(void *Callback)

    {

    u32 IrqStatus;

    u32 Status;

    int TimeOut;

    XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

    /* Read pending interrupts */

    IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);

    /* Acknowledge pending interrupts */

    XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

    /*

    * If no interrupt is asserted, we do not do anything

    */

    if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

    return;

    }

    /*

    * If error interrupt is asserted, raise error flag, reset the

    * hardware to recover from the error, and return with no further

    * processing.

    */

    if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

    //Error = 1;

    xil_printf("tx error! ");

    return;

    }

    /*

    * If Completion interrupt is asserted, then set the TxDone flag

    */

    if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

    TxDone ++;

    }

    if(rx_buffer_index == 0)

    tx_buffer_index = 2;

    else

    tx_buffer_index = rx_buffer_index - 1;

    Status = XAxiDma_SimpleTransfer(AxiDmaInst, (u32)BufferPtr[tx_buffer_index],

    MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

    if (Status != XST_SUCCESS) {

    xil_printf("tx axi dma failed! 0 %d ", Status);

    return;

    }

    }

    4.9.2 main.c文件

    这个主程序比较简单,内容比上一个课程的精简很多,这里需要注意的地方是XGpio_DiscreteWrite(&Gpio, 1, 1);函数这个函数是这只摄像头和DMA之间数据同步的,没有这个同步图像容易错位。另外在主函数里面首先启动DMA接收和发送中断各一次,以后就可以在中断里面继续触发了。

    表3-7-2-1 main.c

    /*

    *

    * www.osrc.cn

    * www.milinker.com

    * copyright by nan jin mi lian dian zi www.osrc.cn

    * axi dma test

    *

    */

    #include "dma_intr.h"

    #include "sys_intr.h"

    #include "xgpio.h"

    volatile int TxDone;

    volatile int RxDone;

    volatile int Error;

    volatile u8 tx_buffer_index;

    volatile u8 rx_buffer_index;

    u32 *BufferPtr[3];

    static XScuGic Intc; //GIC

    static XAxiDma AxiDma;

    static XGpio Gpio;

    #define AXI_GPIO_DEV_ID         XPAR_AXI_GPIO_0_DEVICE_ID

    int init_intr_sys(void)

    {

    DMA_Intr_Init(&AxiDma,0);//initial interrupt system

    Init_Intr_System(&Intc); // initial DMA interrupt system

    Setup_Intr_Exception(&Intc);

    DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//setup dma interrpt system

    DMA_Intr_Enable(&Intc,&AxiDma);

    }

    int main(void)

    {

    u32 Status;

    BufferPtr[0] = (u32 *)BUFFER0_BASE;

    BufferPtr[1] = (u32 *)BUFFER1_BASE;

    BufferPtr[2] = (u32 *)BUFFER2_BASE;

    tx_buffer_index = 0;

    rx_buffer_index = 0;

    TxDone = 0;

    RxDone = 0;

    Error = 0;

    XGpio_Initialize(&Gpio, AXI_GPIO_DEV_ID);

    XGpio_SetDataDirection(&Gpio, 1, 0);

    init_intr_sys();

    Miz702_EMIO_init();

    Ov5640_init_rgb();

    XGpio_DiscreteWrite(&Gpio, 1, 1);

    Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)BufferPtr[rx_buffer_index],

    MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

    Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)BufferPtr[tx_buffer_index],

    MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

    while (1) ;

    return XST_SUCCESS;

    }

    4.10实验效果

    wpsF6B8.tmp

  • 相关阅读:
    groovy 执行shell
    expect 用法
    shebang解释
    docker 安装
    centos7 lvm添加硬盘后扩展磁盘空间
    scoped的原理和deep深度选择器的妙用
    swagger3
    帮评网
    反射工具
    网络只能传输二进制
  • 原文地址:https://www.cnblogs.com/milinker/p/6484206.html
Copyright © 2011-2022 走看看