前言
本文在前文的基础上介绍如何实现四分屏与全屏切换。切换效果点击观看:视频切换显示
硬件平台:anPH1A 开发板链接
软件版本:TD6.0
本工程所有的代码均已上传至Github,开源工程链接,可以把bit流文件直接烧录至anPH1A开发板进行验证。还请大家给点点star喽~ 如果有不理解的地方,欢迎评论区讨论~ 有写错的地方也请大家评论区批评指正~
一、设计概述
除去DDR3控制器、FIFO等常用IP,本设计所用到的主要的IP模块如下。
IP | 功能 | 来源 |
---|---|---|
uidbuf | 基于FDMA信号时序的缓存控制器 适合用于基于RGB时序的视频数据或者数据流传输 | 米联客 |
uiFDMA | 基于AXI总线的自定义内存控制器 简化AXI总线的控制,完成数据的搬运 | 米联客 |
uidbuf_r_baseaddr_switch | 在uidbuf的基础上进行更改 用于全屏切换时选择不同通路的视频源进行读取 | 个人开发 |
uidbufw_interconnect | 基于FDMA信号时序的多路数据写入DDR3仲裁模块 不必使用AXI interconnectIP核,减少逻辑资源的使用 | 个人开发 |
uidbufr_interconnect | 基于FDMA信号时序的多路数据读出DDR3仲裁模块 不必使用AXI interconnectIP核,减少逻辑资源的使用 | 个人开发 |
二、系统实现方案
2.1 理论分析
四分屏显示与全屏显示是互斥的一个状态,为了减少DDR3的吞吐量,在进行四分屏显示时,对全屏显示模块进行复位。同理,在进行全屏显示时,对四分屏显示模块进行复位。此外,全屏显示时,在同一时刻也需要对一路视频源进行存储和显示,其他的三路视频源也不必写入DDR3,减少吞吐量。
全屏显示模块的设计思路为:为每一路视频源划分指定大小的存储空间,当需要对第一路视频进行全屏显示时,就去读取第一路视频存储的位置,当需要对第二路视频进行全屏显示时,就去读取第二路视频存储的位置。
2.2 全屏显示系统框图
在读取的时候通过uidbuf_r_baseaddr_switch控制读取的地址即可
2.3 uidbuf_r_baseaddr_switch模块
控制读取的基地址改变,在每次读取完一帧数据后,判断是否需要更改读取的基地址,I_state_ddr_clk信号由外部传入。
reg [AXI_ADDR_WIDTH-1'b1: 0]R_BASEADDR;
localparam VIDEO_0_BASEADDR = 1843200;
localparam VIDEO_1_BASEADDR = 3686400;
localparam VIDEO_2_BASEADDR = 5529600;
localparam VIDEO_3_BASEADDR = 7372800;
//根据I_state_ddr_clk来进行地址的切换
//何时切换?在一帧视频读完后进行切换,保证每一帧画面的完整性
localparam VIDEO_0 = 3'd0;
localparam VIDEO_1 = 3'd1;
localparam VIDEO_2 = 3'd2;
localparam VIDEO_3 = 3'd3;
always @(posedge I_ui_clk or negedge I_ui_rstn) begin
if (~I_ui_rstn) begin
R_BASEADDR <= VIDEO_0_BASEADDR;
end else if (R_FS) begin
case (I_state_ddr_clk)
VIDEO_0:begin
R_BASEADDR <= VIDEO_0_BASEADDR;
end
VIDEO_1:begin
R_BASEADDR <= VIDEO_1_BASEADDR;
end
VIDEO_2:begin
R_BASEADDR <= VIDEO_2_BASEADDR;
end
VIDEO_3:begin
R_BASEADDR <= VIDEO_3_BASEADDR;
end
default: begin
R_BASEADDR <= VIDEO_0_BASEADDR;
end
endcase
end
end
2.4 跨时钟域处理
此工程是通过接收串口数据,进行命令解析,发送控制信号进行分屏全屏切换。下面为输入至全屏显示模块的控制信号,发送域的时钟为25Mhz,接收域的时钟为DDR IP用户侧时钟(533/4 Mhz)。
从慢时钟域到快时钟域,单bit信号直接打两拍处理即可。多bit信号,在此模块的处理方式为采用独热码+状态机的思路进行处理。
//uart ctrl signal
input wire [3:0] I_screen_switch, //该信号用于选择哪个屏幕进行显示,跨时钟域信号(多bit,慢时钟到快时钟)采用独热码与状态机处理
input wire I_full_rstn, //该模块的复位信号,在分屏显示时,对该模块进行复位(单bit,慢时钟到快时钟)采用打两拍进行处理
多bit信号在进行跨时钟域处理的时候可能会造成同步时序,如下图所示。正常情况下输入序列 00->11,但是因为延迟问题,输入产生了 10 的组合,从而导致接收端解码器输出了一个错误值。
解决方法:独热码+接收端状态机的解决思路进行处理。如下图所示
输入端bdec[1:0]共有四种组合状态,在进行跨时钟域传输前,即发送的时候在bclk时钟域用4位独热码即可对应四种不同的状态,接收端打两拍之后,使用状态机进行处理,假设从状态1110跳转至状态1011,在aclk时钟域进行接收的时候,可能会接收到1111、1010、1011三种状态。其中1010和1011均可以使状态机跳转至正确的状态,1111保持在当前状态。通过接收端的状态机处理,使的电路处于稳定可控的状态。(这里是使用独热码的反码进行处理的)
参考链接:跨时钟域/CDC设计方法总结
2.5 跨时钟域处理代码
(这一块如有错误,还请指正)
//---------跨时钟域信号处理-----------
//发送端的信号I_screen_switch对应的功能解释
//I_screen_switch=4'b0001,video 0全屏
//I_screen_switch=4'b0010,video 1全屏
//I_screen_switch=4'b0100,video 2全屏
//I_screen_switch=4'b1000,video 3全屏
//先对输入至接收端信号在接收时钟为ddr_ip_clk情况下打两拍处理,此情况可能会导致同步失序
reg [3:0] screen_switch_dly1_ddr_clk;
reg [3:0] screen_switch_dly2_ddr_clk;
always @(posedge O_axi_clk or negedge pll_locked) begin
if (~pll_locked) begin
screen_switch_dly1_ddr_clk <= 'd0;
screen_switch_dly2_ddr_clk <= 'd0;
end else begin
screen_switch_dly1_ddr_clk <= I_screen_switch;
screen_switch_dly2_ddr_clk <= screen_switch_dly1_ddr_clk;
end
end
//接收端状态
//这个状态的状态转移取决于独热码里“热”的那一位,即 1 的位置。
localparam VIDEO_0 = 3'd0;
localparam VIDEO_1 = 3'd1;
localparam VIDEO_2 = 3'd2;
localparam VIDEO_3 = 3'd3;
//接收端状态机,接收时钟域为ddr_clk
reg [2:0] state_ddr_clk;
always @(posedge O_axi_clk or negedge pll_locked) begin
if (~pll_locked) begin
state_ddr_clk <= VIDEO_0; //默认VIDEO_0全屏
end else begin
case (state_ddr_clk)
VIDEO_0:begin
if (screen_switch_dly2_ddr_clk[3] == 1'b1) begin
state_ddr_clk <= VIDEO_3;
end else if (screen_switch_dly2_ddr_clk[2] == 1'b1) begin
state_ddr_clk <= VIDEO_2;
end else if (screen_switch_dly2_ddr_clk[1] == 1'b1) begin
state_ddr_clk <= VIDEO_1;
end else begin
state_ddr_clk <= VIDEO_0;
end
end
VIDEO_1:begin
if (screen_switch_dly2_ddr_clk[3] == 1'b1) begin
state_ddr_clk <= VIDEO_3;
end else if (screen_switch_dly2_ddr_clk[2] == 1'b1) begin
state_ddr_clk <= VIDEO_2;
end else if (screen_switch_dly2_ddr_clk[0] == 1'b1) begin
state_ddr_clk <= VIDEO_0;
end else begin
state_ddr_clk <= VIDEO_1;
end
end
VIDEO_2:begin
if (screen_switch_dly2_ddr_clk[3] == 1'b1) begin
state_ddr_clk <= VIDEO_3;
end else if (screen_switch_dly2_ddr_clk[1] == 1'b1) begin
state_ddr_clk <= VIDEO_1;
end else if (screen_switch_dly2_ddr_clk[0] == 1'b1) begin
state_ddr_clk <= VIDEO_0;
end else begin
state_ddr_clk <= VIDEO_2;
end
end
VIDEO_3:begin
if (screen_switch_dly2_ddr_clk[2] == 1'b1) begin
state_ddr_clk <= VIDEO_2;
end else if (screen_switch_dly2_ddr_clk[1] == 1'b1) begin
state_ddr_clk <= VIDEO_1;
end else if (screen_switch_dly2_ddr_clk[0] == 1'b1) begin
state_ddr_clk <= VIDEO_0;
end else begin
state_ddr_clk <= VIDEO_3;
end
end
default: begin
state_ddr_clk <= VIDEO_0;
end
endcase
end
end
三、移植注意事项
1、 在本工程中,为了减少ERAM资源的使用,我们将图像像素点在DDR3中的存储和读取格式设置为RGB565格式,其中每个像素点占用16bit,并将DDR3的数据位宽设置为16bit。由于DDR3的突发传输长度固定为8,每次突发传输8次16bit数据,即总共传输128bit。因此,在进行DDR3存取时,FIFO的读写位宽可以设置为16bit和128bit。与此不同,若将图像像素点的存储格式设置为RGB888格式,每个像素点将占用24bit,并将DDR3的数据位宽设置为32bit。在这种情况下,进行存取时,FIFO的读写位宽将为32bit和256bit,这样会导致ERAM资源消耗的加倍。
2、本工程四路视频源输入的视频分辨率为1280×720
3、使用串口命令发送16进制 00 视频不移动,发送16进制01 视频开始移动。10 分屏显示 11 Video0全屏 12 video1全屏 13 video2全屏 14 video3全屏,波特率为115200。
四、上板验证
四路视频数据是由FPGA内部产生的测试数据,分屏显示与全屏切换效果图如下。