VGA显示器驱动--字符游走

 

起因

项目的起因是在刷哔哩哔哩的时候看到了一个视频

【2023全国大学生FPGA创新设计大赛-国一】紫光同创-视频处理魔盒

当时对于我这个小白来说,羡慕的死死的~~~

当即就在我的日记本上记下了这个项目,立誓要Copy一个~,但实在是太厉害了,我计划一点一点的分模块来完成吧,毕竟我现在也刚开始学习FPGA才2个多月,慢慢来吧

代码我都会上传到我的github上,需要的话大家可以去自取(感觉有帮助的话,请点个Star哦~)

Github: zikwq/FPGA_prj: FPGA_prj (github.com)

展示

VGA显示器驱动--字符游走

起始这个功能并不难实现,具体的实现思路都在这篇文章里面写的很清楚了,感兴趣的就可以点进去慢慢看。FPGA:VGA显示器驱动设计 - quincy的日记

如果很难看懂的话,可以看下我文章末尾的一个参考链接,FPGA就是他带我入门的。

这里只说一下注意的点吧:

  1. 首先这个程序是一个纯verilog程序实现的,当然这里用到了一个PLL锁相环,将我的50MHZ系统时钟进行分频,输出一个25MHZ的时钟频率。(为什么请看上面的那篇文章)

  2. 字模是使用的字模提取的软件来提取的16进制数据

  3. 注意字体显示的区域和你显示器的分辨率,这里是640*480的显示分辨率

代码

module vga_colorb(
    input   wire    clk,
    input   wire    rstn,
    
    output  wire    hsync,
    output  wire    vsync,
    output  wire    [7:0]  vga_rgb
);
​
wire    vga_clk ;
wire    locked  ;
wire    rst_n   ;
wire [9:0]    pix_x   ;
wire [9:0]    pix_y  ;
wire [7:0]   pix_data;
​
assign  rst_n = (rstn && locked);
// 实例化PLL
clk_gen clk_gen_inst 
(
    .areset     ( ~rstn ),
    .inclk0     ( clk ),
    .c0         ( vga_clk ),
    .locked     ( locked )
);
// 实例化控制模块
vga_ctrl vga_ctrl_init(
    .vga_clk   (vga_clk)  ,
    .rstn      (rst_n)  ,
    .pix_data  (pix_data)  , // 显示数据
​
    .pix_x     (pix_x  )  , // x轴有效坐标位置
    .pix_y     (pix_y  )  , // y轴有效坐标位置
    .hsync     (hsync  )  , // 行同步信号
    .vsync     (vsync  )  , // 场同步信号
    .vga_rgb   (vga_rgb)  
);
vga_pic vga_pic_inst(
    . vga_clk(vga_clk)   ,
    . rst_n  (rst_n)   ,
    . pix_x  (pix_x)   ,
    . pix_y  (pix_y)   ,    
​
    .pix_data(pix_data)
);
​
endmodule
module vga_pic(
    input           wire        vga_clk ,//25MHZ
    input           wire        rst_n,
    input           wire  [9:0] pix_x,
    input           wire  [9:0] pix_y,
​
    output          reg  [7:0]pix_data
);
reg         [255:0]         char [63:0];
wire     [9:0]       char_x;
wire     [9:0]       char_y;
reg         [9:0]       CHAR_B_H, CHAR_B_V; // 字符需要显示的位置
​
parameter       CHAR_W      =   10'd256,        // 字符的宽度
                CHAR_H      =   10'd64;         // 字符的高度
parameter       BLACK       =   8'b00000000;    // RGB332色彩格式
parameter       RED         =   8'b11100000;    // 
​
assign  char_x = (((pix_x >= CHAR_B_H) && (pix_x < (CHAR_B_H + CHAR_W)))
                && ((pix_y >= CHAR_B_V) && (pix_y < (CHAR_B_V +CHAR_H)))) 
                ? (pix_x - CHAR_B_H) : 10'h3ff;
assign char_y = (((pix_x >= CHAR_B_H) && (pix_x < (CHAR_B_H + CHAR_W)))
                && ((pix_y >= CHAR_B_V) && (pix_y < (CHAR_B_V +CHAR_H)))) 
                ? (pix_y - CHAR_B_V) : 10'h3ff;
​
// 分频时钟,因为前面的程序用到了PLL锁相环,所以无法使用50MHZ的系统时钟进行分频,这里使用锁相环输出的25MHZ进行分频
reg [27:0] cnt_500;
reg    clk_500;
always @(posedge vga_clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_500 <= 28'd0;
        clk_500 <= 1'd0;
    end else if(cnt_500 == 28'd200000) begin
        cnt_500 <= 28'd0;
        clk_500 <= ~clk_500;
    end else begin
        cnt_500 <= cnt_500 + 1'd1;
    end
end
​
// 典型错误的运动程序,
// always @(posedge clk_500 or negedge rst_n) begin
//     if(!rst_n) begin
//         CHAR_B_H <= 10'd0;
//         CHAR_B_V <= 10'd0;
//      EN_work <= 1'd0;
//     end else if(EN_work == 1'd0) begin
//         CHAR_B_H <= CHAR_B_H + 1'd1;
//         CHAR_B_V <= CHAR_B_V + 1'd1;
//     end else if(EN_work == 1'd1) begin
//         CHAR_B_H <=CHAR_B_H -1'd1;
//         CHAR_B_V <=CHAR_B_V -1'd1;
//     end else if((CHAR_B_H == 10'd382) || (CHAR_B_V == 10'd412)) begin
//         EN_work <= 1'd1;
//     end else if((CHAR_B_H == 10'd0) || (CHAR_B_V == 10'd0)) begin
//         EN_work <= 1'd0;
//     end
// end
​
reg [1:0] dir_h; // 0的时候右移,1的时候左边=移
reg [1:0] dir_v; // 0的时候上移,1的时候下移
​
always @(posedge clk_500 or negedge rst_n) begin
    if(!rst_n) begin
        CHAR_B_H <= 10'd0;  // 初始化字符水平位置
        CHAR_B_V <= 10'd0;  // 初始化字符垂直位置
        dir_h <= 2'b0;      // 初始化水平方向
        dir_v <= 2'b0;      // 初始化垂直方向
    end else begin
        if (dir_h == 2'b0) begin // 向右运动
            CHAR_B_H <= CHAR_B_H + 1'd1;
            if (CHAR_B_H == 10'd382) begin
                dir_h <= 2'b1; // 到达边界,改变运动方向
            end
        end else begin  // 左移动
            CHAR_B_H <= CHAR_B_H - 1'd1;
            if (CHAR_B_H == 10'd0) begin
                dir_h <= 2'b0; // 到达边界,改变运动方向
            end
        end
​
        if (dir_v == 2'b0) begin // 向下运动
            CHAR_B_V <= CHAR_B_V + 1'd1;
            if (CHAR_B_V == 10'd412) begin
                dir_v <= 2'b1; // 到达边界,改变运动方向
            end
        end else begin // 向上运动
            CHAR_B_V <= CHAR_B_V - 1'd1;
            if (CHAR_B_V == 10'd0) begin
                dir_v <= 2'b0; // 到达边界,改变运动方向
            end
        end
    end
end
​
​
// 使用字符取模软件,提取“创新科技”的16进制字模数据
always @ (posedge vga_clk)
begin
    char[0]       <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
    char[1]       <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
    char[2]       <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
    char[3]       <= 256'h 0000000000000000000000000000000000000000000000000000000000000000;
    char[4]       <= 256'h 000000000000000000003C000000000000000000070000000001C00040000000;
    char[5]       <= 256'h 000000000038000000001F00000000000000000003E000000000F00078000000;
    char[6]       <= 256'h 00003800003E000000000F00000000000000060003E000000000F8007E000000;
    char[7]       <= 256'h 00003C00001F000000000700007000000000078001E000000000F0003E000000;
    char[8]       <= 256'h 00003E00001F000000000300007C000000000FC001E000000000F0003C000000;
    char[9]       <= 256'h 00003E00000F00000000000000FE000000001F8001C000000000F0003C000000;
    char[10]      <= 256'h00003C00000F0000000000E001FC000000003E0001C000000000E0003C000000;
    char[11]      <= 256'h00003C00000F0000000007F807C000000000780001C000000000E0003C000000;
    char[12]      <= 256'h00007800000E000000003FE00F0000000000E00001C000000000E00038000000;
    char[13]      <= 256'h00007C00000E0000000FFE003C0000000003C001C1C000000000E00038000000;
    char[14]      <= 256'h0000F780000E0000000FC301F000000000077001F1C000000000E000387C0000;
    char[15]      <= 256'h0000F3E0000E000000000381E000000000187800F1C000000000E0003FFC0000;
    char[16]      <= 256'h0001E1F83C0E0000000003C1E00000000000700071C000000000E0007FF00000;
    char[17]      <= 256'h0001C0FC1E0E000000030380E00000000000700011C000000000E007FFC00000;
    char[18]      <= 256'h0003C07C1E0E000000038300E00000000000700001C000000000FF1FFE000000;
    char[19]      <= 256'h0003803E1E06000000038600E0000000000033C001C00000000FFE0FF8000000;
    char[20]      <= 256'h0007001C1E06000000018600E003E00000003FE001C0000000FFF00030000000;
    char[21]      <= 256'h000700000E06000000008FF8E03FF0000001FE0781C0000000FFE00030000000;
    char[22]      <= 256'h000E00000E06000000007FE0E3FFF000001FF003C1C000000000E00030000000;
    char[23]      <= 256'h001C00000E060000000FF800FFE0000003FF7001E1C000000000E00030000000;
    char[24]      <= 256'h003803000E0600000FFF7000E0E000001FF8F000E1C000000000E00030000000;
    char[25]      <= 256'h00300FC00E0600000FF07800E0F000000FE0F00041C000000000E20033800000;
    char[26]      <= 256'h006FFFE00E06000003003800E0F000000301F00001C0F8000000EC003FE00000;
    char[27]      <= 256'h00C7C1F00E07000000003860C0F000000003F80001DFFC000000F801FFE00000;
    char[28]      <= 256'h018701E00E07000000003FF1C07000000003FF0003FFFC000000F07FE3E00000;
    char[29]      <= 256'h030701C00E0700000000FF81C0700000000777807FF800000001E07F03C00000;
    char[30]      <= 256'h060701C01E07000001FFF801C0700000000E73BFFFC000000003E00003C00000;
    char[31]      <= 256'h080701C01E07000000FE3801C0700000001C707FE1C000000007E00003800000;
    char[32]      <= 256'h000701C01E07000000003801C0700000001C700C01C00000001EE03C03800000;
    char[33]      <= 256'h000701C01E07000000003F01C07000000038700001C00000007CE01E03800000;
    char[34]      <= 256'h00071FC01C07000000303BC1807000000070700001C0000001F8E00707000000;
    char[35]      <= 256'h00070F800C070000003039E38070000000E0700001C000000FF0E00387000000;
    char[36]      <= 256'h000707800C070000007038E38070000001C0700001C000000FC0E001C7000000;
    char[37]      <= 256'h000707800007000000703863807000000300700001C000000780E000EE000000;
    char[38]      <= 256'h000703000007000000F03807007000000600700001C000000300E0007E000000;
    char[39]      <= 256'h000702040007000000E03807007000000800700001C000000000E0003C000000;
    char[40]      <= 256'h000700060007000000E03806006000001000700001C000000000E0007E000000;
    char[41]      <= 256'h000700060007000000E3780E006000000000700001C000000000E000FF800000;
    char[42]      <= 256'h00030006000F000000C1F81C006000000000700001C000000000E001F7C00000;
    char[43]      <= 256'h0003800E020F00000000F818006000000000700001C000000000E007C3F00000;
    char[44]      <= 256'h0003C03E01FF000000007830006000000000F00001C00000001FE01F81FC0000;
    char[45]      <= 256'h0003FFFE00FF000000003060006000000000F00001C000000007E0FC00FF8000;
    char[46]      <= 256'h0001FFFE007F000000000000006000000000700001C000000003E3E0007FF000;
    char[47]      <= 256'h00007FF8007E000000000000006000000000600001C000000001C000003FFC00;
    char[48]      <= 256'h00000000003E000000000000006000000000600001C000000001C00000000000;
    char[49]      <= 256'h00000000003C000000000000006000000000200001C000000000800000000000;
    char[50]      <= 256'h0000000000180000000000000040000000000000018000000000000000000000;
    char[51]      <= 256'h0000000000000000000000000040000000000000008000000000000000000000;
    char[52]      <= 256'h0000000000000000000000000000000000000000008000000000000000000000;
    char[53]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[54]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[55]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[56]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[57]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[58]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[59]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[60]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[61]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[62]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
    char[63]      <= 256'h0000000000000000000000000000000000000000000000000000000000000000;
end
​
// 如果扫描到了要显示的像素,则赋值给红色,否则赋值给黑色
always@(posedge vga_clk or negedge rst_n)
    if(rst_n == 1'b0)
        pix_data    <= BLACK;
    else    if((((pix_x >= (CHAR_B_H - 1'b1))
                && (pix_x < (CHAR_B_H + CHAR_W -1'b1)))
                && ((pix_y >= CHAR_B_V) && (pix_y < (CHAR_B_V + CHAR_H))))
                && (char[char_y][10'd255 - char_x] == 1'b1))
        pix_data    <=  RED;
    else
        pix_data    <=  BLACK;
​
endmodule
 
module vga_ctrl(
    input       wire                vga_clk     ,
    input       wire                rstn        ,
    input       wire     [15:0]     pix_data    , // 显示数据
    
    output      wire     [9:0]      pix_x       , // x轴有效坐标位置
    output      wire     [9:0]      pix_y       , // y轴有效坐标位置
    output      wire                hsync       , // 行同步信号
    output      wire                vsync       , // 场同步信号
    output      wire     [7:0]     vga_rgb     
);
​
parameter   H_SYNC  = 10'd96    ,
            H_BACK  = 10'd40    ,
            H_LEFT  = 10'd8     ,
            H_VALID = 10'd640   ,
            H_RIGHT = 10'd8     ,
            H_FRONT = 10'd8     ,
            H_TOTAL = 10'd800   ;
​
parameter   V_SYNC  = 10'd2     ,
            V_BACK  = 10'd25    ,
            V_TOP   = 10'd8     ,
            V_VALID = 10'd480   ,
            V_BOTTOM= 10'd8     ,
            V_FRONT = 10'd2     ,
            V_TOTAL = 10'd525   ;
​
​
reg     [9:0]       cnt_h ;
reg     [9:0]       cnt_v ;
wire                 rgb_valid ;
wire                 pix_data_req ;
​
​
// cnt_h行计数器,计数范围0-799
always @(posedge vga_clk or negedge rstn) begin
    if(!rstn)
        cnt_h       <=      10'd0;
    else if (cnt_h == H_TOTAL-1'd1)
        cnt_h       <=      10'd0;
    else
        cnt_h       <=      cnt_h + 1'd1;
end
​
// cnt_v列计数器,计数范围0-524并且行计数器计数到799
always @(posedge vga_clk or negedge rstn) begin
    if(!rstn)
        cnt_v       <=      10'd0;
    else if ((cnt_h == H_TOTAL-1'd1) && (cnt_v == V_TOTAL - 1'd1))
        cnt_v       <=      10'd0;
    else if(cnt_h == H_TOTAL-1'd1)
        cnt_v       <=      cnt_v + 1'd1;
    else
        cnt_v       <=      cnt_v;
end
​
// 定义有效数据范围,
assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT) 
                 &&  (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
                 &&  (cnt_v >= V_SYNC + V_BACK + V_TOP)
                 &&  (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)) ? 1'd1 : 1'd0;
​
assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'd1) 
                 &&  (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID -1'd1)
                 &&  (cnt_v >= V_SYNC + V_BACK + V_TOP)
                 &&  (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)) ? 1'd1 : 1'd0;
​
assign pix_x = (pix_data_req == 1'd1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'd1)) : 10'h3ff;
assign pix_y = (pix_data_req == 1'd1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;
assign hsync = (cnt_h <= H_SYNC - 1'd1) ? 1'd1 : 1'd0;
assign vsync = (cnt_v <= V_SYNC - 1'd1) ? 1'd1 : 1'd0;
assign vga_rgb = (rgb_valid == 1'd1) ? pix_data : 8'h0000;
​
​
endmodule

参考链接

  1. 66-第二十九讲-VGA显示器驱动设计与验证(一)哔哩哔哩bilibili

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值