基于ROM的HDMI显示

前言

如何实现FPGA驱动HDMI显示在之前的文章已经实现了,其参考的代码主要是基于野火FPGA的教程,在正点原子的达芬奇FPGA开发板上实现。

系统框架

image.png
  这里简单介绍一下各个框架。
    1、clk_wiz_0
       调用的vivado的PLL生成了两种时钟频率:1280720@60Hz驱动时钟74.25MHz和ODDR2的驱动时钟,5倍的1280720@60Hz驱动时钟371.5MHz。
    2、pic_display
       这个模块的作用是输出RGB888数据,其中也例化了一个ROM用于存储RGB888图片数据。
    3、video_driver
       HDMI的驱动程序
    4、rgb2dvi
       主要作用是将rgb数据转换成可以转换成差分信号。
  接下来开始详细的介绍各个程序

程序设计

1、驱动时钟

驱动时钟的设计就不详细介绍,可以自行百度。需要解释的是,我是基于1280*720@60Hz来驱动的,因此所需要的时钟频率为74.25MHz。
image.png

2、HDMI驱动

直接上代码

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/12/04 18:38:50
// Design Name: 
// Module Name: video_driver
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module video_driver(
    input           pixel_clk,
    input           sys_rst_n,
    
    //RGB接口
    output          video_hs,     //行同步信号
    output          video_vs,     //场同步信号
    output          video_de,     //数据使能
    output  [23:0]  video_rgb,    //RGB888颜色数据
    
    input   [23:0]  pixel_data,   //像素点数据
    output  [10:0]  pixel_xpos,   //像素点横坐标
    output  [10:0]  pixel_ypos    //像素点纵坐标
);

//parameter define

//1280*720 分辨率时序参数
parameter  H_SYNC   =  11'd40;   //行同步
parameter  H_BACK   =  11'd220;  //行显示后沿
parameter  H_DISP   =  11'd1280; //行有效数据
parameter  H_FRONT  =  11'd110;  //行显示前沿
parameter  H_TOTAL  =  11'd1650; //行扫描周期

parameter  V_SYNC   =  11'd5;    //场同步
parameter  V_BACK   =  11'd20;   //场显示后沿
parameter  V_DISP   =  11'd720;  //场有效数据
parameter  V_FRONT  =  11'd5;    //场显示前沿
parameter  V_TOTAL  =  11'd750;  //场扫描周期

//reg define
reg  [10:0] cnt_h;
reg  [10:0] cnt_v;

//wire define
wire       video_en;
wire       data_req;

//*****************************************************
//**                    main code
//*****************************************************

assign video_de  = video_en;

assign video_hs  =  1'b1;  //行同步信号赋值
assign video_vs  =  1'b1;  //场同步信号赋值

//使能RGB数据输出
assign video_en  = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
                 &&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
                 ?  1'b1 : 1'b0;

//RGB888数据输出
assign video_rgb = video_en ? pixel_data : 24'd0;

//请求像素点颜色数据输入
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && 
                    (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
                  && ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
                  ?  1'b1 : 1'b0;

//像素点坐标
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 11'd0;
assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 11'd0;

//行计数器对像素时钟计数
always @(posedge pixel_clk ) begin
    if (!sys_rst_n)
        cnt_h <= 11'd0;
    else begin
        if(cnt_h < H_TOTAL - 1'b1)
            cnt_h <= cnt_h + 1'b1;
        else 
            cnt_h <= 11'd0;
    end
end

//场计数器对行计数
always @(posedge pixel_clk ) begin
    if (!sys_rst_n)
        cnt_v <= 11'd0;
    else if(cnt_h == H_TOTAL - 1'b1) begin
        if(cnt_v < V_TOTAL - 1'b1)
            cnt_v <= cnt_v + 1'b1;
        else 
            cnt_v <= 11'd0;
    end
end

endmodule

在这里输入的时钟信号pixel_clk,代码修改于RGB888 驱动显示的代码,驱动采用DE模式驱动
image.png
  因此此时的VS信号与HS信号直接赋值为高电平信号。此外定义了一个video_en信号,来作为rgb数据输出的控制信号,同时也将这个信号作为DE模式的控制信号。既然采用DE模式驱动,DE信号在有效显示区域时为有效信号,那么我们就应该知道此时显示的位置。位置我们定义了一个行计数器与列计数器,用于有效显示区域的确认。当显示位置为有效显示区域时,我们将高电平赋值给video_en信号,同时也将此信号作为rgb数据的控制信号,高电平时接受rgb数据并且输出。为了实时输出像素点坐标数据,定义了一个data_req信号,为有限显示区域的前一个位置,因为该信号赋值给pixel_xpospixel_ypos存在一个时钟周期的延迟。
  特别注意,该模块使用的时钟信号为74.25MHz

并串转换模块

该模块的功能也十分简单,就是将HDMI驱动模块输出的数据进行并串转换,并且输出为HDMI标准中的差分信号。详细的请参考HDMI——FPGA这篇文章。

RGB数据输出
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/12/04 18:50:05
// Design Name: 
// Module Name: block_move
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module pic_display (
    input                   pixel_clk,
    input                   sys_rst_n,
    input           [10:0]  pixel_xpos,
    input           [10:0]  pixel_ypos,

    output  reg     [23:0]  pixel_data

);


//parameter define
    parameter H_DISP = 11'd1280;
    parameter V_DISP = 11'd720;

    localparam PIC_X_START = 11'd1; //图片起始点横坐标
    localparam PIC_Y_START = 11'd1; //图片起始点纵坐标
    localparam PIC_WIDTH   = 11'd100; //图片宽度
    localparam PIC_HEIGHT  = 11'd62; //图片高度
    localparam BACK_COLOR  = 24'hE0FFFF; //背景色,浅蓝色

//reg define
    reg  [13:0] rom_addr ; //ROM 地址
    wire [10:0] x_cnt; //横坐标计数器
    wire [10:0] y_cnt; //纵坐标计数器
    wire        rom_rd_en ; //ROM 读使能信号
    wire [23:0] rom_rd_data ;//ROM 数据

    assign x_cnt = pixel_xpos - PIC_X_START; //像素点相对于字符区域起始点水平坐标
    assign y_cnt = pixel_ypos - PIC_Y_START; //像素点相对于字符区域起始点垂直坐标
    assign rom_rd_en = 1'b1;
    
//为 LCD 不同显示区域绘制图片、字符和背景色
    always @(posedge pixel_clk or negedge sys_rst_n) begin
        if (!sys_rst_n)
            pixel_data <= BACK_COLOR;
        else if( (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH)
                && (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) )
            pixel_data <= rom_rd_data ; //显示图片
        else
            pixel_data <= BACK_COLOR; //屏幕背景色
    end
//根据当前扫描点的横纵坐标为 ROM 地址赋值
     always @(posedge pixel_clk or negedge sys_rst_n) begin
        if (!sys_rst_n)
            rom_addr <= 14'd0;
        else if( (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH)
                && (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) )
            rom_addr <= rom_addr + 1'b1;
        else if((pixel_ypos >= PIC_Y_START + PIC_HEIGHT))
            rom_addr <= 14'd0;
     end

//ROM:存储图片
     blk_mem_gen_0 blk_mem_gen_0 (
        .clka (pixel_clk), // input wire clka
        .ena (rom_rd_en), // input wire ena
        .addra (rom_addr), // input wire [13 : 0] addra
        .douta (rom_rd_data) // output wire [23 : 0] douta
     );
endmodule

这个模块的功能就是根据输入的坐标显示数据输出相对应的RGB数据。

顶层模块
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/12/04 18:44:46
// Design Name: 
// Module Name: hdmi_colorbar_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module  hdmi_pic_display_top(
  input        sys_clk,
  input        sys_rst_n, 
  output       tmds_clk_p,    // TMDS 时钟通道
  output       tmds_clk_n,
  output [2:0] tmds_data_p,   // TMDS 数据通道
  output [2:0] tmds_data_n,
  output       tmds_oen      // TMDS 输出使能

);

  //wire define
  wire          pixel_clk;
  wire          pixel_clk_5x;
  wire          clk_locked;

  wire  [10:0]  pixel_xpos_w;
  wire  [10:0]  pixel_ypos_w;
  wire  [23:0]  pixel_data_w;

  wire          video_hs;
  wire          video_vs;
  wire          video_de;
  wire  [23:0]  video_rgb;

  //*****************************************************
  //**                    main code
  //*****************************************************

  //assign hpdout = hpdin;

  //例化MMCM/PLL IP核
  clk_wiz_0  clk_wiz_0(
    .clk_in1        (sys_clk),
    .clk_out1       (pixel_clk),        //像素时钟
    .clk_out2       (pixel_clk_5x),     //5倍像素时钟

    .reset          (~sys_rst_n), 
    .locked         (clk_locked)
  );

  //例化视频显示驱动模块
  video_driver u_video_driver(
    .pixel_clk      (pixel_clk),
    .sys_rst_n      (sys_rst_n),

    .video_hs       (video_hs),
    .video_vs       (video_vs),
    .video_de       (video_de),
    .video_rgb      (video_rgb),

    .pixel_xpos     (pixel_xpos_w),
    .pixel_ypos     (pixel_ypos_w),
    .pixel_data     (pixel_data_w)
  );

  //例化视频显示模块
  pic_display  u_pic_display(
    .pixel_clk      (pixel_clk),
    .sys_rst_n      (sys_rst_n),
    .pixel_xpos     (pixel_xpos_w),
    .pixel_ypos     (pixel_ypos_w),
    
    .pixel_data     (pixel_data_w)
  );

  //例化HDMI驱动模块
  dvi_transmitter_top u_rgb2dvi_0(
    .pclk           (pixel_clk),
    .pclk_x5        (pixel_clk_5x),
    .reset_n        (sys_rst_n & clk_locked),

    .video_din      (video_rgb),
    .video_hsync    (video_hs), 
    .video_vsync    (video_vs),
    .video_de       (video_de),

    .tmds_clk_p     (tmds_clk_p),
    .tmds_clk_n     (tmds_clk_n),
    .tmds_data_p    (tmds_data_p),
    .tmds_data_n    (tmds_data_n), 
    .tmds_oen       (tmds_oen)
  );

endmodule

顶层模块就是将所有的模块例化,这里并不加以讲解。

上板验证

实际显示效果
image.png


工程源码:链接: https://pan.baidu.com/s/1mnDjYzFnM6fkJEbhfUKTyw?pwd=rats 提取码: rats 复制这段内容后打开百度网盘手机App,操作更方便哦


参考文献

[1] 正点原子. 达芬奇之FPGA开发指南
  [2] 野火. FPGA+Verilog开发实战指南——基于Xilinx+Spartan6
  [3] 请叫我冻冻.不同分辨率对应的像素输出时钟以及同步信号参数的整理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值