【说在前面的话】
虽然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究竟有几种姿势》
为了便于讲解,本文将主要使用 example 目录下的 [template][cmsis-rtos2][pfb] 模板作为起点。截图来自FastModel。
注意:
1、找到 main.c
2、在函数 display_task() 中找到以下内容:
void display_task(void)
{
...
//! call partial framebuffer helper service
while(arm_fsm_rt_cpl != arm_2d_helper_pfb_task(
&s_tExamplePFB,
(arm_2d_region_list_item_t *)s_tDirtyRegions));
...
}
3、将其修改为:
void display_task(void)
{
...
//! call partial framebuffer helper service
while(arm_fsm_rt_cpl != arm_2d_helper_pfb_task(
&s_tExamplePFB,
NULL));
...
}
其本质是完全不使用Dirty Region List,从而迫使 PFB Helper服务完整的刷新整个屏幕。
【API的异步工作模式】
arm_2d_op_wait_async(NULL);
/*! \note arm_2d_align_centre generate a region '__centre_region' based
*! on the given target tile (frame buffer) and the size of the
*! target area of your following drawing.
*! Use '__centre_region' when required as the target region in
*! 2D operations inside the {...} .
*/
arm_2d_align_centre(tTempPanel, c_tileCMSISLogo.tRegion.tSize) {
//!< copy CMSIS logo (with masks) to the centre of the right panel
arm_2d_rgb565_tile_copy_with_masks(
&c_tileCMSISLogo,
&c_tileCMSISLogoMask,
&tTempPanel,
&c_tileFadeMask,
&__centre_region, //!< generated by arm_2d_align_centre()
/*! remove ARM_2D_CP_MODE_FILL and only keeps
*! mirroring mode
*/
ptLayers[0].wMode &~ ARM_2D_CP_MODE_FILL);
arm_2d_op_wait_async(NULL);
}
姑且不论 arm_2d_rgb565_tile_copy_with_masks() 实际用 __centre_region 做了什么,但考虑到它的生命周期有限,因此必须在退出花括号之前调用 arm_2d_op_wait_async(NULL) 来等待操作完成。
虽然裸机环境下,所有arm-2d的API都是以同步模式来工作的(即退出API就意味着任务完成或者出现了错误),理论上的确完全无需arm_2d_op_wait_async(NULL)调用来实现所谓的同步,但以“异步工作模式”使用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(默认) | 可选 |
-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_tileCMSISLogo;
const arm_2d_tile_t c_tileCMSISLogo = {
.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 数据结构的详细内容,大家可以参考文章《【Arm-2D】不整活儿玩啥GUI?》,这里大家“望文生义”即可,容易发现c_tileCMSISLogo:
描述了图片的尺寸信息:163 * 65
描述了图片的颜色信息:ARM_2D_COLOUR_RGB565
由于直接提供了像素数组的地址,因此这是一个Root tile。
既然拿到了Tile,我们不妨赶快试它一试:
1、将生成的CMSIS_Logo_Final.c加入MDK工程参与编译
2、在example_gui.c中加入对应的引用声明:
extern const arm_2d_tile_t c_tileCMSISLogo;
3、在修改 example_gui.c 中的界面绘制函数 example_gui_refresh():
void example_gui_refresh(const arm_2d_tile_t *ptFrameBuffer, bool bIsNewFrame)
{
arm_2d_rgb16_fill_colour(ptFrameBuffer, NULL, GLCD_COLOR_WHITE);
arm_2d_rgb16_tile_copy( &c_tileCMSISLogo,
ptFrameBuffer,
NULL,
ARM_2D_CP_MODE_COPY);
}
这里,我们首先通过 arm_2d_rgb16_fill_colour() 向整个屏幕填充了白色(GLCD_COLOUR_WHITE);紧接着以 arm_2d_rgb16_tile_copy() 将我们的额 c_tileCMSISLogo 拷贝到了屏幕左上角。运行结果如下:
怎么说呢……运行结果正常,却并不能让我们满意——由于缺乏透明度信息,原本应该是完全透明的部分,由于对应像素值为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_tileCMSISLogo相同的像素尺寸,都是163*65;
c_tileCMSISLogoMask的颜色是 8bit的 ARM_2D_COLOUR_8BIT。
它也是一个root tile。
借助专门的函数 arm_2d_rgb565_tile_copy_with_src_mask(),我们就可以轻松达成所需的效果。具体操作如下:
1、在example_gui.c中加入对应的引用声明:
extern const arm_2d_tile_t c_tileCMSISLogoMask;
2、在修改 example_gui.c 中的界面绘制函数 example_gui_refresh():
void example_gui_refresh(const arm_2d_tile_t *ptFrameBuffer, bool bIsNewFrame)
{
arm_2d_rgb16_fill_colour(ptFrameBuffer, NULL, GLCD_COLOR_WHITE);
arm_2d_rgb565_tile_copy_with_src_mask( &c_tileCMSISLogo,
&c_tileCMSISLogoMask,
ptFrameBuffer,
NULL,
ARM_2D_CP_MODE_COPY);
}
编译后运行效果如下:
如果你还记得本文开篇时的那个辅助函数 arm_2d_align_centre()的话,我们还可以借助它实现Logo居中的效果:
1、确保目标 c 文件中增加了 arm_2d_helper.h 的包含:
#include "arm_2d_helper.h"
2、修改 example_gui_refresh() 函数,加入 arm_2d_align_entre():
void example_gui_refresh(const arm_2d_tile_t *ptFrameBuffer, bool bIsNewFrame)
{
arm_2d_rgb16_fill_colour(ptFrameBuffer, NULL, GLCD_COLOR_WHITE);
arm_2d_align_centre(*ptFrameBuffer, c_tileCMSISLogo.tRegion.tSize) {
arm_2d_rgb565_tile_copy_with_src_mask( &c_tileCMSISLogo,
&c_tileCMSISLogoMask,
ptFrameBuffer,
&__centre_region,
ARM_2D_CP_MODE_COPY);
}
arm_2d_op_wait_async(NULL);
}
运行效果如下:
关于 arm_2d_align_centre() 值得说明的有两点:
arm_2d_align_centre() 需要用户传递两类信息:
目标缓冲区的实例(注意是实例,不是地址)。在本例中,显示缓冲区由指针 ptFrameBuffer 保存,因此,传递给 arm_2d_align_centre() 时需要加入"*" 运算,即"(*ptFrameBuffer)"
目标区域的大小信息。在本例中,因为我们希望将CMSIS Logo居中,因此目标区域的大小信息就保存在 c_tileCMSISLogo 中。具体写为 c_tileCMSISLogo.tRegion.tSize
最终结果就是代码中所呈现的那样:
arm_2d_align_centre(*ptFrameBuffer,
c_tileCMSISLogo.tRegion.tSize) {
...
}
arm_2d_align_centre() 会产生一个局部变量 __centre_region,它的生命周期仅限于 arm_2d_align_centre() 的花括号内,因此,当我们使用 __centre_region 进行绘图时,要将其生命周期考虑在内——必须在其生命结束前加入 arm_2d_op_wait_async() 以确保API执行的有效性:
void example_gui_refresh(const arm_2d_tile_t *ptFrameBuffer, bool bIsNewFrame)
{
arm_2d_rgb16_fill_colour(ptFrameBuffer, NULL, GLCD_COLOR_WHITE);
arm_2d_align_centre(*ptFrameBuffer, c_tileCMSISLogo.tRegion.tSize) {
arm_2d_rgb565_tile_copy_with_src_mask( &c_tileCMSISLogo,
&c_tileCMSISLogoMask,
ptFrameBuffer,
&__centre_region,
ARM_2D_CP_MODE_COPY);
arm_2d_op_wait_async(NULL);
}
}
【说在后面的话】
1.芯片都去哪了?美国SIA年度报告揭秘~
2.树莓派偷偷换上新SoC!
3.MCU从入门到躺平
4.车规级MCU国产替代下的选择题:车厂策略激进,直供能成为未来主流?
5.编程语言成功的几大要素
6.碳中和,不确定世界里的确定性
免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。