【例說Arm-2D界面設(shè)計(jì)】片內(nèi)Flash不夠怎么辦?
掃描二維碼
隨時(shí)隨地手機(jī)看文章
【說在前面的話】
在前面的文章《【例說Arm-2D界面設(shè)計(jì)】“手?jǐn)]GUI”的利器——場景播放器》中,我們詳細(xì)介紹了智能設(shè)備時(shí)代一種“基于面板(Panel)的嵌入式界面設(shè)計(jì)范式”,并以Arm-2D的場景播放器(Scene Player)為例,介紹了小資源環(huán)境下具體“手搓GUI”的方式。
很多小伙伴看了以后大為震撼,并紛紛發(fā)出了叩問靈魂的拷問:
我芯片內(nèi)部Flash已經(jīng)很緊張了,隨便一個(gè)背景圖你讓我存哪里?
一般來說,很多芯片會(huì)提供一個(gè)叫做 XIP 的外設(shè)(也許你的芯片中對(duì)應(yīng)的外設(shè)并不叫這個(gè)名字,但你可以根據(jù)下面的功能描述來對(duì)號(hào)入座),以便:
- 通過QSPI接口連接外部的SPI Flash芯片;
- 將外部Flash芯片中的內(nèi)容映射到4G地址空間中
換句話說,我們可以像訪問內(nèi)部Flash那樣使用芯片外部的SPI Flash,這其中不僅包括存儲(chǔ)數(shù)據(jù)(比如圖片),甚至還可以執(zhí)行代碼。
如果你芯片擁有XIP,那么你的芯片已經(jīng)不屬于我們所討論的“資源受限”的環(huán)境了——因?yàn)榇笕萘康钠釬lash不僅長見而且廉價(jià)。相信很多人對(duì)此都非常熟悉,我就不再贅述了。
如果你的芯片沒有XIP,而你也只能通過外設(shè) SPI 和自己編寫的驅(qū)動(dòng)來訪問外部 Flash,此時(shí),我們?nèi)绾问褂?Arm-2D 來簡化“手搓GUI” 的開發(fā)過程呢?不要急,本文就將為你揭曉謎底。
【什么是虛擬資源(Virtual Resource)】
Arm-2D幾乎所有API的基本操作單位都是“貼圖(Tile)”,它的數(shù)據(jù)結(jié)構(gòu)定義如下:
/*! * \brief a type for tile * */typedef struct arm_2d_tile_t arm_2d_tile_t;struct arm_2d_tile_t { implement_ex(struct { uint8_t bIsRoot : 1; //!< is this tile a root tile uint8_t bHasEnforcedColour : 1; //!< does this tile contains enforced colour info uint8_t bDerivedResource : 1; //!< indicate whether this is a derived resources (when bIsRoot == 0) uint8_t bVirtualResource : 1; //!< indicate whether the resource should be loaded on-demand uint8_t : 4; uint8_t : 8; uint8_t : 8; arm_2d_color_info_t tColourInfo; //!< enforced colour }, tInfo); implement_ex(arm_2d_region_t, tRegion); //!< the region of the tile union { /* when bIsRoot is true, phwBuffer is available, * otherwise ptParent is available */ arm_2d_tile_t *ptParent; //!< a pointer points to the parent tile uint8_t *pchBuffer; //!< a pointer points to a buffer in a 8bit colour type uint16_t *phwBuffer; //!< a pointer points to a buffer in a 16bit colour type uint32_t *pwBuffer; //!< a pointer points to a buffer in a 32bit colour type intptr_t nAddress; //!< a pointer in integer };};
-
貼圖的各類屬性描述信息:tInfo
-
貼圖的尺寸和位置信息: tRegion
-
貼圖的指針或引用
貼圖從種類上來說分類兩種:根貼圖(Root Tile)和子貼圖(Child Tile)。其中,根貼圖是指那些直接“擁有”具體圖片資源(或者是顯示緩沖區(qū))的貼圖(Tile),這表現(xiàn)在:
- 根貼圖的屬性 tInfo.bIsRoot 一定為 true;
- 根貼圖的指針直接指向具體的資源數(shù)組或者現(xiàn)實(shí)緩沖區(qū)
從前面的數(shù)據(jù)結(jié)構(gòu)中,我們可以看到union中有很多指針,比如 pchBuffer、phwBuffer和pwBuffer。由于他們都是共用體,因此這些指針保存的地址值都是相同的,而具體使用哪個(gè)類型的指針則取決于目標(biāo)資源的顏色格式,這一信息可以是省略的,但一般通過 img2c.py 腳本轉(zhuǎn)換出來的tile都會(huì)在tInfo.tColourInfo中包含具體的顏色信息。
值得強(qiáng)調(diào)的是 ptParent 僅在子貼圖中有意義,用于指向自己的父貼圖(Parent Tile),而 nAddress 僅僅是方便對(duì)地址值進(jìn)行四則運(yùn)算的一個(gè)整形變量(uintptr_t)。
也許你已經(jīng)從根貼圖的指針看出了端倪:普通的根貼圖要求其指向的圖片資源必須存在于4G地址空間中——換句話說就是普通指針可以訪問的地方——保存在外部Flash中的圖片資源(在未經(jīng)XIP幫助的情況下)則無法滿足上述要求,因此無法直接用 arm_2d_tile_t 進(jìn)行描述。
為了解決這一問題,arm-2d 在基類 arm_2d_tile_t 的基礎(chǔ)上派生出了一個(gè)新的類:虛擬資源(Virtual Resource),arm_2d_vres_t——專門用于描述這類無法直接訪問的圖片資源。其數(shù)據(jù)結(jié)構(gòu)如下:
/*! * \brief a type for virtual resource * * \note the flag tTile.tInfo.bVirtualResource must be true (1) */typedef struct arm_2d_vres_t arm_2d_vres_t;struct arm_2d_vres_t { /*! base class: tTile */ implement_ex( arm_2d_tile_t, tTile); /*! a reference of an user object */ uintptr_t pTarget; /*! * \brief a method to load a specific part of an image * \param[in] pTarget a reference of an user object * \param[in] ptVRES a reference of this virtual resource * \param[in] ptRegion the target region of the image * \return intptr_t the address of a resource buffer which holds the content */ intptr_t (*Load) ( uintptr_t pTarget, arm_2d_vres_t *ptVRES, arm_2d_region_t *ptRegion); /*! * \brief a method to despose the buffer * \param[in] pTarget a reference of an user object * \param[in] ptVRES a reference of this virtual resource * \param[in] pBuffer the target buffer */ void (*Depose) ( uintptr_t pTarget, arm_2d_vres_t *ptVRES, intptr_t pBuffer );};
【如何使用虛擬資源?】
這里,我們假設(shè)你已經(jīng)按照文章《【喂到嘴邊了的模塊】準(zhǔn)備徒手?jǐn)]GUI?用Arm-2D三分鐘就夠了》的步驟完成了Arm-2D的部署。
準(zhǔn)備階段:
在工程管理器中展開 Acceleration,并找到你的LCD驅(qū)動(dòng)模板 arm_2d_disp_adapter_0.h(這里假設(shè)你只有一個(gè)屏幕):
通過Configuraion Wizard打開圖形配置界面:
假設(shè)你已經(jīng)配置好了其它部分,勾選這里的“Enable the virtual resoure helper service” 選項(xiàng)后保存——至此,我們就為 Display Adapter 0 開啟了其專屬的虛擬資源輔助服務(wù)(Helper Service)。需要強(qiáng)調(diào)的是,每個(gè)Display Adapter 都有自己獨(dú)立的虛擬資源輔助服務(wù),需要獨(dú)立的打開。
Error: L6218E: Undefined symbol __disp_adapter0_vres_get_asset_address (referred from arm_2d_disp_adapter_0.o).Error: L6218E: Undefined symbol __disp_adapter0_vres_read_memory (referred from arm_2d_disp_adapter_0.o).
不要慌,這是我們有意為之——它提醒我們作為用戶需要提供(實(shí)現(xiàn))兩個(gè)最基本額接口函數(shù):
-
__disp_adapter0_vres_read_memory()
void __disp_adapter0_vres_read_memory( intptr_t pObj, void *pBuffer, uintptr_t pAddress, size_t nSizeInByte);
這里:
-
pObj 我們可以暫時(shí)忽略
-
pBuffer 指向一塊緩沖區(qū),用于保存我們從外部存儲(chǔ)器中讀取到的內(nèi)容;
-
pAddress 保存的是目標(biāo)內(nèi)容在外部存儲(chǔ)器中的地址;
-
nSizeInByte 保存的是要讀取的字節(jié)數(shù)
一般來說,如果我們已經(jīng)事先調(diào)試好了一個(gè)SPI Flash讀取函數(shù),就可以輕松的實(shí)現(xiàn)這一函數(shù),比如:
extern void spi_flash_read(void *pBuffer, uint32_t nAddressInFlash, size_t nSize); void __disp_adapter0_vres_read_memory(intptr_t pObj, void *pBuffer, uintptr_t pAddress, size_t nSizeInByte){ ARM_2D_UNUSED(pObj); /* it is just a demo, in real application, you can place a function to * read SPI Flash */ spi_flash_read(pBuffer, (void * const)pAddress, nSizeInByte);}
-
__disp_adapter0_vres_get_asset_address()
uintptr_t __disp_adapter0_vres_get_asset_address( uintptr_t pObj, arm_2d_vres_t *ptVRES);
這里:
-
pObj 我們可以暫時(shí)忽略
-
ptVRES 指向的是我們的目標(biāo)虛擬資源
在最簡單的情況下,假設(shè)你的系統(tǒng)只有一背景圖保存在外部SPI Flash中,且地址為 0x00000000,那么這個(gè)函數(shù)就極其簡單了:
uintptr_t __disp_adapter0_vres_get_asset_address(uintptr_t pObj, arm_2d_vres_t *ptVRES){ ARM_2D_UNUSED(ptVRES); ARM_2D_UNUSED(pObj); return 0x00000000;}
也許你要問,如果我要處理多個(gè)圖片該怎么辦呢?別著急,后面會(huì)有專門的章節(jié)詳細(xì)介紹?,F(xiàn)階段我們先專注于完成一個(gè)最簡單的例子。
完成了上述準(zhǔn)備工作,再次編譯就應(yīng)該毫無問題了。
創(chuàng)建自己的虛擬資源:
在要?jiǎng)?chuàng)建虛擬資源的源代碼中加入對(duì) Display Adapter 0 的頭文件引用:
定義一個(gè) arm_2d_vres_t 類型的靜態(tài)變量(或者全局變量),并使用專門的宏 disp_adapter0_impl_vres 來描述資源的顏色和尺寸信息,比如:
static arm_2d_vres_t s_tMyVirtualRes = disp_adapter0_impl_vres( ARM_2D_COLOUR_RGB565, // 圖片的顏色格式 320, // 圖片的寬度 256, // 圖片的高度 );
其中,宏disp_adapter0_impl_vres() 是 Display Adapter 0 專用的,以此類推,如果你的虛擬資源要在 Display Adapter 1上使用,則對(duì)應(yīng)的描述宏為 disp_adapter1_impl_vres()。不管如何,它們的原型是一樣的:
disp_adapter0_impl_vres(__COLOUR_FORMAT, __WIDTH, __HEIGHT,...)
這里:
-
__COLOUR_FORMAT 是目標(biāo)素材的顏色格式,具體可用的顏色在 arm_2d_type.h 中定義,都以 ARM_2D_COLOUR_ 作為前綴。
-
__WIDTH 是目標(biāo)素材的像素寬度
-
__HEIGHT是目標(biāo)素材的像素高度
-
... 是一系列可選的參數(shù),主要用于初始化 arm_2d_vres_t 中的一些特殊成員(比如 pTarget),這個(gè)在隨后的章節(jié)中會(huì)用到。
在上述例子中,我們創(chuàng)建了一個(gè)虛擬資源 s_tMyVirtualRes,由于它是 arm_2d_tile_t 的派生類,因此可以像普通的貼圖那樣在arm-2d的API中作為素材(source tile)和蒙版(mask)來直接使用,比如:
/* 把 虛擬素材 顯示在屏幕上 */arm_2d_tile_copy( &s_tMyVirtualRes.tTile, /* 素材 */ ptTile, /* 目標(biāo)緩沖區(qū) */ NULL, ARM_2D_CP_MODE_COPY);
效果如下:
正如我們前面說過的,虛擬素材(virtual resource)也是貼圖(Tile)的一種,因此,也可以在它的基礎(chǔ)上創(chuàng)建子貼圖(Child Tile),比如:
staticconst arm_2d_tile_t c_tChildImage = { .tRegion = { .tLocation = { .iX = 160, .iY = 128, }, .tSize = { .iWidth = 160, .iHeight = 128, }, }, .tInfo = { .bIsRoot = false, .bDerivedResource = true, }, .ptParent = (arm_2d_tile_t *)&s_tMyVirtualRes.tTile,};
這里:
-
bIsRoot為false,清晰的標(biāo)明了 c_tChildImage 的子貼圖身份;
-
創(chuàng)建子貼圖作為素材時(shí),bDerivedResource一定要設(shè)置為 true,切記切記!
-
這個(gè)例子中,觀察 tLocation和tSize容易發(fā)現(xiàn):我們實(shí)際上是取了原圖右下角的1/4作為新的素材
修改代碼,將新的素材也拷貝到屏幕上:
/* 把 虛擬素材 顯示在屏幕上 */arm_2d_tile_copy( &s_tMyVirtualRes.tTile, /* 素材 */ ptTile, /* 目標(biāo)緩沖區(qū) */ NULL, ARM_2D_CP_MODE_COPY);/* 把 子貼圖 顯示在屏幕上 */arm_2d_tile_copy( &c_tChildImage, /* 素材 */ ptTile, /* 目標(biāo)緩沖區(qū) */ NULL, ARM_2D_CP_MODE_COPY);
由于我們在拷貝子貼圖時(shí)沒有指定要復(fù)制的位置(給了NULL),因此被默認(rèn)放置到了屏幕的左上角,形成了如下的效果:
【我們有多個(gè)圖片該怎么辦?】
前面的例子中,為了讓小伙伴們快速的體驗(yàn)虛擬資源的爽快,因此我們對(duì)內(nèi)容作了簡化——只演示了一個(gè)圖片的情況——實(shí)際應(yīng)用中,顯然這是無法滿足要求的。
聰明的小伙伴也許已經(jīng)注意到了,當(dāng)存在多個(gè)圖片資源的時(shí)候,決定我們實(shí)際讀取那一張圖片的關(guān)鍵就是函數(shù) __disp_adapter0_vres_get_asset_address() 的返回值——它返回誰的地址,讀取的就是誰的圖片。
觀察它的函數(shù)原型,容易發(fā)現(xiàn)兩個(gè)形參都很有潛質(zhì)。
uintptr_t __disp_adapter0_vres_get_asset_address( uintptr_t pObj, arm_2d_vres_t *ptVRES)
換句話說,支持多圖片的關(guān)鍵就在于如何使用傳遞進(jìn)來的參數(shù)返回對(duì)應(yīng)圖片在外部存儲(chǔ)器中的地址。
進(jìn)一步觀察 arm_2d_vres_t 的結(jié)構(gòu),我們可以注意到一個(gè)有趣的成員 pTarget:
typedef struct arm_2d_vres_t arm_2d_vres_t;struct arm_2d_vres_t { ... /*! a reference of an user object */ uintptr_t pTarget; ...};
無論我們給它賦任何內(nèi)容,它的值都會(huì)作為第一個(gè)實(shí)參傳遞給接口函數(shù):
__disp_adapter0_vres_read_memory(intptr_t pObj, …… )
和
__disp_adapter0_vres_get_asset_address(uintptr_t pObj, ……)
也就是這里的 pObj。
至此,對(duì)多圖片的支持實(shí)際上就形成了兩種方式:
-
面向?qū)ο蟮姆绞剑∣OPC)
-
所見即所得的方式
剩下的篇幅,我們將著重介紹“所見即所得”的方法:
static arm_2d_vres_t s_tVRes0 = disp_adapter0_impl_vres( ARM_2D_COLOUR_RGB565, 32, 32, .pTarget = <這個(gè)資源在外部存儲(chǔ)器中的地址> ); static arm_2d_vres_t s_tVRes1 = disp_adapter0_impl_vres( ARM_2D_COLOUR_RGB565, 32, 32, .pTarget = <這個(gè)資源在外部存儲(chǔ)器中的地址> );...
步驟二:在函數(shù) __disp_adapter0_vres_get_asset_address 里直接將 pObj 的值(也就是 ptVRES->pTarget)的值返回:
uintptr_t __disp_adapter0_vres_get_asset_address(uintptr_t pObj, arm_2d_vres_t *ptVRES){ ARM_2D_UNUSED(ptVRES); return pObj;}