19

傻孩子(GorgonMeducer) · 2021年04月26日

为什么说Arm-2D是小资源单片机的GUI人权卡!

首发:裸机思维
作者: GorgonMeducer 傻孩子

image.png
【Arm-2D究竟算什么?屁股决定脑袋】


自从Arm低调发布Arm-2D以来,小范围内引起了一些讨论,诸如:

"Arm-2D是要抢GUI的饭碗么?"

“Arm-2D为什么带了一些GUI的功能?”

“Arm-2D也是for循环里套像素处理,也没什么黑科技啊!”

“Arm-2D究竟是什么?有没有搞头”

……

之类直指要害的质疑涌现出来。其实,这里的核心问题可以被拆解为如下的逻辑:

  • Arm-2D要服务的目标客户是谁?
  • 这个目标客户的痛点是什么?
  • Arm-2D为目标痛点究竟有没有提供切实的帮助?

实际上,不光很多明眼人看得出来,Arm-2D自己也在仓库的README里写的明明白白:

"Arm-2D focuses on accelerating the low-level 2D image processing... will not compete with the GUI service providers in the Arm ecosystem. In fact, because Arm has proposed a unified set of low-level acceleration APIs, a full ecological level of cooperation can be quickly established between chip manufacturers that provide hardware accelerators and software providers that provide GUI services. Everyone can concentrate on their own works: For example, chip manufacturers can ensure that they receive a wide range of software support by adding drivers for their dedicated 2D accelerators following the Arm-2D standard, and GUI providers only need to build their GUI stack upon Arm-2D APIs; hence a wide range of device support is ensured."

翻译过来:

"Arm-2D致力于在底层加速2D图像处理...不会与Arm生态系统中的GUI服务提供商竞争。实际上,由于Arm提出了一套统一的底层加速API,因此可以在“提供硬件加速器的芯片制造商”与“提供GUI服务的软件提供商”之间快速建立完整的生态级合作——每个人都可以专注于自己的工作:例如,芯片制造商可以通过提供遵循Arm-2D标准的专用2D加速器驱动程序来确保获得广泛的GUI软件支持;而GUI提供商仅需要在 Arm-2D API基础上构建GUI就可以确保获得广泛的芯片支持。"

根据前面的拆解,显然Arm-2D要服务的目标客户包含了半导体厂商和GUI的软件服务提供商,而痛点可以简单的表述为:

  • 芯片厂商和GUI服务提供商很多;
  • 它们彼此都想获得对方的支持;
  • 这是一个多对多的合作问题;
  • 如何将原本点对点的合作方式变成点对面的合作方式,或者说有没有什么一劳永逸的方法,一下节省所有人的时间——就是Arm-2D所要解决的问题。

作为应对,Arm提供的方案就是一个“公共”的API集合——如果大家都按照这一集合来开发就自然而然地实现了一个全生态级别的合作。常言道:“如人饮水冷暖自知”——Arm-2D的想法虽然好,但实际市场接受度如何,还要看这些“受益者”自己怎么觉得了

那么,作为“吃瓜群众的我们”是不是可以进一步理解为:Arm-2D服务的是芯片厂商和GUI服务提供商,而且Arm-2D提供的便利对普通的GUI用户来说是不可见的。

既然如此:Arm-2D“关我屁事”?散了散了。

然而,事实并非如此。

【普通开发者也是目标客户】


如果草率的判断 Arm-2D 只是嵌入式版本的 Direct2D,而与普通开发者无关那就大错特错了。由于嵌入式系统不同于类似PC机这样的通用计算机系统——需要在满足应用需求的情况下尽可能地进行裁剪(不了解这一逻辑的小伙伴可以阅读文章《什么是嵌入式系统(上)—— “为用而专”》),因此长期存在一批需要在“蚊子腿上刮肉”、“螺蛳壳里做道场”的贫下中农——这些开发者受到成本的压迫,不的不在资源极端受限的环境下进行开发:

一块32~64K Flash4~32K SRAMCortex-M0/M3/M4单片机就是面朝黄土背朝天的码农们每天996所要耕耘的土地。

另一方面,由于习惯了智能手机所建立的“事实上的图形交互标准”,很多嵌入式产品也试图模仿这样一套图形交互机制来凸显自身的“智能”“高端”——毕竟“看起来”、“用起来”都像手机一样,就显得“高端大气上档次”了嘛。

通过观察市场,容易注意到这样的事实:一方面,一个带彩色大屏的咖啡机肯定不会比按钮式的咖啡机做出来的咖啡更好喝(都是胶囊,好意思么?),但另一方面,在消费者心中“看起来高端”的咖啡机,做出来的咖啡似乎真的似乎更好喝了呢。(是添加了智商税的“咸味”么?)

这里可能会有小伙伴吐槽:傻孩子,你不要凡尔赛了,奶茶我都快喝不起了,你跟我说什么咖啡机?

当然,我只是举个例子,我家里也没有咖啡机(我喝白开水),但这里的消费“升级”现象是真实的——一方面厂家尝试在竞争中引入更多“智能交互”的“噱头”(哪怕只是彩屏触摸按个按钮),另一方面,理性的用户也希望能更多“白嫖”这样的功能提升。这里的博弈逻辑就是:

  • 消费者知道加个触摸屏其实没啥卵用,服务本质没变
  • 消费者也知道,加个屏幕看起来更高大上,脸上有面子
  • 所以消费者希望,厂家最好白送这个面子工程——也就是白嫖
  • 厂家很清楚面子工程有用(增加竞争力),但消费者愿意多付出的成本有限(毕竟没有实质性改善服务);
  • 最终厂家愿意投入在产品上的成本并不会增加多少——具体表现为,简单的给电路板加个廉价的彩色LCD,芯片都不会换的

老板动动嘴……工程师跑断腿

以下是嵌入式小剧场


小王:老板,这 320 * 240 RGB16 的屏幕光缓冲就要 150KB,咱们芯片的RAM总共才32K,不够用啊

老板:不是有那什么LVGL么?支持部分缓冲技术(Partial Framebuffer,PFB),不需要完整缓冲,你看这效果多好?

image.png

(图片来源于LVGL: https://lvgl.io/)

小王:老板,人家最小配置要 64K Flash,8K RAM,我们应用原本就塞得差不多了,哪有这个富裕啊?就算有空间,你不还寻思着要做功能升级么?

image.png

老板既然人家能用PFB做到,说明小资源环境是绝对能够做到的。那么,你就可以考虑参考LVGL的原理,自己写一套?

小王:(心里想,你给我多少钱工资啊,PFB是那么容易能做到的么?裸机实现成熟GUI的效果?你咋不上天呢?)……

老板:我也不是要为难你,你看,我们的确不需要多复杂的界面,可能就是几个按钮,几个进度条不得了了。不是有那句话么?8帧不卡,9帧流畅,10帧电竞……实际上我们也不需要动画或者高帧率啊。用户按下按钮,2秒内有响应他们就很开心了

小王:就是说我要自己绘制界面咯?

老板:可以贴图啊!事先存好几个图片放在外部FLASH里,用的时候直接刷到LCD里不就行了?我也不是抠门,给你再加个外部FLASH吧,你看咱们屏幕就那么点大,8M空间的还要啥界面绘制?

小王:那我就全贴图咯?

老板也不能全贴图啊!什么半透明啊,光标动画啊,按钮突出啊,基本的智能手机里有的效果还是要有的啊。否则我加这个屏幕做啥呢?

小王:我……我研究下……

老板:年轻人,努努力,我知道有难度,别人做不出来,你做出来了,多有成就啊?明天给我个方案,争取下周出样机。

当晚,灯火阑珊处,又多了一个在各大论坛和技术群里苦苦追寻答案的苦命人:

“跪求大佬如何在裸机下编写图形菜单?”

“跪求如何把参数设置与菜单框架联系在一起”

“跪求如何用状态机实现图形界面”

……

“跪求”……


这样的故事虽然是我杜撰的,但他真真切切的就发生在很多人的身边……

然而,曙光初现了:

Arm-2D 把我们这样的苦命人也作为服务对象:

  • 裸机或者RTOS
  • 小资源
  • 贴图为主
  • 低帧率
  • 有一定特效要求

Arm-2D体会到了这里的痛点:

  • 要支持PFB(部分缓冲技术)来节省RAM资源;
  • 使用PFB来实现图形界面时:理论上能做到的不代表人人都能做到;老鸟能做到不代表能很容易的就可以做到;
  • 开发一定要简单、傻瓜化;
  • 开发一定要简单、傻瓜化;
  • 开发一定要简单、傻瓜化;

Arm-2D提供的解决方案是:

  • 为 PFB 提供了专用 Helper 服务,而用户仅需提供最小的信息:
  • 屏幕的大小
  • PFB的大小
  • 一个向LCD传送像素的函数;
  • 一个图形界面的绘制函数;
  • 用户在绘制图形界面的时候,可以假装使用了完整的 Framebuffer。或者换句话说,PFB对界面的绘制来说是完全透明的;
  • 用户可以自由的配置PFB的大小来平衡帧率和资源的使用,简单理解就是可以在“RAM消耗”和“帧率”之间进行“无级变速”
  • 用户无需担心窗体切割的问题,Arm-2D所有的API都自动处理“贴图、目标缓存和窗体之间尺寸不一致问题”;
  • 提供对脏矩阵列表的支持——随心所欲的指定只刷新屏幕的哪“几个”区域
  • 而在上述特性的基础上,你可以:
  • 进行多个透明图层的合成(Alpha-blending)
  • 在指定的区域里像贴瓷砖那样进行贴图填充(Texture Paving)
  • 文字显示(bit Pattern)
  • 以抠图的方式显示光标(Colour-Masking)
  • 拷贝图片,或者只拷贝图片的一部分(2D-Copy)
  • 画点(有了画点,基本上就啥都成为可能了)
  • 画横线或者竖线
  • 用指定颜色填充指定区域(甚至还可以指定透明度)

简单来说:对普通单片机开发者来说,Arm-2D就是一张GUI的人权卡——通过它,你可以在小资源环境中快速且简单的实现自己所需的简单界面,并且自动获得PFB的支持

从结论上说:

Arm-2D 真的也认真的服务于我们普通人!

Arm-2D真的跟我们普通开发有关!

Arm-2D不一定提供最优的效率(如果你用的是Cortex-M55,它是目前你能获得的最优方案),但它真的节省我们大量的开发时间!

【人权卡的部署也很简单】


Arm-2D的基本设计理念是“傻瓜化”,它表现在部署上就是:

  • 支持“无脑”添加所有 C 源文件
  • 默认情况下无需复杂配置
  • 使用前,调用 arm\_2d\_init() 即可
  • 本身占用RAM极小;
  • 支持最高优化等级(-O3,-Os,-Oz,-Ofast,-Omax,-Omin)

废话少说,下面我们就来实际动手进行Arm-2D的部署吧。

准备阶段:


1、准备一个已有的工程,确保该工程已经能够实现基础的LCD初始化,并能提供一个向LCD指定区域传送位图的函数,其原型如下:

/**

这里,5个参数之间的关系如下图所示:

image.png

简单来说,这个函数就是把 _bitmap_ 指针所指向的“连续存储区域” 中保存的像素信息拷贝到LCD的一个指定矩形区域内,这一矩形区域由位置信息(_x,y_)和体积信息(_width_,_height_)共同确定。

很多LCD都支持一个叫做“操作窗口”的概念,这里的窗口其实就是上图中的矩形区域——一旦你通过指令设置好了窗口,随后连续写入的像素就会被依次自动填充到指定的矩形区域内(而无需用户去考虑何时进行折行的问题)。

此外,如果你有幸使用带LCD控制器的芯片——LCD的显示缓冲区被直接映射到Cortex-M芯片的4GB地址空间中,则我们可以使用简单的存储器读写操作来实现上述函数,以STM32F746G-Discovery开发板为例:

//! STM32F746G-Discovery

2、获取Arm-2D库:

访问网址:

https://github.com/ARM-software/EndpointAI

或者在【裸机思维】公众号中发送关键字“arm-2d”获取对应压缩包。

裸机思维

傻孩子图书工作室。探讨嵌入式系统开发的相关思维、方法、技巧。 欢迎参加新书每日一催活动。

54篇原创内容

Official Account

image.png

需要说明的是,Arm-2D是Arm仓库EndpointAI的一部分。目前与Arm-2D相关的分支有4个:

  • _master_——主分支,包含了最简的arm-2d库
  • _main-arm-2d-developing_——主分支对应的开发分支
  • _main-arm-2d-more-examples_——包含了与主分支一样的内容,并提供了额外的例子(推荐尝鲜的小伙伴使用)
  • _main-arm-2d-more-example-developing_——上述分支的开发分支

后续内容,我们将假设下载的是_main-arm-2d-more-examples_分支中的内容。

部署阶段:


1、提取Arm-2D

解压缩压缩包,然后顺着以下路径找到Arm-2D目录:

"\Kernels\Research\"

image.png

将Arm-2D目录整体拷贝出来,放置到你的目标工程目录下,比如:

image.png

2、将Arm-2D添加到MDK工程中

在工程管理器中新建一个名为“Arm-2D”的分组,并将文件夹“Arm-2D/Library”下“Include”和“Source”中所有内容都添加到分组中:

image.png

为了获取PFB支持,我们还需要再添加对应的Helper服务到工程中来。同样新建一个分组,名为“Arm-2D-Helper”,并将“Arm-2D/Helper”目录下“Include”和“Source”中的所有内容都添加到分组中:

image.png

3、配置编译环境

将“Arm-2D/Library/Include”和“Arm-2D/Helper/Include”添加到Include搜索路径列表里:

image.png

如果你使用Arm Compiler 6(armclang),则需要打开对C11GNU扩展的支持,即直接在"Language C"中选择“gnu11”:

image.png

如果你使用的是Arm Compiler 5(armcc),则需要打开对C99GNU扩展的支持,如下图所示:

image.png

此外,由于现阶段Arm-2D还没有正式完成对armcc的warning清理工作,因此,你会看到海量的warning。在官方正式完成对armcc支持之前,推荐直接手工屏蔽它们,或者干脆关闭所有warning。具体方法为:在工程管理器中,右键键单击Arm-2D,选择“Option for Group”、“C/C++”;在“Misc Controls”文本框中添加如下内容:

--diag_suppress=513,144,174,2803,64,68,188,177

image.png

对待"Arm-2D-Helper"如法炮制,这里就不再赘述。

至此,我们就应该能够成功的完成编译了。

image.png

仔细想想,部署Arm-2D我们其实也没做啥特别的事情,是不是特别简单?

使用准备阶段:


1、包含头文件

在要使用Arm-2D的地方直接包含“arm\_2d.h”,比如:

#include "arm_2d.h"

2、初始化Arm-2D

在使用任何Arm-2D服务之前,需要对库进行初始化,比如:

void main(void)

如果你的芯片SRAM财大气粗——不需要使用PFB,则至此我们已经完成了Arm-2D的全部部署工作。你可以着手第一个“Hello Arm-2D”啦。

PFB Helper 服务的部署:


1、包含头文件

在要使用PFB Helper服务的地方直接包含“arm\_2d\_helper.h”,比如:

#include "arm_2d_helper.h"

2、建立对象

理论上,我们可以建立多个PFB Helper对象——依据应用实际情况而定。这里,我们可以直接使用类型 arm\_2d\_helper\_pfb\_t 来建立一个静态实例:

static arm_2d_helper_pfb_t s_tPFBHelper;

3、初始化PFB服务:

在使用 PFB Helper之前,我们需要对其进行必要的初始化。Arm-2D提供了一个宏模板,可以帮我们简化必要的步骤:

    //! initialise FPB helper

比如,一个典型的例子是:

    //! initialise FPB helper

其中,底层LCD像素绘制函数\_\_pfb\_render\_handler()负责将PFB中的像素发送给LCD:

static void __pfb_render_handler( void *pTarget, const arm_2d_pfb_t *ptPFB)

这里的_arm\_2d\_helper\_report\_rendering\_complete()_ 负责释放从PFB池中分配到的 arm\_2d\_pfb\_t 对象——这点非常关键。


对于使用DMA来异步刷新LCD的系统来说,用户就需要对上述过程做一个修改:

  1. 在 _\_\_pfb\_render\_handler()_ 中向DMA发送刷新请求;
  2. 当DMA完成刷新后,在对应的完成中断处理程序中调用用 arm\_2d\_helper\_report\_rendering\_complete() 来释放 PFB对象;

这里的 _\_\_pfb\_draw\_handler\_t()_ 就是我们绘制图形界面的函数:

static arm_fsm_rt_t __pfb_draw_handler_t( void *pTarget,

在这个例子中,我们简单的实现了一个半透明浮动窗口的效果,(这篇文章实在太长了,就简单做个例子凑个数吧):
image.png

4、调用 PFB Helper服务任务:

要想使用PFB,还需要在超级循环或者某个RTOS任务里调用PFB的服务函数 _arm\_2d\_helper\_pfb\_task()_,由于它是非阻塞的、返回值为状态机的状态 _arm\_fsm\_rt\_t_,因此使用方法非常灵活,例如:

int main (void) 

值得特别说明的是,函数arm\_2d\_helper\_pfb\_task() 的第二个参数是脏矩阵列表(的地址),简单说就是一个由用户指定的刷新区域列表——你让PFB只刷哪些区域,它就只刷哪些区域。为了方便用户,Arm-2D还专门提供了一套宏模板来简化用户的脏矩阵列表定义工作,例如:

    /*! define dirty regions */

在这个例子中,代码定义了两个区域:一个是屏幕正中央一块 80*80 的区域,以及屏幕底部一个高度为8像素的条状区域(可以用于状态信息的显示)——最终的效果是,每次使用PFB进行刷新,这两个区域以外的部分都会被跳过(保持不变),从而节省了大量的处理时间,客观上提高了用户实际可见的帧率(Arm-2D中对于这种情况使用 Update per second而不是Frame per second 进行描述)。

借助这一范例很容易发现:通过宏 ADD\_REGION\_TO\_LIST()我们可以几乎毫无限制的向列表中添加任意数量的区域,其语法为:

ADD_REGION_TO_LIST(<列表名称>,

需要注意的是,列表的最后一个元素一定要用 _ADD\_LAST\_REGION\_TO\_LIST()_来添加,否则代码一定会出现内存溢出的惨状。

整个列表的语法为:

/*! define dirty regions */

这里,“列表名称”实际上就是列表的变量名,而“列表变量的修饰” 则是大家熟悉的类型修饰符,比如 static_、_const 一类——正确使用修饰符既可以节省RAM消耗,也可以在需要的情况下建立允许动态修改内容的列表。

【说在后面的话】


至此,我们完成了Arm-2D在工程中的部署,赋予了那些资源极端受限的单片机以“低帧率换低资源消耗”的方式 实现较为华丽图形界面的“人权”。

其实,不光是小资源系统可以使用PFB来解决“从无到有”的问题资源较为宽裕的芯片也可以使用1/2 甚至是1/4的PFB来换取更多的 SRAM 用于改善或者拓展其它应用性能,比如,改善音频处理类应用的缓冲效果等等。

另一方面,如果将PFB大小设置为完整的屏幕尺寸,实际上就可以将PFB Helper服务当做一个帧缓冲池来使用;此外,倘若上层的GUI软件能向PFB Helper传递脏矩阵列表,就能在刷新帧率上获得极大的优化空间。

作为本系列的第二篇,我们介绍了Arm-2D对普通单片机的意义,并提供了一个手把手的部署教程。后续内容,我们将在PFB平台的基础上以一个个具体的控件特效为例,详细为您介绍Arm-2D API的使用和技巧——什么进度条啊,滑动列表啊,菜单啊,统统都会安排上。如果你想一起追剧,就赶快搭建好测试平台吧。

专栏推荐文章

如果你喜欢我的思维,欢迎订阅裸机思维
版权归裸机思维(傻孩子图书工作室旗下公众号)所有,
所有内容原创,严禁任何形式的转载。
推荐阅读
关注数
1480
内容数
119
探讨嵌入式系统开发的相关思维、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息