听了正点原子的详细讲解后,一段时间不看容易忘记,这里自己记录点笔记,以后回忆快一点,
思路,代码都是学习的正点原子开发,移植源码建议去正点原子官网下载,这里主要思路分析和代码解析。
1.端口解析+硬件流程
总的输入端口:(主要分为 视频输入和音频控制输入)
视频输入:
R[7:0],G[7:0],B[7:0],3*8一共24位,用来色彩数据并行输入
VSYNC:行同步信号,解析见原理解析部分
HSYNC:场同步信号,解析见原理解析部分
VDE:在行同步时序中使用
(此历程简化暂时忽略 音频数据输入【HDMI Audio/Aux data】)
|
到达模块一()
对输入信号编码, R[7:0],G[7:0],B[7:0] +VSYNC+HSYNC+VDE -》R[9:0],G[9:0],B[9:0]
|
到达OSERDESE2模块(并->串)
输入:
R[9:0],G[9:0],B[9:0] (并)
CLK
CLK*5(PLL倍频后的5倍时钟用在并转串(OSESRDESE2模块)的上升沿和下降沿采集中)
Serializer[9:0](时钟编码通道输入,10bit,与CLK CLK*5区分,前者是转化为串口所需要的数据,后者是为模块提供的时钟)
输出:
R (串口)
G (串口)
B (串口)
CLK (串口Serializer时钟编码通道转化来的)
|
到达OBUFDS模块(单端转差分)
2.原理详细解析
2.1 使用5倍CLK的原因
2.2OSERDESE2模块
实现功能:并->串 R[9:0],G[9:0],B[9:0]Serializer[9:0] (并) ->R G B CLK (串)
双数据采集MODE:SDR(单数据沿处理) DDR(双数据沿处理,上升沿+下降沿)
主从模式:主(master) 和 从(Slave)如上图 在下面会举例子
注意从Slave的D1 D2不能使用(数据手册写的),所以这里用D3 D4
SHIFIN/SHIFOUT:从OSERDESE2发送收到的数据给主OSERDESE2
其中一个OSERDESE2模块可以实现8并:1串的转化,但我们9:0有10个口,显然一个OSERDESE2模块不够用,所以用多个OSERDESE2模块的位宽拓展来实现10:1的转化,而两个OSERDESE2模块的连接就需要1个主和一个从连接,如下图
2.2差分线data传输过程:
场同步信号(HSYNC)和行同步信号(VSYNC)![](https://img-blog.csdnimg.cn/direct/65647d442a114ce3845f2cf3a8b2c256.png)
时序解析:当开始传输各式各样的数据时,总需要一些特定时序规律,来辅助数据传输,以保持信号数据的正确传输,此处的时序解析按照时间顺序先后顺序解析
行同步时序(HSYNC)用在显示器每一行的数据传输时序中
场同步时序(VSYNC)用在显示器每一帧的数据传输时序中
行同步时域可以说包含在场同步时域中
场同步循环:
开始场同步(一帧画面开始),VSYNC拉低,延时 o (欧)时间,后拉高->显示后延 p 时间后开始行同步时序部分
行同步循环:(有多少行就循环此过程多烧次)
->拉低HSYNC,延时a后拉高HSYNC->延时显示后沿(b)->拉高DE,data差分先开始传输画面数据,用时(c)->发送完成data数据后,拉低DE->延时显示前沿(d)[原这段时间可以传输其他音频数据等,这里简化历程] 到此本行的数据传输完成 ->下一行的HSYNC拉低,延时a后拉高HSYNC.......(开始行行同步循环循环)
-> 行数据过程传输完毕,总用时q,然后延时显示前沿 r时间 到此本帧的数据传输完成 ->开始下一帧场同步(一帧画面开始),VSYNC拉低,延时 o (欧)时间,后拉高......(开始行场同步循环循环)
3.代码解析
仍然是正点原子代码参考
模块化思维图参考
3.1顶层模块分析在hdmi_colorbar_top.v中
3.1.1定义顶层端口
(输入:时钟 + 复位 )
(输出 1bit的tmds_clk_p和tmds_clk_n + [2:0]的tmds_data_p和tmds_data_n)
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
);
3.1.2wire类型声明
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;
3.1.3MMCM/PLL IP核例化
完成功能:输出1倍原时钟和5倍时钟(用来并转串模块)。
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)
);
MMCM(不是PLL) ip核设置
主要看下面 clk_in 1(板子晶振提供)输入时钟50Mhz
输出
clk_out1 74.25Mhz用于后面的并行时钟(为啥是74.25M勒,这是因为在这个1280*720分辨率下要求的频率是这么多)
clk_out2 74.25*5=371.25Mhz用于后面的并转串行模块的串行时钟
3.1.4 video_driver例化
实现功能:使输出的行同步信号 ,场同步信号 de端口 [23:0]RGB888颜色数据 像素点横坐标纵坐标符合行同步序号和场同步信号的时序要求
输入:pixel_clk 时钟 sys_rst_n 复位 [23:0]pixel_data//像素点数据
输出:video_hs video_vs video_de
[23:0]video_rgb
data_req(未使用)
pixel_xpos像素点横坐标
pixel_ypos像素点纵坐标
这里使用wire连线
//例化视频显示驱动模块
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 )
);
3.1.5 video_display例化
实现功能:在1280*720的屏幕分辨率下
输入的像素点坐标在判断在哪个对应范围
则【23:0】pixel_data输出对应的颜色 实现最后输出彩条
输入:clk 复位信号
11位的x坐标 ,y坐标(来表示屏幕上的那一个像素点)
输出:
【23:0】 pixel_data :该坐标像素点的色彩数据
//例化视频显示模块
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)
);
3.1.6 vi_transmitter_top例化
输入:
pixel_clk 一倍时钟
pixel_clk_5x 5倍时钟
sys_rst_n复位信号
clk_locked类似于clk的开关吧
video_rgb[23:0] rgb输入24位并行输入
video_hs 场同步信号(HSYNC)
video_vs 行同步信号(VSYNC)
video_de 行同步信号中用到的de端口 详细见行同步时序
输出
tmds_clk_p tmds_clk_n clk(不是前面的pixel_clk ,两个用途的时钟)的差分输出
tmds_data_p tmds_data_n 数据的差分输出
//例化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 () //预留的端口,本次实验未用到
);
3.2分析video_driver moudle在video_driver.v中
实现功能:使输出的行同步信号 ,场同步信号 de端口 [23:0]RGB888颜色数据 像素点横坐标纵坐标符合行同步序号和场同步信号的时序要求
输入:pixel_clk 时钟 sys_rst_n 复位 [23:0]pixel_data//像素点数据
输出:video_hs video_vs video_de [23:0]video_rgb data_req
pixel_xpos像素点横坐标
pixel_ypos像素点纵坐标
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 //像素点纵坐标
);
//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; //场扫描周期
//reg define
reg [11:0] cnt_h;
reg [11:0] cnt_v;
reg video_en;
定时器编写
一个定时器用于记录
行进度(一共是行扫描周期如上图的(e),也是H_TOTAL = 11'd1650; //行扫描周期 -1)
(已经加入了各个前沿,后沿等时间,所以是d1650,不是1280,来消影等处理)
每当一个行周期扫描完毕清一次0,这一时刻正是达到了这一行最后一个像素点
一个定时器用于记录
场进度记录(一共是行扫描周期如上图的(s),也是V_TOTAL = 11'd750; //行扫描周期 -1)
每当一个行周期扫描完毕,开始记录场 周期进度+1,当场周期时间达到场周期上限,说明达到了这最后一行最后一个像素点(已经加入了各个前沿,后沿等时间,所以是750,不是720,来消影等处理),清0
//行计数器对像素时钟计数
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
行同步信号(图中HSYNC),和场同步信号(图中VSYNC)用assign组合逻辑来实现
设计思路:
video_de video_en就是图中应该发送有效data的标志位
利用计时器记录当前的行进度和场进度,当进度时>设定的H_SYNC(图中的a) 值时赋值为1 (已经度过行同步时间a,图中HSYNC=1),否则为0(没有度过行同步时间a,图中HSYNC=0)
利用计时器记录当前的行进度和场进度,当进度时>设定的V_SYNC(图中的o) 值时赋值为1 (已经度过场同步时间o,图中VSYNC=1),否则为0(没有度过场同步时间o图中VSYNC=0)
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; //场同步信号赋值
到此的图中VSYNC和图中HSYNC已经实现。
当video_de=1应该发生有效数据时,rgb数据=输入的[23:0]pixel_data数据否则为0
//RGB888数据输出
assign video_rgb = video_de ? pixel_data : 24'd0;
这段是对data_req的操作,简化一下就是在有效数据发送的前2段时间,输出1告诉其他模块,要发送有效data了
//请求像素点颜色数据输入
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,y点数据
//像素点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坐标
a 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;
endlways@ (posedge pixel_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
3.3分析video_displaymoudle在video_display.v中
实现功能:在1280*720的屏幕分辨率下
输入的像素点坐标 判断在哪个对应范围
则【23:0】pixel_data输出对应的颜色 实现最后输出彩条
输入:clk 复位信号
11位的x坐标 ,y坐标(来表示屏幕上的那一个像素点)
输出:
【23:0】 pixel_data :该坐标像素点的色彩数据
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 //像素点数据
);
0到1/5的x坐标输出白色 1/5屏幕的x坐标到 2/5屏幕的x坐标输出黑色 。。。。。。。。
//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_00000000_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
更详细的代码请自我查看,博主简化了一下大意,精力有限码不了一点了
3.4分析dvi_transmitter_top在dvi_transmitter_top.v中
实现功能:
输入:
clk 5倍时钟 复位
【23:0】RGB并行输入 场同步信号 行同步信号 de端口(行同步信号中使用)
输出:
差分时钟信号 差分data信号3(R G B 3对的串行差分信号,一共3*2 =6根线输出)
输出使能位
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类型定义
//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;
手动赋值 后面差分输出的clk10bit编码
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 dvi_encoder encoder_g dvi_encoder encoder_r
完成功能:对三个颜色通道进行TMDS编码成 每个8位输入编码成10bit输出 一共30bit输出
输入:
clk 复位
RGB数据输入的0到8位 场行同步信号
输出:
out 编码后的10位并行blue信号
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)
) ;
此时已经有了TMDS编码后的30bit并行数据,
所以现在RGB要分3组(3*10位=30bit)并转串
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])
);
10bit clk转化为串行
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)
//转换差分信号
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