硬件研发网|技术阅读
登录|注册

您现在的位置是:硬件研发网 > 技术阅读 > FPGA零基础学习:图像显示系统设计

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


大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。“煮酒言欢”进入IC技术圈,这里有近50个IC技术公众号。


大侠好,欢迎来到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)内不允许进行任何其他操作。在复位完成后,输出一个复位完成信号。

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。写地址正常切换即可)。

 


部分参考代码如下:


顶层代码:

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模块代码:

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模块代码

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模块代码

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屏幕上。





- End -

福利】:QQ交流群173560979,进群备注名字+学校/企业。淘宝店铺:https://shop588964188.taobao.com
论坛网址:www.sxznfpga.com
郝旭帅团队制作

往期精选

  










扫描二维码更精彩



喜欢就点个在看再走吧