一、ARM2D的虚拟资源
当我们想要制作GUI界面时,一定会遇到片内flash不够用的情况,特别是M0等小容量片内flash架构芯片,这时候,寻求便宜大碗的外部flash或者sd卡确实是一个不错的选择;
借助ARM2D的文档,我们得知,为了解决这一问题,arm-2d 在基类 arm_2d_tile_t 的基础上派生出了一个新的类:虚拟资源(Virtual Resource ),arm_2d_vres_t------专门用于描述这类无法直接访问的图片资源。
在工程管理器中展开 Acceleration ,并找到arm_2d_disp_adapter_0.h, 通过Configuraion Wizard 打开图形配置界面,在下拉列表"Maximum number of Virtual Resources used per API**" 中勾选除"NO Virtual Resource"以外的选项后保存,比如 1 per API。**
虚拟资源本质上是通过将外部存储器中的图片资源载入到一块芯片内部的缓冲区(RAM)中,并以此来完成后续的贴图操作。如果一个API中用到了一个虚拟素材,就需要一块缓冲;
两个虚拟素材(RGB565的图片的像素数组(Source),图片对应的蒙版(Source Mask))对应两块缓冲;三个虚拟素材,就需要三块缓冲,像素数组(Source),图片对应的蒙版(Source Mask),目标缓冲对应的蒙版(Target Mask)
除此之外,如果只是 使用虚拟资源来保存一些背景图片,或者是GIF逐帧展开后的素材,只需使用 arm_2d_tile_copy_only() 就足以应对。这时候可以开启背景载入模式(Background Loading Mode)
同时我们还需要实现两个函数,从外部存储器的指定地址读取指定长度字节 的函数,以及用于返回当前虚拟资源起始地址的函数
cpp
void __disp_adapter0_vres_read_memory(
intptr_t pObj,
void *pBuffer,
uintptr_t pAddress,
size_t nSizeInByte);
-
pObj我们可以暂时忽略
-
pBuffer 指向一块缓冲区,用于保存我们从外部存储器中读取到的内容;
-
pAddress 保存的是目标内容在外部存储器中的地址;
-
nSizeInByte 保存的是要读取的字节数
cpp
uintptr_t __disp_adapter0_vres_get_asset_address(
uintptr_t pObj,
arm_2d_vres_t *ptVRES);
-
pObj 我们可以暂时忽略
-
ptVRES 指向的是我们的目标虚拟资源
二、资源放入Flash
这里又会存在一个问题,我该怎么把图片等资源放进Flash呢
作者本人大致有三种思路,通过Jlink等工具单独烧录flash;通过串口转SPI烧录;通过FLM算法同步烧录;最终是选择方案三,关于使用STM32制作外部Flash FLM下载算法-CSDN博客
先来看SCT文件,在分析代码前,需要理解几个缩写:
- LR (Load Region):加载域。程序烧录时在 Flash 中的状态。
- ER (Execution Region):执行域。程序运行时,代码和数据所处的地址。
- RO (Read Only):只读段。包括代码(Code)和常量(const)。
- RW (Read Write):读写段。已初始化的全局/静态变量。
- ZI (Zero Initialized):零初始化段。未初始化的全局/静态变量,运行时会被清零。
- ANY:通配符,表示将剩下的、没有指定位置的所有目标文件自动分配。
cpp
LR_IROM1 0x08000000 0x00080000 { ; 加载域:起始地址0x08000000 (内部Flash),大小512KB
ER_IROM1 0x08000000 0x00080000 { ; 执行域:通常与加载域一致
*.o (RESET, +First) ; 将中断向量表(RESET段)放在最前面(必须在0x08000000)
*(InRoot$$Sections) ; 库函数的启动部分,必须在根域(Root Region)
.ANY (+RO) ; 所有的只读代码和常量自动放在这里
.ANY (+XO) ; 所有的只执行代码(如果有)
}
RW_IRAM1 0x20000000 0x0001C000 { ; 执行域:内部RAM1,起始0x20000000,大小112KB
.ANY (+RW +ZI) ; 将变量(RW和ZI)放入RAM
}
RW_IRAM2 0x2001C000 0x00004000 { ; 执行域:内部RAM2,起始0x2001C000,大小16KB
.ANY (+RW +ZI) ; 如果RAM1满了,剩下的变量会自动放到这里
}
}
这部分是 STM32 运行的核心。它定义了 512KB 的 Flash 空间,并将 RAM 分成了两个连续的块(IRAM1 和 IRAM2)
cpp
; 外部 SPI Flash (W25Q128) 区域
LR_EXTFLASH 0x00000000 0x01000000 { ; 加载域:大小16MB
ER_EXTFLASH 0x00000000 0x01000000 {
*(.extflash*) ; 将所有标记为 ".extflash" 的段放在这里
}
}
这里的地址是给 Keil 的烧录算法(.FLM 文件)看的。下载时,链接器生成的 bin 数据中,属于这一段的内容会被烧录算法写入到 SPI Flash 的对应偏移位置
有了这个 SCT 文件,在 C 语言中就可以指定变量的去向了
cpp
__attribute__((section(".extflash"), used))
这里用一张102*102的图片来举例子,补充一点理论,使用arm2d的python脚本生成的图片C文件通常含有两个数组,这里先只考虑RGB数组
原始 PNG 图片
├─ RGB 颜色通道 ────→ c_tileCMSISLogo(颜色 tile)
└─ Alpha 透明度通道 ─→ c_tileCMSISLogoMask(Mask tile)
| 参数 | Mask 位深度 | 透明度级别 | 用途 |
|---|---|---|---|
--a1 |
1位 | 2级(0或255) | 简单透明/不透明,省内存 |
--a2 |
2位 | 4级透明度 | 基础抗锯齿 |
--a4 |
4位 | 16级透明度 | 较好抗锯齿,平衡内存 |
--a8 |
8位 | 256级透明度 | 最佳质量,最耗内存 |
这里先将数组纳入外部flash区域,然后去读取flash内容,说明成功写入了

cpp
uint8_t ext_read_buf[256];
Flash_FastReadBytes(0x00000000u, ext_read_buf, (uint16_t)sizeof(ext_read_buf));
for (int i = 0; i < 16; i++) {
printf("%02X ", ext_read_buf[i]);
}

三、完成接口函数
这里的Flash驱动库我使用网上寻找到的驱动W25Q128的代码(已经测试过,w25flash.c和.h)STM32CubeMX教程20 SPI - W25Q128驱动 - OSnotes
cpp
void __disp_adapter0_vres_read_memory( intptr_t pObj,
void *pBuffer,
uintptr_t pAddress,
size_t nSizeInByte)
{
// 参数检查
if (pBuffer == NULL || nSizeInByte == 0) {
return;
}
// 将地址转换为Flash函数所需的类型
uint32_t flash_addr = (uint32_t)pAddress;
uint8_t *pDestBuffer = (uint8_t *)pBuffer;
size_t remaining_size = nSizeInByte;
// 由于Flash_FastReadBytes的byteCount参数是uint16_t类型
// 如果数据超过65535字节,需要分多次读取
while (remaining_size > 0) {
uint16_t read_size = (remaining_size > 65535) ? 65535 : (uint16_t)remaining_size;
Flash_FastReadBytes(flash_addr, pDestBuffer, read_size);
// 更新地址和缓冲区指针
flash_addr += read_size;
pDestBuffer += read_size;
remaining_size -= read_size;
}
}
uintptr_t __disp_adapter0_vres_get_asset_address(uintptr_t pObj,
arm_2d_vres_t *ptVRES)
{
ARM_2D_UNUSED(ptVRES);
return pObj;
}
在场景里先声明虚拟资源
cpp
static arm_2d_vres_t s_tMyVirtualRes =
disp_adapter0_impl_vres(
ARM_2D_COLOUR_RGB565, // 图片的颜色格式
102, // 图片的宽度
102, // 图片的高度
.pTarget = 0x00000000 //这个资源在外部存储器中的地址
);
在绘图代码区域添加这一行代码就可以正常显示了
cpp
arm_2d_tile_copy(&s_tMyVirtualRes.tTile, ptTile, NULL);
如果使用多个图片,则类似的加上不同地址即可;