VGA模块实现详解

VGA显示器工作原理

vga显示器显示图像,是采用电子枪逐行扫描的方式,在行场同步信号作用下,将构成图像的像素点从左到右,从上到下一点一点地显示在屏幕上,只要扫描速率够快,人眼看到的就是一整幅图像。下面结合行场同步信号进行讲解。

70b3521d54f4417eaf8aeb5cb18529da.png

行同步信号

b9b84cb5a473429a87da13a90970345c.png

行同步信号包含以下阶段:同步、后沿、左边框、有效图像、右边框、前沿

行同步信号的工作时钟周期为像素周期,也就是显示一个像素点所需要的时间。

行同步信号控制电子枪从左到右的扫描,当每扫描完一行后,电子枪指向显示器的最右端(不是屏幕的最右端,下面会解释),在同步脉冲的作用下,电子枪从最右端回到最左端,同步脉冲的持续时间就是电子枪返回所需要的时间,在这段时间电子枪处于关闭状态。电子枪返回最左端后,关闭状态需要持续一段时间才能开启扫描,这段时间称为后沿。开启扫描后,并不是立即扫描屏幕上的像素点,而是先要扫描经过一个左边框(想象一下以前台式电视机,这个左边框就是包裹着屏幕的物理边框),在经过左边框后电子枪就扫描到了屏幕上,这时可以输出有效图像了,在有效图像输出完毕后继续扫描经过一个右边框(也是物理边框)后电子枪会关闭(这时电子枪指向显示器最右端),从电子枪关闭到下一个同步脉冲之间的时间间隔称为前沿。在前沿、后沿、同步这3个阶段电子枪都是关闭的,用以实现消隐。

场同步信号

189f76c8cce547b1999a3dc6fb37f111.png

 场同步信号包含以下阶段:同步、后沿、上边框、有效图像、底边框、前沿

场同步信号的工作时钟周期是行扫描周期,也就是完成一行扫描所需要的时间。

场同步信号控制电子枪从上到下的扫描,每当一帧图像扫描完毕后,电子枪指向显示器的最下端,在同步脉冲的作用下,电子枪从最下端回到最上端,同步脉冲的持续时间就是电子枪返回所需要的时间,在这段时间电子枪处于关闭状态。电子枪返回最上端后,关闭状态需要持续一段时间才能开启扫描,这段时间称为后沿。开启扫描后,并不是立即扫描屏幕上的像素点,而是先要扫描经过一个上边框,在经过上边框后电子枪就扫描到了屏幕上,这时可以输出有效图像了,在有效图像输出完毕后继续扫描经过一个下边框后电子枪会关闭,从电子枪关闭到下一个同步脉冲之间的时间间隔称为前沿。在前沿、后沿、同步这3个阶段电子枪都是关闭的,用以实现消隐。

VGA565模块框图

af889dda1fac4fd7bef37208d35965f3.png

Clk_gen:锁相环模块,产生时钟

Vga_ctrl:vga控制模块,产生行场同步信号和坐标数据并传输行场同步信号和图像数据,坐标数据需要提前产生

Vga_pic:根据接收到的坐标经过1个像素时钟后输出数据

像素时钟计算

a670a02dd0d649bcb573dc6ebf0eb1bb.png

表1

采用640x480@60模式,像素时钟频率Vga_clk = 800x525x60 = 25200000 ≈25.175Mhz(误差忽略不计)

输入50Mhz,用锁相环2分频,输出25Mhz

一、vga_ctrl

先上我(火)哥的代码,慢慢拆析

module  vga_ctrl
(
    input   wire            vga_clk     ,
    input   wire            sys_rst_n   ,
    input   wire    [15:0]  pix_data    ,

    output  wire    [9:0]   pix_x       ,
    output  wire    [9:0]   pix_y       ,
    output  wire            hsync       ,
    output  wire            vsync       ,
    output  wire    [15:0]  vga_rgb

);

parameter   H_SYNC  = 10'd96 ,//同步
            H_BACK  = 10'd40 ,//后沿
            H_LEFT  = 10'd8  ,//左边框
            H_VALID = 10'd640,//有效图像
            H_RIGHT = 10'd8  ,//右边框
            H_FRONT = 10'd8  ,//前沿
            H_TOTAL = 10'd800;//同步+后沿+左边框+有效图像+右边框+前沿

parameter   V_SYNC  = 10'd2  ,//同步
            V_BACK  = 10'd25 ,//后沿
            V_TOP   = 10'd8  ,//上边框
            V_VALID = 10'd480,//有效图像
            V_BOTTOM= 10'd8  ,//底边框
            V_FRONT = 10'd2  ,//前沿
            V_TOTAL = 10'd525;//总共

reg     [9:0]   cnt_h       ;
reg     [9:0]   cnt_v       ;
wire            pix_data_req;
wire            rgb_valid   ;

always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_h   <=  10'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_h   <=  10'd0;
    else
        cnt_h   <=  cnt_h + 1'b1;

always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_v   <=  10'd0;
    else    if((cnt_v == V_TOTAL - 1'b1) && (cnt_h == H_TOTAL - 1'b1))
        cnt_v   <=  10'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_v   <=  cnt_v + 1'b1;
    else
        cnt_v   <=  cnt_v;

assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT) 
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
                    &&(cnt_v >= V_SYNC + V_BACK + V_TOP)
                    &&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
                    ? 1'b1 : 1'b0;

assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1) 
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1)
                    &&(cnt_v >= V_SYNC + V_BACK + V_TOP)
                    &&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
                    ? 1'b1 : 1'b0;

assign pix_x = (pix_data_req == 1'b1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 10'h3ff;

assign pix_y = (pix_data_req == 1'b1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;

assign hsync = (cnt_h <= H_SYNC - 1'b1) ? 1'b1 : 1'b0;

assign vsync = (cnt_v <= V_SYNC - 1'b1) ? 1'b1 : 1'b0;

assign vga_rgb = (rgb_valid == 1'b1) ? pix_data : 16'h0000;

endmodule

总代码

dfe3f9d587194c15b1dd6735c53aaf3d.png

33839f9dc49440c399e6a6b1ede326f9.png

 行同步信号hsync和场同步信号vsync需要一个行计数器cnt_h和一个场计数器cnt_v

根据表1,640x480@60模式需要行计数器cnt_h计数800个,场计数器cnt_v计数525个,其中,计数器计数过程中可分为各个阶段,根据表1,定义各个阶段参数

行计数器参数

parameter   H_SYNC  = 10'd96 ,//同步
            H_BACK  = 10'd40 ,//后沿
            H_LEFT  = 10'd8  ,//左边框
            H_VALID = 10'd640,//有效图像
            H_RIGHT = 10'd8  ,//右边框
            H_FRONT = 10'd8  ,//前沿
            H_TOTAL = 10'd800;//同步+后沿+左边框+有效图像+右边框+前沿

场计数器参数

parameter   V_SYNC  = 10'd2  ,//同步
            V_BACK  = 10'd25 ,//后沿
            V_TOP   = 10'd8  ,//上边框
            V_VALID = 10'd480,//有效图像
            V_BOTTOM= 10'd8  ,//底边框
            V_FRONT = 10'd2  ,//前沿
            V_TOTAL = 10'd525;//总共

行计数器

always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_h   <=  10'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_h   <=  10'd0;
    else
        cnt_h   <=  cnt_h + 1'b1;

场计数器

always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_v   <=  10'd0;
    else    if((cnt_v == V_TOTAL - 1'b1) && (cnt_h == H_TOTAL - 1'b1))
        cnt_v   <=  10'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_v   <=  cnt_v + 1'b1;
    else
        cnt_v   <=  cnt_v;

根据计数器产生行场同步信号:

assign hsync = (cnt_h <= H_SYNC - 1'b1) ? 1'b1 : 1'b0;

assign vsync = (cnt_v <= V_SYNC - 1'b1) ? 1'b1 : 1'b0;

 

信号有效区间

行计数器和场计数器都计数到有效图像阶段信号有效

 

行有效区间(列坐标有效区间):

cnt_h >= H_SYNC + H_BACK + H_LEFT

cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID

场有效区间(行坐标有效区间):

cnt_v >= V_SYNC + V_BACK + V_TOP

cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID

assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT) 
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
                    &&(cnt_v >= V_SYNC + V_BACK + V_TOP)
                    &&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
                    ? 1'b1 : 1'b0;

在有效区间内,rgb_valid为高电平,其用来控制数据从输入到输出的传输,要求数据提前准备好

assign vga_rgb = (rgb_valid == 1'b1) ? pix_data : 16'h0000;

要提前准备数据,就要提前1个像素周期产生坐标信号,因为Vga_pic需要经过1个像素周期来生成数据。

像素坐标的生成:

ee3d6b95717948c18fe10ab57423b711.png  

a670a02dd0d649bcb573dc6ebf0eb1bb.png

表1

信号在有效区内输出坐标信号,在无效区内输出无效坐标信号(全1)以示无效。进入有效区内坐标从(0,0)开始计数。

若不提前产生坐标数据,根据表1,列坐标=行计数器-(同步+后沿+左边框) ;这点好理解,若同步+后沿+左边框=96+40+8=144,行计数器=144,那相减后为0,表示第0列。同理,行坐标=场计数器-(同步+后沿+上边框)。

若要提前产生坐标数据,重点在于操作行计数器。比如,对于640x480@60,若当前坐标为(1,1),那么下一个坐标就是(1,2),改变的是列,而行不变,若当前正在输出坐标(0,0)的数据,那么当前的坐标输出就应为(0,1),以通知Vga_pic模块提前准备好(0,1)的数据。若当前坐标为(0,639),下一个坐标就应为(1,0)。若当前坐标为(439,639),下一个坐标就应为(0,0)。那么如何提前产生像素坐标呢?

每切换一行,行计数器都是从0开始计数,行坐标不变,对应的列坐标从无效区逐渐计数进入有效区,可以将列坐标有效区向左平移一个像素周期,以提前产生像素坐标,如图1。

d88d96dca55f4e72b7d5da80513c7e13.png

图1 

此时的行有效区间:

cnt_h >= H_SYNC + H_BACK + H_LEFT-1

cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID-1

此时的场有效区间(不变):

cnt_v >= V_SYNC + V_BACK + V_TOP

cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID

此时的有效坐标信号:

列坐标=行计数器-(同步+后沿+左边框-1)

行坐标=场计数器-(同步+后沿+上边框)

进入坐标有效区域内需要一个指示信号,在有效区内指示信号拉高

assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1) 
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1)
                    &&(cnt_v >= V_SYNC + V_BACK + V_TOP)
                    &&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
                    ? 1'b1 : 1'b0;

该指示信号用来控制坐标输出

assign pix_x = (pix_data_req == 1'b1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 10'h3ff;

assign pix_y = (pix_data_req == 1'b1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;

至此,vga_ctrl讲解完毕。

二、vga_pic

该模块的功能就是根据输入的坐标数据经过一个像素时钟后输出对应坐标的图像数据,具体实现五花八门。若觉得一个像素周期时间太少,可修改vga_ctrl模块。

 

  • 12
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值