基于FPGA的 TMDS 编码 及 HDMI 显示

这篇具有很好参考价值的文章主要介绍了基于FPGA的 TMDS 编码 及 HDMI 显示。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基于FPGA的 TMDS 编码 及 HDMI 显示


目录

引言

TMDS 编码

原理简介

TMDS编码实现

 HDMI差分数据串行

实现方法

源码

HDMI显示方法

思路

实现

工程结构

源代码分享

板级调试视频


引言

最近在开发板上倒腾了一下 TMDS 视频编码的原理以及实现。特在此做一个记录。文附 全部设计源码、MATLAB源码,需要的可以关注一下。



TMDS 编码

原理简介

TMDS,Transition Minimized Differential Signaling,是一种视频编码方式。其将8位数据编码为10位数据。分为两大阶段:

1、8bit —> 9bit

第一比特不变,接下来的7比特或者是与上一比特异或,或者是同或,取决于哪种结果导致翻转数较少;第9比特指示是哪种操作(异或或者同或);

2、9bit —> 10bit

第10比特决定是否要把阶段1中的前8比特反相,决策依据取决于操作是否有利于整体数据的0-1平衡(即,DC平衡)。

编码流程

基于FPGA的 TMDS 编码 及 HDMI 显示

D 是 8 位 的像素数据

C0C1 为 控制信号

DE 是数据有效信号
cnt是一个寄存器,用来记录数据流的极性,一个大 于0的正值表明了数据流中总共多传了多少个1,一个小于0 的负值表明了数据流中总共多传了多少个0。cnt{t-1}为上一次传输的中的极性,cnt{t}为当前输入的数据的极性。

q_m 最小化传输的编码结果,9 位,由输入的8 位数据经过最小化传输编码原理编码得到。

q_out 对最小化传输编码结果的9 位数据继续进行直流平衡编码后得到的10位输出结果。

N1{x} 统计输入的参数X 中1 的个数
N0{X} 统计输入的参数X 中0 的个数

TMDS编码实现

// | ===================================================---------------------------===================================================
// | ---------------------------------------------------      TMDS 编码模块 	   ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-21
// | 完成时间 : 2022-12-21
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- 
// |			-2- 
// | 			-3- 
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:

module TMDS_ENCODE_MDL(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   	I_PIXEL_CLK  ,//像素数据时钟
input												   	I_SYS_RSTN   ,

input 													I_DATA_EN    ,
input 			[7:0]									I_PIXEL_DATA ,

input 													I_CTRL0      ,
input 													I_CTRL1      ,

output		reg	[9:0]									O_ENCODE_DATA
    );
// | ====================================   模块内部参数声明   ====================================

localparam 		LP_CTRL0 				= 				10'b1101010100;
localparam 		LP_CTRL1 				= 				10'b0010101011;
localparam 		LP_CTRL2 				= 				10'b0101010100;
localparam 		LP_CTRL3 				= 				10'b1010101011;

// | ====================================   模块内部信号声明   ====================================
// 寄存
reg 			[1:0]									R_I_DATA_EN;
reg 			[7:0]									R_I_PIXEL_DATA;
reg 			[1:0]									R_I_CTRL0;
reg 			[1:0]									R_I_CTRL1;

// 4 个判断条件 (依据流程图)
wire 													W_JUDGE_CONDT1;
wire 													W_JUDGE_CONDT2;
wire 													W_JUDGE_CONDT3;
wire 													W_JUDGE_CONDT4;

// 计数
reg 			[3:0]									R_I_PIXEL_DATA_1_NUM;
reg 			[3:0]									R_Q_M_1_NUM;
reg 			[3:0]									R_Q_M_0_NUM;
reg 			[4:0]									R_CNT;


// 9bit编码
wire 			[8:0]									W_Q_M;
reg 			[8:0]									R_Q_M;
// | ====================================   模块内部逻辑设计   ====================================
always @ (posedge I_PIXEL_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_I_DATA_EN 	  <= 2'd0;
		R_I_PIXEL_DATA    <= I_PIXEL_DATA;
		R_I_CTRL0		  <= 2'd0;
		R_I_CTRL1         <= 2'd0;
		R_Q_M 			  <= 9'd0;
	end
	else
	begin
		R_I_DATA_EN 	  <= {R_I_DATA_EN[0],I_DATA_EN};
		R_I_PIXEL_DATA    <= I_PIXEL_DATA;
		R_I_CTRL0		  <= {R_I_CTRL0[0],I_CTRL0};
		R_I_CTRL1         <= {R_I_CTRL1[0],I_CTRL1};
		R_Q_M 			  <= W_Q_M;
	end
end

always @ (posedge I_PIXEL_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_I_PIXEL_DATA_1_NUM <= 4'd0;
	end
	else
	begin
		R_I_PIXEL_DATA_1_NUM <= I_PIXEL_DATA[0] + I_PIXEL_DATA[1] + I_PIXEL_DATA[2] + I_PIXEL_DATA[3] + 
								I_PIXEL_DATA[4] + I_PIXEL_DATA[5] + I_PIXEL_DATA[6] + I_PIXEL_DATA[7] ;
	end
end

assign W_JUDGE_CONDT1 = (R_I_PIXEL_DATA_1_NUM >4) | ((R_I_PIXEL_DATA_1_NUM == 4) & !R_I_PIXEL_DATA[0]); 

assign W_Q_M[0] = R_I_PIXEL_DATA[0];
assign W_Q_M[1] = W_JUDGE_CONDT1 ? ~(W_Q_M[0] ^ R_I_PIXEL_DATA[1]) : W_Q_M[0] ^ R_I_PIXEL_DATA[1];
assign W_Q_M[2] = W_JUDGE_CONDT1 ? ~(W_Q_M[1] ^ R_I_PIXEL_DATA[2]) : W_Q_M[1] ^ R_I_PIXEL_DATA[2];
assign W_Q_M[3] = W_JUDGE_CONDT1 ? ~(W_Q_M[2] ^ R_I_PIXEL_DATA[3]) : W_Q_M[2] ^ R_I_PIXEL_DATA[3];
assign W_Q_M[4] = W_JUDGE_CONDT1 ? ~(W_Q_M[3] ^ R_I_PIXEL_DATA[4]) : W_Q_M[3] ^ R_I_PIXEL_DATA[4];
assign W_Q_M[5] = W_JUDGE_CONDT1 ? ~(W_Q_M[4] ^ R_I_PIXEL_DATA[5]) : W_Q_M[4] ^ R_I_PIXEL_DATA[5];
assign W_Q_M[6] = W_JUDGE_CONDT1 ? ~(W_Q_M[5] ^ R_I_PIXEL_DATA[6]) : W_Q_M[5] ^ R_I_PIXEL_DATA[6];
assign W_Q_M[7] = W_JUDGE_CONDT1 ? ~(W_Q_M[6] ^ R_I_PIXEL_DATA[7]) : W_Q_M[6] ^ R_I_PIXEL_DATA[7];
assign W_Q_M[8] = W_JUDGE_CONDT1 ? 1'b0:1'b1;

assign W_JUDGE_CONDT2 = R_I_DATA_EN[1];

always @ (posedge I_PIXEL_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_Q_M_1_NUM <= 4'd0;
		R_Q_M_0_NUM <= 4'd0;
	end
	else
	begin
		R_Q_M_1_NUM <= W_Q_M[0] + W_Q_M[1] + W_Q_M[2] + W_Q_M[3] + 
					   W_Q_M[4] + W_Q_M[5] + W_Q_M[6] + W_Q_M[7] ;
		R_Q_M_0_NUM <= !W_Q_M[0] + !W_Q_M[1] + !W_Q_M[2] + !W_Q_M[3] + 
					   !W_Q_M[4] + !W_Q_M[5] + !W_Q_M[6] + !W_Q_M[7] ;
	end
end

assign W_JUDGE_CONDT3 = (R_CNT == 5'd0) | (R_Q_M_0_NUM == R_Q_M_1_NUM);
assign W_JUDGE_CONDT4 = (!R_CNT[4] & (R_Q_M_1_NUM > R_Q_M_0_NUM)) | (R_CNT[4] & (R_Q_M_0_NUM > R_Q_M_1_NUM));

always @ (posedge I_PIXEL_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_CNT <= 5'd0;
		O_ENCODE_DATA <= 10'd0;
	end
	else if(W_JUDGE_CONDT2)
	begin
		if(W_JUDGE_CONDT3)
		begin
			O_ENCODE_DATA[7:0] <=  R_Q_M[8] ? R_Q_M[7:0] : ~R_Q_M[7:0];
			O_ENCODE_DATA[8]   <=  R_Q_M[8];
			O_ENCODE_DATA[9]   <= ~R_Q_M[8];
			R_CNT			   <=  R_Q_M[8] ? (R_CNT + R_Q_M_1_NUM - R_Q_M_0_NUM) : (R_CNT + R_Q_M_0_NUM - R_Q_M_1_NUM);
		end
		else
		begin
			if(W_JUDGE_CONDT4)
			begin
				O_ENCODE_DATA[7:0] <= ~R_Q_M[7:0];
				O_ENCODE_DATA[8]   <= R_Q_M[8];
				O_ENCODE_DATA[9]   <= 1'b1;
				R_CNT              <= R_CNT + {R_Q_M[8],1'b0} + R_Q_M_0_NUM - R_Q_M_1_NUM;
			end
			else
			begin
				O_ENCODE_DATA[7:0] <= R_Q_M[7:0];
				O_ENCODE_DATA[8]   <= R_Q_M[8];
				O_ENCODE_DATA[9]   <= 1'b0;
				R_CNT              <= R_CNT - {~R_Q_M[8],1'b0} + R_Q_M_1_NUM - R_Q_M_0_NUM;
			end
		end
	end
	else
	begin
		R_CNT <= 5'd0;
		case({R_I_CTRL1[1],R_I_CTRL0[1]})
			2'b00:
			begin
				O_ENCODE_DATA <= LP_CTRL0;
			end
			2'b01:
			begin
				O_ENCODE_DATA <= LP_CTRL1;
			end
			2'b10:
			begin
				O_ENCODE_DATA <= LP_CTRL2;
			end
			2'b11:
			begin
				O_ENCODE_DATA <= LP_CTRL3;
			end
			default:
			begin
				O_ENCODE_DATA <= LP_CTRL0;
			end
		endcase
	end
end
endmodule

 HDMI差分数据串行

实现方法

每个像素时钟到来时,给出10bit的编码数据,串行发送时,需要用 5倍 于像素数据时钟的时钟频率分别在时钟的双沿将数据逐比特发出。

双沿数据发送使用 ODDR 原语(相同边沿 模式),差分转换使用 OBUFDS 原语。

源码

// | ===================================================---------------------------===================================================
// | ---------------------------------------------------    HDMI 数据发送模块	   ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-21
// | 完成时间 : 2022-12-21
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- 仅包含视频数据发送(无音频)
// |			-2- 1280 * 720 分辨率
// | 			-3- 
// |
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:


module HDMI_DATA_TX_MDL(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   I_CLK_74M25  ,
input												   	I_CLK_371M25 ,

input 													I_SYS_RSTN   ,

input 													I_PIXEL_DATA_EN,
input 				[7:0]								I_PIXEL_DATA_R, 
input 				[7:0]								I_PIXEL_DATA_G,
input 				[7:0]								I_PIXEL_DATA_B,

input 													I_HSYNC,
input													   I_VSYNC,

output 				[2:0]								O_TMDS_DATA_P,
output 				[2:0]								O_TMDS_DATA_N,

output 													O_TMDS_CLK_P,
output 													O_TMDS_CLK_N


    );
// | ====================================   模块内部信号声明   ====================================

wire 				[9:0]								W_ENCODE_DATA_10BIT[2:0];
wire 				[3:0]								W_ODDR_Q;
// 数据分配
wire 				[4:0]								W_TMDS_CH0_D1;
wire 				[4:0]								W_TMDS_CH0_D2;

wire 				[4:0]								W_TMDS_CH1_D1;
wire 				[4:0]								W_TMDS_CH1_D2;

wire 				[4:0]								W_TMDS_CH2_D1;
wire 				[4:0]								W_TMDS_CH2_D2;

wire 				[4:0]								W_TMDS_CH3_D1;
wire 				[4:0]								W_TMDS_CH3_D2;
// 移位寄存
reg 				[4:0]								R_TMDS_CH0_D1;
reg 				[4:0]								R_TMDS_CH0_D2;

reg 				[4:0]								R_TMDS_CH1_D1;
reg 				[4:0]								R_TMDS_CH1_D2;

reg 				[4:0]								R_TMDS_CH2_D1;
reg 				[4:0]								R_TMDS_CH2_D2;

reg 				[4:0]								R_TMDS_CH3_D1;
reg 				[4:0]								R_TMDS_CH3_D2;

// 计数
reg 				[2:0]								R_CNT ;

// | ====================================   模块内部逻辑设计   ====================================

assign 				W_TMDS_CH0_D1			=	{W_ENCODE_DATA_10BIT[0][8],W_ENCODE_DATA_10BIT[0][6],W_ENCODE_DATA_10BIT[0][4],W_ENCODE_DATA_10BIT[0][2],W_ENCODE_DATA_10BIT[0][0]};
assign 				W_TMDS_CH0_D2			=	{W_ENCODE_DATA_10BIT[0][9],W_ENCODE_DATA_10BIT[0][7],W_ENCODE_DATA_10BIT[0][5],W_ENCODE_DATA_10BIT[0][3],W_ENCODE_DATA_10BIT[0][1]};

assign 				W_TMDS_CH1_D1			=	{W_ENCODE_DATA_10BIT[1][8],W_ENCODE_DATA_10BIT[1][6],W_ENCODE_DATA_10BIT[1][4],W_ENCODE_DATA_10BIT[1][2],W_ENCODE_DATA_10BIT[1][0]};
assign 				W_TMDS_CH1_D2			=	{W_ENCODE_DATA_10BIT[1][9],W_ENCODE_DATA_10BIT[1][7],W_ENCODE_DATA_10BIT[1][5],W_ENCODE_DATA_10BIT[1][3],W_ENCODE_DATA_10BIT[1][1]};

assign 				W_TMDS_CH2_D1			=	{W_ENCODE_DATA_10BIT[2][8],W_ENCODE_DATA_10BIT[2][6],W_ENCODE_DATA_10BIT[2][4],W_ENCODE_DATA_10BIT[2][2],W_ENCODE_DATA_10BIT[2][0]};
assign 				W_TMDS_CH2_D2			=	{W_ENCODE_DATA_10BIT[2][9],W_ENCODE_DATA_10BIT[2][7],W_ENCODE_DATA_10BIT[2][5],W_ENCODE_DATA_10BIT[2][3],W_ENCODE_DATA_10BIT[2][1]};

assign 				W_TMDS_CH3_D1			=	{1'b1,1'b1,1'b0,1'b0,1'b0};
assign 				W_TMDS_CH3_D2			=	{1'b1,1'b1,1'b1,1'b0,1'b0};

always @ (posedge I_CLK_371M25)
begin
   if(~I_SYS_RSTN)
   begin
      R_TMDS_CH0_D1  <=  5'd0;
      R_TMDS_CH0_D2  <=  5'd0;
      R_TMDS_CH1_D1  <=  5'd0;
      R_TMDS_CH1_D2  <=  5'd0;
      R_TMDS_CH2_D1  <=  5'd0;
      R_TMDS_CH2_D2  <=  5'd0;
      R_TMDS_CH3_D1  <=  5'd0;
      R_TMDS_CH3_D2  <=  5'd0;
      R_CNT          <=  3'd0;
   end
   else
   begin
	  R_CNT		    <= R_CNT[2] ? 3'd0 : R_CNT + 3'd1;
	  R_TMDS_CH0_D1 <= R_CNT[2] ? W_TMDS_CH0_D1 : {1'b0,R_TMDS_CH0_D1[4:1]};
	  R_TMDS_CH0_D2 <= R_CNT[2] ? W_TMDS_CH0_D2 : {1'b0,R_TMDS_CH0_D2[4:1]};
	  R_TMDS_CH1_D1 <= R_CNT[2] ? W_TMDS_CH1_D1 : {1'b0,R_TMDS_CH1_D1[4:1]};
	  R_TMDS_CH1_D2 <= R_CNT[2] ? W_TMDS_CH1_D2 : {1'b0,R_TMDS_CH1_D2[4:1]};
	  R_TMDS_CH2_D1 <= R_CNT[2] ? W_TMDS_CH2_D1 : {1'b0,R_TMDS_CH2_D1[4:1]};
	  R_TMDS_CH2_D2 <= R_CNT[2] ? W_TMDS_CH2_D2 : {1'b0,R_TMDS_CH2_D2[4:1]};
	  R_TMDS_CH3_D1 <= R_CNT[2] ? W_TMDS_CH3_D1 : {1'b0,R_TMDS_CH3_D1[4:1]};
	  R_TMDS_CH3_D2 <= R_CNT[2] ? W_TMDS_CH3_D2 : {1'b0,R_TMDS_CH3_D2[4:1]};
   end
end

// | ====================================   模块内部模块例化   ====================================
// | TMDS 编码模块
	TMDS_ENCODE_MDL INST_TMDS_ENCODE_MDL_R
		(
			.I_PIXEL_CLK   (I_CLK_74M25),
			.I_SYS_RSTN    (I_SYS_RSTN),
			.I_DATA_EN     (I_PIXEL_DATA_EN),
			.I_PIXEL_DATA  (I_PIXEL_DATA_R),
			.I_CTRL0       (1'b0),
			.I_CTRL1       (1'b0),
			.O_ENCODE_DATA (W_ENCODE_DATA_10BIT[2])
		);
	TMDS_ENCODE_MDL INST_TMDS_ENCODE_MDL_G
		(
			.I_PIXEL_CLK   (I_CLK_74M25),
			.I_SYS_RSTN    (I_SYS_RSTN),
			.I_DATA_EN     (I_PIXEL_DATA_EN),
			.I_PIXEL_DATA  (I_PIXEL_DATA_G),
			.I_CTRL0       (1'b0),
			.I_CTRL1       (1'b0),
			.O_ENCODE_DATA (W_ENCODE_DATA_10BIT[1])
		);
	TMDS_ENCODE_MDL INST_TMDS_ENCODE_MDL_B
		(
			.I_PIXEL_CLK   (I_CLK_74M25),
			.I_SYS_RSTN    (I_SYS_RSTN),
			.I_DATA_EN     (I_PIXEL_DATA_EN),
			.I_PIXEL_DATA  (I_PIXEL_DATA_B),
			.I_CTRL0       (I_HSYNC),
			.I_CTRL1       (I_VSYNC),
			.O_ENCODE_DATA (W_ENCODE_DATA_10BIT[0])
		);
// | ODDR OBUFDS 原语例化
// CH0
   ODDR #(
      .DDR_CLK_EDGE("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE" 
      .INIT        (1'b0       ),  // Initial value of Q: 1'b0 or 1'b1
      .SRTYPE      ("SYNC"     )   // Set/Reset type: "SYNC" or "ASYNC" 
   ) ODDR_CH0 (
      .Q  (W_ODDR_Q[0]      ),     // 1-bit DDR output
      .C  (I_CLK_371M25     ),     // 1-bit clock input
      .CE (1'b1             ),     // 1-bit clock enable input
      .D1 (R_TMDS_CH0_D1[0] ),     // 1-bit data input (positive edge)
      .D2 (R_TMDS_CH0_D2[0] ),     // 1-bit data input (negative edge)
      .R  (1'b0             ),     // 1-bit reset
      .S  (1'b0             )      // 1-bit set
   );
   OBUFDS #(
      .IOSTANDARD("DEFAULT"),      // Specify the output I/O standard
      .SLEW      ("SLOW"   )       // Specify the output slew rate
   ) OBUFDS_CH0 (
      .O  (O_TMDS_DATA_P[0]),      // Diff_p output (connect directly to top-level port)
      .OB (O_TMDS_DATA_N[0]),      // Diff_n output (connect directly to top-level port)
      .I  (W_ODDR_Q[0]     )       // Buffer input
   );

// CH1
   ODDR #(
      .DDR_CLK_EDGE("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE" 
      .INIT        (1'b0       ),  // Initial value of Q: 1'b0 or 1'b1
      .SRTYPE      ("SYNC"     )   // Set/Reset type: "SYNC" or "ASYNC" 
   ) ODDR_CH1 (
      .Q  (W_ODDR_Q[1]      ),     // 1-bit DDR output
      .C  (I_CLK_371M25     ),     // 1-bit clock input
      .CE (1'b1             ),     // 1-bit clock enable input
      .D1 (R_TMDS_CH1_D1[0] ),     // 1-bit data input (positive edge)
      .D2 (R_TMDS_CH1_D2[0] ),     // 1-bit data input (negative edge)
      .R  (1'b0             ),     // 1-bit reset
      .S  (1'b0             )      // 1-bit set
   );
   OBUFDS #(
      .IOSTANDARD("DEFAULT"),      // Specify the output I/O standard
      .SLEW      ("SLOW"   )       // Specify the output slew rate
   ) OBUFDS_CH1 (
      .O  (O_TMDS_DATA_P[1]),      // Diff_p output (connect directly to top-level port)
      .OB (O_TMDS_DATA_N[1]),      // Diff_n output (connect directly to top-level port)
      .I  (W_ODDR_Q[1]     )       // Buffer input
   );

// CH2
   ODDR #(
      .DDR_CLK_EDGE("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE" 
      .INIT        (1'b0       ),  // Initial value of Q: 1'b0 or 1'b1
      .SRTYPE      ("SYNC"     )   // Set/Reset type: "SYNC" or "ASYNC" 
   ) ODDR_CH2 (
      .Q  (W_ODDR_Q[2]      ),     // 1-bit DDR output
      .C  (I_CLK_371M25     ),     // 1-bit clock input
      .CE (1'b1             ),     // 1-bit clock enable input
      .D1 (R_TMDS_CH2_D1[0] ),     // 1-bit data input (positive edge)
      .D2 (R_TMDS_CH2_D2[0] ),     // 1-bit data input (negative edge)
      .R  (1'b0             ),     // 1-bit reset
      .S  (1'b0             )      // 1-bit set
   );
   OBUFDS #(
      .IOSTANDARD("DEFAULT"),      // Specify the output I/O standard
      .SLEW      ("SLOW"   )       // Specify the output slew rate
   ) OBUFDS_CH2 (
      .O  (O_TMDS_DATA_P[2]),      // Diff_p output (connect directly to top-level port)
      .OB (O_TMDS_DATA_N[2]),      // Diff_n output (connect directly to top-level port)
      .I  (W_ODDR_Q[2]     )       // Buffer input
   );

// CH3
   ODDR #(
      .DDR_CLK_EDGE("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE" 
      .INIT        (1'b0       ),  // Initial value of Q: 1'b0 or 1'b1
      .SRTYPE      ("SYNC"     )   // Set/Reset type: "SYNC" or "ASYNC" 
   ) ODDR_CH3 (
      .Q  (W_ODDR_Q[3]      ),     // 1-bit DDR output
      .C  (I_CLK_371M25     ),     // 1-bit clock input
      .CE (1'b1             ),     // 1-bit clock enable input
      .D1 (R_TMDS_CH3_D1[0] ),     // 1-bit data input (positive edge)
      .D2 (R_TMDS_CH3_D2[0] ),     // 1-bit data input (negative edge)
      .R  (1'b0             ),     // 1-bit reset
      .S  (1'b0             )      // 1-bit set
   );
   OBUFDS #(
      .IOSTANDARD("DEFAULT"),      // Specify the output I/O standard
      .SLEW      ("SLOW"   )       // Specify the output slew rate
   ) OBUFDS_CH3 (
      .O  (O_TMDS_CLK_P),          // Diff_p output (connect directly to top-level port)
      .OB (O_TMDS_CLK_N),          // Diff_n output (connect directly to top-level port)
      .I  (W_ODDR_Q[3] )           // Buffer input
   );
endmodule

 

HDMI显示方法

思路

共有两种演示模式,由拨码开关来切换。

开关为 1:彩条轮播;

开关为 0:显示图片;

图片为:(忽略水印)

基于FPGA的 TMDS 编码 及 HDMI 显示

图片尺寸大小:160*277 (由于FPGA的ROM资源有限,此处显示小图片)

ROM配置 : 位宽 24位,深度 160*277

像素时钟:74.25MHz

串行发送时钟:371.25MHz

分辨率:1280*720@60Hz

实现

工程结构

基于FPGA的 TMDS 编码 及 HDMI 显示

源代码分享

测试顶层:

// | ===================================================---------------------------===================================================
// | --------------------------------------------------- HDMI 数据发送测试顶层模块 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-22
// | 完成时间 : 2022-12-22
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- 仅包含视频数据发送(无音频)
// |			-2- 1280 * 720 分辨率
// | 			-3- 
// |
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:

// Resolution_640x480    //时钟为25.175MHz
// Resolution_800x480    //时钟为33MHz,可兼容TFT5.0
// Resolution_800x600    //时钟为40MHz
// Resolution_1024x600   //时钟为51MHz
// Resolution_1024x768   //时钟为65MHz
// Resolution_1280x720   //时钟为74.25MHz
// Resolution_1920x1080  //时钟为148.5MHz

// 宏定义
`define DEF_H_Total_Time    12'd1650
`define DEF_H_Right_Border  12'd0
`define DEF_H_Front_Porch   12'd110
`define DEF_H_Sync_Time     12'd40
`define DEF_H_Back_Porch    12'd220
`define DEF_H_Left_Border   12'd0

`define DEF_V_Total_Time    12'd750
`define DEF_V_Bottom_Border 12'd0
`define DEF_V_Front_Porch   12'd5
`define DEF_V_Sync_Time     12'd5
`define DEF_V_Back_Porch    12'd20
`define DEF_V_Top_Border    12'd0


module HDMI_DISP_TEST_MDL(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   	I_SYS_CLK  ,
input												   	I_SYS_RSTN ,

input 													I_DISP_SEL,

output 													O_HDMI_EN,
output 				[2:0]								O_TMDS_DATA_P,
output 				[2:0]								O_TMDS_DATA_N,
output 													O_TMDS_CLK_P,
output 													O_TMDS_CLK_N

    );

// | ====================================   模块内部参数声明   ====================================
localparam 			LP_DISP_WIDTH			=			1280;
localparam 			LP_DISP_HEIGHT          =			720;

//颜色编码
localparam 			LP_BLACK    			= 			24'h000000; //黑色
localparam 			LP_BLUE     			= 			24'h0000FF; //蓝色
localparam 			LP_RED      			= 			24'hFF0000; //红色
localparam 			LP_PURPPLE  			= 			24'hFF00FF; //紫色
localparam 			LP_GREEN    			= 			24'h00FF00; //绿色
localparam 			LP_CYAN     			= 			24'h00FFFF; //青色
localparam 			LP_YELLOW   			= 			24'hFFFF00; //黄色
localparam 			LP_WHITE    			= 			24'hFFFFFF; //白色
    
localparam 			LP_H_DATA_BEGIN   		=			`DEF_H_Sync_Time  + `DEF_H_Back_Porch    + `DEF_H_Left_Border - 1'b1;
localparam 			LP_H_DATA_END     		=			`DEF_H_Total_Time - `DEF_H_Right_Border  - `DEF_H_Front_Porch - 1'b1;
localparam 			LP_V_DATA_BEGIN   		=			`DEF_V_Sync_Time  + `DEF_V_Back_Porch    + `DEF_V_Top_Border  - 1'b1;
localparam 			LP_V_DATA_END     		=			`DEF_V_Total_Time - `DEF_V_Bottom_Border - `DEF_V_Front_Porch - 1'b1;


// | ====================================   模块内部信号声明   ====================================
wire 													W_CLK_100M;
wire													W_CLK_74M25;
wire 													W_CLK_371M25;

wire 													W_MMCM_LOCKED1;
wire													W_MMCM_LOCKED2;

wire 				[11:0]								W_H_ADDR;
wire 				[11:0]								W_V_ADDR;
reg 				[11:0]								R_H_SCAN_CNT;
reg 				[11:0]								R_V_SCAN_CNT;
wire 													W_H_CNT_OVER;
wire 													W_V_CNT_OVER;
reg 													R_PIXEL_DATA_REQ;
reg 				[23:0]								R_PIXEL_DATA;
reg 													R_H_SYNC;
reg 													R_V_SYNC;
reg 				[7:0]								R_PIXEL_DATA_R;
reg 				[7:0]								R_PIXEL_DATA_G;
reg 				[7:0]								R_PIXEL_DATA_B;


wire 													W_R0_FLAG;//第 0 行标志位 
wire 													W_R1_FLAG;//第 1 行标志位 
wire 													W_R2_FLAG;//第 2 行标志位 
wire 													W_R3_FLAG;//第 3 行标志位

wire 													W_C0_FLAG;//第 0 列标志位
wire 													W_C1_FLAG;//第 1 列标志位
wire 													W_C2_FLAG;//第 0 列标志位
wire 													W_C3_FLAG;//第 1 列标志位

reg 				[8*24-1:0]							R_COLOR;
reg 				[24:0]								R_CNT_25M;
wire 													W_PULSE;

// ROM
wire 				[15:0]								W_ROM_ADDR;
wire 				[23:0]								W_ROM_DATA;

// | ====================================   模块内部逻辑设计   ====================================
// 行标志
assign 		W_R0_FLAG 		 = (W_V_ADDR >= 0                 ) && (W_V_ADDR < LP_DISP_HEIGHT/4  );   
assign 		W_R1_FLAG 		 = (W_V_ADDR >= LP_DISP_HEIGHT/4  ) && (W_V_ADDR < LP_DISP_HEIGHT/2  );   
assign 		W_R2_FLAG 		 = (W_V_ADDR >= LP_DISP_HEIGHT/2  ) && (W_V_ADDR < LP_DISP_HEIGHT/4*3);   
assign 		W_R3_FLAG 		 = (W_V_ADDR >= LP_DISP_HEIGHT/4*3) && (W_V_ADDR < LP_DISP_HEIGHT    );   
// 列标志
assign 		W_C0_FLAG 		 = (W_H_ADDR >= 0                 ) && (W_H_ADDR < LP_DISP_WIDTH/4   );   
assign 		W_C1_FLAG 		 = (W_H_ADDR >= LP_DISP_WIDTH/4   ) && (W_H_ADDR < LP_DISP_WIDTH/2   );  
assign 		W_C2_FLAG 		 = (W_H_ADDR >= LP_DISP_WIDTH/2   ) && (W_H_ADDR < LP_DISP_WIDTH/4*3 );   
assign 		W_C3_FLAG 		 = (W_H_ADDR >= LP_DISP_WIDTH/4*3 ) && (W_H_ADDR < LP_DISP_WIDTH     ); 

always @ (posedge I_SYS_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_CNT_25M <= 25'd0;
		R_COLOR   <= {LP_BLACK,LP_BLUE,LP_RED,LP_PURPPLE,LP_GREEN,LP_CYAN,LP_YELLOW,LP_WHITE};
	end
	else
	begin
		if(R_CNT_25M == 25'd25_000_000)
		begin
			R_CNT_25M <= 25'd0;
		end
		else
		begin
			R_CNT_25M <= R_CNT_25M + 1;
		end

		if(W_PULSE)
		begin
			R_COLOR <= {R_COLOR[0+:7*24],R_COLOR[(8*24-1)-:24]};
		end
		else
		begin
			R_COLOR <= R_COLOR;
		end
	end
end

assign W_PULSE = (R_CNT_25M == 25'd25_000_000);
// 像素数据
always@(*)
begin
	if(~I_SYS_RSTN)
	begin
		R_PIXEL_DATA = 24'd0;
	end
	else if(R_PIXEL_DATA_REQ)
	begin
		if(I_DISP_SEL)
		begin
			case({W_R0_FLAG,W_R1_FLAG,W_R2_FLAG,W_R3_FLAG})
				4'b1000:
				begin
					R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[4*24+:24] : 
								   W_C1_FLAG ? R_COLOR[0*24+:24] : 
								   W_C2_FLAG ? R_COLOR[0*24+:24] : 
								   W_C3_FLAG ? R_COLOR[4*24+:24] :  R_COLOR[0*24+:24];
				end
				4'b0100:
				begin
					R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[0*24+:24] : 
								   W_C1_FLAG ? R_COLOR[7*24+:24] : 
								   W_C2_FLAG ? R_COLOR[7*24+:24] : 
								   W_C3_FLAG ? R_COLOR[0*24+:24] :  R_COLOR[0*24+:24];
	
				end
				4'b0010:
				begin
					R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[0*24+:24] : 
								   W_C1_FLAG ? R_COLOR[7*24+:24] : 
								   W_C2_FLAG ? R_COLOR[7*24+:24] : 
								   W_C3_FLAG ? R_COLOR[0*24+:24] :  R_COLOR[0*24+:24];
	
				end
				4'b0001:
				begin
					R_PIXEL_DATA = W_C0_FLAG ? R_COLOR[4*24+:24] : 
								   W_C1_FLAG ? R_COLOR[0*24+:24] : 
								   W_C2_FLAG ? R_COLOR[0*24+:24] : 
								   W_C3_FLAG ? R_COLOR[4*24+:24] :  R_COLOR[0*24+:24];
	
				end
				default:
				begin
					R_PIXEL_DATA = 24'hFFFFFF;
				end
			endcase
		end
		else
		begin
			if((W_H_ADDR >= 12'd499) && (W_H_ADDR <= 12'd776) && (W_V_ADDR >= 12'd279) && (W_V_ADDR <= 12'd439))
			begin
				R_PIXEL_DATA = W_ROM_DATA;
			end
			else
			begin
				R_PIXEL_DATA = 24'hFFFFFF;
			end
		end
	end
	else
	begin
		R_PIXEL_DATA = 24'hFFFFFF;
	end
end

assign W_ROM_ADDR = ((W_H_ADDR >= 12'd499) && (W_H_ADDR <= 12'd776) && (W_V_ADDR >= 12'd279) && (W_V_ADDR <= 12'd439)) ?
					((W_H_ADDR-12'd499) * 160 + (W_V_ADDR - 12'd279)) : 16'd0;
// 行扫描
always @ (posedge W_CLK_74M25)
begin
	if(~W_MMCM_LOCKED2)
	begin
		R_H_SCAN_CNT <= 12'd0;
	end
	else if(W_H_CNT_OVER)
	begin
		R_H_SCAN_CNT <= 12'd0;
	end
	else
	begin
		R_H_SCAN_CNT <= R_H_SCAN_CNT + 1;
	end

end

assign W_H_CNT_OVER = (R_H_SCAN_CNT >= (`DEF_H_Total_Time - 1));

// 场扫描
always @ (posedge W_CLK_74M25)
begin
	if(~W_MMCM_LOCKED2)
	begin
		R_V_SCAN_CNT <= 12'd0;
	end
	else if(W_H_CNT_OVER)
	begin
		if(W_V_CNT_OVER)
		begin
			R_V_SCAN_CNT <= 12'd0;
		end
		else
		begin
			R_V_SCAN_CNT <= R_V_SCAN_CNT + 1;
		end
	end
	else
	begin
		R_V_SCAN_CNT <= R_V_SCAN_CNT ;
	end

end

assign W_V_CNT_OVER = (R_V_SCAN_CNT >= (`DEF_V_Total_Time - 1));

// 行同步
always @ (posedge W_CLK_74M25)
begin
	if(~W_MMCM_LOCKED2)
	begin
		R_H_SYNC <= 1'b0;
	end
	else
	begin
		R_H_SYNC <= (R_H_SCAN_CNT > (`DEF_H_Sync_Time - 1));
	end 
end
// 场同步
always @ (posedge W_CLK_74M25)
begin
	if(~W_MMCM_LOCKED2)
	begin
		R_V_SYNC <= 1'b0;
	end
	else
	begin
		R_V_SYNC <= (R_V_SCAN_CNT > (`DEF_V_Sync_Time - 1));
	end 
end
// RGB 数据
always @ (posedge W_CLK_74M25)
begin
	if(~W_MMCM_LOCKED2)
	begin
		R_PIXEL_DATA_R <= 8'd0;
		R_PIXEL_DATA_G <= 8'd0;
		R_PIXEL_DATA_B <= 8'd0;
	end
	else if(R_PIXEL_DATA_REQ)
	begin
		R_PIXEL_DATA_R <= R_PIXEL_DATA[16+:8];
		R_PIXEL_DATA_G <= R_PIXEL_DATA[8+:8];
		R_PIXEL_DATA_B <= R_PIXEL_DATA[0+:8];
	end 
	else
	begin
		R_PIXEL_DATA_R <= 8'd0;
		R_PIXEL_DATA_G <= 8'd0;
		R_PIXEL_DATA_B <= 8'd0;		
	end
end
// 数据使能
always @ (posedge W_CLK_74M25)
begin
	if(~W_MMCM_LOCKED2)
	begin
		R_PIXEL_DATA_REQ <= 1'b0;
	end
	else
	begin
		R_PIXEL_DATA_REQ <= (R_H_SCAN_CNT >= LP_H_DATA_BEGIN) && (R_H_SCAN_CNT < LP_H_DATA_END)&&
							(R_V_SCAN_CNT >= LP_V_DATA_BEGIN) && (R_V_SCAN_CNT < LP_V_DATA_END);
	end 
end

// 行 场 地址
assign W_H_ADDR = R_PIXEL_DATA_REQ ? (R_H_SCAN_CNT - LP_H_DATA_BEGIN) : 12'd0;
assign W_V_ADDR = R_PIXEL_DATA_REQ ? (R_V_SCAN_CNT - LP_V_DATA_BEGIN) : 12'd0;

assign O_HDMI_EN = 1'b1;

// | ====================================   模块内部模块例化   ====================================
// | 时钟 IP例化
  MMCM_HDMI INST_MMCM_HDMI
   (
    // Clock out ports
    .O_CLK_74M25(W_CLK_74M25),     // output O_CLK_74M25
    .O_CLK_371M25(W_CLK_371M25),     // output O_CLK_371M25
    // Status and control signals
    .reset(!I_SYS_RSTN ), // input reset
    .locked(W_MMCM_LOCKED2),       // output locked
   // Clock in ports
    .I_CLK_50M(I_SYS_CLK));      // input I_CLK_100M

// | HDMI 数据发送模块例化
	HDMI_DATA_TX_MDL INST_HDMI_DATA_TX_MDL
		(
			.I_CLK_74M25     (W_CLK_74M25),
			.I_CLK_371M25    (W_CLK_371M25),
			.I_SYS_RSTN      (W_MMCM_LOCKED2),
			.I_PIXEL_DATA_EN (R_PIXEL_DATA_REQ),
			.I_PIXEL_DATA_R  (R_PIXEL_DATA_R),
			.I_PIXEL_DATA_G  (R_PIXEL_DATA_G),
			.I_PIXEL_DATA_B  (R_PIXEL_DATA_B),
			.I_HSYNC         (R_H_SYNC),
			.I_VSYNC         (R_V_SYNC),
			.O_TMDS_DATA_P   (O_TMDS_DATA_P),
			.O_TMDS_DATA_N   (O_TMDS_DATA_N),
			.O_TMDS_CLK_P    (O_TMDS_CLK_P),
			.O_TMDS_CLK_N    (O_TMDS_CLK_N)
		);
// ROM
ROM_RGB_DATA INST_ROM_RGB_DATA (
  .clka(W_CLK_74M25),    // input wire clka
  .ena(1'b1),      // input wire ena
  .addra(W_ROM_ADDR),  // input wire [15 : 0] addra
  .douta(W_ROM_DATA)  // output wire [23 : 0] douta
);
endmodule

ROM 初始化文件 .coe 生成 MATLAB源码 

%% -------- 1280 * 720 像素数据提取 存入 .coe文件 --------
% 作    者:Xu Y. B.(CSDN 用户名:在路上,正出发)
% 创建日期:2022-12-22
% 完成日期:2022-12-22


%% 准备
clc;
clearvars;
close all;

%% 图片读取
File_Path = "D:\VIVADO_WORK_SPACE\XC7A35T_DESIGN\Figure\";
Figure4_Name = "Figure4.png";

Figure_Data = imread(strcat(File_Path,Figure4_Name));

imshow(Figure_Data(:,:,:))

DATA_R = uint32(reshape(Figure_Data(:,:,1),[],1));
DATA_G = uint32(reshape(Figure_Data(:,:,2),[],1));
DATA_B = uint32(reshape(Figure_Data(:,:,3),[],1));

DATA_COE = uint32(DATA_R * 256^2 + DATA_G * 256 + DATA_B);

str='D:\VIVADO_WORK_SPACE\XC7A35T_DESIGN\COE_FILE\FIGURE_DATA.coe';%该字符串为文件的路径
fid_coe=fopen(str,'w+');%打开文件获得ID
fprintf(fid_coe,'memory_initialization_radix=10;\n');%写入第一行
fprintf(fid_coe,'memory_initialization_vector=\n');%写入第二行
fprintf(fid_coe,'%d,\n',DATA_COE((1:end-1),1));%写入数据并以逗号隔开
fprintf(fid_coe,'%d;',DATA_COE(end,1));%写入最后一个数据以分号结束

板级调试视频

基于FPGA的TMDS编码以及HDMI驱动 演示视频https://live.csdn.net/v/264920文章来源地址https://www.toymoban.com/news/detail-470602.html



到了这里,关于基于FPGA的 TMDS 编码 及 HDMI 显示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MS3814:DVI/HDMI TMDS FR-4 和电缆均衡器/驱动器

    产品简述 MS3814 是一款 TMDS 均衡 / 驱动器芯片,用于补偿 FR-4 和 电缆到 DVI/HDMI 连接器的损耗。提供完全满足 DVI/HDMI TMDS 要求的输出。芯片还可用于 DVI/HDMI 电缆以延长传输距离,提 高连接器接收侧电缆通道的抖动余量。片上 TMDS 驱动器既可工 作在典型的 DVI/HDMI 电流模式,此

    2024年02月01日
    浏览(40)
  • 【FPGA】通俗理解从VGA显示到HDMI显示

    注:大部分参考内容来自“征途Pro《FPGA Verilog开发实战指南——基于Altera EP4CE10》2021.7.10(上)”  贴个下载地址: 野火FPGA-Altera-EP4CE10征途开发板_核心板 — 野火产品资料下载中心 文档 hdmi显示器驱动设计与验证 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开

    2024年02月09日
    浏览(40)
  • FPGA—HDMI 显示器驱动设计与验证(附代码)

    目录 1.理论 2.实操 2.1 顶层模块 2.2 时钟生成模块 2.3 HDMI 驱动控制模块 2.3.1 编码模块 2.3.2 并行转串行模块 2.4 顶层仿真验证 3.总结 HDMI简介       VGA 接口体积较大;且传输的模拟信号易受外界干扰。因此在VGA 接口之后,首先推出的是 DVI 接口, DVI 是基于 TMDS(Transition Minim

    2024年02月10日
    浏览(39)
  • (七)零基础FPGA图像处理——HDMI彩条显示实验

    此篇为专栏 《FPGA学习笔记》 的第七篇,记录我的学习FPGA的一些开发过程和心得感悟,刚接触FPGA的朋友们可以先去此专栏置顶 《FPGA零基础入门学习路线》来做最基础的扫盲。 本篇内容基于笔者实际开发过程和正点原子资料撰写,将会详细讲解此FPGA实验的全流程, 诚挚 地

    2024年02月03日
    浏览(48)
  • FPGA 20个例程篇:16.HDMI显示彩色风景图

           HDMI接口在消费类电子行业,比如笔记本电脑、液晶电视、投影仪等产品中均得到了广泛的应用,一些专业的视频设备如摄像机、视频切换器、机顶盒等也都会集成HDMI接口,HDMI是新一代的多媒体接口标准即高清多媒体接口,作为应用最为广泛的音视频传输接口,熟练

    2024年02月01日
    浏览(35)
  • FPGA实现SD卡读写照片显示在HDMI显示屏(IP调用)

            概述: TF 卡读写数据,利用 VDMA 和 HDMI 显示视频图像,实现从 SD 卡读取图片并且在 HDMI 显示器上显示。 步骤一:PL端配置IP SD卡配置 确保 SDIO 接口,设置正确,SD_0 是 TF 卡 在ZYNQ的IP核中配置好SD卡,SD0是TF卡,SD1是EMMC    2.VDMA配置 查找官方IP: ·VTC IP:这个 IP 就是

    2024年04月22日
    浏览(27)
  • 基于FPGA:运动目标检测(VGA显示,原理图+源码+硬件选择)

        话不多说,先上视频看效果。 基于FPGA:运动目标检测 开发板Altera:EP4CE10F17C8 摄像头:OV5640 缓存数据:SDRAM 板子是自己制作的     根据帧差法的实现流程,设计的双端口SDRAM控制器,一侧读写端口用做帧缓存,另一个端口用来缓存视频流,如图所示。     在使用

    2024年02月04日
    浏览(31)
  • FPGA 20个例程篇:19.OV7725摄像头实时采集送HDMI显示(三)

           在详细介绍过OV7725 CMOS Sensor的相关背景知识和如何初始化其内部寄存器达到输出预期视频流的目的后,就到了该例程的核心内容即把OV7725输出的视频流预先缓存到外部DDR3颗粒,接着按照HDMI的视频格式把DDR3颗粒内存储的一帧一帧图像数据送显到屏幕上显示,如图1所示

    2024年01月17日
    浏览(45)
  • FPGA 20个例程篇:19.OV7725摄像头实时采集送HDMI显示(一)

           在例程“OV7725摄像头实时采集送HDMI显示”中,我们将走近FPGA图像处理的世界,图像处理、数字信号、高速接口也一直被业界公认为FPGA应用的三大主流领域,这个例程主要基于OV7725摄像头的视频图像采集项目,进行了详细地需求分析,从顶层到底层,从框架到功能,

    2024年02月02日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包