高清晰度多媒体接口(英文:High Definition Multimedia Interface,HDMI)是一种数字化视频/音频接口技术,是适合影像传输的专用型数字化接口,可同时传送音频和影像信号,最高数据传输速度为2.25GB/s,无需在信号传送前进行数/模或者模/数转换。
HDMI向下兼容DVI,但是DVI(数字视频接口)只能用来传输视频,而不能同时传输音频,这是两者最主要的差别。此外,DVI接口的尺寸明显大于HDMI接口。
hdmi引脚图
HDMI接口共有19个引脚,分上下两排,奇数在上,偶数在下,穿插排布。根据其功能,可以将引脚分为4类。HDMI协议与DVI协议在很多方面都是相同的,包括物理连接(TMDS)、有效视频编码算法以及控制字符的定义等。HDMI兼容DVI的数据。但是,相比于DVI,HDMI在视频的消隐期会传输更多的数据,包括音频数据和附加数据。4-bit音频和附加数据将通过TERC4编码机制转换成10-bit TERC4字符,然后在绿色和红色通道上传输。
HDMI传输由三组TMDS通道和一组TMDS clock通道组成,TMDS clock的运行频率是video信号的pixel频率,在每个cycle,每个TMDS data通道发送10bit数据。TMDS通道包括 3 个RGB 数据传输通道和 1 个时钟信号传输通道。
TMDS通道:引脚1-引脚12。负责发送音频、视频及辅助数据;
DDC通道:引脚15、16、17,使用100kHz时钟频率的I²C信号。发送端与接收端可利用DDC沟道得知彼此的发送与接收能力,但HDMI仅需单向获知接收端(显示器)的能力;
CEC通道:引脚13、17。CEC通道为必须预留线路,用来发送工业规格的AV Link协议信号,为单芯线双向串列总线。
其他通道:引脚14位保留引脚,无连接;引脚18为+5V电源;引脚19位热插拔检测引脚。
HDMI在输入附加数据的同时,还需要输入ADE(Aux/Audio Data Enable)信号,其作用和VDE是类似的:当ADE为高电平时,表明输入端的附加数据或者音频数据有效。因为只需要处理基本的视频传输,而不需要处理音频数据或附加数据,那么使用HDMI接口模拟DVI接口并忽略部分引脚进行驱动。
hdmi数据传输
HDMI输出驱动接口在FPGA方面应用时,主要有两种方式:
一种是采用HDMI( DVI)编码芯片。这种操作只需要对对应芯片进行操作配置即可实现功能。
另外一种方式就是使用FPGA进行编写,由 IO 模拟方式实现。通过像素点坐标,来规定显示的内容,像素点坐标由驱动模块根据时钟及行场计数来动态改变。
在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。
hdmi编码:
这里我们当然选择fpga,并且采用TMDS视频传输协议编写代码,在编写HDMI的接口驱动时,核心部分就是将输入的8位宽度的R/G/B数据(或音频数据)编码为10位宽的输出数据,再进行并串转换
tmds数据传输
HSYNC和VSYNC信号在蓝色通道进行编码得到10位字符,然后在视频消隐期传输。
绿色和红色通道的控制信号C0和C1同样需要进行编码,并在消隐期输出。
TMDS通道包括 3 个RGB 数据传输通道和 1 个时钟信号传输通道。每一通道都通过编码算法,将 8 位的视频、音频数据转换成 10 位数据,8 位数据经过编码得到 10 位最小化数据,看似增加了冗余位,但通过这种算法得到的 10 位数据在更长的同轴电缆中传输的可靠性增强了。
最小化传输差分信号是通过异或及异或非等逻辑算法将原始 8位数据转换成 10 位数据,前 8位数据由原始信号经逻辑运算后逻辑得到,第 9 位指示运算的方式,第 10 位对应直流平衡。
要实现TMDS通道传输,首先要将传入的8 位的并行数据进行编码、并/串转换,添加第9位编码位。添加编码位的数据需要进行直流均衡处理,在编码过程中保证信道中传输数据包含的1与0的个数相同。方法是在添加编码位的 9 位数据的后面加上第 10 位数据,1表示反转,0表示没有反转,保证10位数据中1与0个数相同。
tmds编码算法:
直流均衡处理后的10位数据需要进行单端转差分处理。即利用2个引脚间电压差来传送信号,传输数据的数值(“0”或者“1”)由两脚间电压正负极性和大小决定,从而达到DC平衡。一根线上传输原来的信号,另一根线上传输与原来信号相反的信号。这样接收端就可以通过让一根线上的信号减去另一根线上的信号的方式来屏蔽电磁干扰,从而得到正确的信号。
TMDS逻辑代码:
module tmds_encoder(input clk,input rst,
input [7:0]din,input de,input ctrl_1,input ctrl_2,
output reg[9:0]dout);
reg [2:0]datai_num;
reg[3:0]q1_num,q0_num;
reg [7:0]data_d0;
reg [8:0]q_m_d0;
reg[4:0]cnt;//signed num
reg de_d0,de_d1,ctrl_1_d0,
ctrl_1_d1,ctrl_2_d0,ctrl_2_d1;
wire decision1,decision2,decision3;
wire [8:0]q_m;
parameter CTRL_C0 = 10'b11_0101_0100;
parameter CTRL_C1 = 10'b00_1010_1011;
parameter CTRL_C2 = 10'b01_0101_0100;
parameter CTRL_C3 = 10'b10_1010_1011;
assign q_m[0] = data_d0 [0];
assign q_m[1] = decision1 ? (q_m[0] ^ (~ data_d0 [1])) : (q_m[0] ^ data_d0[1]);
assign q_m[2] = decision1 ? (q_m[1] ^ (~ data_d0 [2])) : (q_m[1] ^ data_d0[2]);
assign q_m[3] = decision1 ? (q_m[2] ^ (~ data_d0 [3])) : (q_m[2] ^ data_d0[3]);
assign q_m[4] = decision1 ? (q_m[3] ^ (~ data_d0 [4])) : (q_m[3] ^ data_d0[4]);
assign q_m[5] = decision1 ? (q_m[4] ^ (~ data_d0 [5])) : (q_m[4] ^ data_d0[5]);
assign q_m[6] = decision1 ? (q_m[5] ^ (~ data_d0 [6])) : (q_m[5] ^ data_d0[6]);
assign q_m[7] = decision1 ? (q_m[6] ^ (~ data_d0 [7])) : (q_m[6] ^ data_d0[7]);
assign q_m[8] = decision1 ? 1'b0 : 1'b1;
assign decision1=(datai_num>'d4|(datai_num=='d4&&data_d0[0]==0));
assign decision2 = (cnt == 5'd0) | (q1_num == q0_num);
assign decision3 = ((~cnt[4]) & (q1_num > q0_num))
| (cnt[4] & (q1_num < q0_num));
always @(posedge clk ) begin
datai_num<=#1 din[7]+din[6]+ din[5]+din[4]+
din[3]+din[2]+din[1]+din[0];
q1_num=#1 q_m[7]+q_m[6]+ q_m[5]+q_m[4]+
q_m[3]+q_m[2]+q_m[1]+q_m[0];
q0_num=#1 'd8-(q_m[7]+q_m[6]+ q_m[5]+q_m[4]+
q_m[3]+q_m[2]+q_m[1]+q_m[0]);
data_d0<=#1 din;
q_m_d0<=#1 q_m;
de_d0<=#1 de;
de_d1<=#1 de_d0;
ctrl_1_d0<=#1 ctrl_1;
ctrl_1_d1<=#1 ctrl_1_d0;
ctrl_2_d0<=#1 ctrl_2;
ctrl_2_d1<=#1 ctrl_2_d0;
end
always @(posedge clk or posedge rst) begin
if(rst)begin
dout<='d0;
cnt<='d0;
end
else begin
if(de_d1)//de=high
begin
if(decision2)begin
dout[9]<=#1 ~q_m_d0[8];
dout[8]<=#1 q_m_d0[8];
dout[7:0]<=#1 q_m_d0[8]?q_m_d0[7:0]:~q_m_d0[7:0];
cnt<=#1 q_m_d0[8]?(cnt+q1_num-q0_num):
(cnt+q0_num-q1_num);
end
else if(decision3)begin
dout[9]<=#1 1;
dout[8]<=#1 q_m_d0[8];
dout[7:0]<=#1 ~q_m_d0[7:0];
cnt<=#1 cnt+2*q_m_d0[8]+q0_num-q1_num;
end
else begin
dout[9]<=#1 0;
dout[8]<=#1 q_m_d0[8];
dout[7:0]<=#1 q_m_d0[7:0];
cnt<=#1 cnt-2*q_m_d0[8]+q1_num-q0_num;
end
end
else begin
case({ctrl_2_d1,ctrl_1_d1})
2'b00 : dout <=#1 CTRL_C0;
2'b01 : dout <=#1 CTRL_C1;
2'b10 : dout <=#1 CTRL_C2;
2'b11 : dout <=#1 CTRL_C3;
endcase
cnt <=#1 5'd0;
end
end
end
endmodule
我们后续的代码中还需要用到vivado 提供的serdes原语,serdes又可以分为iserdes(串行转并行)与oserdes(并行转串行),因为FPGA内部跑的频率较低,不能处理那么高的串行频率需要转成并行处理。
一个OSERDESE2只能实现最多8:1的转换率,可以通过位宽扩展实现10:1的并串转换。
在DDR(双倍数据速率)模式下,支持“2,4,6,8,10,14”6种位宽并行数据,其中10bit和14bit需要2个OSERDESE2原语串联才可以完成。其中一个作为Master,另一个作为Slave,通过这种方式最多可实现14:1的并串转换。需要注意的是,在位宽扩展时,Slave模块的数据输入端只能使用D3至D8。
赛灵思官方也提供相应的IP核SelectIO InterfaceWizard,对这些原语进行了封装,提供图形化界面方便使用
OSERDES各个端口的作用:
DATA_RATE_OQ:数据模式,DDR代表上升沿和下降沿都发送数据,SDR代表只在上升沿发送数据
DATA_RATE_TQ:三态门模式,同上,这里我们不关注三态状态,所有与三态设置相关的信号输入设置成0,输出不关心。
INIT_OQ:初始化数据输出端初始值
SERDES_MODE:串行的模式,在10,14串行通信的时候用到
SRVAL_OQ:复位时输出电平的状态
OFB:反馈输出,这里不关心即可
OQ:输出信号
SHIFTOUT1:serdes串联时送给上一个serdes用到的信号
SHIFTOUT2:serdes串联时送给上一个serdes用到的信号
CLK:串行块时钟,在HDMI中是CLKDIV的5倍
CLKDIV:并行数据的随路时钟
DX:并行输入数据
OCE:模块使能信号,默认为1使能
RST:模块复位信号,默认为1复位
SHIFTIN1:serdes串联时送接收下一个serdes用到的信号
SHIFTIN2:serdes串联时送接收下一个serdes用到的信号
dvi传输整体设计
串并转换模块,这里主要是用xilinx原语写的:
module serializer(
input rst,
input para_clk,
input serial_clk,
input [9:0]para_data,
output dout
);
wire cascade1,cascade2;
OSERDESE2 #(
.DATA_RATE_OQ("DDR"),
.DATA_RATE_TQ("SDR"),
.DATA_WIDTH(10),
.SERDES_MODE("MASTER"),
.TBYTE_CTL("FALSE"),
.TBYTE_SRC("FALSE"),
.TRISTATE_WIDTH(1))
OSE_MASTER(
.CLK(serial_clk),
.CLKDIV(para_clk),
.RST(rst),
.OCE(1),
.OQ(dout),
.D1(para_data[0]),
.D2(para_data[1]),
.D3(para_data[2]),
.D4(para_data[3]),
.D5(para_data[4]),
.D6(para_data[5]),
.D7(para_data[6]),
.D8(para_data[7]),
.SHIFTIN1(cascade1),
.SHIFTIN2(cascade2),
.SHIFTOUT1(),
.SHIFTOUT2(),
.OFB(),
.T1(0),
.T2(0),
.T3(0),
.T4(0),
.TBYTEIN(0),
.TCE(0),
.TBYTEOUT(),
.TFB(),
.TQ()
);
OSERDESE2 #(
.DATA_RATE_OQ("DDR"),
.DATA_RATE_TQ("SDR"),
.DATA_WIDTH(10),
.SERDES_MODE("SLAVE"),
.TBYTE_CTL("FALSE"),
.TBYTE_SRC("FALSE"),
.TRISTATE_WIDTH(1))
OSE_SLAVE(
.CLK(serial_clk),
.CLKDIV(para_clk),
.RST(rst),
.OCE(1),
.OQ(),
.D1(0),
.D2(0),
.D3(para_data[8]),
.D4(para_data[9]),
.D5(0),
.D6(0),
.D7(0),
.D8(0),
.SHIFTIN1(),
.SHIFTIN2(),
.SHIFTOUT1(cascade1),
.SHIFTOUT2(cascade2),
.OFB(),
.T1(0),
.T2(0),
.T3(0),
.T4(0),
.TBYTEIN(0),
.TCE(0),
.TBYTEOUT(),
.TFB(),
.TQ()
);
endmodule
为了将串行数据转换成差分数据,还需要用到obufds原语,
OBUFDS是一个输出缓冲器,支持低压差分信号。它隔离出了内电路并向芯片上的信号提供驱动电流。输出用O和OB两个独立接口表示。一个可以认为是主信号,另外一个可以认为是从信号。主信号和从信号是同一个逻辑信号,但是相位相反。
obufds电路原理图:
vivado提供的模板:
OBUFDS #(
.IOSTANDARD("DEFAULT"), // Specify the output I/O standard
.SLEW("SLOW") // Specify the output slew rate
) OBUFDS_inst (
.O(O), // Diff_p output (connect directly to top-level port)
.OB(OB), // Diff_n output (connect directly to top-level port)
.I(I) // Buffer input
);
dvi模块代码:
module dvi_top(
input pclk,
input pclk5,
input rstn,
input de,
input hsync,
input vsync,
input[23:0]vdin,
output tmds_clkp,
output tmds_clkn,
output[2:0] tmds_data_p,
output[2:0] tmds_data_n,
output tmds_en
);
wire rst;
wire[9:0] red_10b,green_10b,blue_10b,clk_10b;
wire[2:0]tmds_serial_data,tmds_serial_clk;
reg rst1,rst2;
assign rst=rst2,tmds_en='d1,clk_10b=10'b11111_00000;
always @(posedge pclk or negedge rstn) begin
if(!rstn)begin
rst1<=1;
rst2<=1;
end
else begin
rst1<=0;
rst2<=rst1;
end
end
tmds_encoder blue_encoder(
.clk(pclk),.rst(rst),
.din(vdin[7:0]),.de(de),.ctrl_1(0),.ctrl_2(0),
.dout(blue_10b));
tmds_encoder green_encoder(
.clk(pclk),.rst(rst),
.din(vdin[15:8]),.de(de),.ctrl_1(0),.ctrl_2(0),
.dout(green_10b));
tmds_encoder red_encoder(
.clk(pclk),.rst(rst),
.din(vdin[23:16]),.de(de),.ctrl_1(hsync),.ctrl_2(vsync),
.dout(red_10b));
serializer clk_serial(
.rst(rst),.para_clk(pclk),
.serial_clk(pclk5),.para_data(clk_10b),
.dout(tmds_serial_clk)
);
serializer blue_serial(
.rst(rst),.para_clk(pclk),
.serial_clk(pclk5),.para_data(blue_10b),
.dout(tmds_serial_data[0])
);
serializer green_serial(
.rst(rst),.para_clk(pclk),
.serial_clk(pclk5),.para_data(green_10b),
.dout(tmds_serial_data[1])
);
serializer red_serial(
.rst(rst),.para_clk(pclk),
.serial_clk(pclk5),.para_data(red_10b),
.dout(tmds_serial_data[2])
);
OBUFDS #(.IOSTANDARD("TMDS_33"))
TMDS0(
.I(tmds_serial_data[0]),
.O(tmds_data_p[0]),
.OB(tmds_data_n[0])
);
OBUFDS #(.IOSTANDARD("TMDS_33"))
TMDS1(
.I(tmds_serial_data[1]),
.O(tmds_data_p[1]),
.OB(tmds_data_n[1])
);
OBUFDS #(.IOSTANDARD("TMDS_33"))
TMDS2(
.I(tmds_serial_data[2]),
.O(tmds_data_p[2]),
.OB(tmds_data_n[2])
);
OBUFDS #(.IOSTANDARD("TMDS_33"))
TMDS3(
.I(tmds_serial_clk),
.O(tmds_clkp),
.OB(tmds_clkn)
);
endmodule
整体的dvi模块再与显示和驱动模块连接
注意还要用到一个分频模块,这里直接调用mmcm ip核
显示驱动:
module hdmi_driver
(input pclk,input rstn,
input[23:0] display_data,
output [10:0]xpos, ypos,
output de,hs,vs,
output[23:0] video_rgb);
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[10:0]h_cnt,v_cnt;
wire en,data_req;
assign hs=(h_cnt<H_SYNC)?0:1,
vs=(v_cnt<V_SYNC)?0:1;
assign de=en,video_rgb=en?display_data:'d0;
assign xpos=data_req?(h_cnt-(H_SYNC+H_BACK-1)):'d0,
ypos=data_req?(v_cnt-(V_SYNC+V_BACK-1)):'d0;
assign en=((h_cnt>=H_SYNC+H_BACK)&&(h_cnt<H_SYNC+H_BACK+H_DISP)&&
(v_cnt>=V_SYNC+V_BACK)&&(v_cnt<V_SYNC+V_BACK+V_DISP));
assign data_req=((h_cnt>=H_SYNC+H_BACK-'d1)&&(h_cnt<H_SYNC+H_BACK+H_DISP-'d1)
&&(v_cnt>=V_SYNC+V_BACK)&&(v_cnt<V_SYNC+V_BACK+V_DISP));
always @(posedge pclk or negedge rstn) begin
if(!rstn)begin
h_cnt<='d0;
v_cnt<='d0;
end
else if(h_cnt==H_TOTAL-'d1)begin
h_cnt<='d0;
if(v_cnt==V_TOTAL-'d1)
v_cnt<='d0;
else v_cnt<=v_cnt+'d1;
end
else begin
h_cnt<=h_cnt+'d1;
end
end
endmodule
画面彩条模块:
module hdmi_display
(input pclk,input rstn,
input [10:0]xpos, ypos,
output reg [23:0]display_data);
parameter WHITE=24'hfff_fff,
RED=24'h000_000,
BLACK=24'hff0_000,
H_DISP = 11'd1280,
V_DISP = 11'd720;
always @(posedge pclk or negedge rstn) begin
if(!rstn)
display_data<=WHITE;
else if(xpos>='d0&&xpos<H_DISP/3*1-1)
display_data<=BLACK;
else if(xpos>=H_DISP/3*1&&xpos<H_DISP/3*2)
display_data<=RED;
else display_data<=WHITE;
end
endmodule
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;
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)
);
hdmi_driver lcd_driver_u
(.pclk(pixel_clk),.rstn(sys_rst_n),
.display_data(pixel_data_w),
.xpos(pixel_xpos_w),.ypos(pixel_ypos_w),
.de(video_de),.hs(video_hs),.vs(video_vs),
.video_rgb(video_rgb));
hdmi_display lcd_u
(.pclk(pixel_clk),
.rstn(sys_rst_n),
.xpos(pixel_xpos_w),
.ypos(pixel_ypos_w),
.display_data(pixel_data_w));
dvi_top dvi_top_u(
.pclk(pixel_clk),
.pclk5(pixel_clk_5x),
.rstn(sys_rst_n & clk_locked),
.de(video_de),
.vdin(video_rgb),
.hsync(video_hs),
.vsync(video_vs),
.tmds_clkp(tmds_clk_p),
.tmds_clkn(tmds_clk_n),
.tmds_data_p(tmds_data_p),
.tmds_data_n(tmds_data_n),
.tmds_en()
);
endmodule
在彩条显示的前提下,进行方块移动,显示一个不停移动的方块,要求方块移动到边界处时能够改变移动方向。显示分辨率为1280*720,刷新速率为60hz。先让方块向下纵向移动到底后再向右横向移动到屏幕另一脚。随会再纵向和横向移动回到原点,不断循环。
为了完成方块的显示,需要同时使用像素点的横坐标和纵坐标来绘制方块所在的矩形区域,另外还需要知道矩形区域左上角的顶点坐标。由于RGB显示的图像在行场同步信号的同步下不停的刷新,因此只要连续改变方块左上角顶点的坐标,并在新的坐标点处重新绘制方块,即可实现方块移动的效果。
方块移动模块代码:
module rgb_display_2(
input pclk, //像素时钟
input rstn, //复位信号
input [10:0] xpos, //像素点横坐标
input [10:0] ypos, //像素点纵坐标
output reg [23:0] display_data //像素点数据
);
parameter H_DISP= 11'd1280; //分辨率--行
parameter V_DISP= 11'd720; //分辨率--列
localparam SIDE_W= 11'd40; //屏幕边框宽度
localparam BLOCK_W = 11'd40; //方块宽度
localparam RED=24'h000_000;//屏幕边框颜色 蓝色
localparam WHITE = 24'b11111111_11111111_11111111;//背景颜色 白色
localparam BLACK = 24'b00000000_00000000_00000000;//方块颜色 黑色
localparam PIC_X_START=11'd100,
PIC_X1_START=11'd240,
PIC_X2_START=11'd380,
PIC_X3_START=11'd520,
PIC_Y_START=11'd100,
PIC_WIDTH=11'D128,
PIC_HEIGHT=11'D128 ;
reg [10:0] block_x = SIDE_W ; //方块左上角横坐标
reg [10:0] block_y = SIDE_W ; //方块左上角纵坐标
reg [21:0] div_cnt; //时钟分频计数器
reg[13:0]rom_addr;
reg[10:0]x1,y1,x2,y2,x3,y3;
wire[13:0]rom_addr1,rom_addr2,rom_addr3;
wire[23:0]rom_rd_data,rom_rd_data1,rom_rd_data2,rom_rd_data3;
reg[1:0] h_dir, v_dir;
wire move_en,pic_flag,pic_flag1,pic_flag2,pic_flag3; //方块移动使能信号,频率为100hz
reg h_stable,v_stable;
wire x_edge0,x_edge1,y_edge0,y_edge1;
reg[7:0] pic1_x,pic1_y,pic2_x,pic2_y,pic3_x,pic3_y,pic4_x,pic4_y;
assign x_edge0=(block_x == SIDE_W - 1'b1);
assign x_edge1=(block_x == H_DISP - SIDE_W - BLOCK_W);
assign y_edge0=(block_y == SIDE_W - 1'b1);
assign y_edge1=(block_y == V_DISP - SIDE_W - BLOCK_W);
assign move_en = (div_cnt == 22'd742500) ? 1'b1 : 1'b0;
assign pic_flag=(xpos>=PIC_X_START)&&(xpos<PIC_X_START+PIC_WIDTH)&&
(ypos>=PIC_Y_START)&&(ypos<PIC_Y_START+PIC_HEIGHT),
pic_flag1=(xpos>=PIC_X1_START)&&(xpos<PIC_X1_START+PIC_WIDTH)&&
(ypos>=PIC_Y_START)&&(ypos<PIC_Y_START+PIC_HEIGHT),
pic_flag2=(xpos>=PIC_X2_START)&&(xpos<PIC_X2_START+PIC_WIDTH)&&
(ypos>=PIC_Y_START)&&(ypos<PIC_Y_START+PIC_HEIGHT),
pic_flag3=(xpos>=PIC_X3_START)&&(xpos<PIC_X3_START+PIC_WIDTH)&&
(ypos>=PIC_Y_START)&&(ypos<PIC_Y_START+PIC_HEIGHT);
assign rom_addr1=pic1_x+128*pic1_y,
rom_addr2=pic2_x+128*pic2_y,
rom_addr3=pic3_x+128*pic3_y ;
blk_mem_gen_0 blk_mem_gen_0 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr), // input wire [13 : 0] addra
.douta (rom_rd_data) // output wire [23 : 0] douta
);
blk_mem_gen_0 blk_mem_gen_1 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr1), // input wire [13 : 0] addra
.douta (rom_rd_data1) // output wire [23 : 0] douta
);
blk_mem_gen_0 blk_mem_gen_2 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr2), // input wire [13 : 0] addra
.douta (rom_rd_data2) // output wire [23 : 0] douta
);
blk_mem_gen_0 blk_mem_gen_3 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr3), // input wire [13 : 0] addra
.douta (rom_rd_data3) // output wire [23 : 0] douta
);
//通过对像素时钟计数,实现时钟分频
always @(posedge pclk or negedge rstn) begin
if(!rstn)
begin
pic1_x<='d0;
pic1_y<='d127;
pic2_x<='d127;
pic2_y<='d127;
pic3_x<='d127;
pic3_y<='d0;
end
else begin
pic1_x<=y1;
pic1_y<='d127-x1;
pic2_x<='d127-x2;
pic2_y<='d127-y2;
pic3_x<='d127-y3;
pic3_y<=x3;
end
end
always @(posedge pclk or negedge rstn) begin
if(!rstn)
rom_addr<='d0;
else if(pic_flag)
rom_addr<=rom_addr+'d1;
else if(ypos>=PIC_Y_START+PIC_HEIGHT)
rom_addr<='d0;
end
always @(posedge pclk or negedge rstn) begin
if(!rstn)
begin
x1<='d0;
y1<='d0;
x2<='d0;
y2<='d0;
x3<='d0;
y3<='d0;
end
else if(pic_flag1)
begin
if(x1!='d127)
x1<=x1+'d1;
else begin
x1<='d0;
if(y1<127)
y1<=y1+'d1;
else y1<='d0;
end
end
else if(pic_flag2)
begin
if(x2!='d127)
x2<=x2+'d1;
else begin
x2<='d0;
if(y2<127)
y2<=y2+'d1;
else y2<='d0;
end
end
else if(pic_flag3)
begin
if(x3!='d127)
x3<=x3+'d1;
else begin
x3<='d0;
if(y3<127)
y3<=y3+'d1;
else y3<='d0;
end
end
else ;
end
always @(posedge pclk ) begin
if (!rstn)
div_cnt <= 22'd0;
else begin
if(div_cnt < 22'd742500)
div_cnt <= div_cnt + 1'b1;
else
div_cnt <= 22'd0; //计数达10ms后清零
end
end
always @(posedge pclk ) begin
if (!rstn) begin
h_dir <= 2'd2;
v_dir <= 2'd1;
end
else begin
if(x_edge0)
begin
if(y_edge0)
begin
h_dir <= 2'd2;
v_dir <= 1'b1;
end
else if(y_edge1)
begin
h_dir <= 2'd1;
v_dir <= 2'd2;
end
else ;
end
else if(x_edge1)
begin
if(y_edge0)
begin
h_dir <= 2'd0;
v_dir <= 2'd2;
end
else if(y_edge1)
begin
h_dir <= 2'd2;
v_dir <= 2'd0;
end
else ;
end
end
end
always @(posedge pclk ) begin
if (!rstn) begin
block_x <= SIDE_W; //方块初始位置横坐标
block_y <= SIDE_W; //方块初始位置纵坐标
end
else if(move_en) begin
if(h_dir==2'd1)
block_x <= block_x + 1'b1; //方块向右移动
else
if(h_dir==2'd0)
block_x <= block_x - 1'b1; //方块向左移动
else if(h_dir==2'd2)
block_x <= block_x;
else ;
if(v_dir==2'd1)
block_y <= block_y + 1'b1; //方块向右移动
else
if(v_dir==2'd0)
block_y <= block_y - 1'b1; //方块向左移动
else if(v_dir==2'd2)
block_y <= block_y;
else ;
end
else begin
block_x <= block_x;
block_y <= block_y;
end
end
always @(posedge pclk ) begin
if (!rstn)
display_data <= WHITE;
else begin
if((xpos < SIDE_W) || (xpos >= H_DISP - SIDE_W)
|| (ypos < SIDE_W) || (ypos >= V_DISP - SIDE_W))
display_data <= RED;
else
if((xpos >= block_x) && (xpos < block_x + BLOCK_W)
&& (ypos >= block_y) && (ypos < block_y + BLOCK_W))
display_data <= BLACK; //绘制方块为黑色
else
if(pic_flag)
display_data<= rom_rd_data;
else
if(pic_flag1)
display_data<= rom_rd_data1;
else
if(pic_flag2)
display_data<= rom_rd_data2;
else
if(pic_flag3)
display_data<= rom_rd_data3;
else display_data <= WHITE; //绘制背景为白色
end
end
endmodule
这段代码的一个难点是控制方块的运动方向及其所在区域,于是我们引入了 wire x_edge0,x_edge1,y_edge0,y_edge1这四个变量表示方块是否运动到x和y方向的边界点,并根据不同的边界点调整方块的运动方向,从而使得方块循环运动。我们同时还调用了rom核用来存储图片数据,并提供代码对图片进行旋转操作,之所以要显示这样一组图片是因为在最后我们将把方块替换成图片进行运动并且使得图片中的内容适应运动方向。
图片小人运动模块代码:
module rgb_display_3(
input pclk, //像素时钟
input rstn, //复位信号
input [10:0] xpos, //像素点横坐标
input [10:0] ypos, //像素点纵坐标
output reg [23:0] display_data //像素点数据
);
parameter H_DISP= 11'd1280; //分辨率--行
parameter V_DISP= 11'd720; //分辨率--列
localparam SIDE_W= 11'd128; //屏幕边框宽度
localparam BLOCK_W = 11'd128; //方块宽度
localparam RED=24'hff0000;//屏幕边框颜色 蓝色
localparam WHITE = 24'b11111111_11111111_11111111;//背景颜色 白色
localparam BLACK = 24'b00000000_00000000_00000000;//方块颜色 黑色
localparam PIC_X_START=11'd228,
// PIC_X1_START=11'd368,
// PIC_X2_START=11'd508,
// PIC_X3_START=11'd648,
PIC_Y_START=11'd228,
PIC_WIDTH=11'D128,
PIC_HEIGHT=11'D128 ;
reg [10:0] block_x = SIDE_W ; //方块左上角横坐标
reg [10:0] block_y = SIDE_W ; //方块左上角纵坐标
reg [21:0] div_cnt; //时钟分频计数器
reg[13:0]rom_addr;
reg[10:0]x1,y1,x2,y2,x3,y3;
wire[13:0]rom_addr1,rom_addr2,rom_addr3;
wire[23:0]rom_rd_data,rom_rd_data1,rom_rd_data2,rom_rd_data3;
reg[1:0]pic_sel;
reg[1:0] h_dir, v_dir;
wire move_en,pic_flag,pic_flag1,pic_flag2,pic_flag3; //方块移动使能信号,频率为100hz
reg h_stable,v_stable;
wire x_edge0,x_edge1,y_edge0,y_edge1;
wire[7:0] pic1_x,pic1_y,pic2_x,pic2_y,pic3_x,pic3_y,pic4_x,pic4_y;
assign x_edge0=(block_x == SIDE_W - 1'b1);
assign x_edge1=(block_x == H_DISP - SIDE_W - BLOCK_W);
assign y_edge0=(block_y == SIDE_W - 1'b1);
assign y_edge1=(block_y == V_DISP - SIDE_W - BLOCK_W);
assign move_en = (div_cnt == 22'd742500) ? 1'b1 : 1'b0;
assign pic_flag=(xpos >= block_x) && (xpos < block_x + BLOCK_W)
&& (ypos >= block_y) && (ypos < block_y + BLOCK_W)&&(pic_sel==2'd1),
pic_flag1=(xpos >= block_x) && (xpos < block_x + BLOCK_W)
&& (ypos >= block_y) && (ypos < block_y + BLOCK_W)&&(pic_sel==2'd0),
pic_flag2=(xpos >= block_x) && (xpos < block_x + BLOCK_W)
&& (ypos >= block_y) && (ypos < block_y + BLOCK_W)&&(pic_sel==2'd3),
pic_flag3=(xpos >= block_x) && (xpos < block_x + BLOCK_W)
&& (ypos >= block_y) && (ypos < block_y + BLOCK_W)&&(pic_sel==2'd2);
assign rom_addr1=pic1_x+128*pic1_y,
rom_addr2=pic2_x+128*pic2_y,
rom_addr3=pic3_x+128*pic3_y ;
assign pic1_x=y1,pic1_y='d127-x1,
pic2_x='d127-x2,
pic2_y='d127-y2,
pic3_x='d127-y3,pic3_y=x3;
blk_mem_gen_0 blk_mem_gen_0 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr), // input wire [13 : 0] addra
.douta (rom_rd_data) // output wire [23 : 0] douta
);
blk_mem_gen_0 blk_mem_gen_1 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr1), // input wire [13 : 0] addra
.douta (rom_rd_data1) // output wire [23 : 0] douta
);
blk_mem_gen_0 blk_mem_gen_2 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr2), // input wire [13 : 0] addra
.douta (rom_rd_data2) // output wire [23 : 0] douta
);
blk_mem_gen_0 blk_mem_gen_3 (
.clka (pclk), // input wire clka
.ena (1), // input wire ena
.addra (rom_addr3), // input wire [13 : 0] addra
.douta (rom_rd_data3) // output wire [23 : 0] douta
);
always @(posedge pclk or negedge rstn) begin
if(!rstn)
rom_addr<='d0;
else if(pic_flag)
rom_addr<=rom_addr+'d1;
else if(rom_addr==14'd16383)
rom_addr<='d0;
end
always @(posedge pclk or negedge rstn) begin
if(!rstn)
begin
x1<='d0;
y1<='d0;
x2<='d0;
y2<='d0;
x3<='d0;
y3<='d0;
end
else begin
if(move_en) begin
if(h_dir==2'd0&&x2!='d127)
x2<=x2+'d1;
else ;
if(v_dir==2'd1&&y1!='d0)
y1<=y1-'d1;
else;
if(v_dir==2'd0&&y3!='d127)
y3<=y3+'d1;
else ; end
if(pic_flag1)
begin
if(move_en&&y1>0)
y1<=y1-'d1;
if(x1!='d127)
x1<=x1+'d1;
else begin
x1='d0;
if(y1<127)
y1<=y1+'d1;
else y1<='d0;
end
end
else if(pic_flag2)
begin
if(x2!='d127)
x2<=x2+'d1;
else begin
x2<='d0;
if(y2<127)
y2<=y2+'d1;
else y2<='d0;
end
end
else if(pic_flag3)
begin
if(x3!='d127)
x3<=x3+'d1;
else begin
x3<='d0;
if(y3<127)
y3<=y3+'d1;
else y3<='d0;
end
end
else ;
end end
always @(posedge pclk ) begin//div_cnt
if (!rstn)
div_cnt <= 22'd0;
else begin
if(div_cnt < 22'd742500)
div_cnt <= div_cnt + 1'b1;
else
div_cnt <= 22'd0; //计数达10ms后清零
end
end
always @(posedge pclk ) begin //h_dir,v_dir
if (!rstn) begin
h_dir <= 2'd2;
v_dir <= 2'd1;
pic_sel<='d0;
end
else begin
if(x_edge0)
begin
if(y_edge0)
begin
h_dir <= 2'd2;
v_dir <= 1'b1;
pic_sel<='d0;
end
else if(y_edge1)
begin
h_dir <= 2'd1;
v_dir <= 2'd2;
pic_sel<='d1;
end
else ;
end
else if(x_edge1)
begin
if(y_edge0)
begin
h_dir <= 2'd0;
v_dir <= 2'd2;
pic_sel<='d3;
end
else if(y_edge1)
begin
h_dir <= 2'd2;
v_dir <= 2'd0;
pic_sel<='d2;
end
else ;
end
end
end
always @(posedge pclk ) begin //block x,y
if (!rstn) begin
block_x <= SIDE_W; //方块初始位置横坐标
block_y <= SIDE_W; //方块初始位置纵坐标
end
else if(move_en) begin
if(h_dir==2'd1)
block_x <= block_x + 1'b1; //方块向右移动
else
if(h_dir==2'd0)
block_x <= block_x - 1'b1; //方块向左移动
else if(h_dir==2'd2)
block_x <= block_x;
else ;
if(v_dir==2'd1)
block_y <= block_y + 1'b1; //方块向右移动
else
if(v_dir==2'd0)
block_y <= block_y - 1'b1; //方块向左移动
else if(v_dir==2'd2)
block_y <= block_y;
else ;
end
else begin
block_x <= block_x;
block_y <= block_y;
end
end
always @(posedge pclk ) begin
if (!rstn)
display_data <= WHITE;
else begin
if((xpos < SIDE_W) || (xpos >= H_DISP - SIDE_W)
|| (ypos < SIDE_W) || (ypos >= V_DISP - SIDE_W))
display_data <= RED;
else
if(pic_flag)
display_data <= rom_rd_data;
else if(pic_flag1)
display_data<= rom_rd_data1;
else
if(pic_flag2)
display_data<= rom_rd_data2;
else
if(pic_flag3)
display_data<= rom_rd_data3;
else display_data <= WHITE; //绘制背景为白色
end
end
endmodule
基本思路与方块移动一致,但是有一个难点需要注意就是因为显示模块一直在移动所以显示的图片内部可能在滑动,考虑到动画是一帧一帧的我们只需要在进入下一帧的时候调整坐标就能正常显示:
hdmi小人动画