【FPGA】基于OV5640的 图像边沿检测

目录

一  项目结构

1.1 设计思路

 1.2 设计流程

 二  接口设计

2.1 摄像头配置模块

2.2 IIC_master 模块

之后就进行数据采集

2.3 采集数据模块

2.4 灰度转化 

2.5 高斯滤波

2.7 二值化

 2.8 Sobel边缘检测

2.9 SDRAM乒乓缓存

 2.10 VGA显示

三 代码设计


一  项目结构

1.1 设计思路

基于 OV5640的 图像边沿检测,采集的图像大小是 1280 * 720 ,采用VGA接口进行显示

项目模块设计:

 1.2 设计流程

  •  本次实验做的是基于OV5640的摄像头数据采集实验,在上电等待20ms后,利用SCCB协议(这里我用的IIC协议)进行摄像头的配置,配置完254个寄存器后,会输出一个配置完成有效信号给摄像头采集模块。
  • 需要接收摄像头配置完成的信号,当场同步信号拉低后,且行参考信号有效时进行数据的采集。但是摄像头的数据是把16位RGB拆分为高八位和低八位发送的,我们需要通过移位+位拼接的方式把两个8bit数据合并成16bit数据输出,同时为了SDRAM模块更好的识别帧头和帧尾,在图像的第一个像素点以及最后一个像素点的时候分别拉高sopeop信号,其余像素点拉低。

  • 数据处理,包括图像的灰度转化、高斯滤波、二值化,Sobel边沿检测等。具体实现后续讲解。

  • 乒乓操作主要⽤于控制数据流,在此项⽬中主要体现为先写SDRAM bank1的数据,同时读SDRAM bank3的数据,当两块bank的数据读写完毕后,切换操作为读bank1的数据,写bank3的数据,这样可以保持数据为完整的⼀帧,使显⽰屏帧与帧之间切换瞬间完成。

  • 显示,利用VGA接口将数据显示到显示屏上。

 二  接口设计

2.1 摄像头配置模块

module cmos_config(
    input               clk         ,
    input               rst_n       ,
    //i2c_master
    output              req         ,//请求
    output      [3:0]   cmd         ,//命令
    output      [7:0]   dout        ,//数据
    input               done        ,//应答
    
    output              config_done     //配置完成信号
);

摄像头配置模块比较简单,之前设计过利用IIC协议来读写EEPROM,摄像头的控制模块还比EEPROM控制模块简单,只涉及到了向摄像头写入数据,即配置它的功能。

配置流程:

  • 主要采用状态机计数器的方式来设计配置模块;
  • 当上电之后计数20ms,之后就可以进行摄像头的配置,有一个配置完成信号,当配置完254个寄存器后,配置信号有效。
  • 配置模块主要就是通过IIC_master模块向摄像头里面写入数据,完成配置。
  • 发送数据是以任务的方式发请求、命令和数据。

2.2 IIC_master 模块

接口模块的作用就只是用来进行发数据或读数据,这里没有用到读数据。

module i2c_master(
    input               clk         ,
    input               rst_n       ,

    input               req         ,
    input       [3:0]   cmd         ,
    input       [7:0]   din         ,

    output      [7:0]   dout        ,
    output              done        ,
    output              slave_ack   ,

    output              i2c_scl     ,
    input               i2c_sda_i   ,
    output              i2c_sda_o   ,
    output              i2c_sda_oe     
    );

设计接口模块

  •  也是用状态机加计数器来设计接口模块,状态机的设计就是根据IIC协议的读写时序来设计的,具体时序之前的博客有写过。
  • 里面很重要的一点就是时钟设计,我用的传输速率是 200k bit/s,因此一个IIC时钟周期    SCL = 50 M / 200 K = 250 次系统时钟周期,需要一个计数器,来记250个系统时钟周期,通过IIC协议收发数据,都是根据SCL来进行的。

 SCL设计:

//scl
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            scl <= 1'b1;
        end
        else if(idle2start | idle2write | idle2read)begin//开始发送时,拉低
            scl <= 1'b0;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF-1)begin 
            scl <= 1'b1;
        end 
        else if(end_cnt_scl && ~stop2idle)begin 
            scl <= 1'b0;
        end 
    end

数据发送:

//sda_out
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sda_out <= 1'b1;
        end
        else if(state_c == START)begin          //发起始位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉高sda总线
                sda_out <= 1'b1;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉低sda总线 
                sda_out <= 1'b0;                //保证从机能检测到起始位
            end 
        end 
        else if(state_c == WRITE && cnt_scl == `LOW_HLAF)begin  //scl低电平时发送数据   并串转换
            sda_out <= tx_data[7-cnt_bit];      
        end 
        else if(state_c == SACK && cnt_scl == `LOW_HLAF)begin  //发应答位
            sda_out <= (command&`CMD_STOP)?1'b1:1'b0;
        end 
        else if(state_c == STOP)begin //发停止位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉低sda总线
                sda_out <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉高sda总线 
                sda_out <= 1'b1;                //保证从机能检测到停止位
            end 
        end 
    end

//sda_out_en  总线输出数据使能
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sda_out_en <= 1'b0;
        end
        else if(idle2start | idle2write | read2sack | rack2stop)begin
            sda_out_en <= 1'b1;
        end
        else if(idle2read | start2read | write2rack | stop2idle)begin 
            sda_out_en <= 1'b0;
        end 
    end

具体实现发送就是入上图,这里不多阐述了。

配置的顶层模块接口:

module cmos_top(
    input           clk     ,        //xclk  24M
    input           rst_n   ,
    
    output          scl     ,        //scl
    inout           sda     ,        //sda
    output          pwdn    ,        
    output          reset   ,

    output          cfg_done        //配置完成信号

);

之后就进行数据采集

2.3 采集数据模块

接口设计:

module capture(
    input           clk     ,//像素时钟 摄像头输出的pclk
    input           rst_n   ,

    input           enable  ,  //采集使能 配置完成
    input           vsync   ,//摄像头场同步信号
    input           href    ,//摄像头行参考信号
    input   [7:0]   din     ,//摄像头像素字节

    output  [15:0]  dout    ,//像素数据
    output          dout_sop,//包文头 一帧图像第一个像素点
    output          dout_eop,//包文尾 一帧图像最后一个像素点
    output          dout_vld //像素数据有效
);

设计思路:

  • 先对场同步信号进行同步打拍,然后检测下降沿
//vsync同步打拍
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            vsync_r <= 2'b00;
        end
        else begin
            vsync_r <= {vsync_r[0],vsync};
        end
    end

    assign vsync_nedge = vsync_r[1] & ~vsync_r[0];  //检测下降沿

检测到下降沿,且接收到摄像头配置完成信号,采集数据标志拉高,采集完一帧图像,标志拉低。之后进行下一帧图像的采集。

  • 采集数据,采集数据标志拉高行参考信号有效时,进行数据采集,这里进行了拼接。摄像头的数据是把16位RGB拆分为高八位和低八位发送的,我们需要通过移位+位拼接的方式把两个8bit数据合并成16bit数据输出。
//data
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            data <= 0;
        end
        else begin
            data <= {data[7:0],din};//左移
            //data <= 16'b1101_1010_1111_0111;//16'hdaf7
        end
    end
  • SOP、EOP、数据有效信号
//data_sop
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            data_sop <= 1'b0;
            data_eop <= 1'b0;
            data_vld <= 1'b0;
        end
        else begin
            data_sop <= add_cnt_h && cnt_h == 2-1 && cnt_v == 0;
            data_eop <= end_cnt_v;
            data_vld <= add_cnt_h && cnt_h[0] == 1'b1;
        end
    end

sop就是一帧图像的第一个像素点,eop是一帧图像的最后一个像素点,数据有效是cnt_h[0] == 1'b1,由于进行了拼接,低位始终是1,因此数据有效。

2.4 灰度转化 

接口设计:

module rgb2gray(

    input           clk         ,
    input           rst_n       ,
    
    input           din_sop     ,
    input           din_eop     ,
    input           din_vld     ,
    input   [15:0]  din         ,//RGB565

    output          dout_sop    ,
    output          dout_eop    ,
    output          dout_vld    ,
    output  [7:0]   dout         //灰度输出
);

原理 :

  • 人眼对RGB颜色的敏感度不同:对绿色最敏感,所以权值最高。对蓝色不敏感,权值最低。在C语言或者Python等高级语言中,权值是0.2990.5870.114,也就是说都是小数,而Verilog不支持小数运算,所以只能先消除小数点来得到乘积,最后再通过移位缩小至近似原来的大小。

  • 由于我们摄像头采集的数据时RGB565格式的,需要转化为RGB888格式的图像,进行带补偿的低三位拓展位宽,然后采用加权法进行彩色图片转灰度,用了一个心理学公式。

  • 心理学公式:将三通道的彩色图像转化位单通道的八位输出

Gray = R*0.299 + G*0.587 + B*0.114
  • 我采用的是将权值扩大1024倍之后再进行加权求和,最后再右移10位.
Gray = (R*306 + G*601 + B*117) >> 10

设计思路:

  • 首先将输入的16位 的彩色图像,转化位24位的图像,即将RGB565 格式转化为 RGB888格式的图像,这里有两种方式的扩展位宽,第一种是采用带补偿的拓展位宽;第二种是采用不带补偿的拓展位宽。
//扩展    RGB565-->RGB888
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            data_r <= 0;
            data_g <= 0;
            data_b <= 0;
        end
        else if(din_vld)begin
            data_r <= {din[15:11],din[13:11]};      //带补偿的  r5,r4,r3,r2,r1, r3,r2,r1
            data_g <= {din[10:5],din[6:5]}   ;      //补偿低三位
            data_b <= {din[4:0],din[2:0]}    ;
            /*
            data_r <= {din[15:11],3'd0};
            data_g <= {din[10:5],2'd0} ;
            data_b <= {din[4:0],3'd0}  ;
            */
        end
    end

转化完之后就可以进行加权求和

  • 这里用到的是10位精度的扩大,人眼对绿色的敏感度最高,权重为 0.587,换成整数是10位的位宽。
//加权   
    //第一拍
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            pixel_r <= 0;
            pixel_g <= 0;
            pixel_b <= 0;
        end
        else if(vld[0])begin
            pixel_r <= data_r * 306;
            pixel_g <= data_g * 601;
            pixel_b <= data_b * 117;
        end
    end
    
    //第二拍
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            pixel <= 0;
        end
        else if(vld[1])begin
            pixel <= pixel_r + pixel_g + pixel_b;
        end
    end


    assign dout = pixel[10 +:8];    //从第十位开始取数据,取八位。

 到这里就已经灰度转化完毕,之后进行高斯滤波。

2.5 高斯滤波

接口设计:

module gauss_filter(
    input           clk         ,
    input           rst_n       ,
    
    input           din_sop     ,
    input           din_eop     ,
    input           din_vld     ,
    input   [7:0]   din         ,//灰度输入

    output          dout_sop    ,
    output          dout_eop    ,
    output          dout_vld    ,
    output  [7:0]   dout         //灰度输出     

);

设计思路:

  • 原理: 高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。

  • 因为高斯滤波涉及到卷积核对二维图像的一个卷积,所以我们首先需要一个移位寄存器来将串行数据变为并行数据。这里调用一个IP核即可:  

    wire    [7:0]   taps0       ; 
    wire    [7:0]   taps1       ; 
    wire    [7:0]   taps2       ; 
//缓存3行数据
    gs_line_buf	gs_line_buf_inst (
	.aclr       (~rst_n     ),
	.clken      (din_vld    ),
	.clock      (clk        ),
    /*input*/
	.shiftin    (din        ),
	.shiftout   (           ),
    /*output*/
	.taps0x     (taps0      ),
	.taps1x     (taps1      ),
	.taps2x     (taps2      )
	);

卷积:

    reg     [7:0]   line0_0     ;
    reg     [7:0]   line0_1     ;
    reg     [7:0]   line0_2     ;

    reg     [7:0]   line1_0     ;
    reg     [7:0]   line1_1     ;
    reg     [7:0]   line1_2     ;

    reg     [7:0]   line2_0     ;
    reg     [7:0]   line2_1     ;
    reg     [7:0]   line2_2     ;

    reg     [9:0]   sum_0       ;//第0行加权和 
    reg     [9:0]   sum_1       ;//第1行加权和
    reg     [9:0]   sum_2       ;//第2行加权和
    
	reg     [7:0]  sum         ;//三行的加权和
  • 首先是参数,lineX_Y表示第X第Y行,我们需要创造一个3*3的矩阵寄存器,所以这里共需要9个寄存器,并且需要求的加权和,所以每一行再分配一个sum,最后再分配一个总sum;
/*
高斯滤波系数,加权平均
	1	2	1
	2	4	2
	1	2	1
*/
	always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            line0_0 <= 0;line0_1 <= 0;line0_2 <= 0;      
            line1_0 <= 0;line1_1 <= 0;line1_2 <= 0;         
            line2_0 <= 0;line2_1 <= 0;line2_2 <= 0;
        end
        else if(vld[0])begin
            line0_0 <= taps0;line0_1 <= line0_0;line0_2 <= line0_1;           
            line1_0 <= taps1;line1_1 <= line1_0;line1_2 <= line1_1;       
            line2_0 <= taps2;line2_1 <= line2_0;line2_2 <= line2_1;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sum_0 <= 0;
            sum_1 <= 0;
            sum_2 <= 0;
        end
        else if(vld[1])begin
            sum_0 <= {2'd0,line0_0} + {1'd0,line0_1,1'd0} + {2'd0,line0_2};
            sum_1 <= {1'd0,line1_0,1'd0} + {line1_1,2'd0} + {1'd0,line1_2,1'd0};
            sum_2 <= {2'd0,line2_0} + {1'd0,line2_1,1'd0} + {2'd0,line2_2};
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sum <= 0;
        end
        else if(vld[2])begin
            sum <= (sum_0 + sum_1 + sum_2)>>4;
        end
    end
  1. 第一个Always完成的是9个八位的寄存器对图像数据的存储;
  2. 第二个Always通过位拼接来实现与卷积核的乘法(直接乘也没问题,只是这样写能显示出老练的技法)与每行的求和;
  3. 第三个Always就是求总和,这里也和灰度寄存器一样使用了移位运算符,这也是因为Verilog不能进行小数运算,实际运算的卷积核内的参数都是扩大了16倍(2^4)。

 克服了边界效应,相对于均值滤波平滑效果更柔和,边缘保留的更好。用一个值相邻像素的加权平均灰度值代替原来的值 。

2.7 二值化

  • 二值化(英语:Binarization)是图像分割的一种最简单的方法。二值化可以把灰度图像转换成二值图像。把大于某个临界灰度值的像素灰度设为灰度极大值,把小于这个值的像素灰度设为灰度极小值,从而实现二值化。(来源维基百科)

图像二值化( Image Binarization)就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。
在数字图像处理中,二值图像占有非常重要的地位,图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。

将256个亮度等级的灰度图像通过适当的阈值选取而获得仍然可以反映图像整体和局部特征的二值化图像。在数字图像处理中,二值图像占有非常重要的地位,首先,图像的二值化有利于图像的进一步处理,使图像变得简单,而且数据量减小,能凸显出感兴趣的目标的轮廓。其次,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像。
所有灰度大于或等于阈值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。
 

接口设计:

module gray2bin(
    input           clk         ,
    input           rst_n       ,
    
    input           din_sop     ,
    input           din_eop     ,
    input           din_vld     ,
    input   [7:0]   din         ,//灰度输入

    output          dout_sop    ,
    output          dout_eop    ,
    output          dout_vld    ,
    output          dout         //二值输出  

);

设计思路:

将经过高斯滤波后的灰度数据输入,进行和设定的 阈值 进行比较,阈值是我们自己设定的,如果大于这个阈值就是 1,即为白色,小于这个阈值为 0 ,即为黑色,

always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            binary     <= 0 ;
            binary_sop <= 0 ;
            binary_eop <= 0 ;
            binary_vld <= 0 ;
        end
        else begin
            binary     <= din>100 ;//二值化阈值可自定义
            binary_sop <= din_sop ;
            binary_eop <= din_eop ;
            binary_vld <= din_vld ;
        end
    end

之后就可以进行Sobel边缘像素点检测。

 2.8 Sobel边缘检测

接口设计:

module sobel(
    input           clk     ,
    input           rst_n   ,
    input           din     ,//输入二值图像
    input           din_sop ,
    input           din_eop ,
    input           din_vld ,

    output          dout    ,
    output          dout_sop,
    output          dout_eop,
    output          dout_vld 
);

设计思路:

  • 边缘检测是是特征提取中的一个研究领域,它能边缘检测出数字图像中亮度变化明显的点,减少数据量,并剔除不相 关的信息,最终保留图像重要的结构属性。同时,Sobel 边缘检测通常带有方向性,可以只检测竖直边缘或垂直边缘 或都检测。

  • Sobel算子提供了水平方向核垂直方向的滤波模板。

  • Gx 和 Gy 如下

 梯度计算:

 

assign dout = g >= 3;//阈值假设为3 当某一个像素点的梯度值大于3,认为其是一个边缘点

 之后要将输出数据扩展成16位的数据,再存到SDRAM里面。

2.9 SDRAM乒乓缓存

这里SDRAM接口模块我是直接调用的IP核,写了一个控制模块,用来向SDRAM里卖弄突发写或者突发读,由于涉及到跨时钟域的数据传输,从摄像头采集到的数据写到SDRAM里面是慢时钟域到快时钟域,从SDRAM里面读取数据显示到屏幕上是快时钟域到慢时钟域。因此设计了两个异步FIFO用来缓存数据。

  • 乒乓操作主要⽤于控制数据流,在此项⽬中主要体现为先写SDRAM bank1的数据,同时读SDRAM bank3的数据,当两块bank的数据读写完毕后,切换操作为读bank1的数据,写bank3的数据,这样可以保持数据为完整的⼀帧,使显⽰屏帧与帧之间切换瞬间完成。

保证读取到的数据是一帧的效果,如果一帧数据没有读完,就开始写入会丢帧。

设计思路:

  1. wfifo设计,慢时钟域写到快时钟域读
    wrfifo	wrfifo_inst (
    	.aclr   (~rst_n     ),
    	.data   (wfifo_data ),
    	.rdclk  (clk        ),
    	.rdreq  (wfifo_rdreq),
    	.wrclk  (clk_in     ),
    	.wrreq  (wfifo_wrreq),
    	.q      (wfifo_q    ),
    	.rdempty(wfifo_empty),
    	.rdusedw(wfifo_usedw),
    	.wrfull (wfifo_full )
    	);
    
        assign wfifo_data = {din_eop,din_sop,din};
        assign wfifo_wrreq = ~wfifo_full & din_vld & ((~wr_finish_r[1] & din_sop) ||wr_data_flag);
        assign wfifo_rdreq = state_c == WRITE && ~avs_waitrequest;
    
    数据进行拼接,将eop,sop和 数据拼接到一起,用于判断能不能写入
  2. 读写仲裁优先级
    /************************读写优先级仲裁*****************************/
    //rd_flag     ;//读请求标志
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                rd_flag <= 0;
            end 
            else if(rfifo_usedw <= `RD_LT)begin   
                rd_flag <= 1'b1;
            end 
            else if(rfifo_usedw > `RD_UT)begin 
                rd_flag <= 1'b0;
            end 
        end
    
    //wr_flag     ;//写请求标志
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                wr_flag <= 0;
            end 
            else if(wfifo_usedw >= `USER_BL)begin 
                wr_flag <= 1'b1;
            end 
            else begin 
                wr_flag <= 1'b0;
            end 
        end
    
    //flag_sel    ;//标记上一次操作
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                flag_sel <= 0;
            end 
            else if(read2done)begin 
                flag_sel <= 1;
            end 
            else if(write2done)begin 
                flag_sel <= 0;
            end 
        end
    
    //prior_flag  ;//优先级标志 0:写优先级高   1:读优先级高     仲裁读、写的优先级
        always @(posedge clk or negedge rst_n)begin 
            if(!rst_n)begin
                prior_flag <= 0;
            end 
            else if(wr_flag && (flag_sel || (~flag_sel && ~rd_flag)))begin   //突发写优先级高
                prior_flag <= 1'b0;
            end 
            else if(rd_flag && (~flag_sel || (flag_sel && ~wr_flag)))begin   //突发读优先级高
                prior_flag <= 1'b1;
            end 
        end
    
    /******************************************************************/    
    

    读写不能同时进行,只有dq一组数据线

  3. 地址设计
    // wr_addr   rd_addr
        always @(posedge clk or negedge rst_n) begin 
            if (rst_n==0) begin
                wr_addr <= 0; 
            end
            else if(add_wr_addr) begin
                if(end_wr_addr)
                    wr_addr <= 0; 
                else
                    wr_addr <= wr_addr+1 ;
           end
        end
        assign add_wr_addr = (state_c == WRITE) && ~avs_waitrequest;
        assign end_wr_addr = add_wr_addr  && wr_addr == `BURST_MAX-1 ;
        
        always @(posedge clk or negedge rst_n) begin 
            if (rst_n==0) begin
                rd_addr <= 0; 
            end
            else if(add_rd_addr) begin
                if(end_rd_addr)
                    rd_addr <= 0; 
                else
                    rd_addr <= rd_addr+1 ;
           end
        end
        assign add_rd_addr = (state_c == READ) && ~avs_waitrequest;
        assign end_rd_addr = add_rd_addr  && rd_addr == `BURST_MAX-1;

    地址设计是根据读写状态来改变的,突发写或突发读都是一次突发多少个数据。

  4. 乒乓操作,写第一块bank,读第三块bank,这样子能保证读出来的是连续的一帧图像

    //change bank
        always  @(posedge clk or negedge rst_n)begin
            if(~rst_n)begin
                wr_bank <= 2'b00;
                rd_bank <= 2'b11;
            end
            else if(change_bank)begin
                wr_bank <= ~wr_bank;
                rd_bank <= ~rd_bank;
            end
        end
    
    //wr_finish     一帧数据全部写到SDRAM
        always  @(posedge clk or negedge rst_n)begin
            if(~rst_n)begin
                wr_finish <= 1'b0;
            end
            else if(~wr_finish & wfifo_q[17])begin  //写完  从wrfifo读出eop
                wr_finish <= 1'b1;
            end
            else if(wr_finish && end_rd_addr)begin  //读完
                wr_finish <= 1'b0;
            end
        end
    
    //change_bank ;//切换bank 
        always  @(posedge clk or negedge rst_n)begin
            if(~rst_n)begin
                change_bank <= 1'b0;
            end
            else begin
                change_bank <= wr_finish && end_rd_addr;
            end
        end
    
    /*********************** wrfifo 写数据   ************************/
    //控制像素数据帧 写入 或 丢帧
    
        always  @(posedge clk_in or negedge rst_n)begin
            if(~rst_n)begin
                wr_data_flag <= 1'b0;
            end 
            else if(~wr_data_flag & ~wr_finish_r[1] & din_sop)begin//可以向wrfifo写数据
                wr_data_flag <= 1'b1;
            end
            else if(/*wr_finish_r[1] && din_sop*/wr_data_flag & din_eop)begin//不可以向wrfifo写入数据
                wr_data_flag <= 1'b0;
            end
        end
    
        always  @(posedge clk_in or negedge rst_n)begin //把wr_finish从wrfifo的读侧同步到写侧
            if(~rst_n)begin
                wr_finish_r <= 0;
            end
            else begin
                wr_finish_r <= {wr_finish_r[0],wr_finish};
            end
        end
    
    /****************************************************************/

  5. rdfifo设计 ,快时钟写到慢时钟读
    rdfifo u_rdfifo(
    	.aclr       (~rst_n     ),
    	.data       (rfifo_data ),
    	.rdclk      (clk_out    ),
    	.rdreq      (rfifo_rdreq),
    	.wrclk      (clk        ),
    	.wrreq      (rfifo_wrreq),
    	.q          (rfifo_q    ), 
    	.rdempty    (rfifo_empty),
    	.wrfull     (rfifo_full ),
    	.wrusedw    (rfifo_usedw)
    );
    
        assign rfifo_data = avs_rddata;
        assign rfifo_wrreq = ~rfifo_full & avs_rddata_vld;
        assign rfifo_rdreq = ~rfifo_empty & rdreq;      //rfifo非空且vga显示是行有效和场有效

输出数据:

 assign dout       = rd_data;

    assign dout_vld   = rd_data_vld;

    assign avm_wrdata = wfifo_q[15:0];

    assign avm_write  = ~(state_c == WRITE && ~avs_waitrequest);

    assign avm_read   = ~(state_c == READ && ~avs_waitrequest);

    assign avm_addr   = (state_c == WRITE)?{wr_bank[1],wr_addr[21:9],wr_bank[0],wr_addr[8:0]}

                       :((state_c == READ)?{rd_bank[1],rd_addr[21:9],rd_bank[0],rd_addr[8:0]}

                       :0);

 2.10 VGA显示

接口设计:

module   vga_interface (    //1280*720
    input           clk      ,//75MHz
    input           rst_n    ,
    input   [15:0]  din      ,
    input           din_vld  ,
    output          rdy      ,
    output  [15:0]  vga_rgb  ,
    output          vga_hsync,
    output          vga_vsync
);

设计思路:

  1. 计数器来设计行有效和场有效区域
  2. 在行场有效显示区域进行输出数据
    //数据输出
        always @(posedge clk or negedge rst_n) begin
            if (!rst_n) begin
                rgb <= 0;
            end
            else if (display_vld) begin
                rgb <= vga_data ;
            end
            else begin
                rgb <= rgb;
            end
        end
        assign display_vld = (cnt_h_addr > H_SYNC + H_BLACK - 1) && (cnt_h_addr < H_TOTAL - H_FRONT -1)
        && (add_v_addr > V_SYNC + V_BLACK - 1) && (add_v_addr < V_TOTAL - V_FRONT - 1);
    
        assign  rgb_r = rgb[15:11];
        assign  rgb_g = rgb[10:05];
        assign  rgb_b = rgb[04:00];
    

    用一个fifo来缓存数据

    //FIFO例化
        vga_buf u_buf(
    	.aclr       (~rst_n     ),
    	.clock      (clk        ),
    	.data       (din        ),
    	.rdreq      (rdreq      ),
    	.wrreq      (wrreq      ),
    	.empty      (empty      ),
    	.full       (full       ),
    	.q          (q_out      ),
    	.usedw      (usedw      )
    );
    
        assign wrreq = ~full && din_vld;
        assign rdreq = ~empty && display_vld;

三 代码设计

链接:https://pan.baidu.com/s/1scHW8ilQwdOKbUqN1aCuPg?pwd=gvj0
提取码:gvj0
--来自百度网盘超级会员V1的分享

猜你喜欢

转载自blog.csdn.net/qq_52445967/article/details/126317509
今日推荐