P_W比如
【说在前面的话】
虽然Arm-2D本身同时支持资源丰富和资源受限的环境,但这一系列的文章主要从资源受限系统的角度出发做出设计上的考量。
目标处理器的系统频率在72MHz以内,覆盖从Cortex-M0到Cortex-M4F的处理器范围;
Flash的大小在128KByte以内;
SRAM的大小在4~32K的范围内;
允许使用XIP将外部的SPI-FLASH映射到4G地址空间中
目标应用场景主要为各类对动态效果没有要求的界面
各类仪器仪表
智能家居的控制面板
手持设备的菜单界面
……
关于刷新率约定:
当整个屏幕都进行完整刷新时,我们用 Frame-per-sec, FPS 来描述;
当我们只刷新屏幕的局部时,我们用 Update-per-sec, UPS 来描述;
目标应用场景在大部分情况下对帧率没有或只有较低的要求
大部分情况下 1~3 FPS 即可满足要求
部分场景下,在屏幕的局部可能会要求达到30 UPS的刷新率(比如动态进度条等等)
在效果允许的情况下,尽可能减小 PFB的尺寸——在相同PFB面积的情况下,尽可能在确保Height不小于8时取Width所允许的最大值。
本系列介绍的各类方法主要适用于无法负担起常规GUI协议栈(比如LVGL)的环境,如果您的条件允许,还是推荐直接使用常规GUI进行界面设计(这类GUI在底层仍然可以使用Arm-2D对一些算法进行加速)。
本文假设读者已经完成了Arm-2D在本地平台的移植:
如果您还没有完成这一步骤,请先移步 Arm-2D集合 中的文章《入门和移植从未如此简单》
如果您想跳过移植的步骤,直接进入Arm-2D的使用和学习环节,可以参考文章《懒人玩Arm-2D究竟有几种姿势》也可以使用 Github仓库中 example目录下提供的 PC移植工程(目前支持直接生成Windows、Linux和MacOS下的原生可执行程序)
为了便于讲解,本文将主要使用 example 目录下的 [template][cmsis-rtos2][pfb] 模板作为起点。
截图来自FastModel
或者 AN547-Cortex-M55_r0(使用免费的Corstone-300-FVP)
详细配置方法,请参考文章《懒人玩Arm-2D究竟有几种姿势》
因为我们主要考虑资源有限的环境,因此推荐使用 -Os(或-Oz) 优化等级、开启 Link-Time-Optimisation、并使用microLib:
【API的异步工作模式】
ARM_2D_OP_WAIT_ASYNC();
static
IMPL_PFB_ON_DRAW(__pfb_draw_scene0_handler)
{
user_scene_0_t *ptThis = (user_scene_0_t *)pTarget;
ARM_2D_UNUSED(ptTile);
ARM_2D_UNUSED(bIsNewFrame);
arm_2d_canvas(ptTile, __top_canvas) {
/*-----------------------draw the foreground begin-----------------------*/
/* following code is just a demo, you can remove them */
arm_2d_align_centre(__top_canvas, c_tileCMSISLogoRGB565.tRegion.tSize) {
/* draw the cmsis logo in the centre of the screen */
arm_2d_rgb565_tile_copy_with_src_mask_only(
&c_tileCMSISLogoRGB565,
&c_tileCMSISLogoMask,
ptTile,
&__centre_region);
ARM_2D_OP_WAIT_ASYNC();
}
/*-----------------------draw the foreground end -----------------------*/
}
ARM_2D_OP_WAIT_ASYNC();
return arm_fsm_rt_cpl;
}
虽然裸机环境下,所有arm-2d的API都是以同步模式来工作的(即退出API就意味着任务完成或者出现了错误)——理论上的确完全无需ARM_2D_OP_WAIT_ASYNC()调用来实现所谓的同步,但以“异步工作模式”使用API写出来的代码拥有最高的兼容性——可以同时在RTOS环境和裸机环境下使用,因此,本系列文章统一以异步模式为蓝本来讲解后续的内容。
(图片来自我自己iPad的截图)
还是作为图表的背景(如下图所示):
(图片资源来自网络:侵删)
很多情况下,我们都可以从目标控件中拆解出圆角矩形来:
它其实保存在一个四方四正的像素数组里(红色边框是我加的,用来让指示范围更加清晰):
当目标背景的颜色也是白色时,复制该贴图,并无异样。就像你们现在看到的那样(假设您阅读本文时使用的是白色背景)。但假设背景是一个不同于白色的其它颜色,甚至是一个墙纸时:
也就是这里,红色边界范围内的“白色”应该是一种类似透明玻璃的颜色——目标背景是什么像素内容,这里就能“透过去”。
Transparency:0表示完全不透明,255表示完全透明;
Opacity:0表示完全透明,255表示完全不透明。
每个像素是16bit,其中R、G、B分别对应5、6、5个二进制位
ARGB32中,RGB888占用24个bit,相对RGB565来说,每个像素多占用了8个bit。
在同时需要保留Alpha信息的情况下,RGB565比RGB888节省了25%的空间。
将Alpha信息从像素中提取出来,单独作为一个数组保存;
在设计阶段,将RGB888转化为RGB565形式保存,从而节省资源的存储空间,也避免了在运行时刻进行颜色格式转换所造成的不必要的性能损失
这是一个命令行工具,需要python3.x 版本,并安装以下的依赖:
pip install Pillow
pip install numpy
具体使用方式如下:
python img2c.py <命令行选项>
选项 | 描述 | |
-h, --help | 显示命令行使用方法 | |
-i <路径> | 输入图片文件的路径(png, bmp, jpeg...) | |
-o <路径> | 输出c源文件的路径 | 可选 |
-name <名称> | 数组的名字 | |
-format <格式> | 输出的颜色格式:rgb32, rgb565, gray8, all(默认) | 可选 |
-dim <宽度>,<高度> | 在输出前修改图片的尺寸 | 可选 |
-rot <角度> | 在输出前将图片旋转指定的角度 | 可选 |
比如,在 examples/benchmark/asset 目录下有一个png图片 CMSIS_Logo_Final.png,我们可以借助命令行将其转化为 tile 数据结构:
python img2c.py -i ..\examples\benchmark\asset\CMSIS_Logo_Final.png --name CMSISLogo
运行成功后,由于我们没有指定输出的路径,因此直接在tools所在目录下生成了一个与图片文件同名(扩展名不同)的c文件 CMSIS_Logo_Final.c:
打开文件,我们可以看到img2c.py按照默认的颜色格式 RGB565 自动生成了对应的像素数组 c_bmpCMSISLogo 和arm-2d的API可以直接使用的arm_2d_tile_t对象c_tileCMSISLogo。
static const uint16_t c_bmpCMSISLogo[163*65] = {
...
};
extern const arm_2d_tile_t c_tileCMSISLogoRGB565;
const arm_2d_tile_t c_tileCMSISLogoRGB565 = {
.tRegion = {
.tSize = {
.iWidth = 163,
.iHeight = 65,
},
},
.tInfo = {
.bIsRoot = true,
.bHasEnforcedColour = true,
.tColourInfo = {
.chScheme = ARM_2D_COLOUR_RGB565,
},
},
.phwBuffer = (uint16_t*)c_bmpCMSISLogo,
};
关于 arm_2d_tile_t 数据结构的详细内容,本身并不复杂,这里大家“望文生义”即可:容易发现c_tileCMSISLogoRGB565:
描述了图片的尺寸信息:163 * 65
描述了图片的颜色信息:ARM_2D_COLOUR_RGB565
由于直接提供了像素数组的地址,因此这是一个“根贴图(Root Tile)”。
既然拿到了Tile,我们不妨赶快试它一试:
1、将生成的CMSIS_Logo_Final.c加入MDK工程参与编译(其实,部署Arm-2D后,其实一个包含相同内容的文件 cmsis_logo.c 已经加入了编译)
2、在对应的场景源代码中加入引用声明:
extern const arm_2d_tile_t c_tileCMSISLogoRGB565;
3、在修改场景绘制函数制函数 __pfb_draw_scene0_handler():
static
IMPL_PFB_ON_DRAW(__pfb_draw_scene0_handler)
{
user_scene_0_t *ptThis = (user_scene_0_t *)pTarget;
ARM_2D_UNUSED(ptTile);
ARM_2D_UNUSED(bIsNewFrame);
arm_2d_canvas(ptTile, __top_canvas) {
/*-----------------------draw the foreground begin-----------------------*/
/* following code is just a demo, you can remove them */
arm_2d_rgb16_tile_copy_only( &c_tileCMSISLogoRGB565,
ptTile,
&__top_canvas);
/*-----------------------draw the foreground end -----------------------*/
}
ARM_2D_OP_WAIT_ASYNC();
return arm_fsm_rt_cpl;
}
这里,我们首先通过 arm_2d_rgb16_fill_colour() 向整个屏幕填充了白色(GLCD_COLOUR_WHITE);紧接着以 arm_2d_rgb16_tile_copy()将我们的额 c_tileCMSISLogoRGB565 拷贝到了屏幕左上角。运行结果如下:
怎么说呢……运行结果正常,却并不能让我们满意——由于缺乏透明度信息,原本应该是完全透明的部分,由于对应像素值为0x0000正好对应了RGB565下的黑色,因此呈现出一个黑色的背景色——这当然不是我们所需要的。
重新打开此前生成的 CMSIS_Logo_Final.c,我们注意到,其实Alpha信息已经被单独提取出来、独立保存并生成了专门的arm_2d_tile_t对象c_tileCMSISLogoMask:
static const uint8_t c_bmpCMSISLogoAlpha[163*65] = {
...
};
extern const arm_2d_tile_t c_tileCMSISLogoMask;
const arm_2d_tile_t c_tileCMSISLogoMask = {
.tRegion = {
.tSize = {
.iWidth = 163,
.iHeight = 65,
},
},
.tInfo = {
.bIsRoot = true,
.bHasEnforcedColour = true,
.tColourInfo = {
.chScheme = ARM_2D_COLOUR_8BIT,
},
},
.pchBuffer = (uint8_t *)c_bmpCMSISLogoAlpha,
};
通过观察,容易发现:
c_tileCMSISLogoMask拥有与c_tileCMSISLogoRGB565相同的像素尺寸,都是163*65;
c_tileCMSISLogoMask的颜色是 8bit的 ARM_2D_COLOUR_8BIT。
它也是一个root tile。
借助专门的函数 arm_2d_rgb565_tile_copy_with_src_mask(),我们就可以轻松达成所需的效果。具体操作如下:
1、在对应的场景源代码中加入对应的引用声明:
extern const arm_2d_tile_t c_tileCMSISLogoMask;
2、修改场景绘制函数制函数 __pfb_draw_scene0_handler():
static
IMPL_PFB_ON_DRAW(__pfb_draw_scene0_handler)
{
user_scene_0_t *ptThis = (user_scene_0_t *)pTarget;
ARM_2D_UNUSED(ptTile);
ARM_2D_UNUSED(bIsNewFrame);
arm_2d_canvas(ptTile, __top_canvas) {
/*-----------------------draw the foreground begin-----------------------*/
/* following code is just a demo, you can remove them */
arm_2d_rgb565_tile_copy_with_src_mask_only(
&c_tileCMSISLogoRGB565,
&c_tileCMSISLogoMask,
ptTile,
&__top_canvas);
/*-----------------------draw the foreground end -----------------------*/
}
ARM_2D_OP_WAIT_ASYNC();
return arm_fsm_rt_cpl;
}
编译后运行效果如下:
如果你还记得本文开篇时的那个辅助函数 arm_2d_align_centre()的话,我们还可以借助它实现Logo居中的效果:
1、确保目标 c 文件中增加了 arm_2d_helper.h 的包含:
#include "arm_2d_helper.h"
2、修改场景绘制函数制函数 __pfb_draw_scene0_handler(),加入 arm_2d_align_entre():
static
IMPL_PFB_ON_DRAW(__pfb_draw_scene0_handler)
{
user_scene_0_t *ptThis = (user_scene_0_t *)pTarget;
ARM_2D_UNUSED(ptTile);
ARM_2D_UNUSED(bIsNewFrame);
arm_2d_canvas(ptTile, __top_canvas) {
/*-----------------------draw the foreground begin-----------------------*/
/* following code is just a demo, you can remove them */
arm_2d_align_centre(__top_canvas, c_tileCMSISLogoRGB565.tRegion.tSize) {
/* draw the cmsis logo in the centre of the screen */
arm_2d_rgb565_tile_copy_with_src_mask_only(
&c_tileCMSISLogoRGB565,
&c_tileCMSISLogoMask,
ptTile,
&__centre_region);
ARM_2D_OP_WAIT_ASYNC();
}
/*-----------------------draw the foreground end -----------------------*/
}
ARM_2D_OP_WAIT_ASYNC();
return arm_fsm_rt_cpl;
}
运行效果如下:
关于 arm_2d_align_centre() 值得说明的有两点:
arm_2d_align_centre() 需要用户传递两类信息:
画布或者Region的实例(注意是实例,不是地址)。在本例中画布为__top_canvas
目标区域的大小信息。在本例中,因为我们希望将CMSIS Logo居中,因此目标区域的大小信息就保存在 c_tileCMSISLogoRGB565 中。具体写为 c_tileCMSISLogoRGB565.tRegion.tSize
最终结果就是代码中所呈现的那样:
arm_2d_canvas(ptTile, __top_canvas) {
arm_2d_align_centre(__top_canvas, c_tileCMSISLogoRGB565.tRegion.tSize) {
...
}
}
arm_2d_align_centre() 会产生一个局部变量 __centre_region,它的生命周期仅限于 arm_2d_align_centre() 的花括号内,因此,当我们使用 __centre_region 进行绘图时,要将其生命周期考虑在内——必须在其生命结束前加入 arm_2d_op_wait_async() 以确保API执行的有效性:
static
IMPL_PFB_ON_DRAW(__pfb_draw_scene0_handler)
{
user_scene_0_t *ptThis = (user_scene_0_t *)pTarget;
ARM_2D_UNUSED(ptTile);
ARM_2D_UNUSED(bIsNewFrame);
arm_2d_canvas(ptTile, __top_canvas) {
/*-----------------------draw the foreground begin-----------------------*/
/* following code is just a demo, you can remove them */
arm_2d_align_centre(__top_canvas, c_tileCMSISLogoRGB565.tRegion.tSize) {
/* draw the cmsis logo in the centre of the screen */
arm_2d_rgb565_tile_copy_with_src_mask_only(
&c_tileCMSISLogoRGB565,
&c_tileCMSISLogoMask,
ptTile,
&__centre_region);
ARM_2D_OP_WAIT_ASYNC();
}
/*-----------------------draw the foreground end -----------------------*/
}
ARM_2D_OP_WAIT_ASYNC();
return arm_fsm_rt_cpl;
}
【说在后面的话】
关于这里提到的详细概念,请务必仔细阅读Arm-2D集合 中的文章《还在手算坐标?试试Layout Assistant吧》 它会让你后续的应用开发事半功倍。
虽然这里用到的很多布局辅助的语法糖是用宏实现的(比如arm_2d_align_centre()),但从应用开发的角度来说,我建议只把它们当做Arm-2D提供的特殊关键字——专注于如何使用它们——而千万不要浪费时间去深究它们的实现原理。因为,对大多数人来说,这是一个深坑,跳进去没有个半年是跳不出来的。
如果你喜欢我的思维、觉得我的文章对你有所启发,
请务必 “点赞、收藏、转发” 三连,这对我很重要!谢谢!
欢迎订阅 裸机思维