|
本帖最后由 正点原子 于 2023-4-18 10:06 编辑
1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016
第十六章DP彩条显示实验
DP接口即DisplayPort接口,DisplayPort是由视频电子标准协会(VESA)发布的显示接口。作为DVI的继任者,DisplayPort将在传输视频信号的同时加入对高清音频信号传输的支持,同时支持更高的分辨率和刷新率。它能够支持单通道、单向、四线路连接,数据传输率10.8Gbps,足以传送未经压缩的视频和相关音频,同时还支持1Mbps的双向辅助通道,供设备控制之用,此外还支持8位和10位颜色。在数据传输上,DisplayPort使用了“micro-packetised”格式。VESA还表示,DisplayPort具备高度的可扩展性,可以在今后不断加入更多新内容。本节实验将带领大家一起去学习使用DP接口实现彩条显示的实验。
本章包括以下几个部分:
16.1简介
16.2实验任务
16.3硬件设计
16.4软件设计
16.5下载验证
16.1简介
DP接口即DisplayPort接口,DisplayPort是由视频电子标准协会(VESA)发布的显示接口。它不仅可以支持全高清显示分辨率(1920×1080),还能支持4k分辨率(3840×2160),以及最新的8k分辨率(7680×4320)。DP接口不仅传输率高,而且可靠稳定,其接口传输的信号由传输图像的数据通道信号以及传输图像相关的状态、控制信息的辅助通道信号组成,具体包含DisplayPort数据传输主要通道(Main Link)、辅助通道(AUX Channel)与连接(Link Training)。
DP接口示意图如下所示:
图 16.1.1 DP接口示意图
从上图中可以看出DP接口由主链路(Main link)、辅助通道(AUC_CH)以及热插拔检测(HPD)信号线组成。其中主链路是单向的、高带宽、低延迟的信道,用于传输同步数据流,如未压缩的视频和音频;辅助通道是半双工,双向通道,用于链路管理和设备控制;热插拔检测(HPD)信号线可以作热插拔检测信号也可以作为接收设备的中断请求。
主链路由四对交流耦合差分信号通道组成(支持只是用一对通信、2对通信或者4对通信三种模式),单个通道的传输速率有1.62Gbps、2.7Gbps、5.4Gbps以及8.1Gbps四种,多个通道同时工作的情况下,所有通道必须使用同一种传输速率。信道的传输速率与像素传输速率的解耦有关,信道的数量与像素位深度(每像素位或bpp)和组件位深度(每组件位或bpc)的解耦有关。在DisplayPort中,无论主链路的个数如何,对于像素编码格式为RGB、YCbCr 4:4:4 / 4:2:2 / 4:2:0 Y-only的像素数据,支持6,8,10,12,16的位深度;而对于RAW像素编码格式得数据,支持像素6、7、8、10、12、14和16位的位深度。
这里还要提醒大家一点DP接口协议中是没有单独得像素时钟通道的,时钟是从ANSI 8b/10b编码规则编码的数据流中提取的。
在进行DP协议数据传输时可以选择不同的通道数量(1、2或4),并且单个通道的速率也是可选的,下面给大家列出一张表展示不同通道速率不同通道模式下DP传输的带宽,如下表所示:
图 16.1.2 DP Main-Link Application Bandwidth
看完了DP的主链路,我们再来看辅助通道(AUC_CH),AUX_CH采用交流耦合差分传输方式,是一条双向半双工传输通道,单一方向速率仅1Mbit/s左右,用来传输设定与控制指令。在AUX CH上使用Manchester-II编码作为AUX交易的信道编码,与Main-Link的情况一样,从数据流中提取时钟。AUX CH有一个半双工,双向PHY层。Source设备是主设备,Sink设备是从设备。Sink设备可以通过切换HPD信号来提示Source设备发起一个AUX请求事务来读取DPCD Link/Sink状态寄存器位,包括IRQ_HPD向量寄存器位。AUX(Auxiliary)的用途包括读取扩展显示识别数据(EDID),以确保DP信号的正确传输;读取显示器所支持的DP接口的信息(如主要通道的数量和DP信号的传输速率;进行各种显示组态暂存器的设定);读取显示器状态暂存器等等。因此,只有先保证AUX的信号正确才能使DP接口的主链路信号正确传输。
而HPD通道就比较简单了,这个通道是由接收端发出的单向中断信号。当接收端设备拉低HPD信号,信号脉冲宽度在0.25ms~2ms之间,此时发送端会在HPD信号上升沿后的100ms以内重新读取接收端设备信息。如果HPD信号拉低时间超过2ms(例如DP数据线被拔掉了,断开连接),发送设备就会停止发送数据,等待接收设备重新连接上。
下面我们来具体了解一下DP接口每个引脚的定义,DP接口图如下所示:
图 16.1.3 DP接口
上图所示的左边大的是正常的DisplayPort接口,而右边小的是Mini DisplayPort接口。Mini DisplayPort是一个微型版本的DisplayPort,由苹果公司于2008年10月14日发表。DP接头的尺寸约为16x4.8毫米,mini DP接头尺寸约为其一半。DP2.0最高支持8K 60Hz,mini DP最高仅支持4K 60Hz。DP版本仍在更新,mini DP一直停留在1.2版本,并不再更新。我们MPSOC开发板板载的就是Mini DisplayPort接口。在引脚上无论Mini DP接口还是普通DP接口都采用了20针引脚设计,如下图所示:
图 16.1.4 DP接口引脚图
每个引脚的功能如下表所示:
表 16.1.1 DP接口引脚定义
到这里关于DP接口就给大家介绍完了,关于更加详细的DP标准介绍大家可以去查看官方的DP协议标准文档。
16.2实验任务
本节实验将使用MPSOC开发板实现DP接口彩条显示实验,如果大家没有DP显示器也不要紧,可以使用Mini DP转HDMI线实现DP接口数据在HDMI显示屏上显示。Mini DP转HDMI线可以在正点原子店铺购买,大家切记不同厂家的转接线存在一定差异,其他渠道的转接线可能无法正常显示。
16.3硬件设计
MPSOC开发板板载一个Mini DP接口,其原理图如下所示:
图 16.3.1 Mini DP接口原理图
从上图中可以看到MPSOC开发板板载的Mini DP接口采用的是2通道模式,且差分引脚连接到了BANK505;辅助通道和热插拔引脚连接到BANK501的MIO27~MIO30,如下图所示:
图 16.3.2 MIO分配
知道了DP接口的实际物理硬件连接后我们就可以到Vivado中去搭建嵌入式工程了,本节实验我们将调用MPSOC内部的DP硬核资源实现DP彩条显示实验。
第一步我们要创建一个Vivado工程,命名为dp_colorbar,具体的创建步骤在前面的“hello world”实验中有详细的讲解,这里不再重复赘述。
第二步在创建好的工程里搭建嵌入式硬件内核,点击Flow Navigator栏的Create Block Design选项,然后添加ZYNQ UltraScale内核(具体步骤参考“hello world”实验)。添加完内核接下来我们来配置内核,因为本节实验需要调用DP硬核资源,所以第一步我们配置DP接口,如下图所示:
图 16.3.3 配置DP接口
如上图所示我们先找到I/O Configuration配置选项,然后修改MIO的电压(通过查看原理图),其中MIO0~MIO25使用的电压是1.8V,MIO26~MIO51使用的电压是3.3V,MIO52~MIO77使用的电压还是1.8V。修改完MIO电压后找到High Speed选项将Display Port选项勾上,并且配置好DPAUX接口对应的MIO是27到30,通道选项选择Dual Higher(双通道)。到这里DP接口配置就完成了,接下来开始对时钟进行配置,如下图所示:
图 16.3.4 配置时钟
如上图所示,输入时钟保持默认就好,不用修改,我们需要修改的是输出时钟,如下图所示:
图 16.3.5 时钟配置
对于输出时钟的配置这里给大家解释一下,首先看上图中标序号4(Range)的那一列,这一列代表所配器件支持的时钟范围,只要在这个范围内都是有效的。而标序号2的这一列代表我们想给这个器件配置什么频率的时钟,序号3代表的是实际产生的时钟频率。因此我们只需要遵行一个原则只要时钟范围在Range内,尽可能的通过改变Source选项使得Requested Freq与Actual Frequency的值相等或者非常接近就可以了。序号5代表我们配置DP相关的时钟。
配置完输出时钟最后我们再来配置DDR(这里注意DDR可以不用配置,因为本节实验不需要用到它,之所以在这里配置是方便之后的工程去使用这个硬件平台),如下图所示:
图 16.3.6 DDR配置
从上图可以看到对于DDR的配置我们只需要关注四个选项即图中标号为2、3、4、5的四个选项即可。其中Load DDR Presets选项是用来选择DDR的型号,MPSOC开发板板载的DDR4型号为MT40A256M16GE,所以这里选择MT40A256M16GE;然后Effective DRAM Bus Width选择有效数据位宽,因为我们是4片DDR4连用(单片数据位宽16bit),因此数据位宽是64bit;再然后DRAM Device Capacity选择每片DDR的容量,根据数据手册可知我们板载的DDR4单片容量为4Gb,所以这里选择4096MBits;最后一个要配置的就是行地址位宽,这里同样根据数据手册知道我们板载的DDR4行地址数据位宽是15位的位宽。
现在我们需要调用的资源就已经配置完了,但是在搭建平台的时候有一些资源是默认选中的如用于PS-PL连接的HP接口以及复位信号,如下图所示:
图 16.3.7 PS-PL互联接口
如上图所示我们把Fabric Reset Enable(PS端产生的复位信号给PL端使用)和AXI HPM0 LPD选项给去掉,因为本节实验只用到了PS部分,没用到PL部分。
同理我们也可以把PL端的时钟信号也去掉,如下图所示:
图 16.3.8 PL端时钟信号
将PL端的时钟信号也去掉后我们的硬件平台就搭建好了,就是一个光秃秃的ZYNQ硬核,(注意,本次配置没有添加串口控制器UART0,需要的话,读者也可以添加)如下图所示:
图 16.3.9 DP显示硬件平台
到这里整个硬件平台就设置完了,接下来我们就可以进入软件代码的编写了。
16.4软件设计
在进入软件平台编写前需要先将之前搭建好的硬件平台导出,导出硬件后我们打开Vitis开始创建软件工程,具体步骤大家可以参考前面的“hello world”实验,软件工程创建完成后如下图所示:
图 16.4.1 DP显示软件工程
在Explorer窗口可以看到我们创建好的工程,分为两部分,上半部分(design_1_wrapper_1)是板级支持包,下半部分(design_1_wrapper_system)是系统工程用来编写代码的。接下来我们先来设置板级支持包,如下图所示:
图 16.4.2 设置板级支持包
按照上图所标的序号步骤,打开板级支持包的设置,如下图所示:
图 16.4.3 修改板级支持包
这里只需要将psu_dp选项的驱动改成dppsu就可以了。关于psu_dp驱动的选择Xilinx官方给出了如下的解释:
图 16.4.4 驱动选择介绍
上图中的意思就是当我们要使用这个驱动的示例程序(本节实验就是在官方的示例程序中修改过来的)时我们要选择驱动,总共有4种用法。第一种是数据来源基于内存的模式,继续往后看大家可以知道本节实验的彩条数据是直接在软件C代码中产生的,也就是说数据来源基于Memory。这种模式下psu_dp驱动选择dppsu,psu_dpdma驱动选择dpdma,这也是本节实验所选的模式。第二种模式是数据从PL端过来到DP控制器,在这种模式下psu_dp驱动也是选择dppsu。第三种是数据从PL端到PL端,这种模式下psu_dp驱动选择avbuf。第四种是数据从Memory到PL端,这种模式下psu_dp驱动也是选择avbuf。
当我们设置好板级支持包后就可以打开示例工程了,如下图所示:
图 16.4.5 打开示例工程
如上图所示先回到板级支持包设置界面,找到Peripheral Drivers选项,然后往下浏览找到psu_dpdma选项,选择导入示例工程(Import Examples),如下图所示:
图 16.4.6 导入示例工程
导入过后我们在左边Explorer栏就可以看到如下示例工程:
图 16.4.7 示例工程
在示例工程中xdpdma_video_example.c是主函数文件,通过它来调用其他的库函数来完成对DP控制器的初始化和系统配置,并产生彩条数据,最终实现DP彩条显示的功能。下面我们就一起来分析一下这个主函数文件。其代码如下所示:
1 #include "xil_exception.h"
2 #include "xil_printf.h"
3 #include "xil_cache.h"
4 #include "xdpdma_video_example.h"
5
6 /************************** Constant Definitions *****************************/
7 #define DPPSU_DEVICE_ID XPAR_PSU_DP_DEVICE_ID
8 #define AVBUF_DEVICE_ID XPAR_PSU_DP_DEVICE_ID
9 #define DPDMA_DEVICE_ID XPAR_XDPDMA_0_DEVICE_ID
10 #define DPPSU_INTR_ID 151
11 #define DPDMA_INTR_ID 154
12 #define INTC_DEVICE_ID XPAR_SCUGIC_0_DEVICE_ID
13
14 #define DPPSU_BASEADDR XPAR_PSU_DP_BASEADDR
15 #define AVBUF_BASEADDR XPAR_PSU_DP_BASEADDR
16 #define DPDMA_BASEADDR XPAR_PSU_DPDMA_BASEADDR
17
18 #define BUFFERSIZE 1920 * 1080 * 4 /* HTotal * VTotal * BPP */
19 #define LINESIZE 1920 * 4 /* HTotal * BPP */
20 #define STRIDE LINESIZE /* The stride value should
21 be aligned to 256*/
22
23 /************************** Variable Declarations ***************************/
24 u8 Frame[BUFFERSIZE] __attribute__ ((__aligned__(256)));
25 XDpDma_FrameBuffer FrameBuffer;
26
27 int main()
28 {
29 int Status;
30
31 Xil_DCacheDisable();
32 Xil_ICacheDisable();
33
34 xil_printf("DPDMA Generic Video Example Test \r\n");
35 Status = DpdmaVideoExample(&RunCfg);
36 if (Status != XST_SUCCESS) {
37 xil_printf("DPDMA Video Example Test Failed\r\n");
38 return XST_FAILURE;
39 }
40
41 xil_printf("Successfully ran DPDMA Video Example Test\r\n");
42
43 return XST_SUCCESS;
44 }
45
46 int DpdmaVideoExample(Run_Config *RunCfgPtr)
47
48 {
49 u32 Status;
50 /* Initialize the application configuration */
51 InitRunConfig(RunCfgPtr);
52 Status = InitDpDmaSubsystem(RunCfgPtr);
53 if (Status != XST_SUCCESS) {
54 return XST_FAILURE;
55 }
56
57 xil_printf("Generating Overlay.....\n\r");
58 GraphicsOverlay(Frame, RunCfgPtr);
59
60 /* Populate the FrameBuffer structure with the frame attributes */
61 FrameBuffer.Address = (INTPTR)Frame;
62 FrameBuffer.Stride = STRIDE;
63 FrameBuffer.LineSize = LINESIZE;
64 FrameBuffer.Size = BUFFERSIZE;
65
66 SetupInterrupts(RunCfgPtr);
67
68 return XST_SUCCESS;
69 }
70
71 void InitRunConfig(Run_Config *RunCfgPtr)
72 {
73 /* Initial configuration parameters. */
74 RunCfgPtr->DpPsuPtr = &DpPsu;
75 RunCfgPtr->IntrPtr = &Intr;
76 RunCfgPtr->AVBufPtr = &AVBuf;
77 RunCfgPtr->DpDmaPtr = &DpDma;
78 RunCfgPtr->VideoMode = XVIDC_VM_1920x1080_60_P;
79 RunCfgPtr->Bpc = XVIDC_BPC_8;
80 RunCfgPtr->ColorEncode = XDPPSU_CENC_RGB;
81 RunCfgPtr->UseMaxCfgCaps = 1;
82 RunCfgPtr->LaneCount = LANE_COUNT_2;
83 RunCfgPtr->LinkRate = LINK_RATE_540GBPS;
84 RunCfgPtr->EnSynchClkMode = 0;
85 RunCfgPtr->UseMaxLaneCount = 1;
86 RunCfgPtr->UseMaxLinkRate = 1;
87 }
88
89 int InitDpDmaSubsystem(Run_Config *RunCfgPtr)
90 {
91 u32 Status;
92 XDpPsu *DpPsuPtr = RunCfgPtr->DpPsuPtr;
93 XDpPsu_Config *DpPsuCfgPtr;
94 XAVBuf *AVBufPtr = RunCfgPtr->AVBufPtr;
95 XDpDma_Config *DpDmaCfgPtr;
96 XDpDma *DpDmaPtr = RunCfgPtr->DpDmaPtr;
97
98
99 /* Initialize DisplayPort driver. */
100 DpPsuCfgPtr = XDpPsu_LookupConfig(DPPSU_DEVICE_ID);
101 XDpPsu_CfgInitialize(DpPsuPtr, DpPsuCfgPtr, DpPsuCfgPtr->BaseAddr);
102 /* Initialize Video Pipeline driver */
103 XAVBuf_CfgInitialize(AVBufPtr, DpPsuPtr->Config.BaseAddr, AVBUF_DEVICE_ID);
104
105 /* Initialize the DPDMA driver */
106 DpDmaCfgPtr = XDpDma_LookupConfig(DPDMA_DEVICE_ID);
107 XDpDma_CfgInitialize(DpDmaPtr,DpDmaCfgPtr);
108
109 /* Initialize the DisplayPort TX core. */
110 Status = XDpPsu_InitializeTx(DpPsuPtr);
111 if (Status != XST_SUCCESS) {
112 return XST_FAILURE;
113 }
114 /* Set the format graphics frame for DPDMA*/
115 Status = XDpDma_SetGraphicsFormat(DpDmaPtr, RGBA8888);
116 if (Status != XST_SUCCESS) {
117 return XST_FAILURE;
118 }
119 /* Set the format graphics frame for Video Pipeline*/
120 Status = XAVBuf_SetInputNonLiveGraphicsFormat(AVBufPtr, RGBA8888);
121 if (Status != XST_SUCCESS) {
122 return XST_FAILURE;
123 }
124 /* Set the QOS for Video */
125 XDpDma_SetQOS(RunCfgPtr->DpDmaPtr, 11);
126 /* Enable the Buffers required by Graphics Channel */
127 XAVBuf_EnableGraphicsBuffers(RunCfgPtr->AVBufPtr, 1);
128 /* Set the output Video Format */
129 XAVBuf_SetOutputVideoFormat(AVBufPtr, RGB_8BPC);
130
131 /* Select the Input Video Sources.
132 * Here in this example we are going to demonstrate
133 * graphics overlay over the TPG video.
134 */
135 XAVBuf_InputVideoSelect(AVBufPtr, XAVBUF_VIDSTREAM1_NONE,
136 XAVBUF_VIDSTREAM2_NONLIVE_GFX);
137 /* Configure Video pipeline for graphics channel */
138 XAVBuf_ConfigureGraphicsPipeline(AVBufPtr);
139 /* Configure the output video pipeline */
140 XAVBuf_ConfigureOutputVideo(AVBufPtr);
141 /* Disable the global alpha, since we are using the pixel based alpha */
142 XAVBuf_SetBlenderAlpha(AVBufPtr, 0, 0);
143 /* Set the clock mode */
144 XDpPsu_CfgMsaEnSynchClkMode(DpPsuPtr, RunCfgPtr->EnSynchClkMode);
145 /* Set the clock source depending on the use case.
146 * Here for simplicity we are using PS clock as the source*/
147 XAVBuf_SetAudioVideoClkSrc(AVBufPtr, XAVBUF_PS_CLK, XAVBUF_PS_CLK);
148 /* Issue a soft reset after selecting the input clock sources */
149 XAVBuf_SoftReset(AVBufPtr);
150
151 return XST_SUCCESS;
152 }
153
154 void SetupInterrupts(Run_Config *RunCfgPtr)
155 {
156 XDpPsu *DpPsuPtr = RunCfgPtr->DpPsuPtr;
157 XScuGic *IntrPtr = RunCfgPtr->IntrPtr;
158 XScuGic_Config *IntrCfgPtr;
159 u32 IntrMask = XDPPSU_INTR_HPD_IRQ_MASK | XDPPSU_INTR_HPD_EVENT_MASK;
160
161 XDpPsu_WriteReg(DpPsuPtr->Config.BaseAddr, XDPPSU_INTR_DIS, 0xFFFFFFFF);
162 XDpPsu_WriteReg(DpPsuPtr->Config.BaseAddr, XDPPSU_INTR_MASK, 0xFFFFFFFF);
163
164 XDpPsu_SetHpdEventHandler(DpPsuPtr, DpPsu_IsrHpdEvent, RunCfgPtr);
165 XDpPsu_SetHpdPulseHandler(DpPsuPtr, DpPsu_IsrHpdPulse, RunCfgPtr);
166
167 /* Initialize interrupt controller driver. */
168 IntrCfgPtr = XScuGic_LookupConfig(INTC_DEVICE_ID);
169 XScuGic_CfgInitialize(IntrPtr, IntrCfgPtr, IntrCfgPtr->CpuBaseAddress);
170
171 /* Register ISRs. */
172 XScuGic_Connect(IntrPtr, DPPSU_INTR_ID,
173 (Xil_InterruptHandler)XDpPsu_HpdInterruptHandler, RunCfgPtr->DpPsuPtr);
174
175 /* Trigger DP interrupts on rising edge. */
176 XScuGic_SetPriorityTriggerType(IntrPtr, DPPSU_INTR_ID, 0x0, 0x03);
177
178
179 /* Connect DPDMA Interrupt */
180 XScuGic_Connect(IntrPtr, DPDMA_INTR_ID,
181 (Xil_ExceptionHandler)XDpDma_InterruptHandler, RunCfgPtr->DpDmaPtr);
182
183 /* Initialize exceptions. */
184 Xil_ExceptionInit();
185 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT,
186 (Xil_ExceptionHandler)XScuGic_DeviceInterruptHandler,
187 INTC_DEVICE_ID);
188
189 /* Enable exceptions for interrupts. */
190 Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
191 Xil_ExceptionEnable();
192
193 /* Enable DP interrupts. */
194 XScuGic_Enable(IntrPtr, DPPSU_INTR_ID);
195 XDpPsu_WriteReg(DpPsuPtr->Config.BaseAddr, XDPPSU_INTR_EN, IntrMask);
196
197 /* Enable DPDMA Interrupts */
198 XScuGic_Enable(IntrPtr, DPDMA_INTR_ID);
199 XDpDma_InterruptEnable(RunCfgPtr->DpDmaPtr, XDPDMA_IEN_VSYNC_INT_MASK);
200
201 }
202
203 u8 *GraphicsOverlay(u8* Frame, Run_Config *RunCfgPtr)
204 {
205 u64 Index;
206 u32 *RGBA;
207 RGBA = (u32 *) Frame;
208 for(Index = 0; Index < BUFFERSIZE/4; Index ++) {
209 if(Index%1920<LINESIZE/4/5){
210 RGBA[Index] = 0xFFFFFFFF;
211 }
212 else if(Index%1920<LINESIZE/4/5*2){
213 RGBA[Index] = 0x000000FF;
214 }
215 else if(Index%1920<LINESIZE/4/5*3){
216 RGBA[Index] = 0xFF0000FF;
217 }
218 else if(Index%1920<LINESIZE/4/5*4){
219 RGBA[Index] = 0xFF00FF00;
220 }
221 else if(Index%1920<LINESIZE/4/5*5){
222 RGBA[Index] = 0xFFFF0000;
223 }
224 }
225 return Frame;
226 }
代码1~20行主要是包含头文件和宏定义,我们需要关注的是第18、19行,这两行定义的是图像的分辨率,我们本节实验使用的是1080P分辨率。代码第24行定义了一个数组Frame,这个数组的类型是u8,数组长度是BUFFERSIZE。__attribute__ ((__aligned__(256)))是让结构体按照256字节去对齐,主要是方便分配统一地址长度,不了解的同学可以去学习一下C语言的使用。代码第25行是结构体变量定义,这个结构体定义了显示图像的信息包括地址、尺寸、步进以及行尺寸。
代码第27~44行是主函数,这个主函数的作用就是调用子函数来具体实现DP彩条显示的功能,首先看代码第31、32行是关闭数据缓存和指令缓存。这里给大家解释一下什么叫缓存,缓存(cache)是介于CPU和直接存储(DDR4)之间的临时缓存,它用来存储CPU之前使用过的数据或者循环调用的数据(这里通常缓存一行),它的速度接近于CPU的速度。因此当CPU需要调用数据的时候可以不直接对DDR进行操作,而是调用cache里的数据(cache已经提前缓存了一行数据),这样CPU调用起来会非常的快(如果直接对DDR进行数据读写是有一定延时的,因为DDR的速度跟不上CPU的反应速度),提高了CPU的效率。但是这种操作模式也有一个弊端,就是当DDR的数据发生改变时cache里的数据不能及时更新,会导致CPU拿到错误数据,反之CPU改变了cache里的数据但是它没有及时映射到DDR中去,也会导致DDR的数据有问题。为了解决这个弊端有两种方案,第一种简单粗暴直接关闭cache,让CPU直接对DDR进行操作,这样会在一定程度上降低CPU的效率;另一种方案就是通过调用清空cache函数(CacheFlush)和无效函数(CacheInvalidateRange)来刷新cache的值。其中CacheFlush函数是将cache中的值清空,注意这里的清空不是指把数据删除,而是把数据推送到DDR中去;而CacheInvalidateRange函数则是表明当前cache中的值无效,需要重新从DDR中读取数据。这样一来就可以实时更新cache中的数据了,关于这两个函数的用法和介绍在代码第三行包含的xil_types.h头文件中有详细介绍(包含指令缓存和数据缓存),大家可以打开这个头文件去学习。
本节实验其实没有用到DDR因为我们的数据是直接在GraphicsOverlay函数中生成的,因此根本没必要使用cache,所以干脆就将cache给关掉。
代码第34行没什么好讲的就是串口打印信息,这里打印的内容大家也可以自行修改。
代码第35~39行是调用DpdmaVideoExample函数,并判断DpdmaVideoExample函数返回的值是XST_SUCCESS还是XST_FAILURE,如果是XST_SUCCESS表明这个例程实验成功,反之则失败。
接下来我们重点来看DpdmaVideoExample函数。这个函数的作用是给大家演示如何在overlay模式下使用XDpDma驱动程序(DP控制器的驱动程序)。在讲解代码之前先给大家解释一下什么是overlay模式,overlay是一种数字视频的显示技术,允许视频信号不经过显卡GPU的处理,仅通过显存直接显示在屏幕上,简单来说就是我们显示一幅图像,它会直接覆盖到原来的图像上,他后面的图像是不变的,只是被挡住看不见了。因此当我们显示动态视频时就可以通过当前图像覆盖上一帧图像的方式来实现视频显示了。然后我们来分析DpdmaVideoExample函数内部的代码。
代码第49~55行是初始化DP控制器的配置,并返回配置结果,如果配置失败则返回XST_FAILURE,配置成功则返回XST_SUCCESS。那么具体配置了哪些内容呢?我们可以看看形参RunCfgPtr都包含了哪些参数,看代码71~87行,InitRunConfig函数就是给RunCfgPtr这个结构体具体配置实际参数。前四个参量是DP控制器的系统函数(API),包含了整个DP的通信协议的配置,例如DP的主链路配置,辅助通道配置等等,大家可以按住Ctrl键打开这四个参量去具体看一下(看这些配置之前一定要先了解DP接口协议,否则你看不懂它配置的是啥)。代码第78~86行就是具体的用户配置了,例如第78行配置图像的分辨率是1080P帧率是60Hz、第79行配置颜色深度、第80行颜色编码模式、第82行设置DP通道数量以及83行设置通道速度等等,其他设置我们保持默认即可。了解完RunCfgPtr之后再回头看DpdmaVideoExample函数就很容易理解了,代码第52行执行的就是初始化函数InitDpDmaSubsystem。
代码第89~152行就是InitDpDmaSubsystem初始化函数的内容了,这个函数里面调用了多个初始化子函数,例如代码第100~101行就是先根据设备ID去查找配置(XDpPsu_LookupConfig),找到配置信息后再去对DP主设备(数据发送端)进行配置(XDpPsu_CfgInitialize)。针对于InitDpDmaSubsystem初始化函数它调用的每一个子函数在代码上方都有英文注释它的作用,并且打开这个子函数还有更详细的注释内容,所以在这里就不带大家每一行都去分析了。
初始化成功后接下来就需要显示图片了,我们再次回到DpdmaVideoExample函数,看代码第58行GraphicsOverlay函数,这个函数里面就包含了彩条显示的信息。GraphicsOverlay函数的具体内容在代码的第203~226行,这是一个指针函数作为参量来被使用,GraphicsOverlay函数返回的值是Frame,这个值里面包含了一帧图像的像素显示信息,最终会传递到FrameBuffer中去,FrameBuffer我们等下再来讲解。我们先来详细讲解一下GraphicsOverlay函数。
在代码的第205行定义了一个64位的变量Index,代码第206行定义了一个32位的指针变量RGBA。代码第207行是先将8位一维数组Frame强制转换成32位指针数组(在代码第24行定义了这个数组Frame,这个数组的类型是u8,数组长度是BUFFERSIZE)。转换完成之后将数组的首地址传给指针变量RGBA,此时指针RGBA直接指向数组Frame里面的元素,那么接下来对RGBA进行操作就可以直接映射到Frame里面的元素(关于指针数组的访问大家有不理解的可以去查找相关C语言的语法书),因此代码第208~224行就执行了一个for循环语句变量Index从0累加到BUFFERSIZE/4(代码第18、19行定义了BUFFERSIZE和LINESIZE的大小),在这个范围内通过if语句去判断RGBA[Index]处于Frame的哪个区域,我们将一帧图像的数据(在Frame中开辟1920*1080大小的区域存储一帧图像数据)分成五个竖直条状,每个条状赋予不同颜色的数值,最终达到彩条显示的效果。最后GraphicsOverlay函数返回Frame,这个Frame在哪里被使用的呢,我们再次返回DpdmaVideoExample函数,在代码第61行就使用了Frame(这里再次进行类型转换,INTPTR类型主要是让Frame在跨平台使用时适应各个平台的长度,不清楚的可以去查询C99标准中关于intptr_t的定义),这里是将Frame包括STRIDE、LINESIZE、BUFFERSIZE都赋值给了结构体FrameBuffer。那么FrameBuffer这个结构体又是干嘛的呢?我们找到这个结构体的用处。
在xdpdma.h中可以找到如下代码:
void XDpDma_InitVideoDescriptor(XDpDma_Descriptor *CurrDesc,
XDpDma_FrameBuffer *FrameBuffer);
void XDpDma_DisplayVideoFrameBuffer(XDpDma *InstancePtr,
XDpDma_FrameBuffer *Plane0,
XDpDma_FrameBuffer *Plane1,
XDpDma_FrameBuffer *Plane2);
void XDpDma_DisplayGfxFrameBuffer(XDpDma *InstancePtr,
XDpDma_FrameBuffer *Plane);
大家可以看到有三个函数都调用了XDpDma_FrameBuffer(FrameBuffer就是XDpDma_FrameBuffer类型,结构体语法大家自行了解),那么这三个函数都有什么作用呢?其中XDpDma_InitVideoDescriptor函数的作用是初始化视频和图像的通道信息;XDpDma_DisplayVideoFrameBuffer函数是将下一帧要显示的视频进行通道缓存;XDpDma_DisplayGfxFrameBuffer函数是将下一帧要显示的图形进行通道缓存;看到这里大家就应该明白了其实FrameBuffer就是包含了一帧图片的像素内容,大小尺寸,将这些信息传递给DP驱动程序,最后将显示内容给显示出来。那么怎么将我们想要显示的图片放到FrameBuffer中去呢?为了方便大家理解我给大家画了一个示意图,如下图所示:
图 16.4.8 DP显示示意图
上图中左边是需要显示的图像,其中每个像素都是由三个字节的RGB颜色数据组成,一幅图像有着自己的分辨率即行有多少个像素,列有多少个像素。而每个像素占三个字节的存储空间,那么整幅图像在DDR中就占用了行分辨率(一行有多少个像素)*场分辨率(一列有多少个像素)*3个字节的空间。上图右边就是DP显示的帧缓存空间,这一帧的空间也是由一个个单独像素空间组成的,单独像素空间是由4个字节的RGBA颜色数据组成(这里默认是RGBA模式,也可以配置成其他模式,例如RGB模式等),其中RGBA的A指的是透明度。因此我们想要显示一幅完整的图片只需要将上图右边的帧缓存空间配置成和我们左边图像一样大小,并且将左边的像素数据转移到右边的缓存空间中就可以显示图像了。
我们以本节彩条实验为例,在代码中GraphicsOverlay函数产生了一幅1920*1080大小的彩条图像,这个图像的数据格式就是RGBA,因此它占用的总空间就是1920*1080*4个字节的空间,我们把这个彩条的所有像素数据装在Frame这个数组当中。要想把这幅彩条图案显示出来就需要把Frame数组当中的所有颜色数据搬运到DP显示帧缓存区域即FrameBuffer当中的Address存储区。那么怎么搬运呢?其实很简单,我们只需要将Frame数组的首地址配置给Address,并且告速FrameBuffer我给你的图像有多大即可,如代码第61~64行所示,这样系统提供的API函数就会自动把这幅彩条图案搬运到DP显示帧缓存区域并且最终显示出来。
最后我们再次回到DpdmaVideoExample函数,这个函数里还调用了一个中断函数SetupInterrupts,这个函数就是DP控制器的一个中断回调函数如代码第154行~201行。这个中断函数的主要作用是用来检测热插拔信号,有两种检测模式,一种是状态检测,一种是脉冲检测,分别对应代码第164、165行。这也跟前文讲述热插拔信号的作用相吻合(当接收端设备拉低HPD信号,信号脉冲宽度在0.25ms~2ms之间,此时发送端会在HPD信号上升沿后的100ms以内重新读取接收端设备信息。如果HPD信号拉低时间超过2ms(例如DP数据线被拔掉了,断开连接),发送设备就会停止发送数据,等待接收设备重新连接上)。大家可以自己试试DEBUG代码,不连接DP线,空载状态下DEBUG代码,会发现代码运行到热插拔中断函数时就会停下来,等待热插拔信号,此时插上DP线程序就可以继续运行下去了,并点亮彩条。
关于中断的详细讲解大家可以去参考前面中断实验的例程,关于DP彩条显示实验的代码到此就讲解完了。这里需要提醒大家注意一点,上文讲解的时候是用的官方示例去讲解的,大家拿到手的例程是我们自己创建的空白工程,然后在空白工程里调用DP的相关控制函数。当然调用的函数和示例模板是一摸一样的,给大家提供的例程除了可以显示竖状彩条外还可以显示横向彩条(横向彩条被注释了,大家需要使用可以解除注释)。
16.5下载验证
首先我们将下载器与MPSOC开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将开发板USB_UART (PS_PORT)接口与电脑连接,用于串口通信(注意创建工程的时候我并没有勾选UART如果大家需要使用UART请自行勾选,方法参考前面“Hello World实验”),然后将DP转接线连接到HDMI线,再连接至HDMI显示屏(这里需要使用主动式转换器,并不是所有的转接线都通用,请大家前往正点原子官方店铺购买),最后将开发板上四个启动模式开关均置为ON,即设置为JTAG模式。最后连接开发板电源给开发板上电。如下图所示:
图 16.5.1 硬件连接图
然后下载代码,稍等一会,显示屏上即可显示彩条图案,如下图所示,说明实验成功。
图 16.5.2显示彩条
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|