FPGA通信之VGA

文章目录

为了做图像处理, 现在我们开始研究VGA

基本概念:

水平扫描:

  • 有效数据段:显示图像的部分。
  • 右边界区域(Right Border):扫描完有效数据后的空白区域。
  • 水平前沿(Front Porch):扫描完有效数据但未收到同步信号前的空白时间段
  • 行同步信号脉冲(Sync Pulse):通知电子枪回到左侧。
  • 水平后沿(Back Porch):电子枪回到左侧的时间段。
  • 左边界区域(LEFT Border):显示图像前的空白区域。

垂直扫描:

  • 有效行段:显示图像的行。
  • 下边界区域(Bottom Border):扫描完所有行后的空白区域。
  • 垂直前沿(Front Porch):扫描完所有行但未收到同步信号前的空白时间段
  • 场同步信号脉冲(Sync Pulse):通知电子枪回到顶部。
  • 垂直后沿(Back Porch):电子枪回到顶部的时间段。
  • 上边界区域(Top Border):显示图像前的空白区域。

时序如下:



基于上述对VGA时序的详细分析,设计一个VGA控制器主要也就包括行计数器、场计数器,以及行、场同步信号在合适的时刻产生低电平脉冲,以及在显示有效区域将图像数据输出

  • 时间节点分析:
    行同步有效: 96个clk --> 0,1,2----95 --->95的下一个时钟加入下一步
    回扫时间: 40个clk --> 96, 97---135 -->135的下一个时钟加入下一步
    左缓冲边界: 8个clk --> 136,137---143 -->143的下一个时钟加入下一步
    数据段: 640个clk--> 144, 145--783 -->783的下一个时钟加入下一步
    右缓冲边界: 8个clk --> 784, 785--791 -->791的下一个时钟加入下一步
    等待回扫: 8个clk --> 792, 793--799 -->799的下一个时钟加入下一步
  • 现在对时钟的感知有以下进步:
    1. 三个点确定两个线段, 也就是两个时间段
    2. 但是现在应该有一个模型在脑子里, 也就是每一个时间点 右方 都有一个自己的时间段
    3. 假如从某个时间节点x开始要维持y个时间单元
    4. 根据第二点结论, 除原点x外我们需要y-1个时间点, 那么我们可以计算出最后一个时间节点的值为x+y-1, 例如从零时刻开始维持8个时钟周期, 那么最后一个时刻对应的节点为7

端口设计

or

疑问

为什么需要输出那么多端口

因为这个模块不是顶层模块 , 所以需要增加很多输出, 作为内部变量方便顶层访问, 比如输出的计数器, 顶层模块访问后可以知道当前显示屏光标的位置,就可以在特定区域实现特定控制

如下为常用的顶层

module VGA (

input i_clk,

input i_arstn,

input 23:0 i_data,

output o_h_sync,

output o_v_sync,

output 23:0 o_data

// 可选隐藏:o_VGA_clk, o_VGA_BLK, o_VGA_ho_cnt, o_VGA_ve_cnt

);

而且, FPGA在连接显示屏前还需要一个DAC芯片(数字转模拟)

如图:顶层模块只需要三个干净的输出,野火电子教程

  1. 系统上电后,板卡传入系统时钟(sys_clk)和复位信号(sys_rst_n)到顶层模块;
  2. 系统时钟由顶层模块传入时钟生成模块(clk_gen),分频产生VGA工作时钟(vga_clk),作为图像数据生成模块(vga_pic)和VGA时序控制模块(vga_ctrl)的工作时钟;
  3. 图像数据生成模块以VGA时序控制模块传入的像素点坐标(pix_x,pix_y)为约束条件,生成待显示彩条图像的色彩信息(pix_data);
  4. 图像数据生成模块生成的彩条图像色彩信息传入VGA时序控制模块,在模块内部使用使能信号滤除掉非图像显示有效区域的图像数据,产生RGB色彩信息(rgb),在行、场同步信号(hsync、vsync)的同步作用下,将RGB色彩信息扫描显示到VGA显示器,显示出彩条图像

不输出时钟怎么保证电子枪移动速度符合时序

电子枪移动速度的同步原理

(1) 模拟信号的"自时钟"特性

CRT显示器内部的行扫描电路本质上是一个模拟振荡器,其振荡频率由以下因素锁定:
HSYNC信号的频率:显示器会强制将自身的行扫描频率与输入的HSYNC信号同步(类似锁相环PLL的原理)。

例如:在640x480@60Hz模式下,HSYNC频率为31.47kHz,显示器会自动调整电子枪的水平扫描速度以匹配这一频率。

(2) 电子枪的物理运动控制

行扫描电路:CRT内部的行偏转线圈(由锯齿波电流驱动)控制电子枪的水平移动速度。
锯齿波的斜率(即电子枪移动速度)由显示器电路根据HSYNC频率自动调整。

FPGA的RGB数据速率必须与电子枪的物理扫描速度匹配,但这通过以下方式保证:
FPGA以固定的像素时钟(如25.175MHz)生成RGB数据。

由于HSYNC频率是像素时钟的衍生信号*(如每800个像素时钟触发一次HSYNC),因此电子枪的移动速度自然与FPGA数据同步。

VGA转HDMI

https://blog.csdn.net/gslscyx/article/details/137930810

仿真电路图

调用锁相环IP生成25Mhz信号

代码

csharp 复制代码
`timescale 1ns / 1ps
//****************************************VSCODE PLUG-IN**********************************// 
//---------------------------------------------------------------------------------------- 
// IDE :                   VSCODE      
// VSCODE plug-in version: Verilog-Hdl-Format-2.4.20240526
// VSCODE plug-in author : Jiang Percy 
//---------------------------------------------------------------------------------------- 
//****************************************Copyright (c)***********************************// 
// Copyright(C)            COMPANY_NAME
// All rights reserved      
// File name:               
// Last modified Date:     2025/05/17 22:22:41 
// Last Version:           V1.0 
// Descriptions:            
//---------------------------------------------------------------------------------------- 
// Created by:             USER_NAME
// Created date:           2025/05/17 22:22:41 
// Version:                V1.0 
// TEXT NAME:              VGA_top.v 
// PATH:                   D:\vivado\VGA\VGA_top.v 
// Descriptions:            
//                          
//---------------------------------------------------------------------------------------- 
//****************************************************************************************// 

module VGA_top(
    input                               sys_clk                    ,
    input                               sys_rst_n                  ,
    output                              o_horizon_syn              ,
    output                              o_vertical_syn             ,
    output             [  23: 0]        o_VGA_data                  
);
    //---------------------------------------------------------------------------------------
    // PLL锁相环IP调用, 将时钟频率定义为25MHZ                                                                                    
    //---------------------------------------------------------------------------------------
    wire                                PPL_clk                     ;//锁相环
    wire                                locked                      ;
    clk_wiz_0 instance_name
   (
    // Clock out ports
    .clk_out1                           (                          ),// output clk_out1
    .clk_out2                           (                          ),// output clk_out2
    .clk_out3                           (                          ),// output clk_out3
    .clk_out4                           (PPL_clk                   ),// output clk_out4
    // Status and control signals
    .reset                              (~sys_rst_n                     ),// input reset
    .locked                             (locked                    ),// output locked
   // Clock in ports
    .clk_in1                            (sys_clk)                  );
    //---------------------------------------------------------------------------------------
    //                                                                                     
    //---------------------------------------------------------------------------------------
    
    wire               [  23: 0]        w_VGA_data                  ;
    wire                                w_VGA_clk                   ;
    wire                                w_VGA_BLK                   ;
    wire               [   9: 0]        w_cnt_horizon               ;
    wire               [   9: 0]        w_cnt_vertical              ;
VGA_crl u_VGA(
    .i_clk                              (PPL_clk                   ),
    .i_arstn                            (sys_rst_n   && locked     ),
    .i_data                             (w_VGA_data                ),
    .o_h_sync                           (o_horizon_syn             ),
    .o_v_sync                           (o_vertical_syn            ),
    .o_data                             (o_VGA_data                ),
    .o_VGA_clk                          (w_VGA_clk                 ),// 输出一个同步时钟, 基于工程实践, 设置为输入时钟的反时钟更佳
    .o_VGA_BLK                          (w_VGA_BLK                 ),// 设置一个消隐信号, 似乎用不到
    .o_VGA_ho_cnt                       (w_cnt_horizon             ),// 方便顶层读取当前扫描位置
    .o_VGA_ve_cnt                       (w_cnt_vertical            ) // 方便顶层读取现在扫描位置
);
   
  
VGA_pic u_VGA_pic(
    .clk                                (PPL_clk                   ),
    .rst_n                              (sys_rst_n    && locked    ),
    .i_cnt_horizon                      (w_cnt_horizon             ),// 从visible像素开始计算
    .i_cnt_vertical                     (w_cnt_vertical            ),// 从visible像素开始计算
    .o_data                             (w_VGA_data                ) 
);
                                                               
                                                                   
endmodule
csharp 复制代码
// verilog 不支持链式条件, 必须分开
`timescale 1ns / 1ps
module VGA_pic(
    input                               clk                        ,
    input                               rst_n                      ,
    input              [   9: 0]        i_cnt_horizon              ,//从visible像素开始计算
    input              [   9: 0]        i_cnt_vertical             ,//从visible像素开始计算
    output             [  23: 0]        o_data                      
);
    //---------------------------------------------------------------------------------------
     // 时序参数定义:
    parameter                           VGA_HS_end                 = 10'd96,//水平同步器维持时间
                VGA_H_bach_Porch=10'd40,
                VGA_left_border=10'd8,
                VGA_H_DATA_time=10'd640,
                VGA_right_border=10'd8,
                VGA_H_front_Porch=10'd8;

    parameter                           period_horizon             = VGA_HS_end+VGA_H_bach_Porch+VGA_left_border+VGA_H_DATA_time+VGA_right_border+VGA_H_front_Porch;
    parameter                           VGA_h_visible_start        = VGA_HS_end+VGA_H_bach_Porch+VGA_left_border-1;
    parameter                           VGA_h_visible_end          = VGA_h_visible_start+VGA_H_DATA_time;
    
    parameter                           VGA_VS_end                 = 10'd2 ,//垂直同步器维持时间
                VGA_V_bach_Porch=10'd25,
                VGA_top_border=10'd8,
                VGA_V_DATA_time=10'd480,
                VGA_bottom_border=10'd8,
                VGA_V_front_Porch=10'd2;

    parameter                           period_vertical            = VGA_VS_end+VGA_V_bach_Porch+VGA_top_border +VGA_V_DATA_time+VGA_bottom_border+VGA_V_front_Porch;
    parameter                           VGA_v_visible_start        = VGA_VS_end+VGA_V_bach_Porch+VGA_top_border-1;
    parameter                           VGA_v_visible_end          = VGA_v_visible_start+VGA_V_DATA_time;

   
   
    //---------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------
    //定义颜色编码
    localparam
        BLACK = 24'h000000,                                         //黑色
        BLUE = 24'h0000FF,                                          //蓝色
        RED = 24'hFF0000,                                           //红色
        PURPPLE =24'hFF00FF,                                        //紫色
        GREEN = 24'h00FF00,                                         //绿色
        CYAN = 24'h00FFFF,                                          //青色
        YELLOW = 24'hFFFF00,                                        //黄色
        WHITE = 24'hFFFFFF;                                         //白色
    //定义每个像素块的默认显示颜色值
    localparam
        R0_C0 = BLACK,                                              //第0行0列像素块
        R0_C1 = BLUE,                                               //第0行1列像素块
        R1_C0 = RED,                                                //第1行0列像素块
        R1_C1 = PURPPLE,                                            //第1行1列像素块
        R2_C0 = GREEN,                                              //第2行0列像素块
        R2_C1 = CYAN,                                               //第2行1列像素块
        R3_C0 = YELLOW,                                             //第3行0列像素块
        R3_C1 = WHITE;                                              //第3行1列像素块                                                                                    
    //---------------------------------------------------------------------------------------
    //---------------------------------------------------------------------------------------
    // 列检测
    wire                                C0_act                      ;
    assign                              C0_act                      = (0 <= i_cnt_horizon && i_cnt_horizon<320);
    wire                                C1_act                      ;
    assign                              C1_act                      = (320 <= i_cnt_horizon && i_cnt_horizon<640);
    // 行检测
    wire                                R1_act,R2_act,R3_act,R0_act  ;
    assign                              R0_act                      = (0<=i_cnt_vertical&& i_cnt_vertical<120);
    assign                              R1_act                      = (120<=i_cnt_vertical&&i_cnt_vertical<240);
    assign                              R2_act                      = (240<=i_cnt_vertical&& i_cnt_vertical<360);
    assign                              R3_act                      = (360<=i_cnt_vertical&& i_cnt_vertical<480);
    //---------------------------------------------------------------------------------------      
    reg                [  23: 0]        r_VGA_data                  ;
    always@(*)begin
        if (R3_act&C1_act) begin
            r_VGA_data = R3_C1;
        end
        else if (R3_act&C0_act) begin
            r_VGA_data = R3_C0;
        end
        else if (R2_act&C1_act) begin
            r_VGA_data = R2_C1;
        end
        else if (R2_act&C0_act) begin
            r_VGA_data = R2_C0;
        end
        else if (R1_act&C1_act) begin
            r_VGA_data = R1_C1;
        end
        else if (R1_act&C0_act) begin
            r_VGA_data = R1_C0;
        end
        else if (R0_act&C1_act) begin
            r_VGA_data = R0_C1;
        end
        else if (R0_act&C0_act) begin
            r_VGA_data = R0_C0;
        end
        else r_VGA_data =24'd0;
    end
    assign                              o_data                      = r_VGA_data;
endmodule
csharp 复制代码
module VGA_crl (
    input                               i_clk                      ,
    input                               i_arstn                    ,
    input              [  23: 0]        i_data                     ,

    output                              o_h_sync                   ,
    output                              o_v_sync                   ,
    output             [  23: 0]        o_data                     ,
    output                              o_VGA_clk                  ,//输出一个同步时钟, 基于工程实践, 设置为输入时钟的反时钟更佳
    output                              o_VGA_BLK                  ,//设置一个消隐信号, 似乎用不到
    output             [   9: 0]        o_VGA_ho_cnt               ,//方便顶层读取当前扫描位置
    output             [   9: 0]        o_VGA_ve_cnt                //方便顶层读取现在扫描位置             
);
   //---------------------------------------------------------------------------------------
   // 时序参数定义:
    parameter                           VGA_HS_end                 = 10'd96,//水平同步器维持时间
                VGA_H_bach_Porch=10'd40,
                VGA_left_border=10'd8,
                VGA_H_DATA_time=10'd640,
                VGA_right_border=10'd8,
                VGA_H_front_Porch=10'd8;

    parameter                           period_horizon             = VGA_HS_end+VGA_H_bach_Porch+VGA_left_border+VGA_H_DATA_time+VGA_right_border+VGA_H_front_Porch;
    parameter                           VGA_h_visible_start        = VGA_HS_end+VGA_H_bach_Porch+VGA_left_border-1;
    parameter                           VGA_h_visible_end          = VGA_h_visible_start+VGA_H_DATA_time;
    
    parameter                           VGA_VS_end                 = 10'd2 ,//垂直同步器维持时间
                VGA_V_bach_Porch=10'd25,
                VGA_top_border=10'd8,
                VGA_V_DATA_time=10'd480,
                VGA_bottom_border=10'd8,
                VGA_V_front_Porch=10'd2;

    parameter                           period_vertical            = VGA_VS_end+VGA_V_bach_Porch+VGA_top_border +VGA_V_DATA_time+VGA_bottom_border+VGA_V_front_Porch;
    parameter                           VGA_v_visible_start        = VGA_VS_end+VGA_V_bach_Porch+VGA_top_border-1;
    parameter                           VGA_v_visible_end          = VGA_v_visible_start+VGA_V_DATA_time;

   
   
   //---------------------------------------------------------------------------------------
    
    reg                [   9: 0]        r_cnt_h                     ;// horizon counter
    reg                [   9: 0]        r_cnt_v                     ;// vertical counter

    always @(posedge i_clk or negedge i_arstn) begin
        if (!i_arstn) begin
            r_cnt_h <= 0;
        end
        else begin
            if (r_cnt_h == period_horizon-1) begin
                r_cnt_h <= 0;
            end
            else begin
                r_cnt_h <= r_cnt_h + 1;
            end
        end
    end
    always @(posedge i_clk or negedge i_arstn) begin
        if (!i_arstn) begin
            r_cnt_v <= 0;
        end
        else begin
            if (r_cnt_h == period_horizon-1) begin
                if (r_cnt_v == period_vertical-1) begin
                    r_cnt_v <= 0;
                end
                else begin
                    r_cnt_v <= r_cnt_v + 1;
                end
            end
            else r_cnt_v <= r_cnt_v;
        end
    end

    wire                                w_VGA_HS                    ;// 行同步信号
    wire                                w_VGA_VS                    ;// 场同步信号
    assign                              w_VGA_HS                    = (r_cnt_h > VGA_HS_end-1);
    assign                              w_VGA_VS                    = (r_cnt_v > VGA_VS_end-1);
/*
    时间节点分析:
        行同步有效: 96个clk --> 0,1,2----95 --->95的下一个时钟加入下一步
        回扫时间:   40个clk --> 96, 97---135 -->135的下一个时钟加入下一步
        左缓冲边界: 8个clk  --> 136,137---143 -->143的下一个时钟加入下一步
        数据段:     640个clk--> 144, 145--783 -->783的下一个时钟加入下一步
        右缓冲边界: 8个clk  --> 784, 785--791 -->791的下一个时钟加入下一步
        等待回扫:   8个clk  --> 792, 793--799  -->799的下一个时钟加入下一步
 */
    wire                                w_dat_act                   ;// 数据使能信号, 我们只希望在中间输出数据, 因此在这个阶段使能, 注意不输出数据不代表直接关闭电子枪
    assign                              w_dat_act                   = (VGA_h_visible_start < r_cnt_h && r_cnt_h <=VGA_h_visible_end) && (VGA_v_visible_start < r_cnt_v && r_cnt_v <= VGA_v_visible_end);
   
    
    //RGB控制
    wire               [  23: 0]        w_VGA_GRB                   ;
    assign                              w_VGA_GRB                   = (w_dat_act)? i_data:24'd0;
    assign                              o_data                      = w_VGA_GRB;
    //同步时钟控制
    assign                              o_VGA_clk                   = ~i_clk;
    //行场同步信号
    assign                              o_h_sync                    = w_VGA_HS;
    assign                              o_v_sync                    = w_VGA_VS;
    //消隐信号, BLK等同于data_act,   高电平代表着输出数据(visible)的时间锻, 可能是低电平有效, 这里和原理不太一样,原理上消隐信号应该只在等待回扫和回扫时间有效
    assign                              o_VGA_BLK                   = w_dat_act;
    //行场的计数器输出, 方便顶层读取当前位置
    assign                              o_VGA_ho_cnt                = (w_dat_act)? (r_cnt_h-VGA_h_visible_start):10'd0;
    assign                              o_VGA_ve_cnt                = (w_dat_act)? (r_cnt_v-VGA_v_visible_start):10'd0;
endmodule

总结:野火电子yyds

将VGA控制器分成三个子模块:

  1. PLL生成固定时钟
  2. VGA_CRL: 实现VGA协议的基本时序控制, 输出VGA数据以及可视区计数器
  3. VGA_PIC: 通过读取CRL的可视区计数器, 知道当前可视区像素位置, 将要输出的图像写在这里, 并把图像背后的像素数据发送给CRL, CRL会通过内部指针决定什么时候传递出顶层端口
相关推荐
仲南音2 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner2 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20202 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy3 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia3 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20203 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子3 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安4 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun4 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun4 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议