基于FPGA的VGA协议实现

目录

  • 一、 内容概要
  • 二、 了解VGA
    • 2.1 概念
  • 三、 VGA基础显示
    • 3.1 条纹显示
    • 3.2 显示字符
      • 3.2.1 准备工作
      • 3.2.2 提取文字
      • 3.2.3 编写代码
      • 3.2.4 编译烧录
    • 3.3 显示图像
      • 3.3.1 准备工作
      • 3.3.2 实现例程
      • 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 条纹显示

直接编译烧录此代码就行

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变量)
//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;
    }
}
//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打印字符代码:

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
在这里插入图片描述

编写代码:

//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

//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


//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


//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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/654484.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

905. 按奇偶排序数组 - 力扣

1. 题目 给你一个整数数组 nums&#xff0c;将 nums 中的的所有偶数元素移动到数组的前面&#xff0c;后跟所有奇数元素。 返回满足此条件的 任一数组 作为答案。 2. 示例 3. 分析 开辟一个数组res用来保存操作过后的元素。第一次遍历数组只插入偶数&#xff0c;第二次遍历数组…

【ArcGISPro】CSMPlugins文件夹

在ArcGISPro软件的CSMPlugins文件夹含有以下一个应用程序的扩展 从文件的名称可以看出美国地质调查局的太空地质学与ESRI合作进行的一个软件扩展&#xff0c;而USGS主要是遥感影像方向的应该&#xff0c;所以估计该dll的主要功能是多遥感影像进行处理&#xff0c;支持软件的不同…

Steam游戏搬砖:靠谱吗,详细版说下搬砖中的核心内容!

可能大家也比较关注国外Steam游戏搬砖这个项目&#xff0c;最近单独找我了解的也比较多&#xff0c;其实也正常&#xff0c;因为现在市面上的项目很多都很鸡肋&#xff0c;而且很多都是一片红海&#xff0c;内卷太过严重&#xff0c;所以对于Steam的关注度也高很多&#xff0c;…

探秘网页内容提取:教你定位特定标签

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、定位带有ID属性的第二个标签 三、定位具有特定属性值的标签 四、提取含有特…

【OpenCV】图形绘制与填充

介绍了绘制、填充图像的API。也介绍了RNG类用来生成随机数。相关API&#xff1a; line() rectangle() circle() ellipse() putText() 代码&#xff1a; #include "iostream" #include "opencv2/opencv.hpp"using namespace std; using namespace cv…

全局配置Maven

如果开着项目&#xff0c;就file->close project 如果创建有问题可以转到这篇rIDEA2024创建maven项目-CSDN博客https://blog.csdn.net/weixin_45588505/article/details/139271562?spm1001.2014.3001.5502

Unity SetParent第二个参数worldPositionStays的意义

初学Unity的小知识&#xff1a; 改变对象的父级有三种调用方式&#xff0c;如下&#xff1a; transMe.SetParent(transParent,true); transMe.SetParent(transParent,false); transMe.parent transParent;具体有什么区别呢&#xff0c;这里写一个测试例子来详细说明&#xff…

React18 apexcharts数据可视化之甜甜圈图

03 甜甜圈图 apexcharts数据可视化之甜甜圈图。 有完整配套的Python后端代码。 本教程主要会介绍如下图形绘制方式&#xff1a; 基本甜甜圈图个性图案的甜甜圈图渐变色的甜甜圈图 面包圈 import ApexChart from react-apexcharts;export function DonutUpdate() {// 数据…

在matlab里面计算一组给定参数的方程的解

如&#xff1a; k (1:1024); f (x)(1-x-k.*x.^2); 在这段代码给出了一组函数&#xff0c;若需要计算f0&#xff0c;可以通过自带的函数实现&#xff1a; x0 zeros(length(k),1); options optimoptions(fsolve,Display,none,TolX,tol,TolFun,tol); tic for ik 1:length…

基于OrangePi AIpro开发一个电子纸屏时钟

OrangePi AIpro 简介 OrangePi AIpro(8T)采用昇腾AI技术路线&#xff0c;具体为4核64位处理器AI处理器&#xff0c;集成图形处理器&#xff0c;支持8TOPS AI算力&#xff0c;拥有8GB/16GB LPDDR4X&#xff0c;可以外接32GB/64GB/128GB/256GB eMMC模块&#xff0c;支持双4K高清…

Web3革命:探索科技与物联网的无限可能

引言 Web3时代正在悄然而至&#xff0c;带来了对互联网的彻底颠覆和改变。作为互联网的下一代&#xff0c;Web3不仅是技术革新的延续&#xff0c;更是对传统互联网模式的重新构想。在这个新时代&#xff0c;科技与物联网的结合将迎来无限的可能性&#xff0c;将探索到一片全新…

如何在Python 中如何导入和引用外部文件(Colab VS Code)

1. 上传文件 在 Google Colab 中&#xff0c;从左侧界面的文件选项中使用 "Upload" 按钮上传文件。 在 VS Code 中&#xff0c;通过菜单栏中的 "File" -> "Open File/Folder" 选项上传文件&#xff08;建议将所有文件放入一个文件夹中&#…

【paper】基于分布式采样的多机器人编队导航信念传播模型预测控制

Distributed Sampling-Based Model Predictive Control via Belief Propagation for Multi-Robot Formation NavigationRAL 2024.4Chao Jiang 美国 University of Wyoming 预备知识 马尔可夫随机场&#xff08;Markov Random Field, MRF&#xff09; 马尔可夫随机场&#xff…

如何解决SEO排名上升后遭遇的攻击问题

随着搜索引擎优化&#xff08;SEO&#xff09;策略的成功实施&#xff0c;网站排名的提升往往会引来更多的流量与关注&#xff0c;但同时也可能成为恶意攻击的目标&#xff0c;包括DDoS攻击、SQL注入、XSS攻击等。这些攻击不仅影响用户体验&#xff0c;还可能导致网站降权甚至被…

目标检测数据集 - 铁路工人安全检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;铁路工人安全检测数据集&#xff0c;真实铁路监控场景高质量图片数据&#xff0c;涉及场景丰富&#xff0c;比如铁路工地工人作业数据、铁路巡检工人作业数据、铁路搬运工人作业数据、铁路场景货车上工人作业数据、铁路旁堆料区工人作业数据等。数据标签…

【图书推荐】《机器学习实战(视频教学版)》

本书用处 快速入门Python机器学习基础算法。 最后3个综合实战项目&#xff08;包括新闻内容分类实战、泰坦尼克号获救预测实战、中药数据分析项目实战&#xff09;可以作为研究可以的素材。 内容简介 本书基于Python语言详细讲解机器学习算法及其应用&#xff0c;用于读者快…

利用预测大模型完成办公室饮水机剩余热水量

背景 在每天上班的时候&#xff0c;很多同事都有喝热水的习惯&#xff0c;但是饮水机内的热水量总是比较少的&#xff0c;如何避免等待&#xff0c;高效的接到热水是我接下来要做的事情的动机。 理论基础 在大量真实数据的情况下&#xff0c;可以分析出用水紧张的时间段和用水…

【全开源】场馆预定系统源码(ThinkPHP+FastAdmin+UniApp)

一款基于ThinkPHPFastAdminUniApp开发的多场馆场地预定小程序&#xff0c;提供运动场馆运营解决方案&#xff0c;适用于体育馆、羽毛球馆、兵乒球馆、篮球馆、网球馆等场馆。 场馆预定系统源码&#xff1a;打造高效便捷的预定体验 一、引言&#xff1a;数字化预定时代的来临 …

专业上门预约洗衣洗鞋管理系统一站式解决方案

洗衣洗鞋店管理系统一站式解决方案&#xff0c;不仅运营稳定且功能强大&#xff0c;堪称现代生活中的得力助手。 在这个快节奏的时代&#xff0c;人们对便捷性的渴望愈发强烈。洗衣洗鞋作为日常生活中的一项琐碎事务&#xff0c;也亟需一个高效、省心的解决方案。为此&#xf…

databricks~Unity Catalog

Unity Catalog hierarchy 包含了用户授权管理信息和元数据信息 workspace with unity catalog hierarchy unity metastore Ref: https://www.youtube.com/playlist?listPLY-V_O-O7h4fwcHcXgkR_zTLvddvE_GfC