HDMI显示器驱动设计与验证

本文详细介绍了HDMI接口的工作原理,重点剖析了TMDS传输和hdmi_ctrl模块,包括数据编码、并串转换过程,以及如何通过encode和par_to_ser模块实现音频视频数据的高效传输。同时概述了DDC和CEC通道的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HDMI简介

HDMI,英文全称“High Definition Multimedia Interface”,高清多媒体接口

VGA接口成本低,结构简单,应用灵活,但是传输的是模拟信号,就容易受到外界干扰源的影响,受到影响会导致信号畸变,传输的图像就会出现问题,VGA接口体积大,不利于便携设备的使用。后来出现了DVI接口,DVIA(只传输模拟信号)、DVID(只传输数字信号)、DVII(兼容数字信号和模拟信号),数字信号比较不容易受到干扰,体积比VGA还大,只能传输图像,不能传输音频信号。后来出现了HDMI接口,体积小,抗干扰能力更强,可以实现音频和数字信号的同步传输,兼容性好
在这里插入图片描述

引脚定义引脚定义
1数据2+11时钟屏蔽
2数据2屏蔽12时钟-
3数据2-13CEC
4数据1+14保留
5数据1屏蔽15DDC时钟线(SCL)
6数据1-16DDC数据线(SDA)
7数据0+17DDC / CEC地
8数据0屏蔽18+5V电源
9数据0-19热插拔检测
10时钟+

按功能可以分为四大类:

  1. TMDS,是一种传输技术,引脚1~引脚12,用于发送音频、视频和各种辅助数据,遵循DVI编码方式,引脚1和引脚3、引脚4和引脚6、引脚7和引脚9、引脚10和引脚12是四对差分信号
  2. DDC通道,显示数据通道,引脚15~引脚17,只需要单向获取接收端的信息,也就是显示器,其实就是I2C接口
  3. CEC通道,引脚13和引脚17
  4. 其他,保留、电源、热插拔检测

HDMI显示原理

发送端将图像数据、音频数据和控制信号转换成4对差分信号,传给接收方,供接收方解码使用
在这里插入图片描述

TMDS传输原理

TMDS,英文全称“Transition Minimized Differential Signaling”,最小化传输差分信号
在这里插入图片描述
发送端收到表示RGB信号的24bit并行数据,这些数据在行场同步信号和使能信号下,与时钟信号一起进行编码并串转换,之后将3个表示RGB的数据和1个时钟信号分别分配到4个独立的通道发送出去

接收端接收到发送端发过来的串行信号,进行解码串并转换,之后发送到显示器的控制端,在时钟信号的同步下进行显示

信号传输步骤

  1. 8bit视频、音频数据转换为10bit的最小传输、直流均衡数据,编码
  2. 并行10bit编码后的数据转换为串行差分信号,并串转换

模块设计

可以使用前面的vga_coloarbar工程,得到rgb、hsync、vsync信号,clk由PPL锁相环得到,DE使能信号可以用从vga_ctrl模块引出的rgb_valid信号代替;经过了vga_pic和vga_ctrl,就可以得到基于TMDS传输原理的hdmi_ctrl模块输入端口信号,R、G、B信号,hsync、vsync行场同步和DE使能信号
在这里插入图片描述

hdmi_ctrl模块要完成TMDS传输方式,需要有两组逻辑实现,encode对接收过来的数据进行编码、par_to_ser对编码后的10bit数据进行串行发送,可以给里面安排独立的两个模块逻辑
在这里插入图片描述
在这里插入图片描述
HDMI的15、16引脚分别是DDC时钟线(SCL)、DDC数据线(SDA),这两路信号连接的是显示器的ROM,采用的是I2C协议,用于读取显示器ROM中的数据,比如生产厂商、型号、支持的分辨率等。这两个接口暂时不使用,强制置位高电平

hdmi_ctrl模块

hdmi_ctrl是采用TDMS原理实现的,需要将输入数据进行扩展编码、双沿发送差分信号,差分信号也就是一组数据和该组数据的反,比如有一组数据3’b010,那么它本身和3’b101就是一组差分信号,分成两个字模块实现,hdmi_ctrl内部就没有逻辑代码,全都是连线

encode模块代码

8bit并行数据转10bit数据转换
在这里插入图片描述
在这里插入图片描述

module encode (
	input	wire			vga_clk		,
	input	wire			sys_rst_n	,
	input	wire			hsync		,
	input	wire			vsync		,
	input	wire			rgb_valid	,
	input	wire	[ 7: 0]	data_in		,
	
	output	reg		[ 9: 0]	data_out
);

	wire				ctrl_1		;		// 组合逻辑计算D数据1的个数
	wire				ctrl_2		;
	wire				ctrl_3		;
	wire	[ 8: 0]		q_m			;		// 8bit到9bit扩展
	
	reg		[ 3: 0]		data_in_n1	;
	reg		[ 7: 0]		data_in_reg	;		// 输入数据打拍	
	reg		[ 4: 0]		cnt			;		// 后面要计算(cnt(t-1) + (N0{q_m[0:7]} - N1{q_m[0:7]})),8+8就需要4bit,再加上一个符号位
	reg		[ 3: 0]		q_m_n1		;
	reg		[ 3: 0]		q_m_n0		;
	reg		[ 8: 0]		q_m_reg		;
	reg					de_reg1		;
	reg					de_reg2		;
	reg					c0_reg1		;
	reg					c0_reg2		;
	reg					c1_reg1		;
	reg					c1_reg2		;

	always @ (posedge vga_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			data_in_n1	<=	4'd0;
		else
			data_in_n1	<=	data_in[0] + data_in[1] + data_in[2] + data_in[3] + 
							data_in[4] + data_in[5] + data_in[6] + data_in[7];
							
	always @ (posedge vga_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			data_in_reg	<=	8'b0;
		else
			data_in_reg	<=	data_in;
			
	assign	ctrl_1	=	((data_in_n1 > 4'd4) || ((data_in_n1 == 4'd4) && (data_in_reg[0] == 1'b0)))
						? 1'b1 : 1'b0 ;

	
	assign	q_m[0]	=	data_in_reg[0];
	assign	q_m[1]	=	(ctrl_1 == 1'b1) ? (q_m[0] ^~ data_in_reg[1]) : (q_m[0] ^ data_in_reg[1]) ;
	assign	q_m[2]	=	(ctrl_1 == 1'b1) ? (q_m[1] ^~ data_in_reg[2]) : (q_m[1] ^ data_in_reg[2]) ;
	assign	q_m[3]	=	(ctrl_1 == 1'b1) ? (q_m[2] ^~ data_in_reg[3]) : (q_m[2] ^ data_in_reg[3]) ;
	assign	q_m[4]	=	(ctrl_1 == 1'b1) ? (q_m[3] ^~ data_in_reg[4]) : (q_m[3] ^ data_in_reg[4]) ;
	assign	q_m[5]	=	(ctrl_1 == 1'b1) ? (q_m[4] ^~ data_in_reg[5]) : (q_m[4] ^ data_in_reg[5]) ;
	assign	q_m[6]	=	(ctrl_1 == 1'b1) ? (q_m[5] ^~ data_in_reg[6]) : (q_m[5] ^ data_in_reg[6]) ;
	assign	q_m[7]	=	(ctrl_1 == 1'b1) ? (q_m[6] ^~ data_in_reg[7]) : (q_m[6] ^ data_in_reg[7]) ;
	assign	q_m[8]	=	(ctrl_1 == 1'b1) ? 1'b0 : 1'b1;

	
	always @ (posedge vga_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			begin
				q_m_n1	<=	4'd0;
				q_m_n0	<=	4'd0;
			end
		else
			begin
				q_m_n1	<=	q_m[0] + q_m[1] + q_m[2] + q_m[3] + 
							q_m[4] + q_m[5] + q_m[6] + q_m[7];
				q_m_n0	<=	4'd8 - (q_m[0] + q_m[1] + q_m[2] + q_m[3] + 
									q_m[4] + q_m[5] + q_m[6] + q_m[7]);
			end

//	assign	ctrl_2	=	((cnt == 5'd0) || (q_m_n1 == q_m_n0)) ? 1'b1 : 1'b0;
//	assign	ctrl_3	=	(((cnt > 5'd0) && (q_m_n1 > q_m_n0)) 
//						|| ((cnt < 5'd0) && (q_m_n0 > q_m_n1)))
//						? 1'b1 : 1'b0;
	/* cnt是由符号位的,所以不能简单的进行大于小于判断,而是应该判断符号位*/
	assign	ctrl_2	=	((cnt == 5'd0) || (q_m_n1 == q_m_n0)) ? 1'b1 : 1'b0;
	assign	ctrl_3	=	(((cnt[4] == 1'b0) && (q_m_n1 > q_m_n0)) 
						|| ((cnt[4] == 1'b1) && (q_m_n0 > q_m_n1)))
						? 1'b1 : 1'b0;

	// 打拍信号统一赋值
	always @ (posedge vga_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			begin
				q_m_reg	<=	9'b0;
				de_reg1	<=	1'b0;
				de_reg2	<=	1'b0;
				c0_reg1	<=	1'b0;
				c0_reg2	<=	1'b0;
				c1_reg1	<=	1'b0;
				c1_reg2	<=	1'b0;
			end
		else
			begin
				q_m_reg	<=	q_m;
				de_reg1	<=	rgb_valid;
				de_reg2	<=	de_reg1;
				c0_reg1	<=	hsync;
				c0_reg2	<=	c0_reg1;
				c1_reg1	<=	vsync;
				c1_reg2	<=	c1_reg1;
			end
	
	always @ (posedge vga_clk or negedge sys_rst_n)
		if (sys_rst_n == 1'b0)
			begin
				data_out	<=	10'b0;
				cnt			<=	5'd0;
			end
		else
			begin
				if (de_reg2 == 1'b1)
					begin
						if (ctrl_2 == 1'b1)
							begin
								data_out[9]		<=	~q_m_reg[8];
								data_out[8]		<=	q_m_reg[8];
								data_out[ 7: 0]	<=	(q_m_reg[8] ? q_m_reg[ 7: 0] : ~q_m_reg[ 7: 0]);
								cnt	<=	(q_m_reg[8] == 1'b0) ? (cnt + (q_m_n0 - q_m_n1)) : (cnt + (q_m_n1 - q_m_n0));
							end
						else
							begin
								if (ctrl_3 == 1'b1)
									begin
										data_out[9]		<=	1'b1;
										data_out[8]		<=	q_m_reg[8];
										data_out[ 7: 0]	<=	~q_m_reg[ 7: 0];
										cnt	<=	cnt + {q_m_reg[8], 1'b0} + (q_m_n0 - q_m_n1);
									end
								else
									begin
										data_out[9]		<=	1'b0;
										data_out[8]		<=	q_m_reg[8];
										data_out[ 7: 0]	<=	q_m_reg[ 7: 0];
										cnt	<=	cnt - {~q_m_reg[8], 1'b0} + (q_m_n1 - q_m_n0);
									end
							end
					end
				else
					begin
						case ({c1_reg2, c0_reg2})
							2'b00:	data_out	<=	10'b00_1010_1011;
							2'b01:	data_out	<=	10'b11_0101_0100;
							2'b10:	data_out	<=	10'b00_1010_1010;
							2'b11:	data_out	<=	10'b11_0101_0101;
							default:	;
						endcase
						cnt	<=	5'd0;
					end
			end
			
endmodule

par_to_ser模块代码

  1. 并行转串行
  2. 单端信号到差分信号的转换
  3. 单沿到双沿的转换

这个模块分为两部分,

  • 第一部分,将并行的10bit数据在25MHz下经过重组、移位,变成串行的10bit数据,再将这10bit数据分成两组5bit信号data_rise、data_fall,方便区分双沿发送,在125MHz下,以移位寄存的方式从低位逐比特发送

  • 第二部分,调用ddio模块,在125MHz下,上升沿发送送入的data_rise[0]、下降沿发送送入的data_fall[0]
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
Quartus有IP核可以调,我用vivado没找着对应的,简单写一下

module ALTERA_ddio_out (
	input	wire	datain_h	,
	input	wire	datain_l	,
	input	wire	outclock	,
	
	output	wire		data_out
);
	
//	always @ (outclock)
//		if (outclock == 1'b1)
//			data_out	<=	datain_h;
//		else
//			data_out	<=	datain_l;

	assign	data_out	=	(outclock == 1'b1) ? datain_h : datain_l;
	
endmodule
module par_to_ser (
	input	wire			clk_5x	,
	input	wire	[ 9: 0]	data_in	,
	
	output	wire			ser_p	,
	output	wire			ser_n
);

	wire	[ 4: 0]		data_rise	;	// 偶数位上升沿发送
	wire	[ 4: 0]		data_fall	;	// 奇数位下降沿发送
	reg		[ 4: 0]		data_rise_s	= 0;
	reg		[ 4: 0]		data_fall_s	= 0;
	reg		[ 2: 0]		cnt = 0;

	assign	data_rise = {data_in[8], data_in[6], data_in[4], data_in[2], data_in[0]};
	assign	data_fall = {data_in[9], data_in[7], data_in[5], data_in[3], data_in[1]};

	always @ (posedge clk_5x)
		begin
			cnt	<=	(cnt[2] == 1'b1) ? 3'd0 : (cnt + 1'b1);
			data_rise_s	<=	(cnt[2] == 1'b1) ? data_rise : {1'b0, data_rise_s[4:1]};
			data_fall_s	<=	(cnt[2] == 1'b1) ? data_fall : {1'b0, data_fall_s[4:1]};
		end

	// 时钟取反的好处:源clk_5x上升沿数据传送过来,取反后ddio相当于在源clk_5x的下降沿位置进行数据采样,更稳定
	ALTERA_ddio_out	ddio_inst_p (
		.datain_h	(data_rise_s[0]	),
		.datain_l	(data_fall_s[0]	),
		.outclock	(~clk_5x		),
		
		.data_out	(ser_p	)
	);
	// 对data_in进行取反,两根线传输
	ALTERA_ddio_out	ddio_inst_n (
		.datain_h	(~data_rise_s[0]	),
		.datain_l	(~data_fall_s[0]	),
		.outclock	(~clk_5x		),
		
		.data_out	(ser_n	)
	);
endmodule

总结

在这里插入图片描述
HDMI的实现一般都是复用了VGA的相关模块,主要是编写了hdmi_ctrl

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值