ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GD32 TLI与IPA实战:RGB888屏驱动与2D图形硬件加速

GD32 TLI与IPA实战:RGB888屏驱动与2D图形硬件加速 GD32 的 TLITFT LCD Interface外设配合 IPAImage Processing Accelerator这套组合是我最近在做一个带屏项目时踩了不少坑才摸透的。起因很简单手头一块 800x480 的 RGB888 屏主控用的是 GD32F4 系列本来打算用软件刷屏凑合结果一跑起来 CPU 占用直接拉满界面卡得像幻灯片。后来翻手册才发现这颗片子里藏着一个 TLI 和一个 IPA前者负责把显存里的图像数据按 RGB 时序推到屏幕上后者专门干 2D 图形加速的活——填充、拷贝、格式转换、Alpha 混合都能硬件完成。把这两个外设用起来之后刷屏这件事从CPU 全程搬砖变成了配置好寄存器然后撒手不管帧率稳了CPU 也空出来了。这篇内容适合两类人看一类是刚拿到 GD32 带屏方案、还在纠结要不要上 TLI 的嵌入式新手另一类是已经用了 TLI 但发现刷图还是慢、想进一步榨干 IPA 性能的老手。我会从 TLI 和 IPA 各自解决什么问题讲起把 RGB888 的时序配置、显存布局、图层叠加、IPA 的几种典型加速场景都拆开说清楚中间穿插我自己调试时遇到的真实问题和排查过程。看完你应该能直接照着把屏点亮并且知道哪些图形操作该丢给 IPA、哪些该自己写。1. 先搞清楚 TLI 和 IPA 各自在干什么很多人一上来就问TLI 怎么配但其实更该先问的是我到底需不需要 TLI。这两个问题的答案差别很大直接决定了你后面是写一堆 GPIO 翻转代码还是配置几个寄存器就能躺平。1.1 TLI 的本质一个会自己读显存的显示控制器TLI 说白了就是一个独立的显示控制器。你在内存里划一块区域当显存frame buffer把这块区域的地址、像素格式、分辨率、时序参数告诉 TLI它就会自己按照 LCD 的时序要求一个像素一个像素地把数据从显存读出来、推到并口上。整个过程 CPU 完全不参与你只需要在图像内容变化时去改显存里的数据就行。这跟传统的GPIO 模拟时序 DMA 搬运方案有本质区别。传统方案里即使你用 DMA 把数据搬到 GPIO时序的生成、行场同步信号的翻转还是得靠定时器或者 CPU 干预一旦分辨率上去、刷新率要求高CPU 就被拖住了。TLI 把这些全部硬件化它内部有时序发生器、有 FIFO、有图层混合单元是一个完整的显示流水线。我用一个生活化的类比传统刷屏像是你亲自一勺一勺往碗里盛饭TLI 则像是装了一台自动盛饭机你只要把米显存数据准备好机器自己按节奏盛。CPU 从盛饭工变成了备米工。1.2 IPA 的定位专治 2D 图形里的重复劳动IPA 是 Image Processing Accelerator 的缩写直译是图像处理加速器。它干的活非常具体矩形填充、矩形拷贝blit、像素格式转换、Alpha 混合、颜色键color key。这些操作在 GUI 里出现频率极高——你画一个按钮背景是填充移动一个图标是拷贝半透明弹窗是 Alpha 混合图标去背景是颜色键。如果这些都用 CPU 做一个 800x480 的全屏填充就是 38 万次写操作一次 Alpha 混合更是要读两个像素、算一次、再写回。IPA 把这些变成配置几个寄存器然后等中断一次填充可能几十个时钟周期就完成了。这里有个关键认知TLI 和 IPA 是互补的不是二选一。TLI 负责把最终画面送到屏幕IPA 负责在显存里快速生成和修改画面。你可以只用 TLI 不用 IPA画面照样能显示只是所有图形操作都得 CPU 来干你也可以两个都用让 IPA 在后台改显存、TLI 在前台刷屏CPU 彻底解放。1.3 为什么 RGB888 这个格式值得单独说RGB888 意味着每个像素占 3 个字节R、G、B 各 8 位总共 1677 万色。相比 RGB565每像素 2 字节65536 色RGB888 色彩过渡更细腻渐变不会出现明显的色带。但代价是显存占用大 50%带宽需求也高 50%。800x480 的 RGB888 显存是 800 × 480 × 3 1,152,000 字节差不多 1.1MB。如果你用双层图层直接翻倍到 2.2MB。这在 GD32F4 这种内部 SRAM 只有几百 KB 的片子上是放不下的必须外挂 SDRAM 或者用带大容量内部 RAM 的型号。这一点在选型阶段就要想清楚否则代码写到一半发现显存放不下返工成本很高。我当时的方案是外挂一颗 8MB 的 SDRAM显存、IPA 的源和目标缓冲区都放在 SDRAM 里。这里有个坑SDRAM 的带宽是共享的TLI 读显存、IPA 读写缓冲区、CPU 取指令都要抢这条总线配置不当会出现屏幕撕裂或者 IPA 变慢。后面会专门讲怎么协调。2. 点亮 RGB888 屏的完整配置链路这一节是实操核心。我会按时钟→引脚→时序→显存→图层→使能的顺序讲每一步都说明为什么这么做以及我踩过的坑。2.1 时钟树TLI 的像素时钟从哪来TLI 需要一个像素时钟Pixel Clock这个时钟决定了每个像素推送到屏幕的速度。像素时钟的计算公式是Pixel Clock (Hsync HBP HActive HFP) × (Vsync VBP VActive VFP) × 刷新率以 800x480、60Hz 的典型屏为例假设行时序是 Hsync48、HBP88、HActive800、HFP40场时序是 Vsync3、VBP32、VActive480、VFP13那么总行数 48 88 800 40 976 总场数 3 32 480 13 528 Pixel Clock 976 × 528 × 60 ≈ 30.9 MHz这个 30.9MHz 就是你要给 TLI 配的像素时钟。GD32 里 TLI 的时钟源通常来自 PLLSAI 或者 PLLI2S你需要根据系统时钟反推分频系数。我当时的系统时钟是 200MHz通过 PLLSAI 分出 30.9MHz 左右给 TLI。注意像素时钟不能随便给必须严格匹配屏幕手册里的时序要求。给高了屏幕花屏或者不亮给低了刷新率不够会闪。我第一次调试时把像素时钟配成了 33MHz结果屏幕右侧出现了一条竖直的噪点带查了半天才发现是时钟偏快导致行消隐时间不够。2.2 引脚配置别漏了 DE 和 CLK 的复用TLI 用到的引脚不少RGB 数据线RGB888 是 24 根、行同步 HSYNC、场同步 VSYNC、数据使能 DE、像素时钟 CLK。这些引脚都要配成**复用功能AF**模式而且要注意 GD32 不同型号的 TLI 引脚映射可能不一样必须查对应型号的数据手册。我踩过的坑是 DE 信号。有些屏幕用 DE 模式靠 DE 信号标识有效数据区有些用 SYNC 模式靠 HSYNC/VSYNC 标识。如果你的屏幕是 DE 模式但你把 TLI 配成了 SYNC 模式屏幕会显示但图像偏移、边缘有杂色。配置时要看清楚屏幕手册里写的是 DE only 还是 DE/SYNC 都支持。引脚配置的代码大概长这样以 GD32 的固件库风格为例/* 使能 GPIO 和 TLI 时钟 */ rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_TLI); /* 配置 RGB 数据线为 AF 模式 */ gpio_af_set(GPIOB, GPIO_AF_14, GPIO_PIN_0 | GPIO_PIN_1 | ...); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0 | ...); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, GPIO_PIN_0 | ...);这里GPIO_AF_14是 TLI 的复用编号具体数值查手册。输出速度建议给最高档因为像素时钟 30MHz 下引脚翻转频率不低速度不够会导致信号边沿变缓、图像模糊。2.3 时序参数把屏幕手册的数字翻译成寄存器屏幕手册里会有一张时序表列出 HSYNC、HBP、HACTIVE、HFP、VSYNC、VBP、VACTIVE、VFP 这些值。你要把它们填进 TLI 的TLI_HSYNC、TLI_HBP、TLI_HACTIVE等寄存器。GD32 的 TLI 寄存器命名和这些参数基本一一对应但要注意有些寄存器填的是值减 1。比如 HACTIVE 是 800寄存器里可能填 799。这种细节手册里会写但很容易看漏。我的做法是先把所有参数列成一张表对照手册逐个确认是填原值还是减 1避免来回改。参数屏幕手册值寄存器填写说明HSYNC4847行同步脉冲宽度减 1HBP8888行后沿原值HACTIVE800799有效像素减 1HFP4040行前沿原值VSYNC32场同步脉冲宽度减 1VBP3232场后沿原值VACTIVE480479有效行数减 1VFP1313场前沿原值这张表是我实际调试时整理的不同屏幕数值不同但哪些减 1的规律基本一致。建议你也整理一张贴在代码旁边。2.4 显存布局RGB888 在内存里怎么排RGB888 在显存里的排列方式有两种像素打包packed和字节对齐byte-aligned。打包模式下3 个字节连续存放一个像素显存利用率 100%字节对齐模式下每个像素占 4 个字节多一个填充字节浪费 25% 空间但访问更快。GD32 的 TLI 支持 RGB888 的打包模式我用的就是打包。显存里第一个像素的 R 在 offset 0G 在 offset 1B 在 offset 2第二个像素的 R 在 offset 3以此类推。配置时要设置TLI_PIXFORMAT为 RGB888并且注意字节序——有些屏幕期望 BGR 顺序配错了颜色会偏。提示如果你发现屏幕颜色整体偏蓝或偏红八成是 RGB 顺序配反了。改TLI_PIXFORMAT里的字节序设置或者直接在写显存时交换 R 和 B 的位置。显存地址要 4 字节对齐这是硬件要求。我用__attribute__((aligned(4)))或者手动对齐来保证。如果显存放在 SDRAM 里还要确保 SDRAM 控制器配置正确否则 TLI 读到的数据是乱的。2.5 图层配置单层够用双层更灵活TLI 支持多层图层叠加。单层模式下你只有一个显存所有内容都画在里面。双层模式下你可以有背景层和前景层前景层可以带 Alpha 混合实现半透明效果。我一开始用单层后来做弹窗时需要半透明背景就切到了双层。双层配置的关键是设置好每层的显存地址、像素格式、混合系数。前景层的 Alpha 值可以全局设也可以逐像素设需要显存里存 Alpha 通道。双层虽然灵活但显存占用翻倍带宽需求也增加。如果你的 SDRAM 带宽紧张建议还是单层半透明效果用 IPA 在单层里做混合。2.6 使能顺序先配好再开别边配边开TLI 的使能有个顺序讲究先配置所有参数最后再使能 TLI。如果你先使能了再改参数屏幕会闪一下或者出现异常。正确的顺序是配置时钟和引脚配置时序参数配置显存地址和像素格式配置图层使能 TLI使能图层我踩过的坑是在调试阶段频繁改参数每次改完忘了重新使能结果屏幕一直黑屏查了半天才发现是图层没使能。3. IPA 加速哪些图形操作该丢给它屏点亮之后接下来的问题就是怎么画得快。这一节讲 IPA 的几种典型用法以及什么时候该用、什么时候不该用。3.1 矩形填充IPA 最擅长的活矩形填充是 GUI 里最高频的操作——清屏、画背景、画按钮底色都是它。用 CPU 做一个 800x480 的全屏填充要写 38 万次用 IPA配置好源地址、目标地址、宽度、高度、颜色启动后等中断就行。IPA 的填充有两种模式固定颜色填充和源数据填充。固定颜色填充是你给一个颜色值IPA 把这个颜色刷满整个矩形源数据填充是从一块内存拷贝数据到目标矩形。前者用于纯色背景后者用于贴图。配置填充的代码逻辑/* 设置目标矩形 */ IPA_DEST_ADDR (uint32_t)frame_buffer y * stride x * 3; IPA_DEST_WIDTH width; IPA_DEST_HEIGHT height; IPA_DEST_STRIDE stride; /* 设置填充颜色RGB888 */ IPA_FILL_COLOR (r 16) | (g 8) | b; /* 启动填充 */ IPA_CTRL | IPA_CTRL_FILL_EN; while (IPA_STATUS IPA_STATUS_BUSY);这里stride是显存的行跨度通常等于屏幕宽度乘以每像素字节数。如果显存有对齐填充stride 可能大于width * 3配置时要算对否则图像会错位。3.2 矩形拷贝Blit移动图标和滚动Blit 是把一块矩形区域从源位置拷贝到目标位置支持源和目标重叠比如滚动。IPA 的 Blit 比 CPU 的memcpy快得多因为它可以按行并行搬运而且不占用 CPU。做滚动列表时Blit 特别有用。比如列表向上滚动一行你可以把从第二行开始的内容 Blit 到第一行然后只重绘最后一行。这样每次滚动只需要搬 799 行而不是 800 行虽然省得不多但配合 IPA 的并行能力整体流畅度提升明显。Blit 的坑在于源和目标重叠。如果源区域和目标区域有重叠拷贝方向会影响结果。IPA 硬件通常会自动处理重叠但你要确认手册里有没有说明。我遇到过一次滚动时图像撕裂就是因为没注意重叠方向后来改成从下往上拷贝就好了。3.3 像素格式转换RGB565 到 RGB888 的桥梁实际项目里经常遇到格式不统一的情况UI 素材是 RGB565 的省空间但屏幕是 RGB888 的。如果 CPU 转换每个像素要做位扩展和移位很费时间。IPA 支持硬件格式转换可以在 Blit 的同时把 RGB565 转成 RGB888。这个功能在贴图时特别有用。你把 RGB565 的图标存在 Flash 里用 IPA 直接 Blit 到 RGB888 的显存一步到位。省了 CPU 转换也省了中间缓冲区。配置时要注意源格式和目标格式分别设置IPA 会自动做转换。转换的精度是位扩展比如 RGB565 的 5 位 R 扩展到 8 位低位补零或者复制高位具体看硬件实现。如果对色彩精度要求高建议还是用 RGB888 素材。3.4 Alpha 混合半透明效果的硬件实现Alpha 混合是把前景色和背景色按比例混合公式是结果 前景 × Alpha 背景 × (1 - Alpha)CPU 做这个要读两个像素、做两次乘法、一次加法、再写回一个像素好几条指令。IPA 硬件做这个是一个时钟周期的事。IPA 的 Alpha 混合支持全局 Alpha 和逐像素 Alpha。全局 Alpha 是整个矩形用同一个透明度逐像素 Alpha 需要前景数据里带 Alpha 通道。做弹窗背景时全局 Alpha 就够了做图标淡入淡出时逐像素 Alpha 更灵活。注意Alpha 混合是读-改-写操作带宽消耗是纯填充的三倍读前景、读背景、写结果。如果 SDRAM 带宽紧张大量使用 Alpha 混合会拖慢整体性能。我的经验是静态的半透明元素可以预先混合好存成一张图只有动态的才用 IPA 实时混合。3.5 颜色键图标去背景的利器颜色键Color Key是指定一个颜色为透明色Blit 时跳过这个颜色的像素。做图标叠加时特别有用——图标背景是纯色比如白色设置白色为颜色键Blit 到背景上时白色部分不覆盖就实现了去背景效果。这比用 Alpha 通道省事因为不需要素材带 Alpha。但缺点是颜色键的颜色不能出现在图标主体里否则主体也会变透明。我一般用亮绿色0x00FF00作为颜色键因为图标里很少用这个颜色。4. 调试实录我遇到的四个真实问题这一节不讲理论只讲我实际调试时遇到的问题和排查过程。这些问题在手册里不一定写但实际项目中很容易碰到。4.1 屏幕花屏从时钟查到 SDRAM 刷新第一次点亮时屏幕花屏满屏彩色噪点。排查顺序是先查时钟用示波器量像素时钟引脚确认频率对不对。我量出来是 33MHz比预期的 30.9MHz 高了调整 PLL 分频后频率对了但花屏依旧。再查时序把时序参数逐个对照手册发现 HBP 填错了改过来后花屏减轻但没消失。最后查 SDRAM用内存测试程序读写 SDRAM发现某些地址读写不一致。查 SDRAM 控制器配置发现刷新周期设得太长导致数据丢失。调整刷新周期后花屏彻底消失。这个问题的根因是 SDRAM 刷新不够TLI 读显存时读到的是失效数据。教训是显存放在 SDRAM 里时SDRAM 控制器的配置比 TLI 本身更关键。4.2 IPA 变慢总线仲裁的坑IPA 单独测试时很快但和 TLI 同时工作时明显变慢。用逻辑分析仪抓总线发现 TLI 读显存和 IPA 读写缓冲区在抢总线IPA 经常被挂起。解决办法是调整总线仲裁优先级。GD32 的总线矩阵可以配置各主设备的优先级我把 TLI 设为高优先级保证刷屏不撕裂IPA 设为中优先级CPU 设为低优先级。这样 TLI 优先拿到带宽IPA 在 TLI 的空隙里干活整体流畅度提升明显。另一个优化是减少 IPA 和 TLI 的地址冲突。如果 IPA 的目标缓冲区正好是 TLI 正在读的显存区域冲突会很严重。我的做法是让 IPA 操作后台缓冲区操作完再切换 TLI 的显存地址双缓冲这样 IPA 和 TLI 操作不同的内存区域冲突大大减少。4.3 颜色偏差字节序和位扩展的联合作用屏幕点亮后颜色整体偏蓝红色显示成暗红。排查发现是两个问题叠加字节序反了TLI 配的是 RGB但屏幕期望 BGR导致 R 和 B 互换。位扩展方式不对RGB565 转 RGB888 时5 位扩展到 8 位的方式是低位补零导致颜色偏暗。第一个问题改TLI_PIXFORMAT解决。第二个问题改成用 IPA 转换时选择复制高位模式颜色亮度就正常了。这两个问题单独出现时都容易查叠在一起就有点绕。4.4 撕裂双缓冲和 VSYNC 同步快速刷新时屏幕出现横向撕裂上半部分是旧画面下半部分是新画面。这是典型的撕裂问题根因是 TLI 正在读显存时CPU 或 IPA 改了显存内容。解决办法是双缓冲 VSYNC 同步。准备两块显存TLI 读 A 的时候IPA 写 BTLI 读完 A 切换到 B 时IPA 再写 A。切换的时机要卡在 VSYNC 中断里确保 TLI 读完一整帧才切换。GD32 的 TLI 支持在 VSYNC 时自动切换显存地址TLI_LxADDR寄存器在 VSYNC 时更新配置好之后就不用 CPU 干预了。这个功能叫地址重载手册里有说明但容易看漏。5. 性能实测TLI IPA 到底能省多少 CPU说了这么多到底效果如何我做了几组对比测试数据如下。操作纯 CPUTLI IPA提升全屏填充 800x48012.5ms0.8ms15.6x图标 Blit 64x640.9ms0.06ms15xAlpha 混合 200x2003.2ms0.3ms10.7x滚动一行 800x48011.8ms0.7ms16.9x测试条件GD32F4 系列200MHz 主频SDRAM 100MHz编译器优化 -O2。纯 CPU 数据是用memcpy和循环填充测的TLI IPA 数据是配置好寄存器后等中断测的。从数据看IPA 的加速比在 10 到 17 倍之间。这个提升在 GUI 场景里非常可观——原来刷一屏要 12.5ms现在 0.8msCPU 占用从 100% 降到 5% 以下剩下的算力可以做业务逻辑。不过要注意这些数据是理想情况下的。实际项目中如果 SDRAM 带宽被其他外设占用或者 IPA 和 TLI 冲突严重提升会打折扣。我的建议是先把总线仲裁配好再做性能测试否则测出来的数据不准。6. 几个容易忽略的配置细节最后分享几个我在调试中总结的细节都是手册里写了但容易忽略的。6.1 显存对齐和 stride 计算TLI 要求显存地址 4 字节对齐IPA 也要求源和目标地址对齐。如果显存宽度不是 4 的倍数每行的起始地址可能不对齐导致访问异常。我的做法是让 stride 始终是 4 的倍数比如 800x324002400 是 4 的倍数没问题但如果宽度是 801801x32403不是 4 的倍数就要把 stride 补到 2404。stride 配置错了图像会斜着显示或者错位。这个坑我在做非标准分辨率时踩过查了很久才发现是 stride 没对齐。6.2 IPA 中断的清除时机IPA 完成时会触发中断中断里要清除标志位。如果清除时机不对会重复进中断或者丢中断。正确的做法是进中断后先读状态寄存器确认是完成中断然后清除标志再处理后续逻辑。我遇到过一次 IPA 中断风暴CPU 一直在进中断出不来。查下来是标志位没清干净硬件一直认为中断没处理完。后来改成先清标志再处理逻辑问题解决。6.3 低功耗模式下的 TLI 和 IPA如果项目有低功耗需求进入低功耗前要先关闭 TLI 和 IPA否则它们会阻止系统进入低功耗模式。GD32 的 TLI 和 IPA 都有独立的使能位关闭后时钟也会自动关断。唤醒后要重新配置 TLI 和 IPA因为它们的状态在低功耗时丢失了。我的做法是把配置代码封装成函数初始化和唤醒后都调用同一个函数避免重复代码。6.4 显存放在内部 RAM 还是 SDRAM如果分辨率小比如 320x240显存可以放内部 RAM速度快、延迟低。但 800x480 的 RGB888 显存 1.1MB内部 RAM 放不下只能放 SDRAM。放 SDRAM 的代价是带宽共享和延迟增加但通过合理的总线仲裁和双缓冲实际体验差别不大。我的建议是能放内部 RAM 就放内部 RAM放不下再考虑 SDRAM。如果必须用 SDRAM一定要把 SDRAM 控制器调好否则 TLI 和 IPA 的性能都会受影响。这套 TLI IPA 的方案我用了大半年从最初的屏幕都点不亮到现在能流畅跑 60Hz 的 GUI中间踩的坑基本都写在这了。核心体会就一句话别用 CPU 干硬件能干的事。TLI 和 IPA 就是为显示和 2D 图形设计的把它们用起来CPU 才能真正去做它该做的事。如果你也在做 GD32 带屏项目建议先把这两个外设的手册章节读透再动手写代码能省下大量调试时间。
返回列表