基于FPGA的vga显示驱动-基础篇

目录

VGA简介

VGA接口及引脚定义

VGA显示原理

vga时序标准

VGA显示模式及相关参数

VGA电路开发板原理图分析

使用vga接口显示不同颜色

绘制模块框图及波形图

编写模块代码

编写仿真代码

仿真验证

​编辑

上板验证

练习

总结

使用vga接口显示静态图片

绘制模块框图及波形图

生成mif文件

配置IP核

编写模块代码

编写仿真代码

仿真验证

上板验证

使用vga接口显示动态图片

编写模块代码

编写仿真代码

仿真验证

上板验证


VGA简介

图像显示设备在日常生活中随处可见,例如家庭电视机、计算机显示屏幕等,这些设 备之所以能够显示我们需要的数据图像信息,归功于视频传输接口。常见的视频传输接口 有三种:VGA 接口、DVI 接口和 HDMI 接口,目前的显示设备都配有这三种视频传输接 口。

三类视频接口的发展历程为VGA→DVI→HDMI。其中VGA接口出现最早,只能传输模拟图像信号; 随后出现的 DVI接口又分为三类:DVI-A、DVI-D、DVI-I,分别可传输纯模拟图像信号、纯数字图像信号和兼容模拟、数字图像信号;最后的 HDMI在传输数字图像信号的基础上又可以传输音频信号。

VGA ,英文全称"Video Graphics Array",译为视频图形阵列,是一种使用模拟信号 进行视频传输的标准协议,由 IBM公司于 1987年推出,因其分辨率高、显示速度快、颜 色丰富等优点,广泛应用于彩色显示器领域。由于 VGA 接口体积较大,与追求小巧便携 的笔记本电脑背道而驰,在笔记本电脑领域,VGA 接口已被逐渐淘汰,但对于体积较大的 台式机,这种情况并未发生,虽然 VGA 标准在当前个人电脑市场中已经过时,但因其在 显示标准中的重要性和良好的兼容性,VGA 仍然是最多制造商所共同支持的一个标准,个 人电脑在加载自己独特驱动程序之前,都必须支持VGA的标准。

早期的CRT显示器只能接收模拟信号,不能接收数字信号,计算机内部显卡将数字信号转换成模拟信号,通过VGA接口传给VGA显示器,虽然现如今许多种类的显示器可以 直接接收数字信号,但为了兼容显卡的VGA接口,大都支持VGA标准。

VGA接口及引脚定义

在最初的应用中, VGA 接口常用于计算机与 VGA 显示器之间的图像传输,在台式计算机、旧式笔记本电脑和 VGA 显示器上一般会有标准的 VGA 接口。

VGA 接口中以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机和 VGA 显示器上一般引出母头接口,使用两头均为公头的 VGA 连接线将计算机与 VGA显示器连接起来,两者图像传输时,使用的是 VGA 图像传输标准,该标准的具体内容在后面小节会详细说明。 VGA 公头、 母头接口和 VGA 连接线,见下图所示,左侧为母头,右侧为公头。

VGA 接口共有 15 个引脚,分为 3 排,每排各 5 个, 按照自上而下、从左向右的顺序排列。 其中第一排的引脚 1、 2、 3 和第三排的引脚 13、 14 最为重要。

VGA 使用工业界通用的 RGB 色彩模式作为色彩显示标准,这种色彩显示标准是根据三原色中红色、绿色、蓝色所占比例多少及三原色之间的相互叠加得到各式各样的颜色。引脚 1 红基色(RED)、引脚 2 绿基色(GREEN)、引脚 3 蓝基色(BLUE)就是 VGA 接口中负责传输三原色的传输通道。要注意的是,这 3 个引脚传输的是模拟信号。

引脚 13 行同步信号(HSYNC)、引脚 14 场同步信号(VSYNC),这两个信号,是在 VGA显示图像时,负责同步图像色彩信息的同步信号。

引脚 5、 9:这两个引脚分别是 VGA 接口的自测试和预留接口,不过不同生产厂家对这两个接口定义不同,在接线时,两引脚可悬空不接。

引脚 4、 11、 12、 15:这四个是 VGA 接口的地址码,可以悬空不接。

引脚 6、 7、 8、 10:这四个引脚接地,无需解释。

VGA显示原理

VGA 显示器显示图像,并不是直接让图像在显示器上显示出来, 而是采用扫描的方式,将构成图像的像素点,在行同步信号和场同步信号的同步下,按照从上到下、 由左到右的顺序扫描到显示屏上。 VGA 显示器扫描方式,具体见图。

结合 VGA 显示器扫描方式示意图,我们简要说明一下 VGA 显示器的扫描规律。

(1) 在行、场同步信号的同步作用下,扫描坐标定位到左上角第一个像素点坐标;

(2) 自左上角(第一行)第一个像素点坐标,逐个像素点向右扫描(图中第一个水平方向箭头);

(3) 扫描到第一行最后一个数据,一行图像扫描完成,进行图像消隐,扫描坐标自第一行行尾转移到第二行行首(图中第一条虚线);

(4) 重复若干次扫描至最后一行行尾,一帧图像扫描完成,进行图像消隐,扫描坐标跳转回到左上角第一行行首(图中对角线箭头),开始下一帧图像的扫描。

在扫描的过程中会对每一个像素点进行单独赋值,使每个像素点显示对应色彩信息,
当一帧图像扫描结束后, 开始下一帧图像的扫描,循环往复,当扫描速度足够快,加之人眼的视觉暂留特性,我们会看到一幅完整的图片,而不是一个个闪烁的像素点。这就是VGA 显示的原理。

vga时序标准

vga行时序

图中 Video 代表传输的图像数据,HSync 为行同步信号。HSync 的上升沿到下一个上升沿构成一个完整的行扫描周期。

每个行扫描周期包含 6 个部分(单位均为像素时钟周期):

  • Sync(同步)
  • Back Porch(后沿)
  • Left Border(左边框)
  • "Addressable" Video(有效图像)
  • Right Border(右边框)
  • Front Porch(前沿)

在行扫描周期内,Video 数据仅在"Addressable" Video 阶段有效,其他阶段均无效。HSync 信号仅在 Sync 阶段保持高电平,其余阶段均为低电平。这种高低电平交替的模式在每个行扫描周期重复进行。

vga场时序

图中 Video 表示传输的图像数据,VSync 是场同步信号。VSync 的上升沿到下一个上升沿构成一个完整的场扫描周期。

每个场扫描周期包含 6 个部分(以行为单位):

  1. Sync(同步)
  2. Back Porch(后沿)
  3. Top Border(上边框)
  4. "Addressable" Video(有效图像)
  5. Bottom Border(底边框)
  6. Front Porch(前沿)

在 HSync(行同步信号)和 VSync 的共同控制下,Video 图像数据完成一帧显示。其中,仅"Addressable" Video 阶段的图像数据有效,其余阶段无效。VSync 信号在 Sync 阶段保持高电平,其余阶段为低电平。完成一个场扫描周期后,系统即开始下一帧图像的扫描。

将行同步时序图与场同步时序图结合起来就构成了 VGA 时序图,如下图:

图中的红色区域表示在一个完整的行扫描周期中,Video 图像信息只在此区域有效, 黄色区域表示在一个完整的场扫描周期中,Video 图像信息只在此区域有效,两者相交的 橙色区域,就是VGA图像的最终显示区域。

VGA显示模式及相关参数

行同步时序可分为 6 个阶段,对于这 6 个阶段的参数是有严格定义的,参数配置不正确, VGA 不能正常显示。 VGA 显示器可支持多种分辨率,不同分辨率对应各阶段的参数是不同的,常用 VGA 分辨率时序参数,具体见图。

下面我们以经典 VGA 显示模式 640x480@60 为例,为讲解一下 VGA 显示的相关参数

  1. 显示模式: 640x480@60
    640x480 是指 VGA 的分辨率, 640 是指有效显示图像每一行有 640 个像素点, 480 是指每一帧图像有 480 行, 640 * 480 = 307200 ≈ 300000,每一帧图片包含约 30 万个像素点,之前某品牌手机广告上所说的 30 万像素指的就是这个; @60 是指 VGA 显示图像的刷新频率, 60 就是指 VGA 显示器每秒刷新图像 60 次,即每秒钟需要显示 60 帧图像。
  2. 时钟(MHz): 25.175MHz,是 VGA 显示的工作时钟,像素点扫描频率
  3. 行同步信号时序(像素 )、 场同步信号时序(行数 )

下面我们以显示模式 640x480@60、 640x480@75 为例,学习一下时钟频率的计算方法。

行扫描周期 * 场扫描周期 * 刷新频率 = 时钟频率

所以行同步信号和场同步信号高电平宽度与显示模式有关。

640x480@60:

|----------------------------------|------------|
| 行扫描周期: 800(像素),场扫描周期: 525(行扫描周期) | 刷新频率: 60Hz |
| 800 * 525 * 60 = 25,200,000 ≈ 25.175MHz (误差忽略不计) ||

640x480@75:

|----------------|-------------------|------------|
| 行扫描周期: 840(像素) | 场扫描周期: 500(行扫描周期) | 刷新频率: 75Hz |
| 840 * 500 * 75 = 31,500,000 = 31.5MHz |||

在计算时钟频率时,要谨记一点,要使用行扫描周期和场扫描周期的参数进行计算,不能使用有效图像的参数进行计算,虽然在有效图像外的其他阶段图像信息均无效, 但图像无效阶段的扫描也花费了扫描时间。

VGA电路开发板原理图分析

由于FPGA芯片只能处理数字信号,而VGA接口传输的是模拟信号,因此图中的vga_r、vga_g、vga_b引脚必须输出模拟信号。这需要通过数模转换将FPGA输出的数字信号转换为模拟信号。原理图中采用权电阻网络实现DAC功能,当然也可以使用专用DAC芯片。二者的主要区别在于权电阻网络的转换精度低于专用DAC芯片。通过观看这个原理图的权电阻网络,我们可以很清晰的看见每个颜色的接口的电阻之间的规律,我们以vga_b为例子进行讲解,假设

|--------------|-----|
| fpga高电平1 | 16V |
| vga_d4接电阻 | 1Ω |
| vga_d3接电阻 | 2Ω |
| vga_d2接电阻 | 4Ω |
| vga_d1接电阻 | 8Ω |
| vga_d0接电阻 | 16Ω |
| vga_b接口处的电阻值 | 1Ω |

根据上面的假设,我们可以写出几组真值表。

|--------|--------|--------|--------|--------|--------------------------------|
| vga_d4 | vga_d3 | vga_d2 | vga_d1 | vga_d0 | vga_b |
| 1 | 0 | 0 | 0 | 0 | (16V/1Ω)*1Ω=16V |
| 1 | 1 | 1 | 0 | 0 | (16V/1Ω+16V/2Ω+16V/4Ω)*1Ω=28V |

这就是权电阻网络实现的DAC,通过电阻去控制电流,进而控制电压(幅值),从而实现数模转换。

我们可以看出高位对vga_b的电压幅值影响最大,最低位对vga_b的电压幅值影响最小。

这里使用的RGB565图像模式,位宽为16bit,高 5位表示红色,低5 位表示蓝色,中间 6 位表示绿色。根据位宽不同,RGB 图形格式还包括 RGB232、 RGB888等,数据位宽越大,表示颜色种类越多,显示图像越细腻。

我们的电脑大多使用的是rgb888模式。将RGB888转换为RGB565的方法很简单:只需从RGB888的三个颜色通道中提取高位数据,并将对应幅值赋予RGB565即可完成模式转换。

使用vga接口显示不同颜色

实现目标1:VGA单颜色显示

实现目标2:VGA显示:横向条纹、纵向条纹、斜线分块显示、显示三角形、圆形、椭圆形等

绘制模块框图及波形图

编写模块代码

模块vga_ctrl

复制代码
module  vga_ctrl(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[15:0]  rgb     ,
    output  wire        hsync   ,
    output  wire        vsync
);
//========== 时序参数(640x480@60Hz)==========
//行同步信号时序(像素)
parameter   H_SYNC  = 16'd96    ,
            H_BACK  = 16'd40    ,
            H_LEFT  = 16'd8     ,
            H_VAILD = 16'd640   ,
            H_RIGHT = 16'd8     ,
            H_FRONT = 16'd8     ,
            H_TOTAL = 16'd800   ;
//场同步信号时序(行数)
parameter   V_SYNC  = 16'd2     ,
            V_BACK  = 16'd25    ,
            V_TOP   = 16'd8     ,
            V_VAILD = 16'd480   ,
            V_BOTTOM= 16'd8     ,
            V_FRONT = 16'd2     ,
            V_TOTAL = 16'd525   ;
//========== 颜色定义 ==========
parameter   RED     =   16'hF800,   //红色
            ORANGE  =   16'hFC00,   //橙色
            YELLOW  =   16'hFFE0,   //黄色
            GREEN   =   16'h07E0,   //绿色
            CYAN    =   16'h07FF,   //青色
            BLUE    =   16'h001F,   //蓝色
            PURPPLE =   16'hF81F,   //紫色
            BLACK   =   16'h0000,   //黑色
            WHITE   =   16'hFFFF,   //白色
            GRAY    =   16'hD69A;   //灰色
//========== 显示模式选择 ==========
// 0:单色(黄色)   1:横向条纹  2:纵向条纹  3:斜线分块正向
// 4:斜线分块反向 5:三角形    6:圆形      7:椭圆形
parameter [2:0] PATTERN = 3'd5;   // 修改此值切换模式

reg [15:0]  cnt_h;
reg [15:0]  cnt_v;
wire        rgb_en;
//cnt_h
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_h <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_h <= 16'd0;
    else
        cnt_h <= cnt_h + 1'b1;
end
//cnt_v
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_v <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1)
        cnt_v <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_v <= cnt_v + 1'b1;
    else
        cnt_v <= cnt_v;
end
//hsync
assign  hsync = (cnt_h < H_SYNC) ? 1'b1 : 1'b0 ;
//vsync
assign  vsync = (cnt_v < V_SYNC) ? 1'b1 : 1'b0 ;
//rgb_en有效显示区域使能
assign  rgb_en = (cnt_h >= H_SYNC + H_BACK + H_LEFT
                &&cnt_h <  H_SYNC + H_BACK + H_LEFT + H_VAILD
                &&cnt_v >= V_SYNC + V_BACK + V_TOP
                &&cnt_v <  V_SYNC + V_BACK + V_TOP + V_VAILD) 
                ? 1'b1 : 1'b0 ;
//建立坐标系
wire [31:0]   x;
wire [31:0]   y;
reg  [15:0]   color_tmp;//根据坐标和模式产生的颜色
//x
assign  x = (rgb_en) ? (cnt_h - H_SYNC - H_BACK - H_LEFT) : 16'hffff;
//y
assign  y = (rgb_en) ? (cnt_v - V_SYNC - V_BACK - V_TOP ) : 16'hffff;
//color_tmp
always @(*)
begin
    color_tmp = BLACK;// 默认颜色
    case(PATTERN)
        3'd0 : color_tmp = YELLOW;//单色(黄色)
        
        3'd1 : if(y < V_VAILD / 4)//横向条纹与y有关
                    color_tmp = RED;
               else if(y >= V_VAILD / 4 * 1 && y < V_VAILD / 4 * 2)
                    color_tmp = ORANGE;
               else if(y >= V_VAILD / 4 * 2 && y < V_VAILD / 4 * 3)
                    color_tmp = BLUE;
               else
                    color_tmp = GREEN;
                    
        3'd2 : if(x < H_VAILD / 4)//纵向条纹与x有关
                    color_tmp = RED;
               else if(x >= H_VAILD / 4 * 1 && x < H_VAILD / 4 * 2)
                    color_tmp = ORANGE;
               else if(x >= H_VAILD / 4 * 2 && x < H_VAILD / 4 * 3)
                    color_tmp = BLUE;
               else
                    color_tmp = GREEN;
                    
        3'd3 : if(y * H_VAILD < x * V_VAILD)//斜线分块正向
                    color_tmp = RED;//上半边
               else
                    color_tmp = BLUE;
                    
        3'd4 : if(y * H_VAILD < V_VAILD * H_VAILD - x * V_VAILD)//斜线分块反向
                    color_tmp = RED;//上半边
               else
                    color_tmp = BLUE;
                    
        3'd5 : if(y < 360 && y * 2 + 360 * 2 > x * 3 && y * 2 + x * 3 > 600 * 2)//三角形
                    color_tmp = YELLOW;
               else
                    color_tmp = PURPPLE;
                    
        3'd6 : if(x * x + 320 * 320 + y * y + 240 * 240 < 2 * 320 * x + 2 * 240 * y + 120 * 120)//圆形(x - 320) * (x - 320) + (y - 240) * (y - 240) < 120*120
                    color_tmp = ORANGE;
               else
                    color_tmp = GREEN;
                    
        3'd7 : if((x - 320) * (x - 320) * 50 * 50  + (y - 240) * (y - 240) * 100 * 100 < 100 * 100 * 50 * 50)//椭圆形
                    color_tmp = RED;
               else
                    color_tmp = BLUE;
                    
        default:color_tmp = YELLOW;
    endcase
end
//最终RGB输出
assign  rgb = (!rgb_en) ? BLACK : color_tmp;

endmodule

顶层模块vga_one_color

复制代码
module  vga_one_color(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[15:0]  rgb     ,
    output  wire        hsync   ,
    output  wire        vsync
);

wire        clk_25Mhz;
wire        locked   ;

pll_25Mhz   pll_25Mhz_inst (
    .areset ( ~rst_n    ),
    .inclk0 ( clk       ),
    .c0     ( clk_25Mhz ),
    .locked ( locked    )
);

vga_ctrl    vga_ctrl_inst(
    .clk   (clk_25Mhz   ) ,
    .rst_n (locked      ) ,

    .rgb   (rgb         ) ,
    .hsync (hsync       ) ,
    .vsync (vsync       )
);

endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  vga_one_color_tb();

reg     clk     ;
reg     rst_n   ;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    #123
    rst_n = 1'b1;
end

always #10 clk = ~clk;

vga_one_color   vga_one_color_inst(
    .clk   (clk  ) ,
    .rst_n (rst_n) ,

    .rgb   () ,
    .hsync () ,
    .vsync ()
);
endmodule

仿真验证

hsync没有问题

vsync没有问题

两个计数器没有问题

显示区域没有问题,x,y坐标也没有问题,仿真验证通过。

上板验证

练习

VGA显示,使用其他分辨率显示(只需要按照显示模式,修改一些参数配置即可)

总结

**vga显示原理:**VGA采用逐行扫描方式显示图像。在行同步信号和场同步信号的精确控制下,电子束(或视频信号)从屏幕左上角开始,按照从左到右、从上到下的顺序依次扫描每一个像素点。扫描过程中,每个像素点被单独赋予对应的RGB色彩值。一行扫描结束后,行消隐期使电子束快速跳转到下一行行首;一帧扫描完成后,场消隐期让电子束从右下角跳回左上角,开始下一帧刷新。如此循环往复,当刷新率足够高(通常≥60Hz)时,利用人眼的视觉暂留效应,观察者便看到连续稳定的完整画面。

**解释数模转换方法(介绍权电阻网络):**由于FPGA输出的是数字信号(0和1),而VGA接口需要模拟电压来表现颜色深浅,因此必须进行数模转换。权电阻网络是一种简单经济的实现方案。以蓝色通道为例,数字位从高位到低位分别通过阻值按二进制倍数递增的电阻(如1Ω、2Ω、4Ω、8Ω、16Ω)接入负载。高位接小电阻,权重大,对输出电流贡献大;低位接大电阻,权重小。当数字位为高电平(如16V)时,对应支路产生电流,所有有效支路的电流在负载电阻上叠加,形成最终的模拟电压。也就是说使用电阻,控制各支路的电流,进而去控制vga三原色接口的模拟电压。

回答不同分辨率下使用的时钟怎么计算: 分辨率不同,则对应的有效显示区域不同,则行扫描周期和场扫描周期都不同,所以发送一帧数据所需要时钟周期数为行扫描周期 * 场扫描周期。然后根据刷新频率,我们可以知道1s发送多少帧。这样我们就可以得到1s内的时钟周期数也就是vga的时钟频率为:行扫描周期 * 场扫描周期 * 刷新频率。

使用vga接口显示静态图片

实现目标:使用matlab将.jpg\.png图片转换成.mif文件,然后将.mif文件存储到rom-ip核里,最后通过vga接口显示到显示器上。下面使用的分辨率为800x600@60。

绘制模块框图及波形图

生成mif文件

使用下面的matlab代码生成mif文件。

Matlab 复制代码
%a 是一个三维矩阵 (行,列,3)
%imread 函数默认将图片解码为 RGB888 格式,颜色通道顺序为R-G-B。
a = imread('tupian1.png'); %  读入图片 jpg png

y1=zeros(1,16384);% 存储红色分量,开辟一个16384的一维的全为0的空间,加速代码运行
y2=zeros(1,16384);% 存储绿色分量
y3=zeros(1,16384);% 存储蓝色分量
y4=zeros(1,16384);
y=zeros(1,16384); % 存储最终的 RGB565 像素值
%下面的设置使用EP4CE10F17C8芯片的60%多的存储类资源
%m=130;%行像素 图片宽度(列数)
%n=116;%场像素 图片高度(行数)
m=130;
n=73;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%111111
fild=fopen('tupian.mif','wt');
fprintf(fild,'%s\n','WIDTH=16;');% 数据位宽为 16 bit(RGB565恰好占16位)
fprintf(fild,'%s\n\n','DEPTH=16384;'); % 深度 16384
fprintf(fild,'%s\n','ADDRESS_RADIX=UNS;'); % 地址采用无符号十进制
fprintf(fild,'%s\n\n','DATA_RADIX=UNS;'); % 数据采用无符号十进制
fprintf(fild,'%s\n','CONTENT BEGIN');

for i=1:n       % 遍历行(高度)
    for j=1:m   % 遍历列(宽度)
        z= (i-1)*m+j ;% 二维坐标转一维索引(从1开始)
        z0=round(z-1);% 生成从0开始的地址(MIF要求地址从0开始)
        y1(z)= a(i,j,1) ;%提取红色通道 8bit
        y2(z)= a(i,j,2) ;%提取绿色通道 8bit
        y3(z)= a(i,j,3) ;%提取蓝色通道 8bit
        %RGB565  fix向0取整数  RGB888 转 RGB565 的位截断与拼合
        y(z)= fix(y1(z)/2^3)*2^11 + fix(y2(z)/2^2)*2^5 + fix(y3(z)/2^3) ;%彩色
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%彩色转黑白 彩色图转为灰度图(使用心理视觉权重:红 0.4,绿 0.5,蓝 0.1)。
        %y4(z)= 0.4*y1(z)+0.5*y2(z)+0.1*y3(z) ;%RGB转灰度值 
        %%将计算出的灰度值 y4 再次按 RGB565 的位宽拆分成 R、G、B 相等(即 R=G=B=灰度值截断),显示出来就是黑白图像。
        %y(z)= fix(y4(z)/8)*2^11 + fix(y4(z)/4)*2^5 + fix(y4(z)/8) ;%黑白

    fprintf(fild,'\t%d\t',z0); % 写入地址(十进制)
    fprintf(fild,'%s\t',':');  % 写入冒号分隔符
    fprintf(fild,'%d\t',y(z)); % 写入 RGB565 数值
    fprintf(fild,'%s\n',';');  % 写入分号结束符
    end
end
fprintf(fild,'%s\n','END;');

配置IP核

配置pll_40Mhz、rom_16x16384

编写模块代码

模块vga_ctrl

Matlab 复制代码
module  vga_ctrl(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[15:0]  rgb     ,
    output  wire        hsync   ,
    output  wire        vsync
);
//========== 时序参数(800x600@60Hz)==========
//行同步信号时序(像素)
parameter   H_SYNC  = 16'd128 ,
            H_BACK  = 16'd88  ,
            H_LEFT  = 16'd0   ,
            H_VAILD = 16'd800 ,
            H_RIGHT = 16'd0   ,
            H_FRONT = 16'd40  ,
            H_TOTAL = 16'd1056;
//场同步信号时序(行数)
parameter   V_SYNC  = 16'd4   ,
            V_BACK  = 16'd23  ,
            V_TOP   = 16'd0   ,
            V_VAILD = 16'd600 ,
            V_BOTTOM= 16'd0   ,
            V_FRONT = 16'd1   ,
            V_TOTAL = 16'd628 ;
//========== 颜色定义 ==========
parameter   RED     =   16'hF800,   //红色
            ORANGE  =   16'hFC00,   //橙色
            YELLOW  =   16'hFFE0,   //黄色
            GREEN   =   16'h07E0,   //绿色
            CYAN    =   16'h07FF,   //青色
            BLUE    =   16'h001F,   //蓝色
            PURPPLE =   16'hF81F,   //紫色
            BLACK   =   16'h0000,   //黑色
            WHITE   =   16'hFFFF,   //白色
            GRAY    =   16'hD69A;   //灰色
////////////////////////////////////////////////图片宽度和高度
parameter   PIC_W   =   16'd130, //必须为偶数
            PIC_H   =   16'd72 ; //必须为偶数

reg [15:0]  cnt_h;
reg [15:0]  cnt_v;
wire        rgb_en;
//cnt_h
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_h <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_h <= 16'd0;
    else
        cnt_h <= cnt_h + 1'b1;
end
//cnt_v
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_v <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1)
        cnt_v <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_v <= cnt_v + 1'b1;
    else
        cnt_v <= cnt_v;
end
//hsync
assign  hsync = (cnt_h < H_SYNC) ? 1'b1 : 1'b0 ;
//vsync
assign  vsync = (cnt_v < V_SYNC) ? 1'b1 : 1'b0 ;
//rgb_en有效显示区域使能
assign  rgb_en = (cnt_h >= H_SYNC + H_BACK + H_LEFT
                &&cnt_h <  H_SYNC + H_BACK + H_LEFT + H_VAILD
                &&cnt_v >= V_SYNC + V_BACK + V_TOP
                &&cnt_v <  V_SYNC + V_BACK + V_TOP + V_VAILD) 
                ? 1'b1 : 1'b0 ;
//////////////////////////////建立坐标系
wire [15:0]   dx;
wire [15:0]   dy;
//dx
assign  dx = (rgb_en) ? (cnt_h - H_SYNC - H_BACK - H_LEFT) : 16'hffff;
//dy
assign  dy = (rgb_en) ? (cnt_v - V_SYNC - V_BACK - V_TOP ) : 16'hffff;
///////////////////////////从rom核里提取数据
wire        rden;
reg [13:0]  addr;
wire[15:0]  q   ;
//rden,每一行提取2个时钟周期读取
assign  rden = (dx >= H_VAILD / 2 - PIC_W / 2 - 2'd2
              &&dx <  H_VAILD / 2 + PIC_W / 2 - 2'd2
              &&dy >= V_VAILD / 2 - PIC_H / 2
              &&dy <  V_VAILD / 2 + PIC_H / 2)
                ? 1'b1 : 1'b0 ;
//addr
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        addr <= 14'd0;
    else    if(rden)
        addr <= addr + 1'b1;
    else    if(addr == PIC_W * PIC_H)
        addr <= 14'd0;
    else
        addr <= addr;
end
rom_16x16384    rom_16x16384_inst (
    .address( addr      ),
    .clock  ( clk       ),
    .rden   ( rden      ),
    .q      ( q         )
);
//////////////////////////////最终RGB输出
assign  rgb = (dx >= H_VAILD / 2 - PIC_W / 2
             &&dx <  H_VAILD / 2 + PIC_W / 2
             &&dy >= V_VAILD / 2 - PIC_H / 2
             &&dy <  V_VAILD / 2 + PIC_H / 2) ? q : BLACK ;

endmodule

顶层模块vga_pic

Matlab 复制代码
module  vga_pic(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[15:0]  rgb     ,
    output  wire        hsync   ,
    output  wire        vsync
);

wire        clk_40Mhz;
wire        locked   ;

pll_40Mhz   pll_40Mhz_inst (
    .areset ( ~rst_n    ),
    .inclk0 ( clk       ),
    .c0     ( clk_40Mhz ),
    .locked ( locked    )
);

vga_ctrl    vga_ctrl_inst(
    .clk   (clk_40Mhz   ) ,
    .rst_n (locked      ) ,

    .rgb   (rgb         ) ,
    .hsync (hsync       ) ,
    .vsync (vsync       )
);

endmodule

编写仿真代码

Matlab 复制代码
`timescale  1ns/1ps
module  vga_pic_tb();

reg     clk     ;
reg     rst_n   ;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    #123
    rst_n = 1'b1;
end

always #10 clk = ~clk;

vga_pic   vga_pic_inst(
    .clk   (clk  ) ,
    .rst_n (rst_n) ,

    .rgb   () ,
    .hsync () ,
    .vsync ()
);
endmodule

仿真验证

这里的仿真直接参考vga动态显示图片的仿真图查看。

上板验证

使用vga接口显示动态图片

实现目标:使用上面的图片从显示器的左上角处显示,然后向右下角移动,步进为1,到达边界时反弹,实现效果如下图:

这里不再绘制模块框图及波形图了。直接根据上面的代码进行修改。

编写模块代码

模块vga_ctrl

Matlab 复制代码
module  vga_ctrl(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[15:0]  rgb     ,
    output  wire        hsync   ,
    output  wire        vsync
);

/* //========== 时序参数(640x480@60Hz)==========
//行同步信号时序(像素)
parameter   H_SYNC  = 16'd96    ,
            H_BACK  = 16'd40    ,
            H_LEFT  = 16'd8     ,
            H_VAILD = 16'd640   ,
            H_RIGHT = 16'd8     ,
            H_FRONT = 16'd8     ,
            H_TOTAL = 16'd800   ;
//场同步信号时序(行数)
parameter   V_SYNC  = 16'd2     ,
            V_BACK  = 16'd25    ,
            V_TOP   = 16'd8     ,
            V_VAILD = 16'd480   ,
            V_BOTTOM= 16'd8     ,
            V_FRONT = 16'd2     ,
            V_TOTAL = 16'd525   ; */

//========== 时序参数(800x600@60Hz)==========
//行同步信号时序(像素)
parameter   H_SYNC  = 16'd128 ,
            H_BACK  = 16'd88  ,
            H_LEFT  = 16'd0   ,
            H_VAILD = 16'd800 ,//dx坐标范围:0-799
            H_RIGHT = 16'd0   ,
            H_FRONT = 16'd40  ,
            H_TOTAL = 16'd1056;
//场同步信号时序(行数)
parameter   V_SYNC  = 16'd4   ,
            V_BACK  = 16'd23  ,
            V_TOP   = 16'd0   ,
            V_VAILD = 16'd600 ,//dy坐标范围:0-699
            V_BOTTOM= 16'd0   ,
            V_FRONT = 16'd1   ,
            V_TOTAL = 16'd628 ;
//========== 颜色定义 ==========
parameter   RED     =   16'hF800,   //红色
            ORANGE  =   16'hFC00,   //橙色
            YELLOW  =   16'hFFE0,   //黄色
            GREEN   =   16'h07E0,   //绿色
            CYAN    =   16'h07FF,   //青色
            BLUE    =   16'h001F,   //蓝色
            PURPPLE =   16'hF81F,   //紫色
            BLACK   =   16'h0000,   //黑色
            WHITE   =   16'hFFFF,   //白色
            GRAY    =   16'hD69A;   //灰色
//========== 图片宽度和高度 ==================
parameter   PIC_W   =   16'd130, //必须为偶数
            PIC_H   =   16'd72 ; //必须为偶数

reg [15:0]  cnt_h;
reg [15:0]  cnt_v;
wire        rgb_en;
//cnt_h
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_h <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_h <= 16'd0;
    else
        cnt_h <= cnt_h + 1'b1;
end
//cnt_v
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_v <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1)
        cnt_v <= 16'd0;
    else    if(cnt_h == H_TOTAL - 1'b1)
        cnt_v <= cnt_v + 1'b1;
    else
        cnt_v <= cnt_v;
end
//hsync
assign  hsync = (cnt_h < H_SYNC) ? 1'b1 : 1'b0 ;
//vsync
assign  vsync = (cnt_v < V_SYNC) ? 1'b1 : 1'b0 ;
//rgb_en有效显示区域使能
assign  rgb_en = (cnt_h >= H_SYNC + H_BACK + H_LEFT - 2'd2
                &&cnt_h <  H_SYNC + H_BACK + H_LEFT + H_VAILD - 2'd2
                &&cnt_v >= V_SYNC + V_BACK + V_TOP
                &&cnt_v <  V_SYNC + V_BACK + V_TOP + V_VAILD) 
                ? 1'b1 : 1'b0 ;
//============== 建立二维坐标系 ================
wire [15:0]   dx;
wire [15:0]   dy;
//dx
assign  dx = (rgb_en) ? (cnt_h - H_SYNC - H_BACK - H_LEFT + 2'd2) : 16'hffff;
//dy
assign  dy = (rgb_en) ? (cnt_v - V_SYNC - V_BACK - V_TOP ) : 16'hffff;
//=============== 从rom核里提取数据 =====================
parameter   OFFSET = 1'b1;//偏移量

reg [15:0]  offset_x;//x轴的显示起始位置
reg [15:0]  offset_y;//y轴的显示起始位置
reg         dir_x; // 方向标志 1: 向右移动,0: 向左移动
reg         dir_y; // 方向标志 1: 向下移动,0: 向上移动
wire        frame_end;//扫描完一帧的结束标志信号
wire        rden;
reg [13:0]  addr;
wire[15:0]  q   ;
//offset_x
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        offset_x <= 16'd0;
    else    if(frame_end && dir_x && offset_x + PIC_W >= H_VAILD)
        offset_x <= H_VAILD - PIC_W;//图片向右移动,图片右边缘超出或等于有效图像显示区的右边界
    else    if(frame_end && dir_x && offset_x + PIC_W < H_VAILD)
        offset_x <= offset_x + OFFSET;
    else    if(frame_end && !dir_x && offset_x <= 16'd0)
        offset_x <= 16'd0;//图片向左移动,图片左边缘超出或等于有效图像显示区的左边界
    else    if(frame_end && !dir_x && offset_x > 16'd0)
        offset_x <= offset_x - OFFSET;
    else
        offset_x <= offset_x;
end
//offset_y
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        offset_y <= 16'd0;
    else    if(frame_end && dir_y && offset_y + PIC_H >= V_VAILD)
        offset_y <= V_VAILD - PIC_H;//图片向下移动,图片下边缘超出或等于有效图像显示区的下边界
    else    if(frame_end && dir_y && offset_y + PIC_H <  V_VAILD)
        offset_y <= offset_y + OFFSET;
    else    if(frame_end && !dir_y && offset_y <= 16'd0)
        offset_y <= 16'd0;//图片向上移动,图片上边缘超出或等于有效图像显示区的上边界
    else    if(frame_end && !dir_y && offset_y >  16'd0)
        offset_y <= offset_y - OFFSET;
    else
        offset_y <= offset_y;
end
//dir_x
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        dir_x <= 1'b1;// 初始向右
    else    if(frame_end && dir_x && offset_x + PIC_W >= H_VAILD)//到右边界
        dir_x <= 1'b0;// 改为向左
    else    if(frame_end && !dir_x && offset_x <= 16'd0)//到左边界
        dir_x <= 1'b1;// 改为向右
    else
        dir_x <= dir_x;
end
//dir_y
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        dir_y <= 1'b1;// 初始向下
    else    if(frame_end && dir_y && offset_y + PIC_H >= V_VAILD)//到下边界
        dir_y <= 1'b0;
    else    if(frame_end && !dir_y && offset_y <= 16'd0)//到上边界
        dir_y <= 1'b1;
    else
        dir_y <= dir_y;
end
//frame_end
assign   frame_end = (cnt_h == H_TOTAL - 1'b1 && cnt_v == V_TOTAL - 1'b1);
//rden与q之间存在2拍延迟,每一行提前2个时钟周期读取(由于dx没有负值,将dx坐标轴提前2拍)
assign  rden = (dx >= offset_x
              &&dx <  offset_x + PIC_W
              &&dy >= offset_y
              &&dy <  offset_y + PIC_H)
                ? 1'b1 : 1'b0 ;
//addr
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        addr <= 14'd0;
    else    if(rden)
        addr <= addr + 1'b1;
    else    if(frame_end)//addr == PIC_W * PIC_H
        addr <= 14'd0;
    else
        addr <= addr;
end
rom_16x16384    rom_16x16384_inst (
    .address( addr      ),
    .clock  ( clk       ),
    .rden   ( rden      ),
    .q      ( q         )
);
////////////////////////////////////////////最终RGB输出
assign  rgb = (dx  >= offset_x + 2'd2            //dx坐标轴提前2拍,rgb要和q保持对齐
              &&dx <  offset_x + PIC_W + 2'd2
              &&dy >= offset_y
              &&dy <  offset_y + PIC_H) ? q : BLACK ;

endmodule

顶层模块vga_dynamic_pic

Matlab 复制代码
module  vga_dynamic_pic(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire[15:0]  rgb     ,
    output  wire        hsync   ,
    output  wire        vsync
);

wire        clk_40Mhz;
wire        locked   ;

pll_40Mhz   pll_40Mhz_inst (
    .areset ( ~rst_n    ),
    .inclk0 ( clk       ),
    .c0     ( clk_40Mhz ),
    .locked ( locked    )
);

vga_ctrl    vga_ctrl_inst(
    .clk   (clk_40Mhz   ) ,
    .rst_n (locked      ) ,

    .rgb   (rgb         ) ,
    .hsync (hsync       ) ,
    .vsync (vsync       )
);

endmodule

编写仿真代码

Matlab 复制代码
`timescale  1ns/1ps
module  vga_dynamic_pic_tb();

reg     clk     ;
reg     rst_n   ;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    #123
    rst_n = 1'b1;
end

always #10 clk = ~clk;

vga_dynamic_pic   vga_dynamic_pic_inst(
    .clk   (clk  ) ,
    .rst_n (rst_n) ,

    .rgb   () ,
    .hsync () ,
    .vsync ()
);
endmodule

仿真验证

第一帧数据rden在坐标(0,0)拉高的,q和rgb是与有效区域(cnt_h:216-1015)(cnt_v:27-626)对齐的,第一帧数据的第一行的开始位置没有问题,addr也没有问题。

第一帧数据的第一行的结束位置没有问题,宽度为130,addr也没有问题。

第一帧数据的高度为72没有问题,第一帧数据验证通过。

扫描一帧结束标志信号没有问题,显示坐标更新没有问题,addr复位没有问题。

第二帧数据rden在坐标(1,1)处拉高,相对的q和rgb也是步进为1进行对齐的,说明第二帧数据开始也没有问题。

仿真验证通过。

上板验证

https://live.csdn.net/v/532820