基于FPGA的VGA协议实现

目录

  • [一、 内容概要](#一、 内容概要)
  • [二、 了解VGA](#二、 了解VGA)
    • [2.1 概念](#2.1 概念)
  • [三、 VGA基础显示](#三、 VGA基础显示)
    • [3.1 条纹显示](#3.1 条纹显示)
    • [3.2 显示字符](#3.2 显示字符)
      • [3.2.1 准备工作](#3.2.1 准备工作)
      • [3.2.2 提取文字](#3.2.2 提取文字)
      • [3.2.3 编写代码](#3.2.3 编写代码)
      • [3.2.4 编译烧录](#3.2.4 编译烧录)
    • [3.3 显示图像](#3.3 显示图像)
      • [3.3.1 准备工作](#3.3.1 准备工作)
      • [3.3.2 实现例程](#3.3.2 实现例程)
      • [3.3.3 编译烧录](#3.3.3 编译烧录)
  • 四、参考链接

一、 内容概要

  1. 深入了解VGA协议,理解不同显示模式下的VGA控制时序参数(行频、场频、水平/垂直同步时钟周期、显示后沿/前沿等概念和计算方式)

  2. 通过Verilog编程,在至少2种显示模式下(640480@60Hz,1024768@75Hz)分别实现以下VGA显示,并对照VGA协议信号做时序分析:1)屏幕上显示彩色条纹;2)显示自定义的汉字字符(姓名-学号);3)(FPGA方向同学做)输出一幅彩色图像。

二、 了解VGA

2.1 概念

VGA(Video Graphics Array)视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准。VGA接口即电脑采用VGA标准输出数据的专用接口。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。

VGA接口是一种D型接口,上面共有15针孔,分成三排,每排五个。 其中,除了2根NC(Not Connect)信号、3根显示数据总线和5个GND信号,比较重要的是3根RGB彩色分量信号和2根扫描同步信号HSYNC和VSYNC针。VGA接口中彩色分量采用RS343电平标准。RS343电平标准的峰值电压为1V。VGA接口是显卡上应用最为广泛的接口类型,多数的显卡都带有此种接口。有些不带VGA接口而带有DVI(Digital Visual Interface数字视频接口)接口的显卡,也可以通过一个简单的转接头将DVI接口转成VGA接口,通常没有VGA接口的显卡会附赠这样的转接头。

外观:


管脚图 :

通信协议 :

VGA接口的通信协议主要基于模拟信号传输,具体包括:

  1. 视频信号:通过VGA接口传输的信号包括RGB(红绿蓝)颜色分量和同步信号。
  2. 同步信号:包括水平同步(HSync)和垂直同步(VSync)信号,用于同步显示器的刷新率。
  3. 控制信号:如DDC(Display Data Channel),用于传输显示器的配置信息。
    通信方式:
  4. 模拟传输:VGA接口通过模拟信号传输视频数据,这意味着信号在传输过程中可能会受到干扰,导致画质下降。
  5. 信号类型:VGA接口传输的信号包括RGB颜色分量信号和同步信号。
  6. 信号传输:信号通过VGA电缆中的导线传输,通常需要屏蔽以减少电磁干扰。
    特点:
  7. 易用性:VGA接口易于使用,且在很多设备上都能找到。
  8. 普及性:由于其历史悠久,VGA接口在很多老旧设备上仍然得到支持。
  9. 局限性:由于是模拟信号,VGA在长距离传输或高分辨率显示时可能会遇到信号衰减和画质下降的问题。

三、 VGA基础显示

VGA管脚说明:

3.1 条纹显示

直接编译烧录此代码就行

java 复制代码
module VGA_colorbar_test(
OSC_50,     //原CLK2_50时钟信号
VGA_CLK,    //VGA自时钟
VGA_HS,     //行同步信号
VGA_VS,     //场同步信号
VGA_BLANK,  //复合空白信号控制信号  当BLANK为低电平时模拟视频输出消隐电平,此时从R9~R0,G9~G0,B9~B0输入的所有数据被忽略
VGA_SYNC,   //符合同步控制信号      行时序和场时序都要产生同步脉冲
VGA_R,      //VGA绿色
VGA_B,      //VGA蓝色
VGA_G);     //VGA绿色
 input OSC_50;     //外部时钟信号CLK2_50
 output VGA_CLK,VGA_HS,VGA_VS,VGA_BLANK,VGA_SYNC;
 output [7:0] VGA_R,VGA_B,VGA_G;
 parameter H_FRONT = 16;     //行同步前沿信号周期长
 parameter H_SYNC = 96;      //行同步信号周期长
 parameter H_BACK = 48;      //行同步后沿信号周期长
 parameter H_ACT = 640;      //行显示周期长
 parameter H_BLANK = H_FRONT+H_SYNC+H_BACK;        //行空白信号总周期长
 parameter H_TOTAL = H_FRONT+H_SYNC+H_BACK+H_ACT;  //行总周期长耗时
 parameter V_FRONT = 11;     //场同步前沿信号周期长
 parameter V_SYNC = 2;       //场同步信号周期长
 parameter V_BACK = 31;      //场同步后沿信号周期长
 parameter V_ACT = 480;      //场显示周期长
 parameter V_BLANK = V_FRONT+V_SYNC+V_BACK;        //场空白信号总周期长
 parameter V_TOTAL = V_FRONT+V_SYNC+V_BACK+V_ACT;  //场总周期长耗时
 reg [10:0] H_Cont;        //行周期计数器
 reg [10:0] V_Cont;        //场周期计数器
 wire [7:0] VGA_R;         //VGA红色控制线
 wire [7:0] VGA_G;         //VGA绿色控制线
 wire [7:0] VGA_B;         //VGA蓝色控制线
 reg VGA_HS;
 reg VGA_VS;
 reg [10:0] X;             //当前行第几个像素点
 reg [10:0] Y;             //当前场第几行
 reg CLK_25;
 always@(posedge OSC_50)begin 
      CLK_25=~CLK_25;         //时钟
 end 

 assign VGA_SYNC = 1'b0;   //同步信号低电平
 assign VGA_BLANK = ~((H_Cont<H_BLANK)||(V_Cont<V_BLANK));  //当行计数器小于行空白总长或场计数器小于场空白总长时,空白信号低电平
 assign VGA_CLK = ~CLK_to_DAC;  //VGA时钟等于CLK_25取反
 assign CLK_to_DAC = CLK_25;

 always@(posedge CLK_to_DAC)begin
        if(H_Cont<H_TOTAL)           //如果行计数器小于行总时长
            H_Cont<=H_Cont+1'b1;      //行计数器+1
        else H_Cont<=0;              //否则行计数器清零
        if(H_Cont==H_FRONT-1)        //如果行计数器等于行前沿空白时间-1
            VGA_HS<=1'b0;             //行同步信号置0
        if(H_Cont==H_FRONT+H_SYNC-1) //如果行计数器等于行前沿+行同步-1
            VGA_HS<=1'b1;             //行同步信号置1
        if(H_Cont>=H_BLANK)          //如果行计数器大于等于行空白总时长
            X<=H_Cont-H_BLANK;        //X等于行计数器-行空白总时长   (X为当前行第几个像素点)
        else X<=0;                   //否则X为0
end

 always@(posedge VGA_HS)begin
        if(V_Cont<V_TOTAL)           //如果场计数器小于行总时长
            V_Cont<=V_Cont+1'b1;      //场计数器+1
        else V_Cont<=0;              //否则场计数器清零
        if(V_Cont==V_FRONT-1)       //如果场计数器等于场前沿空白时间-1
            VGA_VS<=1'b0;             //场同步信号置0
        if(V_Cont==V_FRONT+V_SYNC-1) //如果场计数器等于行前沿+场同步-1
            VGA_VS<=1'b1;             //场同步信号置1
        if(V_Cont>=V_BLANK)          //如果场计数器大于等于场空白总时长
            Y<=V_Cont-V_BLANK;        //Y等于场计数器-场空白总时长    (Y为当前场第几行)  
        else Y<=0;                   //否则Y为0
end

 reg valid_yr;

 always@(posedge CLK_to_DAC)begin
    if(V_Cont == 10'd32)         //场计数器=32时
        valid_yr<=1'b1;           //行输入激活
    else if(V_Cont==10'd512)     //场计数器=512时
        valid_yr<=1'b0;           //行输入冻结
 end

 wire valid_y=valid_yr;       //连线   
 reg valid_r;     

 always@(posedge CLK_to_DAC)begin
    if((H_Cont == 10'd32)&&valid_y)     //行计数器=32时
        valid_r<=1'b1;                   //像素输入激活
    else if((H_Cont==10'd512)&&valid_y) //行计数器=512时 
        valid_r<=1'b0;                   //像素输入冻结
 end

 wire valid = valid_r;               //连线
 assign x_dis=X;       //连线X
 assign y_dis=Y;       //连线Y
 // reg[7:0] char_bit;
 // always@(posedge CLK_to_DAC)
 //     if(X==10'd144)char_bit<=9'd240;   //当显示到144像素时准备开始输出图像数据
 //     else if(X>10'd144&&X<10'd384)     //左边距屏幕144像素到416像素时    416=144+272(图像宽度)
 //         char_bit<=char_bit-1'b1;       //倒着输出图像信息
         
 reg[29:0] vga_rgb;                //定义颜色缓存
 always@(posedge CLK_to_DAC) begin
     if(X>=0&&X<200)begin    //X控制图像的横向显示边界:左边距屏幕左边144像素  右边界距屏幕左边界416像素
         vga_rgb<=30'hffffffffff;   //白色
     end
     else if(X>=200&&X<400)begin
         vga_rgb<=30'hf00ff65f1f;   
     end
     else if(X>=400&&X<600)begin
         vga_rgb<=30'h9563486251; 
     end
     else begin
         vga_rgb<=30'h5864928654; 
     end
 end
 assign VGA_R=vga_rgb[23:16];
 assign VGA_G=vga_rgb[15:8];
 assign VGA_B=vga_rgb[7:0];
endmodule

结果:

此640*480@60hz,修改模式照着上面参数表修改参数即可

可以参考表:

也可以直接问AI:

3.2 显示字符

3.2.1 准备工作

  1. 汉字点阵提取软件

链接:https://pan.baidu.com/s/1EInMlOq8J4y0kcXMPFYUGQ

提取码:1234

  1. 转换程序(自己写的,将提取的字模转换为verilog变量)
java 复制代码
//CharacterData.java
import java.util.List;

public class CharacterData {
    private List<String> charData;
    private String charaName;

    public CharacterData(List<String> charData, String charaName) {
        this.charData = charData;
        this.charaName = charaName;
    }

    // Getters and Setters
    public List<String> getCharData() {
        return charData;
    }

    public void setCharData(List<String> charData) {
        this.charData = charData;
    }

    public String getCharaName() {
        return charaName;
    }

    public void setCharaName(String charaName) {
        this.charaName = charaName;
    }
}
java 复制代码
//CharDataParser.java
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class CharDataParser {

    public static List<CharacterData> parseCharDataFromFile(String filePath) {
        List<CharacterData> charDataList = new ArrayList<>();
        List<String> currentData = new ArrayList<>();
        String currentName = null;

        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim(); // 去除行首行尾的空白字符
                String[] items = line.split(",");
                // 查找 "//" 并检查其后是否有非空白字符
                int commentIndex = line.indexOf("//");
                if (commentIndex != -1) {
                    // 截取 "//" 后面的字符名称
                    currentName = line.substring(commentIndex + 2).trim();
                    // 截取 "//" 之前的数据作为字模数据
                    items = line.substring(0, commentIndex).trim().split(",");
                    for (int i =0; i < items.length; i++) {
                        currentData.add(items[i]);
                    }
                    // 如果当前已经有字符名称,保存当前数据
                    if (currentName != null && !currentData.isEmpty()) {
                        charDataList.add(new CharacterData(currentData, currentName));
                        currentData = new ArrayList<>(); // 重置当前数据列表
                        currentName = null; // 重置字符名称
                    }
                } else if (!line.isEmpty()) {
                    for (int i =0; i < items.length; i++) {
                        currentData.add(items[i]);
                    }
                }
            }
            // 检查文件末尾是否有未保存的数据
            if (currentName != null && !currentData.isEmpty()) {
                charDataList.add(new CharacterData(currentData, currentName));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return charDataList;
    }
    public static List<String> parseToVGA(List<CharacterData> charDataList) {
        List<String> charData = new ArrayList<>();

        for (int i = 0 ;i<16;i++){
            String data="";
            for (int j = 0; j<charDataList.size();j++) {
                data+=charDataList.get(j).getCharData().get(i*2);
                data+=charDataList.get(j).getCharData().get(i*2+1);
            }
            charData.add(data);
        }
        return charData;
    }
    public static void writeToFile(List<String> charDataList, String filePath) {
        int dataSize = 16; // 每行数据的大小
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {

            // 写入文件
            for (int i = 0; i < charDataList.size(); i++) {
                String lineName = "char_line" + String.format("%02x", i) + "=240'h";
                writer.write(lineName);
                writer.write(charDataList.get(i));
                if(i==charDataList.size()-1){
                    writer.write(";");
                }else {
                    writer.write(",");
                }
                writer.newLine(); // 添加换行符以便分隔每行数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        // 假设你的文件路径是 "path/to/your/notepad.txt"
        List<CharacterData> charDataList = parseCharDataFromFile("E:/file.txt");
        // 打印结果,以验证解析是否正确
        for (CharacterData data : charDataList) {
            System.out.println("Character Name: " + data.getCharaName());
            for (String line : data.getCharData()) {
                System.out.println(line);
            }
        }
        List<String> charDataList2 = parseToVGA(charDataList);
        for (String line : charDataList2) {
            System.out.println(line);
        }
        writeToFile(charDataList2, "E:/file_out.txt");
    }
}

3.2.2 提取文字

打开取字模软件

点击设置进行参数调整

输入文字点击生成字模

这个时候可以手动将字模数据转换成verilog变量,格式是:

VGA是一行一行刷新数据的,共16行,所以需要提取出字模的每一行(每四位为一行)每个字四位四位提取拼接为一行

如"你好":

复制代码
10010800
1001083F
10010820
887F0810
88403F08
4C202404
2C042404
0A04A47F
89142404
88241204
48241404
48440804
28441404
08042204
08052105
08020002

接下来使用下我提供的一键转换代码

首先打开idea新建项目,创建两个文件到main里

修改参数:

建议不要用提供的路径,把相对路径修改为绝对路径,如D:/file.txt

打开输入文件,将字模文件生成的数据复制粘贴到里面

点击运行

然后找到输出文件:

直接复制到代码里面就行

3.2.3 编写代码

vga打印字符代码:

java 复制代码
module vga(
OSC_50,     //原CLK2_50时钟信号
VGA_CLK,    //VGA自时钟
VGA_HS,     //行同步信号
VGA_VS,     //场同步信号
VGA_BLANK,  //复合空白信号控制信号  当BLANK为低电平时模拟视频输出消隐电平,此时从R9~R0,G9~G0,B9~B0输入的所有数据被忽略
VGA_SYNC,   //符合同步控制信号      行时序和场时序都要产生同步脉冲
VGA_R,      //VGA绿色
VGA_B,      //VGA蓝色
VGA_G);     //VGA绿色
 input OSC_50;     //外部时钟信号CLK2_50
 output VGA_CLK,VGA_HS,VGA_VS,VGA_BLANK,VGA_SYNC;
 output [7:0] VGA_R,VGA_B,VGA_G;
 parameter H_FRONT = 16;     //行同步前沿信号周期长
 parameter H_SYNC = 96;      //行同步信号周期长
 parameter H_BACK = 48;      //行同步后沿信号周期长
 parameter H_ACT = 640;      //行显示周期长
 parameter H_BLANK = H_FRONT+H_SYNC+H_BACK;        //行空白信号总周期长
 parameter H_TOTAL = H_FRONT+H_SYNC+H_BACK+H_ACT;  //行总周期长耗时
 parameter V_FRONT = 11;     //场同步前沿信号周期长
 parameter V_SYNC = 2;       //场同步信号周期长
 parameter V_BACK = 31;      //场同步后沿信号周期长
 parameter V_ACT = 480;      //场显示周期长
 parameter V_BLANK = V_FRONT+V_SYNC+V_BACK;        //场空白信号总周期长
 parameter V_TOTAL = V_FRONT+V_SYNC+V_BACK+V_ACT;  //场总周期长耗时
 reg [10:0] H_Cont;        //行周期计数器
 reg [10:0] V_Cont;        //场周期计数器
 wire [7:0] VGA_R;         //VGA红色控制线
 wire [7:0] VGA_G;         //VGA绿色控制线
 wire [7:0] VGA_B;         //VGA蓝色控制线
 reg VGA_HS;
 reg VGA_VS;
 reg [10:0] X;             //当前行第几个像素点
 reg [10:0] Y;             //当前场第几行
 reg CLK_25;
 always@(posedge OSC_50)
    begin 
      CLK_25=~CLK_25;         //时钟
    end 
    assign VGA_SYNC = 1'b0;   //同步信号低电平
    assign VGA_BLANK = ~((H_Cont<H_BLANK)||(V_Cont<V_BLANK));  //当行计数器小于行空白总长或场计数器小于场空白总长时,空白信号低电平
    assign VGA_CLK = ~CLK_to_DAC;  //VGA时钟等于CLK_25取反
    assign CLK_to_DAC = CLK_25;
 always@(posedge CLK_to_DAC)
    begin
        if(H_Cont<H_TOTAL)           //如果行计数器小于行总时长
            H_Cont<=H_Cont+1'b1;      //行计数器+1
        else H_Cont<=0;              //否则行计数器清零
        if(H_Cont==H_FRONT-1)        //如果行计数器等于行前沿空白时间-1
            VGA_HS<=1'b0;             //行同步信号置0
        if(H_Cont==H_FRONT+H_SYNC-1) //如果行计数器等于行前沿+行同步-1
            VGA_HS<=1'b1;             //行同步信号置1
        if(H_Cont>=H_BLANK)          //如果行计数器大于等于行空白总时长
            X<=H_Cont-H_BLANK;        //X等于行计数器-行空白总时长   (X为当前行第几个像素点)
        else X<=0;                   //否则X为0
    end
 always@(posedge VGA_HS)
    begin
        if(V_Cont<V_TOTAL)           //如果场计数器小于行总时长
            V_Cont<=V_Cont+1'b1;      //场计数器+1
        else V_Cont<=0;              //否则场计数器清零
        if(V_Cont==V_FRONT-1)       //如果场计数器等于场前沿空白时间-1
            VGA_VS<=1'b0;             //场同步信号置0
        if(V_Cont==V_FRONT+V_SYNC-1) //如果场计数器等于行前沿+场同步-1
            VGA_VS<=1'b1;             //场同步信号置1
        if(V_Cont>=V_BLANK)          //如果场计数器大于等于场空白总时长
            Y<=V_Cont-V_BLANK;        //Y等于场计数器-场空白总时长    (Y为当前场第几行)  
        else Y<=0;                   //否则Y为0
    end
    reg valid_yr;
 always@(posedge CLK_to_DAC)
    if(V_Cont == 10'd32)         //场计数器=32时
        valid_yr<=1'b1;           //行输入激活
    else if(V_Cont==10'd512)     //场计数器=512时
        valid_yr<=1'b0;           //行输入冻结
    wire valid_y=valid_yr;       //连线   
    reg valid_r;            
 always@(posedge CLK_to_DAC)   
    if((H_Cont == 10'd32)&&valid_y)     //行计数器=32时
        valid_r<=1'b1;                   //像素输入激活
    else if((H_Cont==10'd512)&&valid_y) //行计数器=512时 
        valid_r<=1'b0;                   //像素输入冻结
    wire valid = valid_r;               //连线
    wire[10:0] x_dis;     //像素显示控制信号
    wire[10:0] y_dis;     //行显示控制信号
    assign x_dis=X;       //连线X
    assign y_dis=Y;       //连线Y
        parameter

        char_line00=256'h0000210400200000000000000000000000000000000000000000000000000000,
        char_line01=256'h1FF0108402220000000000000000000000000000000000000000000000000000,
        char_line02=256'h10101088FA220000000000000000000000000000000000000000000000000000,
        char_line03=256'h1010F8002222000007F00FE00FF0008007E01FFC07E007F007E00FE00FF00FE0,
        char_line04=256'h10100BFE23FE0000081830183018078018183008181808181818301830183018,
        char_line05=256'h101010202000000010003818380C0180381C2010381C1000381C3818380C3818,
        char_line06=256'h1FF0102027FE00003000001810180180300C0020300C3000300C001810180018,
        char_line07=256'h000039FCF820000037F0006000180180300C0040300C37F0300C006000180060,
        char_line08=256'h0000542020400000380C01F000600180300C0080300C380C300C01F0006001F0,
        char_line09=256'h3FF8902023FE0000300C001801800180300C0180300C300C300C001801800018,
        char_line0a=256'h200813FE22520000300C000C06000180300C0300300C300C300C000C0600000C,
        char_line0b=256'h200810203A520000300C380C08040180381803003818300C3818380C0804380C,
        char_line0c=256'h20081020E252000018183018300C01801C1003801C1018181C103018300C3018,
        char_line0d=256'h200810204252000007E00FE03FF80FF807E0030007E007E007E00FE03FF80FE0,
        char_line0e=256'h3FF8102002520000000000000000000000000000000000000000000000000000,
        char_line0f=256'h2008102002060000000000000000000000000000000000000000000000000000;

    reg[7:0] char_bit;
    always@(posedge CLK_to_DAC)
        if(X==10'd164)char_bit<=9'd256;   //当显示到164像素时准备开始输出图像数据
        else if(X>10'd164&&X<10'd420)     //左边距屏幕164像素到420像素时    420=164+256(图像宽度)
            char_bit<=char_bit-1'b1;       //倒着输出图像信息
            
    reg[29:0] vga_rgb;                //定义颜色缓存
    always@(posedge CLK_to_DAC) 
        if(X>10'd164&&X<10'd420)    //X控制图像的横向显示边界:左边距屏幕左边164像素  右边界距屏幕左边界420像素
            begin case(Y)            //Y控制图像的纵向显示边界:从距离屏幕顶部160像素开始显示第一行数据
                10'd200:
                if(char_line00[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;  //如果该行有数据 则颜色为红色
                else vga_rgb<=30'b0000000000_0000000000_0000000000;                      //否则为黑色
                10'd201:
                if(char_line01[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd202:
                if(char_line02[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd203:
                if(char_line03[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd204:
                if(char_line04[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000; 
                10'd205:
                if(char_line05[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd206:
                if(char_line06[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000; 
                10'd207:
                if(char_line07[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd208:
                if(char_line08[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000; 
                10'd209:
                if(char_line09[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd210:
                if(char_line0a[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd211:
                if(char_line0b[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd212:
                if(char_line0c[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd213:
                if(char_line0d[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd214:
                if(char_line0e[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                10'd215:
                if(char_line0f[char_bit])vga_rgb<=30'b1111111111_0000000000_0000000000;
                else vga_rgb<=30'b0000000000_0000000000_0000000000;
                default:vga_rgb<=30'h0000000000;   //默认颜色黑色
            endcase 
        end
    else vga_rgb<=30'h000000000;             //否则黑色
    assign VGA_R=vga_rgb[23:16];
    assign VGA_G=vga_rgb[15:8];
    assign VGA_B=vga_rgb[7:0];
endmodule

注意:

代码中这几个部分是控制显示长度的,根据实际情况调整,256是代表宽度,420是代表逆向打印的起始位置,164是起始位置减去长度(420-256),一个汉字16位,可以根据实际情况计算宽度

3.2.4 编译烧录

新建Quartus项目编译烧录

结果:

3.3 显示图像

3.3.1 准备工作

  1. 首先下载位图转换软件
    BMP2MIf
  2. 找到一张图片,转化为位图,可以去在线转换
  3. 把bmp图片转化为mif

3.3.2 实现例程

引入IP核,在右侧找到ip catalog,如果没有则可以从顶部view里面找到打开

搜索找到ROM:1-PORT双击

设置:

words大小取决于图片大小,大于图片大小就行,

24是图片位数,根据实际情况修改

next

取消勾选

next

选择生成的mif文件(24位位图是mif,若是16位应该使用HEX)

勾选然后finish

编写代码:

java 复制代码
//vga_para.v
`define 	vga_640_480

`ifdef	vga_640_480
	`define	H_Right_Border 8
	`define	H_Front_Porch  8
	`define	H_Sync_Time    96
	`define	H_Back_Porch	40
	`define	H_Left_Border	8
	`define	H_Data_Time		640
	`define	H_Total_Time	800
	`define	V_Bottom_Border	8
	`define	V_Front_Porch	2
	`define	V_Sync_Time		2
	`define	V_Back_Porch	25
	`define	V_Top_Border	8
	`define	V_Data_Time		480
	`define	V_Total_Time	525

`elsif	vga_1920_1080
	`define	H_Right_Border 0
	`define	H_Front_Porch  88
	`define	H_Sync_Time    44
	`define	H_Back_Porch	148
	`define	H_Left_Border	0
	`define	H_Data_Time		1920
	`define	H_Total_Time	2200
	`define	V_Bottom_Borde	0
	`define	V_Front_Porch	4
	`define	V_Sync_Time		5
	`define	V_Back_Porch	36
	`define	v_Top_Border	2
	`define	v_Data_Time		1080
	`define	v_Total_Time	1125

	
`endif
java 复制代码
//vga_drive.v
//`define 	vga_1920_1080L
`include "vga_para.v"

module vga_drive(

	input						clk			, //640*480--25.2M
	input						rst_n			,
	input	 		[15:0]	data_display,
	
	output reg	[11:0] 	h_addr		,//数据有效显示区域行地址
	output reg	[11:0]	v_addr		,//数据有效显示区域场地址
	
	output reg				vsync			,
	output reg				hsync			,
	output reg 	[4:0]		vga_r			,
	output reg 	[5:0]		vga_g			,
	output reg 	[4:0]		vga_b			,
	output reg				vga_blk		,//消隐信号
	output					vga_clk		,
	output 					sync			
	
);

wire				clk_25M;	
wire				locked;

pll25	pll_inst (
	.areset 		( ~rst_n ),
	.inclk0     ( clk ),
	.c0 		   ( clk_25M ),
	);
	


assign	vga_clk = clk_25M;

assign	sync = hsync && vsync;

parameter	H_SYNC_STA = 1;
parameter   H_SYNC_STO = `H_Sync_Time;
parameter   H_Data_STA = `H_Right_Border + `H_Front_Porch + `H_Sync_Time;
parameter   H_Data_STO = `H_Right_Border + `H_Front_Porch + `H_Sync_Time + `H_Data_Time;

parameter   V_SYNC_STA = 1;
parameter   V_SYNC_STO = `V_Sync_Time;
parameter   V_Data_STA = `V_Bottom_Border + `V_Front_Porch + `V_Sync_Time;
parameter   V_Data_STO = `V_Bottom_Border + `V_Front_Porch + `V_Sync_Time + `V_Data_Time;


wire				add_h_addr		;
wire				end_h_addr		;


reg	[11:0]	cnt_v_addr;//行地址寄存器
wire				add_cnt_v_addr	;
wire				end_cnt_v_addr	;

reg	[11:0]	cnt_h_addr		;//场地址寄存器
wire				add_cnt_h_addr ;
wire				end_cnt_h_addr ;

always@(posedge	vga_clk or negedge	rst_n)begin
	if(!rst_n)begin
		cnt_h_addr <= 12'd0;
	end
	else if(add_cnt_h_addr)begin
		if(end_cnt_h_addr)
			cnt_h_addr <= 12'd0;
		else
			cnt_h_addr <= cnt_h_addr + 12'd1;
	end
	else begin
		cnt_h_addr <= 12'd0;
	end
		
	
end
assign	add_cnt_h_addr = 1'b1;
assign	end_cnt_h_addr = add_cnt_h_addr && cnt_h_addr >= `H_Total_Time - 1;
 

always@(posedge	vga_clk or negedge	rst_n)begin
	if(!rst_n)begin
		cnt_v_addr <= 12'd0;
	end
	else if(add_cnt_v_addr)begin
		if(end_cnt_v_addr)
			cnt_v_addr <= 12'd0;
		else
			cnt_v_addr <= cnt_v_addr + 12'd1;
	end
	else begin
		cnt_v_addr <= cnt_v_addr;
	end
		
	
end
assign	add_cnt_v_addr = end_cnt_h_addr;
assign	end_cnt_v_addr = add_cnt_v_addr && cnt_v_addr >= `V_Total_Time - 1; 


//行场同步信号
always@(posedge	vga_clk or negedge	rst_n)begin
	if(!rst_n)
		hsync <= 1'b1;
	else if(cnt_h_addr == H_SYNC_STA -1)
		hsync <= 1'b0;
	else if(cnt_h_addr == H_SYNC_STO-1)
		hsync <= 1'b1;
	else
		hsync <= hsync;
end

always@(posedge	vga_clk or negedge	rst_n)begin
	if(!rst_n)
		vsync <= 1'b1;
	else if(cnt_v_addr == V_SYNC_STA -1)
		vsync <= 1'b0;
	else if(cnt_v_addr == V_SYNC_STO-1)
		vsync <= 1'b1;
	else
		vsync <= vsync;
end



//有效显示区域
always@(posedge vga_clk or negedge rst_n)begin
	if(!rst_n)
		h_addr <= 12'b0;
	else	if(cnt_h_addr >= H_Data_STA && cnt_h_addr <= H_Data_STO)begin
		h_addr <= cnt_h_addr - H_Data_STA ;
		vga_blk <= 1'b1;
	end
   else begin
		h_addr <= 12'b0;
		vga_blk <= 1'b0;
	end
	
end
always@(posedge vga_clk or negedge rst_n)begin
	if(!rst_n)
		v_addr <= 12'b0;
	else	if(cnt_v_addr >= V_Data_STA  && cnt_v_addr <= V_Data_STO)begin
		v_addr <= cnt_v_addr - V_Data_STA;
	end
   else begin
		v_addr <= 12'b0;
	end
	
end


//数据显示
always@(posedge vga_clk or negedge rst_n)begin
	if(!rst_n)begin
		vga_r <= 5'h0;
		vga_g <= 6'h0;
		vga_b <= 5'h0;
	end
	else	if(cnt_h_addr >= H_Data_STA - 1 && cnt_h_addr <= H_Data_STO - 1 && 
					cnt_v_addr >= V_Data_STA - 1 && cnt_v_addr <= V_Data_STO - 1)begin
		vga_r <= data_display[15:11];	//data_display[23-:8]
		vga_g <= data_display[10:5];	//data_display[15-:8]
		vga_b <= data_display[4:0];	//data_display[7-:8]
	end
	else begin
		vga_r <= 5'h0;
		vga_g <= 6'h0;
		vga_b <= 5'h0;
	end
		
end








endmodule
java 复制代码
//top.v
module	top(
input						clk,
input						rst_n,
	
 output	 				vsync,	
 output 					hsync,		
 output  	[4:0]		vga_r,		
 output  	[5:0]		vga_g,		
 output  	[4:0]		vga_b,		
 output					vga_clk,
 output					vga_blk,
 output					sync
);





wire	[11:0]   h_addr;
wire	[11:0]   v_addr;
wire	[15:0]	data_display;



vga_drive  inst_vga_drive(
.clk				(clk), //640*480--25.2M
.rst_n			(rst_n),
.data_display	(data_display),

.h_addr			(h_addr),//数据有效显示区域行地址
.v_addr			(v_addr),//数据有效显示区域场地址

.vsync			(vsync),
.hsync			(hsync),
.vga_r			(vga_r),
.vga_g			(vga_g),
.vga_b			(vga_b),
.vga_blk			(vga_blk),
.vga_clk     	(vga_clk),
.sync				(sync)
	
);

data_gen		inst_data_gen(	
.clk				(vga_clk), //640*480--25.2M
.rst_n			(rst_n),
.h_addr			(h_addr),//数据有效显示区域行地址
.v_addr			(v_addr),//数据有效显示区域场地output	

.data_disp	(data_display)
);





endmodule
java 复制代码
//data_gen.v
module data_gen (
    input   wire              clk         , //vga clk 640*480 25.2MHz
    input   wire              rst_n       , //复位信号

    input   wire   [10:0]     h_addr      , //数据有效显示区域行地址
    input   wire   [10:0]     v_addr      , //数据有效显示区域场地址

    output  reg    [23:0]     data_disp     //

);

    
    reg			[ 13:0 ]		rom_address				; // ROM地址
    wire		[ 23:0 ]		rom_data				; // 图片数据

    wire						flag_enable_out2			; // 图片有效区域
    wire						flag_clear_rom_address		; // 地址清零
    wire						flag_begin_h			    ; // 图片显示行
    wire						flag_begin_v			    ; // 图片显示列
    
    parameter	height = 24; // 图片高度
    parameter	width  = 24; // 图片宽度

    reg [ 223:0 ] char_line[ 15:0 ];//16*14个字符=224,224*16的字符存储区
//参数定义
    parameter
        BLACK   = 24'h000000,
        RED     = 24'hFF0000,
        GREEN   = 24'h00FF00,
        BLUE    = 24'h0000FF,
        YELLOW  = 24'hFFFF00,
        SKY_BULE= 24'h00FFFF,
        PURPLE  = 24'hFF00FF,
        GRAY    = 24'hC0C0C0,
        WHITE   = 24'hFFFFFF; 

    always @( posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        data_disp = BLACK;
    end
    else if ( flag_enable_out2 ) begin
        data_disp = rom_data;
    end
    else begin
        data_disp = BLACK;
    end
    end

    //ROM地址计数器
    always @( posedge clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            rom_address <= 0;
        end
        else if ( flag_clear_rom_address ) begin //计数满清零
            rom_address <= 0;
        end
            else if ( flag_enable_out2 ) begin  //在有效区域内+1
            rom_address <= rom_address + 1;
            end
        else begin  //无效区域保持
            rom_address <= rom_address;
        end
    end
    assign flag_clear_rom_address = rom_address == height * width - 1;
    assign flag_begin_h     = h_addr > ( ( 640 - width ) / 2 ) && h_addr < ( ( 640 - width ) / 2 ) + width + 1;
    assign flag_begin_v     = v_addr > ( ( 480 - height )/2 ) && v_addr <( ( 480 - height )/2 ) + height + 1;
    assign flag_enable_out2 = flag_begin_h && flag_begin_v;

    //实例化ROM
    pic_rom	rom_inst (
    .address    ( rom_address   ),
    .clock      ( clk           ),
    .q          ( rom_data      )
    );
endmodule

3.3.3 编译烧录

结果:

四、参考链接

1.https://blog.csdn.net/qq_45659777/article/details/124834294

相关推荐
hahaha601612 小时前
3NRD降噪算法FPGA实现8并行度设计
fpga开发
松小白song13 小时前
一次“载电流显示为 0“的排查经历:负值钳位引发的踩坑
fpga开发
yingheceping16 小时前
复旦微FPGA开发板厂商TOP5甄选榜:2026选型参考
fpga开发·fpga
NamoAmitabha12819 小时前
PL 端的 GPIO IP(AXI GPIO)---PS控制----deepseek
fpga开发·zynq--ps
hahaha60161 天前
3D降噪算法YUV降噪FPGA实现
fpga开发
Hello-FPGA2 天前
基于RFSOC FPGA ZCU216 的CoaXPress 采集demo
fpga开发
9527华安2 天前
FPGA基于ADV7182解码PAL视频,Video Processing Subsystem去隔行,提供6套工程源码和技术支持
fpga开发·音视频·pal·adv7182
hahaha60162 天前
数字图像时域滤波或者3D降噪算法FPGA实现
算法·计算机视觉·fpga开发
GateWorld2 天前
Xilinx 7系列FPGA时钟网络详解
fpga开发·时钟网络·xilinx 7系列
szxinmai主板定制专家2 天前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x