【玩转Arm-2D】入门和移植从未如此简单

文摘   科技   2024-05-13 06:41   英国  


【说在前面的话】


  

在往期的文章《什么是嵌入式系统(下)——沉淀模型》我们曾提到过:

现在的计算机技术差不多领先嵌入式技术大约20年,现在嵌入式系统无论在资源上、理论上还是方法论上,都与上世纪80年代的计算机前沿技术相当。

GorgonMeducer 傻孩子,公众号:裸机思维什么是嵌入式(下)—— “重力”和“沉淀”

这意味着,本世纪初应用在个人电脑上的一些技术也可能会被逐步引入到深度嵌入式系统上(Deep Embedded System)——这类系统的典型也就是大家所熟知的单片机或者说Cortex-M处理器

这不,Arm在Github上发布了一个专门针对“全体” Cortex-M处理器的2D图形加速库——Arm-2D:

https://github.com/ARM-software/Arm-2D

根据Github仓库中README的描述,我们可以简单的把这个2D图形加速库理解为是一个专门针对Cortex-M处理器的标准“显卡驱动。虽然这里的“显卡驱动”只是一个夸张的说法——似乎没有哪个Cortex-M处理器“配得上”所谓的显卡,但其实也并没有差多远——因为根据最新的趋势,随着单片机资源的逐步丰富(较高级的工艺节点正在逐步降价),处理器不仅跑得越来越快、存储器越来越大,而且大量的厂商已经或者正在考虑给Cortex-M处理器配备专属的2D图形加速引擎——在这一方面,ST和NXP走的最靠前——相信Chrome-Art(又叫DMA2D)大家早已耳熟能详。实际上,一些芯片公司正在考虑(甚至已经)给下一代Cortex-M处理器配备真正的2D-GPU在这一背景下,Arm-2D的上线传达了一个来自行业的清晰信号:兄弟们,这不是演习,一个属于Cortex-M专2D图形加速的时代到来了

【Cortex-M的显卡驱动意义何在?】

Arm是行业内 “老” 生态玩家了——几乎所有的行为都会从生态的角度加以考量——所以要想搞清楚为啥Arm在此时为“Cortex-M”定制显卡驱动,就必须要搞清楚整个深度嵌入式生态究竟发生了什么。要想做到这一点,不妨设想一下:


你是一个芯片公司的产品经理:


  • 你觉得智能手表应用应该很火,或者拥有彩屏的智能IoT终端设备应该很火。
  • 考虑到低功耗和低成本,一些产品使用Cortex-M(而不是Cortex-A)来实现应该很合理。于是,你司定义了一款配备有2D图形加速引擎的Cortex-M处理器。
  • 芯片很快就做出来了,开发板配上了一个480*272分辨率的触摸屏,结合特别设计的PCB(以及极高识别度的PCB配色)——怎么看都显得高端!大气!上档次!
  • 问题来了:
  1. 市面上完全没有针对单片机(裸机或者RTOS)的第三方2D类跑分软件(专业说法叫Benchmark)——这如何才能体现你硬件的强大呢?测评机构又如何把复杂的2D处理以简单数值的形式呈现在大众面前呢?
  2. 市面上有那么多第三方GUI提供商,他们都有针对Cortex-M芯片的GUI产品,但我要如何说服他们增加对我的芯片提供支持呢?


你是一个GUI软件提供商:


  • 你们之前的产品在Cortex-A以及Linux环境下小有名气。

  • 最近看到很多软件公司纷纷瞄准了深度嵌入式市场,提供了定制化的GUI产品,比如微软的GUIX,Qt的Qt for Cortex-M。于是你也很快提供了对应的GUI产品,但问题随之而来:

  1. 市面上完全没有针对单片机的第三方2D类跑分软件……

  2. 与Cortex-A以及Linux环境较为规范的软件环境不同,深度嵌入环境碎片化太严重了

    1. LCD规格不同

    2. 连接LCD的硬件接口不同(LCD带宽不同)

    3. LCD的操作方式不同(有无LCD外设驱动器,驱动器的类型)

    4. 目标芯片的资源不同、系统频率不同

    5. 软件环境不同——RTOS千差万别,甚至还有裸机

    6. 芯片厂家提供的2D图形加速硬件每个都不一样……

  3. 总结来说,如果要支持一款硬件平台,就要针对它的硬件为其做移植和定制……

  4. 考虑到团队资源有限,所以能“官方”支持的硬件也有限……



总结来说:

  • 芯片厂家以“定制化的”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日常应用场景中所需的工作量和复杂度。(该Benchmark的静态截图大约就是下面这个样子,注意这是在模拟器上的效果,所以帧率很低):


  • 让不同的图层以不同的速度和角度飘来飘去以覆盖更多可能的情况——模拟日常GUI中可能出现的不同复杂度;

  • 渲染1000帧,记录下每一帧所消耗的CPU时钟周期数,并提供最小值、最大值和平均值等信息

  • 以帧渲染所需的平均周期数作为依据,计算 “30FPS所需最小处理器频率”——并以这一数值作为跑分结果


值得说明的是:

  • 这一跑分软件在统计“渲染一帧所需的周期数”时并不会把 “从RAM向LCD发送数据”所消耗的时间计算在内——因为“刷新显存”所消耗的时间由芯片和LCD之间的连接方式(或者说传输带宽)决定,而与芯片的2D图形处理能力无关。



如果你真的在意在某种LCD连接方式下系统的实际FPS是多少,只要额外测量“刷新显存”所消耗的时间(比如毫秒),然后追加到渲染一帧所需的毫秒数上即可。比如,在上面GIF所示的效果图中,LCD Latency(也就是“刷新显存”所需时间)为43ms,而平均渲染一帧所需的时间是 4ms(229FPS),综合下来实际一帧的所消耗的时间是 43+4 = 47ms(为了简单,直接按照50ms计算),此时,我们可以认为最终帧率大约为 20FPS(1/50ms)。


  • 实际上,经常有小伙伴看到Benchmark的动态效果时会不无讽刺的说:“嗯,很酷炫,我就想知道处理器除了做这个事情还剩下多少能力来处理具体应用。”  其实,“30FPS所需最小时钟频率” 就是为了直观的回答这个问题的。


上面这张表格,列举了大家常用的几类Cortex-M处理器在纯粹依靠“处理其本身”而不借助“专门2D图形加速器”的情况下,以320*240(RGB565)分辨率为目标,达到30帧每秒刷新率所需的最小频率,比如:
  • Cortex-M0+需要大约 194MHz 就够了,换句话说:如果你的Cortex-M0+跑250MHz,你还剩下56MHz的CPU性能可以用于应用。


拥有双核Cortex-M0+跑250MHz的树莓派Pico狂喜


  • Cortex-M3/M4 大约需要 105MHz,也就是说:如果你的芯片跑个216MHz,就还有大约111MHz用于具体应用(话说,这年月哪个Cortex-M4还不跑个百来十兆的?)

  • 使用了SIMD指令集加速(Helium指令集)的Cortex-M55居然只要16MHz就能达到30FPS的刷新率——着实让人觉得恐怖。


更直观的,这里是官方提供的2D性能的倍率比较(以Cortex-M4的性能为基准)。Cortex-M55 Helium居然是Cortex-M4性能的近乎6倍、Cortex-M0的10倍!——如果说Arm-2D是显卡驱动的话,那么Cortex-M55 Helium就是一款自带“集成显卡”的Cortex-M处理器了


在部署方式上,经过社区的大量反馈和测试,终于来到了“点几下鼠标”就能轻松部署的时代——如何使用CMSIS-Pack在三分钟内将 Arm-2D 部署到位,就是本文将要介绍的主要内容


【准备工作】


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

void Disp0_DrawBitmap (uint32_t x,  uint32_t y, uint32_t width, uint32_t height, const uint8_t *bitmap)


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


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

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

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

//! STM32F746G-Discovery#define GLCD_WIDTH     480#define GLCD_HEIGHT    272
#define LCD_DB_ADDR 0xC0000000#define LCD_DB_PTR ((volatile uint16_t *)LCD_DB_ADDR)
void Disp0_DrawBitmap (uint32_t x, uint32_t y, uint32_t width, uint32_t height, const uint8_t *bitmap) {    volatile uint16_t *phwDes = LCD_DB_PTR + y * GLCD_WIDTH + x; const uint16_t *phwSrc = (const uint16_t *)bitmap; for (int_fast16_t i = 0; i < height; i++) { memcpy ((uint16_t *)phwDes, phwSrc, width * 2); phwSrc += width; phwDes += GLCD_WIDTH;    }}



【如何获取安装包】


MDK 中部署 Arm-2D的第一步是获取对应的 cmsis-pack。我们你也可以Pack Installer中直接找到Arm-2D

也可以通过KEIL官网直接下载:

https://www.keil.arm.com/packs/arm-2d-arm/versions/

为了方便国内用户,可以在关注公众号【裸机思维】后发送关键字“arm-2d”来获取网盘链接。

值得说明的是,Pack-Installer和Keil官网中只能找到Arm-2DRelease 版本(通常2~6个月发布一个版本),而拥有最新功能、最新错误修复的版本则可以通过【裸机思维】公众号提供的网盘链接获得。


比如:本文编写时Release版本是2023年12月发布的1.1.5,而拥有众多新特性的版本是 v1.1.6-dev。要想获得最新的版本,推荐向【裸机思维】公众号发送关键字“Arm-2D”获取网盘链接。


获得 Arm-2Dcmsis-pack后,可以直接双击进行安装:

如果你手上的MDK是较新的版本,比如 5.38 或者更新,一般无脑Next安装即可。反之,强烈推荐你安装最新的 MDK 以避免一系列不必要的麻烦——这也是社区中很多前人用血泪史总结出的共同建议——如果一开始就用最新的 MDK 就可以节省大量的时间。

【傻瓜部署教程】

Arm-2D 的部署不可谓不简单,基本可以通过在 RTE 中勾选对应选项完成大部分工作。具体请参考下面的手把手教程吧:


步骤一:加入组件

在 MDK 工程中依次选择 Project -> Manage -> Run-Time Environment 来打开 RTE 配置窗口:



其实,也可以通过工具栏中的快捷按钮来实现同样的目标:


在 窗口中找到 Acceleration,并将其展开:

如图所示,勾选以下组件:
  • CoreArm-2D的核心(必选)
  • Alpha-Blending:大部分与透明度相关的操作,比如基于蒙版的拷贝、透明图层合成、透明色块填充等等
  • Transform:旋转、缩放等操作(支持蒙版、抠图和透明度)
  • Filter:渲染滤镜(目前支持抗锯齿和模糊效果)

为了简化我们与LCD显示驱动进行对接,需要勾选以下服务:
  • FPBPartial Frame-Buffer模块,支持部分刷新的核心组件
  • Display Adapter:一个使用 PFB 来适配 LCD 底层驱动的代码模板,帮我我们快速在上层绘图和底层LCD刷新之间建立桥梁。一般来说 Display Adapter 与 屏幕是一一对应关系:如果你有一块屏幕,这里就选“1”,如果你有两块屏幕,这里就选“2”,以此类推。

由于 Display Adapter 依赖了一些额外(Extra)的模块,因此,如果你只勾选了上述的部分,你会在窗口中看到橙色的警告:

这里警告的含义是说:Display Adapter依赖了模块 ControlsLCD ASCII Printf,但你没有勾选它们。简单的单击左下角的 Resolve 按钮,RTE会自动帮你勾选上所依赖的模块。

单击“OK” 按钮完成组件的添加。




为了方便后续的开发,强烈推荐下载并安装 perf_counter 模块,具体步骤请参考文章《【喂到嘴边了的模块】超级嵌入式系统“性能/时间”工具箱》,这里简述下关键步骤:

  1. 关注【裸机思维】公众号后发送关键字“perf_counter”来获取对应的 cmsis-pack
  2. 下载并安装 cmsis-pack
  3. 打开RTE后找到 Utilities,并勾选 perf_counter 中的 Core,推荐以 Source形式进行部署
  4. 如果出现橙色警告,单击Resolve按钮来解决


如果因为某些原因,你确实无法(或者不愿意)使用perf_counter来为Arm-2D提供时间基准服务,则需要手动实现下列两个函数:

externint64_t arm_2d_helper_get_system_timestamp(void);
externuint32_t arm_2d_helper_get_reference_clock_frequency(void)

其中:

  • arm_2d_helper_get_system_timestamp() 要返回一个int64_t类型的时间戳,它的精度至少要是10KHz级别,并且不允许(在你我的有生之年内)溢出;

  • arm_2d_helper_get_reference_clock_frequency() 要返回这个时间戳的实际频率,同样,根据要求,它至少要大于等于10KHz


如果觉得上述要求实现起来比较麻烦,真心不如去实现perf_counter。好消息是,perf_counter在最新的版本中已经解绑了SysTick——允许用户将perf_couner移植到任意架构下、使用任意精度的定时器了。鉴于篇幅限制,这里就不再赘述。


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


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


注意:Arm-2D即将在未来的版本中停止对Arm Compiler 5的支持,如果没有特殊原因,请尽可能使用Arm Compiler 6。另外,Arm Compiler 5编译Arm-2D的尺寸可以轻松达到150KB以上,而同等情况下,Arm Compiler 6编译的结果则只有 30多KB——差距巨大。


此外,在使用Arm Compiler 6时,除调试专用的-O0以外,请务必开启Link-Time-Optimization。Arm-2D推荐的优化选项是:
  • 如果只在乎性能不在乎尺寸,请选择-Ofast + Link Time Optimization
  • 如果只在乎尺寸完全不在乎性能,请选择-Oz + Link Time Optimization
  • 如果在乎尺寸和性能的平衡,请选择-Os + Link Time Optimization
  • 由于Arm Compiler 6 只在 -O0 时拥有较好的单步调试效果(可以观察局部变量,可以在几乎所有代码处下断点),因此-O1、-O2和-O3其实是非常尴尬的存在——他们在优化上比不过-Ofast、-Oz以及-Os,却跟上述三者一样都无法提供很好的单步调试效果。使用-O1、-O2、-O3属于“开了但没有完全开”“开优化好处没有享受到最大,缺点却吃满了”。因此,对于Arm Compiler 6来说,要么不开优化,要开就开最高
  • 不开Link Time Optimization的Arm Compiler 6 无法拉开和 gcc 的差距。开了Link Time Optimization的 Arm Compiler 6 可以瞬间超越(或者至少战平)IAR。从大量的实际工程中可以看到,相对 Arm Compiler 5也可以轻松实现30%到50%的提升(无论是尺寸还是性能)。



此外,由于Arm-2D依赖CMSIS ,可以通过配置MDK的RTE的方式来获得最新版本CMSIS的支持。

1、如下图所示,通过工具栏最右边的按钮打开Pack Installer


我们会看到类似这样的窗口:


在右半部分的Packs选项卡中,找到ARM::CMSIS,确保它显示“Up to date”,如果没有就单击对应的按钮进行更新。Arm-2D所依赖的CMSIS版本不得低于5.7.0(如果你要用Cortex-M55,则版本不得低于5.8.0)


2、通过如下图所示工具栏正中间的按钮打开RTE配置窗口:

Software Component列表中,展开CMSIS,并勾选上COREDSP。这里需要注意的是,DSP部分如果有Source的选项请选择Source选项——这将允许我们直接使用源代码的形式来编译CMSIS-DSP的库。



此外,如果你不确定RTE中所使用的CMSIS是否为最新的版本的话,可以单击Select Packs按钮:



看到窗体顶部 “Use latest Software Packs for Target” 被勾选,基本上就可以高枕无忧了。依次单击OK关闭对话框后,我们就成功的将CMSIS加入到了编译中。这里,由于我们选择了使用源代码的方式来编译CMSIS,因此可能还需要对CMSIS-DSP的源代码进行额外的设置。


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





步骤三:模块配置
在工程管理器中展开 Acceleration,并找到新加入的显示驱动适配器文件(arm_2d_disp_adapter_0.h)



双击打开后,在编辑器的左下角选择 Configuration Wizard


进入图形配置界面:

根据你的屏幕填写正确的信息:

  • 颜色位数(Screen Colour Depth)

  • 横向分辨率(Width of the screen)

  • 纵向分辨率(Height of the Screen)

  • 部分刷新缓冲块的宽度(Width of the PFB Block),一般优先考虑为整行或者1/2行像素的宽度

  • 部分刷新缓冲块的高度(Height of the PFB Block),一般推荐为1/10屏幕像素高度。在RAM较为紧缺时,考虑8或者1。

  • 进行帧率计算时,平均多少帧做一次数据更新(Number of iterations),默认是30,选0将关闭帧率实时计算功能

  • 实时帧率统计模式(FPS Calculation Mode),默认为 Render-Only FPS(只计算Arm-2D渲染部分的帧率)而Real FPS则计算实际的帧率。

  • 如果你的LCD控制芯片无法以硬件的方式交换RGB565像素的高低字节,则你可能需要勾选 Swap the high and low bytes 通过软件来实现这一功能。



此外,Display Adapter 支持用DMA+ISR的方式来实现异步刷新。本文为了降低难度,使用的是直接在 Disp0_DrawBitmap() 函数中直接向LCD传输数据的同步刷新模式。如果你是一个有LCD驱动经验的工程师,想一步到位,可以参考这篇文章《群友看傻了!三个简单步骤我就把LCD刷新率逼到了理论极限》的内容,这里就不再赘述。



保存后关闭窗口。


Acceleration中找到 arm_2d_cfg.h


同样打开它的 Configuration Wizard 图形配置界面:

由于我们用到了 Extra中的一些模块,比如 ContolsLCD ASCII Printf,因此需要提供对应的信息:比如屏幕的颜色位数、分辨率和 printf 打印行缓冲的大小(默认值是64个ASCII字符)。

对于 Arm-2D General Configuration 中的一些选项,这里有必要做一下说明:
  • Enable Asynchronous Programmers' model support:目前推荐关闭
  • Enable anti-alias support for all transform operations:使能旋转、缩放操作的抗锯齿功能
  • Enable Support for accessing individual Colour channels:当你的目标屏幕是 RGB888,而你又需要支持 PNG 图片时推荐打开。

Patches for improving performance 中都是一些通过Hack掉某些可能用不到的特性或者功能来换取性能的选项。推荐做法是:
  1. 保持默认选项
  2. 在完成应用后依次尝试:如果对应用没有明显的影响,就勾选以换取一定的性能提升。

Benchmark 中是一些跑Arm-2D基准测试相关的选项。这里,我们需要设置屏幕的分辨率。如果你的Flash尺寸小于512K,则务必勾选Use Tiny mode to run benchmark——它将开启TINY模式,确保哪怕最低 64K Flash的芯片也可以运行最小的2D基准测试


步骤四:添加代码

main() 函数所在的源代码文件中包含头文件:

#include "arm_2d_helper.h"
并在 main()函数中完成对 arm-2d 的初始化:
int main(void) {    system_init();     // 包括 LCD 在内的系统初始化    ...    arm_irq_safe {        arm_2d_init(); // 初始化 arm-2d    }    ...    while(1) {        ...    }}
以上就完成了对 arm-2d 模块的初始化。

如果你使用了 Display Adapter 来辅助移植,则还需要包含下面的头文件:
#include "arm_2d_disp_adapters.h"
并在 main() 函数中加入代码:
int main(void) {    system_init();     // 包括 LCD 在内的系统初始化    ...    arm_irq_safe {        arm_2d_init(); // 初始化 arm-2d    }
// 初始化 Display Adapter 0 disp_adapter0_init();     while (true) { ...        // 执行 Display Adapter 的刷新任务        disp_adapter0_task();        ...    } }

编译、下载,如果一切顺利,你应该可以在屏幕上看到类似如下的画面:




屏幕当中是一个不停旋转的“载入圈”,屏幕底部是当前的帧率信息——这里,FPS后面的数字表示“绘图”的帧率(冒号后面的数字是帧率所对应的时间)这种分开显示的方式完全是为了方便我们寻找性能瓶颈而准备的。


如果你看到画面中的载入圈圈并未旋转,则可以按照以下思路来检查:
1、有没有部署 perf_counter 
2、如果没有部署perf_counter,有没有按照要求实现函数 arm_2d_helper_get_system_timestamp()arm_2d_helper_get_reference_clock_frequency()
3、如果部署了 perf_counter,则应该对照文章《》来检查是否正确初始化了perf_counter。在Cortex-M平台下,则应该检查运行 disp_adapter0_task() 的时候,SysTick是否处于运行状态



至此,我们就完成了裸机环境下整个 Arm-2D的部署。

【如何开始玩耍?】

相信很多小伙伴看到这里在高兴之余,其实也是一脸懵逼的:


我该怎么玩?
这里的文字怎么打印出来的?
圆角矩形怎么画出来的?
死亡小圈圈怎么画出来的?
我自己的界面该怎么办?

详情请点击并订阅这里的合集:Arm-2D 合集 然后打开后续的文章:《Arm-2D应用开发入门》、《零基础Arm-2D API绘图入门无忧》……这里就不再赘述。



【常见问题】



问题一:安装 CMSIS-Pack 时失败


如果,你在安装 cmsis-pack 的时候就会遇到如下所示的问题:


不要慌,通常安装最新MDK、且避免修改默认安装目录就可以解决。怕麻烦的小伙伴可以在关注【裸机思维】后发送关键字“MDK”获取最新MDK的网盘链接。

值得强调的是,MDK为开源社区提供了 Community 版本,除了不能商用,几乎没有任何限制(对芯片、代码尺寸、调试均没有限制)。Community 版本的本质是一种 License,使用的安装文件与其它版本并无不同。对这一“官方白嫖版”感兴趣的小伙伴可以通过下面的链接来获取:


https://www.keil.arm.com/mdk-community/


如果你的运气特别差,安装了最新MDK也无法解决上述问题,还可以通过Pack-Installer的导入功能最后“搏一搏”——打开 Pack Installer 后依次单击 File->Import



在弹出窗口中选中下载获得的 cmsis-pack 进行安装。

如果还不能解决,请确认你的 MDK 是否安装在默认的安装目录下(C:\Keil_v5),如果不是,尝试重新安装到默认目录下。


再不行……再不行就换台电脑吧。






问题四:编译时报告某些系统的intrinsics重复定义,比如__set_PRIMASK 等等

这是由于工程中自带的cmsis版本太低,且与RTE所部署的cmsis冲突导致的。具体解决方法请参考文章《CMSIS玩家的“阴间成就”指南》。


问题三:编译时报告与 ARM_PRIVATE() 相关的错误

这类问题是由于你的 MDK 工程中存在独立的 CMSIS,且该 CMSIS 与 RTE中所添加的 CMSIS 存在冲突(工程中的 CMSIS 版本过于老旧),具体解决方案请参考文章《CMSIS玩家的“阴间成就”指南》,这里就不再赘述。

此外,要检查你是否正确开启了 GNU 扩展和对应的C标准(Arm Compiler 5要开启 C99,Arm Compiler 6要开启 gnu99


问题四:提示找不到__aeabi_assert

这是由于我们在工程中选择了 microLib,而 microLib 没有为 assert.h 提供底层实现导致的。添加如下代码即可:
#include "arm_2d.h"#include "cmsis_compiler.h"
#if defined(__MICROLIB)void __aeabi_assert(const char *chCond, const char *chLine, int wErrCode) { ARM_2D_UNUSED(chCond); ARM_2D_UNUSED(chLine); ARM_2D_UNUSED(wErrCode); while(1) { __NOP(); }}#endif


问题四:提示找不到 Disp0_DrawBitmap

当你选择 Display Adapter 服务时,需要用户提供一个向 LCD 刷新数据的函数。当你有多个屏幕时,需要在 RTE 里为 Display Adapter 选择对应的数量:


此时,我们可以在 Acceleration 中看到添加的代码文件:



注意到这里每个文件后面都有一个对应的数字,指代对应的 Display Adapter 模板。而每个 Display Adapter 都需要一个属于自己的底层刷新函数:Dispn_DrawBitmap(),具体请参考本文的【准备工作】章节。


问题五:出现Hardfault

检查栈(Stack)的大小,推荐在 0xC00(3K)以上为易,HEAP在1K以上。



【说在后面的话】


只要你安装好了 arm-2d cmsis-pack,并准备好了LCD底层驱动函数 Disp0_DrawBitmap() (记得事先测试满足要求),那么整个Arm-2D的部署工作几乎可以在3分钟之内轻松完成。

本文看起来这么长,实际上是因为包含了大量“解惑”的讲解和很多避免用户踩坑的步骤细节。当你跟着教程成功做过一遍以后,下次再部署时就可谓轻车熟路了——如果可能,尽可能使用最新的MDK来尝试,这是社区中众多前辈的吐血推荐,他可以让你避免90%的坑,从而避免大量不必要的时间浪费

新版的 cmsis-pack 除了简化用户部署外,还引入了一个方便裸机用户开发简易 GUI 应用的服务:场景播放器(scene player)——它允许我们将界面拆分成若干场景:
  • 每个场景都由(可选的)背景和前景组成
  • 用户可以
    • 事先设定好一连串场景然后依次切换
    • 也可以在运行时刻通过API追加(Append)新的场景
  • 场景的切换会自动避免帧撕裂的问题

虽然 arm_2d_disp_adapter_0.c 已经为我们演示了场景播放器的使用,但为了降低大家的学习门槛,我将在下一篇文章中详细为大家介绍这种“基于场景”的低成本GUI设计方式



原创不易,

如果你喜欢我的思维、觉得我的文章对你有所启发,

请务必 “点赞、收藏、转发” 三连,这对我很重要!谢谢!


欢迎订阅 裸机思维



裸机思维
傻孩子图书工作室。探讨嵌入式系统开发的相关思维、方法、技巧。
 最新文章