(2D图形处理的技术其实早在红白机时代就成熟了,6502能做到的事情,Cortex-M自然也不在话下)
【说在前面的话】
在往期的文章《什么是嵌入式系统(下)——沉淀模型》我们曾提到过:
现在的计算机技术差不多领先嵌入式技术大约20年,现在嵌入式系统无论在资源上、理论上还是方法论上,都与上世纪80年代的计算机前沿技术相当。 GorgonMeducer 傻孩子,公众号:裸机思维什么是嵌入式(下)—— “重力”和“沉淀”
这意味着,作为2021年的今天,本世纪初应用在个人电脑上的一些技术也可能会被逐步引入到深度嵌入式系统上(Deep Embedded System)——这类系统的典型也就是大家所熟知的单片机或者说Cortex-M处理器。
这不,刚到四月份Arm就悄悄的、以试探性的态度在Github上发布了一个专门针对“全体” Cortex-M处理器的2D图形加速库——Arm-2D(地址如下):
https://github.com/ARM-software/EndpointAI/tree/master/Kernels/Research/Arm-2D
根据Github仓库中README的描述,我们可以简单的把这个2D图形加速库理解为是一个专门针对Cortex-M处理器的“显卡驱动”标准。虽然这里的“显卡驱动”只是一个夸张的说法——似乎没有哪个Cortex-M处理器“配得上”所谓的显卡,但其实也并没有差多远——因为根据最新的趋势,随着单片机资源的逐步丰富(较高级的工艺节点正在逐步降价),处理器不仅跑得越来越快、存储器越来越大,而且大量的厂商已经或者正在考虑给Cortex-M处理器配备专属的2D图形加速引擎——在这一方面,ST和NXP走的最靠前——相信Chrome-Art(又叫DMA2D)大家早已耳熟能详。实际上,一些芯片公司正在考虑给下一代Cortex-M处理器配备真正的2D-GPU。在这一背景下,也许是为了避开“愚人节项目”的嫌疑,Arm-2D的上线有意避开了4月1号——这传达了一个来自行业的清晰信号:兄弟们,这不是演习,一个属于Cortex-M专2D图形加速的时代到来了。
【Cortex-M的显卡驱动意义何在?】
Arm是行业内 “老” 生态玩家了——几乎所有的行为都会从生态的角度加以考量——所以要想搞清楚为啥Arm在此时为“Cortex-M”定制显卡驱动,就必须要搞清楚整个深度嵌入式生态究竟发生了什么。要想做到这一点,不妨设想一下:
你是一个芯片公司的产品经理:
- 你觉得未来智能手表应用应该很火,或者拥有彩屏的智能IoT终端设备应该很火。
- 考虑到低功耗和低成本,一些产品使用Cortex-M(而不是Cortex-A)来实现应该很合理。于是,你司定义了一款配备有2D图形加速引擎的Cortex-M处理器。
- 芯片很快就做出来了,开发板配上了一个480*272分辨率的触摸屏,结合特别设计的PCB(以及极高识别度的PCB配色)——怎么看都显得高端!大气!上档次!
- 问题来了:
- 市面上完全没有针对单片机(裸机或者RTOS)的第三方2D类跑分软件(专业说法叫Benchmark)——这如何才能体现你硬件的强大呢?测评机构又如何把复杂的2D处理以简单数值的形式呈现在大众面前呢?
- 市面上有那么多第三方GUI提供商,他们都有针对Cortex-M芯片的GUI产品,但我要如何说服他们增加对我的芯片提供支持呢?
你是一个GUI软件提供商:
- 你们之前的产品在Cortex-A以及Linux环境下小有名气。
- 最近看到很多软件公司纷纷瞄准了深度嵌入式市场,提供了定制化的GUI产品,比如微软的GUIX,Qt的Qt for Cortex-M。于是你也很快提供了对应的GUI产品,但问题随之而来:
- 市面上完全没有针对单片机的第三方2D类跑分软件……
- 与Cortex-A以及Linux环境较为规范的软件环境不同,深度嵌入环境碎片化太严重了:
- LCD规格不同
- 连接LCD的硬件接口不同(LCD带宽不同)
- LCD的操作方式不同(有无LCD外设驱动器,驱动器的类型)
- 目标芯片的资源不同、系统频率不同
- 软件环境不同——RTOS千差万别,甚至还有裸机
- 芯片厂家提供的2D图形加速硬件每个都不一样……
- 总结来说,如果要支持一款硬件平台,就要针对它的硬件为其做移植和定制……
- 考虑到团队资源有限,所以能“官方”支持的硬件也有限……
总结来说:
- 芯片厂家以“定制化的”2D图形加速硬件为芯片提供了“差异化”的卖点——但换个角度看其实就是加剧了硬件的碎片化。同时,芯片厂商也苦于找不到大量的GUI协议栈为芯片提供软件支持;
- GUI软件提供商苦于由硬件碎片化所带来的庞大移植工作量。它们希望自己的团队能更多集中精力来开发GUI本身,而不是疲于为新的硬件平台提供“官方支持”
突然有一天,累的吭哧吭哧的两方突然觉得哪里不对劲,然后都默默的把头转向了Arm——这个生态系统中一直号称中立的第三方……
于是Arm在大家灼热的目光下弱弱的在Github上扔了一个叫Arm-2D的显卡驱动标准,提出了这样一个议案:
“要不……我提供一个API抽象层?”
- 芯片厂家:你只要按照我的标准为你的2D加速引擎写好驱动就行了——分分钟获得所有以Arm-2D作为底层的GUI的支持;
- GUI提供商:但凡你GUI中需要用到硬件加速的地方,都可以直接调用我提供的API就行了——如果芯片实际有硬件加速,自然就能得到加速,如果芯片没有硬件加速,那就用我提供的软件优化算法——总的来说,就是一次移植,哪儿哪儿都能用,有没有实际硬件加速都不要紧。
- 对裸机用户:你们也不要着急,我知道你们硬件平台资源紧张——用不起专业GUI协议栈:你们一般要么不用GUI、要么就纯粹自己动手直接写简易的GUI——别担心:Arm-2D也给你们也带来了大礼包,让你们有能力简单的就能实现“那些未曾设想过的道路”。
“什么什么?” 裸机用户惊呼:“不是B2B之间的事情么?咋突然有我们的福利了?”
先卖个关子,文章后面再讲。
【面向深度嵌入式的2D处理跑分】
虽然并没建立第三方2D跑分的意愿,Arm-2D为了展示不同处理器(及不同硬件加速器)在典型GUI负载下的2D处理能力,本着“实在找不到只能硬着头皮自己上”的态度,提供了一个参考的2D性能跑分——官方的名称是:实现30FPS所需的最小处理器频率:Minimal Frequency Required for 30FPS(MHz)。
其实际原理是这样的:
- 建立一个比较典型的2D图形处理负载来模拟GUI日常应用场景中所需的工作量和复杂度。(大约就是下面这个样子):
- 让不同的图层以不同的速度和角度飘来飘去以覆盖更多可能的情况——模拟日常GUI中可能出现的不同复杂度;
- 渲染1000帧,记录下每一帧所消耗的CPU时钟周期数,并提供最小值、最大值和平均值等信息。
- 以帧渲染所需的平均周期数作为依据,计算 “30FPS所需最小处理器频率”——并以这一数值作为跑分结果。
值得说明的是:
- 这一跑分软件在统计“渲染一帧所需的周期数”时并不会把 “从RAM向LCD发送数据”所消耗的时间计算在内——因为“刷新显存”所消耗的时间由芯片和LCD之间的连接方式(或者说传输带宽)决定,而与芯片的2D图形处理能力无关。
如果你真的在意在某种LCD连接方式下系统的实际FPS是多少,只要额外测量“刷新显存”所消耗的时间(比如毫秒),然后追加到渲染一帧所需的毫秒数上即可。比如,在上面GIF所示的效果图中,LCD Latency(也就是“刷新显存”所需时间)为43ms,而平均渲染一帧所需的时间是 4ms(229FPS),综合下来实际一帧的所消耗的时间是 43 4 = 47ms(为了简单,直接按照50ms计算),此时,我们可以认为最终帧率大约为 20FPS(1/50ms)。
- 实际上,经常有小伙伴看到如前面GIF所示的图形效果时会不无讽刺的说:“嗯,很酷炫,我就想知道处理器除了做这个事情还剩下多少能力来处理具体应用。” 其实,“30FPS所需最小时钟频率” 就是为了直观的回答这个问题的。
上面这张表格,列举了大家常用的几类Cortex-M处理器在纯粹依靠“处理其本身”而不借助“专门2D图形加速器”的情况下,以320*240(RGB565)分辨率为目标,达到30帧每秒刷新率所需的最小频率,比如:
- Cortex-M0 需要大约 81MHz 就够了,换句话说:如果你的Cortex-M0 跑133MHz,你还剩下52MHz的CPU性能可以用于应用。
拥有双核Cortex-M0 跑133MHz的树莓派Pico狂喜
- Cortex-M3/M4 大约需要 47MHz,也就是说:如果你的芯片跑个72MHz,就还有大约25MHz用于具体应用(话说,这年月哪个Cortex-M4还不跑个百来十兆的?)
- 值得注意的是,新一代的Cortex-M55在不使用SIMD加速的情况下居然可以跟拥有超标量流水线的Cortex-M7刚正面——都在24MHz以内,实在是出乎意料。
- 而使用了SIMD指令集加速(Helium指令集)的Cortex-M55居然只要8MHz就能达到30FPS的刷新率——着实让人觉得恐怖。
更直观的,这里是官方提供的2D性能的倍率比较(以Cortex-M4的性能为基准)。Cortex-M55 Helium居然是Cortex-M4性能的近乎6倍、Cortex-M0的10倍!——如果说Arm-2D是显卡驱动的话,那么Cortex-M55 Helium就是一款自带“集成显卡”的Cortex-M处理器了。
最后,值得说明的是:Arm-2D在Git仓库中提供了这一跑分的MDK工程——位于“examples/alpha_blending” 路径下。如果你对手边硬件的2D处理性能非常好奇,则只需要提供以下接口,就可以试它一试:
- 一个用于向LCD绘制位图的函数 GLCD_DrawBitmap(),具体请参考:
https://github.com/ARM-software/EndpointAI/blob/master/Kernels/Research/Arm-2D/examples/alpha_blending/display_adapter/display_adapter.c
其函数原型为:
代码语言:javascript复制/**
fn int32_t GLCD_DrawBitmap (uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *bitmap)
brief Draw bitmap (bitmap from BMP file without header)
param[in] x Start x position in pixels (0 = left corner)
param[in] y Start y position in pixels (0 = upper corner)
param[in] width Bitmap width in pixels
param[in] height Bitmap height in pixels
param[in] bitmap Bitmap data
returns
- b 0: function succeeded
- b -1: function failed
*/
int32_t GLCD_DrawBitmap (uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *bitmap);
- 一对定义了LCD实际分辨率的宏:
#define GLCD_WIDTH 320 /* Screen Width (in pixels) */
#define GLCD_HEIGHT 240 /* Screen Height (in pixels) */
最后,需要注意,你手边硬件的跑分结果不一定与官方数据相吻合,因为它们取决于对应平台RAM和Flash的速度、有没有cache之类等环境要素——官方给出的数据是在“SRAM单周期访问”、“程序在RAM中执行”这样较为理想的状况下获得的。
实际上,例子工程已经提供了对好几个硬件平台的支持,比如Arm官方的MPS2平台(https://developer.arm.com/tools-and-software/development-boards/fpga-prototyping-boards/mps2):
MPS3平台(https://developer.arm.com/tools-and-software/development-boards/fpga-prototyping-boards/mps3):
——当然这样的神仙板子一般“寻常百姓”家估计是不会有的。如果你是正版MDK,其实可以直接使用FastModel这样的模拟器来尝鲜(注意:模拟器的跑分是完全不可信的)。
再或者,如果你幸运的拥有官方工程中所支持的STM32F746G-Discovery开发板(搞GUI评估的,谁手里没有一块?)就可以直接编译和下载了。
【面向普通用户的福利是啥】
对使用RTOS和裸机的普通用户来说,可能你的硬件平台是如下的情况:
- 芯片资源很小,大约只有 64K Flash,4~32K SRAM;
- 目标应用对帧率没啥要求,基本上就是用户按个按钮,1秒内有响应(甚至2秒内有响应)都能忍——比如咖啡机啊、一些低端设备的控制面板啊等等——简而言之,帧率在1FPS左右;
- 产品虽然低成本,但是仍然想通过彩屏实现类似智能手机那样的界面:
- 动画就不想了,但界面要好看
- 内部Flash也不大(但可以用QSPI连接外部Flash来保存一些贴图),希望可以用简单的贴图组合的形式实现较为华丽的界面
- 有透明效果(Alpha-blending)
- 能在指定的区域内大规模重复贴图(Texture Paving)
- 能实现不规则窗体
- 能实现光标(通过抠图实现)(Colour-Masking)
- 如果可能,希望屏幕尺寸尽可能大
其实,这样的芯片资源太普遍了——随手拿个Cortex-M0/M3就差不多了。考虑到作为面子工程的LCD屏幕其实也并不太贵——如果能用彩屏实现类似智能手机界面(没有动画)来提升逼格,那产品的竞争力肯定是“岗岗”的。
问题来了,4K~32K SRAM的实在太小了,就算目标屏幕是常见的 320*240 (RGB565)SPI/8080 接口屏,那么一个完整的屏幕缓冲区也要消耗掉 320*240*sizeof(uint16_t)= 150KB 的RAM——完全开销不起。
一般来说,此时我们面前有两个选择:
- 使用LVGL,因为它支持部分缓冲技术(Partial Frame-buffer, PFB)——LVGL允许你用最少“1行的像素缓冲”来实现整个界面的绘制——妥妥的黑科技。
- 如果你用的是STM32,就可以免费使用它所提供的TouchGFX——它也支持PFB技术。
什么?你的应用和FLASH开销不起LVGL或者TouchGFX这样的GUI协议栈?
什么?你买不到STM32芯片?
啥?你用的是国产Cortex-M处理器……
啥,你用的是裸机?而且觉得界面本来就很简单,不想用GUI……
嗯……好吧……
你用Arm-2D吧。因为,它为“无法使用正规GUI协议栈”的用户提供了傻瓜式的PFB支持,且具有以下特性:
- PFB可以为任意尺寸和形状:比如,既可以是LVGL那样的行状PFB(比如320*1),也可以是更为小巧的矩形(比如8*8或8*4之类……越大越快);
- 对支持的屏幕分辨率没有限制——屏幕越大,帧率越低呗……
- 消耗的RAM极端的小(8*8 RGB16 就是128Byte)——以低帧率换低RAM消耗;
- 用户使用Arm-2D的API的时候,可以“假装” 目标缓冲区是完整的——换句话说,用户使用API的时候甚至不用知道是否正在使用PFB,因而不用担心使用PFB会引入额外的界面设计难度;
- 渲染的时候支持局部刷新,从而在某些情况下仍然获得很高的帧率(换句话说:你可以指定只渲染整个界面的某一个小部分)。
比如,下面这个动态进度条效果,其实只刷新了改变的部分,从而在很低的LCD带宽下实现了很高的帧率(>30FPS):
参考信息:
上述效果的硬件环境:
- Cortex-M4,25MHz系统频率,LCD 320*240 RGB565 SPI连接(25MHz外设频率),
- 局部刷新中间改变的区域。帧率 > 30FPS
- 整个工程资源消耗:
Program Size: Code=117236 RO-data=2904 RW-data=108 ZI-data=2516
其中包含:
1KByte 栈,
512Byte 堆,
FPB (160*1*sizeof(uint16_t))=320Byte
工程整体消耗RAM:2624Byte
是不是惊呆了?
例子工程在 “main-arm-2d-more-example” 分支下的example目录中可以找到。
【Arm-2D库怎么用呢?】
Arm-2D库的使用不仅简单直接,官方在document目录下也提供了必要的文档,例如Introduction.md提供了技术综述,How_to_use_tile_operations.md 提供了基本API的详细使用说明,how_to_deploy_the_arm_2d_library.md提供了手把手的移植教程等等。虽然这些文档是英文写的,不过不要紧,因为:
我会在近期以周更的形式,具体一个案例一个案例的为您介绍这些API的使用——力求做到以极小的资源消耗在裸机下实现酷炫的界面效果。例如,下面这个动态进度条的效果,其代码算上类型定义也总共不超过100行。