2.HDMI
本节有点难不过听懂了的话会感到非常有趣
先说流程
1.8位的视频信号经过8转10 TMDS(视频编码)使其相比传统的传输方式有更好的电磁兼容性能,以减小传输信号过程是产生的上冲和下冲,DC平衡时信号对电磁干扰减小。
2.再将低速的信号并行信号转化为高速的串行信号
3.最后再将单端信号转化为差分信号
主要设计的模块有
Serializer:并转串(通过Xilinx的OSERDESE2原语实现)
HDMI简介
1.常见的视频接口DP、HDMI、DVI、VGA,今天主要讨论HDMI,VAG作为古老视频接口采用的是模拟信号传输,极易受到干扰而造成视频失真,其他虽然略有差异但都采用数字差分信号,从而保证视频高清,HDMI(高清多媒体接口)能传输视频和音频信号,向下兼容DVI,但DVI只能传输视频信号,DP主要特点是完全免费,而且区别于HDMI一个bit一个bit的传输,DP传输的是数据包包含更多数据,带宽也更高。
2.HDMI引脚定义
无论是DVI还是HDMI在物理层面都采用TMDS差分传输技术,Xilinx在Spartan-3之后加入了对TMDS支持使我们能在FPGA内部实现DVI和HDMI接口。
本次实验只是传输视频并不传输音频,因此我们只需要实现DVI接口的驱动逻辑即可。下面我们来简单了解一下什么是TMDS视频传输协议。
HDMI视频传输通过连接的四根串行通道实现。HDMI默认使用三个RGB通道,第四个通道是时钟通道用于传输像素时钟。独立的时钟通道保证数据在接收端能正常恢复。
如果每个像素点颜色深度为24位,每个颜色分量占8位,那么每个通道颜色分量数据将通过编码器转化为10位像素字符,然后这10位像素字符通过并转串转化成串行数据发送出去,这个过程中数据速率是实际像素速率的10倍。
数据传输过程中,数据通道上传的是编码后的有效像素字符。而在每一帧的行与行之间传输的是控制字符每个通道的控制字符有两位共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HSYNC)以及帧同步(VSYNC)也可以用来指定数据传输的边界。
HDMI除了传输控制字符外还可以传输视频源中的音频信息或附加信息如字幕等。这就是DVI与HDMI之间的主要区别。
从前面可以看出TMDS连接在逻辑上分为两个阶段:编码和并串转换,在编码阶段编码器将视频中的像素数据和HDMI的音频/附加数据,以及行场同步的信号分别编码成十位字符流发送出去。
DVI编码器在视频有效数据段输出像素数据,在消隐阶段输出控制数据,VDE高电平时表示视频有效,低电平时表示数据消隐。
每个输入通道都要使用TMDS编码算法进行编码,每个8bit数据都要被转化成460个特定的10-bit字符中的一个。这个编码机制大致上实现了传输过程中的直流平衡,即每一段时间内高电平数大致等于低电平数。这是由于HDMI的传输线上必须串联一个电容来实现高通滤波,如果有的数据恰好都为高或者都为低,就会导致传输信号被认为是低频信号而失真。
除了视频信号外每个通道的2-bit控制位也要进行编码四种情况分别为10’b1101010100,10’b0010101011,10’b0101010100,和 10’b1010101011。可见每个控制字符都要七次以上的电平翻转。视频字符和控制字符状态跳转次数的不同将会被用于发送和接收设备的同步。
最后对三通道的10-bit字符进行等效并转串,这使用的是FPGA内专用的硬件资源实现的。
代码编写
1.顶层模块
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: hdmi_colorbar_top
// Last modified Date: 2019/7/1 9:30:00
// Last Version: V1.1
// Descriptions: HDMI彩条显示实验顶层模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/7/1 9:30:00
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module hdmi_colorbar_top(
input sys_clk,
input sys_rst_n,
output tmds_clk_p, // TMDS 时钟通道
output tmds_clk_n,
output [2:0] tmds_data_p, // TMDS 数据通道
output [2:0] tmds_data_n
);
//wire define
wire pixel_clk;
wire pixel_clk_5x;
wire clk_locked;
wire [10:0] pixel_xpos_w;
wire [10:0] pixel_ypos_w;
wire [23:0] pixel_data_w;
wire video_hs;
wire video_vs;
wire video_de;
wire [23:0] video_rgb;
//*****************************************************
//** main code
//*****************************************************
//例化MMCM/PLL IP核
clk_wiz_0 clk_wiz_0(
.clk_in1 (sys_clk),
.clk_out1 (pixel_clk), //像素时钟
.clk_out2 (pixel_clk_5x), //5倍像素时钟
.reset (~sys_rst_n),
.locked (clk_locked)
);
//例化视频显示驱动模块
video_driver u_video_driver(
.pixel_clk ( pixel_clk ),
.sys_rst_n ( sys_rst_n ),
.video_hs ( video_hs ),
.video_vs ( video_vs ),
.video_de ( video_de ),
.video_rgb ( video_rgb ),
.data_req (),
.pixel_xpos ( pixel_xpos_w ),
.pixel_ypos ( pixel_ypos_w ),
.pixel_data ( pixel_data_w )
);
//例化视频显示模块
video_display u_video_display(
.pixel_clk (pixel_clk),
.sys_rst_n (sys_rst_n),
.pixel_xpos (pixel_xpos_w),
.pixel_ypos (pixel_ypos_w),
.pixel_data (pixel_data_w)
);
//例化HDMI驱动模块
dvi_transmitter_top u_rgb2dvi_0(
.pclk (pixel_clk),
.pclk_x5 (pixel_clk_5x),
.reset_n (sys_rst_n & clk_locked),
.video_din (video_rgb),
.video_hsync (video_hs),
.video_vsync (video_vs),
.video_de (video_de),
.tmds_clk_p (tmds_clk_p),
.tmds_clk_n (tmds_clk_n),
.tmds_data_p (tmds_data_p),
.tmds_data_n (tmds_data_n),
.tmds_oen () //预留的端口,本次实验未用到
);
endmodule
2.异步复位
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: asyn_rst_syn
// Last modified Date: 2019/7/1 9:30:00
// Last Version: V1.1
// Descriptions: 异步复位,同步释放,并转换成高电平有效
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/7/1 9:30:00
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module asyn_rst_syn(
input clk, //目的时钟域
input reset_n, //异步复位,低有效
output syn_reset //高有效
);
//reg define
reg reset_1;
reg reset_2;
//*****************************************************
//** main code
//*****************************************************
assign syn_reset = reset_2;
//对异步复位信号进行同步释放,并转换成高有效
always @ (posedge clk or negedge reset_n) begin
if(!reset_n) begin
reset_1 <= 1'b1;
reset_2 <= 1'b1;
end
else begin
reset_1 <= 1'b0;
reset_2 <= reset_1;
end
end
endmodule
3.编码模块代码
//
//
// Xilinx, Inc. 2008 www.xilinx.com
//
//
//
// File name : dvi_encoder.v
//
// Description : TMDS encoder
//
// Date - revision : Jan. 2008 - v 1.0
//
// Author : Bob Feng
//
// Copyright 2006 Xilinx, Inc.
// All rights reserved
//
//
`timescale 1 ps / 1ps
module dvi_encoder (
input clkin, // pixel clock input
input rstin, // async. reset input (active high)
input [7:0] din, // data inputs: expect registered
input c0, // c0 input
input c1, // c1 input
input de, // de input
output reg [9:0] dout // data outputs
);
// Counting number of 1s and 0s for each incoming pixel
// component. Pipe line the result.
// Register Data Input so it matches the pipe lined adder
// output
reg [3:0] n1d; //number of 1s in din
reg [7:0] din_q;
//计算像素数据中“1”的个数
always @ (posedge clkin) begin
n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];
din_q <=#1 din;
end
///
// Stage 1: 8 bit -> 9 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
///
wire decision1;
assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));
wire [8:0] q_m;
assign q_m[0] = din_q[0];
assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);
assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);
assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);
assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);
assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);
assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);
assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);
assign q_m[8] = (decision1) ? 1'b0 : 1'b1;
/
// Stage 2: 9 bit -> 10 bit
// Refer to DVI 1.0 Specification, page 29, Figure 3-5
/
reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m
always @ (posedge clkin) begin
n1q_m <=#1 q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7];
n0q_m <=#1 4'h8 - (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
parameter CTRLTOKEN0 = 10'b1101010100;
parameter CTRLTOKEN1 = 10'b0010101011;
parameter CTRLTOKEN2 = 10'b0101010100;
parameter CTRLTOKEN3 = 10'b1010101011;
reg [4:0] cnt; //disparity counter, MSB is the sign bit
wire decision2, decision3;
assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);
/
// [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]
/
assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));
// pipe line alignment
reg de_q, de_reg;
reg c0_q, c1_q;
reg c0_reg, c1_reg;
reg [8:0] q_m_reg;
always @ (posedge clkin) begin
de_q <=#1 de;
de_reg <=#1 de_q;
c0_q <=#1 c0;
c0_reg <=#1 c0_q;
c1_q <=#1 c1;
c1_reg <=#1 c1_q;
q_m_reg <=#1 q_m;
end
///
// 10-bit out
// disparity counter
///
always @ (posedge clkin or posedge rstin) begin
if(rstin) begin
dout <= 10'h0;
cnt <= 5'h0;
end else begin
if (de_reg) begin
if(decision2) begin
dout[9] <=#1 ~q_m_reg[8];
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);
end else begin
if(decision3) begin
dout[9] <=#1 1'b1;
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 ~q_m_reg[7:0];
cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);
end else begin
dout[9] <=#1 1'b0;
dout[8] <=#1 q_m_reg[8];
dout[7:0] <=#1 q_m_reg[7:0];
cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);
end
end
end else begin
case ({c1_reg, c0_reg})
2'b00: dout <=#1 CTRLTOKEN0;
2'b01: dout <=#1 CTRLTOKEN1;
2'b10: dout <=#1 CTRLTOKEN2;
default: dout <=#1 CTRLTOKEN3;
endcase
cnt <=#1 5'h0;
end
end
end
endmodule
4.发送顶层模块
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: dvi_transmitter_top
// Last modified Date: 2019/7/1 9:30:00
// Last Version: V1.1
// Descriptions: DVI发送端顶层模块
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/7/1 9:30:00
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module dvi_transmitter_top(
input pclk, // pixel clock
input pclk_x5, // pixel clock x5
input reset_n, // reset
input [23:0] video_din, // RGB888 video in
input video_hsync, // hsync data
input video_vsync, // vsync data
input video_de, // data enable
output tmds_clk_p, // TMDS 时钟通道
output tmds_clk_n,
output [2:0] tmds_data_p, // TMDS 数据通道
output [2:0] tmds_data_n,
output tmds_oen // TMDS 输出使能
);
//wire define
wire reset;
//并行数据
wire [9:0] red_10bit;
wire [9:0] green_10bit;
wire [9:0] blue_10bit;
wire [9:0] clk_10bit;
//串行数据
wire [2:0] tmds_data_serial;
wire tmds_clk_serial;
//*****************************************************
//** main code
//*****************************************************
assign tmds_oen = 1'b1;
assign clk_10bit = 10'b1111100000;
//异步复位,同步释放
asyn_rst_syn reset_syn(
.reset_n (reset_n),
.clk (pclk),
.syn_reset (reset) //高有效
);
//对三个颜色通道进行编码
dvi_encoder encoder_b (
.clkin (pclk),
.rstin (reset),
.din (video_din[7:0]),
.c0 (video_hsync),
.c1 (video_vsync),
.de (video_de),
.dout (blue_10bit)
) ;
dvi_encoder encoder_g (
.clkin (pclk),
.rstin (reset),
.din (video_din[15:8]),
.c0 (1'b0),
.c1 (1'b0),
.de (video_de),
.dout (green_10bit)
) ;
dvi_encoder encoder_r (
.clkin (pclk),
.rstin (reset),
.din (video_din[23:16]),
.c0 (1'b0),
.c1 (1'b0),
.de (video_de),
.dout (red_10bit)
) ;
//对编码后的数据进行并串转换
serializer_10_to_1 serializer_b(
.reset (reset), // 复位,高有效
.paralell_clk (pclk), // 输入并行数据时钟
.serial_clk_5x (pclk_x5), // 输入串行数据时钟
.paralell_data (blue_10bit), // 输入并行数据
.serial_data_out (tmds_data_serial[0]) // 输出串行数据
);
serializer_10_to_1 serializer_g(
.reset (reset),
.paralell_clk (pclk),
.serial_clk_5x (pclk_x5),
.paralell_data (green_10bit),
.serial_data_out (tmds_data_serial[1])
);
serializer_10_to_1 serializer_r(
.reset (reset),
.paralell_clk (pclk),
.serial_clk_5x (pclk_x5),
.paralell_data (red_10bit),
.serial_data_out (tmds_data_serial[2])
);
serializer_10_to_1 serializer_clk(
.reset (reset),
.paralell_clk (pclk),
.serial_clk_5x (pclk_x5),
.paralell_data (clk_10bit),
.serial_data_out (tmds_clk_serial)
);
//转换差分信号
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS0 (
.I (tmds_data_serial[0]),
.O (tmds_data_p[0]),
.OB (tmds_data_n[0])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS1 (
.I (tmds_data_serial[1]),
.O (tmds_data_p[1]),
.OB (tmds_data_n[1])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS2 (
.I (tmds_data_serial[2]),
.O (tmds_data_p[2]),
.OB (tmds_data_n[2])
);
OBUFDS #(
.IOSTANDARD ("TMDS_33") // I/O电平标准为TMDS
) TMDS3 (
.I (tmds_clk_serial),
.O (tmds_clk_p),
.OB (tmds_clk_n)
);
endmodule
5.10:1并转串
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: serializer_10_to_1
// Last modified Date: 2019/7/1 9:30:00
// Last Version: V1.1
// Descriptions: 用于实现10:1并串转换
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/7/1 9:30:00
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
`timescale 1ns / 1ps
module serializer_10_to_1(
input reset, // 复位,高有效
input paralell_clk, // 输入并行数据时钟
input serial_clk_5x, // 输入串行数据时钟
input [9:0] paralell_data, // 输入并行数据
output serial_data_out // 输出串行数据
);
//wire define
wire cascade1; //用于两个OSERDESE2级联的信号
wire cascade2;
//*****************************************************
//** main code
//*****************************************************
//例化OSERDESE2原语,实现并串转换,Master模式
OSERDESE2 #(
.DATA_RATE_OQ ("DDR"), // 设置双倍数据速率
.DATA_RATE_TQ ("SDR"), // DDR, BUF, SDR
.DATA_WIDTH (10), // 输入的并行数据宽度为10bit
.SERDES_MODE ("MASTER"), // 设置为Master,用于10bit宽度扩展
.TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH (1) // 3-state converter width (1,4)
)
OSERDESE2_Master (
.CLK (serial_clk_5x), // 串行数据时钟,5倍时钟频率
.CLKDIV (paralell_clk), // 并行数据时钟
.RST (reset), // 1-bit input: Reset
.OCE (1'b1), // 1-bit input: Output data clock enable
.OQ (serial_data_out), // 串行输出数据
.D1 (paralell_data[0]), // D1 - D8: 并行数据输入
.D2 (paralell_data[1]),
.D3 (paralell_data[2]),
.D4 (paralell_data[3]),
.D5 (paralell_data[4]),
.D6 (paralell_data[5]),
.D7 (paralell_data[6]),
.D8 (paralell_data[7]),
.SHIFTIN1 (cascade1), // SHIFTIN1 用于位宽扩展
.SHIFTIN2 (cascade2), // SHIFTIN2
.SHIFTOUT1 (), // SHIFTOUT1: 用于位宽扩展
.SHIFTOUT2 (), // SHIFTOUT2
.OFB (), // 以下是未使用信号
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TBYTEIN (1'b0),
.TCE (1'b0),
.TBYTEOUT (),
.TFB (),
.TQ ()
);
//例化OSERDESE2原语,实现并串转换,Slave模式
OSERDESE2 #(
.DATA_RATE_OQ ("DDR"), // 设置双倍数据速率
.DATA_RATE_TQ ("SDR"), // DDR, BUF, SDR
.DATA_WIDTH (10), // 输入的并行数据宽度为10bit
.SERDES_MODE ("SLAVE"), // 设置为Slave,用于10bit宽度扩展
.TBYTE_CTL ("FALSE"), // Enable tristate byte operation (FALSE, TRUE)
.TBYTE_SRC ("FALSE"), // Tristate byte source (FALSE, TRUE)
.TRISTATE_WIDTH (1) // 3-state converter width (1,4)
)
OSERDESE2_Slave (
.CLK (serial_clk_5x), // 串行数据时钟,5倍时钟频率
.CLKDIV (paralell_clk), // 并行数据时钟
.RST (reset), // 1-bit input: Reset
.OCE (1'b1), // 1-bit input: Output data clock enable
.OQ (), // 串行输出数据
.D1 (1'b0), // D1 - D8: 并行数据输入
.D2 (1'b0),
.D3 (paralell_data[8]),
.D4 (paralell_data[9]),
.D5 (1'b0),
.D6 (1'b0),
.D7 (1'b0),
.D8 (1'b0),
.SHIFTIN1 (), // SHIFTIN1 用于位宽扩展
.SHIFTIN2 (), // SHIFTIN2
.SHIFTOUT1 (cascade1), // SHIFTOUT1: 用于位宽扩展
.SHIFTOUT2 (cascade2), // SHIFTOUT2
.OFB (), // 以下是未使用信号
.T1 (1'b0),
.T2 (1'b0),
.T3 (1'b0),
.T4 (1'b0),
.TBYTEIN (1'b0),
.TCE (1'b0),
.TBYTEOUT (),
.TFB (),
.TQ ()
);
endmodule
6.视频驱动
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: video_driver
// Last modified Date: 2020/05/28 20:28:08
// Last Version: V1.0
// Descriptions: 视频显示驱动模块
//
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2020/05/28 20:28:08
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module video_driver(
input pixel_clk ,
input sys_rst_n ,
//RGB接口
output video_hs , //行同步信号
output video_vs , //场同步信号
output video_de , //数据使能
output [23:0] video_rgb , //RGB888颜色数据
output reg data_req ,
input [23:0] pixel_data , //像素点数据
output reg [10:0] pixel_xpos , //像素点横坐标
output reg [10:0] pixel_ypos //像素点纵坐标
);
//parameter define
//1280*720 分辨率时序参数
parameter H_SYNC = 11'd40; //行同步
parameter H_BACK = 11'd220; //行显示后沿
parameter H_DISP = 11'd1280; //行有效数据
parameter H_FRONT = 11'd110; //行显示前沿
parameter H_TOTAL = 11'd1650; //行扫描周期
parameter V_SYNC = 11'd5; //场同步
parameter V_BACK = 11'd20; //场显示后沿
parameter V_DISP = 11'd720; //场有效数据
parameter V_FRONT = 11'd5; //场显示前沿
parameter V_TOTAL = 11'd750; //场扫描周期
//1920*1080分辨率时序参数
//parameter H_SYNC = 12'd44; //行同步
//parameter H_BACK = 12'd148; //行显示后沿
//parameter H_DISP = 12'd1920; //行有效数据
//parameter H_FRONT = 12'd88; //行显示前沿
//parameter H_TOTAL = 12'd2200; //行扫描周期
//
//parameter V_SYNC = 12'd5; //场同步
//parameter V_BACK = 12'd36; //场显示后沿
//parameter V_DISP = 12'd1080; //场有效数据
//parameter V_FRONT = 12'd4; //场显示前沿
//parameter V_TOTAL = 12'd1125; //场扫描周期
//reg define
reg [11:0] cnt_h;
reg [11:0] cnt_v;
reg video_en;
//*****************************************************
//** main code
//*****************************************************
assign video_de = video_en;
assign video_hs = ( cnt_h < H_SYNC ) ? 1'b0 : 1'b1; //行同步信号赋值
assign video_vs = ( cnt_v < V_SYNC ) ? 1'b0 : 1'b1; //场同步信号赋值
//使能RGB数据输出
always @(posedge pixel_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
video_en <= 1'b0;
else
video_en <= data_req;
end
//RGB888数据输出
assign video_rgb = video_de ? pixel_data : 24'd0;
//请求像素点颜色数据输入
always @(posedge pixel_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
data_req <= 1'b0;
else if(((cnt_h >= H_SYNC + H_BACK - 2'd2)
&& (cnt_h < H_SYNC + H_BACK + H_DISP - 2'd2))
&& ((cnt_v >= V_SYNC + V_BACK)
&& (cnt_v < V_SYNC + V_BACK+V_DISP)))
data_req <= 1'b1;
else
data_req <= 1'b0;
end
//像素点x坐标
always@ (posedge pixel_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
pixel_xpos <= 11'd0;
else if(data_req)
pixel_xpos <= cnt_h + 2'd2 - H_SYNC - H_BACK ;
else
pixel_xpos <= 11'd0;
end
//像素点y坐标
always@ (posedge pixel_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
pixel_ypos <= 11'd0;
else if((cnt_v >= (V_SYNC + V_BACK)) && (cnt_v < (V_SYNC + V_BACK + V_DISP)))
pixel_ypos <= cnt_v + 1'b1 - (V_SYNC + V_BACK) ;
else
pixel_ypos <= 11'd0;
end
//行计数器对像素时钟计数
always @(posedge pixel_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_h <= 11'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 11'd0;
end
end
//场计数器对行计数
always @(posedge pixel_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_v <= 11'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 11'd0;
end
end
endmodule
7.视频显示
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: video_display
// Last modified Date: 2019/7/1 9:30:00
// Last Version: V1.1
// Descriptions: 视频显示模块,显示彩条
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2019/7/1 9:30:00
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module video_display(
input pixel_clk,
input sys_rst_n,
input [10:0] pixel_xpos, //像素点横坐标
input [10:0] pixel_ypos, //像素点纵坐标
output reg [23:0] pixel_data //像素点数据
);
//parameter define
parameter H_DISP = 11'd1280; //分辨率——行
parameter V_DISP = 11'd720; //分辨率——列
localparam WHITE = 24'b11111111_11111111_11111111; //RGB888 白色
localparam BLACK = 24'b00000000_00000000_00000000; //RGB888 黑色
localparam RED = 24'b11111111_00001100_00000000; //RGB888 红色
localparam GREEN = 24'b00000000_11111111_00000000; //RGB888 绿色
localparam BLUE = 24'b00000000_00000000_11111111; //RGB888 蓝色
//*****************************************************
//** main code
//*****************************************************
//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
always @(posedge pixel_clk ) begin
if (!sys_rst_n)
pixel_data <= 16'd0;
else begin
if((pixel_xpos >= 0) && (pixel_xpos < (H_DISP/5)*1))
pixel_data <= WHITE;
else if((pixel_xpos >= (H_DISP/5)*1) && (pixel_xpos < (H_DISP/5)*2))
pixel_data <= BLACK;
else if((pixel_xpos >= (H_DISP/5)*2) && (pixel_xpos < (H_DISP/5)*3))
pixel_data <= RED;
else if((pixel_xpos >= (H_DISP/5)*3) && (pixel_xpos < (H_DISP/5)*4))
pixel_data <= GREEN;
else
pixel_data <= BLUE;
end
end
endmodule
8.tb文件
`timescale 1ns/1ns
module tb_hdmi_colorbar_top();
reg sys_clk ;
reg sys_rst_n ;
wire tmds_clk_p ; // TMDS 时钟通道
wire tmds_clk_n ;
wire [2:0] tmds_data_p ; // TMDS 数据通道
wire [2:0] tmds_data_n ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#201
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
hdmi_colorbar_top hdmi_colorbar_top_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.tmds_clk_p (tmds_clk_p ), // TMDS 时钟通道
.tmds_clk_n (tmds_clk_n ),
.tmds_data_p (tmds_data_p), // TMDS 数据通道
.tmds_data_n (tmds_data_n)
);
endmodule
9.仿真结果
补充一些细节
1.引脚约束差分信号时只需要约束P,N工具会自动帮咱们约束好
2.本节已经涉及到了数字图像这一大类知识,这也是后续FPGA的一大方向,这里简单阐述一下数字信号处理的过程。
感光设备与光电转化器采集图像信号,对图像进行前处理并压缩,将压缩后的数字信号进行解码,对图像进行后处理最终显示出来。此次实验只涉及图像的编码,后续会补充图像处理的全流程
图像前处理包括DPC(坏点矫正)、CFA(色彩滤波矩阵)、STATS(图像统计)、CCM(颜色校正矩阵)、GAMMA(伽马较正)、CSC(颜色空间转化)、Enhance(去噪与图像增强)
-
坏点矫正:由于制造工艺的限制,感光元件不可能没有坏点,咱们要做的就是用与坏点附加相近的像素值来代替坏点的值。
-
色彩滤波矩阵:很多图像传感器都支持输出原始的拜尔格式的图像数据,即一个像素点只输出一个感光色彩的数值。而CFA处理原理上也是利用每个像素周边的像素值进行特定的运算,获得这个像素点其他两个感光色彩值。拜尔图像经过CFA处理后就会输出RGB图像。
-
图像统计:视频图像的自动曝光、自动对焦和自动白平衡都需要对图像进行统计。
-
颜色矫正矩阵:图像传感器与人眼认为的标准色彩并不完全一致,因此需要使用某种方法来较真传感器输出的色彩数值,使其接近标准观察者。
-
伽马矫正:伽马矫正是为了解决非线性响应的显示设备导致图像失真。
-
颜色空间转化:研究图像的老头们发明了许多存贮色彩数据的格式,如RGB、YUV格式等,他们之间可以通过特定的算法进行矫正。
-
去噪与加强:对于某些应用可能需要对采集的图像进行去噪处理和图像增强,以满足后续图像的进一步处理需求。
图像前处理的侧重点是希望尽可能获得清晰优质的原始图像,图像后处理的图像就是对图像进行各种变换或处理,以提取出图像中的有用信息。常规处理方法有图像的平滑、锐化、边缘提取。图像域的变化是图像处理的一个小分支,通过类似傅里叶变换、小波变换等处理后,频域数据提取出来的某些明显特征非常适合各种应用的处理。
上板验证
一个字爽!