前言
如何实现FPGA驱动HDMI显示在之前的文章已经实现了,其参考的代码主要是基于野火FPGA的教程,在正点原子的达芬奇FPGA开发板上实现。
系统框架
这里简单介绍一下各个框架。
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。
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模式驱动
因此此时的VS信号与HS信号直接赋值为高电平信号。此外定义了一个video_en
信号,来作为rgb数据输出的控制信号,同时也将这个信号作为DE模式的控制信号。既然采用DE模式驱动,DE信号在有效显示区域时为有效信号,那么我们就应该知道此时显示的位置。位置我们定义了一个行计数器与列计数器,用于有效显示区域的确认。当显示位置为有效显示区域时,我们将高电平赋值给video_en
信号,同时也将此信号作为rgb数据的控制信号,高电平时接受rgb数据并且输出。为了实时输出像素点坐标数据,定义了一个data_req
信号,为有限显示区域的前一个位置,因为该信号赋值给pixel_xpos
与pixel_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
顶层模块就是将所有的模块例化,这里并不加以讲解。
上板验证
实际显示效果
工程源码:链接: https://pan.baidu.com/s/1mnDjYzFnM6fkJEbhfUKTyw?pwd=rats 提取码: rats 复制这段内容后打开百度网盘手机App,操作更方便哦
参考文献
[1] 正点原子. 达芬奇之FPGA开发指南
[2] 野火. FPGA+Verilog开发实战指南——基于Xilinx+Spartan6
[3] 请叫我冻冻.不同分辨率对应的像素输出时钟以及同步信号参数的整理
标签:11,基于,ROM,HDMI,clk,PIC,video,data,pixel From: https://www.cnblogs.com/fangrunze/p/17986836