【說在前面的話】
說實(shí)話,LVGL這么有牌面的項(xiàng)目,其維護(hù)者居然沒聽說過cmsis-pack,這著實(shí)讓我略為破防:
連lwIP都在Pack-Installer里有個(gè)坑位,難道這是個(gè)LVGL的“燈下黑”?
對這樣的嵌入式開源界的“世界級明星白菜”,難得遇到一個(gè)“染指”的機(jī)會,怎能不“拱”一下呢?
省略一萬字的過程描述……
我為LVGL做的CMSIS-Pack終于被合并入倉庫主線啦!
【如何獲取 LVGL cmsis-pack】
1、用戶可以通過LVGL在Github的倉庫直接下載:
https://github.com/lvgl/lvgl/tree/master/env_support/cmsis-pack2、不久的將來,應(yīng)該可以直接通過MDK的Pack-Installer進(jìn)行直接安裝,就像lwIP那樣:
無論采用哪種方法,一旦完成安裝,以后就可以通過Pack-Installer來獲取最新版本啦。
【如何在MDK中部署LVGL】
步驟一:配置RTE
在MDK中通過菜單 Project->Manage->Run-Time Enviroment 打開RTE配置窗口:
在RTE配置界面中找到LVGL,將其展開:
與其它平臺下部署LVGL不同,cmsis-pack允許大家像點(diǎn)菜那樣只將所需的模塊(或者功能)加入到工程中。
注意,這里必點(diǎn)的是“Essential”,它是LVGL的核心服務(wù)。一般來說,為了使用LVGL所攜帶的豐富控件(Widgets),我們還需要選中“Extra Themes”。如果你是第一次為當(dāng)前硬件平臺進(jìn)行LVGL移植,則非常推薦加點(diǎn)“Porting”——它會為你添加移植所需的模板,非常方便。 單擊“OK”關(guān)閉RTE配置窗口,我們會看到LVGL已經(jīng)被加入到工程列表中了:
此時(shí),我們就已經(jīng)可以成功編譯了。
步驟二:配置LVGL
將LVGL展開,找到配置頭文件 lv_conf_cmsis.h:
該文件其實(shí)就是LVGL官方移植文檔中所提到的lv_conf.h,它是基于lv_conf_template.h修改而來。值得說明的是,一些模塊的開關(guān)宏都被刪除了,例如:
LV_USE_GPU_STM32_DMA2D
LV_USE_GPU_NXP_PXP
……
這是因?yàn)椋?span style="color:rgb(0,209,0);">當(dāng)我們在RTE配置窗口中勾選對應(yīng)選項(xiàng)時(shí),cmsis-pack就會自動把對應(yīng)的宏定義加入到 RTE_Components.h 里——換句話說,再也不用我們手動添加啦!
其它對LVGL的配置,請參考官方文檔,這里就不再贅述。
步驟三:使用模板進(jìn)行移植
當(dāng)我們在RTE中選擇了porting模塊后,三個(gè)移植模板會被加入到工程列表中。它們是可以編輯的,保存在當(dāng)前工程的RTE/LVGL目錄中。
這些模板極大的簡化了我們的驅(qū)動移植過程,下面,我們將以lv_port_disp_template為例,為大家介紹這些模板的使用方法:
1、打開 lv_port_disp_template.h,將開頭處 #if0 修改為 #if 1,使整個(gè)頭文件生效:
2、更新對 lvgl.h頭文件的引用路徑,從 "lvgl/lvgl.h" 改為 "lvgl.h"
/*********************
* INCLUDES
*********************/
#include "lvgl.h"
這是因?yàn)?span style="color:rgb(61,170,214);">cmsis-pack已經(jīng)為 lvgl.h 添加了引用路徑,因此在整個(gè)工程的任意地方都可以直接使用 #include "lvgl.h"。
3、補(bǔ)充對 lv_port_disp_init() 函數(shù)的聲明:
/**********************
* GLOBAL PROTOTYPES
**********************/
extern voidlv_port_disp_init(void);
4、打開lv_port_disp_template.c,將開頭處#if0修改為#if 1,使整個(gè)遠(yuǎn)文件生效。并以與步驟2相同的方式處理好對 lvgl.h 的引用。
5、根據(jù)官方 porting 文檔的指導(dǎo),根據(jù)你的硬件實(shí)際情況,在三種緩沖模式中做出選擇:
需要特別強(qiáng)調(diào)的是:如果你的系統(tǒng)沒有DMA或者替用戶完成Frame Buffer刷新的專門LCD控制器,那么雙緩沖其實(shí)是沒有意義的(因?yàn)闊o論如何都是CPU在干活,因此不會比單緩沖模式有任何性能上的本質(zhì)不同)。
6、找到disp_init() 函數(shù),并在其中添加LCD的初始化代碼。該函數(shù)會被 lv_port_disp_init()調(diào)用。
7、找到 disp_flush()函數(shù),并根據(jù)你的硬件實(shí)際情況,將其改寫。比如這是使用 GLCD_DrawBitmap進(jìn)行實(shí)現(xiàn)的參考代碼:
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
GLCD_DrawBitmap(area->x1, //!< x
area->y1, //!< y
area->x2 - area->x1 + 1, //!< width
area->y2 - area->y1 + 1, //!< height
(constuint8_t*)color_p);
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
GLCD_DrawBitmap 用于將給定的顯示緩沖區(qū)刷新到LCD,其函數(shù)原型如下:
/**
\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個(gè)參數(shù)之間的關(guān)系如下圖所示:
簡單來說,這個(gè)函數(shù)就是把bitmap指針?biāo)赶虻摹斑B續(xù)存儲區(qū)域”中保存的像素信息拷貝到LCD的一個(gè)指定矩形區(qū)域內(nèi),這一矩形區(qū)域由位置信息(x,y)和體積信息(width,height)共同確定。很多LCD都支持一個(gè)叫做“操作窗口”的概念,這里的窗口其實(shí)就是上圖中的矩形區(qū)域——一旦你通過指令設(shè)置好了窗口,隨后連續(xù)寫入的像素就會被依次自動填充到指定的矩形區(qū)域內(nèi)(而無需用戶去考慮何時(shí)進(jìn)行折行的問題)。
此外,如果你有幸使用帶LCD控制器的芯片——LCD的顯示緩沖區(qū)被直接映射到Cortex-M芯片的4GB地址空間中,則我們可以使用簡單的存儲器讀寫操作來實(shí)現(xiàn)上述函數(shù),以STM32F746G-Discovery開發(fā)板為例:
//!STM32F746G-Discovery
#define GLCD_WIDTH 480
#define GLCD_HEIGHT 272
#defineLCD_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)
{
volatileuint16_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;
}
7、在 main.c 中加入對 lv_port_disp_template.h 的引用:
8、在main()函數(shù)中對LVGL進(jìn)行初始化:
int main(void)
{
...
lv_init();
lv_port_disp_init();
...
while(1) {
}
}
至此,我們就完成了LVGL在MDK工程的部署。是不是特別簡單?
【時(shí)間相關(guān)的移植】
根據(jù)官方移植文檔的要求,我們實(shí)際上還需要處理兩個(gè)問題:
-
讓lvgl 知道從復(fù)位開始經(jīng)歷了多少毫秒
-
以差不多5ms為間隔,調(diào)用函數(shù)lv_timer_handler() 來進(jìn)行事件處理(包括刷新)
依據(jù)平臺的不同,小伙伴們當(dāng)然有自己的解決方案。這里,我推薦一個(gè)MDK環(huán)境下基于perf_counter的方案,它更通用,也更簡單。關(guān)于它的使用文章,小伙伴可以參考《【喂到嘴邊了的模塊】超級嵌入式系統(tǒng)“性能/時(shí)間”工具箱》,這里就不再贅述。 步驟一:獲取 perf_counter 的cmsis-pack
關(guān)注公眾號【裸機(jī)思維】后,向后臺發(fā)送關(guān)鍵字“perf_counter”獲取對應(yīng)的cmsis-pack網(wǎng)盤鏈接。下載后安裝。
步驟二:在工程中部署 perf_counter
打開RTE配置窗口,找到 Utilities 后展開,選中 perf_counter的 Core:
需要說明的是,無論你用不用操作系統(tǒng),這里關(guān)于各類操作系統(tǒng)的 Patch 你即便不選擇也能正常工作,不必?fù)?dān)心。單擊OK后即完成了部署。 在main()函數(shù)中初始化 perf_counter(別忘記添加對頭文件 perf_counter.h 的包含):
intmain(void)
{
/*配置MCU的系統(tǒng)時(shí)鐘頻率 */
/*重要:更新 SystemCoreClock 變量*/
SystemCoreClockUpdate();
/*初始化perf_counter*/
init_cycle_counter(true);
...
while(1) {
}
...
}
需要特別說明的是:
-
調(diào)用 init_cycle_counter() 之前,最好通過 SystemCoreClockUpdate() 來將當(dāng)前的系統(tǒng)頻率更新到關(guān)鍵全局變量 SystemCoreClock 上。你當(dāng)然也可以自己用賦值語句來做,比如:
extern uint32_t SystemCoreClock;
SystemCoreClock=72000000ul; /* 72MHz */
-
如果你已經(jīng)有應(yīng)用或者RTOS占用了SysTick(一般都是這樣),則應(yīng)該將 true 傳遞給 init_cycle_counter() 作為參數(shù)——告訴 perf_counter SysTick已經(jīng)被占用了;反之則應(yīng)該傳遞 false,此時(shí) perf_counter 會用最大值 0x00FFFFFF來初始化SysTick。
步驟三:更新 lv_conf_cmsis.h
打開 lv_conf_cmsis.h,找到宏 LV_TICK_CUSTOM 所在部分:/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
將其替換為:
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
extern uint32_t SystemCoreClock;
(get_system_ticks()/(SystemCoreClock/1000ul))
步驟四:處理lv_timer_handler()
雖然 LVGL 的官方文檔中專門指出過lv_timer_handler()-
不是線程安全的
-
應(yīng)該放在較低優(yōu)先級的中斷處理程序中
-
在RTOS中使用時(shí),應(yīng)該考慮通過互斥量來建立臨界區(qū)來避免與lv_tick_inc() 產(chǎn)生“沖突”
然而,使用 perf_counter() 進(jìn)行部署時(shí),由于我們避開了lv_tick_inc() ,因此上述限制就都“煙消云散”了,我們完全可以將 lv_timer_handler() 簡單的放置到 SysTick_Handler中,比如:
void SysTick_Handler(void)
{
//!典型的 1ms 中斷
staticuint8_ts_chDivider = 0;
if((++s_chDivider)>= 5) {
s_chDivider=0;
//! 每 5ms 處理一次
lv_timer_handler();
}
/*! \note please do not put following code here
*!
*! lv_tick_inc(5);
*!
*! Use a custom tick source that tells the elapsed time in milliseconds.
*! It removes the need to manually update the tick with `lv_tick_inc()`)
*! #define LV_TICK_CUSTOM 1
*! #if LV_TICK_CUSTOM
*! extern uint32_t SystemCoreClock;
*! #define LV_TICK_CUSTOM_INCLUDE "perf_counter.h"
*! #define LV_TICK_CUSTOM_SYS_TIME_EXPR \
*! (get_system_ticks() / (SystemCoreClock / 1000ul))
*! #endif
*/
}
當(dāng)然,以上處理只是一種“偷懶”,實(shí)際上,考慮到LVGL的繪圖過程可能會“耗時(shí)過長”,如果SysTick還有別的功能,那么直接將lv_timer_handler()放置在SysTick_Handler中其實(shí)并不是一個(gè)值得推薦的方案,裸機(jī)環(huán)境下,一個(gè)更為實(shí)用的方案是:
staticvolatilebools_bLVTMRFlag = false;
void SysTick_Handler(void)
{
//! 典型的 1ms 中斷
static uint8_t s_chDivider = 0;
if ((++s_chDivider) >= 5) {
s_chDivider = 0;
//! 每 5ms 處理一次
s_bLVTMRFlag = true;
}
/*! \note please do not put following code here
*!
*! lv_tick_inc(5);
*!
*! Use a custom tick source that tells the elapsed time in milliseconds.
*! It removes the need to manually update the tick with `lv_tick_inc()`)
*! #define LV_TICK_CUSTOM 1
*! #if LV_TICK_CUSTOM
*! extern uint32_t SystemCoreClock;
*! #define LV_TICK_CUSTOM_INCLUDE "perf_counter.h"
*! #define LV_TICK_CUSTOM_SYS_TIME_EXPR \
*! (get_system_ticks() / (SystemCoreClock / 1000ul))
*! #endif
*/
}
intmain(void)
{
SystemCoreClockUpdate();
init_cycle_counter(true);
...
lv_init();
lv_port_disp_init();
...
while(1){
...
do {
boolbLVFlag;
//!原子保護(hù)
__IRQ_SAFE {
bLVFlag = s_bLVTMRFlag;
s_bLVTMRFlag = false;
};
if (bLVFlag) {
lv_timer_handler();
}
} while(0);
}
}
【跑分從未如此簡單】
完成移植后,也許你會急于想知道當(dāng)前環(huán)境下自己的平臺能跑出怎樣的幀率吧?別著急,LVGL的cmsis-pack已經(jīng)為您最好了準(zhǔn)備。打開RTE配置窗口,勾選benchmark:
在main.c 中加入對lv_demo_benchmark.h 的引用:
在 LVGL 初始化代碼后,加入benchmark 無腦入口函數(shù):
int main(void)
{
lv_init();
lv_port_disp_init();
lv_demo_benchmark();
while(1) {
}
}
編譯,運(yùn)行,走起:
嗯…… Slow but common case……
【裝逼從未如此簡單】
完成移植后,也許你“又”會急于想知道當(dāng)前環(huán)境下自己的平臺能跑出怎樣的效果吧?(咦?為什么要說又?)別著急,LVGL的cmsis-pack已經(jīng)為您最好了準(zhǔn)備。打開RTE配置窗口,勾選Demo:Widgets:
在main.c中加入對lv_demo_widgets.h的引用:
在 LVGL 初始化代碼后,加入Demo Widgets的無腦入口函數(shù):
int main(void)
{
lv_init();
lv_port_disp_init();
lv_demo_benchmark();
lv_demo_widgets();
while(1) {
}
}
需要特別注意的是:要跑這個(gè)Demo,Stack(棧)和 Heap(堆)各自都不能小于 4K,切記,切記!
編譯,運(yùn)行,走起:
【說在后面的話】
能參與像 LVGL 這樣的項(xiàng)目,且在PR在三天內(nèi)就并入主線,是我始料未及的。不得不佩服項(xiàng)目維護(hù)者的心胸和效率。 雖然很多人因?yàn)锳PI兼容性的問題仍然在堅(jiān)守 LVGL 7.x,但我相信隨著cmsis-pack大幅降低了 MDK 用戶的部署門檻后,應(yīng)該會有越來越多的用戶在新項(xiàng)目中使用 LVGL 8——畢竟MCU每個(gè)項(xiàng)目基本都是“新建文件夾”——也不能說沒有歷史傳承,只能說基本沒有……
最后,對在MDK中用cmsis-pack來部署LVGL的過程感到好奇,但又想有個(gè)參考的小伙伴,可以關(guān)注下面這個(gè)開源項(xiàng)目: https://github.com/GorgonMeducer/lv_port_mps3_an547_cm55 如果他對你有所幫助的話,還請賞賜個(gè)Star呀。
審核編輯 黃昊宇
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19104瀏覽量
304829 -
MDK
+關(guān)注
關(guān)注
4文章
209瀏覽量
32063 -
LVGL
+關(guān)注
關(guān)注
1文章
83瀏覽量
2957
發(fā)布評論請先 登錄
相關(guān)推薦
評論