基于FPGA的HDMI图片显示
无病呻吟[滑稽]
首先说一下我用的板子,是XILINX的zynq-7020,这也是我学习的第一块FPGA板子,我知道这块板子的难度是很高的,我的想法是一步到位,如果只买单纯的一块像ax-4010那种的话等我以后想要学一些高阶教程还要重新买板子(穷学生党,怎么实惠怎么来,嘿嘿)。回归正题,这个HDMI图像显示是我的一个突发奇想,灵感来自于官方教程的HDMI字符显示部分,他是在ROM中初始化一段字符的数据,然后通过HDMI显示在显示器上。学完之后我就想能不能将一张图片的数据存储到ROM中然后用同样的方式显示出来,说做就做。
1.导出图片数据
首先要获得存入ROM的图片数据,图片是我在网上找的一张萌图,置于萌不萌大佬们自己看(就是萌,不接受大佬们的反驳):
在博客上搜了用matlab将图片导出.coe文件的办法,这里直接把链接放到下面:
链接: Matlab中将图像数据生成coe文件.
以这个代码为模板稍微修改一下就可以用,亲测代码很好用,这张图片的RGB数据我只导入的R部分的数据,都是重复工作,只是一个小点子,我就没有全部导入,如果你想完全还原这张图片的话你可以弄一个24位宽的数组,然后导出来即可。不过导入图片数据的时候给自己挖了一个巨坑,下面会说到。
2.将数据存入ROM中
例化IP的部分这里我就不说了,直接说结构。我用到的这张图片242x400的,ROM的位宽选择8位,深度就根据图片的尺寸来,是96800,ROM设置成单口,然后将coe文件导入到ROM中这一步就结束了。
3.程序设计
这里说几个图片显示所用到的一些时序的概念,显示器显示图片是按照从左上角一点点扫描到右下角,然后探针又返回到左上角这样的反复循环,这里就有了行和列的概念,对于行来说基本单位是图片的一个像素点,一行所显示的像素点个数是由你所定义的显示标准(分辨率)决定的,对于列来说基本单位是行,列的尺寸也是由显示标准决定。在完整的传输一行数据的过程中有几个时序参数:1.行有效信号:这个参数是根据水平同步信号(HSYNC)得来,HSYNC规定了一张图片(一帧)中每一行数据的有效起始点;2.总行时间(h_total):从HSYNC的一个上升沿到下一个上升沿到来所需要的时间;3.行消隐脉冲:
(sync_h):各行之间的低电平时间。对于列也有几个时许参数:1.垂直同步信号(VSYNC):规定了一幅图片的起点;2.总列时间(v_total);3.场前肩(torch_f):从场计数开始到场同步的时间;场后肩(torch_b):从场同步结束到行同步开始的时间。(这部分是我从《基于FPGA的数字图像处理原理及应用》书上学到的,这里面的场前肩,场后肩我还没有理解…)
从ROM中存储数据是1位的,而1像素是八位数据,所以按照地址查询数据的时候要每个时钟给地址加8来保证一次传输一个像素数据。没有什么其他注意的点了,这里的RGB转HDMI部分我直接用的官方例程给的IP核,按照官方代码搬过来用的,这个可以上淘宝XILINX官方店里面提供的链接下载到。下面直接上代码吧。
//这个是第一个文件,这部分代码实现把从ROM读出来的数据变成RGB数据然后与生成的行同步信号,场同步信号,图片有效信号一起输出给RGB2HDMI。
// An highlighted block
////////////////////////////////////////////////////////////////////////////////
// color bar generator module //
// //
// Author: meisq //
// msq@qq.com //
// ALINX(shanghai) Technology Co.,Ltd //
// heijin //
// WEB: http://www.alinx.cn/ //
// BBS: http://www.heijin.org/ //
// //
//////////////////////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd //
// All rights reserved //
// //
// This source file may be used and distributed without restriction provided //
// that this copyright statement is not removed from the file and that any //
// derivative work contains the original copyright notice and the associated //
// disclaimer. //
// //
//////////////////////////////////////////////////////////////////////////////////
//================================================================================
// Revision History:
// Date By Revision Change Description
//--------------------------------------------------------------------------------
//2013/4/16 1.0 Original
//2013/4/18 1.1 vs timing
//2013/5/7 1.2 remove some warning
//2017/7/17 1.3
//*******************************************************************************/
`include "video_define.v"
module color_bar(
input clk, //pixel clock
input rst, //reset signal high active
output hs, //horizontal synchronization
output vs, //vertical synchronization
output de, //video valid
output[7:0] rgb_r, //video red data
output[7:0] rgb_g, //video green data
output[7:0] rgb_b //video blue data
);
//video timing parameter definition
`ifdef VIDEO_1280_720
parameter H_ACTIVE = 16'd1280; //horizontal active time (pixels)
parameter H_FP = 16'd110; //horizontal front porch (pixels)
parameter H_SYNC = 16'd40; //horizontal sync time(pixels)
parameter H_BP = 16'd220; //horizontal back porch (pixels)
parameter V_ACTIVE = 16'd720; //vertical active Time (lines)
parameter V_FP = 16'd5; //vertical front porch (lines)
parameter V_SYNC = 16'd5; //vertical sync time (lines)
parameter V_BP = 16'd20; //vertical back porch (lines)
parameter HS_POL = 1'b1; //horizontal sync polarity, 1 : POSITIVE,0 : NEGATIVE;
parameter VS_POL = 1'b1; //vertical sync polarity, 1 : POSITIVE,0 : NEGATIVE;
`endif
//480x272 9Mhz
`ifdef VIDEO_480_272
parameter H_ACTIVE = 16'd480;
parameter H_FP = 16'd2;
parameter H_SYNC = 16'd41;
parameter H_BP = 16'd2;
parameter V_ACTIVE = 16'd272;
parameter V_FP = 16'd2;
parameter V_SYNC = 16'd10;
parameter V_BP = 16'd2;
parameter HS_POL = 1'b0;
parameter VS_POL = 1'b0;
`endif
//640x480 25.175Mhz
`ifdef VIDEO_640_480
parameter H_ACTIVE = 16'd640;
parameter H_FP = 16'd16;
parameter H_SYNC = 16'd96;
parameter H_BP = 16'd48;
parameter V_ACTIVE = 16'd480;
parameter V_FP = 16'd10;
parameter V_SYNC = 16'd2;
parameter V_BP = 16'd33;
parameter HS_POL = 1'b0;
parameter VS_POL = 1'b0;
`endif
//800x480 33Mhz
`ifdef VIDEO_800_480
parameter H_ACTIVE = 16'd800;
parameter H_FP = 16'd40;
parameter H_SYNC = 16'd128;
parameter H_BP = 16'd88;
parameter V_ACTIVE = 16'd480;
parameter V_FP = 16'd1;
parameter V_SYNC = 16'd3;
parameter V_BP = 16'd21;
parameter HS_POL = 1'b0;
parameter VS_POL = 1'b0;
`endif
//800x600 40Mhz
`ifdef VIDEO_800_600
parameter H_ACTIVE = 16'd800;
parameter H_FP = 16'd40;
parameter H_SYNC = 16'd128;
parameter H_BP = 16'd88;
parameter V_ACTIVE = 16'd600;
parameter V_FP = 16'd1;
parameter V_SYNC = 16'd4;
parameter V_BP = 16'd23;
parameter HS_POL = 1'b1;
parameter VS_POL = 1'b1;
`endif
//1024x768 65Mhz
`ifdef VIDEO_1024_768
parameter H_ACTIVE = 16'd1024;
parameter H_FP = 16'd24;
parameter H_SYNC = 16'd136;
parameter H_BP = 16'd160;
parameter V_ACTIVE = 16'd768;
parameter V_FP = 16'd3;
parameter V_SYNC = 16'd6;
parameter V_BP = 16'd29;
parameter HS_POL = 1'b0;
parameter VS_POL = 1'b0;
`endif
//1920x1080 148.5Mhz
`ifdef VIDEO_1920_1080
parameter H_ACTIVE = 16'd1920;
parameter H_FP = 16'd88;
parameter H_SYNC = 16'd44;
parameter H_BP = 16'd148;
parameter V_ACTIVE = 16'd1080;
parameter V_FP = 16'd4;
parameter V_SYNC = 16'd5;
parameter V_BP = 16'd36;
parameter HS_POL = 1'b1;
parameter VS_POL = 1'b1;
`endif
parameter H_TOTAL = H_ACTIVE + H_FP + H_SYNC + H_BP;//horizontal total time (pixels)
parameter V_TOTAL = V_ACTIVE + V_FP + V_SYNC + V_BP;//vertical total time (lines)
//define the RGB values for 8 colors
//parameter WHITE_R = 8'hff;
//parameter WHITE_G = 8'hff;
//parameter WHITE_B = 8'hff;
//parameter YELLOW_R = 8'hff;
//parameter YELLOW_G = 8'hff;
//parameter YELLOW_B = 8'h00;
//parameter CYAN_R = 8'h00;
//parameter CYAN_G = 8'hff;
//parameter CYAN_B = 8'hff;
//parameter GREEN_R = 8'h00;
//parameter GREEN_G = 8'hff;
//parameter GREEN_B = 8'h00;
//parameter MAGENTA_R = 8'hff;
//parameter MAGENTA_G = 8'h00;
//parameter MAGENTA_B = 8'hff;
//parameter RED_R = 8'hff;
//parameter RED_G = 8'h00;
//parameter RED_B = 8'h00;
//parameter BLUE_R = 8'h00;
//parameter BLUE_G = 8'h00;
//parameter BLUE_B = 8'hff;
//parameter BLACK_R = 8'h00;
//parameter BLACK_G = 8'h00;
//parameter BLACK_B = 8'h00;
reg hs_reg; //horizontal sync register
reg vs_reg; //vertical sync register
reg hs_reg_d0; //delay 1 clock of 'hs_reg'
reg vs_reg_d0; //delay 1 clock of 'vs_reg'
reg[11:0] h_cnt; //horizontal counter
reg[11:0] v_cnt; //vertical counter
reg[11:0] active_x; //video x position
reg[11:0] active_y; //video y position
reg[7:0] rgb_r_reg; //video red data register
reg[7:0] rgb_g_reg; //video green data register
reg[7:0] rgb_b_reg; //video blue data register
reg h_active; //horizontal video active
reg v_active; //vertical video active
wire video_active; //video active(horizontal active and vertical active)
reg video_active_d0; //delay 1 clock of video_active
/**********User Define************/
reg [19:0]osd_ram_addr;
reg pos_vs_d0;
reg pos_vs_d1;
reg region_active;
reg region_active_d0;
//reg region_active_d1;
//reg region_active_d2;
wire [7:0]q;
//wire[11:0] pos_x; //X坐标
//wire[11:0] pos_y; //Y坐标
//wire pos_hs;
//wire pos_vs;
//wire pos_de;
//wire [7:0]video_r;
//wire [7:0]video_g;
//wire [7:0]video_b;
parameter OSD_WIDTH = 12'd242; //设置OSD的宽度,可根据字符生成软件设置
parameter OSD_HEGIHT = 12'd400; //设置OSD的高度,可根据字符生成软件设置
/*********************************/
assign hs = hs_reg_d0;
assign vs = vs_reg_d0;
assign video_active = h_active & v_active;
assign de = video_active_d0;
assign rgb_r = rgb_r_reg;
assign rgb_g = rgb_g_reg;
assign rgb_b = rgb_b_reg;
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
begin
hs_reg_d0 <= 1'b0;
vs_reg_d0 <= 1'b0;
video_active_d0 <= 1'b0;
end
else
begin
hs_reg_d0 <= hs_reg;
vs_reg_d0 <= vs_reg;
video_active_d0 <= video_active;
end
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
h_cnt <= 12'd0;
else if(h_cnt == H_TOTAL - 1)//horizontal counter maximum value
h_cnt <= 12'd0;
else
h_cnt <= h_cnt + 12'd1;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
active_x <= 12'd0;
else if(h_cnt >= H_FP + H_SYNC + H_BP - 1)//horizontal video active
active_x <= h_cnt - (H_FP[11:0] + H_SYNC[11:0] + H_BP[11:0] - 12'd1);
else
active_x <= active_x;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
active_y <= 12'd0;
else if(v_cnt >= V_FP + V_SYNC + V_BP - 1)//horizontal video active
active_y <= v_cnt - (V_FP[11:0] + V_SYNC[11:0] + V_BP[11:0] - 12'd1);
else
active_y <= active_y;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
v_cnt <= 12'd0;
else if(h_cnt == H_FP - 1)//horizontal sync time
if(v_cnt == V_TOTAL - 1)//vertical counter maximum value
v_cnt <= 12'd0;
else
v_cnt <= v_cnt + 12'd1;
else
v_cnt <= v_cnt;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
hs_reg <= 1'b0;
else if(h_cnt == H_FP - 1)//horizontal sync begin
hs_reg <= HS_POL;
else if(h_cnt == H_FP + H_SYNC - 1)//horizontal sync end
hs_reg <= ~hs_reg;
else
hs_reg <= hs_reg;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
h_active <= 1'b0;
else if(h_cnt == H_FP + H_SYNC + H_BP - 1)//horizontal active begin
h_active <= 1'b1;
else if(h_cnt == H_TOTAL - 1)//horizontal active end
h_active <= 1'b0;
else
h_active <= h_active;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
vs_reg <= 1'd0;
else if((v_cnt == V_FP - 1) && (h_cnt == H_FP - 1))//vertical sync begin
vs_reg <= HS_POL;
else if((v_cnt == V_FP + V_SYNC - 1) && (h_cnt == H_FP - 1))//vertical sync end
vs_reg <= ~vs_reg;
else
vs_reg <= vs_reg;
end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
v_active <= 1'd0;
else if((v_cnt == V_FP + V_SYNC + V_BP - 1) && (h_cnt == H_FP - 1))//vertical active begin
v_active <= 1'b1;
else if((v_cnt == V_TOTAL - 1) && (h_cnt == H_FP - 1)) //vertical active end
v_active <= 1'b0;
else
v_active <= v_active;
end
//always@(posedge clk or posedge rst)
//begin
// if(rst == 1'b1)
// begin
// rgb_r_reg <= 8'h00;
// rgb_g_reg <= 8'h00;
// rgb_b_reg <= 8'h00;
// end
// else if(video_active)
// if(active_x == 12'd0)
// begin
// rgb_r_reg <= WHITE_R;
// rgb_g_reg <= WHITE_G;
// rgb_b_reg <= WHITE_B;
// end
// else if(active_x == (H_ACTIVE/8) * 1)
// begin
// rgb_r_reg <= YELLOW_R;
// rgb_g_reg <= YELLOW_G;
// rgb_b_reg <= YELLOW_B;
// end
// else if(active_x == (H_ACTIVE/8) * 2)
// begin
// rgb_r_reg <= CYAN_R;
// rgb_g_reg <= CYAN_G;
// rgb_b_reg <= CYAN_B;
// end
// else if(active_x == (H_ACTIVE/8) * 3)
// begin
// rgb_r_reg <= GREEN_R;
// rgb_g_reg <= GREEN_G;
// rgb_b_reg <= GREEN_B;
// end
// else if(active_x == (H_ACTIVE/8) * 4)
// begin
// rgb_r_reg <= MAGENTA_R;
// rgb_g_reg <= MAGENTA_G;
// rgb_b_reg <= MAGENTA_B;
// end
// else if(active_x == (H_ACTIVE/8) * 5)
// begin
// rgb_r_reg <= RED_R;
// rgb_g_reg <= RED_G;
// rgb_b_reg <= RED_B;
// end
// else if(active_x == (H_ACTIVE/8) * 6)
// begin
// rgb_r_reg <= BLUE_R;
// rgb_g_reg <= BLUE_G;
// rgb_b_reg <= BLUE_B;
// end
// else if(active_x == (H_ACTIVE/8) * 7)
// begin
// rgb_r_reg <= BLACK_R;
// rgb_g_reg <= BLACK_G;
// rgb_b_reg <= BLACK_B;
// end
// else
// begin
// rgb_r_reg <= rgb_r_reg;
// rgb_g_reg <= rgb_g_reg;
// rgb_b_reg <= rgb_b_reg;
// end
// else
// begin
// rgb_r_reg <= 8'h00;
// rgb_g_reg <= 8'h00;
// rgb_b_reg <= 8'h00;
// end
//end
always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
begin
rgb_r_reg <= 8'h00;
rgb_g_reg <= 8'h00;
rgb_b_reg <= 8'h00;
end
else if(video_active)
if(region_active_d0 == 1'b1)
begin
rgb_r_reg <= q;
rgb_g_reg <= 8'h00;
rgb_b_reg <= 8'h00;
end
else
begin
rgb_r_reg <= 8'hff;
rgb_g_reg <= 8'hff;
rgb_b_reg <= 8'hff;
end
else
begin
rgb_r_reg <= 8'h00;
rgb_g_reg <= 8'h00;
rgb_b_reg <= 8'h00;
end
end
always @(posedge clk)
begin
pos_vs_d0 <= vs_reg_d0;
pos_vs_d1 <= pos_vs_d0;
end
always@(posedge clk)
begin
region_active_d0 <= region_active;
// region_active_d1 <= region_active_d0;
// region_active_d2 <= region_active_d1;
end
always@(posedge clk)
begin
if(active_y >= 12'd9 && active_y <= 12'd9 + OSD_HEGIHT - 12'd1 && active_x >= 12'd9 && active_x <= 12'd9 + OSD_WIDTH - 12'd1)
region_active <= 1'b1;
else
region_active <= 1'b0;
end
always @(posedge clk)
begin
if(pos_vs_d0 == 1'b0 && pos_vs_d1 == 1'b1)
osd_ram_addr <= 20'd0;
else if(region_active == 1'b1)
osd_ram_addr <= osd_ram_addr + 20'd8;
end
osd_rom osd_rom_m0(
.clka (clk ),
.ena (1'b1 ),
.addra (osd_ram_addr[19:3] ), //生成的字符一个点为1bit,由于数据宽度为8bit,因此8个周期检查一次数据
.douta (q )
);
ila ila_m0(
.clk(clk),
.probe0(rgb_r_reg),
.probe1(rgb_g_reg),
.probe2(rgb_b_reg),
.probe3(active_x),
.probe4(active_y),
.probe5(q),
.probe6(region_active),
.probe7(osd_ram_addr)
);
//timing_gen_xy timing_gen_xy_m0(
// .rst_n (rst ),
// .clk (clk ),
// .i_hs (hs_reg_d0 ),
// .i_vs (vs_reg_d0 ),
// .i_de (video_active_d0 ),
// .i_data ({{rgb_r_reg},{rgb_g_reg},{rgb_b_reg}} ),
// .o_hs (pos_hs ),
// .o_vs (pos_vs ),
// .o_de (pos_de ),
// .o_data ({{video_r},{video_g},{video_b}} ),
// .x (pos_x ),
// .y (pos_y )
//);
endmodule
// 头文件,将各个模块连接到一起
module top(
input sys_clk,
output hdmi_oen,
output TMDS_clk_n,
output TMDS_clk_p,
output [2:0]TMDS_data_n,
output [2:0]TMDS_data_p
);
wire video_clk;
wire video_clk_5x;
wire video_hs;
wire video_vs;
wire video_de;
wire[7:0] video_r;
wire[7:0] video_g;
wire[7:0] video_b;
color_bar hdmi_color_bar(
.clk(video_clk),
.rst(1'b0),
.hs(video_hs),
.vs(video_vs),
.de(video_de),
.rgb_r(video_r),
.rgb_g(video_g),
.rgb_b(video_b)
);
video_clock video_clock_m0
(
// Clock in ports
.clk_in1(sys_clk),
// Clock out ports
.clk_out1(video_clk),
.clk_out2(video_clk_5x),
// Status and control signals
.reset(1'b0),
.locked()
);
rgb2dvi_0 rgb2dvi_m0 (
// DVI 1.0 TMDS video interface
.TMDS_Clk_p(TMDS_clk_p),
.TMDS_Clk_n(TMDS_clk_n),
.TMDS_Data_p(TMDS_data_p),
.TMDS_Data_n(TMDS_data_n),
.oen(hdmi_oen),
//Auxiliary signals
.aRst_n(1'b1), //-asynchronous reset; must be reset when RefClk is not within spec
// Video in
.vid_pData({video_r,video_g,video_b}),
.vid_pVDE(video_de),
.vid_pHSync(video_hs),
.vid_pVSync(video_vs),
.PixelClk(video_clk),
.SerialClk(video_clk_5x)// 5x PixelClk
);
endmodule
//video_define.v
//这部分代码是定义你的显示标准的
`define VIDEO_1280_720
运行结果
运行结果如下图所示:
这里我只导入了图片的R数据,背景设置成了红色,这些在代码部分都有体现。
下面讲讲我遇到的一个坑吧,就是在将图片输出成coe文件的时候,一定一定要注意你存的数据是按照行存的还是按照列存的,我在刚开始写的时候并没有注意导致把行列数据存反了,后来debug才发现。bug图长得有点丑,为了不破坏萌兔兔的美感我就不放上来了。
总结
第一次写博客,说实话有点不是很会写,没有像写论文一样用很书面的语言,我写博客的目的是为了总结自己学到的东西,之前看着夏雨闻老师的Verilog书的时候就萌生了写博客的念头,后来因为在忙研究方向而没有开始,我的研究方向是和fpga没有半毛钱瓜葛的,一个是数电,一个是光传感,人生嘛,总会遇到很多不如意的事情,我不能凭借自己的意志决定自己的研究方向,但是我并不打算妥协,这三年的经历很大程度会决定我后半生的工作方向,我是希望能把我感兴趣的东西变成我吃饭的家伙,只能对自己说———努力吧骚年!!!!!!!!!!
本文地址:https://blog.csdn.net/lang_jiaming/article/details/107441101