FPGA综合项目——边缘检测系统

FPGA综合项目——边缘检测系统

目录

  • 0.此篇总结
  • 1.系统功能
  • 2.模块划分
  • 3.PLL
  • 4.SCCB模块
  • 5.摄像头配置模块
  • 6.采集模块
  • 7.灰度模块
  • 8.高斯滤波模块
  • 9.二值模块
  • 10.边缘检测模块
  • 11.存储模块
  • 12.VGA模块
  • 13.顶层模块
  • 14.管脚配置及上板实验

0.此篇总结

将会学到的东西:

①PLL分频的使用,也就是PLL IP核

②sccb通信,包括原理、写时序以及读时序,类似IIC通信

③ov7670摄像头的配置,内部164个寄存器的配置,通过一个包含关系的参数文件

④彩图转灰度图的一个常用公式,FPGA中怎么处理小数的乘法除法

⑤高斯滤波的原理,和插值像素原理相似

⑥移位寄存器IP核的使用

⑦二值化,用一个阈值区分01

⑧索贝尔两算子的边缘检测算法

⑨存储的乒乓操作、ram IP核的使用

⑩VGA时序

1.系统功能

通过摄像头采集事实图像给FPGA,经过FPGA处理后将最终结果显示在VGA设备上。夏效果图如下:

FPGA综合项目——边缘检测系统_第1张图片

2.模块划分

FPGA综合项目——边缘检测系统_第2张图片
最后的模块RTL图:

FPGA综合项目——边缘检测系统_第3张图片

3.PLL

时钟是最基本的信号,我们要考虑到各个模块或者说外设正常工作所需要的时钟,我们的三个外设中,摄像头和VGA的时钟为25MHz,而博主用的FPGA时钟为50MHz,所以要用PLL分出25M的时钟。

			module pll_ip
		 (
			inclk0,  //FPGA的50M时钟输入
			c0,		//25M分频,用于摄像头和VGA
		);

4.SCCB模块

SCCB模块用于FPGA和摄像头的通讯,FPGA要先给摄像头做好配置,就要通过SCCB通讯来实现。该模块的作用就是:

			当配置模块给出写命令时,产生写时序;
			当配置模块给出读命令时,产生读时序,并将读到的数据返回给采集模块;

FPGA综合项目——边缘检测系统_第4张图片
注意读入sio_d_r和读出rdata,模块左端为FPGA,右端为摄像头设备ov7670

先来看看SCCB的时序:
在这里插入图片描述
在sio_c为1,sio_d拉低时,数据传输开始
在sio_c为1,sio_d拉高时,数据传输结束
数据转换要在sio_c为0时完成

再来看看写时序:
在这里插入图片描述
起始位0,表示开始,然后写入‘写地址’、‘x’、‘寄存器地址’、‘x’、写入的数据、‘x’、最后拉高表示传输结束,x全部取1就行,当写入为8位时,共30位数据。

再来看看读时序:
FPGA综合项目——边缘检测系统_第5张图片

和写时序不一样的,读时序分为两个阶段,起始位、读地址、x、寄存器地址、x、终止位。这是第一阶段; 起始位、读地址、x、读数据、x、终止位。这是第二阶段,若读数据8位,则每个阶段都是21位数据。

代码如下:

		module sccb(
		    clk       , //输入,25M时钟
		    rst_n     , //输入,复位
		    ren       , //输入,读使能,由配置模块给出
		    wen       , //输入,写使能,由配置模块给出
		    sub_addr  , //输入,寄存器地址,由配置模块给出
		    rdata     , //输出,读到的数据
		    rdata_vld , //输出,读到数据有效
		    wdata     , //输入,要写入的数据
		    rdy       , //输出,准备信号
		    sio_c     , //输出,SCCB的传输时钟
		    sio_d_r   , //输入,读到的数据
		    en_sio_d_w, //输出,写数据传输使能
		    sio_d_w     //输出,写数据输出      
		);
		
		    //参数定义
		    parameter      SIO_C  = 120 ;  //配置4.8us的sccb时钟 sio_c
		
		    //输入信号定义
		    input               clk      ;//25m
		    input               rst_n    ;
		    input               ren      ;
		    input               wen      ;
		    input [7:0]         sub_addr ;
		    input [7:0]         wdata    ;
		
		    //输出信号定义
		    output[7:0]         rdata    ;
		    output              rdata_vld;
		    output              sio_c    ;//208kHz 
		    output              rdy      ;
		
		    input               sio_d_r   ;
		    output              en_sio_d_w;
		    output              sio_d_w   ;
		
		    reg                 en_sio_d_w;
		    reg                 sio_d_w   ;
		
		    //输出信号reg定义
		    reg [7:0]           rdata    ;
		    reg                 rdata_vld;
		    reg                 sio_c    ;
		    reg                 rdy      ;
		
		    //中间信号定义
		    reg  [7:0]          count_sck     ;
		    reg  [4:0]          count_bit     ;
		    reg  [1:0]          count_duan    ;
		    reg                 flag_r        ;
		    reg                 flag_w        ;
		    reg  [4:0]          bit_num       ;
		    reg  [1:0]          duan_num      ;
		    reg  [29:0]         out_data      ;
		
		    wire                add_count_sck ;
		    wire                end_count_sck ;
		    wire                add_count_bit ;
		    wire                end_count_bit ;
		    wire                add_count_duan;
		    wire                end_count_duan;
		    wire                sio_c_h2l     ;
		    wire                sio_c_l2h     ;
		    wire                en_sio_d_w_h2l;
		    wire                en_sio_d_w_l2h;
		    wire                out_data_time ;
		    wire                rdata_time    ;
		    wire [7:0]          rd_com        ;
		    
		
		    //sccb 时钟周期 4.8us 
		    //在读或者写状态下加一
		    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;
		
		    //区分读写输出的个数
		    //读21个,写30个
		    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;
			
		    //读工作标志
		    //当收到读使能时,flag_r置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
		
		    //写工作标志
		    //当收到写使能时,flag_w置1
		    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
		
		    //读工作时(收到读使能时),sio_d输出 21 bit数据
		    //SCCB时序中,读分为两个阶段,因此bit_num = 21、duan_num = 2;
		
		    //写工作时(收到写使能时),sio_d输出 30 bit数据
		    //SCCB时序中,写只有一个阶段,因此bit_num = 30、duan_num = 1;
		    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
		
		    //SCCB时钟的高低变化
		    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
		    
		    //一定要结合波形分析,开始和结束占用了两个“0”数据,因此在SCCB时钟高低变化时应排除掉
		    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 @ (*)begin
		        if(flag_r)begin
		            out_data <= {1'h0,rd_com,1'h1,sub_addr,1'h1,1'h0,1'h1,9'h0};//最后9位都为0 也就是有效21位
		        end
		        else if(flag_w)begin
		            out_data <= {1'h0,8'h42,1'h1,sub_addr,1'h1,wdata,1'h1,1'h0,1'h1};//有效30位
		        end
		        else begin
		            out_data <= 0;
		        end
		    end
		
		    //区分写地址和读地址
		    assign rd_com = (flag_r && count_duan == 0)? 8'h42 : 8'h43;
		
		    //写数据使能,发往摄像头
		    //在写数据的时候为1
		    //除了读数据阶段以及复位,其他时间写数据使能都为1
		    //读数据前也要进行写操作,要写进去读的地址等信息
		    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
		
		    //设置读数据时,给摄像头写数据的使能为0
		    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;
		
		    //写入数据,在sccb时钟周期的1/4处开始写
		    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]; //一位一位赋值,共30位
		        end
		    end
		
		    assign out_data_time = count_bit >= 0 && count_bit < bit_num && add_count_sck && count_sck == SIO_C/4-1;
		
		    //读出数据,在sccb时钟周期的3/4处开始读
		    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;  //一位一位赋值,共8位
		        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;
		
		
		    //读出数据有效
		    //在读操作下完成最后的阶段置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

		    //准备信号
		    //不读不写,置1
		    always  @(*)begin
		        if(ren || wen || flag_r || flag_w)begin
		            rdy = 0;
		        end
		        else begin
		            rdy = 1;
		        end
		    end
		
		endmodule

5.摄像头配置模块

摄像头用到ov7670,共有164个寄存器需要配置。配置信号为 操作码+地址+配置值

本模块的作用:管理ov7670的寄存器配置信号,产生相应的读写命令

FPGA综合项目——边缘检测系统_第6张图片

配置模块代码如下:

		module ov7670_config(
		    clk        ,    //输入,25M时钟
		    rst_n      ,    //输入,复位
		    config_en  ,    //输入,配置使能
		    rdy        ,    //输入,准备信号
		    rdata      ,    //输入,读到的数据
		    rdata_vld  ,    //输入,读到数据有效信号
		    wdata      ,    //输出,写入摄像头寄存器的数据
		    addr       ,    //输出,寄存器地址
		    wr_en	   ,    //输出,写使能
			rd_en      ,    //输出,读使能
		    cmos_en    ,    //输出,配置完成后的采集使能 ,给到采集模块
		    pwdn
		             
		    );
		
		    parameter      DATA_W  =         8;  //位宽参数
		    parameter      RW_NUM  =         2;  //先写后读,计数为0则写,为1则读   
		    
		    //输入信号定义
		    input               clk      ;   //50Mhz
		    input               rst_n    ;
		    input               config_en;
		    input               rdy      ;
		    input [DATA_W-1:0]  rdata    ;
		    input               rdata_vld;
		
		    //输出信号定义
		    output[DATA_W-1:0]  wdata    ;
		    output[DATA_W-1:0]  addr     ;
		    output              cmos_en  ;
		    output              wr_en    ;
		    output              rd_en    ;
		    output              pwdn     ;

		    //输出信号reg定义
		    reg   [DATA_W-1:0]  wdata    ;
		    reg   [DATA_W-1:0]  addr     ;
		    reg                 cmos_en  ;
		    reg                 wr_en    ;
		    reg                 rd_en    ;
		
		    //中间信号定义
		    reg   [8 :0]        reg_cnt  ;
		    wire                add_reg_cnt;
		    wire                end_reg_cnt;
		    reg                 flag     ;
		    reg   [17:0]        add_wdata;   //配置表信号 “操作码2比特”+“地址8比特”+“配置值8比特”
		
		    reg   [ 1:0]        rw_cnt     ;
		    wire                add_rw_cnt ;
		
		    assign              pwdn = 0;
		
		    `include "ov7670_para.v" //寄存器地址及数据
				
		    //配置寄存器个数计数,当配置完164个寄存器后给出采集使能
		    //结束一个寄存器的设置时,+1,继续下一个寄存器的设置
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            reg_cnt <= 0;
		        end
		        else if(add_reg_cnt)begin
		            if(end_reg_cnt)
		                reg_cnt <= 0;
		            else
		                reg_cnt <= reg_cnt + 1;
		        end
		    end
		
		    assign add_reg_cnt = end_rw_cnt;   
		    assign end_reg_cnt = add_reg_cnt && reg_cnt==REG_NUM-1;
		
		       
		    //写读计数器 0写1读 单独的配置一个寄存器的操作方式
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            rw_cnt <= 0;
		        end
		        else if(add_rw_cnt) begin
		            if(end_rw_cnt)
		                rw_cnt <= 0;
		            else
		                rw_cnt <= rw_cnt + 1;
		        end
		    end
		
		    assign  add_rw_cnt = flag && rdy;
		    assign  end_rw_cnt = add_rw_cnt && rw_cnt==RW_NUM-1;
		
		
		    //配置使能过来到配置结束,flag为1
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            flag <= 1'b0;
		        end
		        else if(config_en)begin
		            flag <= 1'b1;
		        end
		        else if(end_reg_cnt)begin
		            flag <= 1'b0;
		        end
		    end
		
		
		    //cmos_en ,采集使能,配置完为1
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            cmos_en <= 1'b0;
		        end
		        else if(end_reg_cnt)begin
		            cmos_en <= 1'b1;
		        end
		    end
		
		
		    //配置值为add_wdata的低八位
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            wdata <= 8'b0;
		        end
		        else begin
		            wdata <= add_wdata[7:0];
		        end
		    end
		
		
		    //地址为add_wdata的高八位
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            addr <= 8'b0;
		        end
		
		
		        else begin
		            addr <= add_wdata[15:8];
		        end
		    end
		
		
		    //写使能
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            wr_en <= 1'b0;
		        end
		        else if(add_rw_cnt && rw_cnt==0 && add_wdata[16])begin
		            wr_en <= 1'b1;
		        end
		        else begin
		            wr_en <= 1'b0;
		        end
		    end
		
		
		    //读使能
		    always  @(posedge clk or negedge rst_n)begin
		        if(rst_n==1'b0)begin
		            rd_en <= 1'b0;
		        end
		        else if(add_rw_cnt && rw_cnt==1 && add_wdata[17])begin
		            rd_en <= 1'b1;
		        end
		        else begin
		            rd_en <= 1'b0;
		        end
		    end
			
		endmodule

此模块有一个输入config_en需要完善,因此根据配置的速度来写一个产生配置使能的小模块key_en,这里用key_vld的第二位来作配置使能信号。
代码如下:

		module key_en(
		    clk    ,
		    rst_n  ,
		    key_in ,
		    key_vld 
		);
		parameter                  	DATA_W 	  = 20          ;
		parameter                  	KEY_W 	  = 4           ;
		parameter               	TIME_20MS = 500_000   ;
		
		input                    	clk                     ;
		input                   	rst_n                   ;
		input      [KEY_W-1 :0]		key_in                  ;
		output     [KEY_W-1 :0]     key_vld                 ;
		
		
		reg        [KEY_W-1 :0]     key_vld                 ;
		reg        [DATA_W-1:0]     cnt                     ;
		wire                       	add_cnt                 ;
		wire                       	end_cnt                 ;
		reg					        flag                    ;
		reg     [KEY_W-1 :0]        key_in_ff1              ;
		reg     [KEY_W-1 :0]        key_in_ff0              ;
		
		always  @(posedge clk or negedge rst_n)begin
		    if(rst_n==1'b0)begin
		        cnt <= 20'b0;
		    end
		    else if(add_cnt)begin
		        if(end_cnt)
		            cnt <= 20'b0;
		        else
		            cnt <= cnt + 1'b1;
		    end
		    else begin
		        cnt <= 0;
		    end
		end
		
		
		assign add_cnt = flag==1'b0 && (key_in_ff1!=0);
		assign end_cnt = add_cnt && cnt == TIME_20MS - 1;
		
		
		
		always  @(posedge clk or negedge rst_n)begin
		    if(rst_n==1'b0)begin
		        flag <= 1'b0;
		    end
		    else if(end_cnt)begin
		        flag <= 1'b1;
		    end
		    else if(key_in_ff1==0)begin
		        flag <= 1'b0;
		    end
		end
		
		
		always  @(posedge clk or negedge rst_n)begin
		    if(rst_n==1'b0)begin
		        key_in_ff0 <= 0;
		        key_in_ff1 <= 0;
		    end
		    else begin
		        key_in_ff0 <= key_in    ;
		        key_in_ff1 <= key_in_ff0;
		    end
		end
		
		
		always  @(posedge clk or negedge rst_n)begin
		    if(rst_n==1'b0)begin
		        key_vld <= 0;
		    end
		    else if(end_cnt)begin
		        key_vld <= key_in_ff1;
		    end
		    else begin
		        key_vld <= 0;
		    end
		end
		endmodule

6.采集模块

采集要求:分辨率640*480、RGB565格式图像、30帧/s。
注意:①因为采集的时RGB565的数据,所以一个像素点共16位数据,但是摄像头采集一次的数据为8位,所以需要采集两次才为一个像素点。②当行信号为高时,数据才有效。
FPGA综合项目——边缘检测系统_第7张图片
代码如下:

		module cmos_capture(
		    clk         ,   //输入,25M时钟
		    rst_n       ,   //输入,复位
		    en_capture  ,   //输入,采集使能,由配置模块给出
		    vsync       ,   //输入,帧信号,由FPGA给出
		    href        ,   //输入,行信号,由FPGA给出
		    din         ,   //输入,摄像头的8位数据输入
		    dout        ,   //输出,采集到的16位图像数据
		    dout_vld    ,   //输出,数据输出有效信号
		    dout_sop    ,   //输出,一帧的起始信号,即一帧开始传输
		    dout_eop        //输出,一帧的结束信号,即一帧传输结束
		);
		
		    parameter     COL    = 640; //一行的像素点个数
		    parameter     ROW    = 480; //行
		
		    input          clk          ; 
		    input          rst_n        ;
		    input          en_capture   ; 
		    input          vsync        ; 
		    input          href         ; 
		    input  [7:0]   din          ; 
		    
		    output [15:0]  dout         ; 
		    output         dout_vld     ; 
		    output         dout_sop     ; 
		    output         dout_eop     ; 
		
		    reg    [15:0]  dout         ;
		    reg            dout_vld     ;
		    reg            dout_sop     ;
		    reg            dout_eop     ;
		    
		    reg    [10:0]  count_x      ;  
		    reg    [9:0]   count_y      ;
		    reg            flag_capture ;   
		    reg            vsync_ff0    ; 
		
		    wire           add_count_x  ;
		    wire           end_count_x  ;
		    wire           add_count_y  ;
		    wire           end_count_y  ;
		    wire           vsync_l2h    ;  
		    wire           din_vld      ; 
		    wire           flag_dout_vld;
			
		     //一行的像素计数,共640个像素点
		     //两个时钟一个像素点
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            count_x <= 0;
		        end
		        else if(add_count_x)begin
		            if(end_count_x)begin
		                count_x <= 0;
		            end
		            else begin
		                count_x <= count_x + 1;
		            end
		        end
		    end
		
		    //当数据输入有效且采集的时候+1
		    assign add_count_x = flag_capture && din_vld;
		    assign end_count_x = add_count_x && count_x == COL*2-1;
		
		    //当行信号href为1时,数据有效
		    assign din_vld = flag_capture && href;    
		
		
		    //行计数器,共480行
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            count_y <= 0;
		        end
		        else if(add_count_y)begin
		            if(end_count_y)begin
		                count_y <= 0;
		            end
		            else begin
		                count_y <= count_y + 1;
		            end
		        end
		    end
		
		    //完成一行像素采集要转到下一行时+1
		    assign add_count_y = end_count_x;
		    assign end_count_y = add_count_y && count_y == ROW-1;           
		
		
		    //开始采集指示信号,在vsync上升沿时置1
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            flag_capture <= 0;
		        end
		        else if(flag_capture == 0 && vsync_l2h && en_capture)begin
		            flag_capture <= 1;
		        end
		        else if(end_count_y)begin
		            flag_capture <= 0;
		        end
		    end
		
		    //监沿器:鉴定vsync的上升沿,而这个上升沿表示新的一帧图像要开始传输了
		    assign vsync_l2h = vsync_ff0 == 0 && vsync == 1;         
		
		
		    //将vsync过一个D触发器打拍
		    //vsync_ff0 为前一时刻
		    //vsync为当前时刻
		    always @ (posedge clk or negedge rst_n)begin      
		        if(!rst_n)begin
		            vsync_ff0 <= 0;
		        end
		        else begin
		            vsync_ff0 <= vsync;
		        end
		    end
		
		
		    //采集数据输出,将采到的din给dout的低8位,下一次
		    always @ (posedge clk or negedge rst_n)begin        
		        if(!rst_n)begin
		            dout <= 0;
		        end
		        else if(din_vld)begin
		            dout <= {dout[7:0],din};
		        end
		    end
		          
		    //当前像素点输出有效指示信号
		    always @ (posedge clk or negedge rst_n)begin    
		        if(!rst_n)begin
		            dout_vld <= 0;
		        end
		        else if(flag_dout_vld)begin
		            dout_vld <= 1;
		        end
		        else begin
		            dout_vld <= 0;
		        end
		    end
		
		     //第二个时钟来的时候为第一个像素点
		    assign flag_dout_vld = add_count_x && count_x[0] == 1;
		
		
		
		    //一帧数据开始
		    always @ (posedge clk or negedge rst_n)begin
		
		
		        if(!rst_n)begin
		            dout_sop <= 0;
		        end
		        else if(flag_dout_vld && count_x[10:1] == 0 && count_y == 0)begin
		            dout_sop <= 1;
		        end
		        else begin
		            dout_sop <= 0;
		        end
		    end
		
		
		
		    //一帧数据读完
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_eop <= 0;
		        end
		        else if(flag_dout_vld && count_x[10:1] == COL-1 && count_y == ROW-1)begin
		            dout_eop <= 1;
		        end
		        else begin
		            dout_eop <= 0;
		        end
		    end
		
		endmodule

7.灰度模块

图像处理的第一步,灰度处理:即色彩图转为灰度图,我们用到一个比较出名的色彩公式:

		F= RED * 0.299 + GREEN * 0.587 + BLUE *  0.114

那么问题又来了,怎么做小数的乘法呢?事实上,在实际工程中我们都会避免效率低下的浮点运算,所以第一步就是让它变成整数运算。①因为公式里的浮点运算是三位精度,因此我们可以先乘以1000以后再除以1000。换句话说就是用299、587、114除以1000。②对于除法,在FPGA中采用除法器是相当耗费资源的事,所以我们采用不消耗资源的移位法进行除法计算,但是移位法的缺点在于只能除以2的倍数的值,因此通过右移10位来除以1024。③当然考虑到精度问题,我们也可以用乘以2^8再右移8位,即0.299 * 256 右移八位,依此类推。

		最终的公式为:F = (RED * 76 + GREEN * 150 + BLUE * 30)>>8

FPGA综合项目——边缘检测系统_第8张图片
具体代码如下:

		module rgb565_gray(
		    clk         ,   //输入,25M
		    rst_n       ,   //输入,复位
		    din         ,   //输入,采集到的图像数据,一次一个像素点
		    din_vld     ,   //输入,输入有效信号
		    din_sop     ,   //输入,一帧图像传输的开始
		    din_eop     ,   //输入,一帧图像传输的结束
		    dout        ,   //输出,灰度转化后的图像数据,一次一个像素点
		    dout_vld    ,   //输出,输出有效信号
		    dout_sop    ,   //输出,一帧图像传输的开始
		    dout_eop        //输出,一帧图像传输的结束
		);
		
		    //输入输出定义
		    input         clk     ;
		    input         rst_n   ;
		    input  [15:0] din     ; //采集的数据16位
		    input         din_vld ;
		    input         din_sop ;
		    input         din_eop ;
		
		    output [7:0]  dout     ;
		    output        dout_vld ;
		    output        dout_sop ;
		    output        dout_eop ;
		
		    //信号类型定义
		    reg    [7:0]  dout     ;
		    reg           dout_vld ;
		    reg           dout_sop ;
		    reg           dout_eop ;
		
		    wire   [7:0]  red     ;
		    wire   [7:0]  green   ;
		    wire   [7:0]  blue    ;
		
		    //补齐8位数据,用原数据的低位补齐
		    assign red   = {din[15:11],din[13:11]};
		    assign green = {din[10:5],din[6:5]};
		    assign blue  = {din[4:0],din[2:0]}; 
		
		    //当输入有效时,实现灰度公式
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout <= 0;
		        end
		        else if(din_vld)begin
		            dout <= (red*76 + green*150 + blue*30) >> 8;
		        end
		    end
		
		    //输出有效跟随输入有效,即有一个输入有效就有一个输出有效
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_vld <= 0;
		        end
		        else begin
		            dout_vld <= din_vld;
		        end
		    end
		
		    //输出开始跟随输入开始,即一帧的第一个像素输入就开始输出一帧第一个像素
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_sop <= 0;
		        end
		        else begin
		            dout_sop <= din_sop;
		        end
		    end
		
		    输出结束跟随输入结束,即一帧的最后一个像素输入就开始输出一帧最后一个像素
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_eop <= 0;
		        end
		        else begin
		            dout_eop <= din_eop;
		        end
		    end
		
		endmodule

8.高斯滤波模块

高斯滤波采用3 * 3 的掩膜,即三行三列的像素点,我们要依次将一帧图像的所有3 * 3 掩膜的像素点进行滤波。下图的掩模具体的计算公式如下:
FPGA综合项目——边缘检测系统_第9张图片

FPGA综合项目——边缘检测系统_第10张图片
其中f(x,y)为灰度像素值,g(x,y)为高斯滤波后的像素值。由公式可知,这与我们在优化显示AMG8833模块中的原理类似(具体可以看此链接AMG8833优化显示),都是以附近像素为原始数据的带有权重的数据处理方法。

我们知道方法之后,怎么在640 * 480 的一帧像素中构建3 * 3的掩膜呢?这里我们需要用到移位寄存器的IP核。
FPGA综合项目——边缘检测系统_第11张图片
如上图所示为移位寄存器的端口设置,taps是可以设置的在指定位置输出的抽头,将抽头数设置为3,即可输出3行;将抽头间的距离设置为480,则意味着第一个抽头的输出是在寄存器链的第480位,第二个在480 * 2 后输出;而shiftout是寄存器末尾的输出,与最后一个抽头的输出一致,这里我们用不到。

现在我们只是可以做到输出3行,那么怎么输出三列呢?可以用D触发器进行打拍寄存。信号经过D触发器后后慢上一拍(参考D触发器打拍)所以打两拍之后就得到了前两排、前一拍、现在的、共3个数据,所以完成了3列的数据寄存。由此3 * 3的掩模便做出来了。
FPGA综合项目——边缘检测系统_第12张图片
然后就是高斯算法:

	①计算每一行的代数和
	②每一列的代数和相加除以16,并输出

模块如图所示:
FPGA综合项目——边缘检测系统_第13张图片
代码如下:

		module gs_filter(
		    clk         ,   //输入,25M时钟
		    rst_n       ,   //输入,复位
		    din         ,   //输入,经过灰度处理的8位数据
		    din_vld     ,   //输入,输入数据有效
		    din_sop     ,   //输入,一帧图像传输的开始
		    din_eop     ,   //输入,一帧图像传输的结束
		    dout        ,   //输出,数据输出
		    dout_vld    ,   //输出,数据有效
		    dout_sop    ,   //输出,一帧图像传输的开始
		    dout_eop        //输出,一帧图像传输的结束
		);
		
		    //输入输出定义
		    input         clk     ;
		    input         rst_n   ;
		    input  [7:0]  din     ;
		    input         din_vld ;
		    input         din_sop ;
		    input         din_eop ;
		
		    output [7:0]  dout    ;
		    output        dout_vld;
		    output        dout_sop;
		    output        dout_eop;
		
		    //型号类型定义
		    reg    [7:0]  dout    ;
		    reg           dout_vld;
		    reg           dout_sop;
		    reg           dout_eop;
		
		    reg           din_vld_ff0 ;
		    reg           din_vld_ff1 ;
		    reg           din_vld_ff2 ;
		    reg           din_sop_ff0 ;
		    reg           din_sop_ff1 ;
		    reg           din_sop_ff2 ;
		    reg           din_eop_ff0 ;
		    reg           din_eop_ff1 ;
		    reg           din_eop_ff2 ;
		
		    reg    [7:0]  taps0_ff0   ;
		    reg    [7:0]  taps0_ff1   ;
		    reg    [7:0]  taps1_ff0   ;
		    reg    [7:0]  taps1_ff1   ;
		    reg    [7:0]  taps2_ff0   ;
		    reg    [7:0]  taps2_ff1   ;
		
		    reg    [15:0] gs_0        ;
		    reg    [15:0] gs_1        ;
		    reg    [15:0] gs_2        ;
		
		    wire   [7:0]  taps0   ;
		    wire   [7:0]  taps1   ;
		    wire   [7:0]  taps2   ;
		
		    //例化移位寄存器IP核
		    shift_ipcore u1(
			    .clken      (din_vld    ),
			    .clock      (clk        ),
			    .shiftin    (din        ),
			    .shiftout   (           ),//悬空
			    .taps0x     (taps0      ),
			    .taps1x     (taps1      ),
			    .taps2x     (taps2      ) 
		    );
		
		    //异步处理消除亚稳态
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            din_vld_ff0 <= 0;
		            din_vld_ff1 <= 0;
		            din_vld_ff2 <= 0;
		            din_sop_ff0 <= 0;
		            din_sop_ff1 <= 0;
		            din_sop_ff2 <= 0;
		            din_eop_ff0 <= 0;
		            din_eop_ff1 <= 0;
		            din_eop_ff2 <= 0;
		        end
		        else begin
		            din_vld_ff0 <= din_vld;
		            din_vld_ff1 <= din_vld_ff0;
		            din_vld_ff2 <= din_vld_ff1;
		
		            din_sop_ff0 <= din_sop;
		            din_sop_ff1 <= din_sop_ff0;
		            din_sop_ff2 <= din_sop_ff1;
		
		            din_eop_ff0 <= din_eop;
		            din_eop_ff1 <= din_eop_ff0;
		            din_eop_ff2 <= din_eop_ff1;
		        end
		    end
		
		    //D触发器打拍寄存前两拍、前一拍的数据
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            taps0_ff0 <= 0;
		            taps0_ff1 <= 0;
		            taps1_ff0 <= 0;
		            taps1_ff1 <= 0;
		            taps2_ff0 <= 0;
		            taps2_ff1 <= 0;
		        end
		        else if(din_vld_ff0)begin
		            taps0_ff0 <= taps0;
		            taps0_ff1 <= taps0_ff0;
		            taps1_ff0 <= taps1;
		            taps1_ff1 <= taps1_ff0;
		            taps2_ff0 <= taps2;
		            taps2_ff1 <= taps2_ff0;
		        end
		    end
		
		    //计算3 * 3 掩模第一行的和
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            gs_0 <= 0;
		        end
		        else if(din_vld_ff1)begin
		            gs_0 <= taps0_ff1 + 2*taps1_ff1 + taps2_ff1;
		        end
		    end
		
		    //计算3 * 3 掩模第二行的和
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            gs_1 <= 0;
		        end
		        else if(din_vld_ff1)begin
		            gs_1 <= 2*taps0_ff0 + 4*taps1_ff0 + 2*taps2_ff0;
		        end
		    end
		
		    //计算3 * 3 掩模第三行的和
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            gs_2 <= 0;
		        end
		        else if(din_vld_ff1)begin
		            gs_2 <= taps0 + 2*taps1 + taps2;
		        end
		    end
		
		    //三行之和除以16,即右移4位
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout <= 0;
		        end
		        else if(din_vld_ff2)begin
		            dout <= (gs_0 + gs_1 + gs_2) >> 4;
		        end
		    end
		
		    //输出有效,当输出有效时输出有效
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_vld <= 0;
		        end
		        else if(din_vld_ff2)begin
		            dout_vld <= 1;
		        end
		        else begin
		            dout_vld <= 0;
		        end
		    end
		
		    //一帧输出开始,输入开始时输出开始
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_sop <= 0;
		        end
		        else if(din_sop_ff2)begin
		            dout_sop <= 1;
		        end
		        else begin
		            dout_sop <= 0;
		        end
		    end
		
		    //一帧输出结束,输入结束时输出结束
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_eop <= 0;
		        end
		        else if(din_eop_ff2)begin
		            dout_eop <= 1;
		        end
		        else begin
		            dout_eop <= 0;
		        end
		    end
		
		endmodule

9.二值模块

此模块较为简单,顾名思义就是变为2值数据,也就是0和1。具体的做法为设定一个阈值,大于阈值为1,否则为0。
FPGA综合项目——边缘检测系统_第14张图片
代码如下:

		module gray_bit(
		    clk         ,   //输入,25M时钟
		    rst_n       ,   //输入,复位
		    din         ,   //输入,经过高斯处理的8位数据 
		    din_vld     ,   //输入,输入数据有效
		    din_sop     ,   //输入,一帧图像传输的开始
		    din_eop     ,   //输入,一帧图像传输的结束
		    dout        ,   //输出,数据输出
		    dout_vld    ,   //输出,数据有效
		    dout_sop    ,   //输出,一帧图像传输的开始
		    dout_eop    ,  //输出,一帧图像传输的结束
		    value           //输入,设定的阈值      
		);
		
		    input         clk     ;
		    input         rst_n   ;
		    input  [7:0]  value   ;
		    input  [7:0]  din     ;
		    input         din_vld ;
		    input         din_sop ;
		    input         din_eop ;
		
		    output        dout    ;
		    output        dout_vld;
		    output        dout_sop;
		    output        dout_eop;
		
		    reg           dout    ;
		    reg           dout_vld;
		    reg           dout_sop;
		    reg           dout_eop;
		
		    //大于阈值输出1,否则为0
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout <= 0;
		        end
		        else if(din >= value)begin
		            dout <= 1;
		        end
		        else begin
		            dout <= 0;
		        end
		    end
		
		    //输出有效跟随输入有效
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_vld <= 0;
		        end
		        else begin
		            dout_vld <= din_vld;
		        end
		    end
		
		    //输出开始跟随输入开始
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_sop <= 0;
		        end
		        else begin
		            dout_sop <= din_sop;
		        end
		    end
		
		    //输出结束跟随输入结束
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            dout_eop <= 0;
		        end
		        else begin
		            dout_eop <= din_eop;
		        end
		    end
		
		endmodule

10.边缘检测模块

与高斯滤波模块一样,此模块同样需要用到3 * 3 的掩模。只不过算法不一样。
具体的算法参考(索贝尔边缘检测算法)
在这里插入图片描述
FPGA综合项目——边缘检测系统_第15张图片
A代表原始图像,Gx、Gy表示索贝尔算子。具体计算式为:
在这里插入图片描述
在这里插入图片描述
其中其中f(x,y), 表示图像(x,y)点的灰度值,换为模块中的步骤则为:

				①按公式计算一行或一列的代数和
				②求出3 * 3 矩阵的行或者列的差值,用绝对值表示
				③行的绝对值与列的绝对值相加,判断是否为边缘点,输出

模块图:
FPGA综合项目——边缘检测系统_第16张图片

代码中的打拍顺序图:
FPGA综合项目——边缘检测系统_第17张图片
代码如下:

			module sobel(
			    clk         ,   //输入,25M时钟
			    rst_n       ,   //输入,复位
			    din         ,   //输入,经过灰度处理的8位数据 
			    din_vld     ,   //输入,输入数据有效
			    din_sop     ,   //输入,一帧图像传输的开始
			    din_eop     ,   //输入,一帧图像传输的结束
			    dout        ,   //输出,数据输出
			    dout_vld    ,   //输出,数据有效
			    dout_sop    ,   //输出,一帧图像传输的开始
			    dout_eop        //输出,一帧图像传输的结束 
			);
			
			    input         clk     ;
			    input         rst_n   ;
			    input         din     ;
			    input         din_vld ;
			    input         din_sop ;
			    input         din_eop ;
			
			    output        dout    ;
			    output        dout_vld;
			    output        dout_sop;
			    output        dout_eop;
			
			    reg           dout    ;
			    reg           dout_vld;
			    reg           dout_sop;
			    reg           dout_eop;
			
			    reg           din_vld_ff0 ;
			    reg           din_vld_ff1 ;
			    reg           din_vld_ff2 ;
			    reg           din_vld_ff3 ;
			
			    reg           din_sop_ff0 ;
			    reg           din_sop_ff1 ;
			    reg           din_sop_ff2 ;
			    reg           din_sop_ff3 ;
			
			    reg           din_eop_ff0 ;
			    reg           din_eop_ff1 ;
			    reg           din_eop_ff2 ;
			    reg           din_eop_ff3 ;
			
			    reg           taps0_ff0   ;
			    reg           taps0_ff1   ;
			    reg           taps1_ff0   ;
			    reg           taps1_ff1   ;
			    reg           taps2_ff0   ;
			    reg           taps2_ff1   ;
			
			    reg    [7:0]  gx_0        ;
			    reg    [7:0]  gx_2        ;
			    reg    [7:0]  gy_0        ;
			    reg    [7:0]  gy_2        ;
			    reg    [7:0]  gx          ;
			    reg    [7:0]  gy          ;
			    reg    [7:0]  g           ;
			
			    wire          taps0_tmp   ;
			    wire          taps1_tmp   ;
			    wire          taps2_tmp   ;
			
			    wire          taps0       ;
			    wire          taps1       ;
			    wire          taps2       ;
			
			    //例化移位寄存器IP 用来获得3 * 3 掩模
			    shift2_ipcore u1(
				    .clken      (din_vld    ),
				    .clock      (clk        ),
				    .shiftin    (din        ),
				    .shiftout   (           ),
				    .taps0x     (taps0_tmp  ),
				    .taps1x     (taps1_tmp  ),
				    .taps2x     (taps2_tmp  ) 
			    );
			
			    assign taps0 = taps0_tmp;
			    assign taps1 = taps1_tmp;
			    assign taps2 = taps2_tmp;
			
			    //异步处理
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            din_vld_ff0 <= 0;
			            din_vld_ff1 <= 0;
			            din_vld_ff2 <= 0;
			            din_vld_ff3 <= 0;
			            din_sop_ff0 <= 0;
			            din_sop_ff1 <= 0;
			            din_sop_ff2 <= 0;
			            din_sop_ff3 <= 0;
			            din_eop_ff0 <= 0;
			            din_eop_ff1 <= 0;
			            din_eop_ff2 <= 0;
			            din_eop_ff3 <= 0;
			        end
			        else begin
			            din_vld_ff0 <= din_vld;
			            din_vld_ff1 <= din_vld_ff0;
			            din_vld_ff2 <= din_vld_ff1;
			            din_vld_ff3 <= din_vld_ff2;
			
			            din_sop_ff0 <= din_sop;
			            din_sop_ff1 <= din_sop_ff0;
			            din_sop_ff2 <= din_sop_ff1;
			            din_sop_ff3 <= din_sop_ff2;
			
			            din_eop_ff0 <= din_eop;
			            din_eop_ff1 <= din_eop_ff0;
			            din_eop_ff2 <= din_eop_ff1;
			            din_eop_ff3 <= din_eop_ff2;
			        end
			    end
			
			    //用D触发器打拍获得前两拍、前一拍的数据
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            taps0_ff0 <= 0;
			            taps0_ff1 <= 0;
			            taps1_ff0 <= 0;
			            taps1_ff1 <= 0;
			            taps2_ff0 <= 0;
			            taps2_ff1 <= 0;
			        end
			        else if(din_vld_ff0)begin
			            taps0_ff0 <= taps0;
			            taps0_ff1 <= taps0_ff0;
			            taps1_ff0 <= taps1;
			            taps1_ff1 <= taps1_ff0;
			            taps2_ff0 <= taps2;
			            taps2_ff1 <= taps2_ff0;
			        end
			    end
			    
			    //gx按列算
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            gx_0 <= 0;
			        end
			        else if(din_vld_ff1)begin
			            gx_0 <= taps0_ff1 + 2*taps1_ff1 + taps2_ff1;
			        end
			    end
			
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            gx_2 <= 0;
			        end
			        else if(din_vld_ff1)begin
			            gx_2 <= taps0 + 2*taps1 + taps2;
			        end
			    end
			
			    //gy按行算
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            gy_0 <= 0;
			        end
			        else if(din_vld_ff1)begin
			            gy_0 <= taps0_ff1 + 2*taps0_ff0 + taps0;
			        end
			    end
			
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            gy_2 <= 0;
			        end
			        else if(din_vld_ff1)begin
			            gy_2 <= taps2_ff1 + 2*taps2_ff0 + taps2;
			        end
			    end
			
			    //gx用绝对值表示
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            gx <= 0;
			        end
			        else if(din_vld_ff2)begin
			            gx <= (gx_0>gx_2) ? (gx_0-gx_2) : (gx_2-gx_0);
			        end
			    end
			
			    //gy用绝对值表示
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            gy <= 0;
			        end
			        else if(din_vld_ff2)begin
			            gy <= (gy_0>gy_2) ? (gy_0-gy_2) : (gy_2-gy_0);
			        end
			    end
			
			    //相加得到G
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            g <= 0;
			        end
			        else if(din_vld_ff3)begin
			            g <= gx + gy;
			        end
			    end
			
			    //判断边缘并输出
			    always @ (*)begin
			        dout = (g>=1) ? 1 : 0;
			    end
			
			    //输出有效
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            dout_vld <= 0;
			        end
			        else if(din_vld_ff3)begin
			            dout_vld <= 1;
			        end
			        else begin
			            dout_vld <= 0;
			        end
			    end
			
			    //输出开始
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            dout_sop <= 0;
			        end
			        else if(din_sop_ff3)begin
			            dout_sop <= 1;
			        end
			        else begin
			            dout_sop <= 0;
			        end
			    end
			    
			    //输出结束
			    always @ (posedge clk or negedge rst_n)begin
			        if(!rst_n)begin
			            dout_eop <= 0;
			        end
			        else if(din_eop_ff3)begin
			            dout_eop <= 1;
			        end
			        else begin
			            dout_eop <= 0;
			        end
			    end
			
			endmodule

11.存储模块

本项目需要存储的数据量其实是不大的,因而使用FPGA片内的两个RAM就能完成存储功能。,其工作方式为:

		每个RAM可以保存1幅320*200的图像
		①图像数据开始时保存到RAM0,同时VGA从RAM1中读取图像数据进行显示。
		②读取RAM0的数据进行显示。同时模块准备将新的图像数据写到RAM1当中。
		③ 需要注意的是:当写完一幅图像并且读完一幅图像时,RAM才开始切换。

ram的ip核:

FPGA综合项目——边缘检测系统_第18张图片

FPGA综合项目——边缘检测系统_第19张图片
模块图:
FPGA综合项目——边缘检测系统_第20张图片
代码如下:

		module ram(
		    clk         ,   //输入,25M时钟
		    rst_n       ,   //输入,复位
		    din         ,   //输入,输入1位数据
		    din_vld     ,   //输入,输入数据有效信号
		    din_sop     ,   //输入,一帧数据的开始
		    din_eop     ,   //输入,一帧数据的结束
		    rd_addr     ,   //输入,要读RAM的地址
		    rd_en       ,   //输入,读RAM的使能
		    rd_end      ,   //输入,读结束信号
		    rd_addr_sel ,   //输入,读切换信号,用来切换RAM
		    dout        ,   //输出,输出1位数据
		    wr_end          //输出,写结束信号 
		);
		
		    input         clk        ;
		    input         rst_n      ;
		    input         din        ;
		    input         din_vld    ;
		    input         din_sop    ;
		    input         din_eop    ;
		    input  [15:0] rd_addr    ;
		    input         rd_en      ;
		    input         rd_end     ;
		    input         rd_addr_sel;
		
		    output        dout     ;
		    output        wr_end   ;
		
		    reg           dout     ;
		    reg           wr_end   ;
		
		    reg    [9:0]  cnt_col         ;
		    reg    [9:0]  cnt_row         ;
		
		    reg           wr_data         ;
		    reg    [15:0] wr_addr         ;
		    reg           wr_addr_sel     ;
		    reg           wr_en0          ;
		    reg           wr_en1          ;
		
		    reg           rd_en0          ;
		    reg           rd_en1          ;
		    reg           flag_wr         ;
		
		    wire          add_cnt_col     ;
		    wire          end_cnt_col     ;
		    wire          add_cnt_row     ;
		    wire          end_cnt_row     ;
		
		    wire          display_area    ;
		    wire          q0              ;
		    wire          q1              ;
		
		    //1行640个像素点
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            cnt_col <= 0;
		        end
		        else if(add_cnt_col)begin
		            if(end_cnt_col)
		                cnt_col <= 0;
		            else
		                cnt_col <= cnt_col + 1;
		        end
		    end
		
		    assign add_cnt_col = (flag_wr || (wr_end==0 && din_sop)) && din_vld;
		    assign end_cnt_col = add_cnt_col && cnt_col == 640-1;
		
		    //480行像素点,即一帧为640*480
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            cnt_row <= 0;
		        end
		        else if(add_cnt_row)begin
		            if(end_cnt_row)
		                cnt_row <= 0;
		            else
		                cnt_row <= cnt_row + 1;
		        end
		    end
		
		    assign add_cnt_row = end_cnt_col;
		    assign end_cnt_row = add_cnt_row && cnt_row == 480-1;
		
		    //显示区域为 (160--480)*(140--340)  320*200的一帧图像
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            wr_data <= 0;
		        end
		        else if(display_area)begin
		            wr_data <= din;
		        end
		    end
		
		    assign display_area = cnt_col >= 160 && cnt_col < 480 && cnt_row >= 140 && cnt_row <= 340;
		
		    //写到ram的地址
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            wr_addr <= 0;
		        end
		        else if(display_area)begin
		            wr_addr <= (cnt_col-160) + 320*(cnt_row-140);
		        end
		    end
		
		    //当读和写都完成时,写切换标志信号wr_addr_sel信号取反
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            wr_addr_sel <= 0;
		        end
		        else if(wr_end && rd_end)begin
		            wr_addr_sel <= ~wr_addr_sel;
		        end
		    end
		
		    //一帧数据传完,写结束
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            wr_end <= 0;
		        end
		        else if(end_cnt_row)begin
		            wr_end <= 1;
		        end
		        else if(wr_end && rd_end)begin
		            wr_end <= 0;
		        end
		    end
		    
		    //写指示区域信号,在写结束且一帧图像又要开始时置1
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            flag_wr <= 0;
		        end
		        else if(wr_end == 0 && din_sop)begin
		            flag_wr <= 1;
		        end
		        else if(end_cnt_row)begin
		            flag_wr <= 0;
		        end
		    end
		
		    //写到RAM0的使能,在显示区域且切换标志为0且数据有效时开始使能
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            wr_en0 <= 0;
		        end
		        else if(display_area && wr_addr_sel==0 && din_vld)begin
		            wr_en0 <= 1;
		        end
		        else begin
		            wr_en0 <= 0;
		        end
		    end
		
		    //写到RAM1的使能,在显示区域且切换标志为1且数据有效时开始使能
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            wr_en1 <= 0;
		        end
		        else if(display_area && wr_addr_sel==1 && din_vld)begin
		            wr_en1 <= 1;
		        end
		        else begin
		            wr_en1 <= 0;
		        end
		    end
		
		    //例化
		    //说明一下,因为rd_addr最多给到16位,也就是RAM最多能存2^16 = 65536个像素点
		    //所以对于640* 480的一帧图像,我们只能选择中间的320*200的区域来进行显示
		    ram_ipcore u0(
			    .clock      (clk       ),
			    .data       (wr_data   ),
			    .rdaddress  (rd_addr   ),
			    .rden       (rd_en0    ),
			    .wraddress  (wr_addr   ),
			    .wren       (wr_en0    ),
			    .q          (q0        ) 
		    );
		
		    ram_ipcore u1(
			    .clock      (clk       ),
			    .data       (wr_data   ),
			    .rdaddress  (rd_addr   ),
			    .rden       (rd_en1    ),
			    .wraddress  (wr_addr   ),
			    .wren       (wr_en1    ),
			    .q          (q1        ) 
		    );
		
		    //RAM0读使能在读切换信号为0且读使能有效时置1
		    always @ (*)begin
		        if(rd_en && rd_addr_sel == 0)
		            rd_en0 = 1;
		        else 
		            rd_en0 = 0;
		    end
		
		    //RAM1读使能在读切换信号为1且读使能有效时置1
		    always @ (*)begin
		        if(rd_en && rd_addr_sel == 1)
		            rd_en1 = 1;
		        else 
		            rd_en1 = 0;
		    end
		
		    //输出也得看从哪个RAM读
		    always @ (*)begin
		        if(rd_addr_sel == 0)begin
		            dout = q0;
		        end
		        else if(rd_addr_sel == 1)begin
		            dout = q1;
		        end
		        else begin
		            dout = 0;
		        end
		    end
	
		endmodule

12.VGA模块

此模块的作用为读取存储模块的数据并驱动到外部显示器进行显示。具体的VGA时序可以看VGA接口时序。

FPGA综合项目——边缘检测系统_第21张图片
具体代码如下:

	module vga_driver(
		    clk         ,   //输入,25m时钟
		    rst_n       ,   //输入,复位
		    din         ,   //输入,输入数据
		    wr_end      ,   //输入,写数据结束
		    vga_hys     ,   //输出,行同步信号
		    vga_vys     ,   //输出,场同步信号
		    vga_rgb     ,   //输出,16位的颜色信号
		    rd_addr     ,   //输出,16位读地址
		    rd_en       ,   //输出,读使能信号
		    rd_end      ,   //输出,读结束信号
		    rd_addr_sel     //输出,地址改变信号
		);
		
		    parameter     DATA_W = 16;
		    parameter     COL   = 320;
		    parameter     ROW   = 200;
		    parameter     COL_2 = 160;
		    parameter     ROW_2 = 100;
		
		    input         clk      ;
		    input         rst_n    ;
		    input         din      ;
		    input         wr_end   ;
		
		    output        vga_hys    ;
		    output        vga_vys    ;
		    output [DATA_W-1:0]  vga_rgb    ;
		    output [15:0] rd_addr    ;
		    output        rd_en      ;
		    output        rd_end     ;
		    output        rd_addr_sel;
		
		    reg           vga_hys    ;
		    reg           vga_vys    ;
		    reg    [DATA_W-1:0]  vga_rgb    ;
		    reg    [15:0] rd_addr    ;
		    reg           rd_en      ;
		    reg           rd_end     ;
		    reg           rd_addr_sel;
		
		    reg    [9:0]  cnt_hys         ;
		    reg    [9:0]  cnt_vys         ;
		    reg           vga_hys_tmp     ;
		    reg           vga_vys_tmp     ;
		    reg           vga_hys_tmp_ff0 ;
		    reg           vga_vys_tmp_ff0 ;
		    reg           display_area    ;
		    reg           e_area          ;
		    reg           display_area_ff0;
		    reg           e_area_ff0      ;
		    reg           display_area_ff1;
		    reg           e_area_ff1      ;
		    reg    [9:0]  x               ;
		    reg    [9:0]  y               ;
		
		    wire          add_cnt_hys     ;
		    wire          end_cnt_hys     ;
		    wire          add_cnt_vys     ;
		    wire          end_cnt_vys     ;
		    wire   [9:0]  x0              ;
		    wire   [9:0]  x1              ;
		    wire   [9:0]  y0              ;
		    wire   [9:0]  y1              ;
		
		    //一行的时钟数 在一定的时钟输出像素点
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            cnt_hys <= 0;
		        end
		        else if(add_cnt_hys)begin
		            if(end_cnt_hys)
		                cnt_hys <= 0;
		            else
		                cnt_hys <= cnt_hys + 1;
		        end
		    end
		
		    assign add_cnt_hys = 1;
		    assign end_cnt_hys = add_cnt_hys && cnt_hys == 800-1;
		
		    //525行数据,一行的像素点显示完后+1到下一行
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            cnt_vys <= 0;
		        end
		        else if(add_cnt_vys)begin
		            if(end_cnt_vys)
		                cnt_vys <= 0;
		            else
		                cnt_vys <= cnt_vys + 1;
		        end
		    end
		
		    assign add_cnt_vys = end_cnt_hys;
		    assign end_cnt_vys = add_cnt_vys && cnt_vys == 525-1;
		
		    //一行的时钟数到达96 即vga的同步脉冲数96个后,将vga_hys_tmp拉高
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            vga_hys_tmp <= 0;
		        end
		        else if(add_cnt_hys && cnt_hys == 95)begin
		            vga_hys_tmp <= 1;
		        end
		        else if(end_cnt_hys)begin
		            vga_hys_tmp <= 0;
		        end
		    end
		
		    //场同步信号在第二个vys时钟拉高
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            vga_vys_tmp <= 0;
		        end
		        else if(add_cnt_vys && cnt_vys == 1)begin
		            vga_vys_tmp <= 1;
		        end
		        else if(end_cnt_vys)begin
		            vga_vys_tmp <= 0;
		        end
		    end
		
		    //vga_hys异步处理
		    //vga_vys异步处理
		    //打两拍
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            vga_hys_tmp_ff0 <= 0;
		            vga_vys_tmp_ff0 <= 0;
		            vga_hys <= 0;
		            vga_vys <= 0;
		        end
		        else begin
		            vga_hys_tmp_ff0 <= vga_hys_tmp;
		            vga_vys_tmp_ff0 <= vga_vys_tmp;
		            vga_hys <= vga_hys_tmp_ff0;
		            vga_vys <= vga_vys_tmp_ff0;
		        end
		    end
		
		    //显示区域
		    always @ (*)begin
		        display_area = cnt_hys >= 141 && cnt_hys < (141+646) && cnt_vys >= 32 && cnt_vys < (32+484);
		    end
		
		    //有效边缘区域
		    always @ (*)begin
		
		        e_area = cnt_hys >= x0 && cnt_hys < x1 && cnt_vys >= y0 && cnt_vys < y1;
		    end
		
		    //对显示区域信号进行打拍
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            display_area_ff0 <= 0;
		            e_area_ff0 <= 0;
		            display_area_ff1 <= 0;
		            e_area_ff1 <= 0;
		        end
		        else begin
		            display_area_ff0 <= display_area;
		            e_area_ff0 <= e_area;
		            display_area_ff1 <= display_area_ff0;
		            e_area_ff1 <= e_area_ff0;
		        end
		    end
		
		    //有效区域 320*200 在正中间
		    assign x0 = 141 + (323 - COL_2);    //304
		    assign x1 = 141 + (323 + COL_2);    //624
		    assign y0 = 32 + (242 - ROW_2);     //174
		    assign y1 = 32 + (242 + ROW_2);     //374
		
		    //读地址的计算
		    //RAM中地址按位数一位一位存储
		    //地址信号16位,能存65536个
		    always @ (*)begin
		        x = cnt_hys - x0;
		    end
		
		    always @ (*)begin
		        y = cnt_vys - y0;
		    end
		
		    always @ (*)begin
		        rd_addr = COL*y + x;
		    end
		 
		    //读完写完一帧,换RAM继续读写
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            rd_addr_sel <= 1;
		        end
		        else if(rd_end && wr_end)begin
		            rd_addr_sel <= ~rd_addr_sel;
		        end
		    end
		
		    //当最后一行显示完,读结束
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            rd_end <= 0;
		        end
		        else if(end_cnt_vys)begin
		            rd_end <= 1;
		        end
		        else begin
		            rd_end <= 0;
		        end
		    end
		
		    //在有效显示区域 320*200 时 读开始
		    always @ (*)begin
		        rd_en = e_area;
		    end
		
		    //rgb565 全1为白 全0为黑
		    always @ (posedge clk or negedge rst_n)begin
		        if(!rst_n)begin
		            vga_rgb <= 0;
		        end
		        else if(display_area_ff1)begin
		            if(e_area_ff1)begin
		                vga_rgb <= ~{DATA_W{din}};
		            end
		            else begin
		                vga_rgb <= {DATA_W{1'b1}};
		            end
		        end
		        else begin
		            vga_rgb <= 0;
		        end
		    end
		
		endmodule

13.顶层模块

顶层模块的作用就是将各个模块连接在一起。

代码如下:

		module byjc(
		    clk         ,
		    rst_n       ,
		
		    key_in     ,
		    vsync       ,
		    href        ,
		    din         ,
		
		    xclk        ,
		    pwdn        ,
		    sio_c       ,
		    sio_d       , 
		
		    vga_hys     ,
		    vga_vys     ,
		    vga_rgb      
		);
		
		
		    input         clk     ;
		    input         rst_n   ;
		    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     ;
		    inout         sio_d     ;
		    wire          en_sio_d_w;
		    wire          sio_d_w   ;
		    wire          sio_d_r   ;
		    assign sio_d = en_sio_d_w ? sio_d_w : 1'dz;
		    assign sio_d_r = sio_d;
		
		
		    wire           clk_25m       ;
		    wire           locked        ;
		    wire   [3:0]   key_num       ;
		    wire           en_coms       ;
		    wire   [7:0]   value_gray    ;
		    wire           rdy           ;
		    wire           wen           ;
		    wire           ren           ;
		    wire   [7:0]   addr      ;
		    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   [7:0]   gray_dout     ;
		    wire           gray_dout_vld ;
		    wire           gray_dout_sop ;
		    wire           gray_dout_eop ;
		    wire   [7:0]   gs_dout       ;
		    wire           gs_dout_vld   ;
		    wire           gs_dout_sop   ;
		    wire           gs_dout_eop   ;
		    wire           bit_dout      ;
		    wire           bit_dout_vld  ;
		    wire           bit_dout_sop  ;
		    wire           bit_dout_eop  ;
		    wire           sobel_dout    ;
		    wire           sobel_dout_vld;
		    wire           sobel_dout_sop;
		    wire           sobel_dout_eop;
		    wire   [15:0]  rd_addr       ;
		    wire           rd_en         ;
		    wire           vga_data      ;
		    wire           rd_end        ;
		    wire           wr_end        ;
		    wire           rd_addr_sel   ;
		    wire[3:0]      key_vld       ;
			 wire 				en_vld      ;
		    wire [7:0]           sub_addr;
		    
		
		    pll_ipcore u0(
			    .inclk0 (clk    ),
			    .c0     (xclk   ) 
		    );
		
		
		    key_en u1(
		       .clk    (xclk    ),
		        .rst_n  (rst_n   ),
		       .key_in (key_in  ),
		        .key_vld(key_vld )   
		    );
		
		    ov7670_config u2(
		        .clk         (xclk        ),
		        .rst_n       (rst_n       ),
		        .config_en   (key_vld[1] /*en_vld*/  ),
		        .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 u3(
		        .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 u4(
		        .clk         (xclk             ),
		        .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    ) 
		    );
		
		    rgb565_gray u5(
		        .clk         (xclk             ),
		        .rst_n       (rst_n            ),
		        .din         (cmos_dout        ),
		        .din_vld     (cmos_dout_vld    ),
		        .din_sop     (cmos_dout_sop    ),
		        .din_eop     (cmos_dout_eop    ),
		        .dout        (gray_dout        ),
		        .dout_vld    (gray_dout_vld    ),
		        .dout_sop    (gray_dout_sop    ),
		        .dout_eop    (gray_dout_eop    ) 
		    );
		
		    gs_filter u6(
		        .clk         (xclk             ),
		        .rst_n       (rst_n            ),
		        .din         (gray_dout        ),
		        .din_vld     (gray_dout_vld    ),
		        .din_sop     (gray_dout_sop    ),
		        .din_eop     (gray_dout_eop    ),
		        .dout        (gs_dout          ),
		        .dout_vld    (gs_dout_vld      ),
		        .dout_sop    (gs_dout_sop      ),
		        .dout_eop    (gs_dout_eop      ) 
		    );
		    
		    gray_bit u7(
		        .clk         (xclk             ),
		        .rst_n       (rst_n            ),
		        .value       ( 150             ),
		        .din         (gs_dout          ),
		        .din_vld     (gs_dout_vld      ),
		        .din_sop     (gs_dout_sop      ),
		        .din_eop     (gs_dout_eop      ),
		        .dout        (bit_dout         ),
		        .dout_vld    (bit_dout_vld     ),
		        .dout_sop    (bit_dout_sop     ),
		        .dout_eop    (bit_dout_eop     ) 
		    );
		
		    sobel u8(
		        .clk         (xclk             ),
		        .rst_n       (rst_n            ),
		        .din         (bit_dout         ),
		        .din_vld     (bit_dout_vld     ),
		        .din_sop     (bit_dout_sop     ),
		        .din_eop     (bit_dout_eop     ),
		        .dout        (sobel_dout       ),
		        .dout_vld    (sobel_dout_vld   ),
		        .dout_sop    (sobel_dout_sop   ),
		        .dout_eop    (sobel_dout_eop   )     
		    );
		
		    
		    ram_sel u9(
		        .clk         (xclk             ),
		        .rst_n       (rst_n            ),
		        .din         (sobel_dout       ),
		        .din_vld     (sobel_dout_vld   ),
		        .din_sop     (sobel_dout_sop   ),
		        .din_eop     (sobel_dout_eop   ),
		        .rd_addr     (rd_addr          ),
		        .rd_en       (rd_en            ),
		        .rd_end      (rd_end           ),
		        .rd_addr_sel (rd_addr_sel      ),
		        .dout        (vga_data         ),
		        .wr_end      (wr_end           )         
		    );
		
		    vga_driver u10(
		        .clk         (xclk             ),
		        .rst_n       (rst_n            ),
		        .din         (vga_data         ),
		        .wr_end      (wr_end           ),
		        .vga_hys     (vga_hys          ),
		        .vga_vys     (vga_vys          ),
		        .vga_rgb     (vga_rgb          ),
		        .rd_addr     (rd_addr          ),
		        .rd_en       (rd_en            ),
		        .rd_end      (rd_end           ),
		        .rd_addr_sel (rd_addr_sel      ) 
		    );
		
		endmodule

整个项目编译后,RTL连接如图所示:

FPGA综合项目——边缘检测系统_第22张图片

14.管脚配置及上板实验

编译成功后,还要进行管脚的配置,再搭上硬件,整个边缘检测系统就算成功了,当然中间省略了关键的一步就是波形的验证仿真,因为工作量太大就不一一陈述了。下面分析一下有哪些管脚需要配置,也就是那些是直接连到FPGA上的信号。

FPGA综合项目——边缘检测系统_第23张图片
FPGA综合项目——边缘检测系统_第24张图片

配置完管脚之后再次编译,上板实验得到如下结果:

FPGA综合项目——边缘检测系统_第25张图片

你可能感兴趣的:(FPGA综合项目,fpga)