嵌入式显示子系统DSS编程:DMA旋转、FIFO阈值与视频层配置实战
1. 显示子系统编程模型从寄存器到像素的旅程在嵌入式图形界面开发里最让人头疼的往往不是上层的应用逻辑而是底层那块“黑盒子”——显示控制器。你写好了绚丽的UI画好了每一帧但屏幕上的图像就是出不来或者出来的是扭曲、撕裂、颜色诡异的画面。这种时候十有八九是显示子系统Display Subsystem 简称DSS的配置出了问题。它就像图形流水线的总调度负责把内存里那一堆枯燥的数字变成屏幕上鲜活的像素。很多人觉得配置DSS寄存器是芯片厂商驱动工程师的活儿应用层开发者不用管。但实际踩过坑就知道当你要实现一些特定效果比如图像旋转、画中画、不同格式视频叠加或者单纯想优化一下带宽和功耗时不理解DSS那几十个关键寄存器简直是寸步难行。特别是DMA旋转和FIFO阈值调节这两个功能用好了是性能利器用错了就是显示异常和系统卡顿的罪魁祸首。我以TI OMAP平台的DSS为例因为它足够经典其编程模型的思想在众多ARM SoC的显示控制器中都有体现。我们将抛开枯燥的寄存器手册描述从“为什么要这么配置”和“实际怎么配”的角度把DMA旋转、FIFO管理、视频层配置这一套组合拳彻底讲透。无论你是在做汽车仪表盘、工业HMI还是智能家居面板这套底层逻辑都是相通的。2. 核心基石DMA旋转的硬件原理与配置逻辑图像旋转听起来是个软件活儿但在资源紧张的嵌入式系统里用CPU去逐像素计算坐标变换绝对是下策不仅耗电还会严重占用CPU资源导致系统响应迟缓。DSS提供的硬件DMA旋转功能其核心思想非常巧妙不改变内存中图像的存储方式而是改变DMA控制器读取内存的顺序和步长。2.1 理解“基地址、像素增量、行增量”三位一体这是DMA旋转的“灵魂三要素”。你可以把帧缓冲区Frame Buffer想象成一张铺在内存里的方格纸每个格子存一个像素。DMA引擎就是你的眼睛它按照某种规则依次“看”这些格子并把看到的颜色送到屏幕上。基地址 (Base Address,DSS.DISPC_VIDn_BAj): 你的眼睛从哪个格子开始看。对于0度旋转正常显示这就是图像左上角第一个像素的地址。像素增量 (Pixel Increment,DSS.DISPC_VIDn_PIXEL_INC): 看完一个格子后你的眼睛要移动多少字节才能看到同一行下一个格子。对于连续存储这通常是1个像素的大小比如RGB565格式是2字节。行增量 (Row Increment,DSS.DISPC_VIDn_ROW_INC): 看完一行最后一个格子后你的眼睛要移动多少字节才能跳到下一行的第一个格子。这通常是1行的大小图像宽度 × 像素大小。要实现90度旋转我们需要让DMA引擎从原始图像的左下角开始读取然后向上移动读取一列读完一列后再向右移动读取下一列。这个“读取路径”的转变就是通过精心计算这三个值来实现的。手册里给出的90度旋转公式是基地址ba (iw * (ih - 1) * ps)ba: 原始图像基地址左上角iw: 图像宽度-1像素数-1ih: 图像高度-1行数-1ps: 像素大小字节计算逻辑iw * ps是一行的字节数。(ih - 1)表示最后一行从0开始计数。所以这个公式计算的是最后一行、第一列那个像素的地址也就是旋转后的新起点左下角。像素增量-(iw * ps) - (ps - 1)计算逻辑iw * ps是一行的跨度。负号表示向上移动地址减小。-(iw * ps)让读取位置从当前行跳到上一行的同一列。-(ps - 1)是一个调整项对于2字节像素如RGB565ps-11最终增量为-iw*ps -1。这确保了在连续读取时能准确指向上一行对应像素的起始字节。行增量(iw * (ih - 1) * ps) 1计算逻辑当读完一列从下到上后需要跳到右边下一列的顶部对于旋转后的视图开始读。(iw * (ih - 1) * ps)是从图像左下角跳到右上角所需的偏移量即从最后一行第一列跳到第一行最后一列。1则是一个微调确保地址对齐到下一个像素的起始。实操心得地址对齐是性能的生命线手册里反复强调“基地址应对齐到突发传输边界”。这是什么意思现代内存控制器如DDR以“突发”为单位传输数据比如一次传64字节。如果你的基地址是0x1001而突发边界是0x1000那么第一次取数据就需要两个突发周期先取0x1000-0x103F再取0x1040-0x107F效率减半。对于RGB24格式3字节/像素宽度最好是12字节4像素的倍数这样一行数据能刚好填满整数个突发避免带宽浪费。在配置前务必用malloc或芯片专用内存分配器确保缓冲区地址是32字节或64字节对齐的。2.2 不同数据格式与存储位置的策略选择DMA旋转并非万能其效率和应用场景取决于图像数据的格式和存放位置。1. 图像数据在片内SRAM这是DMA旋转的最佳场景。片内SRAM延迟极低DMA引擎可以高效地进行“非连续”地址访问即使用非1的像素/行增量。此时你可以直接使用上一节的公式配置寄存器实现0°、90°、180°、270°旋转及镜像。这也是性能最优的方案。2. 图像数据在外部SDRAM使用VRFB旋转当图像太大必须放在外部DDR SDRAM时情况变了。SDRAM对访问模式非常敏感频繁的非连续、小跨度访问会严重破坏“行缓冲”的局部性导致性能急剧下降。这时TI引入了虚拟旋转帧缓冲VRFB模块。VRFB的原理可以理解为在片内开辟一块“中转站”。它的工作分两步写入阶段旋转SMS共享内存管理模块中的VRFB引擎以高效的块传输方式从SDRAM中读取原始图像在写入片内VRFB区域时就完成物理上的像素重排旋转。读取阶段显示DSS的DMA引擎从VRFB区域读取数据。此时对于DSS来说它读取的是一块“已经旋转好的、连续存储”的图像。因此DISPC_xxx_PIXEL_INC恒为1DISPC_xxx_ROW_INC也只需要简单的行末偏移(2048 - iw) * ps 1访问模式变得非常规整对SDRAM友好。关键抉择点小图像、要求极低延迟用片内SRAM DMA旋转。大图像、或系统带宽紧张用SDRAM VRFB旋转。RGB24格式特别注意由于其3字节像素不对齐32位边界DMA旋转支持有限通常只支持0°和180°且行增量必须是1或12的倍数。VRFB是更好的选择。2.3 旋转配置的完整流程与避坑指南配置旋转不是简单地套公式写寄存器。它是一个有严格顺序的过程否则可能导致显示错乱或DMA挂死。标准配置流程失能视频/图形层首先确保DISPC_VIDn_ATTRIBUTES[0] VIDENABLE或DISPC_GFX_ATTRIBUTES[0] GFXENABLE位为0。不要在通道活跃时修改关键参数。设置窗口属性配置DISPC_xxx_SIZE旋转后显示在屏幕上的窗口大小和DISPC_xxx_POSITION窗口位置。切记旋转操作改变的是内存读取方式但显示窗口的大小需要你根据旋转后的图像宽高手动设置。例如一个480x272的图像旋转90度后窗口大小应设置为272x480。计算并置旋转参数根据目标旋转角度和数据格式RGB16/YUV422选择正确的公式见表15-49, 15-51, 15-52, 15-53计算BAj、PIXEL_INC、ROW_INC并写入寄存器。仅视频层YUV数据配置色彩空间转换系数如果旋转的是YUV422数据需要确保DISPC_VIDn_CONV_COEF0~4寄存器已根据标准如BT.601或BT.709正确配置以便在旋转后能正确转换为RGB显示。设置管道属性配置DISPC_xxx_ATTRIBUTES寄存器。这里有几个关键位VIDROTATION/GFXROTATION:仅当像素数据为RGB24格式时才需要设置旋转角度对于RGB16或YUV422硬件通过PIXEL_INC和ROW_INC识别旋转此位应保持为0。这是一个极易出错的地方。VIDROWREPEATENABLE:仅在YUV422格式且旋转90°/270°时需使能。因为YUV422是2像素打包存储YUYV旋转读取时需要将同一行数据取两次以分离出Y分量。VIDCHANNELOUT/GFXCHANNELOUT: 选择输出到LCD还是TV编码器。配置FIFO阈值根据旋转后的数据流特性调整DISPC_xxx_FIFO_THRESHOLD这部分我们下一章详述。使能管道并触发更新将VIDENABLE或GFXENABLE位置1。然后必须通过置位DISPC_CONTROL[5] GOLCD或[6] GODIGITAL来触发硬件更新配置。软件需要轮询等待硬件将该位清0表明更新完成。常见问题排查图像错位、撕裂或颜色异常症状图像能显示但位置不对或是斜的。检查PIXEL_INC和ROW_INC计算错误特别是正负号。用一个小图像如8x8和单色填充进行测试在内存中查看实际读取顺序。症状旋转90/270度后图像垂直方向出现重复条纹。检查YUV422格式下VIDROWREPEATENABLE位是否已正确使能。症状使能后无任何显示或DMA似乎停止工作。检查BAj地址是否未对齐特别是RGB24。是否在管道活跃时修改了BAj、PIXEL_INC、ROW_INC等寄存器必须遵循“失能-配置-使能-触发更新”的流程。症状使用VRFB时图像底部或右侧有残留数据。检查Offset值计算是否正确。VRFB的页面大小是固定的如32行如果你的图像高度不是32的整数倍需要填充。Offset就是用来跳过这些填充区域的。务必根据图15-127的示意图计算Δih和Δiw。3. 性能关键视频FIFO阈值调优与带宽管理如果说DMA旋转解决了“读什么”的问题那么FIFO阈值管理就是解决“何时读”和“读多少”的问题它直接决定了显示的流畅度和系统总线的负载。3.1 FIFO工作原理高低水位线的艺术DSS内部为每个图形/视频管道都设有FIFO先入先出缓冲区。它的工作模式很像一个家庭水塔水塔FIFO缓存从内存读来的像素数据。水泵DMA引擎负责从内存水源往水塔抽水。水龙头显示时序发生器以固定速率像素时钟从水塔抽水显示到屏幕上。DISPC_xxx_FIFO_THRESHOLD寄存器就是设置水塔的低水位线和高水位线。当水塔水量消耗到低水位线时触发警报水泵开始工作。水泵会持续抽水直到水量达到高水位线才停止。然后水龙头继续放水水量再次下降到低水位线循环往复。寄存器字段VIDFIFOLOWTHRESHOLD[11:0](低阈值)单位是32位字。当FIFO中剩余数据量小于等于此值时触发DMA请求。VIDFIFOHIGHTHRESHOLD[27:16](高阈值)单位是32位字。DMA请求的目标填充水平。为什么要这样设计如果只有一条水位线比如低于50%就抽水抽到100%停。那么在水泵抽水期间水龙头可能已经把水放空导致“断流”屏幕撕裂。高低水位线机制提供了一个缓冲区间。低水位线保证了不会轻易断流高水位线则避免了频繁启停水泵DMA请求让DMA传输能以更高效的突发模式进行减少总线仲裁开销。3.2 FIFO合并FIFOMERGE模式详解在DISPC_CONFIG[14]有一个关键的FIFOMERGE位。当置为1时GFX、VID1、VID2三个独立的FIFO在物理上会合并成一个大FIFO。这样做有什么好处假设有三个管道同时工作每个FIFO深度为128字。如果不合并每个FIFO都需要独立维护自己的高低阈值。在某一时刻可能VID1的FIFO快空了触发DMA而GFX的FIFO还很满。这会导致DMA请求分散总线利用率不高。 合并后三个管道共享一个384字的大FIFO。DMA引擎可以集中为这个大FIFO服务。只要合并FIFO的总量低于低阈值就发起一次大的DMA传输填充到高阈值。这显著减少了DMA请求的次数和总线仲裁冲突尤其在高分辨率、多图层叠加的场景下对提升整体带宽效率至关重要。重要配置差异 当启用FIFOMERGE时你为VID1或VID2通道设置的阈值VIDFIFOLOWTHRESHOLD和VIDFIFOHIGHTHRESHOLD必须乘以3。因为硬件内部是将合并后的大FIFO深度均分给三个逻辑通道来管理阈值。如果你还按原来的值设置会导致阈值计算错误DMA行为异常。3.3 阈值计算与实战调优策略手册不会告诉你具体设多少这需要根据实际场景计算和调试。第一步理解基本参数像素时钟 (PCLK)显示器刷新的根本频率。例如1024x76860Hz像素时钟大约65MHz。DMA突发长度 (Burst Size)由VIDBURSTSIZE配置通常是4、8或16个32位字。这决定了DMA一次请求传输的数据量。总线延迟 (Latency)从DMA发出请求到数据开始返回的延迟时间。这是最不确定的因素取决于总线繁忙程度。第二步估算阈值一个保守的起点是确保FIFO在DMA响应延迟期间不会下溢。计算“安全深度”安全深度 ≈ 最大总线延迟时间 × 像素消耗速率。像素消耗速率 像素时钟 × 每像素字节数 / 4 (转换为32位字)。例如65MHz像素时钟RGB565格式2字节/像素消耗速率 ≈ 65M * 2 / 4 32.5 M 字/秒。假设最坏总线延迟为1us则安全深度 ≈ 32.5 M字/秒 * 1e-6秒 ≈ 33字。设置阈值低阈值 安全深度。例如设为40字。高阈值 低阈值 N × 突发长度。N建议为2-4以保证一次DMA能有效填充。如果突发长度是8字高阈值可设为 40 2*8 56字。必须确保高阈值 FIFO总深度。对于合并模式是总深度/3。第三步动态调整与观测理论计算只是起点实际需要借助芯片的性能计数器或通过观察现象来调优。现象屏幕上方偶尔出现横向撕裂线。诊断FIFO下溢。显示引擎在行扫描开始时FIFO数据不足。调整适当提高低阈值给DMA响应留出更多时间。或者检查总线负载是否有其他高优先级主设备如CPU、GPU在抢占带宽。现象系统总带宽占用过高其他任务卡顿。诊断DMA请求过于频繁或每次传输效率不高。调整增大高阈与低阈值之间的差值让每次DMA传输的数据量更大减少请求次数。同时确保图像缓冲区的宽度和基地址是突发长度对齐的以最大化总线利用率。现象启用FIFOMERGE后显示异常。检查阈值是否忘了乘以3三个图层的像素时钟和数据格式是否差异巨大如果其中一个图层消耗极慢可能会“占着茅坑不拉屎”影响其他图层。此时可能需要关闭合并或调整各图层的优先级VIDARBITRATION位。实操心得利用自刷新SELFREFRESH功能降低功耗对于静态或低频更新的画面如仪表盘背景、LOGOVIDSELFREFRESH位是个神器。置位后DMA引擎在加载完一帧数据到FIFO后后续帧将不再访问系统内存而是重复使用FIFO中的数据。这可以大幅降低内存带宽和系统功耗。其激活序列是在帧t设置位帧t1加载并显示帧t2开始自刷新。注意在自刷新期间你不能更新帧缓冲区内容除非先关闭自刷新。4. 视频层配置全解析从使能到缩放配置好DMA和FIFO只是打通了数据通路。要让图像正确显示还需要对视频层本身进行精细配置。4.1 视频窗口与图像缓冲区的区别这是两个极易混淆的概念必须分清图像缓冲区 (Picture in System Memory)由VIDORGSIZEX/Y定义。这是存储在内存中的原始图像的尺寸。比如你有一张800x600的图片。视频窗口 (Video Window on Screen)由VIDSIZEX/Y和VIDPOSX/Y定义。这是最终显示在屏幕上的区域的位置和大小。这两者可以不同从而实现缩放和裁剪。缩放VIDORGSIZE≠VIDSIZE。例如将800x600的缓冲区缩小到400x300的窗口显示就需要启用缩放滤波器。裁剪VIDPOS可以让你只显示缓冲区的一部分。例如缓冲区是800x600但窗口设为400x300位置为(200, 150)那么显示的就是缓冲区中间的那一块400x300区域。关键约束VIDSIZEVIDPOS必须完全位于屏幕分辨率范围内不能超出边界。4.2 缩放滤波器配置原理与系数计算当显示窗口大小与原始图像大小不同时需要用到硬件缩放滤波器。DSS支持水平和垂直方向的独立缩放采用多相滤波算法来保证质量。缩放系数计算 缩放的核心是计算步长增量VIDFIRHINC(水平) 和VIDFIRVINC(垂直)。公式如下VIDFIRHINC (VIDORGSIZEX / VIDSIZEX) * 1024 VIDFIRVINC (VIDORGSIZEY / VIDSIZEY) * 1024注意寄存器里存储的是SIZE-1的值所以实际计算时VIDORGSIZEX和VIDSIZEX需要加1后再代入公式。放大ORGSIZESIZE系数 1024。表示输出一个像素需要输入小于一个像素的数据即插值。缩小ORGSIZESIZE系数 1024。表示输出一个像素需要输入多个像素的数据即抽取并滤波。滤波器抽头配置3-Tap vs 5-Tap通过VIDVERTICALTAPS选择。抽头数越多滤波效果越好但需要更多的行缓冲区Line Buffer。5抽头模式能提供更平滑的缩放效果特别是缩小图像时能减少锯齿。行缓冲区分割VIDLINEBUFFERSPLIT。当使用5抽头垂直滤波器时必须将此位置1将原有的3个行缓冲区分割成6个来使用以满足5抽头滤波对更多参考行的需求。系数设置FIR_COEF_Hi和FIR_COEF_HVi寄存器组用于设置滤波系数。TI通常会提供一组针对不同缩放比优化过的默认系数。除非你有特殊的图像处理需求如锐化否则不建议修改这些系数。重点在于理解系数与“相位”的关系滤波器在每个输出像素位置对输入像素的加权权重是不同的这些权重就是系数它们根据VIDHORIZONTALACCU和VIDVERTICALACCU累加器决定的相位进行循环选取。配置流程与禁忌在使能缩放 (VIDRESIZEENABLE)之前必须设置好所有相关寄存器VIDORGSIZE,VIDSIZE,VIDFIRHINC/VINC, 滤波器系数等。修改任何缩放相关寄存器后必须通过置位GOLCD来触发硬件更新。严禁在缩放使能状态下动态修改VIDORGSIZE或VIDSIZE。这会导致滤波器状态混乱产生严重图像失真。正确做法是先失能缩放修改尺寸重新计算系数再使能缩放并触发更新。4.3 色彩空间转换CSC配置视频管道支持YUV422到RGB的转换这是播放视频的关键。转换通过一个3x3矩阵乘法实现矩阵系数存储在CONV_COEF0~4寄存器中。标准系数选择 手册中的表15-47给出了常用标准下的系数。BT.601 vs BT.709这是标清SD和高清HD视频的色彩空间标准。如果你的视频源是480i/p或576i/p用BT.601如果是720p、1080i/p用BT.709。用错会导致颜色饱和度偏差。VidFullRange (0 vs 1)这指的是YUV分量的取值范围。VidFullRange0(Limited Range): Y取值范围16~235 Cb/Cr取值范围16~240。这是电视广播标准。VidFullRange1(Full Range): Y、Cb、Cr取值范围都是0~255。常见于计算机生成的视频或某些视频文件。如何判断大多数MPEG视频流使用Limited Range。如果你从摄像头直接采集YUV数据可能是Full Range。配置错误最明显的现象是对比度异常Limited Range当Full Range用画面发灰反之则过曝。实操步骤根据视频源格式从表15-47中选择对应的系数数组。将9个系数RY, GY, BY, RCr, GCr, BCr, RCb, GCb, BCb按顺序填入CONV_COEF0~4寄存器。注意每个寄存器存放的系数顺序和位域。确保VIDCOLORCONVENABLE位已使能。5. 实战配置一个旋转叠加的视频播放层假设我们要在800x480的LCD屏幕上实现一个画中画功能主画面显示摄像头预览RGB565, 800x480小窗口播放一个本地视频YUV422, 320x240并且小窗口需要旋转90度显示。步骤1内存与缓冲区规划分配主画面缓冲区fb_main, 大小 800 * 480 * 2 768,000 字节。地址需64字节对齐。分配视频缓冲区fb_video, 大小 320 * 240 * 2 153,600 字节YUV422视为2字节/像素。地址需64字节对齐。视频缓冲区内容为YUV422格式如UYVY。步骤2配置视频层VID1用于画中画失能层DISPC_VID1_ATTRIBUTES[0] 0。设置原始图像大小VIDORGSIZEX 320 - 1 319(0x13F)VIDORGSIZEY 240 - 1 239(0xEF)设置显示窗口旋转后旋转90度后宽高互换。VIDSIZEX 240 - 1 239(0xEF)VIDSIZEY 320 - 1 319(0x13F)VIDPOSX 500(屏幕右侧)VIDPOSY 80计算并设置DMA旋转参数假设数据在SDRAM使用VRFB使用VRFB旋转90度公式表15-52。DISPC_VID1_BA0 VBA90 offset(VBA90从VRFB驱动获取offset根据VRFB页面大小计算)。DISPC_VID1_PIXEL_INC 1。DISPC_VID1_ROW_INC (2048 - ih) * ps 1。ih是原始图像高度240ps是像素大小2YUV422按2字节/像素算这里注意对于VRFBYUV422的ps在计算ROW_INC时仍用2但VRFB内部会处理打包格式。更准确地说对于YUV422iw和ih应使用像素数但ps用每个“处理单元”的字节数对于VRFBYUV422的ps通常是4因为它是按32位字处理的。这里必须查阅VRFB章节的详细定义这是一个容易混淆的点。我们假设按典型情况YUV422在VRFB中ps4。ROW_INC (2048 - 240) * 4 1 1808 * 4 1 7233(0x1C41)。设置管道属性VIDFORMAT UYVY(根据具体格式选择)。VIDCOLORCONVENABLE 1(使能YUV转RGB)。VIDROTATION 0(重要对于非RGB24格式此位为0)。VIDROWREPEATENABLE 1(重要YUV422且旋转90度必须置1)VIDCHANNELOUT LCD。配置色彩空间转换选择BT.601 Limited Range系数填入CONV_COEF0~4。配置FIFO阈值估算小窗口的像素消耗速率较低可设置较小的阈值。例如低阈值20高阈值52假设突发长度8。使能并更新VIDENABLE 1。置位DISPC_CONTROL[5] GOLCD等待清0。步骤3配置图形层GFX用于主画面略配置为0度旋转覆盖全屏优先级低于视频层以实现叠加。步骤4系统集成与调试使用示波器或逻辑分析仪抓取LCD的时序信号VSYNC, HSYNC, PCLK, DE确保时序正确。在内存中写入测试图案如渐变色条、网格到fb_video观察显示是否正确颜色转换和旋转是否无误。使用性能监控工具如OMAP的PM计数器观察DSS带宽占用和FIFO下溢/上溢次数微调FIFO阈值。测试动态切换尝试改变小窗口的位置、大小或旋转角度观察切换过程是否平滑有无闪屏。确保遵循“失能-配置-使能-更新”的流程。嵌入式显示编程就像在微观世界里搭建一座精密的桥梁连接着数据与光影。寄存器是砖石时序是蓝图而DMA和FIFO则是维持桥梁畅通的车流调度系统。理解每个寄存器位背后的物理意义掌握数据流在硬件中的真实路径是解决一切显示问题的根本。手册提供了公式但真正的技巧在于如何根据具体的屏幕、内存、总线负载和功耗要求去权衡和调整这些参数。调试显示问题不妨从最简单的纯色块开始用内存查看器确认DMA的读取地址序列是否正确再用示波器确认时序一步步缩小范围。当你第一次看到通过自己配置的寄存器让图像完美地旋转、缩放、叠加在屏幕上时那种对硬件掌控的成就感便是嵌入式开发最纯粹的乐趣所在。