4. 图形显示缓冲区管理

在RA MCU上驱动图形HMI时,屏幕上的每一个像素最终都来自一块内存区域,这块区域就是帧缓冲区(Frame Buffer)。图形刷新是否流畅、是否出现撕裂,本质上取决于帧缓冲区如何组织、由谁写入、何时切换。本节从显存布局出发,逐步展开单缓冲、双缓冲、三缓冲的差异,并给出在RA MCU上使用SDRAM或内部SRAM作为显存的实际配置思路。

一、帧缓冲区的基本结构

帧缓冲区是一段连续内存,按扫描顺序存放每个像素的颜色值。以常见的RGB565格式为例,每个像素占2字节,若分辨率为480×272,则单帧所需空间为:

480 × 272 × 2 Byte = 261,120 Byte ≈ 255 KB

RA系列MCU内部SRAM容量因型号而异,例如RA6M5拥有512 KB SRAM,理论上可容纳一帧RGB565图像,但若同时运行GUIX、网络协议栈和应用程序,剩余空间会非常紧张。因此,当分辨率提高到800×480或使用RGB888时,通常需要外扩SDRAM作为显存。RA MCU通过外部总线控制器(如RA6M5的BUS模块)连接SDRAM,并将帧缓冲区定位到SDRAM地址空间,例如0x68000000起始区域。

帧缓冲区的关键参数包括:起始地址、宽度(像素)、高度(像素)、像素格式、行跨度(Stride)。行跨度不一定等于宽度×像素字节数,因为硬件可能要求每行按16字节或32字节对齐,以提升LCD控制器或DMA2D的读取效率。配置时若忽略对齐,可能出现图像错位或刷新异常。

二、单缓冲、双缓冲与三缓冲的机制差异

单缓冲只有一块帧缓冲区。CPU或DMA2D直接向这块区域绘制,LCD控制器同时从同一区域读取并扫描输出。当绘制速度与扫描速度不同步时,屏幕上会出现上半部分是新画面、下半部分是旧画面的撕裂(Tearing)现象。单缓冲实现简单、占用内存最少,但只适合静态画面或对撕裂不敏感的场景。

双缓冲使用两块帧缓冲区:前台缓冲(Front Buffer)供LCD控制器读取,后台缓冲(Back Buffer)供CPU/DMA2D绘制。绘制完成后,在垂直消隐期(VBlank)交换前后台指针,使新画面整帧生效。双缓冲消除了撕裂,但若绘制一帧的时间超过一个刷新周期,交换时后台缓冲尚未完成,仍可能出现卡顿或等待。双缓冲需要两倍显存,480×272 RGB565下约510 KB。

三缓冲在前台和后台之外再增加一块待显示缓冲。绘制完成的缓冲进入“就绪”队列,LCD控制器在VBlank时从队列中取最新完整帧。三缓冲进一步解耦绘制与显示,允许CPU在LCD读取某一帧时继续绘制下一帧,适合动画、视频或高帧率UI。代价是显存增至三倍,且需要更精细的队列管理,避免帧序错乱。

单缓冲 / 双缓冲 / 三缓冲 显存与刷新关系 单缓冲 帧缓冲区 FB0 CPU绘制 + LCD读取 同一区域读写冲突 风险:画面撕裂 显存占用:1× 双缓冲 前台 FB0 LCD读取 后台 FB1 CPU绘制 VBlank 交换指针 绘制与显示分离 显存占用:2× 三缓冲 前台 FB0 就绪 FB1 绘制 FB2 VBlank 取最新完整帧 绘制/显示完全解耦 显存占用:3×

三、在RA MCU上配置显存

以RA6M5 + 外部SDRAM + RGB565 LCD为例,显存配置通常包含以下环节。

1. 确定显存位置与容量。若使用SDRAM,需在链接脚本或FSP的Memory配置中将帧缓冲区段定位到SDRAM区域。例如定义fb0、fb1两个数组,并指定段属性:

#define LCD_WIDTH   480
#define LCD_HEIGHT  272
#define FB_SIZE     (LCD_WIDTH * LCD_HEIGHT * 2)

uint16_t fb0[LCD_WIDTH * LCD_HEIGHT] __attribute__((section(".sdram_fb")));
uint16_t fb1[LCD_WIDTH * LCD_HEIGHT] __attribute__((section(".sdram_fb")));

若使用内部SRAM,则需评估剩余空间。RA6M5的512 KB SRAM中,若GUIX占用约120 KB,协议栈占用约80 KB,剩余约300 KB,仅能容纳一帧480×272 RGB565。此时可考虑降低分辨率、使用RGB332,或采用局部刷新策略。

2. 配置LCD控制器或DMA2D。RA MCU通常通过GLCDC(Graphics LCD Controller)驱动RGB接口屏。在FSP中配置GLCDC时,需指定输入帧缓冲区地址、行跨度、像素格式和输出时序。若使用DMA2D加速绘制,则需配置源地址、目标地址、区域大小和颜色格式,使绘制操作直接写入后台缓冲。

3. 实现缓冲切换。双缓冲切换通常在VBlank中断中完成。GLCDC提供垂直消隐中断,可在回调中交换前后台指针:

volatile uint16_t *front = fb0;
volatile uint16_t *back  = fb1;

void glcdc_vblank_callback(display_callback_args_t *p_args)
{
    if (p_args->event == DISPLAY_EVENT_VBLANK) {
        volatile uint16_t *tmp = front;
        front = back;
        back  = tmp;
        R_GLCDC_BufferChange(&g_display0_ctrl, (uint8_t *)front, DISPLAY_FRAME_LAYER_1);
    }
}

三缓冲则需要维护一个就绪队列,在VBlank中判断是否有新帧就绪,若有则切换,否则保持当前帧。队列深度通常为2或3,需防止生产者覆盖尚未显示的就绪帧。

四、无撕裂刷新的判断与验证

验证是否实现无撕裂,可从以下方面观察:

  • 视觉检查:播放快速移动的矩形或滚动文字,若边缘整齐、无水平错位,说明切换与VBlank同步良好。
  • 帧率测量:使用GPIO翻转或DWT计数器统计每秒实际切换次数,与LCD刷新率(如60 Hz)对比。若切换次数明显低于刷新率,说明绘制耗时过长,需优化DMA2D或降低绘制区域。
  • 缓冲状态监控:在调试器中观察前后台指针是否在VBlank中成对交换,避免在非消隐期切换导致撕裂。
三缓冲并非总是优于双缓冲。若绘制一帧的时间远小于一个刷新周期(例如仅2 ms),双缓冲已足够,三缓冲只会增加显存和调度复杂度。选择策略应基于实测的绘制耗时与刷新率之比。
使用SDRAM作为显存时,需注意SDRAM刷新与LCD读取的带宽竞争。若SDRAM带宽不足,可能出现画面闪烁或颜色异常。建议将帧缓冲区放在SDRAM的连续区域,并合理配置总线优先级,确保GLCDC读取不被CPU大量占用。

总结来说,帧缓冲区管理是图形HMI稳定刷新的基础。单缓冲适合静态低要求场景,双缓冲是消除撕裂的通用方案,三缓冲则为高帧率动画提供更充裕的绘制窗口。在RA MCU上,显存可位于内部SRAM或外部SDRAM,关键在于根据分辨率、像素格式和可用内存做出权衡,并通过VBlank同步切换实现无撕裂显示。