目录

AT32F403A试玩-移植LVGL并与ESP32-S3对比

最近被AT32F403A芯片240MHz的Cortex-M4 CPU与高达224k的内存吸引,买了几片回来移植了LVGL试玩了一下。本文介绍试玩过程中的感受,并与ESP32-S3对比其LVGL的效果,以及由此引发的一些对MCU从Flash中执行代码的思考。

AT32F403A初步印象

AT32F403A雅特力推出的MCU系列,该MCU最初的卖点是封装与二进制兼容STM32F103系列,我实测了几个程序,包括USB在内的各个外设确实都可以直接兼容,甚至直接用STM32CubeMX选择STM32F103就可以直接进行开发。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/at32f403a-datasheet.jpg
AT32F403A的数据手册

该芯片的亮点,主要有以下几点:

  • 相比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,可以正常使用。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/at32f403acgt7-on-bluepill.jpg
换到BluePill开发板上的AT32F403ACGT7

在AT32F403A移植LVGL并测试

AT32F403A在使用雅特力提供的固件库之后,摇身一变,可以提供超过STM32F4的性能。鉴于其比较高的主频频率与SPI速度,本文就尝试在该芯片上移植LVGL,并测试性能。

硬件与编译配置

在没有特殊说明的情况下,后文中所有测试均基于如下软硬件环境进行:

  • 一块320*240分辨率、ST7789V控制器的液晶屏
  • CPU均运行于240MHz
  • LVGL均采用最新的v8.2版本,使用LVGL源码中自带的Demo程序
  • 使用双缓冲+DMA的驱动方式驱动屏幕
  • 编译器均使用了当前正式最新版的编译器(Arm Compiler 6.16Xtensa GCC 8.4.0-Patch3
  • 编译时均启用最高等级的性能优化(AC6使用-Ofast、GCC使用-O2

 AT32F403A+LVGL8.2跑分工程

由于有STM32的开发经验,LCD驱动开发与LVGL的移植工作很快便完成了。该工程基于Keil,使用60MHz的SPI总线频率(虽然手册里写的最高50MHz,但实测60MHz也正常运行),具体的移植细节请参看工程。

为了达到最好的成绩,运行程序之前,使用雅特力的ISP工具将芯片的0等待执行Flash大小配置为256k(该大小同时是芯片出厂默认的大小),并对LVGL进行了裁剪,让整体的二进制程序小于256k

程序运行效果如下,可以看到性能还是非常强劲的,加权平均帧率甚至达到了198fps,这也是我见过的第一款在这个Benchmark结果中Slow but common cases中获得All good成绩的MCU。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/at32f403a-benchmark-256k-0wait.jpg
AT32F403A在256k零等待ROM下的LVGL跑分成绩

ESP32-S3的LVGL测试

与此同时,我也请出了我们的老朋友ESP32-S3AT32F403A进行同台竞技。有关于ESP32上移植LVGL的过程,在此就不在赘述了,下面直接给出使用的工程。

 ESP32S3+LVGL8.2跑分工程

对于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的跑分也将在开启交换高低字节功能的前提下进行。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/esp32s3-benchmark-16k-icache.jpg
ESP32-S3在16k指令Cache下的LVGL跑分成绩

对Flash中执行代码的思考

产生上述的性能差异,除了ARM自家优秀的Arm Compiler相比GCC的速度与代码密度优势与ARM内核相比Xtensa内核的IPC优势之外,主要影响性能的还是Flash性能与从Flash中执行代码的效率。

众所周知,最近这几年各种国产32遍地开花,但是一些新公司大多都只能做STM32F103级别的兼容片,其中一个很大的原因,就是这些公司没有长期积累的Flash加速技术。Flash的速度相对CPU有巨大的差异,从Flash直接执行代码时,就需要插入的等待周期。当CPU速度较快时,需要插入的等待周期会越来越多,导致CPU大部分时间都在空转,性能与运行频率严重不符。

此处以STM32F103STM32F407举例说明。从STM32F103的手册中可以看到,其CPU运行在72MHz下时,从Flash中执行代码,需要插入2个等待周期,且只有简单的预取措施。1-2个等待周期对于STM32F103这种定位的产品,还算可以接受。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/stm32f1-flash-wait-cycles.png
STM32F1参考手册中对Flash等待周期的描述

但从STM32F407的手册中可以看到,由于其CPU运行在168MHz下,因此需要更为夸张的等待周期,最多可达8个CPU周期。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/stm32f4-flash-wait-cycles.png
STM32F4参考手册中对Flash等待周期的描述

不过,ST在提高CPU频率的同时,引入了基于指令预取队列和分支缓存的自适应实时存储器加速器ART Accelerator。这很好的解决了Flash等待周期的问题。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/stm32f4-flash-art-accelerator.png
STM32F4参考手册中对ART Accelerator的描述

那么对于没有ART AcceleratorIP的公司,有没有既提高CPU频率,又能保证从Flash中执行代码效率的方法呢?答案自然是有的。最近的高性能国产32,都不约而同的使用了SRAM来加速Flash代码执行,例如AT32F403ACH32V307GD32F103等。

AT32F403A举例,这颗芯片片上总共有1M的Flash与352k的SRAM。其中,SRAM被分为了两部分,一部分是用户可用的SRAM,另一部分则用作Flash的零等待加速缓存;同时,Flash也被分为了两块,分别是前部的零等待区域与后部的需要等待的区域。零等待的Flash区大小与被划为Flash的缓存区的SRAM大小一致。每次上电的时候,硬件会自动将零等待区域的Flash内容拷贝到SRAM中,然后将Flash前部的零等待区映射到SRAM缓冲区中。

与此同时,AT32F403A还提供了两种选项,分别是96k+256k224k+128k的用户SRAM与0等待Flash区。可以看出,这种方法虽然可以实现真正的0等待执行代码,但却付出了很大的代价:片上过半的SRAM都被用作了加速缓存,不能被用户使用。

同样,对于靠SPI Flash起家的GigaDevice来说,GD32F103也使用了片外串行NOR Flash+片上同样大小SRAM的结构,如下图所示(来源于zeptobars.com)。芯片在上电时,同样会将Flash中的全部数据读取到内部SRAM中零等待执行,因此其运行速度比STM32F103会快许多。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/gd32f103cb-die-shot.jpg
GD32F103CB的晶圆摄像

而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之后,重新进行性能测试。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/atrery-isp-tool.png
雅特力的ISP工具

结果如下图所示。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/at32f403a-benchmark-128k-0wait.jpg
AT32F403A在128k零等待ROM下的LVGL跑分成绩

可以看到,得分发生了大幅度的下滑,其幅度可达40%以上。将大约40%的程序代码移出0等待Flash区域就可以造成如此大的性能降低,这足以表明零等待SRAM的巨大加速作用。对于需要较大内存的用户来说,可能就需要对运行性能与可用内存做出取舍了。

对于ESP32-S3来说,之前的性能测试使用的是默认的16k指令缓存与32k的数据缓存。将指令缓存改为32k,数据缓存改为64k后,重新进行测试,结果如下图所示。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/esp32s3-benchmark-32k-icache.jpg
ESP32-S3在32k指令Cache下的LVGL跑分成绩

性能有大约25%的提升,超过了128k0等待Flash大小的AT32。

我也同时移植了LVGL的音乐播放器Demo。这个Demo由于有很多图片资源与高级效果,因此固件大小特别大(AT32F403A上约600kESP32-S3上约800k),可以用来测试两款芯片在大容量程序下的表现。测试前,AT32F403A设置为256k0等待Flash空间,ESP32-S3设置32k指令缓存,下面分别是测试结果。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/at32f403a-music-demo.jpg
AT32F403A运行音乐播放器Demo的帧率

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/esp32s3-music-demo.jpg
ESP32-S3运行音乐播放器Demo的帧率

可以看到,在程序体积大幅超过0等待Flash空间之后,AT32F403A高性能的CPU几乎被Flash等待所抹平,取得了与ESP32-S3几乎相同的成绩,且此时ESP32-S3仅使用了32kSRAM作为指令缓存。

测试ESP32-S3的极限性能

看到这里的你可能会好奇,ESP32-S3有512kSRAM空间,要是把程序都塞进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中之后,得到了如下的成绩。

https://img.yuanze.wang/posts/at32f403a-esp32s3-lvgl-compare/esp32s3-benchmark-in-iram.jpg
ESP32-S3在将LVGL放入SRAM后的LVGL跑分成绩

可以看出,性能有略微的提升,但是提升幅度很小。由此可以得出结论,ESP32-S3的MMU在开启32kCache后,已经可以基本满足CPU的高性能执行。此外的性能差距应该是由于处理器架构不同与编译器的优化效果不同导致的。

总结

本文借ESP32-S3AT32F403A运行LVGL效果的比较,讨论了Flash对芯片运行性能的影响,以及一些芯片对应的解决方案。

ESP32-S3选择了基于MMU的XIP方案,使用较小的RAM开销实现了较强的性能。其MMU的性能在较大的指令缓存下已经足够满足CPU的运行需要,对于物联网芯片这种需要大容量程序的芯片来说,可以在保证大容量程序执行效率的同时,将更多SRAM资源留给用户,并提高了用户选择Flash大小与型号的灵活性。

AT32F403A则使用了大量的RAM来实现了Flash上的0等待执行。对于程序代码比较小的通用MCU来说,通过SRAM缓存Flash的做法结合强大的CPU内核性能,提供了令人印象深刻的性能。大容量Flash+SRAM缓存部分代码的做法可以平衡代码容量与代码执行效率,用户可以自行选择哪些代码放入0等待区,哪些代码放入需要等待的区域。同时用户可以自行决定用于缓存Flash代码的SRAM大小,在一些内存不足的应用场合也可以通过牺牲一些性能来换取程序的正常运行。