前言
LVGL是一個(gè)免費(fèi)開(kāi)源的嵌入式圖形庫(kù),它特別擅長(zhǎng)為資源受限的微控制器或小型處理器設(shè)備打造漂亮的用戶界面。由于它設(shè)計(jì)得非常輕巧高效,同時(shí)對(duì)內(nèi)存和處理器要求不高,因此能輕松適配各種屏幕尺寸和類型的嵌入式硬件平臺(tái)。它提供了異常豐富的現(xiàn)成組件,像按鈕、圖表、列表這些常用控件一應(yīng)俱全,并且支持高級(jí)特性比如流暢的動(dòng)畫(huà)、抗鋸齒字體顯示和矢量圖形的平滑縮放效果。
開(kāi)發(fā)者可以用C語(yǔ)言相對(duì)便捷地構(gòu)建出視覺(jué)效果現(xiàn)代、交互流暢的界面,加上其完善的文檔和活躍的中文社區(qū)支持,LVGL大大降低了在嵌入式系統(tǒng)上開(kāi)發(fā)專業(yè)級(jí)GUI的門(mén)檻和復(fù)雜度,是物聯(lián)網(wǎng)設(shè)備、工控面板、穿戴設(shè)備等嵌入式屏幕交互開(kāi)發(fā)的強(qiáng)力助手。
本期我們介紹在STM32中移植并使用LVGL進(jìn)行GUI開(kāi)發(fā),開(kāi)發(fā)板采用STM32N6570-DK,開(kāi)發(fā)平臺(tái)采用STM32CubeIDE1.18.1,LVGL版本為v8.2,參考資料為正點(diǎn)原子LGVL教程。
1
LGVL庫(kù)下載和精簡(jiǎn)
在LVGL的官網(wǎng)中可以跳轉(zhuǎn)到其GitHub倉(cāng)庫(kù)處,分支選擇為v8.2版本并下載。
LVGL源文件僅保留如圖五個(gè)文件即可,其中examples文件夾可以進(jìn)一步裁剪。
examples文件夾中僅保留porting文件夾內(nèi)容即可,因此精簡(jiǎn)完的LVGL庫(kù)只有如下文件:
2
工程搭建
除了lvgl庫(kù)以外,需要準(zhǔn)備好顯示屏驅(qū)動(dòng)程序和觸摸驅(qū)動(dòng)程序,并有一個(gè)可以用的工程模板。
該工程除了屏幕的驅(qū)動(dòng)之外,還需要開(kāi)啟一個(gè)定時(shí)器用來(lái)給lvgl底層提供時(shí)基,推薦定時(shí)1毫秒。
確認(rèn)頭文件和源文件地址設(shè)置正確并開(kāi)啟了編譯優(yōu)化。
打開(kāi)lv_conf_template.h文件,將宏定義使能修改為1,代表著使能這部分內(nèi)容。
并且將lv_conf_template.h重命名為lv_conf.h!!!
并且將lv_conf_template.h重命名為lv_conf.h!!!
并且將lv_conf_template.h重命名為lv_conf.h!!!
(重要的事情說(shuō)三遍)
接著打開(kāi)examples的porting文件夾中的lv_port_disp_template.c/.h還有l(wèi)v_port_indev_template.c/.h添加刷新和觸摸驅(qū)動(dòng)。
這四個(gè)文件(.c/.h),都要將宏定義使能修改為1。
lv_port_disp_template.c的初始化函數(shù)void lv_port_disp_init(void)提供了三種刷新方式,我們選擇方式1(局部刷新)并修改屏幕寬度和屏幕大小。
這里的800*10可以根據(jù)自己的RAM大小修改,例如我修改為800*480
/*Initialize your display and the required peripherals.*/
staticvoiddisp_init(void)
{
? ??/*You code here*/
??BSP_LCD_Init(0,?0);//屏幕初始化,換做自己原來(lái)的
}
/*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.*/
staticvoiddisp_flush(lv_disp_drv_t * disp_drv, constlv_area_t * area, lv_color_t * color_p)
{
? ??/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
? ??BSP_LCD_FillRGBRect(0, area->x1, area->y1, (uint16_t *)color_p, area->x2-area->x1+1, area->y2-area->y1+1);
? ??/*IMPORTANT!!!
? ? ?*Inform the graphics library that you are ready with the flushing*/
? ??lv_disp_flush_ready(disp_drv);
}
并在disp_init函數(shù)和disp_flush函數(shù)中提供屏幕初始化和區(qū)域刷新驅(qū)動(dòng)。
lv_port_indev_template.c/.h提供了輸入設(shè)備邏輯,我們將沒(méi)有用到的設(shè)備內(nèi)容全部刪除/注釋掉,由于我們僅采用觸摸屏故僅保留touchpad內(nèi)容。
所以初始化中僅保留touchpad相關(guān)函數(shù)和語(yǔ)句,其他函數(shù)也僅保留如下四個(gè)函數(shù)。
TS_State_t TS_State;//觸摸屏全局變量
/*Initialize your touchpad*/
staticvoidtouchpad_init(void)
{
? ??/*Your code comes here*/
? TS_Init_t TS_Init;
? ??/* 初始化觸摸屏 */
? TS_Init.Width?=?800; ? ? ? ?// 屏幕寬度
? TS_Init.Height?=?480; ? ? ??// 屏幕高度
? ? TS_Init.Orientation?=?TS_SWAP_NONE;?// 方向,不旋轉(zhuǎn)
? TS_Init.Accuracy?=?5; ? ? ??// 精度,5個(gè)像素的抖動(dòng)會(huì)被過(guò)濾
??BSP_TS_Init(0, &TS_Init);
}
/*Will be called by the library to read the touchpad*/
//這個(gè)函數(shù)沒(méi)做修改
staticvoidtouchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
? ? staticlv_coord_t last_x =?0;
? ? staticlv_coord_t last_y =?0;
? ??/*Save the pressed coordinates and the state*/
? ??if(touchpad_is_pressed()) {
? ? ? ??touchpad_get_xy(&last_x, &last_y);
? ? ? ? data->state =?LV_INDEV_STATE_PR;
? ? }?else?{
? ? ? ? data->state =?LV_INDEV_STATE_REL;
? ? }
? ??/*Set the last pressed coordinates*/
? ? data->point.x?= last_x;
? ? data->point.y?= last_y;
}
/*Return true is the touchpad is pressed*/
staticbooltouchpad_is_pressed(void)
{
? ??/*Your code comes here*/
? ??//檢測(cè)到觸摸
??if?(BSP_TS_GetState(0, &TS_State) ==?BSP_ERROR_NONE) {
? ? ?if?(TS_State.TouchDetected) {
? ? ? ? ? returntrue;//檢測(cè)到返回真
? ? ?}
? }
? ? returnfalse;
}
/*Get the x and y coordinates if the touchpad is pressed*/
staticvoidtouchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
? ??/*Your code comes here*/
??// 獲取觸摸坐標(biāo)
? ? (*x) = TS_State.TouchX;
? ? (*y) = TS_State.TouchY;
}
保留touchpad內(nèi)容并完成“觸摸初始化”,“檢測(cè)觸摸生效”,“獲取觸摸位置”三個(gè)內(nèi)容。
??HAL_TIM_Base_Start_IT(&htim18);//開(kāi)啟定時(shí)器
??lv_init();//Lvgl初始化
??lv_port_disp_init();//屏幕初始化
??lv_port_indev_init();//輸入設(shè)備輸出化
//上述三個(gè)步驟順序不能調(diào)換
while?(1)
? {
? ??lv_timer_handler();
? ??HAL_Delay(5);
? }
在main主函數(shù)中加入lvgl初始化和設(shè)備初始化,并在While循環(huán)中添加延時(shí)并觸發(fā)定時(shí)器時(shí)基句柄(這里的時(shí)間不用太精確)。
voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
? ?static?Timnumber?=?0;
? ?if(htim->Instance?==?TIM18)
? ?{
? ? ?lv_tick_inc(1);//lvgl時(shí)基觸發(fā) 1
? ? ?//如果是5ms延時(shí)就填入5
? ? ?Timnumber++;
? ? ?if(Timnumber?==?500)
? ? ?{
? ? ? ?//每500秒LED反轉(zhuǎn)
? ? ? ?HAL_GPIO_TogglePin(GPIOO,?GPIO_PIN_1);
? ? ? ?Timnumber?=?0;
? ? ?}
? ?}
}
定時(shí)器回調(diào)函數(shù)中添加lvgl時(shí)基函數(shù)。(記得開(kāi)啟定時(shí)器TIM哦),至此lvgl的工程配置完畢。
3
lvgl測(cè)試
lvgl提供了好幾個(gè)測(cè)試Demo,我們將stressDemo進(jìn)行測(cè)試。
在lvgl.h中找到LV_USE_DEMO_STRESS宏定義并將其設(shè)置為1使能。
main中包含這個(gè)Demo的頭文件,并在lvgl初始化后調(diào)用demo例程。
??HAL_TIM_Base_Start_IT(&htim18);//開(kāi)啟定時(shí)器
??lv_init();//Lvgl初始化
??lv_port_disp_init();//屏幕初始化
??lv_port_indev_init();//輸入設(shè)備輸出化
??lv_demo_stress();//demo例程
??while(1)
? {
? ??lv_timer_handler();
? ??HAL_Delay(5);
??
效果如下: