AT32F403A试玩-移植LVGL并与ESP32-S3对比
最近被AT32F403A
芯片240MHz的Cortex-M4 CPU与高达224k
的内存吸引,买了几片回来移植了LVGL试玩了一下。本文介绍试玩过程中的感受,并与ESP32-S3对比其LVGL的效果,以及由此引发的一些对MCU从Flash中执行代码的思考。
AT32F403A初步印象
AT32F403A
是雅特力推出的MCU系列,该MCU最初的卖点是封装与二进制兼容STM32F103
系列,我实测了几个程序,包括USB在内的各个外设确实都可以直接兼容,甚至直接用STM32CubeMX选择STM32F103
就可以直接进行开发。
该芯片的亮点,主要有以下几点:
- 相比
STM32F103
大的多的Flash与RAM空间; - 240MHz的Cortex-M4内核,且提供零等待的Flash区域;
- 在小封装上提供了丰富的外设引脚重映射功能,例如48脚封装可以使用SDIO、64脚封装可以使用XMC驱动8位并口屏幕等;
- 外设相比
STM32F103
丰富许多,例如4组SPI(最高支持50MHz
)、2组SDIO、8组USART等。
除此之外,该芯片的价格也比较实惠,我购买样片的价格大约是一片14元,且据说这个价格还是涨价后的价格。如果该芯片能保持这个价格或者更低的价格,那在各路国产32里面,还算比较有竞争力的。
测试所使用的开发板为经典的BluePill(应该是升级版,接口是Type-C的),我将AT32F403ACGT7
直接更换掉原来的STM32F103C8T6
,可以正常使用。
在AT32F403A移植LVGL并测试
AT32F403A
在使用雅特力提供的固件库之后,摇身一变,可以提供超过STM32F4的性能。鉴于其比较高的主频频率与SPI速度,本文就尝试在该芯片上移植LVGL,并测试性能。
在没有特殊说明的情况下,后文中所有测试均基于如下软硬件环境进行:
- 一块
320*240
分辨率、ST7789V
控制器的液晶屏 - CPU均运行于
240MHz
下 - LVGL均采用最新的
v8.2
版本,使用LVGL源码中自带的Demo程序 - 使用双缓冲+DMA的驱动方式驱动屏幕
- 编译器均使用了当前正式最新版的编译器(
Arm Compiler 6.16
与Xtensa GCC 8.4.0-Patch3
) - 编译时均启用最高等级的性能优化(AC6使用
-Ofast
、GCC使用-O2
)
由于有STM32的开发经验,LCD驱动开发与LVGL的移植工作很快便完成了。该工程基于Keil,使用60MHz
的SPI总线频率(虽然手册里写的最高50MHz
,但实测60MHz
也正常运行),具体的移植细节请参看工程。
为了达到最好的成绩,运行程序之前,使用雅特力的ISP工具将芯片的0等待执行Flash大小配置为256k
(该大小同时是芯片出厂默认的大小),并对LVGL进行了裁剪,让整体的二进制程序小于256k
。
程序运行效果如下,可以看到性能还是非常强劲的,加权平均帧率甚至达到了198fps
,这也是我见过的第一款在这个Benchmark结果中Slow but common cases
中获得All good
成绩的MCU。
ESP32-S3的LVGL测试
与此同时,我也请出了我们的老朋友ESP32-S3
与AT32F403A
进行同台竞技。有关于ESP32上移植LVGL的过程,在此就不在赘述了,下面直接给出使用的工程。
对于ESP32-S3,Flash设置为QIO
@80MHz
,Cache保持IDF默认的16k iCache
+32k dCache
,驱动屏幕使用的SPI运行在80MHz
下,同时关闭任务看门狗,将主循环中lv_task_handler()
旁的延时注释掉(我提供的工程中没有注释,测试时则会将其注释掉)。
程序运行效果如下。相比AT32F403A
来说,还是逊色不少的。既然两款芯片都跑在同样的240MHz
频率下,是什么导致的二者性能差距呢?下文中我将对此进行讨论。
由于ESP32-S3
的DMA只支持按字节传输,因此需要在LVGL选项中开启交换高低字节功能,这项功能是有一定性能开销的。上文给出的AT32F403A
的例子未开启交换高低字节功能,得出198fps
的成绩,开启后将得出182fps
的成绩。虽然这并没有造成对比结果的不同,但也请读者了解这一细节。
ESP32全系列的GDMA
模块均只支持按字节传输,只有ESP32-S3
上新引入的并口/RGB LCD控制器才可以使能硬件高低字节交换功能,其他型号的ESP32,在使用I2S驱动8位并口屏幕或SPI驱动串口屏幕时,包括ESP32-S3
在使用SPI接口的串口屏幕时,均需要在LVGL选项中开启交换高低字节功能。
后文中ESP32-S3
的跑分也将在开启交换高低字节功能的前提下进行。
对Flash中执行代码的思考
产生上述的性能差异,除了ARM自家优秀的Arm Compiler
相比GCC
的速度与代码密度优势与ARM内核相比Xtensa内核的IPC优势之外,主要影响性能的还是Flash性能与从Flash中执行代码的效率。
众所周知,最近这几年各种国产32遍地开花,但是一些新公司大多都只能做STM32F103
级别的兼容片,其中一个很大的原因,就是这些公司没有长期积累的Flash加速技术。Flash的速度相对CPU有巨大的差异,从Flash直接执行代码时,就需要插入的等待周期。当CPU速度较快时,需要插入的等待周期会越来越多,导致CPU大部分时间都在空转,性能与运行频率严重不符。
此处以STM32F103
与STM32F407
举例说明。从STM32F103
的手册中可以看到,其CPU运行在72MHz
下时,从Flash中执行代码,需要插入2个等待周期,且只有简单的预取措施。1-2个等待周期对于STM32F103
这种定位的产品,还算可以接受。
但从STM32F407
的手册中可以看到,由于其CPU运行在168MHz
下,因此需要更为夸张的等待周期,最多可达8个CPU周期。
不过,ST在提高CPU频率的同时,引入了基于指令预取队列和分支缓存的自适应实时存储器加速器ART Accelerator
。这很好的解决了Flash等待周期的问题。
那么对于没有ART Accelerator
IP的公司,有没有既提高CPU频率,又能保证从Flash中执行代码效率的方法呢?答案自然是有的。最近的高性能国产32,都不约而同的使用了SRAM来加速Flash代码执行,例如AT32F403A
、CH32V307
、GD32F103
等。
以AT32F403A
举例,这颗芯片片上总共有1M
的Flash与352k
的SRAM。其中,SRAM被分为了两部分,一部分是用户可用的SRAM,另一部分则用作Flash的零等待加速缓存;同时,Flash也被分为了两块,分别是前部的零等待区域与后部的需要等待的区域。零等待的Flash区大小与被划为Flash的缓存区的SRAM大小一致。每次上电的时候,硬件会自动将零等待区域的Flash内容拷贝到SRAM中,然后将Flash前部的零等待区映射到SRAM缓冲区中。
与此同时,AT32F403A
还提供了两种选项,分别是96k
+256k
与224k
+128k
的用户SRAM与0等待Flash区。可以看出,这种方法虽然可以实现真正的0等待执行代码,但却付出了很大的代价:片上过半的SRAM都被用作了加速缓存,不能被用户使用。
同样,对于靠SPI Flash起家的GigaDevice来说,GD32F103
也使用了片外串行NOR Flash+片上同样大小SRAM的结构,如下图所示(来源于zeptobars.com)。芯片在上电时,同样会将Flash中的全部数据读取到内部SRAM中零等待执行,因此其运行速度比STM32F103
会快许多。
而ESP32家族,则采取了另一个方案:外置Flash并使用MMU来XIP,即通过MMU直接从外部串行Flash中就地执行代码。这是由于ESP32物联网芯片的定位,从诞生之初就要考虑到WiFi、IP协议栈等的巨大代码量,因此并未设计片上Flash,而是采用了外挂NOR Flash的方案。
相比直接使用SRAM对Flash的数据进行1:1的缓存,XIP只需要较小的SRAM作为MMU的Cache,但对MMU的设计要求较高,若MMU的性能不佳,则会直接降低整体的性能。
对于本文中使用的ESP32-S3
来说,其相比之前的ESP32芯片,增加了MMU Cache大小的用户配置权限。之前的ESP32芯片,只能够使用固定的16k
指令缓存与32k
的数据缓存。在ESP32-S3
上,用户可以选择16k
/32k
的指令缓存与16k
/32k
/64k
的数据缓存。更大的缓存会减少用户可用的SRAM大小,却可以显著提升性能。
再次测试AT32F403A与ESP32-S3
通过雅特力的ISP软件,将SRAM分区改为224k
+128k
之后,重新进行性能测试。
结果如下图所示。
可以看到,得分发生了大幅度的下滑,其幅度可达40%以上。将大约40%的程序代码移出0等待Flash区域就可以造成如此大的性能降低,这足以表明零等待SRAM的巨大加速作用。对于需要较大内存的用户来说,可能就需要对运行性能与可用内存做出取舍了。
对于ESP32-S3
来说,之前的性能测试使用的是默认的16k
指令缓存与32k
的数据缓存。将指令缓存改为32k
,数据缓存改为64k
后,重新进行测试,结果如下图所示。
性能有大约25%的提升,超过了128k
0等待Flash大小的AT32。
我也同时移植了LVGL的音乐播放器Demo。这个Demo由于有很多图片资源与高级效果,因此固件大小特别大(AT32F403A
上约600k
,ESP32-S3
上约800k
),可以用来测试两款芯片在大容量程序下的表现。测试前,AT32F403A
设置为256k
0等待Flash空间,ESP32-S3
设置32k
指令缓存,下面分别是测试结果。
可以看到,在程序体积大幅超过0等待Flash空间之后,AT32F403A
高性能的CPU几乎被Flash等待所抹平,取得了与ESP32-S3
几乎相同的成绩,且此时ESP32-S3
仅使用了32k
SRAM作为指令缓存。
测试ESP32-S3的极限性能
看到这里的你可能会好奇,ESP32-S3有512k
SRAM空间,要是把程序都塞进SRAM里执行,会不会有很大的性能提升呢?下面就来试试。
关于如何把目标或者库放入SRAM里,ESP-IDF提供了一个很方便的链接器脚本片段工具,用户可以仅编写链接器脚本片段,之后由ESP-IDF内部的工具将其转换为真正的链接器脚本并插入到整个工程的链接器脚本之中,大大方便了用户。
将下列代码保存为linker_fragment.lf
文件,放入components/lvgl
中。
[mapping:lvgl]
archive: liblvgl.a
entries:
* (noflash)
然后修改components/lvgl/env_support/cmake/esp.cmake
文件,在第24行的idf_component_register
的括号中加入LDFRAGMENTS ${LVGL_ROOT_DIR}/linker_fragment.lf
,保存,即可将整个lvgl库链接到SRAM中。
由于ESP32-S3的代码密度较低,因此编译出来的代码会比较大,SRAM中只能放下LVGL库本身,demo代码仍需放到Flash中,因此将components/lvgl/demos
路径移动到components/
下,在其中新建CMakeLists.txt
文件将其加入工程,将其独立于lvgl编译即可。
components/lvgl/demos
位置之后,需要修改demo中的头文件引用路径才可以编译成功。经过上述操作,将LVGL库放入SRAM中之后,得到了如下的成绩。
可以看出,性能有略微的提升,但是提升幅度很小。由此可以得出结论,ESP32-S3
的MMU在开启32k
Cache后,已经可以基本满足CPU的高性能执行。此外的性能差距应该是由于处理器架构不同与编译器的优化效果不同导致的。
总结
本文借ESP32-S3
与AT32F403A
运行LVGL效果的比较,讨论了Flash对芯片运行性能的影响,以及一些芯片对应的解决方案。
ESP32-S3
选择了基于MMU的XIP方案,使用较小的RAM开销实现了较强的性能。其MMU的性能在较大的指令缓存下已经足够满足CPU的运行需要,对于物联网芯片这种需要大容量程序的芯片来说,可以在保证大容量程序执行效率的同时,将更多SRAM资源留给用户,并提高了用户选择Flash大小与型号的灵活性。
AT32F403A
则使用了大量的RAM来实现了Flash上的0等待执行。对于程序代码比较小的通用MCU来说,通过SRAM缓存Flash的做法结合强大的CPU内核性能,提供了令人印象深刻的性能。大容量Flash+SRAM缓存部分代码的做法可以平衡代码容量与代码执行效率,用户可以自行选择哪些代码放入0等待区,哪些代码放入需要等待的区域。同时用户可以自行决定用于缓存Flash代码的SRAM大小,在一些内存不足的应用场合也可以通过牺牲一些性能来换取程序的正常运行。