从一块480×320的3.5寸RGB屏说起吧。之前我用软件模拟8080时序,刷一帧全屏数据大概要90多毫秒,跑LVGL动画时整个界面像PPT翻页,触摸拖动控件根本跟不上手指。后来把总线换成FSMC、再把像素数据交给DMA搬运,同样一块屏,实测LVGL的刷新率直接翻了一倍多,动画才算是真正"滑"起来。这个过程我在STM32F407上完整踩了一遍,CubeMX配置里也埋了好几个默认值陷阱,这篇就把整个改造过程和避坑点一次性说清楚。
FSMC加DMA驱动LVGL并不是什么冷门操作,但网上的教程大多停留在"能点亮屏幕"或者"能跑demo"的层面,真正把时序参数、内存映射、DMA请求握手、LVGL flush回调这些环节串起来讲的很少。这篇文适合已经能用GPIO模拟方式点亮屏幕、想进一步榨干硬件特性的开发者,也适合那些用CubeMX生成工程后,面对FSMC一堆时序参数不知道该怎么调的人。
1. 为什么3.5寸并口屏卡顿:从模拟总线到FSMC的跨越
1.1 软件模拟8080时序的瓶颈在哪里
先复盘一下最初的方案。屏幕是ILI9488驱动IC的3.5寸屏,16位并口,像素格式用RGB565。软件模拟的方式很简单:把RS、WR、RD、CS这些控制脚用GPIO控制,数据线用另外16个GPIO,写一个命令或者数据时,先把数据放到GPIO的输出寄存器,然后拉低WR再拉高,一个写周期就完成了。
#define LCD_WR_FAST() { WR_GPIO->BRR = WR_Pin; WR_GPIO->BSRR = WR_Pin; } // 一次16位数据写入 void LCD_WriteData_GPIO(uint16_t data) { GPIO->BSRR = data; // 把bit映射到ODR的对应引脚 LCD_WR_FAST(); }问题在于,一个像素是2字节,480×320分辨率全屏就是307200字节。每次写一个16位数据,需要执行一次"GPIO赋值+拉低WR+拉高WR",即便编译器优化到极限,单次写周期也要4到6条指令。算下来,纯刷一帧全屏就要60毫秒以上,这还不算命令写入、地址设置、触摸扫描和LVGL本身的开销。
还有一个被很多人忽略的隐藏损耗:取模运算和地址切换。LVGL的flush回调是按屏幕的一个矩形区域来的,每次flush都要重新设置ILI9488的列地址和行地址,然后再连续写入若干像素。地址切换本身不贵,但如果在flush内部还用软件循环一个像素一个像素地写,每一次写都要重新判断边界、做像素格式转换,CPU的时间就全耗在这上面了。
1.2 FSMC把写屏幕变成了写内存
FSMC全称是Flexible Static Memory Controller,在F407上它能直接映射NOR Flash、SRAM、PSRAM这类并行存储设备。LCD的8080接口从时序上看,和SRAM的写操作高度相似,区别只是有没有地址线。LCD这边,寄存器选择靠RS引脚,对应FSMC的地址线A0;数据总线直接挂在FSMC的16位数据线上;WR和RD控制信号对应FSMC的写使能和读使能。
这样一来,最关键的变化产生了:CPU向某个内存地址写一个16位数据,FSMC硬件会在一个总线周期内自动完成整个8080写时序,包括地址建立、数据建立、WR脉冲宽度等。不再需要一条一条指令去翻转GPIO,代码里看起来就是普通的指针赋值:
#define LCD_REG_ADDR ((volatile uint16_t *)0x6C000000) // RS=0的时候是命令 #define LCD_DATA_ADDR ((volatile uint16_t *)0x6C000002) // RS=1的时候是数据 *LCD_REG_ADDR = 0x002A; // 写命令 *LCD_DATA_ADDR = 0x0000; // 写数据这个过程中,FSMC的Bank1第三区域(NE3片选)会把LCD映射到0x6C000000起始的地址空间。A0地址线接到LCD的RS引脚,所以A0=0时写入的是命令,A0=1时是数据。对CPU来说,LCD成了一个16位宽的"内存块",操作系统和编译器那一层根本感知不到后面挂的是屏幕。
之前模拟方式下写一个像素需要几十条指令,现在变成一条STR指令,硬件自动处理完WR时序后,CPU可以直接做下一件事。等到这一步,刷全屏的耗时理论上可以压到几毫秒级别,卡顿的主因就被消除了。
1.3 DMA参与进来后,CPU真正被解放
FSMC把"写像素"从软件循环变成了单条访存指令,但一次flush少则几百字节,多则几万字节,如果全部靠CPU一条一条STR指令往外写,虽然比GPIO模拟快得多,CPU依然要占用不少时间。尤其LVGL在渲染完成之后,flush阶段其实不需要CPU做太多逻辑判断,此时正是DMA切入的最佳时机。
STM32F407的DMA2是挂在外设总线(AHB1)上的,FSMC的写操作也在这个总线上。于是可以这样设计:LVGL渲染好的帧缓冲在内部SRAM(或者外部SRAM)中,DMA把这段缓冲以内存到内存或者内存到外设的模式搬到LCD的数据地址上。
这里有个重要的前提:DMA搬数据到FSMC的LCD地址时,实际上是把内存数据写到0x6C000002这个地址。FSMC的控制器会自动把此次AHB写操作转换成LCD的写时序。也就是说,DMA和外设FSMC之间是一种"地址驱动"的关系,不需要专门的硬件外设请求信号。这和串口DMA、SPI DMA完全不同,串口需要RXNE/TXE事件来触发DMA请求,而FSMC只是AHB总线上的一个从设备,DMA只要写地址,FSMC就自然产生一个外部总线周期。
这种模式下,CPU只需要启动一次DMA传输,然后可以在剩余时间内去处理触摸、计时器回调或者让LVGL继续渲染下一帧的部分内容。等到DMA传输完成中断触发,再把LVGL的flush完成信号发出去。整个"渲染-搬运-刷屏"流水线就建立起来了。
2. CubeMX工程配置全流程与最容易翻车的三处细节
2.1 引脚和FSMC外设的基础配置
F407上FSMC的引脚是固定的,Bank1的NE1到NE4、数据线D0-D15、控制线NOE、NWE、A0-A25,这些引脚不能随便改。3.5寸并口屏实际用到的信号有CS、RS、WR、RD、RST,以及16根数据线D0-D15。用FSMC驱动时,CS接NE片选,RS接A0,WR接NWE,RD接NOE,RST随便找一个GPIO控制。
在CubeMX里,操作路径是:
- 找到"FSMC"外设,使能Bank1 NOR/SRAM Controller,选择Bank1的NE1或NE3(注意3.5寸屏模块上丝印一般会标CS引脚,需要和原理图对应)。
- Memory type选择"LCD Interface",因为ILI9488这类屏的8080时序和标准SRAM略有差异,主要体现在地址建立时间(Address Setup Time)上,LCD模式会自动把读写复用和地址锁存做得更贴合约时序。
- Data bus width选择16 bit。
引脚会自动分配到PG0-PG15这一组数据线以及对应的控制引脚。如果引脚冲突,CubeMX左下角会直接报红色错误,这时候先检查其他外设是不是占用了同样的引脚。
NE片选的选择会影响地址映射。NE1对应0x60000000起始,NE2对应0x64000000,NE3对应0x68000000,NE4对应0x6C000000。上面代码里用的0x6C000000其实就是NE4。如果你在CubeMX里选了NE3,地址就要改成0x68000000,这个对应关系记错了,屏幕肯定点不亮。
2.2 时序参数到底怎么填:HCLK、周期数、实际t值换算
FSMC的时序配置是新手最容易"抄一份配置但不知其所以然"的地方。ILI9488的8080接口写周期时序参数,常见值如下:
| 参数 | 符号 | 典型值 |
|---|---|---|
| 地址建立时间 | tAS | 0~10ns |
| 地址保持时间 | tAH | 0~5ns |
| 数据建立时间 | tDS | 15~30ns |
| 写脉冲宽度 | tWP | 15~30ns |
| 写周期时间 | tWC | 66~100ns |
STM32F407的FSMC时序配置是以HCLK周期数来设置的。HCLK一般是168MHz,一个周期约5.95ns。所以,一个Write Pulse Width如果是3个HCLK周期,大概就是17.85ns,这在ILI9488的范围内是可以接受的。
CubeMX里对应的字段是:
- Address setup time:地址建立,建议1个HCLK周期。
- Address hold time:地址保持,0或者1个HCLK周期。
- Data setup time:数据建立,建议2~3个HCLK周期。
- Bus turn around time:总线周转,一般设0。
- Write pulse width:写脉冲宽度,建议3个HCLK周期。
实际项目中我把Write Operation Timing设成了:
- Address setup:1
- Address hold:1
- Data setup:2
- Write pulse:4
这个组合在IL9488上跑得很稳。FSMC时序余量不能太大,否则刷屏性能会被拖慢,这和CPU超频降压的逻辑有些类似。但也不能太激进,如果设到0和1的组合,在特定的线路长度和电源噪声下可能偶发花屏,这种问题很难复现和排查。
2.3 DMA请求模式:Normal循环还是Memory-to-Memory
CubeMX中DMA配置是另外一个坑。很多人会把LVGL的flush DMA配成"Memory-to-Memory"模式,然后每次flush时调用HAL_DMA_Start,之后再人工等传输完成。这种方案能工作,但效率不是最优,因为你必须在flush函数里忙等或者等待中断,DMA和CPU之间其实没有形成流水。
对于FSMC写LCD数据,更合理的模式是:DMA设置成Memory到Peripheral模式,Peripheral地址固定为LCD数据地址(0x6C000002),Memory地址指向LVGL的flush缓冲,数据宽度16位,方向Memory-to-Peripheral。注意,这个"Peripheral"对FSMC来说不是传统意义上的DMA请求源,而是AHB总线上的写目标地址。CubeMX的DMA配置页面里,外设选"FSMC"可能没有直接选项,此时你可以在"Peripheral"里选择"Memory-to-Memory"的变通方案,或者手动在代码里配置。
我自己更推荐的写法是:不依赖CubeMX的DMA图形化绑定,直接在代码里用LL库或者HAL库初始化一个DMA流,方向为MemoryToMemory,然后在LVGL的flush回调里启动传输。因为我们实际上并不需要一个真正的硬件外设请求信号,MemoryToMemory模式下DMA会连续搬运,直到计数归零。
这里插一个细节:AHB总线上的内存到内存DMA,源地址和目标地址的增量控制很重要。源地址(SRAM缓冲)每次传输后地址递增1(按16位字为单位就是递增2字节),目标地址(LCD数据地址)固定不变。
2.4 CubeMX配置避坑清单
把几个高频翻车点集中列出来,都是我一个人踩过的或者帮别人排查过的:
- 数据宽度不一致。LCD是16位总线,DMA的数据宽度也必须配置成HalfWord(16位)。如果配置成Byte或者Word,搬运出来的像素字节序会错乱,画面出现颜色通道互换或整个色调偏色。
- Cache问题。F407没有D-Cache,但是在某些板载SRAM或外部SDRAM方案里会存在写缓冲,导致DMA搬运后数据没有及时写出。不过F407内部SRAM一般不会有这个问题,真正严重的是后面接SDRAM时的Cache一致性,这里先提一句。
- LCD模块上的电平转换芯片。很多3.5寸屏模块板载了74LVC4245之类的电平转换器,DIR和OE引脚需要额外接GPIO拉高或拉低,不接的话数据方向锁死,FSMC写入全被吞掉。这类问题和FSMC本身无关,但排障时最容易忽视。
- NE片选和FSMC地址线的对应。A0接RS之后,命令地址是0x6C000000,数据地址要加2,不是加1。因为FSMC按字节寻址,但LCD是16位设备,所以数据地址是基地址加2字节偏移。这个"偏移到底是1还是2"的问题每次都有人问。
3. LVGL端口层重构:FSMC+DMA协同工作的核心逻辑
3.1 平铺帧缓冲用单缓冲还是双缓冲
LVGL在stm32上的移植,一般会分配一个全屏帧缓冲或者若干行缓冲。全屏缓冲的好处是可以在flush回调里一次性把整帧或者一个大矩形交给DMA,坏处是如果缓冲放在内部SRAM,会挤占LVGL的渲染内存。F407的内部SRAM一共128KB(112KB+16KB),一个480×320 RGB565缓冲区就是300KB,显然放不下全屏。
因此常用的方案是:
- 单缓冲 + 部分刷新:LVGL只分配一个16KB左右的行缓冲,每次flush一行或者几行。这个方案内存占用最小,但DMA每次搬运的数据量很小,对FSMC的连续写优势发挥不出来。
- 多行缓冲:分配8到16行的缓冲,LVGL渲染完这些行后一次性flush,DMA搬运的数据量增大,效率更高。实测在3.5寸屏上,行缓冲从8行增加到24行,帧率提升明显。
- 双缓冲 + DMA后台搬运:两个缓冲交替,一个给LVGL渲染,另一个交给DMA搬运。渲染和传输真正并行,流畅度最好,但内存开销也最大。
对于F407这款芯片,我会建议在MDK或者CubeIDE的链接脚本里,把LVGL的绘图缓冲放到CCM RAM(0x10000000)之外的空间,因为DMA无法访问CCM RAM。很多人直接把LVGL缓冲定义在默认的SRAM里其实没问题,但如果你尝试优化时把缓冲塞进CCM RAM,就会遇到"LVGL渲染正常但屏幕不刷新"的诡异现象,根源就是DMA压根读不到CCM RAM的数据。
3.2 flush回调的正确姿势
LVGL的disp_flush_cb需要做到:
- 把绘制缓冲的地址和区域信息交给DMA。
- 启动DMA传输。
- 在DMA传输完成中断里,调用
lv_disp_flush_ready(disp)通知LVGL该缓冲可以继续使用。 - flush回调返回前,不能让LVGL再次改写这块缓冲。
static void disp_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t addr = (uint32_t)color_p; lcd_set_window(area->x1, area->y1, area->x2, area->y2); // 如果配置成MemoryToMemory模式 HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, (uint32_t)addr, (uint32_t)LCD_RAM_ADDR, ((area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1))); }这段代码里有个非常关键的点:lcd_set_window必须在DMA启动之前完成。因为DMA搬运的数据是线性的一长串,LCD的列地址和行地址寄存器只有在设置窗口之后,后续写入的数据才会被分配到对应区域。如果顺序反过来,DMA数据虽然被刷进了LCD的GRAM,但是写到了上一次设置的窗口里,画面会错乱甚至出现"条纹平铺"的效果。
另一个点是,HAL_DMA_Start_IT和LVGL的缓冲区生命周期管理非常容易冲突。我在第一次接入DMA时,直接在flush回调里启动DMA后立即返回,LVGL会认为缓冲区可以继续渲染,但实际上DMA还在读取这块内存。典型的症状就是画面偶尔出现残影或撕裂。正确的做法是:同一个缓冲在使用DMA搬运期间,LVGL不能修改它。所以flush回调里必须设置一个flushing标志,在DMA完成中断里清除这个标志并调用lv_disp_flush_ready。
volatile bool flushing = false; void DMA2_Stream0_IRQHandler(void) { if (HAL_DMA_GetITSource(&hdma_memtomem_dma2_stream0, DMA_IT_TCIF)) { CLEAR_BIT(hdma_memtomem_dma2_stream0.Instance->HISR, DMA_LISR_TCIF0); flushing = false; lv_disp_flush_ready(&disp_drv); } }3.3 移植后LVGL的显存配置细节
LVGL的lv_conf.h里有几个关键宏需要确认:
LV_COLOR_DEPTH设为16,匹配RGB565。LV_COLOR_16_SWAP:这个宏控制字节序,如果屏幕显示颜色错乱,蓝红交换,可以试着打开或者关闭。FSMC是16位并口直连,一般不需要SWAP,但如果你中间加了串转并的IC或者屏模块做了字节交换,就需要调整。LV_MEM_SIZE:LVGL动态内存池的大小,建议至少16KB。如果开了较多控件或者字体,可以加到32KB。LV_DISP_DEF_REFR_PERIOD:默认30ms,即刷新率约33fps。当你把FSMC+DMA打通后,这个值可以往下降了,比如设到15ms甚至10ms,LVGL会尝试以更高频率刷新脏矩形区域。
LV_DPI也很重要,它影响控件尺寸和触摸坐标映射。3.5寸屏一般设LV_DPI为100左右,如果控件显示太小,可以适当提高到120。这个参数不直接和流畅度相关,但对使用体验影响很大。
3.4 为什么说FSMC+DMA的组合对LVGL是"天然搭档"
LVGL的渲染机制是:它维护一组"脏矩形",不需要的时候不重绘。一次重绘可能只涉及一个小按钮的范围,比如64×64像素,那么数据量只有8KB。这个数据量交给DMA搬运,在168MHz的F407上大概几十微秒就完成。此时CPU几乎完全空闲,LVGL可以马上进入下一帧的渲染,动画的帧率上限取决于渲染耗时,而不是总线写屏耗时。
FSMC的写入速度上限取决于你配置的时序参数。如果用上面说的Write Pulse 4,HCLK周期约5.95ns,实际写周期大概40ns。32KB数据搬完,理论耗时约1.3ms,这里面既有DMA搬运的时间,也有FSMC时序列的消耗。相比之前模拟方式的几十毫秒,性能提升自然就是"翻倍"级别的。
这套方案最大的价值是:LVGL动画的瓶颈从"写屏"转移到了"渲染"。而渲染这一块,LVGL本身有软件优化,配合Cortex-M4的FPU和适当的编译器优化,480×320分辨率的简单界面跑到45到50帧并不是不可能。
4. 实测数据与踩坑记录:流畅度翻倍背后的真实代价
4.1 实测帧率和CPU占用,别只看跑分
我用的测试场景是LVGL官方的lv_demo_widgets,以及一个自己做的带滑动列表、图表和开关控件的界面。测试环境:
- STM32F407VET6,HCLK 168MHz。
- 外部25MHz晶振,PLL倍频到168MHz。
- ILI9488驱动的3.5寸TFT,FSMC 16位并口。
- LVGL版本8.3,编译器为ARM Compiler 6。
改动前后的数据:
| 方案 | 全屏刷色耗时 | LVGL帧率(简单动画) | CPU占用率(动画运行时) |
|---|---|---|---|
| GPIO模拟8080 | 约85ms | 12~15 fps | 高,接近满负荷 |
| FSMC直接写(无DMA) | 约5ms | 26~30 fps | 中等,约50% |
| FSMC + DMA搬运 | 约2.5ms(DMA阶段) | 40~50 fps | 低,约25% |
注意第二行到第三行的变化比较隐蔽。FSMC直接写时,LVGL帧率已经很不错,但CPU占用率还是偏高。加了DMA之后,帧率看起来只从30提高到了45左右,没有"翻倍"那么夸张,但CPU占用率显著下降。这些释放出来的CPU时间可以被触摸扫描、通信协议栈或者业务逻辑利用,这是"流畅度翻倍"的另一层含义。
如果只盯着帧率看,一些demo可能提升不到一倍。但在包含大量控件、同时还有串口通信或者ADC采样的真实项目里,多出来的CPU余量直接决定了系统的实时性。这是我个人认为FSMC+DMA最有价值的收益点。
4.2 花屏问题排查实录:DMA传输被中断抢占
我在跑lv_demo_music时遇到一个很诡异的偶发花屏,不是每次都复现,但一旦出现,屏幕下半部分会出现水平方向的错位条纹。
排查了一个下午,最后用逻辑分析仪抓WR和NWE信号,发现一个问题:DMA正在搬运的过程中,系统滴答定时器中断打入,中断服务函数正好里调用了HAL_IncTick等短操作,这本身不影响DMA。但如果此时其他高优先级中断(比如串口中断)执行时间较长,FSMC总线上可能会出现"被打断"的现象——不是FSMC被真正打断,而是DMA请求优先级和CPU访问FSMC发生总线竞争,导致同一帧数据里混入了几条延迟的写操作。
这种偶发问题的最简单处理办法:
- 把DMA中断优先级设置成最高,确保搬运完成时能及时通知LVGL。
- 在启动DMA前,临时屏蔽掉不必要的高频中断,或者在中断服务函数里不要做耗时操作。
- 给LCD的CS信号加一个RC滤波,减少总线竞争导致的信号毛刺。
最后我选择的是方案2和3的组合。把DMA中断放在优先级分组2的最高抢占优先级,串口中断放低一级,同时给CS加了一个100Ω电阻和10pF电容的RC滤波,花屏问题消失。
4.3 触摸和显示坐标不同步,也是DMA造成的?
系统升级到FSMC+DMA之后,出现了触摸位置和显示图标错位的现象,触摸点在屏幕上方,实际高亮的控件在下方。一开始以为是触摸校准参数被改动,重新校准了多次还是不行。
后来发现,原因是屏幕的扫描方向和LCD_RST拉升时序。FSMC写入速度变快之后,如果上电初始化时序太快,ILI9488在某些批次下会进入错误的扫描模式。以前模拟GPIO方式初始化慢,给了屏足够的稳定时间,不会触发这个问题。换成FSMC后,如果用HAL_Delay只给了10ms的复位时间,在低温或者电压偏低的场景下,屏控制器偶尔会没完成内部初始化,导致GRAM的X/Y寻址方向和触摸面板的坐标方向不一致。
解决办法是把LCD_RST的低电平时间从10ms增加到50ms,并且在初始化命令sequence结束后追加20ms的空闲时间。这个时间成本在初始化阶段完全可以接受,但能省掉后续"显示错位"这种极其迷惑的排查过程。
4.4 另外一个容易被忽视的性能杀手:LVGL的内存碎片
在优化完FSMC+DMA之后,有一段时间我发现动画帧率会随着运行时间慢慢下降。比如刚开机跑45fps,运行5分钟后变成30fps,再过一会儿又恢复,呈周期性的波动。
这个现象一度让我怀疑是DMA没有得到正确的释放,后来用lv_mem_monitor打印内存状态,发现LVGL的动态内存碎片率在某个控件频繁创建删除后飙升到30%以上。LVGL的内存分配策略在小内存设备上需要谨慎设计,LV_MEM_SIZE设得太小,碎片率会快速升高。把LV_MEM_SIZE从默认的16KB提高到32KB,并把LV_MEM_ATTR设成在SRAM的连续区域,同时避免在动画回调里频繁创建临时控件,这个问题就缓解了。
LVGL的内存碎片和FSMC没有直接关系,但它会反噬你辛辛苦苦省下来的CPU余量。性能优化是一个整体工程,单点提速并不能保证最终体验。
4.5 如果效果还是不够好,下一步还能怎么榨性能
FSMC+DMA只是第一步,再往下走,有几个方向值得尝试。
一个是开启LV_USE_PERF_MONITOR,它在屏幕上显示实时的帧率和CPU占用率,这个宏在lv_conf.h里,日常开发建议一直开着,能直观看到优化是否有效果。
另一个是使用外部SRAM。F407可以通过FSMC同时挂载LCD(一个NE片选)和外部SRAM(另一个NE片选),把LVGL的绘制缓冲放到外部SRAM,这样内部SRAM的压力大大降低。但因为外部SRAM的访问速度比内部SRAM慢,DMA从外部SRAM搬运数据到LCD地址时,FSMC和DMA都挂在AHB上,总线竞争会增加,实际性能提升可能不如预期。这时可以尝试让DMA从外部SRAM搬运,LVGL渲染缓冲也放外部SRAM,CPU的取指和数据访问则尽量留在内部SRAM。
还可以尝试把LVGL的disp_drv的full_refresh配置打开,强制全屏刷新而不是脏矩形刷新。这在某些场景下可以减少窗口切换时的撕裂感,但会牺牲一定性能,适合在动画复杂度较低时使用。
结语:一点个人实操体会
整个改造做下来,我最深的体会是,FSMC+DMA在F407上不是单一的外设配置问题,而是"总线架构、时序参数、LVGL缓冲区生命周期管理、中断优先级"四件事的串联。任何一个环节没对齐,屏幕要么不亮,要么亮了但性能不稳定。如果你正在做类似移植,建议按照"先FSMC点亮屏幕、再CPU直接写屏、最后上DMA"的顺序一步步来,每走一步都在LVGL的perf monitor里记录数据,确认当前步的性能基线,再进入下一步。这样出了问题,能快速定位是FSMC配置、DMA配置还是LVGL对接的锅。
最后分享一个小技巧:调试FSMC时序时,不要用LVGL的动画界面来观察流畅度,直接写一个全屏刷色循环,用示波器或者逻辑分析仪抓WR引脚的频率。全屏刷色频率直接反映FSMC的写吞吐量,这个是硬指标,排除了LVGL渲染和脏矩形优化的干扰。硬指标达标了,再去调LVGL层的缓冲大小和刷新策略,效果立竿见影。