1)实验平台:正点原子新起点V2开发板 2)平台采购地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码 手册 视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)正点原子FPGA感兴趣的同学可以加群讨论:99424016 4)关注正点原子微信官方账号,获取最新信息更新 第二十四章HDMI彩条显示实验
HDMI接口在消费类电子行业,如电脑、液晶电视、投影仪等产品中得到了广泛的应用。一些专业的视频设备如摄像机、视频切换器等也都集成了HDMI接口。在本章中,我们将学习如何驱动新起点开发板HDMI接口。 本章包括以下几个部分: 2424.1简介 24.2实验任务 24.3硬件设计 24.4程序设计 24.5下载验证
24.1简介 HDMI英文全称是新一代多媒体接口标准High-Definition Multimedia Interface,即高清多媒体接口。它可以同时传输视频和音频,简化设备的接口和连接,提供更高的数据传输带宽,可以传输无压缩的数字音频和高分辨率的视频信号。HDMI 1.0版于2002年发布,最高数据传输速度为5Gbps;而2017年发布的HDMI 2.标准理论带宽可达48Gbps。 在HDMI界面出现前,被广泛使用VGA接口。VGA的全称是Video Graphics Array,即视频图形阵列,是使用模拟信号进行视频传输的标准。VGA界面采用15针插针结构,传输模拟信号颜色重量、同步等信号,是许多旧显卡、笔记本电脑和投影仪使用的界面。由于VGA接口传输模拟信号,容易干扰VGA字体容易虚拟,信号线长,图像有拖尾现象。VGA接口如下图所示:
图 24.1.1 VGA接口 VGA界面除了容易干扰信号外,体积也很大,所以VGA接口已经逐渐退出舞台,一些显示器不再有VGA接口,在数字设备高度发展的今天,取而代之的是HDMI接口和DP(Display Port)接口等。 HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者的主要区别。DVI界面尺寸明显大于HDMI接口如下图所示:
图 24.1.2 DVI接口(左)和HDMI接口(右)实物图 图 24.1.右侧是生活中最常见的A型HDMI引脚定义如下图所示:
图 24.1.3 HDMI界面引脚定义 DVI和HDMI接口协议用于物理层TMDS音视频数据的标准传输。TMDS(Transition TYPE_Cmized Differential Signaling,差分信号的最小化传输是美国Silicon Image在公司开发的高速数据传输技术中DVI和HDMI差分信号用于在视频接口中传输高速串行数据。TMDS两个引脚用于差分传输技术 24.1.3中的“数据2 传输数据的值(0或1)由两个引脚之间的正负极性和大小决定。 因为这个实验只是用来的HDMI接口显示图像,不需要传输音频,所以我们只需要实现DVI接口的驱动逻辑就足够了。但在此之前,我们需要对它有一个简单的了解TMDS视频传输协议。 图 24.1.4是TMDS发送端与接收端的连接示意图。DVI或HDMI使用视频传输TMDS通过四个串行通道实现连接。对于DVI其中三个通道用于传输视频中每个像素点的红色、绿色和蓝色(RGB 4:4:4格式)。HDMI默认也用三个RGB但也可以选择传输像素点的亮度和色度信息(YCrCb 4:4:4或YCrCb 4:2:2格式)。第四个通道是传输像素时钟的时钟通道。独立的TMDS时钟通道为接收端提供参考频率,确保接收端数据正确恢复。
图 24.1.4 TMDS连接示意图 若每个像素点的颜色深度为24位,即RGB每个颜色分量各占8位,每个通道上的颜色数据将通过8位B/10B的编码器(Encoder)转换成10位像素字符。然后这个10位的字符通过并串转换器(Serializer)将数据转换为串行数据,最后从TMDS发送数据通道。串行数据的速率是实际像素时钟速率的10倍。 编码后的有效像素字符在数据通道上传输视频图像。在每帧图像的行与行之间,以及视频中不同帧之间的时间间隔间的时间间隔(隐藏期)。每个通道上有两个控制信号的输入接口,对应四个不同的控制字符。这些控制字符为视频同步提供了(HSYNC)以及帧同步(VSYNC)也可用于指定传输数据的边界(用于同步)。 对于DVI整个视频的消隐期用于传输控制字符。而HDMI除控制字符外,还可用于传输音频或其它附加数据,如字幕信息。这就是DVI和HDMI协议之间最重要的区别。从图 24.1.这种差异也可以在4中看到,即Auxiliary Data标有HDMI Only”,即它是HDMI独特的接口。 从前面的介绍中我们可以看出,TMDS在编码阶段,编码器将视频源中的像素数据分为两个阶段:编码和串转换。HDMI音频/附加数据和行同步和场同步信号分别编码成10位字符流。然后在并串转换阶段将上述字符流转换为串行数据流,并从三个差异输出通道发送。 DVI编码器在视频有效数据段输出像素数据,在消隐期输出控制数据 24.1.5所示。其中VDE(Video Data Enable)视频数据有效,低电平代表当前视频消隐期。
图 24.1.5 DVI编码输出示意图 图 24.1.给出了三个通道DVI编码器示意图。对像素数据RGB编码器的逻辑与三个颜色通道完全相同。VDE选择输出视频像素数据或控制数据进行各种渠道。HSYNC和VSYNC在蓝色通道中编码信号获得10位字符,然后在视频消隐期传输。绿红通道控制信号C0和C1还需要编码,并在消隐期输出。但是DVI这两个通道的控制信号在规范中预留(未使用),因此将其置于2‘b00。
图 24.1.6 DVI编码器示意图 使用每个通道输入的视频像素数据DVI规范中的TMDS编码算法。每个8-bit数据将转换为460个特定的10个-bit字符中的一个。该编码机制大致实现了传输过程中的直流平衡,即一段时间内传输的高电平(数字1)数大致等于低电平(数字0)数。同时,每个编码后的10-bit状态跳转(从1到0或从0到1)的数量将限制在五次以内。 除视频数据外,每个通道2-bit还应编码控制信号的状态,编码后对应四个不同的10-bit控制字符分别为10'b1101010100,10’b0010101011,10’b0101010100,和10’b1010101011。可见每个控制字符跳转超过7次。视频字符和控制字符状态跳转次数的差异将用于发送和接收设备的同步。 HDMI协议与DVI协议在很多方面都是一样的,包括物理连接(TMDS)、有效的视频编码算法和控制字符的定义。但是相比于DVI,HDMI更多的数据,包括音频数据和附加数据,将在视频消隐期传输。4-bit音频和附加数据将通过TERC4编码机制转换为10-bit TERC4字符,然后在绿色和红色通道上传输。 HDMI在输入附加数据时,还需要输入ADE(Aux/Audio Data Enable)信号,其作用和VDE是相似的:当ADE在高电平时,表示输入端的附加数据或音频数据有效。如果你想了解更多关于相关HDMI详情请参考开发板数据(A盘)/8_FPGA参考资料/HDMI/《HDMI Specification 13a》。为了简单起见,我们在这里HDMI接口当作DVI驱动接口。 编码后,3个通道的10-bit这个字符将并串转换程是通过调用Quartus软件中的ALTDDIO_OUT IP核来实现。 24.2实验任务 本章的实验任务是驱动新起点开发板上的HDMI接口,在显示器上显示彩条图案。HDMI接口输出的分辨率为1280*720,刷新速率为60hz。 24.3硬件设计 新起点FPGA开发板板载的HDMI接口原理图如下图所示:
图 24.3.1 HDMI接口原理图1 图 24.3.1是新起点FPGA开发板HDMI接口原理图的一部分,其中HDMI的三个数据通道HDMI_D[2:0]和一个时钟通道HDMI_CLK直接与TMDS差分引脚相连,此种方案的HDMI接口,其输出的分辨率支持720P,而使用性能更加强劲的FPGA芯片,能支持输出更高分辨率的图像。图中AZ1045是TVS二极管,用于保护线路,防止HDMI差分线上的电压差过高。 HDMI_CEC指的是用户电气控制(Consumer Electronics Control),它用于HDMI连接线上的设备之间进行信息交换。当一个设备的状态发生变化时,CEC可以使用远程控制或自动改变设置来命令连接的关联设备的状态发生相应的变化。例如,如果用户放置一张碟片到蓝光播放器并开始播放,那么高清电视机将会自动打开电源,设置正确的视频输入格式和打开环绕声设备等等,这种关联通信提供了一个更好的客户体验。 HDMI_HPD指的是热拔插检测(Hot Plug Detect),当视频设备与接收设备通过HDMI连接时,接收设备将HPD置为高电平,通知发送设备。当发送设备检测到HPD为低电平时,表明断开连接。
图 24.3.2 HDMI接口原理图2 在图 24.3.2中,HDMI_SCL_LS和HDMI_SDA_LS是HDMI接口的显示数据通道(DDC,Display Data Channel),用于HDMI 发送端和接收端之间交换一些配置信息,通过I2C协议通信。发送端通过 DDC通道,读取接收端保存在 EEPROM中的EDID数据,获取接收端的信息,确认接收端终端显示的设置和功能,决定跟接收端之间以什么格式传输音/视频数据。由于HDMI座子上的DDC通道是5V IO,而FPGA引脚的IO是3.3V,因此通过MOS管(AO3400)进行电平转换。 本次实验只使用了HDMI接口的TMDS数据和TMDS时钟等信号,各端口的管脚分配如下表所示: 表 24.3.1 HDMI彩条显示实验管脚分配 相关的管脚约束如下所示: set_location_assignment PIN_M2 -to sys_clk set_location_assignment PIN_M1 -to sys_rst_n
set_location_assignment PIN_B12 -to tmds_clk_p set_location_assignment PIN_A12 -to tmds_clk_n set_location_assignment PIN_B9 -to tmds_data_p[2] set_location_assignment PIN_A9 -to tmds_data_n[2] set_location_assignment PIN_B10 -to tmds_data_p[1] set_location_assignment PIN_A10 -to tmds_data_n[1] set_location_assignment PIN_B11 -to tmds_data_p[0] set_location_assignment PIN_A11 -to tmds_data_n[0] 24.4程序设计 由于本次实验只需要通过HDMI接口显示图像,因此将其当成DVI接口进行驱动。另外我们只需要实现图像的发送功能。由此得出本次实验的系统框图如下所示:
图 24.4.1 系统框图 本次实验在LCD彩条显示实验的基础上添加一个锁相环和RGB2DVI模块,将RGB888格式的视频图像转换成TMDS数据输出。本章的重点是介绍RGB2DVI模块,其余模块的介绍请参考LCD彩条显示实验。 顶层模块hdmi_colorbar_top的原理图如下所示:
图 24.4.2 顶层模块原理图 由上图可知,FPGA部分包括五个模块,顶层模块(hdmi_colorbar_top)、PLL锁相环模块(pll_clk)、 视频显示模块(video_display)、视频驱动模块(video_driver)和RGB转DVI模块(dvi_transmitter_top)。其中在顶层模块中完成对其余模块的例化。 PLL锁相环模块(pll_clk):该模块通过调用PLL IP核实现,总共输出两个时钟,频率分别为75Mhz和375Mhz,两者的频率相差5倍。其中75Mhz作为其余各模块的操作时钟;而375Mhz作为并行转串行的操作时钟。 视频显示模块(video_display):该模块用于控制HDMI显示器显示的像素值,即彩条。 视频驱动模块(video_driver):该模块和LCD彩条显示实验中的LCD驱动模块类似,不同的是,本次实验输出的图像分辨率为720P,且数据格式为RGB888共24位数据。 RGB转DVI模块(dvi_transmitter_top):该模块实现RGB接口到DVI接口的转换,是本次实验重点介绍的模块。 系统的顶层模块为hdmi_colorbar_top,其代码如下所示:
1 module hdmi_colorbar_top(
2 input sys_clk,
3 input sys_rst_n,
4
5 output tmds_clk_p, // TMDS 时钟通道
6 output tmds_clk_n,
7 output [2:0] tmds_data_p, // TMDS 数据通道
8 output [2:0] tmds_data_n
9 );
10
11 //wire define
12 wire pixel_clk;
13 wire pixel_clk_5x;
14 wire clk_locked;
15
16 wire [10:0] pixel_xpos_w;
17 wire [10:0] pixel_ypos_w;
18 wire [23:0] pixel_data_w;
19
20 wire video_hs;
21 wire video_vs;
22 wire video_de;
23 wire [23:0] video_rgb;
24
25 //*****************************************************
26 //** main code
27 //*****************************************************
28
29 //例化PLL IP核
30 pll_clk u_pll_clk(
31 .areset (~sys_rst_n),
32 .inclk0 (sys_clk),
33 .c0 (pixel_clk), //像素时钟
34 .c1 (pixel_clk_5x), //5倍像素时钟
35 .locked (clk_locked)
36 );
37
38 //例化视频显示驱动模块
39 video_driver u_video_driver(
40 .pixel_clk (pixel_clk),
41 .sys_rst_n (sys_rst_n),
42
43 .video_hs (video_hs),
44 .video_vs (video_vs),
45 .video_de (video_de),
46 .video_rgb (video_rgb),
47
48 .pixel_xpos (pixel_xpos_w),
49 .pixel_ypos (pixel_ypos_w),
50 .pixel_data (pixel_data_w)
51 );
52
53 //例化视频显示模块
54 video_display u_video_display(
55 .pixel_clk (pixel_clk),
56 .sys_rst_n (sys_rst_n),
57
58 .pixel_xpos (pixel_xpos_w),
59 .pixel_ypos (pixel_ypos_w),
60 .pixel_data (pixel_data_w)
61 );
62
63 //例化HDMI驱动模块
64 dvi_transmitter_top u_rgb2dvi_0(
65 .pclk (pixel_clk),
66 .pclk_x5 (pixel_clk_5x),
67 .reset_n (sys_rst_n & clk_locked),
68
69 .video_din (video_rgb),
70 .video_hsync (video_hs),
71 .video_vsync (video_vs),
72 .video_de (video_de),
73
74 .tmds_clk_p (tmds_clk_p),
75 .tmds_clk_n (tmds_clk_n),
76 .tmds_data_p (tmds_data_p),
77 .tmds_data_n (tmds_data_n)
78 );
79
80 endmodule
在代码的30至36行,我们通过调用时钟IP核来产生两个时钟,其中pixel_clk为像素时钟,而pixel_clk_5x为并串转换模块所需要的串行数据时钟,其频率为pixel_clk的5倍。 在顶层模块中,video_display模块(第54行)负责产生RGB888格式的彩条图案,然后在video_driver模块(第39行)的驱动下按照工业标准的VGA显示时序输出视频信号、行场同步信号以及视频有效信号。这些信号作为RGB2DVI模块(第64行)的输入,最终转换成DVI/HDMI接口标准的TMDS串行数据输出到HDMI接口。 video_display模块和video_driver模块与LCD彩条显示实现中的lcd_driver和lcd_display模块几乎完全相同,只不过将数据位宽改为了24位,即RGB888格式,如果大家对这两个模块不熟悉的话,请参考《LCD彩条显示实验》 接下来,我们在整个系统的顶层模块中调用RGB2DVI模块,通过HDMI接口输出彩条图案。 RGB2DVI模块的原理图如下所示:
图 24.4.3 RGB2DVI模块原理图 由上图可知,RGB转DVI模块(dvi_transmitter_top)例化了异步复位模块、DVI编码模块(dvi_encoder)和10位并行转串行模块(serializer_10_to_1),其中DVI编码模块和10位并行转串行模块被例化了多次。 RGB2DVI模块的设计框图如下所示:
图 24.4.4 RGB2DVI模块框图 图 24.4.4中,dvi_encoder模块负责对数据进行编码,serializer_10_to_1模块对编码后的数据进行并串转换,最后转化成TMDS差分信号传输。我们用红/绿/蓝三种颜色分别标识出了输入的视频数据video_din中三个不同的颜色通道。从图中也可以看出,每个颜色通道的处理过程都是一样的,都是先经过dvi_encoder进行编码,然后经过serializer_10_to_1模块进行并串转换,最后转换成TMDS差分信号。 整个系统需要两个输入时钟,一个是视频的像素时钟Pixel Clk,另外一个时钟Pixel Clk x5的频率是像素时钟的五倍。由前面的简介部分我们知道,并串转换过程实现的是10:1的转换率,理论上转换器需要一个10倍像素时钟频率的串行时钟。这里我们只用了一个5倍的时钟频率,这是因为ALTDDIO_OUT IP核可以实现DDR的功能,即它在五倍时钟频率的基础上又实现了双倍数据速率。 TMDS连接的时钟通道我们采用与数据通道相同的并转串逻辑来实现。通过对10位二进制序列10’b11111_00000在10倍像素时钟频率下进行并串转换,就可以得到像素时钟频率下的TMDS参考时钟。 另外需要注意的是,图中左下脚HDMI的音频/附加数据输入在本次实验中并未用到,因此以虚线表示。 dvi_transmitter_top模块代码如下所示:
1 module dvi_transmitter_top(
2 input pclk, // pixel clock
3 input pclk_x5, // pixel clock x5
4 input reset_n, // reset
5
6 input [23:0] video_din, // RGB888 video in
7 input video_hsync, // hsync data
8 input video_vsync, // vsync data
9 input video_de, // data enable
10
11 output tmds_clk_p, // TMDS 时钟通道
12 output tmds_clk_n,
13 output [2:0] tmds_data_p, // TMDS 数据通道
14 output [2:0] tmds_data_n
15 );
16
17 //wire define
18 wire reset;
19
20 //并行数据
21 wire [9:0] red_10bit;
22 wire [9:0] green_10bit;
23 wire [9:0] blue_10bit;
24 wire [9:0] clk_10bit;
25
26 //串行数据
27 wire [2:0] tmds_data_serial;
28 wire tmds_clk_serial;
29
30 //*****************************************************
31 //** main code
32 //*****************************************************
33
34 assign clk_10bit = 10'b1111100000;
35
36 //异步复位,同步释放
37 asyn_rst_syn reset_syn(
38 .reset_n (reset_n),
39 .clk (pclk),
40
41 .syn_reset (reset) //高有效
42 );
43
44 //对三个颜色通道进行编码
45 dvi_encoder encoder_b (
46 .clkin (pclk),
47 .rstin (reset),
48
49 .din (video_din[7:0]),
50 .c0 (video_hsync),
51 .c1 (video_vsync),
52 .de (video_de),
53 .dout (blue_10bit)
54 ) ;
55
56 dvi_encoder encoder_g (
57 .clkin (pclk),
58 .rstin (reset),
59
60 .din (video_din[15:8]),
61 .c0 (1'b0),
62 .c1 (1'b0),
63 .de (video_de),
64 .dout (green_10bit)
65 ) ;
66
67 dvi_encoder encoder_r (
68 .clkin (pclk),
69 .rstin (reset),
70
71 .din (video_din[23:16]),
72 .c0 (1'b0),
73 .c1 (1'b0),
74 .de (video_de),
75 .dout (red_10bit)
76 ) ;
77
78 //对编码后的数据进行并串转换
79 serializer_10_to_1 serializer_b(
80 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
81 .paralell_data (blue_10bit), // 输入并行数据
82
83 .serial_data_p (tmds_data_p[0]), // 输出串行数据P
84 .serial_data_n (tmds_data_n[0]) // 输出串行数据N
85 );
86
87 serializer_10_to_1 serializer_g(
88 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
89 .paralell_data (green_10bit), // 输入并行数据
90
91 .serial_data_p (tmds_data_p[1]), // 输出串行数据P
92 .serial_data_n (tmds_data_n[1]) // 输出串行数据N
93 );
94
95 serializer_10_to_1 serializer_r(
96 .serial_clk_5x (pclk_x5), // 输入串行数据时钟
97 .paralell_data (red_10bit), // 输入并行数据
98
99 .serial_data_p (tmds_data_p[2]), // 输出串行数据P
100 .serial_data_n (tmds_data_n[2]) // 输出串行数据N
101 );
102
103 serializer_10_to_1 serializer_clk(
104 .serial_clk_5x (pclk_x5),
105 .paralell_data (clk_10bit),
106
107 .serial_data_p (tmds_clk_p), // 输出串行时钟P
108 .serial_data_n (tmds_clk_n) // 输出串行时钟N
109 );
110
111 endmodule
在dvi_transmitter_top模块中,不仅例化了编码模块和并转串模块,还例化了asyn_rst_syn模块。编码模块要求复位信号高电平有效,因此,我们在asyn_rst_syn模块中将低电平有效的异步复位信号转换成高有效,同时对其进行异步复位,同步释放处理。 dvi_encoder模块代码如下所示:
1 `timescale 1 ps / 1ps
2
3 module dvi_encoder (
4 input clkin, // pixel clock input
5 input rstin, // async. reset input (active high)
6 input [7:0] din, // data inputs: expect registered
7 input c0, // c0 input
8 input c1, // c1 input
9 input de, // de input
10 output reg [9:0] dout // data outputs
11 );
12
13
14 // Counting number of 1s and 0s for each incoming pixel
15 // component. Pipe line the result.
16 // Register Data Input so it matches the pipe lined adder
17 // output
18
19 reg [3:0] n1d; //number of 1s in din
20 reg [7:0] din_q;
21
22 //计算像素数据中“1”的个数
23 always @ (posedge clkin) begin
24 n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
25
26 din_q <=#1 din;
27 end
28
29 ///
30 // Stage 1: 8 bit -> 9 bit
31 // Refer to DVI 1.0 Specification, page 29, Figure 3-5
32 ///
33 wire decision1;
34
35 assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
36
37 wire [8:0] q_m;
38 assign q_m[0] = din_q[0];
39 assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
40 assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
41 assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
42 assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
43 assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
44 assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
45 assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[<