马上注册,看完整文章,学更多FPGA知识。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
第1节 OV7670摄像头显示--作者:小黑同学
1.1 总体设计1.1.1 概述OV7670是一种图像传感器,图像传感器,体积小,工作电压低,提供单片VGA摄像头和影像处理器的所有功能。通过SCCB总线控制,可以输入整帧、子采样、取窗口等方式的各种分辨率8位影像数据。该产品VGA图像最高达到30帧/秒。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、饱和度、色度等都可以通过SCCB接口编程。OmmiVision图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、托尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。
1.1.2 设计目标本工程使用ov7670摄像头、SDRAM、VGA显示器、按键等,实现摄像头显示的功能,具体要求如下: 1、 通过SCCB接口对摄像头中的寄存器进行配置。 2、 通过按键/矩阵键盘控制是否对摄像头进行配置。 3、 对摄像头输出的图像数据进行采集之后,通过SDRAM进行缓存 4、 通过VGA显示器显示摄像头采集到的图像。
1.1.3 系统结构框图系统结构框图如下图一所示:
图一 1.1.4模块功能
按键检测模块实现功能1、 将外来异步信号打两拍处理,将异步信号同步化。 2、 实现20ms按键消抖功能。 3、 实现矩阵键盘或者普通案件的检测功能,并输出有效按键信号。
锁相环PLL_11、 将摄像头输出的像素时钟转化为同频同相的25M时钟。
锁相环PLL_2
1、产生摄像头需要的25M时钟xlclk。 2、 产生SDRAM工作需要的100M时钟。
配置模块模块实现功能1、 根据按键信息,将配置表中的寄存器信息读出。 2、 根据SCCB接口模块的指示,将配置信息输出到SCCB模块。
SCCB接口模块实现功能1、 根据上游模块的读写使能命令、地址以及数据,产生对应的SCCB时序,从而将数据写入摄像头指定的寄存器中,或者从指定的摄像头寄存器中读取数据。
图像采集模块实现功能1、 根据摄像头输出格式,对像素数据进行接收。 2、 对摄像头输出数据进行串并转换。
存储控制模块实现功能1、 通过乒乓操作的方法,控制SDRAM的读写以及Bank地址,以保证图像数据连续显示到显示器上。
SDRAM接口模块实现功能1、 接收上游模块发送的读/写请求、Bank地址、行地址和写数据,产生SDRAM的控制时序。
VGA接口模块实现功能1、 产生VGA时序,在有效显示区域,将图像数据送入显示器进行显示。
1.1.5顶层信号
信号名 | | | | | | | | | | | | | | | 4位按键信号,开发板按键为矩阵键盘时,不需要该信号 | | | | 4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号 | | | | 4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号 | | | | SDRAM数据总线,既能作为数据输出,也能作为数据输入。 | | | | SDRAM时钟使能信号,决定是否启用clk输入,为高电平时,时钟有效。 | | | | SDRAM片选信号,决定设备内是否启用命令输入,当cs为低时启用,当cs为高时禁用命令输入。 | | | | | | | | | | | | | | | | 数据掩码,控制I/O的高低字节,低电平有效。例如:2’b10,表示数据高字节无效,低字节有效。 | | | | | | | | Bank地址选择信号,通过该信号决定哪个Bank正处于激活、读、写、预充电等命令期间。 | | | | SDRAM输入时钟,除cke外,SDRAM的所有输入与该引脚的上升沿同步获得。 | | | | | | | | | | | | | | | | | | | | | | | | 摄像头待机状态指示信号,该信号为1表示,摄像头处于待机状态,不工作,该信号为0,摄像头处于唤醒状态。 | | | | | | | | | | | | | | | | | | | | |
1.1.6参考代码
下面是使用工程的顶层代码: - module mdyOV7670CameraDisplay_top(
- clk ,
- rst_n ,
- key_in ,
- pclk ,
- vsync ,
- href ,
- din ,
- xclk ,
- pwdn ,
- sio_c ,
- sio_d ,
- vga_hys ,
- vga_vys ,
- vga_rgb ,
- cke ,
- cs ,
- ras ,
- cas ,
- we ,
- dqm ,
- sd_addr ,
- sd_bank ,
- sd_clk ,
- dq
- );
- input clk ;
- input rst_n ;
- input pclk ;
- input [3:0] key_in ;
- input vsync ;
- input href ;
- input [7:0] din ;
- output xclk ;
- output pwdn ;
- output vga_hys ;
- output vga_vys ;
- output [15:0] vga_rgb ;
- output sio_c ;
- output cs ;
- output ras ;
- output cas ;
- output we ;
- output [1 :0] dqm ;
- output [12:0] sd_addr ;
- output [1 :0] sd_bank ;
- output sd_clk ;
- output cke ;
-
-
-
- inout [15:0] dq ;
- inout sio_d ;
- wire [15:0] dq_in ;
- wire [15:0] dq_out ;
- wire dq_out_en ;
- wire en_sio_d_w ;
- wire sio_d_w ;
- wire sio_d_r ;
- wire xclk_ ;
- wire clk_100m ;
- wire locked ;
- wire [3:0] key_num ;
- wire en_coms ;
- wire [7:0] value_gray ;
- wire rdy ;
- wire wen ;
- wire ren ;
- wire [7:0] wdata ;
- wire capture_en ;
- wire [7:0] rdata ;
- wire rdata_vld ;
- wire [15:0] cmos_dout ;
- wire cmos_dout_vld ;
- wire cmos_dout_sop ;
- wire cmos_dout_eop ;
- wire [15:0] rd_addr ;
- wire rd_en ;
- wire [15:0] vga_data ;
- wire rd_end ;
- wire wr_end ;
- wire rd_addr_sel ;
- wire [3:0] key_vld ;
- wire display_area ;
- wire [7:0] sub_addr ;
- wire cke ;
- wire cs ;
- wire ras ;
- wire cas ;
- wire we ;
- wire [1 :0] dqm ;
- wire [12:0] sd_addr ;
- wire [1 :0] sd_bank ;
- wire sd_clk ;
- wire [15:0] sd_rdata ;
- wire sd_rdata_vld ;
- wire [15:0] fifo2sd_wdata ;
- wire wr_ack ;
- wire rd_ack ;
- wire wr_req ;
- wire rd_req ;
- wire [1 :0] bank ;
- wire [12:0] addr ;
- assign dq_in = dq;
- assign dq = dq_out_en?dq_out:16'hzzzz;
- assign sio_d = en_sio_d_w ? sio_d_w : 1'dz;
- assign sio_d_r = sio_d;
-
-
-
- pll_sd pll_sd_inst2 (
- .inclk0 (clk ),
- .c0 (xclk ),
- .c1 (clk_100m )
- );
- cmos_pll u_cmos_pll(
- .inclk0 (pclk ),
- .c0 (clk_25M )
- );
-
-
- key_module#(.KEY_W(4)) u_key_module(
- .clk (xclk ),
- .rst_n (rst_n ),
- .key_in (key_in ),
- .key_vld (key_vld )
- );
- ov7670_config u4(
- .clk (xclk ),
- .rst_n (rst_n ),
- .config_en (key_vld[1] ),
- .rdy (rdy ),
- .rdata (rdata ),
- .rdata_vld (rdata_vld ),
- .wdata (wdata ),
- .addr (sub_addr ),
- .wr_en (wen ),
- .rd_en (ren ),
- .cmos_en (en_capture ),
- .pwdn (pwdn )
- );
- sccb u5(
- .clk (xclk ),
- .rst_n (rst_n ),
- .ren (ren ),
- .wen (wen ),
- .sub_addr (sub_addr ),
- .rdata (rdata ),
- .rdata_vld (rdata_vld ),
- .wdata (wdata ),
- .rdy (rdy ),
- .sio_c (sio_c ),
- .sio_d_r (sio_d_r ),
- .en_sio_d_w (en_sio_d_w ),
- .sio_d_w (sio_d_w )
- );
- cmos_capture u6(
- .clk (clk_25M ),
- .rst_n (rst_n ),
- .en_capture (en_capture ),
- .vsync (vsync ),
- .href (href ),
- .din (din ),
- .dout (cmos_dout ),
- .dout_vld (cmos_dout_vld),
- .dout_sop (cmos_dout_sop),
- .dout_eop (cmos_dout_eop)
- );
-
- vga_config u11(
- .clk (clk_25M ),
- .clk_in (clk_100m ),
- .rst_n (rst_n ),
- .din (cmos_dout ),
- .din_vld (cmos_dout_vld),
- .din_sop (cmos_dout_sop),
- .din_eop (cmos_dout_eop),
- .dout (vga_data ),
- .wr_req (wr_req ),
- .rd_req (rd_req ),
- .wr_ack (wr_ack ),
- .rd_ack (rd_ack ),
- .wdata (fifo2sd_wdata),
- .sd_rdata (sd_rdata ),
- .sd_rdata_vld (sd_rdata_vld ),
- .display_area (display_area ),
- .bank (bank ),
- .addr (addr )
- );
-
-
- sdram_intf u20 (
- .clk (clk_100m ),
- .rst_n (rst_n ),
- .wr_req (wr_req ),
- .rd_req (rd_req ),
- .dq_in (dq_in ),
- .dq_out (dq_out ),
- .dq_out_en (dq_out_en ),
- .wr_ack (wr_ack ),
- .rd_ack (rd_ack ),
- .rdata (sd_rdata ),
- .rdata_vld (sd_rdata_vld ),
- .cke (cke ),
- .cs (cs ),
- .ras (ras ),
- .cas (cas ),
- .we (we ),
- .dqm (dqm ),
- .sd_addr (sd_addr ),
- .sd_bank (sd_bank ),
- .sd_clk (sd_clk ),
- .wdata (fifo2sd_wdata),
- .bank (bank ),
- .addr (addr )
-
- );
-
-
- vga_driver u12(
- .clk (clk_25M ),
- .rst_n (rst_n ),
- .din (vga_data ),
- .vga_hys (vga_hys ),
- .vga_vys (vga_vys ),
- .vga_rgb (vga_rgb ),
- .display_area(display_area )
- );
-
- endmodule
复制代码
1.2 按键检测模块设计1.2.1接口信号
下面为使用矩阵键盘时的接口信号:
下面是使用普通按键时的接口信号:
1.2.2 设计思路
在前面的案例中已经有按键检测的介绍,所以这里不在过多介绍,详细介绍请看下方链接:
1.2.3 参考代码
1. //矩阵键盘
1.3 锁相环1.3.1 接口信号PLL_1:pll_sd PLL_2:cmos_pll
1.3.2 设计思路
此模块是使用Quartus生成的PLL IP核,相关的生成步骤、功能原理等可以看明德扬论坛中关于PLL的介绍。
1.3.3 时钟网络
本工程共有5个时钟,硬件上的晶振提供的50M时钟。由于摄像头的驱动时钟为25M,因此需要由锁相环产生一个25M的时钟xclk,摄像头输出图像数据的同时,会输出一个像素时钟pclk,该时钟是和图像数据对齐的,由于摄像头属于外设,是临时插在开发板上的,因此,插得不牢、晃动、碰撞等,都有可能造成摄像头输出的时钟pclk的不稳定,因此这里将此时钟经过锁相环,产生一个同频同相的稳定时钟clk_25M,作为之后图像处理模块的工作时钟。而SDRAM的工作时钟是100M,因此需要通过锁相环产生100M的时钟。通过架构图中模块的颜色来进行判别,按键检测模块、配置模块和SCCB模块都是采用的xclk;图像采集模块、存储控制模块和VGA接口模块都是采用的clk_25M;SDRAM接口模块的工作时钟为100M。
1.4 配置模块设计
1.4.1接口信号
1.4.2配置表需要配置的寄存器信息都在配置表文件中保存,通过寄存器计数器reg_cnt进行读取,18bit位宽的信号add_wdata为保存配置信息的信号,其中add_wdata[17]表示读属性,当其为1时,表示该寄存器可读;daa_wdata[16]表示写属性,当其为1时,表示该寄存器可写;add_wdata[17:8]表示寄存器地址;add_wdata[7:0]表示要往寄存器中写入的值。
寄存器地址表示的寄存器以及数据每一位代表的意思,可以参考ov7670_中文版数据手册
举例说明:add_wdata={2’b11,16’h1e31},其中2’b11表示该寄存器可读可写,寄存器地址为8’h1e,查数据手册可知,寄存器名为MVFP,表示水平镜像/竖直翻转使能。数据的位[7:8]作为保留位,没有作用;位[5]表示水平镜像使能,0为正常,1为镜像;位[4]表示竖直翻转使能,0为正常,1为翻转;位[3]保留;位[2]表示消除黑太阳使能;位[1:0]保留。写数据为8’h31,表示开启水平镜像和竖直翻转。 注意,该文件不可综合,不要添加到编译软件中综合,否则会报错,只需要将此文件放到工程目录下即可。
1.4.3参考代码- parameter REG_NUM = 164;
- always@(*) begin
- case(reg_cnt)
- 0 : add_wdata = {2'b11,16'h1204};
- 1 : add_wdata = {2'b11,16'h40d0};
- 2 : add_wdata = {2'b11,16'h3a04};
- 3 : add_wdata = {2'b11,16'h3dc8};
- 4 : add_wdata = {2'b11,16'h1e31};
- 5 : add_wdata = {2'b11,16'h6b00};
- 6 : add_wdata = {2'b11,16'h32b6};
- 7 : add_wdata = {2'b11,16'h1713};
- 8 : add_wdata = {2'b11,16'h1801};
- 9 : add_wdata = {2'b11,16'h1902};
- 10 : add_wdata = {2'b11,16'h1a7a};
- 11 : add_wdata = {2'b11,16'h030a};
- 12 : add_wdata = {2'b11,16'h0c00};
- 13 : add_wdata = {2'b11,16'h3e10};
- 14 : add_wdata = {2'b11,16'h7000};
- 15 : add_wdata = {2'b11,16'h7100};
- 16 : add_wdata = {2'b11,16'h7211};
- 17 : add_wdata = {2'b11,16'h7300};
- 18 : add_wdata = {2'b11,16'ha202};
- 19 : add_wdata = {2'b11,16'h1180};
- 20 : add_wdata = {2'b11,16'h7a20};
- 21 : add_wdata = {2'b11,16'h7b1c};
- 22 : add_wdata = {2'b11,16'h7c28};
- 23 : add_wdata = {2'b11,16'h7d3c};
- 24 : add_wdata = {2'b11,16'h7e55};
- 25 : add_wdata = {2'b11,16'h7f68};
- 26 : add_wdata = {2'b11,16'h8076};
- 27 : add_wdata = {2'b11,16'h8180};
- 28 : add_wdata = {2'b11,16'h8288};
- 29 : add_wdata = {2'b11,16'h838f};
- 30 : add_wdata = {2'b11,16'h8496};
- 31 : add_wdata = {2'b11,16'h85a3};
- 32 : add_wdata = {2'b11,16'h86af};
- 33 : add_wdata = {2'b11,16'h87c4};
- 34 : add_wdata = {2'b11,16'h88d7};
- 35 : add_wdata = {2'b11,16'h89e8};
- 36 : add_wdata = {2'b11,16'h13e0};
- 37 : add_wdata = {2'b11,16'h0010};
- 38 : add_wdata = {2'b11,16'h1000};
- 39 : add_wdata = {2'b11,16'h0d00};
- 40 : add_wdata = {2'b11,16'h1428};
- 41 : add_wdata = {2'b11,16'ha505};
- 42 : add_wdata = {2'b11,16'hab07};
- 43 : add_wdata = {2'b11,16'h2475};
- 44 : add_wdata = {2'b11,16'h2563};
- 45 : add_wdata = {2'b11,16'h26a5};
- 46 : add_wdata = {2'b11,16'h9f78};
- 47 : add_wdata = {2'b11,16'ha068};
- 48 : add_wdata = {2'b11,16'ha103};
- 49 : add_wdata = {2'b11,16'ha6df};
- 50 : add_wdata = {2'b11,16'ha7df};
- 51 : add_wdata = {2'b11,16'ha8f0};
- 52 : add_wdata = {2'b11,16'ha990};
- 53 : add_wdata = {2'b11,16'haa94};
- 54 : add_wdata = {2'b11,16'h13ef};
- 55 : add_wdata = {2'b11,16'h0e61};
- 56 : add_wdata = {2'b11,16'h0f4b};
- 57 : add_wdata = {2'b11,16'h1602};
- 58 : add_wdata = {2'b11,16'h2102};
- 59 : add_wdata = {2'b11,16'h2291};
- 60 : add_wdata = {2'b11,16'h2907};
- 61 : add_wdata = {2'b11,16'h330b};
- 62 : add_wdata = {2'b11,16'h350b};
- 63 : add_wdata = {2'b11,16'h371d};
- 64 : add_wdata = {2'b11,16'h3871};
- 65 : add_wdata = {2'b11,16'h392a};
- 66 : add_wdata = {2'b11,16'h3c78};
- 67 : add_wdata = {2'b11,16'h4d40};
- 68 : add_wdata = {2'b11,16'h4e20};
- 69 : add_wdata = {2'b11,16'h6900};
-
- 70 : add_wdata = {2'b11,16'h7419};
- 71 : add_wdata = {2'b11,16'h8d4f};
- 72 : add_wdata = {2'b11,16'h8e00};
- 73 : add_wdata = {2'b11,16'h8f00};
- 74 : add_wdata = {2'b11,16'h9000};
- 75 : add_wdata = {2'b11,16'h9100};
- 76 : add_wdata = {2'b11,16'h9200};
- 77 : add_wdata = {2'b11,16'h9600};
- 78 : add_wdata = {2'b11,16'h9a80};
- 79 : add_wdata = {2'b11,16'hb084};
- 80 : add_wdata = {2'b11,16'hb10c};
- 81 : add_wdata = {2'b11,16'hb20e};
- 82 : add_wdata = {2'b11,16'hb382};
- 83 : add_wdata = {2'b11,16'hb80a};
- 84 : add_wdata = {2'b11,16'h4314};
- 85 : add_wdata = {2'b11,16'h44f0};
- 86 : add_wdata = {2'b11,16'h4534};
- 87 : add_wdata = {2'b11,16'h4658};
- 88 : add_wdata = {2'b11,16'h4728};
- 89 : add_wdata = {2'b11,16'h483a};
- 90 : add_wdata = {2'b11,16'h5988};
- 91 : add_wdata = {2'b11,16'h5a88};
- 92 : add_wdata = {2'b11,16'h5b44};
- 93 : add_wdata = {2'b11,16'h5c67};
- 94 : add_wdata = {2'b11,16'h5d49};
- 95 : add_wdata = {2'b11,16'h5e0e};
- 96 : add_wdata = {2'b11,16'h6404};
- 97 : add_wdata = {2'b11,16'h6520};
- 98 : add_wdata = {2'b11,16'h6605};
- 99 : add_wdata = {2'b11,16'h9404};
- 100 : add_wdata = {2'b11,16'h9508};
- 101 : add_wdata = {2'b11,16'h6c0a};
- 102 : add_wdata = {2'b11,16'h6d55};
- 103 : add_wdata = {2'b11,16'h6e11};
- 104 : add_wdata = {2'b11,16'h6f9f};
- 105 : add_wdata = {2'b11,16'h6a40};
- 106 : add_wdata = {2'b11,16'h0140};
- 107 : add_wdata = {2'b11,16'h0240};
- 108 : add_wdata = {2'b11,16'h13e7};
- 109 : add_wdata = {2'b11,16'h1500};
-
- 110 : add_wdata = {2'b11,16'h4f80};
- 111 : add_wdata = {2'b11,16'h5080};
- 112 : add_wdata = {2'b11,16'h5100};
- 113 : add_wdata = {2'b11,16'h5222};
- 114 : add_wdata = {2'b11,16'h535e};
- 115 : add_wdata = {2'b11,16'h5480};
- 116 : add_wdata = {2'b11,16'h589e};
-
- 117 : add_wdata = {2'b11,16'h4108};
- 118 : add_wdata = {2'b11,16'h3f00};
- 119 : add_wdata = {2'b11,16'h7505};
- 120 : add_wdata = {2'b11,16'h76e1};
- 121 : add_wdata = {2'b11,16'h4c00};
- 122 : add_wdata = {2'b11,16'h7701};
-
- 123 : add_wdata = {2'b11,16'h4b09};
- 124 : add_wdata = {2'b11,16'hc9F0};
- 125 : add_wdata = {2'b11,16'h4138};
- 126 : add_wdata = {2'b11,16'h5640};
-
-
- 127 : add_wdata = {2'b11,16'h3411};
- 128 : add_wdata = {2'b11,16'h3b02};
- 129 : add_wdata = {2'b11,16'ha489};
- 130 : add_wdata = {2'b11,16'h9600};
- 131 : add_wdata = {2'b11,16'h9730};
- 132 : add_wdata = {2'b11,16'h9820};
- 133 : add_wdata = {2'b11,16'h9930};
- 134 : add_wdata = {2'b11,16'h9a84};
- 135 : add_wdata = {2'b11,16'h9b29};
- 136 : add_wdata = {2'b11,16'h9c03};
- 137 : add_wdata = {2'b11,16'h9d4c};
- 138 : add_wdata = {2'b11,16'h9e3f};
- 139 : add_wdata = {2'b11,16'h7804};
-
-
- 140 :add_wdata = {2'b11,16'h7901};
- 141 :add_wdata = {2'b11,16'hc8f0};
- 142 :add_wdata = {2'b11,16'h790f};
- 143 :add_wdata = {2'b11,16'hc800};
- 144 :add_wdata = {2'b11,16'h7910};
- 145 :add_wdata = {2'b11,16'hc87e};
- 146 :add_wdata = {2'b11,16'h790a};
- 147 :add_wdata = {2'b11,16'hc880};
- 148 :add_wdata = {2'b11,16'h790b};
- 149 :add_wdata = {2'b11,16'hc801};
- 150 :add_wdata = {2'b11,16'h790c};
- 151 :add_wdata = {2'b11,16'hc80f};
- 152 :add_wdata = {2'b11,16'h790d};
- 153 :add_wdata = {2'b11,16'hc820};
- 154 :add_wdata = {2'b11,16'h7909};
- 155 :add_wdata = {2'b11,16'hc880};
- 156 :add_wdata = {2'b11,16'h7902};
- 157 :add_wdata = {2'b11,16'hc8c0};
- 158 :add_wdata = {2'b11,16'h7903};
- 159 :add_wdata = {2'b11,16'hc840};
- 160 :add_wdata = {2'b11,16'h7905};
- 161 :add_wdata = {2'b11,16'hc830};
- 162 :add_wdata = {2'b11,16'h7926};
-
- 163 : add_wdata = {2'b11,16'h0903};
- 164 : add_wdata = {2'b11,16'h3b42};
-
- default : add_wdata = 0;
- endcase
- end
复制代码
1.4.4配置模块设计思路
该模块主要功能是将配置表中的数据读出,并对读出的数据进行解析,识别读写属性,根据读写属性,产生读写使能;识别寄存器地址和写数据,并将其分离。 由于配置表文件不可综合,因此无法通过例化获取,可以通过以下语句进行调用:
该模块产生的读使能、写使能、寄存器地址和写数据,都会送给下游SCCB接口模块,而SCCB接口属于单总线接口。不能同时读写,因此,该模块在对配置表进行读取的时候,需要读取两次,先进行写,然后在进行读,该操作由计数器控制,如下图所示:
读写计数器rw_cnt:该计数器表示读写阶段,数第一个表示处于写数据阶段,数第二个表示处于读数据阶段。加一条件为(flag && rdy),表示按下配置开始按键,并且下游模块准备好的时候就加一;结束条件为数两个,只需要区分读阶段和写阶段,因此数两个即可,数完就清零。 待配置的寄存器共有164个,因此需要一个寄存器计数器来对它进行计数,如下图所示:
寄存器计数器reg_cnt:该计数器主要对需要配置的寄存器个数进行计数。加一条件问end_rw_cnt,由于一个寄存器需要读写两个阶段,所以为读写计数器的结束条件;结束条件为数164个,需要配置的寄存器共164个,数完就清零。 下面介绍一下该模块的其他信号设计思路:
配置指示信号flag:该信号初始状态为0,表示还没有开始配置;当接收到开始配置使能信号config_en的时候,变为1,表示开始进入配置状态;当配置完成之后,也就是寄存器计数器数完了,便退出配置状态,因此该信号从高变低的条件为end_reg_cnt。 配置完成指示信号cmos_en:当该信号为高电平时,表示摄像头的寄存器已经经过最少一次配置了。初始状态为0,表示没有配置过;变1的条件为end_reg_cnt,也就是寄存器全部配置完一次之后,便置为高电平。
寄存器写数据wdata:初始状态为0,之后,直接取从配置表读出的add_wdata的低8位。
寄存器地址addr:初始状态位0,之后,直接取add_wdata的第8到第15位即可。
写使能wr_en:一个寄存器需要读和写两个阶段,先进行写,然后在读,add_wdata的第16位表示写属性。所以该信号从0变1的条件为(add_rw_cnt && rw_cnt==0 && add_wdata[16])。
读使能rd_en:写之后,在进行读,add_wdata的第17位指示读属性,因此读使能拉高的条件位(add_rw_cnt && rw_cnt==1 && add_wdata[17])。
1.4.5参考代码
1.5 SCCB接口模块设计
1.5.1接口信号
1.5.2设计思路
本工程使用的SCCB接口协议,只有两根线,时钟线sio_c和数据线sio_d,因此如果要使用这个接口,我们需要时钟线的频率要求、读数据的时序和写数据的时序。知道了需求,那么在查看数据手册的时候便容易很多。 下图为三线传输开始的标志,本工程中使用的是两线传输,因此可以将下图中的SCCB_E信号忽略,然后可以看出,在sio_c为高的时候,sio_d拉低,表示数据传输的开始,但是在图中看不出sio_d拉低到sio_c拉低的间隔时间是多少。
下图是三线传输停止的标志,本工程使用的是两线传输,因此还是将SCCB_E信号忽略即可,由此得出,在sio_c为高的时候,将sio_d拉高,表示传输的结束,但是下图仍然无法看出从sio_c拉高到sio_d拉高间隔的时间
下图为OV7670数据手册中关于SCCB接口时序的描述,开始条件保持时间为tHD:STA,开始条件建立时间为tSU:STO。
下表为ov7670摄像头数据手册中关于SCCB时序的一些时间参数,查表得到,tHD:STA和tSU:STO最小值为600ns,因此对于这两个参数们可以根据我们使用的方便进行设置。而SCCB的时钟频率,最大值为400K,也可以根据我们的方便进行设置,只要不超过400K就可以了。
经过上面的时序图以及表格的参考,我们基本上可以将sio_c这个信号设计出来了,下面我们在关注sio_d,该信号我们主要关注读数据时序和写数据时序,下图为3相写传输周期,也就是写时序共有三段组成,第一段ID Address为读写属性,第二段Sub-address为寄存器地址,第三段Write Date为写数据,每段都是由9bit数据组成,数据从高到低排列,最低位x为不关心为,下面再使用的时候统一设置为1即可。
下图为读时序,读的时序比较特别,由两相写传输周期和两相读传输周期组成,首先执行写传输周期,该周期会发送读写属性与寄存器地址,目的是为了先识别要读取数据的寄存器,之后执行读传输周期,该周期会发送读写属性,之后开始接收返回的数据即可,8bit 的数据接收完成之后,FPGA需要驱动NA 位为1 ,之后读周期结束。
下面开始本模块的设计,首先确定sio_c的频率,这里我将它设置为120个系统时钟周期为1个sio_c的时钟周期,时钟频率约等于208K。为了方便计算,将开始条件保持时间和开始条件建立时间都设置为1个sio_c周期。每个周期发送1bit数据,因此还需要一个计数器来计数每阶段共有多少bit的数据。而读时序的话,由两段组成,因此还需要一个计数器来数写数据时序和读数据时序需要的段数,最终的到下面计数器的架构图:
下面开始对本模块各个信号进行设计: Sio_c周期计数器count_sck:该计数器表示sio_c一个周期需要的时钟周期个数。加一条件为(flag_r || flag_w),表示接收到读使能或者写使能之后便开始计数;结束条件为数120个,跟工程设置的SCCB时钟频率为208Khz,一个周期约为4800ns,因此需要数120个,数完就清零。 位计数器count_bit:该计数器表示每个阶段需要传输的数据位数。加一条件为end_count_sck,每传输一位需要一个sio_c的周期;结束条件为数bit_num个,阶段不同,传输的数据数也不同,写数据阶段bit_num=30,读数据阶段bit_num=21。
阶段计数器count_duan:该计数器表示传输数据所需要的阶段。加一条件为end_count_bit,每个阶段的数据传输完成之后,表示当前阶段结束;结束条件为数duan_num个,写数据阶段,duan_num=1,读数据阶段duan_num=2。
SCCB时钟信号sio_c:初始状态为高电平,由于频率为208K,因此高电平和低电平各持续60个时钟周期,又由于开始条件保持时间和开始条件建立时间的存在,sio_c在每个阶段的第一个数据和最后一个数据部分不能变为低电平,所以变低的条件为(count_bit>=0 && count_bit < (bit_num-2) &&add_count_sck && count_sck == SIO_C-1);从低变高的条件为(count_bit>=1 && count_bit < bit_num && add_count_sck&& count_sck == SIO_C/2-1)。
读状态指示信号flag_r:初始状态为0,表示不处于读状态,当接收到读使能ren有效的时候,变为高电平,表示进入读状态;当段计数器count_duan数完之后,该信号变为低电平,表示读阶段结束。
写状态指示信号flag_w:初始状态为0,表示不处于写状态,当接收到写使能wen有效的时候,变为高电平,表示进入写状态;当段计数器count_duan数完之后,该信号变为低电平,表示写阶段结束。
要输出的数据out_data:读数据阶段out_data= {1'h0,rd_com,1'h1,addr_ff,1'h1,1'h0,1'h1,9'h0},其中从左往右依次是起始位0、读写属性rd_com、1bit的x、寄存器地址、1bit的x、1个间隔为0、结束位1和9bit的0。间隔位0的存在是由于数据传输结束是一个拉高的操作,因此需要先将数据线拉低,在发送结束位。后面9bit0仅仅是为了将out_data的位宽占满,如果不补的话,会在高位自动补零,这样就不对了。读写属性rd_com会因为读写的不同而变化,写的时候为8’h42,读的时候为 8’ h43;写数据阶段out_data={1'h0,8'h42,1'h1,addr_ff,1'h1,wdata_ff,1'h1,1'h0,1'h1},其中从左往右依次是起始位0、写属性8’h42、1bit的x、寄存器地址、1bit的x、写数据wdata_ff、1个间隔为0、结束位1。
三态门使能en_sio_d_w:当该信号为0,此时只允许从机往FPGA发送数据。当该信号为1时,只允许FPGA往从机发送数据。因此该信号初始状态为0,当需要进行写数据的时候,将该信号拉高即可,拉高的条件为(ren || wen)和(flag_r &&count_duan==1 && count_bit==18 && add_count_sck &&count_sck==1-1),这两个条件分别是在接收到读使能或者写使能的时候,都需要先往从机里面写入数据,再读数据的第二阶段,将数据读出之后,还要发送间隔位、停止位等。再读数据得时候,将三态门使能信号拉低,也就是在读的第二阶段,所以拉低的条件为(flag_r && count_duan==1 && count_bit==10 &&add_count_sck && count_sck==1-1)。
写数据线sio_d_w:在写数据区域,将out_data的值按照顺序赋值即可,注意在赋值的时候,要在SCCB时钟一个时钟周期开始的时候进行,因此条件为(count_bit>= 0 && count_bit < bit_num && add_count_sck &&count_sck == SIO_C/4-1)。
读数据rdata:读出数据的时间为读时序的第二阶段,注意在取值的时候,需要SCCB时钟的高电平的中间时刻进行(flag_r&& count_duan==1 && count_bit>=10 && count_bit <18 && add_count_sck && count_sck==SIO_C/4*3-1)。
读数据有效指示信号rdata_vld:初始状态为0,表示此时读数据信号无效。当读数据阶段全部完成之后,读数据有效,将该信号拉高一个时钟周期,因此条件为(flag_r && end_count_duan)。
准备好接收指示信号rdy:该信号为0时,表示当前模块处于发送或者接收状态,不能接收上游模块发送的数据。该信号为1时,表示当前看模块处于空闲状态,可以接受上游模块发送的数据。因此该信号为1的条件是(wen || ren || flag_r|| flag_w)。注意rdy信号需要使用组合逻辑产生。
1.5.3参考代码- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- count_sck <= 0;
- end
- else if(add_count_sck)begin
- if(end_count_sck)begin
- count_sck <= 0;
- end
- else begin
- count_sck <= count_sck + 1;
- end
- end
- end
- assign add_count_sck = flag_r || flag_w;
- assign end_count_sck = add_count_sck && count_sck == SIO_C-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- count_bit <= 0;
- end
- else if(add_count_bit)begin
- if(end_count_bit)begin
- count_bit <= 0;
- end
- else begin
- count_bit <= count_bit + 1;
- end
- end
- end
- assign add_count_bit = end_count_sck;
- assign end_count_bit = add_count_bit && count_bit == bit_num+2-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- count_duan <= 0;
- end
- else if(add_count_duan)begin
- if(end_count_duan)begin
- count_duan <= 0;
- end
- else begin
- count_duan <= count_duan + 1;
- end
- end
- end
- assign add_count_duan = end_count_bit;
- assign end_count_duan = add_count_duan && count_duan == duan_num-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- flag_r <= 0;
- end
- else if(ren)begin
- flag_r <= 1;
- end
- else if(end_count_duan)begin
- flag_r <= 0;
- end
- end
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- flag_w <= 0;
- end
- else if(wen)begin
- flag_w <= 1;
- end
- else if(end_count_duan)begin
- flag_w <= 0;
- end
- end
- always @(*)begin
- if(flag_r)begin
- bit_num = 21;
- duan_num = 2;
- end
- else if(flag_w)begin
- bit_num = 30;
- duan_num = 1;
- end
- else begin
- bit_num = 1;
- duan_num = 1;
- end
- end
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- sio_c <= 1;
- end
- else if(sio_c_h2l)begin
- sio_c <= 0;
- end
- else if(sio_c_l2h)begin
- sio_c <= 1;
- end
- end
- assign sio_c_h2l = count_bit >= 0 && count_bit < (bit_num-2) && add_count_sck && count_sck == SIO_C-1;
- assign sio_c_l2h = count_bit >= 1 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/2-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- wdata_ff <= 0;
- addr_ff <= 0;
- end
- else if(wen ||ren) begin
- wdata_ff <= wdata ;
- addr_ff <= sub_addr ;
- end
- end
- always @ (*)begin
- if(flag_r)begin
- out_data = {1'h0,rd_com,1'h1,addr_ff,1'h1,1'h0,1'h1,9'h0};
- end
- else if(flag_w)begin
- out_data = {1'h0,8'h42,1'h1,addr_ff,1'h1,wdata_ff,1'h1,1'h0,1'h1};
- end
- else begin
- out_data = 0;
- end
- end
- assign rd_com = (flag_r && count_duan == 0)? 8'h42 : 8'h43;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- en_sio_d_w <= 0;
- end
- else if(ren || wen)begin
- en_sio_d_w <= 1;
- end
- else if(end_count_duan)begin
- en_sio_d_w <= 0;
- end
- else if(en_sio_d_w_h2l)begin
- en_sio_d_w <= 0;
- end
- else if(en_sio_d_w_l2h)begin
- en_sio_d_w <= 1;
- end
- end
- assign en_sio_d_w_h2l = flag_r && count_duan == 1 && count_bit == 10 && add_count_sck && count_sck == 1-1;
- assign en_sio_d_w_l2h = flag_r && count_duan == 1 && count_bit == 18 && add_count_sck && count_sck == 1-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- sio_d_w <= 1;
- end
- else if(out_data_time)begin
- sio_d_w <= out_data[30-count_bit-1];
- end
- end
- assign out_data_time = count_bit >= 0 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/4-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- rdata <= 0;
- end
- else if(rdata_time)begin
- rdata[17-count_bit] <= sio_d_r;
- end
- end
- assign rdata_time = flag_r && count_duan==1 && count_bit>=10 && count_bit<18 && add_count_sck && count_sck==SIO_C/4*3-1;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- rdata_vld <= 0;
- end
- else if(flag_r && end_count_duan)begin
- rdata_vld <= 1;
- end
- else begin
- rdata_vld <= 0;
- end
- end
- always @(*)begin
- if(ren || wen || flag_r || flag_w)begin
- rdy = 0;
- end
- else begin
- rdy = 1;
- end
- end
- endmodule
复制代码
1.6 图像采集模块设计1.6.1接口信号
信号名 | | | | | | | | | | | | | | | 摄像头配置完成指示信号,当其为1时,表示摄像头完成了配置。 | | | | 摄像头帧同步信号,该信号拉高几个时钟周期,表示一帧图像传输的开始。 | | | | 摄像头行同步信号,该信号为高电平时,摄像头输出的数据有效。 | | | | | | | | | | | | | | | | | | | | |
1.6.2设计思路下面时序图是从OV7670摄像头数据手册中得到的,可以看出来摄像头输出的数据跟随像素时钟时钟,一个时钟周期输出一个字节的数据,而且数据只在行同步信号href为高时有效,我们可以将行同步信号href当作是摄像头输出数据有效指示信号来使用。
从下面时序图中可以看出,帧同步信号拉高三个时钟周期,表示将要开始一帧图像的传输,在vsync拉高之后,到图像开始传输,还有一段时间,这段时间不用管是多少,可以看行同步信号href是否为高就可以了。
我们配置摄像头输出格式为RGB565,因此一个像素是由两个字节组成,而输出数据接口的位宽是一个字节,因此摄像头两个时钟周期才能输出一个完整的图像数据,由下图可以看出先输出的是高8位数据,然后再输出低8位数据。
从上面几个时序图中,已经知道了摄像头输出图像的时刻以及方式,便可以开始对数据进行接收,这里采用行、场两个计数的架构,如下图所示:
行计数器cnt_x:该计数器用来表示摄像头输出图像一行数据的个数。加一条件为din_vld,表示输入的数据有效,便开始计数。结束条件为数1280个,一行有640个像素数据,每个像素都是两个字节,摄像头每次输出一个字节,因此共输出1280个。 列计数器cnt_y:该计数器用来计数一帧图像的行数。加一条件为end_cnt_x,表示每数完1行就加一。结束条件为数480个,一帧图像就是480行,数完就清零。
下面再看一下该模块其他信号的设计:
采集状态指示信号flag_capture:初始状态为0,表示不处于采集状态,当检测到帧同步信号的上升沿和配置完成指示信号有效的时候,便进入采集状态,因此该信号变为1的条件为(flag_capture==0&& vsync_12h && en_capture)。当一帧图像传输完成之后,就退出采集状态,因此该信号变0的条件为end_cnt_y。
输入数据有效指示信号din_vld:当进入到采集状态,并且检测到行同步信号为1的时候,输入数据就是有效的,该信号由组合逻辑产生,即flag_capture&& href。
输出图像数据dout:初始状态为0,当输入数据有效时,按照数据的排列顺序,进行串并转换。
输出图像数据有效指示信号dout_vld:一个像素数据为2个字节,摄像头每个时钟输出一个字节,因此是输出两次有效一次。初始状态为0,表示数据无效,当行计数器cnt_x的第0位为1的时候,该信号变为1,表示输出图像数据有效。
第一个有效数据指示信号dout_sop:初始状态为0。当行计数器cnt_x等于1,并且列计数器等于0的时候,表示输出第一个有效数据,该信号拉高,其他时候为0。
最后一个有效数据指示信号dout_eop:当行计数器cnt_x等于1280-1,并且列计数器等于480-1的时候,表示输出最后一个有效数据,该信号为高,其他时候为低电平。
1.6.3参考代码
1.7 存储控制模块设计
1.7.1接口信号1.7.2设计思路
本工程的主体时钟为25M,显示器的分辨率为640*480,相当于一个时钟周期显示一个像素。而摄像头的工作时钟也是25M,但是数据位宽为8bit,相当于两个时钟输出一个像素,如果直接将采集模块接收到的数据送给显示器显示,那么一个像素点会被显示两次,这肯定是不对的,因此需要先将摄像头输出的数据缓存起来,当存够一帧图像之后,再送给显示器进行显示,显示器本该1秒显示60帧图像的,降低到了30帧,但是显示的图像是正确而且连续的。
所以本模块的作用就是存储控制,再SDRAM中划分两个区域,通过乒乓操作的方式进行存储的控制,该操作的主要的难点在于摄像头是不断输出数据的,而显示器也需要不断的进行显示才可以,两边都不能等待,但是SDRAM的读写都是使用的同一组数据总线,这也就造成了它注定无法进行同时读写。但是对于本工程来说,由于工程主体时钟25M和SDRAM工作时钟100M相比有较大差距,因此可以使用一种“伪同时读写”的操作,达到“同时读写”的效果。此方法的介绍请在明德扬论坛中搜索“需要同时读写SDRAM的解决办法”。
本模块内部包含两个FIFO(fifo原理以及使用,可以在明德扬论坛搜索学习),fifo_16bwrite和fifo_16bread,前者为写FIFO,后者为读FIFO,两个FIFO的作用主要是跨时钟域处理和数据缓存。下面介绍一下该模块中各个信号的设计。
写FIFO写使能wfifo_wrreq:输入图像数据从第一个有效数据开始一直到最后一个有效数据期间,写使能信号一直为高电平,即(din_vld && (din_sop || get_data_flag))。
数据写入指示信号get_data_flag:初始状态为0,表示没有数据需要写入;拉高的条件为(din_vld&& din_sop),表示接收到第一个有效数据时,便开始写入;有高变低的条件为(din_vld && din_eop),表示当检测到最后一个有效数据之后,将该信号拉低,停止写入。 写FIFO数据读出指示信号rd_wfifo_flag:初始状态为0,表示不需要读出数据;变高的条件为wfifo_rdusedw>=512,表示当写FIFO内部数据量达到512个的时候,便准备将数据读出 由于一帧图像的像素点共640*480=307200个,而SDRAM一行有512个存储空间,要将图像存储进SDRAM中,共需要600行。因此提出两个计数器的架构:
写数据行计数器cnt_rd_wfifo:该信号表示SDRAM中每一行中要写入的数据量。加一条件为(rd_wfifo_flag&& ((cnt_rd_wfifo==0 && wr_ack) || (cnt_rd_wfifo!=0)),表示在写数据准备读出状态下,如果检测到写响应信号有效,便开始计数;结束条件为数512个,SDRAM每行内存容量为512个数据,数完就清零。
写SDRAM列地址计数器cnt_waddr:该计数器用于计数SDRAM的列地址。加一条件为end_cnt_rd_wfifo,表示每行写完就加一;结束条件为数600个,总需要600行。
读FIFO数据写入指示信号wr_rfifo_flag:初始状态为0,表示不需要写入数据;变高的条件为rfifo_rdusedw<550,表示当读FIFO内部数据量小于550个的时候,便请求写入数据。
读使能rd_req:再读FIFO不处于请求数据写入状态的时候,检测到读FIFO内部的数据量少于550个的时候,读使能信号有效,直到接收到读响应。
写使能wr_req:在写FIFO不处于请求输出的状态的时候,检测到写FIFO内部的数据量至少有512个的时候,写使能信号有效,直到接收到写响应。
写FIFO读使能wfifo_rdreq:当写FIFO内部不为空,并且行计数器开始计数的时候,该信号有效。
读数据行地址计数器cnt_wr_rfifo:该计数器表示读SDRAM时候的行地址;加一条件为sd_rdata_vld,表示SDRAM读出数据有效就加一;结束条件为数512个。
读数据列地址计数器cnt_raddr:该计数器表示读SDRAM时候的列地址;加一条件为end_cnt_wr_rfifo,表示读出一行数据,就加一;结束条件为数600个。
写Bank地址waddr_bank:初始状态为2’b0,当一帧图像的数据全部写进去之后,写Bank地址取反,变为2’b11。
读Bank地址raddr_bank:初始状态为2’b11,当一帧图像全部读出来之后,并且与写Bank地址相等,则取反,变为2’b00。
Bank切换计数器cnt_bank:由于读写Bank的切换需要时间,因此需要此计数器来决定间隔的时间,当开始切换Bank的时候开始计数,经过调试,共间隔8个时钟周期。
读FIFO的读使能:当进入到有效显示区域的时候,便将FIFO内部数据读出。
读FIFO的写使能:当SDRAM输出有效数据的时候,写使能便拉高,将数据写入。
1.7.3参考代码
- always @(*)begin
- if(rd_wfifo_flag)
- bank=waddr_bank;
- else
- bank=raddr_bank;
- end
- always @(*)begin
- if(rd_wfifo_flag)
- addr=waddr;
- else
- addr=raddr;
- end
-
-
- fifo_16bwrite fifo_16bwrite_inst (
- .aclr ( ~rst_n ),
- .data (din ),
- .rdclk (clk_in ),
- .rdreq (wfifo_rdreq ),
- .wrclk (clk ),
- .wrreq (wfifo_wrreq ),
- .q (wdata ),
- .rdempty (wfifo_rdempty ),
- .rdusedw (wfifo_rdusedw )
- );
- fifo_16bread fifo_16bread_inst (
- .aclr ( ~rst_n ) ,
- .data (sd_rdata ),
- .rdclk (clk ),
- .rdreq (display_area ),
- .wrclk (clk_in ),
- .wrreq (sd_rdata_vld ),
- .q (dout ),
- .rdempty (rfifo_rdempty ),
- .rdusedw (rfifo_rdusedw )
- );
-
- assign wfifo_wrreq= din_vld &&(din_sop || get_data_flag);
- assign waddr = cnt_waddr ;
- assign wbank = waddr_bank ;
-
- always @ (posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- get_data_flag <= 0;
- end
- else if(din_vld && din_sop)begin
- get_data_flag <= 1;
- end
- else if(din_vld && din_eop) begin
- get_data_flag <= 0;
- end
- end
-
- assign rd_wfifo_start = rd_wfifo_flag==0&&wfifo_rdusedw>=512;
- always @ (posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- rd_wfifo_flag <= 0;
- end
- else if(wfifo_rdusedw>=512)begin
- rd_wfifo_flag <= 1;
- end
- else if(end_cnt_rd_wfifo) begin
- rd_wfifo_flag <= 0;
- end
- end
- always @ (posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- wr_req <= 0;
- end
- else if(rd_wfifo_flag==0&&wfifo_rdusedw>=512)begin
- wr_req <= 1 ;
- end
- else if(wr_ack) begin
- wr_req <= 0;
- end
- end
-
- always @(posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- cnt_rd_wfifo <= 0;
- end
- else if(add_cnt_rd_wfifo)begin
- if(end_cnt_rd_wfifo)
- cnt_rd_wfifo <= 0;
- else
- cnt_rd_wfifo <= cnt_rd_wfifo + 1;
- end
- end
-
- assign add_cnt_rd_wfifo = rd_wfifo_flag&&((cnt_rd_wfifo==0&&wr_ack)||(cnt_rd_wfifo!=0));
- assign end_cnt_rd_wfifo = add_cnt_rd_wfifo && cnt_rd_wfifo== 512-1;
- always @(*)begin
- if(add_cnt_rd_wfifo&&wfifo_rdempty==0)
- wfifo_rdreq=1;
- else
- wfifo_rdreq=0;
- end
- always @(posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- cnt_waddr <= 0;
- end
- else if(add_cnt_waddr)begin
- if(end_cnt_waddr)
- cnt_waddr <= 0;
- else
- cnt_waddr <= cnt_waddr + 1;
- end
- end
- assign add_cnt_waddr = end_cnt_rd_wfifo;
- assign end_cnt_waddr = add_cnt_waddr && cnt_waddr==600-1 ;
- assign raddr= cnt_raddr;
- assign rbank= raddr_bank;
-
- assign wr_rfifo_star= wr_rfifo_flag==0&&rfifo_rdusedw<550;
- always @(posedge clk_in or negedge rst_n)begin
- if(rst_n==1'b0)begin
- wr_rfifo_flag <= 0;
- end
- else if(wr_rfifo_star)begin
- wr_rfifo_flag <= 1;
- end
- else if(end_cnt_wr_rfifo)begin
- wr_rfifo_flag <= 0;
- end
- end
- always @(posedge clk_in or negedge rst_n)begin
- if(rst_n==1'b0)begin
- rd_req <= 0;
- end
- else if(wr_rfifo_star)begin
- rd_req <= 1;
- end
- else if(rd_ack)begin
- rd_req <= 0;
- end
- end
- always @(posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- cnt_wr_rfifo <= 0;
- end
- else if(add_cnt_wr_rfifo)begin
- if(end_cnt_wr_rfifo)
- cnt_wr_rfifo <= 0;
- else
- cnt_wr_rfifo <= cnt_wr_rfifo + 1;
- end
- end
- assign add_cnt_wr_rfifo = sd_rdata_vld ;
- assign end_cnt_wr_rfifo = add_cnt_wr_rfifo && cnt_wr_rfifo== 512-1;
-
-
- always @(posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- cnt_raddr <= 0;
- end
- else if(add_cnt_raddr)begin
- if(end_cnt_raddr)
- cnt_raddr <= 0;
- else
- cnt_raddr <= cnt_raddr + 1;
- end
- end
- assign add_cnt_raddr = end_cnt_wr_rfifo;
- assign end_cnt_raddr = add_cnt_raddr && cnt_raddr==600-1 ;
-
- always @(posedge clk_in or negedge rst_n)begin
- if(rst_n==1'b0)begin
- waddr_bank <= 2'b00;
- end
- else if(end_cnt_waddr)begin
- waddr_bank <= ~waddr_bank;
- end
- end
- assign change_bank = end_cnt_raddr&&raddr_bank==waddr_bank;
- always @(posedge clk_in or negedge rst_n)begin
- if(rst_n==1'b0)begin
- raddr_bank <= 2'b11;
- end
- else if(end_cnt_raddr&&raddr_bank==waddr_bank)begin
- raddr_bank <= ~raddr_bank;
- end
- end
- always @(posedge clk_in or negedge rst_n)begin
- if(rst_n==1'b0)begin
- change_bank_instr <= 0 ;
- end
- else if(change_bank)begin
- change_bank_instr<=1;
- end
- else if(end_cnt_bank)begin
- change_bank_instr <= 0 ;
- end
- end
- always @(posedge clk_in or negedge rst_n)begin
- if(!rst_n)begin
- cnt_bank <= 0;
- end
- else if(add_cnt_bank)begin
- if(end_cnt_bank)
- cnt_bank <= 0;
- else
- cnt_bank <= cnt_bank + 1;
- end
- end
- assign add_cnt_bank = change_bank_instr;
- assign end_cnt_bank = add_cnt_bank && cnt_bank==8-1 ;
复制代码
1.8 SDRAM接口模块设计
本模块的设计跟上一个案例《SDRAM读写控制器》基本一样,这里就不再做介绍,想要了解的可以去明德扬论坛搜素相关文章即可。
1.9 VGA接口模块设计
本模块的设计可以参考明德扬书籍《FPGA至简设计原理与应用》中关于VGA部分的案例,也可以在明德扬论坛中搜索书籍名称。
1.10 效果和总结该工程无论在哪一个板子上都是使用的显示器观察现象,因此我们只看一个就可以了
感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章:
1.11 公司简介明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。
[设计教程下载]
至简设计系列案例_OV7670摄像头显示.pdf
(1.97 MB, 下载次数: 1481)
|