基于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日
    浏览(66)
  • 【FPGA】通俗理解从VGA显示到HDMI显示

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

    2024年02月09日
    浏览(47)
  • 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日
    浏览(46)
  • (七)零基础FPGA图像处理——HDMI彩条显示实验

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

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

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

    2024年02月01日
    浏览(44)
  • 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日
    浏览(33)
  • 基于FPGA:运动目标检测(VGA显示,原理图+源码+硬件选择)

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

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

            在介绍完OV7725初始化配置和视频采集模块后,就到了整个项目的核心部分即DDR3乒乓存储图像模块,为了实现整个FPGA项目工程当中良好的实时性,乒乓操作在广泛应用在FPGA视频加速处理和数字信号处理中。        关于乒乓操作,有很多的FPGA相关书籍都多多少少做了

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

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

    2024年02月02日
    浏览(55)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包