目录

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

0.此篇总结

参考:《手把手教你学FPGA设计:基于大道至简的至简设计法》-----潘文明,易文兵编著

将会学到的东西:

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

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

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

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

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

⑥移位寄存器IP核的使用

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

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

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

⑩VGA时序

1.系统功能

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

2.模块划分


最后的模块RTL图:

3.PLL

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

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

4.SCCB模块

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

         当配置模块给出写命令时,产生写时序;当配置模块给出读命令时,产生读时序;模块中读到的数据rdata并没有用到;

先来看看SCCB的时序:

在sio_c为1,sio_d拉低时,数据传输开始
在sio_c为1,sio_d拉高时,数据传输结束
数据转换要在sio_c为0时完成

再来看看写时序:

起始位0,表示开始,然后写入‘从机地址(8位)’、‘x’(等待应答)、‘寄存器地址’、‘x’(等待应答)、写入的数据、‘x’等待应答、最后拉高表示传输结束,x全部取1就行,当写入为8位时,共30位数据。

再来看看读时序:

和写时序不一样的,读时序分为两个阶段,起始位、从机地址、x、寄存器地址、x、终止位。这是第一阶段; 起始位、从机地址+1(表示读)、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      ;//25minput               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)beginif(rst_n==1'b0)begincount_sck <= 0;endelse if(add_count_sck)beginif(end_count_sck)begincount_sck <= 0;endelse begincount_sck <= count_sck + 1;endendendassign 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)beginif(rst_n==1'b0)begincount_bit <= 0;endelse if(add_count_bit)beginif(end_count_bit)begincount_bit <= 0;endelse begincount_bit <= count_bit + 1;endendendassign 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)beginif(rst_n==1'b0)begincount_duan <= 0;endelse if(add_count_duan)beginif(end_count_duan)begincount_duan <= 0;endelse begincount_duan <= count_duan + 1;endendendassign add_count_duan = end_count_bit;assign end_count_duan = add_count_duan && count_duan == duan_num-1;//读工作标志//当收到读使能时,flag_r置1always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_r <= 0;endelse if(ren)beginflag_r <= 1;endelse if(end_count_duan)beginflag_r <= 0;endend//写工作标志//当收到写使能时,flag_w置1always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag_w <= 0;endelse if(wen)beginflag_w <= 1;endelse if(end_count_duan)begin flag_w <= 0;endend//读工作时(收到读使能时),sio_d输出 21 bit数据//SCCB时序中,读分为两个阶段,因此bit_num = 21、duan_num = 2;//写工作时(收到写使能时),sio_d输出 30 bit数据//SCCB时序中,写只有一个阶段,因此bit_num = 30、duan_num = 1;always  @(*)beginif(flag_r)beginbit_num = 21;duan_num = 2;endelse if(flag_w)beginbit_num = 30;duan_num = 1;endelse beginbit_num = 1;duan_num = 1;endend//SCCB时钟的高低变化always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginsio_c <= 1;endelse if(sio_c_h2l)beginsio_c <= 0;endelse if(sio_c_l2h)beginsio_c <= 1;endend//一定要结合波形分析,开始和结束占用了两个“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 @ (*)beginif(flag_r)beginout_data <= {1'h0,rd_com,1'h1,sub_addr,1'h1,1'h0,1'h1,9'h0};//最后9位都为0 也就是有效21位endelse if(flag_w)beginout_data <= {1'h0,8'h42,1'h1,sub_addr,1'h1,wdata,1'h1,1'h0,1'h1};//有效30位endelse beginout_data <= 0;endend//区分写地址和读地址assign rd_com = (flag_r && count_duan == 0)? 8'h42 : 8'h43;//写数据使能,发往摄像头//在写数据的时候为1//除了读数据阶段以及复位,其他时间写数据使能都为1//读数据前也要进行写操作,要写进去读的地址等信息always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginen_sio_d_w <= 0;endelse if(ren || wen)beginen_sio_d_w <= 1;endelse if(end_count_duan)beginen_sio_d_w <= 0;endelse if(en_sio_d_w_h2l)beginen_sio_d_w <= 0;endelse if(en_sio_d_w_l2h)beginen_sio_d_w <= 1;endend//设置读数据时,给摄像头写数据的使能为0assign 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)beginif(rst_n==1'b0)beginsio_d_w <= 1;endelse if(out_data_time)beginsio_d_w <= out_data[30-count_bit-1]; //一位一位赋值,共30位endendassign 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)beginif(rst_n==1'b0)beginrdata <= 0;endelse if(rdata_time)beginrdata[17-count_bit] <= sio_d_r;  //一位一位赋值,共8位endendassign rdata_time = flag_r && count_duan==1 && count_bit>=10 && count_bit<18 && add_count_sck && count_sck==SIO_C/4*3-1;//读出数据有效//在读操作下完成最后的阶段置1always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrdata_vld <= 0;endelse if(flag_r && end_count_duan)beginrdata_vld <= 1;endelse beginrdata_vld <= 0;endend//准备信号//不读不写,置1always  @(*)beginif(ren || wen || flag_r || flag_w)beginrdy = 0;endelse beginrdy = 1;endendendmodule

5.摄像头配置模块

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

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

配置模块代码如下:

     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      ;   //50Mhzinput               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)beginif(rst_n==1'b0)beginreg_cnt <= 0;endelse if(add_reg_cnt)beginif(end_reg_cnt)reg_cnt <= 0;elsereg_cnt <= reg_cnt + 1;endendassign 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)beginif(rst_n==1'b0)beginrw_cnt <= 0;endelse if(add_rw_cnt) beginif(end_rw_cnt)rw_cnt <= 0;elserw_cnt <= rw_cnt + 1;endendassign  add_rw_cnt = flag && rdy;assign  end_rw_cnt = add_rw_cnt && rw_cnt==RW_NUM-1;//配置使能过来到配置结束,flag为1always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginflag <= 1'b0;endelse if(config_en)beginflag <= 1'b1;endelse if(end_reg_cnt)beginflag <= 1'b0;endend//cmos_en ,采集使能,配置完为1always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)begincmos_en <= 1'b0;endelse if(end_reg_cnt)begincmos_en <= 1'b1;endend//配置值为add_wdata的低八位always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwdata <= 8'b0;endelse beginwdata <= add_wdata[7:0];endend//地址为add_wdata的高八位always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginaddr <= 8'b0;endelse beginaddr <= add_wdata[15:8];endend//写使能always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginwr_en <= 1'b0;endelse if(add_rw_cnt && rw_cnt==0 && add_wdata[16])beginwr_en <= 1'b1;endelse beginwr_en <= 1'b0;endend//读使能always  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginrd_en <= 1'b0;endelse if(add_rw_cnt && rw_cnt==1 && add_wdata[17])beginrd_en <= 1'b1;endelse beginrd_en <= 1'b0;endendendmodule

此模块有一个输入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)beginif(rst_n==1'b0)begincnt <= 20'b0;endelse if(add_cnt)beginif(end_cnt)cnt <= 20'b0;elsecnt <= cnt + 1'b1;endelse begincnt <= 0;endendassign 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)beginif(rst_n==1'b0)beginflag <= 1'b0;endelse if(end_cnt)beginflag <= 1'b1;endelse if(key_in_ff1==0)beginflag <= 1'b0;endendalways  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginkey_in_ff0 <= 0;key_in_ff1 <= 0;endelse beginkey_in_ff0 <= key_in    ;key_in_ff1 <= key_in_ff0;endendalways  @(posedge clk or negedge rst_n)beginif(rst_n==1'b0)beginkey_vld <= 0;endelse if(end_cnt)beginkey_vld <= key_in_ff1;endelse beginkey_vld <= 0;endendendmodule

6.采集模块

采集要求:分辨率640*480、RGB565格式图像;
注意:①因为采集的时RGB565的数据,所以一个像素点共16位数据,但是摄像头采集一次的数据为8位,所以需要采集两次才为一个像素点。②当行信号为高时,数据才有效。

代码如下:

     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)beginif(!rst_n)begincount_x <= 0;endelse if(add_count_x)beginif(end_count_x)begincount_x <= 0;endelse begincount_x <= count_x + 1;endendend//当数据输入有效且采集的时候+1assign 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)beginif(!rst_n)begincount_y <= 0;endelse if(add_count_y)beginif(end_count_y)begincount_y <= 0;endelse begincount_y <= count_y + 1;endendend//完成一行像素采集要转到下一行时+1assign add_count_y = end_count_x;assign end_count_y = add_count_y && count_y == ROW-1;           //开始采集指示信号,在vsync上升沿时置1always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginflag_capture <= 0;endelse if(flag_capture == 0 && vsync_l2h && en_capture)beginflag_capture <= 1;endelse if(end_count_y)beginflag_capture <= 0;endend//监沿器:鉴定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)beginvsync_ff0 <= 0;endelse beginvsync_ff0 <= vsync;endend//采集数据输出,将采到的din给dout的低8位,下一次always @ (posedge clk or negedge rst_n)begin        if(!rst_n)begindout <= 0;endelse if(din_vld)begindout <= {dout[7:0],din};endend//当前像素点输出有效指示信号always @ (posedge clk or negedge rst_n)begin    if(!rst_n)begindout_vld <= 0;endelse if(flag_dout_vld)begindout_vld <= 1;endelse begindout_vld <= 0;endend//第二个时钟来的时候为第一个像素点assign flag_dout_vld = add_count_x && count_x[0] == 1;//一帧数据开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse if(flag_dout_vld && count_x[10:1] == 0 && count_y == 0)begindout_sop <= 1;endelse begindout_sop <= 0;endend//一帧数据读完always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse if(flag_dout_vld && count_x[10:1] == COL-1 && count_y == ROW-1)begindout_eop <= 1;endelse begindout_eop <= 0;endendendmodule

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


具体代码如下:

     module rgb565_gray(clk         ,   //输入,25Mrst_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)beginif(!rst_n)begindout <= 0;endelse if(din_vld)begindout <= (red*76 + green*150 + blue*30) >> 8;endend//输出有效跟随输入有效,即有一个输入有效就有一个输出有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse begindout_vld <= din_vld;endend//输出开始跟随输入开始,即一帧的第一个像素输入就开始输出一帧第一个像素always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse begindout_sop <= din_sop;endend输出结束跟随输入结束,即一帧的最后一个像素输入就开始输出一帧最后一个像素always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse begindout_eop <= din_eop;endendendmodule

8.高斯滤波模块

高斯滤波采用3 * 3 的掩膜,即三行三列的像素点,我们要依次将一帧图像的所有3 * 3 掩膜的像素点进行滤波。下图的掩模具体的计算公式如下:


其中f(x,y)为灰度像素值,g(x,y)为高斯滤波后的像素值。由公式可知,这与我们在优化显示AMG8833模块中的原理类似(具体可以看此链接AMG8833优化显示),都是以附近像素为原始数据的带有权重的数据处理方法。

我们知道方法之后,怎么在640 * 480 的一帧像素中构建3 * 3的掩膜呢?这里我们需要用到移位寄存器的IP核。

如上图所示为移位寄存器的端口设置,taps是可以设置的在指定位置输出的抽头,将抽头数设置为3,即可输出3行;将抽头间的距离设置为640,则意味着第一个抽头的输出是在寄存器链的第640位,第二个在640 * 2 后输出;而shiftout是寄存器末尾的输出,与最后一个抽头的输出一致,这里我们用不到。

现在我们只是可以做到输出3行,那么怎么输出三列呢?可以用D触发器进行打拍寄存。信号经过D触发器后后慢上一拍(参考D触发器打拍)所以打两拍之后就得到了前两排、前一拍、现在的、共3个数据,所以完成了3列的数据寄存。由此3 * 3的掩模便做出来了。

然后就是高斯算法:

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

模块如图所示:

代码如下:

     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)beginif(!rst_n)begindin_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;endelse begindin_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;endend//D触发器打拍寄存前两拍、前一拍的数据always @ (posedge clk or negedge rst_n)beginif(!rst_n)begintaps0_ff0 <= 0;taps0_ff1 <= 0;taps1_ff0 <= 0;taps1_ff1 <= 0;taps2_ff0 <= 0;taps2_ff1 <= 0;endelse if(din_vld_ff0)begintaps0_ff0 <= taps0;taps0_ff1 <= taps0_ff0;taps1_ff0 <= taps1;taps1_ff1 <= taps1_ff0;taps2_ff0 <= taps2;taps2_ff1 <= taps2_ff0;endend//计算3 * 3 掩模第一行的和always @ (posedge clk or negedge rst_n)beginif(!rst_n)begings_0 <= 0;endelse if(din_vld_ff1)begings_0 <= taps0_ff1 + 2*taps1_ff1 + taps2_ff1;endend//计算3 * 3 掩模第二行的和always @ (posedge clk or negedge rst_n)beginif(!rst_n)begings_1 <= 0;endelse if(din_vld_ff1)begings_1 <= 2*taps0_ff0 + 4*taps1_ff0 + 2*taps2_ff0;endend//计算3 * 3 掩模第三行的和always @ (posedge clk or negedge rst_n)beginif(!rst_n)begings_2 <= 0;endelse if(din_vld_ff1)begings_2 <= taps0 + 2*taps1 + taps2;endend//三行之和除以16,即右移4位always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout <= 0;endelse if(din_vld_ff2)begindout <= (gs_0 + gs_1 + gs_2) >> 4;endend//输出有效,当输出有效时输出有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse if(din_vld_ff2)begindout_vld <= 1;endelse begindout_vld <= 0;endend//一帧输出开始,输入开始时输出开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse if(din_sop_ff2)begindout_sop <= 1;endelse begindout_sop <= 0;endend//一帧输出结束,输入结束时输出结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse if(din_eop_ff2)begindout_eop <= 1;endelse begindout_eop <= 0;endendendmodule

9.二值模块

此模块较为简单,顾名思义就是变为2值数据,也就是0和1。具体的做法为设定一个阈值,大于阈值为1,否则为0。

代码如下:

     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,否则为0always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout <= 0;endelse if(din >= value)begindout <= 1;endelse begindout <= 0;endend//输出有效跟随输入有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse begindout_vld <= din_vld;endend//输出开始跟随输入开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse begindout_sop <= din_sop;endend//输出结束跟随输入结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse begindout_eop <= din_eop;endendendmodule

10.边缘检测模块

与高斯滤波模块一样,此模块同样需要用到3 * 3 的掩模。只不过算法不一样。
具体的算法参考(索贝尔边缘检测算法)


A代表原始图像,Gx、Gy表示索贝尔算子。具体计算式为:


其中其中f(x,y), 表示图像(x,y)点的灰度值,换为模块中的步骤则为:

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

模块图:

代码中的打拍顺序图:

代码如下:

         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)beginif(!rst_n)begindin_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;endelse begindin_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;endend//用D触发器打拍获得前两拍、前一拍的数据always @ (posedge clk or negedge rst_n)beginif(!rst_n)begintaps0_ff0 <= 0;taps0_ff1 <= 0;taps1_ff0 <= 0;taps1_ff1 <= 0;taps2_ff0 <= 0;taps2_ff1 <= 0;endelse if(din_vld_ff0)begintaps0_ff0 <= taps0;taps0_ff1 <= taps0_ff0;taps1_ff0 <= taps1;taps1_ff1 <= taps1_ff0;taps2_ff0 <= taps2;taps2_ff1 <= taps2_ff0;endend//gx按列算always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingx_0 <= 0;endelse if(din_vld_ff1)begingx_0 <= taps0_ff1 + 2*taps1_ff1 + taps2_ff1;endendalways @ (posedge clk or negedge rst_n)beginif(!rst_n)begingx_2 <= 0;endelse if(din_vld_ff1)begingx_2 <= taps0 + 2*taps1 + taps2;endend//gy按行算always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingy_0 <= 0;endelse if(din_vld_ff1)begingy_0 <= taps0_ff1 + 2*taps0_ff0 + taps0;endendalways @ (posedge clk or negedge rst_n)beginif(!rst_n)begingy_2 <= 0;endelse if(din_vld_ff1)begingy_2 <= taps2_ff1 + 2*taps2_ff0 + taps2;endend//gx用绝对值表示always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingx <= 0;endelse if(din_vld_ff2)begingx <= (gx_0>gx_2) ? (gx_0-gx_2) : (gx_2-gx_0);endend//gy用绝对值表示always @ (posedge clk or negedge rst_n)beginif(!rst_n)begingy <= 0;endelse if(din_vld_ff2)begingy <= (gy_0>gy_2) ? (gy_0-gy_2) : (gy_2-gy_0);endend//相加得到Galways @ (posedge clk or negedge rst_n)beginif(!rst_n)beging <= 0;endelse if(din_vld_ff3)beging <= gx + gy;endend//判断边缘并输出always @ (*)begindout = (g>=1) ? 1 : 0;end//输出有效always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_vld <= 0;endelse if(din_vld_ff3)begindout_vld <= 1;endelse begindout_vld <= 0;endend//输出开始always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_sop <= 0;endelse if(din_sop_ff3)begindout_sop <= 1;endelse begindout_sop <= 0;endend//输出结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindout_eop <= 0;endelse if(din_eop_ff3)begindout_eop <= 1;endelse begindout_eop <= 0;endendendmodule

11.存储模块

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

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

ram的ip核:


模块图:

代码如下:

     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 ,   //输入,读切换信号,用来切换RAMdout        ,   //输出,输出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)beginif(!rst_n)begincnt_col <= 0;endelse if(add_cnt_col)beginif(end_cnt_col)cnt_col <= 0;elsecnt_col <= cnt_col + 1;endendassign 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*480always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincnt_row <= 0;endelse if(add_cnt_row)beginif(end_cnt_row)cnt_row <= 0;elsecnt_row <= cnt_row + 1;endendassign 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)beginif(!rst_n)beginwr_data <= 0;endelse if(display_area)beginwr_data <= din;endendassign display_area = cnt_col >= 160 && cnt_col < 480 && cnt_row >= 140 && cnt_row <= 340;//写到ram的地址always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_addr <= 0;endelse if(display_area)beginwr_addr <= (cnt_col-160) + 320*(cnt_row-140);endend//当读和写都完成时,写切换标志信号wr_addr_sel信号取反always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_addr_sel <= 0;endelse if(wr_end && rd_end)beginwr_addr_sel <= ~wr_addr_sel;endend//一帧数据传完,写结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_end <= 0;endelse if(end_cnt_row)beginwr_end <= 1;endelse if(wr_end && rd_end)beginwr_end <= 0;endend//写指示区域信号,在写结束且一帧图像又要开始时置1always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginflag_wr <= 0;endelse if(wr_end == 0 && din_sop)beginflag_wr <= 1;endelse if(end_cnt_row)beginflag_wr <= 0;endend//写到RAM0的使能,在显示区域且切换标志为0且数据有效时开始使能always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_en0 <= 0;endelse if(display_area && wr_addr_sel==0 && din_vld)beginwr_en0 <= 1;endelse beginwr_en0 <= 0;endend//写到RAM1的使能,在显示区域且切换标志为1且数据有效时开始使能always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginwr_en1 <= 0;endelse if(display_area && wr_addr_sel==1 && din_vld)beginwr_en1 <= 1;endelse beginwr_en1 <= 0;endend//例化//说明一下,因为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且读使能有效时置1always @ (*)beginif(rd_en && rd_addr_sel == 0)rd_en0 = 1;else rd_en0 = 0;end//RAM1读使能在读切换信号为1且读使能有效时置1always @ (*)beginif(rd_en && rd_addr_sel == 1)rd_en1 = 1;else rd_en1 = 0;end//输出也得看从哪个RAM读always @ (*)beginif(rd_addr_sel == 0)begindout = q0;endelse if(rd_addr_sel == 1)begindout = q1;endelse begindout = 0;endendendmodule

12.VGA模块

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


具体代码如下:

 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)beginif(!rst_n)begincnt_hys <= 0;endelse if(add_cnt_hys)beginif(end_cnt_hys)cnt_hys <= 0;elsecnt_hys <= cnt_hys + 1;endendassign add_cnt_hys = 1;assign end_cnt_hys = add_cnt_hys && cnt_hys == 800-1;//525行数据,一行的像素点显示完后+1到下一行always @ (posedge clk or negedge rst_n)beginif(!rst_n)begincnt_vys <= 0;endelse if(add_cnt_vys)beginif(end_cnt_vys)cnt_vys <= 0;elsecnt_vys <= cnt_vys + 1;endendassign 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)beginif(!rst_n)beginvga_hys_tmp <= 0;endelse if(add_cnt_hys && cnt_hys == 95)beginvga_hys_tmp <= 1;endelse if(end_cnt_hys)beginvga_hys_tmp <= 0;endend//场同步信号在第二个vys时钟拉高always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_vys_tmp <= 0;endelse if(add_cnt_vys && cnt_vys == 1)beginvga_vys_tmp <= 1;endelse if(end_cnt_vys)beginvga_vys_tmp <= 0;endend//vga_hys异步处理//vga_vys异步处理//打两拍always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_hys_tmp_ff0 <= 0;vga_vys_tmp_ff0 <= 0;vga_hys <= 0;vga_vys <= 0;endelse beginvga_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;endend//显示区域always @ (*)begindisplay_area = cnt_hys >= 141 && cnt_hys < (141+646) && cnt_vys >= 32 && cnt_vys < (32+484);end//有效边缘区域always @ (*)begine_area = cnt_hys >= x0 && cnt_hys < x1 && cnt_vys >= y0 && cnt_vys < y1;end//对显示区域信号进行打拍always @ (posedge clk or negedge rst_n)beginif(!rst_n)begindisplay_area_ff0 <= 0;e_area_ff0 <= 0;display_area_ff1 <= 0;e_area_ff1 <= 0;endelse begindisplay_area_ff0 <= display_area;e_area_ff0 <= e_area;display_area_ff1 <= display_area_ff0;e_area_ff1 <= e_area_ff0;endend//有效区域 320*200 在正中间assign x0 = 141 + (323 - COL_2);    //304assign x1 = 141 + (323 + COL_2);    //624assign y0 = 32 + (242 - ROW_2);     //174assign y1 = 32 + (242 + ROW_2);     //374//读地址的计算//RAM中地址按位数一位一位存储//地址信号16位,能存65536个always @ (*)beginx = cnt_hys - x0;endalways @ (*)beginy = cnt_vys - y0;endalways @ (*)beginrd_addr = COL*y + x;end//读完写完一帧,换RAM继续读写always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginrd_addr_sel <= 1;endelse if(rd_end && wr_end)beginrd_addr_sel <= ~rd_addr_sel;endend//当最后一行显示完,读结束always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginrd_end <= 0;endelse if(end_cnt_vys)beginrd_end <= 1;endelse beginrd_end <= 0;endend//在有效显示区域 320*200 时 读开始always @ (*)beginrd_en = e_area;end//rgb565 全1为白 全0为黑always @ (posedge clk or negedge rst_n)beginif(!rst_n)beginvga_rgb <= 0;endelse if(display_area_ff1)beginif(e_area_ff1)beginvga_rgb <= ~{DATA_W{din}};endelse beginvga_rgb <= {DATA_W{1'b1}};endendelse beginvga_rgb <= 0;endendendmodule

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连接如图所示:

14.管脚配置及上板实验

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


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

15. 后记:资源使用情况


片上的内存用的比较多,可以尝试采用sdram来进行存储

总的基本逻辑单元用了1071个,其中组合逻辑占973个,时序逻辑占395个


纯组合逻辑676个基本逻辑单元

纯时序逻辑98个基本逻辑单元

组合时序联合297个基本逻辑单元

组合/时序 = 937/395 = 2.37

FPGA综合项目——图像边缘检测系统相关推荐

  1. 基于FPGA的实时图像边缘检测系统设计(上)

    今天给大侠带来基于FPGA的实时图像边缘检测系统设计,由于篇幅较长,分三篇.今天带来第一篇,上篇,话不多说,上货. 导读 随着科学技术的高速发展,FPGA在系统结构上为数字图像处理带来了新的契机.图像 ...

  2. 微信小程序开发 | 综合项目-点餐系统

    综合项目-点餐系统 8.1 开发前准备 8.1.1 项目展示 8.1.2 项目分析 8.1.3 项目初始化 8.1.4 封装网络请求 8.2 [任务1]商家首页 8.2.1 任务分析 8.2.2 焦点 ...

  3. 《MySQL数据操作与查询》- 综合项目 - 航空售票系统

    Mysql & SqlServer综合项目需求 1.系统整体功能 系统需支持以下功能: 维护客户信息.航班信息和票务信息 支持客户按多种条件组合查询航班信息和票务信息 支持客户根据票务信息订购 ...

  4. sobel算子原理_「学术论文」基于Sobel算法图像边缘检测的FPGA实现

    摘要: 针对嵌入式软件无法满足数字图像实时处理速度问题,提出用硬件加速器的思想,通过FPGA实现Sobel边缘检测算法.通过乒乓操作.并行处理数据和流水线设计,大大提高算法的处理速度.采用模块的硬件设 ...

  5. 基于小波的图像边缘检测,小波变换边缘检测原理

    1.什么是"小波神经网络"?能干什么用呀 小波神经网络(Wavelet Neural Network, WNN)是在小波分析研究获得突破的基础上提出的一种人工神经网络.它是基于小波 ...

  6. FPGA 开发项目参考

    [机器人专题]电子导盲犬 清华大学 ZED+小车套件 基于Zynq的伺服机器人设计 重庆大学 ZED+小车套件 [机器人专题]家庭安防智能机器人 湖南大学 ZED+小车套件 机器人专题-智能服务生机器 ...

  7. 树莓派python交互界面实例_树莓派综合项目2:智能小车(二)tkinter图形界面控制...

    一.介绍 树莓派综合项目2:智能小车(一)四轮驱动中,实现了代码输入对四个电机的简单控制,本章将使用Python 的图形开发界面的库--Tkinter 模块(Tk 接口),编写本地运行的图形界面,控制 ...

  8. 一、FPGA Cyclone Ⅳ OV5640图像实时采集系统设计

    一.FPGA Cyclone Ⅳ OV5640图像实时采集系统设计 1.系统框架 2.摄像头配置模块 3.图像数据拼接模块 4.SDRAM操作模块 5.乒乓缓存模块 6.VGA驱动模块 7.顶层模块 ...

  9. 综合项目——智能分类垃圾桶

    综合项目--智能分类垃圾桶 一.讲在前面 之前做过许多项目,也写了许多工程代码,但是一直没能好好整理,导致我每做一个新的项目就跟重头开始似的,为了更好进行代码资料的管理,我决定开辟这个博客,作为我资料 ...

最新文章

  1. max hit in personalization - CRM My Opportunity搜索的实现
  2. 工业以太网交换机在实际应用中的优势
  3. 解决mac使用svn: E170000: Unrecognized URL scheme for h
  4. 3S基础知识:用MapX快速开发
  5. 零基础自学SQL课程 | SQL中的日期函数大全
  6. >> 读书记录_2015~2020
  7. Ribbon负载均衡分析
  8. 【正点原子Linux连载】第二十章 V4L2摄像头应用编程-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1
  9. PMP-项目成本管理
  10. Dava基础Day17
  11. 100个高频Spring面试题,助你一臂之力
  12. 从零开始学习3D可视化之摄像机
  13. 总结软连接与硬连接的区别
  14. java 中输入字符的方法(顺便判断元音辅音)
  15. 在线导入Excel自定义报表,助力快速攻克金融系统开发难点
  16. 面试字节跳动测试岗位一般问什么测试点_字节跳动面试问题集合
  17. WORD 软回车(Shift + Enter)替换成硬回车(Enter)
  18. 给有兴趣、有责任要讲课、分享的朋友推荐两本书
  19. 你的NAS被勒索病毒攻击了怎么办?
  20. 网店版重生系列:都是Spring配置中自动注入惹的祸

热门文章

  1. 85人教版高中英语第一册第十五课 NAPOLEON'S THREE QUESTIONS
  2. [附源码]SSM计算机毕业设计小区物业管理系统论文JAVA
  3. FS2712C筋膜枪单片机方案MCU方案原理图
  4. shopify cli 安装记录
  5. 【Unity3D入门教程】Unity3D界面介绍及游戏对象基本操作
  6. nginx+tomcat集群配置
  7. 【C语言】从入门到入土(入门篇)
  8. Win11系统无法创建pin密码的解决方法教学
  9. jquery根据属性查询元素
  10. 网友言:读研3年后工作“啪”的没了 2023年计算机考研意义何在