利用摄像头捕获数据、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协议与8.5节类似,但是需要在图像显示有效区去读取FIFO,然后将数据输出到VGA接口上。由于摄像头的接口是RGB565,而VGA接口为RGB232接口,故将RGB565对应的高位输出到RGB232上(再分配管脚时,低位不分配也可以)。具体代码查看vga_ctrl。
本系统中的图像模式为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屏幕上。
大家好,我是【FPGA功夫熊猫】精益求精,不断推荐好文章。