在实际应用中,一般选择外置存储器作为帧缓存,而不是选择片内的 BRAM,这是由于 ZYNQ 片内的BRAM 存储容量非常小。我们知道 ZYNQ7010 的 BRAM 存储容量为 2.1Mbit,ZYNQ7020 的为 4.9Mbit,假设 RGB LCD 屏的分辨率为 800480,那么存储一帧图像需要的存储容量为(如:RGB888 数据格式=24 位)800480*24= 9216000bit≈8.79Mbit,整个 BRAM 的存储容量不足以存储单帧图像,更何况有时需要更多个显存的情况,因此使用外置存储器作为帧缓存。
要想使用 VDMA 的多帧缓存功能缓冲图像数据,还需要启动 VDMA 的同步锁相模式,只有启动 VDMA的同步锁相模式,VDMA 的多帧缓存功能才能正常运行,否则 VDMA 多帧缓存功能也不能彻底解决多帧图像数据叠加到一起的情况。
同步锁相 (Genlock)
VDMA 支持四种同步锁相模式,分别是:
? Genlock Master(同步锁相主模式)
? Genlock Slave(同步锁相从模式)
? Dynamic Genlock Master(动态同步锁相主模式)
? Dynamic Genlock Slave(动态同步锁相从模式)
AXI Stream Video信号 不同于普通的AXI Stream 信号
它有 DATA VALID READY SOF EOL
SOF 信号 表示一帧的开始 EOL 表示 一帧的结束
在此之前介绍一下 VDMA
在block design 中点击 connect 之后会产生一个 额外的 Process System Reset
在 Vivado 中,Processor System Reset IP 是一个用于处理处理器系统复位的 IP 核。它的主要作用是提供一个可编程的复位信号,用于将处理器系统(例如,MicroBlaze 或 ARM 处理器)和相关的外设进行复位操作。
Processor System Reset IP 允许你在 FPGA 设计中实现对整个处理器系统的控制。一些主要的功能和作用包括:
1.复位控制:
允许你通过设置寄存器的值来控制何时、如何以及在何种条件下进行复位操作。
2.外设复位:
不仅仅是处理器本身,还可以通过该 IP 核对连接的外设进行复位。这有助于确保系统在重新启动时处于已知状态。
3.灵活性:
具有可编程的参数,例如复位持续时间、复位后的初始状态等,使得你可以根据设计需求调整处理器系统的复位行为。
4.中断:
支持将复位事件与中断相关联,以便在系统复位时触发相应的中断处理程序。
5.可编程触发条件:
允许你基于外部条件(例如输入引脚的状态)来触发复位。
在 FPGA 设计中,Processor System Reset IP 是确保处理器系统可控状态的关键组件,特别是在设计中需要在运行时对处理器系统进行复位或重新配置的情况下。
连接完成这个模块之后 我们会发现其实还留有输出的一个模块没有接上去 对于这样的video_out 的 IP核 我们可以通过 xillinx自己提供的指定模块来实现
我们在add IP 一栏搜索加上 video_out IP 核
下面展示 video_out 这个 IP核 是如何在 VTC的帮助下 实现
在 Vivado 中,VTC(Video Timing Controller)和 Video Out IP 核通常配合使用以生成正确的视频时序和输出图像。以下是它们的一般配合方式:
1.VTC(Video Timing Controller):
这种配合方式确保了图像的正确显示,VTC 提供了正确的时序信息,而 Video Out IP 核负责图像的处理和输出。在 Vivado 中,你可以使用图形界面配置这些 IP 核,并使用连接功能将它们连接在一起。
我们创建一个zynq核
我们对于zynq核进行修改
我们修改DDR
使能uart
再打开数据传输的HP口
完成之后点击run block automation
然后是完成之后的结构如下所示
注意 这样的zynq我们并没有修改其中的PL端的时钟是50Mhz
添加 VDMA的IP 进行相关配置
因为我们这样的设计 仅仅是传输数据仅仅是read 所以上述的 可以修改一下去掉write端
采用一帧缓存
(图片写错了 stream data_width 是 24 因为 RGB是 888 正好 24)
后续保持默认
再点击run connect 让其完成自动连接
现在配置
驱动 vdma 的 video out 端口和 video time control 端口
video out是一种stream流转 video 读写口的 IP
我们如此配置
接下来配置 video time control IP核
因为我们想要输入一个固定的数值 所以直接设置
因为想要匹配的是720P的分辨率这里直接省事照用了
接下来配置 PLL 来产生720P所需的频率参数
74.25Mhz 和 371.25Mhz
再添加一整个时钟复位系统
这是我们目前的 block design
先连接clock
reset连接
vtg_ce 端口驱动VTC
两根大数据流的连接
现在添加我们自己 上一个实验写的HDMI 转接代码
我没有封装成IP 独立显示 比较的具体
附上代码
module dvi_transmitter_top(
input pclk ,
input sys_rst_n ,
input pclk_x5 ,
input video_hsync ,
input video_vsync ,
input video_de ,
input [23 : 0] video_din ,
output tmds_clk_p ,
output tmds_clk_n ,
output [2 : 0] tmds_data_p ,
output [2 : 0] tmds_data_n ,
output tmds_oen
);
assign tmds_oen = 1 ;
// next is define
wire reset ;
wire [9:0] blue_10bit ;
wire [9:0] green_10bit ;
wire [9:0] red_10bit ;
wire [2:0] tmds_data_serial ;
wire tmds_clk_serial ;
reset_syn u_reset_syn(
.pclk ( pclk ),
.reset_n ( sys_rst_n ),
.reset ( reset )
);
dvi_encoder u_dvi_encoder_blue(
.clkin ( pclk ),
.rstin ( reset ),
.din ( video_din[7:0] ),
.c0 ( video_hsync ),
.c1 ( video_vsync ),
.de ( video_de ),
.dout ( blue_10bit )
);
dvi_encoder u_dvi_encoder_green(
.clkin ( pclk ),
.rstin ( reset ),
.din ( video_din[15:8] ),
.c0 ( 1'b0 ),
.c1 ( 1'b0 ),
.de ( video_de ),
.dout ( green_10bit )
);
dvi_encoder u_dvi_encoder_red(
.clkin ( pclk ),
.rstin ( reset ),
.din ( video_din[23:16] ),
.c0 ( 1'b0 ),
.c1 ( 1'b0 ),
.de ( video_de ),
.dout ( red_10bit )
);
serializer10 u_serializer10_blue(
.reset ( reset ),
.paralell_clk ( pclk ),
.serial_clk_5x ( pclk_x5 ),
.paralell_data ( blue_10bit ),
.serial_data_out ( tmds_data_serial[0] )
);
serializer10 u_serializer10_green(
.reset ( reset ),
.paralell_clk ( pclk ),
.serial_clk_5x ( pclk_x5 ),
.paralell_data ( green_10bit ),
.serial_data_out ( tmds_data_serial[1] )
);
serializer10 u_serializer10_red(
.reset ( reset ),
.paralell_clk ( pclk ),
.serial_clk_5x ( pclk_x5 ),
.paralell_data ( red_10bit ),
.serial_data_out ( tmds_data_serial[2] )
);
serializer10 u_serializer10_clk(
.reset ( reset ),
.paralell_clk ( pclk ),
.serial_clk_5x ( pclk_x5 ),
.paralell_data ( 10'b1111100000 ),
.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
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
module reset_syn(
input pclk ,
input reset_n ,
output reg reset
);
reg reset1 ;
always@( posedge pclk or negedge reset_n)
begin
if( reset_n == 0)
begin
reset1 <= 1 ;
end
else
begin
reset1 <= 0 ;
reset <= reset1 ;
end
end
endmodule
module serializer10 (
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 ;
// 此处的代码 来自 vivado的 原语 和 正点原子的同时调配
// 这是 master接口
OSERDESE2 #(
.DATA_RATE_OQ("DDR"), // 设置双倍数据速率
.DATA_RATE_TQ("DDR"), // DDR, BUF, SDR
.DATA_WIDTH(10), // 输入的并行数据宽度为 10bit
// .INIT_OQ(1'b0), // Initial value of OQ output (1'b0,1'b1)
// .INIT_TQ(1'b0), // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE("MASTER"), // MASTER, SLAVE
//.SRVAL_OQ(1'b0), // OQ output value when SR is used (1'b0,1'b1)
// .SRVAL_TQ(1'b0), // TQ output value when SR is used (1'b0,1'b1)
.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 (
.OFB(), // 未使用
.OQ(serial_data_out), // 串行输出数据
// SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
.SHIFTOUT1(), // SHIFTIN1 用于位宽扩展
.SHIFTOUT2(), // SHIFTIN2 用于位宽扩展
.TBYTEOUT(), // 未使用
.TFB(), // 未使用
.TQ(), // 未使用
.CLK(serial_clk_5x), // 串行数据时钟,5 倍时钟频率
.CLKDIV(paralell_clk), // 并行数据时钟
// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
.D1(paralell_data[0]),
.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]),
.OCE(1'b1), // 1-bit input: Output data clock enable
.RST(reset), // 1-bit input: Reset
// SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
.SHIFTIN1(cascade1), // SHIFTIN1 用于位宽扩展
.SHIFTIN2(cascade2), // SHIFTIN2 用于位宽扩展
// T1 - T4: 1-bit (each) input: Parallel 3-state inputs
.T1(1'b0), // 未使用
.T2(1'b0), // 未使用
.T3(1'b0), // 未使用
.T4(1'b0), // 未使用
.TBYTEIN(1'b0), // 未使用
.TCE(1'b0) // 未使用
);
// slave接口
OSERDESE2 #(
.DATA_RATE_OQ("DDR"), // 设置双倍数据速率
.DATA_RATE_TQ("DDR"), // DDR, BUF, SDR
.DATA_WIDTH(10), // 输入的并行数据宽度为 10bit
// .INIT_OQ(1'b0), // Initial value of OQ output (1'b0,1'b1)
// .INIT_TQ(1'b0), // Initial value of TQ output (1'b0,1'b1)
.SERDES_MODE("SLAVE"), // MASTER, SLAVE
//.SRVAL_OQ(1'b0), // OQ output value when SR is used (1'b0,1'b1)
// .SRVAL_TQ(1'b0), // TQ output value when SR is used (1'b0,1'b1)
.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 (
.OFB(), // 未使用
.OQ(), // 串行输出数据
// SHIFTOUT1 / SHIFTOUT2: 1-bit (each) output: Data output expansion (1-bit each)
.SHIFTOUT1(cascade1), // SHIFTIN1 用于位宽扩展
.SHIFTOUT2(cascade2), // SHIFTIN2 用于位宽扩展
.TBYTEOUT(), // 未使用
.TFB(), // 未使用
.TQ(), // 未使用
.CLK(serial_clk_5x), // 串行数据时钟,5 倍时钟频率
.CLKDIV(paralell_clk), // 并行数据时钟
// D1 - D8: 1-bit (each) input: Parallel data inputs (1-bit each)
.D1(1'b0),
.D2(1'b0),
.D3(paralell_data[8]),
.D4(paralell_data[9]),
.D5(1'b0),
.D6(1'b0),
.D7(1'b0),
.D8(1'b0),
.OCE(1'b1), // 1-bit input: Output data clock enable
.RST(reset), // 1-bit input: Reset
// SHIFTIN1 / SHIFTIN2: 1-bit (each) input: Data input expansion (1-bit each)
.SHIFTIN1(), // SHIFTIN1 用于位宽扩展
.SHIFTIN2(), // SHIFTIN2 用于位宽扩展
// T1 - T4: 1-bit (each) input: Parallel 3-state inputs
.T1(1'b0), // 未使用
.T2(1'b0), // 未使用
.T3(1'b0), // 未使用
.T4(1'b0), // 未使用
.TBYTEIN(1'b0), // 未使用
.TCE(1'b0) // 未使用
);
endmodule
第一个pclk 连接到 74.25M 的端口 另一个连接至371.25Mhz 的端口
reset_n 连接至 clock_wizard 的 locked端口
其他的照旧连接
剩下的 我们单独引出管脚
接下来我们验证一下 连接
没问题
我们创建一个 XDC的管脚约束
#HDMI
set_property -dict {PACKAGE_PIN J20 IOSTANDARD TMDS_33 } [get_ports {tmds_data_p[2]}]
set_property -dict {PACKAGE_PIN K19 IOSTANDARD TMDS_33 } [get_ports {tmds_data_p[1]}]
set_property -dict {PACKAGE_PIN G19 IOSTANDARD TMDS_33 } [get_ports {tmds_data_p[0]}]
set_property -dict {PACKAGE_PIN J18 IOSTANDARD TMDS_33 } [get_ports tmds_clk_p]
这是总的blockdesign
接下来 创建顶层 generate output
请注意把 system_wrapper设置为顶层
然后再生成比特流
再打开 vitis
我们创建一个 plantform 和 application
我们从BSP中获取 xilinx官方提供的VDMA驱动代码
点击
我们将 vdma_api.c 复制进入我们的src文件中
再创建一个 main 和 vdma_api.h文件
所以整个src的文件包应该包括
main.c vdma_api.c vdma_api.h
下面附上 main.c 代码
#include"stdio.h"
#include"xparameters.h"
#include "xaxivdma.h"
#include "vdma_api.h"
#include "xil_cache.h"
#define VDMA_ID XPAR_AXI_VDMA_0_DEVICE_ID
#define WIDTH 1280 // 图像宽度
#define HEIGHT 720 // 图像的高度
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR
// VDMA帧缓存的地址
int frame_buffer_addr = DDR_BASE_ADDR + 0x1000000 ;
int main()
{
Xil_DCacheDisable();
XAxiVdma vdma_inst; // 这是例化的驱动实例
int i , j ;
u8* vdma_buffer_addr = (u8*)frame_buffer_addr ;
// 往VDMA的帧缓存写入图案
for(j=0 ; j<HEIGHT ; j++ ){
for(i = 0 ; i<WIDTH ; i++){
(vdma_buffer_addr +jWIDTH3 + i3 + 0) = 0x00 ; // 往像素蓝色通道写入00
(vdma_buffer_addr +jWIDTH3 + i3 + 1) = 0x00 ; // 往像素绿色通道写入00
(vdma_buffer_addr +jWIDTH3 + i3 + 2) = 0xff ; // 往像素红色通道写入ff
}
}
// 配置并启动VDMA
run_triple_frame_buffer(
&vdma_inst, // VDMA 驱动实例
VDMA_ID, // VDMA 器件ID
WIDTH, // 图像宽度
HEIGHT, // 图像高度
frame_buffer_addr, // VDMA帧缓存的起始地址
0,
0);
return 0 ;
}
下面附上 vdma_api.h代码
#include "xaxivdma.h"
#include "xparameters.h"
#include "xil_exception.h"
int run_triple_frame_buffer(XAxiVdma* InstancePtr, int DeviceId, int hsize,
int vsize, int buf_base_addr, int number_frame_count,
int enable_frm_cnt_intr) ;
最后 build project
再点击烧录
实验结果