前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【教程更新】一网打尽Arm-2D的资料和傻瓜部署教程

【教程更新】一网打尽Arm-2D的资料和傻瓜部署教程

作者头像
GorgonMeducer 傻孩子
发布2021-08-25 10:31:02
2K0
发布2021-08-25 10:31:02
举报
文章被收录于专栏:裸机思维裸机思维

【说在前面的话】


随着时间的推移,从4月份更新第一个版本以来,Arm-2D也逐渐走入成熟期,截止到我编写这篇文稿的时间,其版本已经来到了0.9.8,而Github开发分支上的版本也进入了0.9.9 dev

在社区的帮助下,如今的版本不仅修复了一些不容易发现的bug,还追加了一些喜闻乐见的新特性,比如:

  • 支持将目标贴图进行任意角度旋转的rotation类API
    • 角度是浮点数
    • 用户可以指定贴图饶哪个坐标进行旋转(而不是默认绕贴图中心旋转)
    • 支持可选的抗锯齿效果
    • 默认支持抠图
    • 支持透明度效果
  • 提供了一个python脚本 img2c.py用来帮助用户将给定的图片转换成Arm-2D格式的tile

作为一个资料综合贴,本文希望能够帮您解决以下问题:

  • Arm-2D公开课和对应的资料贴从哪里找
  • Arm-2D的傻瓜图文部署教程
  • Arm-2D依赖哪个版本的CMSIS?以及如何在MDK环境下安装和部署最新的CMSIS
  • 如何利用Arm-2D提供的 PFB Helper 来降低资源消耗
  • 如何利用PFB Helper将 RGB16 的高低字节交换

如果你对Arm-2D的话题感兴趣,并且不想错过历史上以及未来所有我会发布的相关文章,欢迎单击文章最后的“阅读原文”来订阅话题#Arm-2D

【公开课和资料下载】


此前,受到极客社区的邀请,我有幸为大家献上了一期名为“Arm-2D初探——填补空白还是屋上架屋”的公开课。原本计划是1个小时,无奈说的太嗨了,一不小心就讲了3个小时……

如果你错过了这次直播,可以通过下面的连接进行回看(记得要完成新手任务):

https://aijishu.com/l/1110000000204649

讲课所使用的PPT可以通过下面的连接来下载:

https://cdn-file.aijishu.com/261/007/2610079804-60ab64d80a07e?_upt=31db16cd1622241529

虽然公开课用了3个小时里里外外详细的介绍了Arm-2D的方方面面,然而,为了节省时间,Arm-2D的移植却故意没有过多提及——这里我们就必须要做一下补充。手把手的教程是在此前的文章《为什么说Arm-2D是小资源单片机的GUI人权卡!》的基础上扩展而来:

  • 增加了CMSIS配置的流程
  • 按照最新版本的要求加入了一些API接口依赖的描述

希望大家喜欢。

【Arm-2D的部署很简单】


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

  • 支持“无脑”添加所有 C 源文件
  • 默认情况下无需复杂配置
  • 使用前,调用 arm_2d_init() 即可
  • 本身占用RAM极小;
  • 支持最高优化等级(-O3,-Os,-Oz,-Ofast,-Omax,-Omin)
  • 支持Arm Compiler 5、Arm Compiler 6、GCC和LLVM(理论上也支持IAR)。

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

准备阶段:


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

代码语言: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)

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

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

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

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

代码语言:javascript
复制
//! 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)

int32_t GLCD_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;
    }

    return 0;
}

2、获取Arm-2D库:

访问网址:

代码语言:javascript
复制
https://github.com/ARM-software/EndpointAI

或者在【裸机思维】公众号中发送关键字“arm-2d”获取对应压缩包(压缩包体积还更小一些)

需要说明的是,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目录:

代码语言:javascript
复制
"\Kernels\Research\"

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

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

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

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

3、配置编译环境

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

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

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

此外,由于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)


如果你的MDK版本较老,同时因为某些原因又不想更新MDK版本,可以通过Pack Installer导入仓库的办法获取最新的CMSIS。具体步骤如下:

1、通过git工具将最新版本的CMSIS从https://github.com/ARM-software/CMSIS_5 的develop 分支下载到本地。比如,我使用的工具就是Github Desktop:

2、打开Pack Installer,并通过菜单File->Manage Local Repository 打开仓库管理窗口:

3、单击Add,并把刚刚从Github上获取的CMSIS加入仓库中:

4、成功后,我们会看到最新的CMSIS已经被加入到Pack列表中了:

此时,单击OK。经过一番等待,我们发现最新的CMSIS 5.8.0(还没有release哦)已经被加入到我们的MDK环境中了:


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

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

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

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

在工程管理器中,找到CMSIS,在右键的弹出菜单中选择“Options for Component Class 'CMSIS'”:

在弹出窗口中选中DSP,并切换到 C/C++选项卡,如果你使用的是Arm Compiler 6,推荐将Optimisation Level设置为 -Ofast,并在Misc Controls中加入小写的“-w” 选项以屏蔽所有的Warning(这一屏蔽效果仅对CMSIS-DSP的源代码有效):

如果你使用的是Arm Compiler 5,则推荐将优化等级设置为Level 3(-O3),并确保旁边的 Optimize for Time处于明确的勾选状态。最后在Misc Controls里加入大写的“-W”来屏蔽所有的Warning。


有的小伙伴可能会对“明确的勾选状态”,下面的截图就是一个“非明确的勾选状态”:

仔细对比,你会发现,不光勾选的颜色是灰色的,其背景色也不是白色——这表示它会根据用户总体的工程设置来编译CMSIS-DSP。


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

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

使用准备阶段:


1、包含头文件

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

代码语言:javascript
复制
#include "arm_2d.h"

2、初始化Arm-2D

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

代码语言:javascript
复制
void main(void)
{
    ...
    arm_2d_init();
    ...
    while(1) {
        ...
    }
}

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

PFB Helper 服务的部署:


1、包含头文件

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

代码语言:javascript
复制
#include "arm_2d_helper.h"

2、建立对象

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

代码语言:javascript
复制
static arm_2d_helper_pfb_t s_tPFBHelper;

3、初始化PFB服务:

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

代码语言:javascript
复制
    //! initialise FPB helper
    if (ARM_2D_HELPER_PFB_INIT( 
            <PFB Helper对象的地址>,          //!< FPB Helper object
            <LCD的像素宽度>,                 //!< screen width
            <LCD的像素高度>,                 //!< screen height
            <像素的数据类型,比如uint16_t>,   //!< colour date type
            <PFB的像素宽度>,                 //!< PFB block width
            <PBF的像素高度>,                 //!< PFB block height
            <PFB池中PFB的数量,一般写1>,      //!< number of PFB in the PFB pool
            {
                .evtOnLowLevelRendering = {
                    //! callback for low level rendering 
                    .fnHandler = &<底层绘图函数>,                         
                },
                .evtOnDrawing = {
                    //! callback for drawing GUI 
                    .fnHandler = &<图形面绘制函数>, 
                },
            },
            //.FrameBuffer.bSwapRGB16 = true,
        ) < 0) {
        //! error detected
        assert(false);
    }

注意,如果你的屏幕需要将RGB16的高低字节进行交换,则只需要把第20行的注释符号"//"删除即可,这样我们就可以通过选项:

代码语言:javascript
复制
.FrameBuffer.bSwapRGB16 = true,

让 PFB Helper帮我们自动进行高低字节的交换。

一个典型的例子是:

代码语言:javascript
复制
    //! initialise FPB helper
    if (ARM_2D_HELPER_PFB_INIT( 
            &s_tPFBHelper,     //!< FPB Helper object
            320,               //!< screen width
            240,               //!< screen height
            uint16_t,          //!< colour date type
            16,                //!< PFB block width
            16,                //!< PFB block height
            1,                 //!< number of PFB in the PFB pool
            {
                .evtOnLowLevelRendering = {
                    //! callback for low level rendering 
                    .fnHandler = &__pfb_render_handler,                         
                },
                .evtOnDrawing = {
                    //! callback for drawing GUI 
                    .fnHandler = &__pfb_draw_handler, 
                },
            }
        ) < 0) {
        //! error detected
        assert(false);
    }

其中,底层LCD像素绘制函数__pfb_render_handler()负责将PFB中的像素发送给LCD:

代码语言:javascript
复制
static 
IMPL_PFB_ON_LOW_LV_RENDERING(__pfb_render_handler)
{
    const arm_2d_tile_t *ptTile = &(ptPFB->tTile);

    ARM_2D_UNUSED(pTarget);
    ARM_2D_UNUSED(bIsNewFrame);

    GLCD_DrawBitmap(ptTile->tRegion.tLocation.iX,
                    ptTile->tRegion.tLocation.iY,
                    ptTile->tRegion.tSize.iWidth,
                    ptTile->tRegion.tSize.iHeight,
                    ptTile->pchBuffer);

    arm_2d_helper_pfb_report_rendering_complete(&s_tPFBHelper, 
                                                (arm_2d_pfb_t *)ptPFB);
}

这里的arm_2d_helper_pfb_report_rendering_complete() 负责释放从PFB池中分配到的 arm_2d_pfb_t 对象——这点非常关键。


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

  1. __pfb_render_handler() 中向DMA发送刷新请求;
  2. 当DMA完成刷新后,在对应的完成中断处理程序中调用用 arm_2d_helper_pfb_report_rendering_complete() 来释放 PFB对象;

这里的 __pfb_draw_handler() 就是我们绘制图形界面的函数:

代码语言:javascript
复制
static 
IMPL_PFB_ON_DRAW(__pfb_draw_handler)
{
    ARM_2D_UNUSED(pTarget);
    ARM_2D_UNUSED(bIsNewFrame);

    arm_2d_region_t tBox = {
        .tLocation = {50,50},
        .tSize = {200, 100},
    };
    //! 背景填充白色
    arm_2d_rgb16_fill_colour(ptTile, NULL, GLCD_COLOR_WHITE);
    //! 在box指定的区域绘制黑色影子
    arm_2d_rgb16_fill_colour(ptTile, &tBox, GLCD_COLOR_BLACK);
    //! 适当向左上角移动box
    tBox.tLocation.iX -= 10;
    tBox.tLocation.iY -= 10;
    //! 在box指定的区域填充蓝色,并且使用 50%(128/255)的透明效果
    arm_2d_rgb565_fill_colour_with_alpha(   
        ptTile, 
        &tBox, 
        (arm_2d_color_rgb565_t){GLCD_COLOR_BLUE}, 
        128);      //!< 透明度

    return arm_fsm_rt_cpl;
}

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

4、调用 PFB Helper服务任务:

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

代码语言:javascript
复制
int main (void) 
{
    lcd_init();
    arm_2d_init();
    
    //! initialise FPB helper
    if (ARM_2D_HELPER_PFB_INIT( 
            &s_tPFBHelper,     //!< FPB Helper object
            320,               //!< screen width
            240,               //!< screen height
            uint16_t,          //!< colour date type
            320,               //!< PFB block width
            1,                 //!< PFB block height
            1,                 //!< number of PFB in the PFB pool
            {
                .evtOnLowLevelRendering = {
                    //! callback for low level rendering 
                    .fnHandler = &__pfb_render_handler,                         
                },
                .evtOnDrawing = {
                    //! callback for drawing GUI 
                    .fnHandler = &__pfb_draw_handler_t, 
                },
            }
        ) < 0) {
        //! error detected
        assert(false);
    }

    while(1) {
        //! call partial framebuffer helper service
        while(arm_fsm_rt_cpl != arm_2d_helper_pfb_task(&s_tPFBHelper, NULL));
    }

}

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

代码语言:javascript
复制
    /*! define dirty regions */
    IMPL_ARM_2D_REGION_LIST(s_tDirtyRegions, static const)

        /* a region for the busy wheel */
        ADD_REGION_TO_LIST(s_tDirtyRegions,
            .tLocation = {(APP_SCREEN_WIDTH - 80) / 2,
                          (APP_SCREEN_HEIGHT - 80) / 2},
            .tSize = {
                .iWidth = 80,
                .iHeight = 80,  
            },
        ),

        /* a region for the status bar on the bottom of the screen */
        ADD_LAST_REGION_TO_LIST(s_tDirtyRegions,
            .tLocation = {0,APP_SCREEN_HEIGHT - 8},
            .tSize = {
                .iWidth = APP_SCREEN_WIDTH,
                .iHeight = 8,  
            },
        ),

    END_IMPL_ARM_2D_REGION_LIST()

    //! call partial framebuffer helper service
    while(arm_fsm_rt_cpl != arm_2d_helper_pfb_task( 
             &s_tPFBHelper, 
             (arm_2d_region_list_item_t *)s_tDirtyRegions));

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

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

代码语言:javascript
复制
ADD_REGION_TO_LIST(<列表名称>,
    .tLocation = {<坐标信息>},
    .tSize = {<尺寸信息>}
),

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

整个列表的语法为:

代码语言:javascript
复制
/*! define dirty regions */
IMPL_ARM_2D_REGION_LIST(列表名称, <列表变量的修饰>)
    ...
END_IMPL_ARM_2D_REGION_LIST()

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

【说在后面的话】


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

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

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

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


原创不易,

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

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

欢迎订阅 裸机思维

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 裸机思维 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云直播
云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档