FPGA零基础学习:图像显示系统设计

2021-03-23 09:56:29 浏览数 (1)

FPGA零基础学习:图像显示系统设计

大侠好,欢迎来到FPGA技术江湖。本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。

图像显示系统设计

作者:郝旭帅 校对:陆辉

利用摄像头捕获数据、SDRAM缓存数据、VGA协议驱动屏幕显示图像构成图像实时显示系统。

摄像头捕获数据的速度(12MHz、6MHz、3MHz)与VGA协议驱动速度(25MHz)不同,导致摄像头捕获数据不能够直接输出给VGA,所以中间必须加入大容量的缓冲器。

整个设计需要的时钟有:给摄像头提供24MHz的时钟,给SDR SDRAM提供的100MHz的时钟(相移270度),给SDR SDRAM控制器提供的100MHz的时钟,给VGA协议驱动提供的100MHz的时钟。

时钟产生

采用片内的PLL产生所需的时钟。

摄像头驱动设计

摄像头设计共分为三部分:硬件复位(ov7670_hardware_reset)、寄存器配置(ov7670_reg_init)、数据捕获输出(ov7670_cap)。

硬件复位和寄存器配置采用24MHz的时钟进行驱动,摄像头捕获模块采用摄像头输出的pclk来进行驱动。

硬件复位的操作为将cmos_rst_n信号拉低一段时间(大约1ms),拉高后一段时间(大约1ms)内不允许进行任何其他操作。在复位完成后,输出一个复位完成信号。

代码语言:javascript复制
module ov7670_hardware_reset (

  input   wire                        clk,
  input   wire                        rst_n,
  
  output  wire                        cmos_rst_n,
  output  wire                        cmos_hardware_rst_done
);

  parameter       T               =   50_000;
  
  reg                 [15:0]          cnt;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt <= 16'd0;
    else
      if (cnt < T)
        cnt <= cnt   1'b1;
      else
        cnt <= cnt;
  end
  
  assign cmos_rst_n = (cnt < T/2) ? 1'b0 : 1'b1;
  
  assign cmos_hardware_rst_done = (cnt == T) ? 1'b1 : 1'b0;

endmodule

摄像头有很多寄存器,具体可以查看手册中所对应的信息,这里只给出一些关键寄存器的配置。

配置寄存器的地址和配置所需的数据拼接到一起形成一个八位的数据。具体代码查看reg_config。

利用线性序列机实现SCCB协议驱动,将对应的数据配置进去。具体代码查看sccb_wr。

编写控制器从reg_config中读出数据,控制sccb_wr模块将数据配置到摄像头中,配置完成后需要等待10帧的图像(摄像头输出的VS信号为帧同步信号,有一次的高脉冲表示一帧,设计时只需要等待VS信号的10个上升沿即可),才能够输出稳定的图像信息。具体代码查看ov7670_reg_config_ctrl。

图像数据的捕获比较简单,按照摄像头手册的标准输出时序进行捕获即可。由于摄像头输出的数据为RGB565,而摄像头接口只有三位数据线,所以输出时,每两个数据对应一个像素点。具体代码查看ov7670_cap。

VGA协议驱动

VGA协议与8.5节类似,但是需要在图像显示有效区去读取FIFO,然后将数据输出到VGA接口上。由于摄像头的接口是RGB565,而VGA接口为RGB232接口,故将RGB565对应的高位输出到RGB232上(再分配管脚时,低位不分配也可以)。具体代码查看vga_ctrl。

SDR SDRAM控制器

本系统中的图像模式为640X480,在SDRAM中存储的方式设定为SDRAM每一行存储160个像素点,利用四行的存储空间存储一行的图像信息。故而需要将SDR SDRAM控制器中的读写模块更改为页读页写模式,并且每次突发的长度为160。具体代码查看sdr_wr_ctrl和sdr_rd_ctrl。

SDR SDRAM的控制器中共分为四部分:输入缓冲器(sdr_wrfifo)、输出缓冲器(sdr_rdfifo)、SDR SDRAM驱动(sdr_drive)和读写控制器(sdr_mem_ctrl)。

输入缓冲器为一个FIFO,捕获到摄像头数据输入到此FIFO中,然后写入到SDRAM中。

输出缓冲器为一个FIFO,SDRAM的数据输入到此FIFO中,然后被VGA模块读出输出给VGA接口。

SDR SDRAM驱动为控制接口模块,完成对SDRAM的写入和读出。

读写控制器为控制上述三个模块进行协调工作的模块:当输入缓冲器中的数量大于160时,读出160个写入SDRAM中;当输出缓冲器中的数量小于160时,从SDRAM中读出160个写入到输出缓冲器中。每次控制读写命令发出后,等待100个时钟周期(等待SDRAM控制器读写进行)。在进行写入和读出时,为了防止图像撕裂(写入速度比读出速度要慢,读出数据时,就会发生前半帧为新数据,后半帧为旧数据,造成一种图像撕裂的感觉),采用两个bank进行缓冲(当输出地址在最后一行时,需要判断输入地址的位置,当输入地址在另外一个bank的下半部分或者已经在本bank时,读地址切换到另外一个bank。写地址正常切换即可)。

部分参考代码如下:

顶层代码:

代码语言:javascript复制
module ov7670_sdram_vga640x480 (

  input     wire                      clk, // 50MHz
  input     wire                      rst_n,
  
//  VGA
  output    wire                      vga_vs,
  output    wire                      vga_hs,
  output    wire          [15:0]      vga_rgb,
  
//  SDRAM
  output    wire                      sdr_clk,
  output    wire                      sdr_cke,
  output    wire                      sdr_cs_n,
  output    wire                      sdr_ras_n,
  output    wire                      sdr_cas_n,
  output    wire                      sdr_we_n,
  output    wire          [1:0]       sdr_ba,
  output    wire          [11:0]      sdr_addr,
  inout     wire          [15:0]      sdr_dq,
  output    wire          [1:0]       sdr_dqm,
  
//  ov7670 
  output    wire                      cmos_xclk,
  output    wire                      cmos_rst_n,
  output    wire                      cmos_pwdn,
  output    wire                      cmos_sccb_c,
  inout     wire                      cmos_sccb_d,
  input     wire                      cmos_pclk,
  input     wire                      cmos_vs,
  input     wire                      cmos_href,
  input     wire          [7:0]       cmos_data
);

  wire                                clk_100m;
  wire                                clk_25m;
  wire                                pll_locked;
  reg                     [1:0]       rst_n_100m;
  reg                     [1:0]       rst_n_cmos_xclk;
  reg                     [1:0]       rst_n_25m;
  wire                                cmos_init_done;
  wire                                cmos_cap_valid;
  wire                    [15:0]      cmos_cap_data;
  wire                                cmos_frame_flag;
  wire                                vga_rden;
  wire                    [15:0]      vga_data;
  
  pll_my  pll_my_inst (
        .areset       ( ~rst_n ),
        .inclk0       ( clk ),
        .c0           ( clk_100m ),
        .c1           ( sdr_clk ),
        .c2           ( cmos_xclk ),
        .c3           ( clk_25m ),
        .locked       ( pll_locked )
      );
      
  initial rst_n_100m = 2'b00;
  always @ (posedge clk_100m) rst_n_100m <= {rst_n_100m[0], pll_locked};
  
  initial rst_n_cmos_xclk = 2'b00;
  always @ (posedge cmos_xclk) rst_n_cmos_xclk <= {rst_n_cmos_xclk[0], pll_locked};
  
  initial rst_n_25m = 2'b00;
  always @ (posedge clk_25m) rst_n_25m <= {rst_n_25m[0], pll_locked};
  
  ov7670_drive ov7670_drive_inst(

      .clk                (cmos_xclk),
      .rst_n              (rst_n_cmos_xclk[1]),

      .cmos_rst_n         (cmos_rst_n),
      .cmos_pwdn          (cmos_pwdn),
      .cmos_sccb_c        (cmos_sccb_c),
      .cmos_sccb_d        (cmos_sccb_d),
      .cmos_pclk          (cmos_pclk),
      .cmos_vs            (cmos_vs),
      .cmos_href          (cmos_href),
      .cmos_data          (cmos_data),
      
      .cmos_init_done     (cmos_init_done),
      .cmos_cap_data      (cmos_cap_data),
      .cmos_cap_valid     (cmos_cap_valid),
      .cmos_frame_flag    (cmos_frame_flag)
    );
      
  fifo_sdr_ctrl fifo_sdr_ctrl_inst(

      .clk                (clk_100m),
      .rst_n              (rst_n_100m[1]),
      
      .wrclk              (cmos_pclk),
      .wren               (cmos_cap_valid),
      .wrdata             (cmos_cap_data),
      
      .rdclk              (clk_25m),
      .rden               (vga_rden),
      .rddata             (vga_data),
      
      .sdr_cke            (sdr_cke),
      .sdr_cs_n           (sdr_cs_n),
      .sdr_ras_n          (sdr_ras_n),
      .sdr_cas_n          (sdr_cas_n),
      .sdr_we_n           (sdr_we_n),
      .sdr_ba             (sdr_ba),
      .sdr_addr           (sdr_addr),
      .sdr_dq             (sdr_dq),
      .sdr_dqm            (sdr_dqm)
    );

  vga_ctrl vga_ctrl_inst(

      .clk                (clk_25m),
      .rst_n              (rst_n_25m[1]),
      
      .vga_rden           (vga_rden),
      .vga_data           (vga_data),   
      
      .vga_hs             (vga_hs),
      .vga_vs             (vga_vs),
      .vga_rgb            (vga_rgb)
    );

endmodule 

vga_ctrl模块代码:

代码语言:javascript复制
module vga_ctrl (

  input   wire                    clk,
  input   wire                    rst_n,
  
  output  wire                    vga_rden,
  input   wire      [15:0]        vga_data,
  
  output  reg                     vga_hs,
  output  reg                     vga_vs,
  output  reg       [15:0]        vga_rgb
);

// 640x480x60Hz  

  parameter         HS_A        =   96;
  parameter         HS_B        =   48;
  parameter         HS_C        =   640;
  parameter         HS_D        =   16;
  parameter         HS_E        =   800;
  
  parameter         VS_A        =   2;
  parameter         VS_B        =   33;
  parameter         VS_C        =   480;
  parameter         VS_D        =   10;
  parameter         VS_E        =   525;
  
  reg               [9:0]         cnt_hs;
  reg               [9:0]         cnt_vs;
  wire                            hs_en;
  wire                            vs_en;
  wire                            en;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt_hs <= 10'd0;
    else
      if (cnt_hs < HS_E - 1'b1)
        cnt_hs <= cnt_hs   1'b1;
      else  
        cnt_hs <= 10'd0;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      cnt_vs <= 10'd0;
    else  
      if (cnt_hs == HS_E - 1'b1)
        if (cnt_vs < VS_E - 1'b1)
          cnt_vs <= cnt_vs   1'b1;
        else
          cnt_vs <= 10'd0;
      else
        cnt_vs <= cnt_vs;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      vga_hs <= 1'b1;
    else
      if (cnt_hs < HS_A)
        vga_hs <= 1'b0;
      else
        vga_hs <= 1'b1;
  end
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      vga_vs <= 1'b1;
    else
      if (cnt_vs < VS_A)
        vga_vs <= 1'b0;
      else
        vga_vs <= 1'b1;
  end
  
  assign hs_en = (cnt_hs > HS_A   HS_B - 1'b1) && (cnt_hs < HS_A   HS_B   HS_C);
  assign vs_en = (cnt_vs > VS_A   VS_B - 1'b1) && (cnt_vs < VS_A   VS_B   VS_C);
  assign en = hs_en & vs_en;
  
  assign vga_rden = en;
  
  always @ (posedge clk, negedge rst_n) begin
    if (rst_n == 1'b0)
      vga_rgb <= 16'd0;
    else
      if (en == 1'b1)
        vga_rgb <= vga_data;
      else
        vga_rgb <= 16'd0;
  end

endmodule 

ov7670_drive模块代码:

代码语言:javascript复制
module ov7670_drive (

  input     wire                  clk,
  input     wire                  rst_n,
  
  output    wire                  cmos_rst_n,
  output    wire                  cmos_pwdn,
  output    wire                  cmos_sccb_c,
  inout     wire                  cmos_sccb_d,
  input     wire                  cmos_pclk,
  input     wire                  cmos_vs,
  input     wire                  cmos_href,
  input     wire          [7:0]   cmos_data,
  
  output    wire                  cmos_init_done,
  output    wire          [15:0]  cmos_cap_data,
  output    wire                  cmos_cap_valid,
  output    wire                  cmos_frame_flag
);

  wire                            cmos_hardware_rst_done;
  
  assign cmos_pwdn = 1'b0;
  
  reg                     [1:0]   rst_n_pclk;
  
  initial rst_n_pclk = 2'b00;
  
  always @ (posedge cmos_pclk) rst_n_pclk <= {rst_n_pclk[0], rst_n};
  
  ov7670_hardware_reset ov7670_hardware_reset_inst(

      .clk                        (clk),
      .rst_n                      (rst_n),
      
      .cmos_rst_n                 (cmos_rst_n),
      .cmos_hardware_rst_done     (cmos_hardware_rst_done)
    );
    
  ov7670_reg_init ov7670_reg_init_inst(

      .clk                        (clk),
      .rst_n                      (rst_n),
      
      .cmos_hardware_rst_done     (cmos_hardware_rst_done),
      .cmos_vs                    (cmos_vs),
      .cmos_frame_flag            (cmos_frame_flag),
      
      .cmos_sccb_c                (cmos_sccb_c),
      .cmos_sccb_d                (cmos_sccb_d),
      
      .cmos_init_done             (cmos_init_done)
    );
      
  ov7670_cap ov7670_cap_inst(

      .clk                        (cmos_pclk),
      .rst_n                      (rst_n_pclk[1]),
      
      .cmos_init_done             (cmos_init_done),
      .cmos_href                  (cmos_href),
      .cmos_data                  (cmos_data),
      
      .cmos_cap_data              (cmos_cap_data),
      .cmos_cap_valid             (cmos_cap_valid)
    );


endmodule 

fifo_sdr_ctrl模块代码:

代码语言:javascript复制
module fifo_sdr_ctrl (

  input     wire                      clk,
  input     wire                      rst_n,
  
  input     wire                      wrclk,
  input     wire                      wren,
  input     wire      [15:0]          wrdata,
  
  input     wire                      rdclk,
  input     wire                      rden,
  output    wire      [15:0]          rddata,
  
  output    wire                      sdr_cke,
  output    wire                      sdr_cs_n,
  output    wire                      sdr_ras_n,
  output    wire                      sdr_cas_n,
  output    wire                      sdr_we_n,
  output    wire          [1:0]       sdr_ba,
  output    wire          [11:0]      sdr_addr,
  inout     wire          [15:0]      sdr_dq,
  output    wire          [1:0]       sdr_dqm
);

  wire                                sdr_wrfifo_rden;
  wire                    [15:0]      sdr_wrfifo_rddata;
  wire                    [8:0]       sdr_wrfifo_rdusedw;
  wire                    [15:0]      sdr_rdfifo_wrdata;
  wire                                sdr_rdfifo_wren;
  wire                    [8:0]       sdr_rdfifo_wrusedw;
  wire                                local_sdr_wr;
  wire                                local_sdr_rd;
  wire                                local_sdr_ready;
  wire                    [21:0]      local_sdr_addr;

  sdr_wrfifo  sdr_wrfifo_inst (
      .aclr           ( ~rst_n ),
      .data           ( wrdata ),
      .rdclk          ( clk ),
      .rdreq          ( sdr_wrfifo_rden ),
      .wrclk          ( wrclk ),
      .wrreq          ( wren ),
      .q              ( sdr_wrfifo_rddata ),
      .rdusedw        ( sdr_wrfifo_rdusedw )
    );

  sdr_rdfifo  sdr_rdfifo_inst (
      .aclr           ( ~rst_n ),
      .data           ( sdr_rdfifo_wrdata ),
      .rdclk          ( rdclk ),
      .rdreq          ( rden ),
      .wrclk          ( clk ),
      .wrreq          ( sdr_rdfifo_wren ),
      .q              ( rddata ),
      .wrusedw        ( sdr_rdfifo_wrusedw )
    );

  sdr_mem_ctrl sdr_mem_ctrl_inst(

      .clk            (clk),
      .rst_n          (rst_n),
      
      .wrfifo_rdusedw (sdr_wrfifo_rdusedw),
      
      .local_sdr_wr   (local_sdr_wr),
      .local_sdr_addr (local_sdr_addr),
      .local_sdr_ready(local_sdr_ready),
      
      .rdfifo_wrusedw (sdr_rdfifo_wrusedw),
      
      .local_sdr_rd   (local_sdr_rd)
    );
    
  sdr_drive sdr_drive_inst(

      .clk                  (clk),
      .rst_n                (rst_n),
      
      .local_wr             (local_sdr_wr),
      .local_rd             (local_sdr_rd),
      .local_addr           (local_sdr_addr),
      .local_wrdata_rden    (sdr_wrfifo_rden),
      .local_wrdata         (sdr_wrfifo_rddata),
      .local_rddata         (sdr_rdfifo_wrdata),
      .local_rdflag         (sdr_rdfifo_wren),
      .local_ready          (local_sdr_ready),
     
      .sdr_cke              (sdr_cke),
      .sdr_cs_n             (sdr_cs_n),
      .sdr_ras_n            (sdr_ras_n),
      .sdr_cas_n            (sdr_cas_n),
      .sdr_we_n             (sdr_we_n),
      .sdr_ba               (sdr_ba),
      .sdr_addr             (sdr_addr),
      .sdr_dq               (sdr_dq),
      .sdr_dqm              (sdr_dqm)
    );

endmodule 

具体设计参考代码_15_ov7670_sdram_vga640x480,代码获取方式可以加QQ交流群咨询。

综合下板后,开发板即可将摄像头捕获到的图像,显示到VGA屏幕上。

0 人点赞