(八)零基础FPGA图像处理——HDMI方块移动实验

(八)零基础FPGA图像处理——HDMI方块移动实验

0 致读者

此篇为专栏 《Ryan的FPGA学习笔记》 的第八篇,记录我的学习 FPGA 的一些开发过程和心得感悟,刚接触 FPGA 的朋友们可以先去此专栏置顶《FPGA零基础入门学习路线》来做最基础的扫盲。

本篇内容基于笔者实际开发过程和正点原子资料撰写,将会详细讲解此FPGA实验的全流程,诚挚地欢迎各位读者在评论区或者私信我交流!

本文的工程文件开源地址如下(基于ZYNQ7020,大家 clone 到本地就可以直接跑仿真,如果要上板请根据自己的开发板更改约束即可):

https://github.com/ChinaRyan666/FPGA-HDMI-Block



1 实验任务

本文的实验任务是使用 ZYNQ 开发板上的 HDMI 接口在显示器上显示一个不停移动的方块,要求方块移动到边界处时能够改变移动方向。显示分辨率为 1280*720, 刷新速率为 60hz



2 HDMI 简介

我在本专栏的《(七)零基础FPGA图像处理——HDMI彩条显示实验》中对 HDMI 视频传输标准作了详细的介绍, 包括 HDMI 接口定义、行场同步时序、以及显示分辨率等。如果大家对这部分内容不是很熟悉的话, 请参考《(七)零基础FPGA图像处理——HDMI彩条显示实验》中的 HDMI 简介部分。



3 程序设计

3.1 总体模块设计

本次实验总体模块框图与 HDMI 彩条显示实验 完全一致, 只是需要对 lcd_display 模块进行修改。

由此得出本次实验的系统框图如下所示:

在这里插入图片描述


3.2 HDMI 显示模块设计

根据实验任务可知,本文只需要修改 video_display 模块内部的代码。在本次实验中,为了完成方块的显示,需要同时使用像素点的横坐标纵坐标来绘制方块所在的矩形区域,另外还需要知道矩形区域左上角的顶点坐标。 由于 RGB 显示的图像在行场同步信号的同步下不停的刷新, 因此只要连续改变方块左上角顶点的坐标, 并在新的坐标点处重新绘制方块,即可实现方块移动的效果。

HDMI 显示模块框图与彩条显示模块完全一致,模块框图如下图所示:

在这里插入图片描述


3.2.1 绘制波形图

本次方块的大小为 40x40,且该方块的显示范围为 x 坐标为(40~ 1239), y 坐标为(40~ 679)。其中 block_x 为方块当前横坐标, block_y 为当前方块纵坐标。当方块的宽度确定时, 如果我们知道方块左上方顶点的坐标,就能轻而易举的画出整个方块区域。因此,我们将方块的移动简化为其左上角顶点的移动。

由于像素时钟相对于方块移动速度而言过快, 我们通过计数器对时钟计数, 得到一个频率为 100hz 的脉冲信号 move_en,用它作为使能信号来控制方块的移动。 方块的移动方向分为水平方向 h_direct 和竖直方向 v_direct,当方块移动到上下边框时, 竖直移动方向改变; 当方块移动到左右边框时,水平移动方向改变。当 move_en 的频率为 100hz 时,方块每秒钟在水平和竖直方向上分别移动 100 个像素点的距离, 也可以通过调整 move_en 的频率,来加快或减慢方块移动的速度。

根据显示模块分析,我们可以进行波形图的绘制

在这里插入图片描述


3.2.2 代码编写

根据显示模块分析以及绘制的波形图,编写的显示模块代码如下:

module  video_display(
    input             pixel_clk,                //VGA驱动时钟
    input             sys_rst_n,                //复位信号
    
    input      [10:0] pixel_xpos,               //像素点横坐标
    input      [10:0] pixel_ypos,               //像素点纵坐标    
    output reg [23:0] pixel_data                //像素点数据
    );    

//parameter define    
parameter  H_DISP  = 11'd1280;                  //分辨率--行
parameter  V_DISP  = 11'd720;                   //分辨率--列
parameter  DIV_CNT = 22'd750000;				//分频计数器

localparam SIDE_W  = 11'd40;                    //屏幕边框宽度
localparam BLOCK_W = 11'd40;                    //方块宽度
localparam BLUE    = 24'h0000ff;    			//屏幕边框颜色 蓝色
localparam WHITE   = 24'hffffff;    			//背景颜色 白色
localparam BLACK   = 24'h000000;    			//方块颜色 黑色


//reg define
reg [10:0] block_x = SIDE_W ;                   //方块左上角横坐标
reg [10:0] block_y = SIDE_W ;                   //方块左上角纵坐标
reg [21:0] div_cnt;                             //时钟分频计数器
reg        h_direct;                            //方块水平移动方向,1:右移,0:左移
reg        v_direct;                            //方块竖直移动方向,1:向下,0:向上

//wire define   
wire move_en;                                   //方块移动使能信号,频率为100hz

//*****************************************************
//**                    main code
//*****************************************************
assign move_en = (div_cnt == DIV_CNT) ? 1'b1 : 1'b0;

//通过对div驱动时钟计数,实现时钟分频
always @(posedge pixel_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)
        div_cnt <= 22'd0;
    else begin
        if(div_cnt < DIV_CNT) 
            div_cnt <= div_cnt + 1'b1;
        else
            div_cnt <= 22'd0;                   //计数达10ms后清零
    end
end

//当方块移动到边界时,改变移动方向
always @(posedge pixel_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        h_direct <= 1'b1;                       //方块初始水平向右移动
        v_direct <= 1'b1;                       //方块初始竖直向下移动
    end
    else begin
        if(block_x == SIDE_W + 1'b1)            //到达左边界时,水平向右
            h_direct <= 1'b1;               
        else                                    //到达右边界时,水平向左
        if(block_x == H_DISP - SIDE_W - BLOCK_W + 1'b1)
            h_direct <= 1'b0;               
        else
            h_direct <= h_direct;
            
        if(block_y == SIDE_W + 1'b1)            //到达上边界时,竖直向下
            v_direct <= 1'b1;                
        else                                    //到达下边界时,竖直向上
        if(block_y == V_DISP - SIDE_W - BLOCK_W + 1'b1)
            v_direct <= 1'b0;               
        else
            v_direct <= v_direct;
    end
end

//根据方块移动方向,改变其纵横坐标
always @(posedge pixel_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        block_x <= SIDE_W + 1'b1;                     //方块初始位置横坐标
        block_y <= SIDE_W + 1'b1;                     //方块初始位置纵坐标
    end
    else if(move_en) begin
        if(h_direct) 
            block_x <= block_x + 1'b1;          //方块向右移动
        else
            block_x <= block_x - 1'b1;          //方块向左移动
            
        if(v_direct) 
            block_y <= block_y + 1'b1;          //方块向下移动
        else
            block_y <= block_y - 1'b1;          //方块向上移动
    end
    else begin
        block_x <= block_x;
        block_y <= block_y;
    end
end

//给不同的区域绘制不同的颜色
always @(posedge pixel_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) 
        pixel_data <= BLACK;
    else begin
        if(  (pixel_xpos < SIDE_W) || (pixel_xpos >= H_DISP - SIDE_W)
          || (pixel_ypos <= SIDE_W) || (pixel_ypos > V_DISP - SIDE_W))
            pixel_data <= BLUE;                 //绘制屏幕边框为蓝色
        else
        if(  (pixel_xpos >= block_x - 1'b1) && (pixel_xpos < block_x + BLOCK_W - 1'b1)
          && (pixel_ypos >= block_y) && (pixel_ypos < block_y + BLOCK_W))
            pixel_data <= BLACK;                //绘制方块为黑色
        else
            pixel_data <= WHITE;                //绘制背景为白色
    end
end

endmodule

代码中 第 15 至 19 行声明了一系列的参数,方便大家修改边框尺寸方块大小、 以及各部分的颜色等。 其中边框尺寸方块宽度均以像素点为单位。当方块的宽度确定时, 如果我们知道方块左上方顶点的坐标,就能轻而易举的画出整个方块区域。因此,我们将方块的移动简化为其左上角顶点的移动

代码第 98 至 112 行根据 RGB 驱动模块输出的纵横坐标判断当前像素点所在的区域, 对不同区域中的像素点赋以不同的颜色值,从而实现边框方块以及背景颜色的绘制。


3.3 顶层模块编写

实验工程的其他各子功能模块均与本专栏上一篇博客 HDMI 彩条显示实验 完全一致,在本文我们只需要按照 HDMI 彩条显示实验 对顶层模块进行例化即可。

顶层模块 top_hdmi_block_move 的代码如下:

module  top_hdmi_block_move(
    input          sys_clk,
	input          sys_rst_n,
	
    output         tmds_clk_n,
    output         tmds_clk_p,
    output  [2:0]  tmds_data_n,
    output  [2:0]  tmds_data_p
);

//*****************************************************
//**                    main code
//*****************************************************

//wire define
wire          pixel_clk;
wire          pixel_clk_5x;
wire  [10:0]  pixel_xpos_w;
wire  [10:0]  pixel_ypos_w;
wire  [23:0]  pixel_data_w;
wire          video_hs;
wire          video_vs;
wire          video_de;
wire  [23:0]  video_rgb;

//例化MMCM/PLL IP核
clk_wiz_0  clk_wiz_0
(
    // Clock out ports
    .clk_out1     ( pixel_clk ),     // output pixel_clk_74_25m
    .clk_out2     ( pixel_clk_5x ),  // output pixel_clk_x5_371_25m
    
	// Status and control signals
    .reset   ( ~sys_rst_n ),                 // input reset
    .locked  (),                             // output locked
    
	// Clock in ports
    .clk_in1 ( sys_clk )                     // input clk_in1
);

//例化RGB数据驱动模块
video_driver  u_video_driver(
    .pixel_clk      ( pixel_clk ),
    .sys_rst_n      ( sys_rst_n ),

    .video_hs       ( video_hs ),
    .video_vs       ( video_vs ),
    .video_de       ( video_de ),
    .video_rgb      ( video_rgb ),
	.data_req		(),

    .pixel_xpos     ( pixel_xpos_w ),
    .pixel_ypos     ( pixel_ypos_w ),
	.pixel_data     ( pixel_data_w )
);

//例化RGB数据显示模块
video_display  u_video_display(
    .pixel_clk      ( pixel_clk ),
    .sys_rst_n      ( sys_rst_n ),

    .pixel_xpos     ( pixel_xpos_w ),
    .pixel_ypos     ( pixel_ypos_w ),
    .pixel_data     ( pixel_data_w )
);

//例化HDMI驱动模块
dvi_transmitter_top u_rgb2dvi_0(
    .pclk           (pixel_clk),
    .pclk_x5        (pixel_clk_5x),
    .reset_n        (sys_rst_n ),
                
    .video_din      (video_rgb),
    .video_hsync    (video_hs), 
    .video_vsync    (video_vs),
    .video_de       (video_de),
                
    .tmds_clk_p     (tmds_clk_p),
    .tmds_clk_n     (tmds_clk_n),
    .tmds_data_p    (tmds_data_p),
    .tmds_data_n    (tmds_data_n), 
    .tmds_oen       ()                    //预留的端口,本次实验未用到
    );

endmodule


4 仿真验证

4.1 编写TestBench

顶层模块参考代码介绍完毕,开始对顶层模块进行仿真。代码编写如下:

module tb_top_hdmi_block_move();

reg          sys_clk	;
reg          sys_rst_n	;
						
wire         tmds_clk_n	;
wire         tmds_clk_p	;
wire  [2:0]  tmds_data_n;
wire  [2:0]  tmds_data_p;

initial begin
	sys_clk = 1'b1;
	sys_rst_n <= 1'b0;
	#201
	sys_rst_n <= 1'b1;
end

always #10 sys_clk <= ~sys_clk;

defparam top_hdmi_block_move_inst.u_video_display.DIV_CNT = 75;

top_hdmi_block_move top_hdmi_block_move_inst(
    .sys_clk	(sys_clk	),
	.sys_rst_n	(sys_rst_n	),

    .tmds_clk_n	(tmds_clk_n	),
    .tmds_clk_p	(tmds_clk_p	),
    .tmds_data_n(tmds_data_n),
    .tmds_data_p(tmds_data_p)
);

endmodule

由于分频计数器数值太大,所以在仿真代码的第 20 行通过调用模块的映射,对 DIV_CNT 的参数进行修改,从而缩短整体模块仿真的时间。


4.2 仿真波形分析

接下来打开 Modelsim 软件对代码进行仿真,仿真的波形我们重点来看一下 HDMI 显示模块部分的波形图,如下图所示:

在这里插入图片描述

HDMI 显示模块仿真波形


在这里插入图片描述

div_cnt 仿真波形图


div_cnt 的最大计数为 75,当计数器计数到 74 是 move_en 拉高一个时钟周期,当计数到最大值后计数器清零。

在这里插入图片描述

行场像素点 a


在这里插入图片描述

行场像素点 b


在这里插入图片描述

行场信号像素点 c


从行场信号像素点的仿真图,可以看出横坐标的像素点为 1~ 1280,纵坐标的像素点为 1~ 720,与我们编写的代码数据是一致的。

在这里插入图片描述

方块垂直移动方向波形 a


在这里插入图片描述

方块垂直移动方向波形 b


block_y 等于 41 时, v_direct 的使能拉高,当计数到 640 时, v_direct 的使能拉低。

在这里插入图片描述

方块水平移动方向波形 a


在这里插入图片描述

方块水平移动方向波形 b


方块水平移动的使能信号 h_direct 同理,当 block_x 等于 41 时,h_direct 使能拉高;当 block 等于 1200 时,hdirect 的使能拉低。

在这里插入图片描述

方块有效值区域


在这里插入图片描述

方块有效值区域


从方块有效值区域的仿真图可以看出,横坐标的像素有效值区域411240。HDMI 显示模块与我们编写的代码是一致的,下面就可以进行下载验证了。



5 下载验证

我们下载比特流.bit文件,验证 HDMI 显示方块移动的功能。下载完成后观察显示器显示的图案如下图所示, 图中的黑色方块能够不停的移动,且碰撞到蓝色边框时能改变移动方向,说明 HDMI 方块移动程序下载验证成功!

在这里插入图片描述



6 总结

本文主要是基于《(七)零基础FPGA图像处理——HDMI彩条显示实验》进行了一个方块移动弹跳的实验, 通过本文的实验,相信您已经掌握了像素图像弹跳的方法。在实验中我们可以通过改变 DIV_CNT 参数改变方块弹跳的速度,DIV_CNT 的参数越大移动速度越慢,这个参数不是固定的,大家有兴趣可以自己尝试修改一下。

希望以上的内容对您有所帮助,诚挚地欢迎各位读者在评论区或者私信我交流!

微博:沂舟Ryan (@沂舟Ryan 的个人主页 - 微博 )

GitHub:ChinaRyan666

微信公众号:沂舟无限进步(内含精品资料及详细教程)

如果对您有帮助的话请点赞支持下吧!

集中一点,登峰造极。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChinaRyan666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值