FPGA_WebLab/public/doc/06/doc.md

27 KiB
Raw Blame History

基础-6-HDMI显示

6.1 章节导读

随着多媒体技术的快速发展高清显示已成为嵌入式系统与FPGA应用中不可或缺的一部分。HDMIHigh-Definition Multimedia Interface作为目前最主流的视频数字传输标准广泛应用于电视、显示器、笔记本、摄像头等各类终端设备中。相比传统的模拟VGA接口HDMI具有传输带宽高、支持音视频同步、无压缩信号传输等优点能更好地满足现代图像处理和显示系统的需求。

在FPGA开发中掌握HDMI显示技术不仅是实现图像/视频输出的基础能力更是后续图像识别、视频监控、图形用户界面GUI等复杂系统设计的前提。因此本实验以HDMI显示为核心内容带领大家从零开始构建一个完整的视频输出链路。通过配置显示参数、生成时序控制信号、输出RGB图像数据等关键步骤最终实现在HDMI接口上稳定输出画面。

在本次实验中我们将学习利用实验板的HDMI接口和MS7210芯片进行HDMI显示实验的设计。

6.2 理论学习

6.2.1 VGA时序

VGA显示是在行同步和帧同步场同步的信号同步下按照从上到下从左到右的顺序扫描到显示屏上。VGA扫描方式见下图所示

无法显示图片时显示的文字
图1.VGA扫描顺序

如上图所示每一帧图像都是从左上角开始逐行扫描形成所以规定最左上角的像素点为第一个像素点坐标是00以这个像素为起点向右x坐标逐渐增大向下y坐标逐渐增大重复若干次后扫描到右下角完成一帧图像的扫描扫描完成后进行图像消隐随后指针跳回左上角重新进行新一帧的扫描。

在扫描的过程中会对每一个像素点进行单独赋值使每个像素点显示对应色彩信息当扫描速度足够快加之人眼的视觉暂留特性我们会看到一幅完整的图片这就是VGA 显示的原理。

VGA显示除了要有像素点的信息还需要有行同步HSync和场同步VSync两个信号辅助显示。行同步信号规定了一行像素的开始与结束场同步信号规定了一帧图像的开始与结束。在VESA DMT 1.12版本的标准文档中给出的VGA时序图如下图所示

无法显示图片时显示的文字
图2.VGA标准时序

行同步时序如下图所示:

无法显示图片时显示的文字
图3.行同步时序

行同步的一个扫周期要经过6个部分分别是Sync同步、 Back Porch后沿、 Left Border左边框、 “Addressable” Video有效图像、 Right Border右边框、 Front Porch前沿这些过程的长度都是以像素为单位的也就是以像素时钟为单位例如Sync的值为96也就意味着Sync阶段要经历96个像素时钟。HSync信号会在Sync同步阶段拉高不同的芯片可能有不同标准以确定新一行的开始与上一行的结束。而完整的一行像素很多但有效的真正能显示在屏幕上的像素只有 “Addressable” Video有效图像部分的像素其他阶段的像素均无效无法显示在屏幕中。

场同步时序如下图所示:

无法显示图片时显示的文字
图4.场同步时序

场同步时序与行同步时序相同也是分为6个部分在Sync同步阶段拉高标志着一帧的结束和新一帧的开始其中像素只有在“Addressable” Video有效图像阶段才有效其他阶段均无效。而场同步信号的基本单位是行比如Sync的值为2也就意味着Sync同步阶段要经历两行。

那么我们将行同步和场同步信号结合起来,遍可以得到一帧图像的样貌,如下图所示:

无法显示图片时显示的文字
图5.一帧图像组成示意图

可以看到在行场同步信号构成了一个二维坐标系,原点在左上方,中间遍形成了一帧图像,而真正能显示在屏幕中的图像只有 “Addressable” Video有效图像部分。

现在我们知道了行同步和场同步都要经历6个部分那么这些部分的长度都是如何规定的呢VGA 行时序对行同步时间、 消隐时间、 行视频有效时间和行前肩时间有特定的规范, 场时序也是如此。 常用VGA 分辨率时序参数如下表所示:

无法显示图片时显示的文字
图6.常用VGA分辨率时序参数

6.2.2 MS7210芯片

MS7210是一款HD发送芯片支持4K@30Hz的视频3D传输格式。可以支持的最高分辨率高达4K@30Hz最高采样率达到300MHz。MS7210支持YUV和RGB 之间的色彩空间转换数字接口支持YUV以及RGB格式输入。MS7210的IIS接口以及S/PDIF 接口支持高清音频的传输其中S/PDIF接口既可以兼容IEC61937标准下的压缩音频传输同时还支持高比特音频HBR的传输在高比特音频HBR模式下音频采样率最高为768KHz。MS7210的IIC 地址可以根据SA引脚进行选择。当 SA引脚上拉到电源电压或者悬空时地址为 OxB2。当 SA 引脚连接到 GND 时地址为0x56。

无法显示图片时显示的文字
图7.MS7210芯片
无法显示图片时显示的文字
图8.MS7210功能框图

MS7210芯片可以通过IIC协议对内部寄存器进行配置有关芯片寄存器配置需要向芯片厂家进行申请。

6.3 实战演练

6.3.1实验目标

6.3.2硬件资源

实验板共有一个HDMI-OUT接口由MS7210驱动一个HDMI-IN接口由MS7200驱动。

无法显示图片时显示的文字
图9.板载HDMI芯片

实验箱配备一个小型HDMI显示器该显示器HDMI接口与HDMI-OUT接口连接图像可以显示在显示屏中通过摄像头可以在网站观察现象

实验箱显示器
图10.实验箱显示器

6.3.3程序设计

在设计程序时我们先对本实验工程有一个整体认知首先来看一下HDMI彩条显示实验的整体框图。

实验箱显示器
图11.HDMI彩条显示整体框图

可见整个实验一共由好多个模块组成,下面是各个模块简介

模块名称 功能描述 备注
hdmi_top 顶层模块
ms7210_ctrl_iic_top ms7210芯片配置和iic顶层模块 参考小眼睛例程
ms7210_ctl ms7210芯片配置和时序控制模块 使用小眼睛例程
iic_dri iic驱动模块 使用小眼睛例程
vga_ctrl vga时序信号生成模块 参考野火例程
vga_pic vga像素数据生成模块 参考野火例程

本次实验主要完成vga_ctrl和vga_pic模块的设计。

对于vga_ctrl模块我们主要完成hsyncvsync信号xy坐标数据有效rgb_valid信号的设计。经过我们前面的学习已经对vga时序有了一定的了解我们可以想象到这几个信号也只是一种计数器而已。

本实验要实现640x480的彩条显示相关参数如下所示

//parameter define
parameter H_SYNC    =   10'd96  ,   //行同步
          H_BACK    =   10'd40  ,   //行时序后沿
          H_LEFT    =   10'd8   ,   //行时序左边框
          H_VALID   =   10'd640 ,   //行有效数据
          H_RIGHT   =   10'd8   ,   //行时序右边框
          H_FRONT   =   10'd8   ,   //行时序前沿
          H_TOTAL   =   10'd800 ;   //行扫描周期
parameter V_SYNC    =   10'd2   ,   //场同步
          V_BACK    =   10'd25  ,   //场时序后沿
          V_TOP     =   10'd8   ,   //场时序上边框
          V_VALID   =   10'd480 ,   //场有效数据
          V_BOTTOM  =   10'd8   ,   //场时序下边框
          V_FRONT   =   10'd2   ,   //场时序前沿
          V_TOTAL   =   10'd525 ;   //场扫描周期

首先设计两个计数器cnt_hcnt_v分别对像素和行进行计数,一个像素时钟过后cnt_h加一,一行过后cnt_v加一,扫描完一帧之后,计数器归零。

而其他的状态信号则可以根据计数器的计数进行设计。hsync信号只要cnt_h < H_SYNC就拉高vsync信号类似。当计数到有效数据部分数据有效信号rgb_valid就可以拉高注意由于时序逻辑有一个时钟周期的反应时间所以xy的坐标变化比rgb_valid提前一个时钟周期。参考代码如下所示

`timescale  1ns/1ns
////////////////////////////////////////////////////////////////////////
// Author        : EmbedFire
// 实验平台: 野火FPGA系列开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com
////////////////////////////////////////////////////////////////////////

module  vga_ctrl
(
    input   wire            vga_clk     ,   //输入工作时钟,频率25MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire    [15:0]  pix_data    ,   //输入像素点色彩信息

    output  wire    [11:0]  pix_x       ,   //输出VGA有效显示区域像素点X轴坐标
    output  wire    [11:0]  pix_y       ,   //输出VGA有效显示区域像素点Y轴坐标
    output  wire            hsync       ,   //输出行同步信号
    output  wire            vsync       ,   //输出场同步信号
    output  wire            rgb_valid   ,
    output  wire    [15:0]  rgb             //输出像素点色彩信息
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter H_SYNC    =   10'd96  ,   //行同步
          H_BACK    =   10'd40  ,   //行时序后沿
          H_LEFT    =   10'd8   ,   //行时序左边框
          H_VALID   =   10'd640 ,   //行有效数据
          H_RIGHT   =   10'd8   ,   //行时序右边框
          H_FRONT   =   10'd8   ,   //行时序前沿
          H_TOTAL   =   10'd800 ;   //行扫描周期
parameter V_SYNC    =   10'd2   ,   //场同步
          V_BACK    =   10'd25  ,   //场时序后沿
          V_TOP     =   10'd8   ,   //场时序上边框
          V_VALID   =   10'd480 ,   //场有效数据
          V_BOTTOM  =   10'd8   ,   //场时序下边框
          V_FRONT   =   10'd2   ,   //场时序前沿
          V_TOTAL   =   10'd525 ;   //场扫描周期

//wire  define
wire            pix_data_req    ;   //像素点色彩信息请求信号

//reg   define
reg     [11:0]  cnt_h           ;   //行同步信号计数器
reg     [11:0]  cnt_v           ;   //场同步信号计数器

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//cnt_h:行同步信号计数器
always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_h   <=  12'd0   ;
    else    if(cnt_h == H_TOTAL - 1'd1)
        cnt_h   <=  12'd0   ;
    else
        cnt_h   <=  cnt_h + 1'd1   ;

//hsync:行同步信号
assign  hsync = (cnt_h  <=  H_SYNC - 1'd1) ? 1'b1 : 1'b0  ;

//cnt_v:场同步信号计数器
always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_v   <=  12'd0 ;
    else    if((cnt_v == V_TOTAL - 1'd1) &&  (cnt_h == H_TOTAL-1'd1))
        cnt_v   <=  12'd0 ;
    else    if(cnt_h == H_TOTAL - 1'd1)
        cnt_v   <=  cnt_v + 1'd1 ;
    else
        cnt_v   <=  cnt_v ;

//vsync:场同步信号
assign  vsync = (cnt_v  <=  V_SYNC - 1'd1) ? 1'b1 : 1'b0  ;

//rgb_valid:VGA有效显示区域
assign  rgb_valid = (((cnt_h >= H_SYNC + H_BACK + H_LEFT)
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID))
                    &&((cnt_v >= V_SYNC + V_BACK + V_TOP)
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)))
                    ? 1'b1 : 1'b0;

//pix_data_req:像素点色彩信息请求信号,超前rgb_valid信号一个时钟周期
assign  pix_data_req = (((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1)
                    && (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1))
                    &&((cnt_v >= V_SYNC + V_BACK + V_TOP)
                    && (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)))
                    ? 1'b1 : 1'b0;

//pix_x,pix_y:VGA有效显示区域像素点坐标
assign  pix_x = (pix_data_req == 1'b1)
                ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 12'hfff;
assign  pix_y = (pix_data_req == 1'b1)
                ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 12'hfff;

//rgb:输出像素点色彩信息
assign  rgb = (rgb_valid == 1'b1) ? pix_data : 16'b0 ;

endmodule

对于vga_pic模块我们可以根据x坐标范围0~639分成十份每一份输出不同的颜色。参考代码如下所示

`timescale  1ns/1ns
////////////////////////////////////////////////////////////////////////
// Author        : EmbedFire
// 实验平台: 野火FPGA系列开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com
////////////////////////////////////////////////////////////////////////

module  vga_pic
(
    input   wire            vga_clk     ,   //输入工作时钟,频率25MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire    [11:0]  pix_x       ,   //输入VGA有效显示区域像素点X轴坐标
    input   wire    [11:0]  pix_y       ,   //输入VGA有效显示区域像素点Y轴坐标

    output  reg     [15:0]  pix_data        //输出像素点色彩信息
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter   H_VALID =   12'd640 ,   //行有效数据
            V_VALID =   12'd480 ;   //场有效数据

parameter   RED     =   16'hF800,   //红色
            ORANGE  =   16'hFC00,   //橙色
            YELLOW  =   16'hFFE0,   //黄色
            GREEN   =   16'h07E0,   //绿色
            CYAN    =   16'h07FF,   //青色
            BLUE    =   16'h001F,   //蓝色
            PURPPLE =   16'hF81F,   //紫色
            BLACK   =   16'h0000,   //黑色
            WHITE   =   16'hFFFF,   //白色
            GRAY    =   16'hD69A;   //灰色

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//pix_data:输出像素点色彩信息,根据当前像素点坐标指定当前像素点颜色数据
always@(posedge vga_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        pix_data    <= 16'd0;
    else    if((pix_x >= 0) && (pix_x < (H_VALID/10)*1))
        pix_data    <=  RED;
    else    if((pix_x >= (H_VALID/10)*1) && (pix_x < (H_VALID/10)*2))
        pix_data    <=  ORANGE;
    else    if((pix_x >= (H_VALID/10)*2) && (pix_x < (H_VALID/10)*3))
        pix_data    <=  YELLOW;
    else    if((pix_x >= (H_VALID/10)*3) && (pix_x < (H_VALID/10)*4))
        pix_data    <=  GREEN;
    else    if((pix_x >= (H_VALID/10)*4) && (pix_x < (H_VALID/10)*5))
        pix_data    <=  CYAN;
    else    if((pix_x >= (H_VALID/10)*5) && (pix_x < (H_VALID/10)*6))
        pix_data    <=  BLUE;
    else    if((pix_x >= (H_VALID/10)*6) && (pix_x < (H_VALID/10)*7))
        pix_data    <=  PURPPLE;
    else    if((pix_x >= (H_VALID/10)*7) && (pix_x < (H_VALID/10)*8))
        pix_data    <=  BLACK;
    else    if((pix_x >= (H_VALID/10)*8) && (pix_x < (H_VALID/10)*9))
        pix_data    <=  WHITE;
    else    if((pix_x >= (H_VALID/10)*9) && (pix_x < H_VALID))
        pix_data    <=  GRAY;
    else
        pix_data    <=  BLACK;

endmodule

在顶层模块我们首先要利用PLL ip核生成iic的驱动时钟进行初始化由于ms7210芯片的需要我们通过计数设置一个延迟复位信号由于我们的彩条颜色是按照RGB565格式生成的所以需要向RGB888进行转换只需要填0补位即可同时由于板载时钟是27M与25.175M相差不大,所以直接使用板载时钟作为像素时钟输出。然后我们将输出的行场同步信号,像素时钟,像素数据,像素数据有效信号等与模块相连接即可完成设计。顶层模块参考代码如下:

`timescale 1ns / 1ns
module hdmi_top(
    input wire          sys_clk       ,// input system clock 50MHz   
    input               rstn_in       , 
    output              rstn_out      ,
    output              hd_scl        ,
    inout               hd_sda        ,
    output              led_int       ,

//hdmi_out 
    output              pixclk_out    ,//pixclk                           
    output  wire        vs_out        , 
    output  wire        hs_out        , 
    output  wire        de_out        ,
    output  wire [7:0]  r_out         , 
    output  wire [7:0]  g_out         , 
    output  wire [7:0]  b_out         

);
wire                        cfg_clk    ;
wire                        locked     ;
wire                        rstn       ;
wire                        init_over  ;
reg  [15:0]                 rstn_1ms   ;
//**********************************************//
//*****************MS7210初始化******************//
//**********************************************//
//**************仿真时不编译此部分***************//
`ifndef SIM
//初始化成功标志
assign    led_int    =     init_over;
//生成10M IIC时钟
PLL u_pll (
  .clkout0(cfg_clk),    // output
  .lock(locked),          // output
  .clkin1(sys_clk)       // input
);
//ms7210初始化模块
ms7210_ctrl_iic_top ms7210_ctrl_iic_top_inst(
    .clk         (  cfg_clk    ), //input       clk,
    .rst_n       (  rstn_out   ), //input       rstn,
                        
    .init_over   (  init_over  ), //output      init_over,
    .iic_scl     (  hd_scl    ), //output      iic_scl,
    .iic_sda     (  hd_sda    )  //inout       iic_sda
);
//延迟复位
always @(posedge cfg_clk)
begin
	if(!locked)
	    rstn_1ms <= 16'd0;
	else
	begin
		if(rstn_1ms == 16'h2710)
		    rstn_1ms <= rstn_1ms;
		else
		    rstn_1ms <= rstn_1ms + 1'b1;
	end
end
assign rstn_out = (rstn_1ms == 16'h2710) && rstn_in;
//**********************************************//
`else
assign led_int  =     1;
assign rstn_out = rstn_in;

`endif
//**********************************************//
//**********************************************//
//**********************************************//
//**********************************************//
wire [15:0] rgb565;
wire [15:0] pix_data ;
wire [11:0] pix_x;
wire [11:0] pix_y;
//vga行场同步控制模块
vga_ctrl  vga_ctrl_inst (
    .vga_clk        (sys_clk        ),
    .sys_rst_n      (rstn_out       ),
    .pix_data       (pix_data       ),
    .pix_x          (pix_x          ),
    .pix_y          (pix_y          ),
    .hsync          (hs_out         ),
    .vsync          (vs_out         ),
    .rgb_valid      (de_out         ),
    .rgb            (rgb565         )
  );
//彩条数据生成模块
vga_pic  vga_pic_inst (
    .vga_clk        (sys_clk        ),
    .sys_rst_n      (rstn_out       ),
    .pix_x          (pix_x          ),
    .pix_y          (pix_y          ),
    .pix_data_out   (pix_data       )
  );
//RGB565转RGB888
    assign pixclk_out   =  sys_clk    ;//直接使用27M时钟与25.175相差不大
    assign r_out = {rgb565[15:11],3'b0};
    assign g_out = {rgb565[10: 5],2'b0};
    assign b_out = {rgb565[ 4: 0],3'b0};
endmodule

6.3.4仿真验证

由于仿真不需要对MS7210芯片进行初始化所以我们在top文件中加入条件编译指令并且在仿真文件中定义SIM宏那么就可以在仿真中不编译ms7210初始化相关代码只对vga时序进行仿真。我们只需要提供时钟和复位即可对模块进行仿真。仿真文件如下所示

`timescale 1ns / 1ns
`define SIM
module hdmi_top_tb;

  // Parameters

  //Ports
  reg  sys_clk;
  reg rstn_in;
  wire rstn_out;
  wire hd_scl;
  wire hd_sda;
  wire led_int;
  wire pixclk_out;
  wire  vs_out;
  wire  hs_out;
  wire  de_out;
  wire [7:0] r_out;
  wire [7:0] g_out;
  wire [7:0] b_out;

  initial begin
    sys_clk = 0;
    rstn_in = 0;
    #100
    rstn_in = 1;
  end
  always #(500/27) sys_clk = ~sys_clk;
  hdmi_top  hdmi_top_inst (
    .sys_clk(sys_clk),
    .rstn_in(rstn_in),
    .rstn_out(rstn_out),
    .hd_scl(hd_scl),
    .hd_sda(hd_sda),
    .led_int(led_int),
    .pixclk_out(pixclk_out),
    .vs_out(vs_out),
    .hs_out(hs_out),
    .de_out(de_out),
    .r_out(r_out),
    .g_out(g_out),
    .b_out(b_out)
  );

endmodule

直接点击sim文件夹下hebav文件夹中的do.bat文件即可利用ModuleSim对模块进行仿真仿真波形如下

无法显示图片时显示的文字
图10.仿真波形(一)

从上图我们可以发现vsync信号拉高了两个行同步信号的长度与设计相符

无法显示图片时显示的文字
图11.仿真波形(二)
无法显示图片时显示的文字
图12.仿真波形(三)

从图11和12中我们可以看到当cnt_h信号计数结束后会恢复0cnt_v会加一hsync信号会拉高96个像素时钟0~95cnt_h和hsync与设计相符。

无法显示图片时显示的文字
图13.仿真波形(四)

如图13所示当cnt_h计数到H_SYNC + H_BACK + H_LEFT也就是144时rgb_valid拉高xy轴坐标比rgb_valid提前一个时钟周期以便pix_data准备好数据符合设计。

无法显示图片时显示的文字
图14.仿真波形(五)

从每一行看每一行被分成了10个部分每部分像素数据分别对应不同颜色符合设计要求。可以进行下一步上板验证。

6.3.5上板验证

仿真已经通过,可以进行上板验证,上板前要先进行管脚约束。端口与对应管脚如下表所示:

端口名称 信号类型 对应管脚 功能
sysclk Input D18 27M时钟
rstn_in Input C22 外部输入复位
rstn_out Output G25 输出ms7210复位
hd_scl Output K22 iic SCL信号
hd_sda Output K23 iic SDA信号
led_int Output A20 配置完成信号
pixclk_out Output G25 像素时钟输出
vs_out Output R21 Vsync输出
hs_out Output R20 Hsync输出
de_out Output N19 RGB_valid输出
r_out[0] Output N21 RGB888输出
r_out[1] Output L23 RGB888输出
r_out[2] Output L22 RGB888输出
r_out[3] Output L25 RGB888输出
r_out[4] Output L24 RGB888输出
r_out[5] Output K26 RGB888输出
r_out[6] Output K25 RGB888输出
r_out[7] Output P16 RGB888输出
g_out[0] Output T25 RGB888输出
g_out[1] Output P25 RGB888输出
g_out[2] Output R25 RGB888输出
g_out[3] Output P24 RGB888输出
g_out[4] Output P23 RGB888输出
g_out[5] Output N24 RGB888输出
g_out[6] Output N23 RGB888输出
g_out[7] Output N22 RGB888输出
b_out[0] Output P19 RGB888输出
b_out[1] Output P21 RGB888输出
b_out[2] Output P20 RGB888输出
b_out[3] Output M22 RGB888输出
b_out[4] Output M21 RGB888输出
b_out[5] Output N18 RGB888输出
b_out[6] Output R22 RGB888输出
b_out[7] Output T22 RGB888输出

管脚分配可以直接编写.fdc文件也可以使用PDS内置的工具进行分配。完成管脚分配之后就可以生成sbit文件将文件提交到网站后点击烧录即可将sbit下载到实验板中在摄像头页面即可观察到显示屏中显示出彩条。

6.4 章末总结

本次实验主要学习VGA时序的相关知识并使用HD硬核进行HDMI显示感兴趣的同学可以尝试使用HDMI显示其他图像。