目錄
一、LVGL?簡(jiǎn)述
二、復(fù)制一個(gè)STM32工程
三、下載 LVGL
四、裁剪 源文件
五、工程添加 LVGL 文件?
六、注冊(cè)?顯示
七、注冊(cè)?觸摸屏
八、LVGL 心跳、任務(wù)刷新
九、開(kāi)跑 LVGL?
十、控件的事件添加、響應(yīng)處理
十?一、幾個(gè)好玩小事情
十?二、顯示中文
一、LVGL?簡(jiǎn)述
- 豐富且強(qiáng)大的模塊化圖形組件:按鈕 、圖表 、列表、滑動(dòng)條、圖片等
- 高級(jí)的圖形引擎:動(dòng)畫(huà)、抗鋸齒、透明度、平滑滾動(dòng)、圖層混合等效果
- 支持多種輸入設(shè)備:觸摸屏、 鍵盤(pán)、編碼器、按鍵等
- 不依賴(lài)特定的硬件平臺(tái)
- 配置可裁剪,最低資源占用:64 kB Flash,16 kB RAM
- 基于UTF-8的多語(yǔ)種支持,例如中文、日文、韓文、阿拉伯文等
- 可以通過(guò)類(lèi)CSS的方式來(lái)設(shè)計(jì)、布局圖形界面(例如:Flexbox、Grid)
- 支持操作系統(tǒng)、外置內(nèi)存、以及硬件加速(已內(nèi)建支持STM32 DMA2D)
- 即便僅有單緩沖區(qū)(frame buffer)的情況下,也可保證渲染如絲般順滑
支持模擬器仿真,可以無(wú)硬件依托進(jìn)行開(kāi)發(fā)
二、復(fù)制一個(gè)STM32工程
準(zhǔn)備好一個(gè)STM32的工程,這個(gè)工程要求如下:
1、硬件的要求
- 芯片資源:Flash>128K, RAM>64K; (LVGL至少占用:?Flash>64K,?RAM>16K);?
- 與芯片型號(hào)無(wú)關(guān),F(xiàn)1、F4、H7等系列的芯片,滿足上述資源的都行;
- 不建議使用常用的STM32F103C8,資源太小,裁剪難度大,強(qiáng)行移植了也會(huì)很卡。
- 顯示屏:建議使用16位色深的彩屏, 1.44寸、2.8寸、4.3寸等等;
- 不建議使用常用的0.96寸OLED屏,指甲大小的單色屏,耗100K資源去撐它,沒(méi)搞頭。
2、軟件的環(huán)境
- 庫(kù)支持方式:標(biāo)準(zhǔn)庫(kù)、寄存器、手?jǐn)]HAL庫(kù)、CubeMX生成的HAL庫(kù)、LL庫(kù),都可以;
- 開(kāi)發(fā)環(huán)境:Keil、CubeIDE,都可以;?
3、STM32工程的要求
- 堆棧大?。篐eap、Stack,設(shè)置為:0x1000; ? ( 鏈接:如何設(shè)置堆棧大小 )??
- 準(zhǔn)備:畫(huà)點(diǎn)函數(shù),用于后面注冊(cè)LVGL的顯示功能;
- 準(zhǔn)備:觸摸檢測(cè)函數(shù) (返回:0-未按下、1-按下)、坐標(biāo)獲取函數(shù),用于注冊(cè)LVGL的觸屏功能;
上述,是LVGL最基礎(chǔ)的資源需要。
如果有一項(xiàng)你沒(méi)看懂,就先把它盤(pán)透,回頭再盤(pán)LVGL。
本篇,復(fù)制了開(kāi)發(fā)板的一個(gè)示例作移植的基礎(chǔ)工程:"顯示屏_2.8寸_觸摸檢測(cè)_XPT2046"。
4、復(fù)制源工程后,測(cè)試是否可用
- 編譯:以確保 0?Error;?
- 燒錄:以確保能正常運(yùn)行,觸摸和顯示都正常(忽視下圖中的文字顯示,非必要)。
- 對(duì)于這個(gè)工程,最低限度,你必須已懂得調(diào)用函數(shù),實(shí)現(xiàn)如下圖畫(huà)線效果。
三、下載 LVGL
盡管LVGL已發(fā)布了v9.0、v9.1等,但v8.3版,是目前最廣泛使用的版本。
v8.3版本,網(wǎng)上教程資源眾多、移植簡(jiǎn)單;
更重要的:多款主流可視化設(shè)計(jì)工具,都支持LVGL的v8.3版本!
因此,強(qiáng)烈推薦使用v8.3版本。
?官方下載鏈接:https://github.com/lvgl/lvgl
1、選擇版本
2、下載
3、下載后,解壓縮得到文件夾:lvgl-release-v8.3
四、源文件?裁剪
上一步得到的源文件夾: lvgl-release-v8.3。
里頭文件眾多:源代碼、幫助文檔、官方示例等等。
不用發(fā)暈,需要用到的,僅僅是:3個(gè)文件夾 + 2個(gè)h文件。
1、新建一個(gè)文件夾
因?yàn)長(zhǎng)VGL源代碼中的頭文件,使用了相對(duì)路徑,如在 "lvgl.h" 中:
為了令移植后的文件能直接使用這些相對(duì)路徑,我們復(fù)制文件時(shí),按下方目錄結(jié)構(gòu)來(lái)操作:
- 在你喜歡的硬盤(pán)位置,新建文件夾:LVGL?
- 在源文件夾中,把下圖選中的 3個(gè)文件夾、2個(gè)h文件,? 復(fù)制到新建的 LVGL文件夾中;
完成后,"LVGL" 文件夾,是這個(gè)樣子的:
提醒:
- 網(wǎng)上好些教程,在keil工程目錄下新建 Middlewares 文件夾,在里面再新建LVGL文件夾。
- 如果你使用的是標(biāo)準(zhǔn)庫(kù)的工程,或者是自己手?jǐn)]建立的HAL庫(kù)工程,都可以那樣操作。
- 但是,如果使用CubeMX、CubeIDE生成的工程,就不要使用 “Middlewares”?作文件夾名稱(chēng)。
- 因?yàn)?"Middlewares",剛好是CubeMX可能生成的文件夾,用來(lái)存放中間件,如:FreeRTOS、FatFS等支持文件。如果你沒(méi)有使能這些中間件,那么 ,CubeMX重新生成工程時(shí),"Middlewares"文件夾就會(huì)被認(rèn)為不需要了,被刪除掉。
?2、修改 lv_conf.h 文件名
在 "LVGL" 文件夾中,有 h文件:"lv_conf_template.h",是LVGL配置參數(shù)的重要文件。
- 原文件名:“l(fā)v_conf_template.h”,修改為: "lv_conf.h";
完成后, "LVGL" 文件夾,是這個(gè)樣子的:
3、刪除不需要的文件夾
打開(kāi)文件夾:"LVGL / examples":
- 只保留?porting?文件夾,其它的文件夾和文件,都刪除掉。
完成后,文件夾"LVGL / examples",是這個(gè)樣子的:
4、修改?porting?里面的文件名稱(chēng)
打開(kāi)剛才的? "porting"? 文件夾:
- 6個(gè)文件的名稱(chēng),都刪除 "_template"?字樣
完成后,"porting"? 文件夾,是這個(gè)樣子的:
好了,現(xiàn)在 "LVGL" 文件夾,已經(jīng)是我們需要的效果。
這個(gè) "LVGL" 文件夾,以后可以復(fù)制給各類(lèi)的工程使用,不限于STM32的工程,通用。
五、STM32工程添加 LVGL 文件?
現(xiàn)在,我們開(kāi)始給STM32工程添加LVGL源文件。
1、復(fù)制 LVGL 文件夾,粘貼到STM32工程目錄下。
每個(gè)人的工程文件夾,幾乎都不一樣,沒(méi)關(guān)系的。
- 把上一步做好的 LVGL 文件夾,復(fù)制到工程目錄下
完成后,是這個(gè)樣子的:
2、打開(kāi)Keil,在工程里,添加4個(gè)文件夾(Groups);
文件夾名稱(chēng) (Groups) | 用于存放什么文件 |
LVGL_myGui | 用戶(hù)自己的界面代碼文件、官方demo等 |
LVGL_conf | LVGL 的兩個(gè)h文件 |
LVGL_porting | LVGL 的接口文件,?如顯示、觸摸屏、鍵盤(pán)等 |
LVGL_src | LVGL 的所有底層c文件 |
操作過(guò)程、完成后,是這個(gè)樣子的:
提示:
- 操作完成后,先點(diǎn)擊OK,保存操作。
- 網(wǎng)上好些教程會(huì)新建近10個(gè)Group,?分開(kāi)存放各個(gè)子功能文件。4個(gè)文件夾夠了,簡(jiǎn)單直觀。
- 這里用下劃線作名稱(chēng)分界線。嘗試過(guò)使用“ / ”,?感覺(jué)沒(méi)下劃線直觀。你可以用自己喜歡風(fēng)格。
添加完后,再編譯一次,確認(rèn):0?Error,?不要覺(jué)得事多。?
3、為每一個(gè)文件夾組(Groups),添加需要的文件
特別地:這一步,是整個(gè)移植里,最容易出錯(cuò)的步驟!務(wù)必在開(kāi)始操作前,反復(fù)看兩次本步圖解。
很多人在后面的編譯中,出現(xiàn)error,提示缺少文件,基本是在這一步把某個(gè)文件添加漏了。
提醒:一點(diǎn)也不難,但務(wù)必細(xì)心地操作。
操作過(guò)程,步驟如下:
重要:每個(gè)文件夾(Group),需要添加的文件,如下表:
文件夾 (Group) | 添加文件 |
---|---|
LVGL_myGUI | 不用添加。 |
LVGL_conf | 共2個(gè)文件:"LVGL"下的:?lv_conf.h、lvgl.h(要選擇文件類(lèi)型才能看到h文件) |
LVGL_porting | 共4個(gè)文件:"LVGL/?examples / porting" 下的:lv_port_disp.c 、lv_port_disp.h、 lv_port_indev.c、lv_port_indev.h;(要選擇文件類(lèi)型才能看到 h 文件) |
LVGL_src | 近200+的c文件:"LVGL /?src" 下的所有 c 文件;?重點(diǎn):包括src里所有子、子子文件夾的 c 文件.?不用添加h和mk文件. |
在操作 LVGL_conf? 和 LVGL_porting 添加h文件時(shí),需要在選擇窗口中,把文件類(lèi)型設(shè)置 *.*:
細(xì)細(xì)解釋一下 LVGL_src?的添加,:
- src文件夾下,會(huì)有多重的子文件夾,必須慢慢地、把每一個(gè)子文件夾的C文件全部添加進(jìn)來(lái);
- 只須添加 c?文件,不用添加其它類(lèi)型的文件,如:h、mk等 (技巧:文件類(lèi)型配置為?*.c );
- 添加完畢后,必須點(diǎn)擊"OK"保存,? 不然,你會(huì)后悔。
完成后,Keil的工程文件管理器,是這個(gè)樣子的:
4、添加頭文件路徑
打勾C99,并,添加3個(gè)頭文件路徑:
- 添加:LVGL?文件夾的路徑
- 添加:LVGL\src?文件夾的路徑
- 添加:LVGL\examples\porting?文件夾的路徑?
操作過(guò)程、完成后,是這個(gè)樣子的:
5、編譯驗(yàn)證
來(lái)到這一步,需要用到的文件,已經(jīng)添加完畢。
我們?cè)谶@里必須先編譯一次,以驗(yàn)證文件是否都添加完整。
正常情況下,編譯后: 0 Error。會(huì)有一大堆?Warning,不用管,不影響的。
如果,編譯后,有? Error?報(bào)錯(cuò):
- 檢查是否打勾: C99
- 先檢查頭文件路徑 ,是否添加完成;
- 如果頭文件路徑添加正確,那么,多是添加文件那一步有遺漏:刪除4個(gè)Group,重新添加。
六、注冊(cè)?顯示
1、啟用 lv_conf.h
雙擊打開(kāi) lv_conf.h,對(duì)以下內(nèi)容進(jìn)行修改,以啟用此文件。
- 第15行,原:#if 0,修改為:#if 1?
完成后,是這個(gè)樣子的:
2、啟用 lv_port_disp.h
雙擊打開(kāi)?lv_port_disp.h,修改以下內(nèi)容,以啟用此文件:
- 第7行,原:#if 0,?修改為:#if 1?
- 第22行,原:“l(fā)vgl/lvgl.h",?修改為:”lvgl.h"
完成后,是這個(gè)樣子的:
3、啟用? lv_port_disp.c?
雙擊打開(kāi)?lv_port_disp.c,修改以下內(nèi)容,以啟用此文件:
- 第7行,原:#if 0,?修改為:#if 1?
- 第12行,原"lv_port_disp_template.h",?修改為:"lv_port_disp.h"
完成后,是這個(gè)樣子的:
4、添加 LCD?驅(qū)動(dòng)的頭文件
在 lv_port_disp.c中:
- 第14行,插入你的LCD驅(qū)動(dòng)文件,如:#include "bsp_LCD_ILI341.h",寫(xiě)上你的h文件。
- 第20行、第25行,是顯示屏的寬、高度。查看你的顯示屏參數(shù),填寫(xiě)實(shí)際像素即可。
插入LCD的頭文件,目的是為了讓這個(gè)c文件,能調(diào)用LCD的:?畫(huà)點(diǎn)函數(shù);
注意一個(gè):LVGL默認(rèn)使用橫屏的方式,這一點(diǎn)要注意,別寫(xiě)反了。
完成后,是這個(gè)樣子的
5、選擇創(chuàng)建緩存的方式,3選1
還是在 lv_port_disp.c 中,向下滾動(dòng),
(會(huì)出現(xiàn)很多錯(cuò)誤提示,不用管。也可以先編譯一次,讓剛才啟用的h文件生效,錯(cuò)誤就會(huì)消失)
第86行到101行,LVGL?提供了創(chuàng)建顯示緩沖區(qū)的3種方式,這里,必須3選1。
絕大多數(shù)情況下,使用第1種方法,即:只創(chuàng)建1個(gè)緩沖區(qū);
- 注釋掉第90~101行,即:不使用第2和第3種方法;
完成后,是這個(gè)樣子的:
6、關(guān)聯(lián)?畫(huà)點(diǎn)函數(shù)
還是在 lv_port_disp.c 中,向下滾動(dòng),找到disp_flush( )函數(shù):
- 第173行,替換你的?LCD 的畫(huà)點(diǎn)函數(shù);? 參數(shù):x坐標(biāo)、y坐標(biāo)、16位顏色值。
- 小編用的畫(huà)點(diǎn)函數(shù):LCD_DrawPoint( x, y, color_p->full) ;
你的畫(huà)點(diǎn)函數(shù),可能和小篇所用的不一樣,照樣畫(huà)瓢即可。
完成后,是這個(gè)樣子的:
這里給LVGL一個(gè)畫(huà)點(diǎn)函數(shù)后, LVGL就能完成需要的顯示操作了。
進(jìn)階技巧:提高刷屏效率
一般地,畫(huà)點(diǎn)函數(shù)的底層操作:發(fā)送X坐標(biāo)指令、X值、Y坐標(biāo)指令、Y值、顏色值。
假如要刷320x240的整屏,至少傳輸14萬(wàn)次指令、14萬(wàn)次坐標(biāo)值,7萬(wàn)次顏色值。
相當(dāng)?shù)睾臅r(shí)。
要是你的LCD驅(qū)動(dòng)文件中,有區(qū)域填充顏色的函數(shù),就能大量地減少指令、坐標(biāo)值的發(fā)送次數(shù)。
下面是使用 魔女開(kāi)發(fā)板 LCD驅(qū)動(dòng)文件中所提供的?區(qū)域填充?函數(shù),可以效仿參考。
- LCD_DispFlush(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);
如果沒(méi)有區(qū)域填充函數(shù),不用強(qiáng)求,直接使用畫(huà)點(diǎn)函數(shù)吧,先完成,再完善。
至此,顯示部分的修改、注冊(cè),已完成。
點(diǎn)擊編譯:0?Erros。
提示:
細(xì)心的朋友,如果參考過(guò)其它LVGL教程,可能會(huì)有疑問(wèn)。
為什么操作這么少?是不是漏了?disp_init()的那部分?
是的,我們沒(méi)有為 disp_init()函數(shù)填入LCD的初始化函數(shù)。
沒(méi)必要這樣做。
在第九部分,將會(huì)在main.c的 main( ) 里直接調(diào)用LCD初始化函數(shù)。
這樣更符合開(kāi)發(fā)習(xí)慣,也使思路更清晰。
七、注冊(cè)?觸摸屏
1、啟用 "lv_port_indev.h"
打開(kāi)"lv_port_indev.h",?修改以下內(nèi)容,以啟動(dòng)此文件:
- 第8行,原:#if 0,? 修改成:#if 1
- 第20行,原:"lvgl / lvgl.h", 修改成:"lvgl.h"
完成后,是這個(gè)樣子的:
2、啟動(dòng) "lv_port_indev.c"
打開(kāi)"lv_port_indev.c",?修改以下內(nèi)容,以啟動(dòng)此文件:
- 第 7行,原:#if 0,?修改為:#if 1
- 第12行,原:“l(fā)v_port_indev_template.h",?修改為:"lv_port_indev.h"
- 第13行,原:"../../lvgl.h",修改為:"lvgl.h"
完成后,是這個(gè)樣子的:
3、添加? 觸屏 的驅(qū)動(dòng)頭文件
還是在 "lv_port_indev.c"?中:
- 第14行,插入:#include "觸摸屏的頭文件",小編這邊是:#include "bsp_XPT2046.h"
目的是為了讓這個(gè)c文件,能調(diào)用觸屏的:觸摸狀態(tài)檢測(cè)函數(shù)、坐標(biāo)獲取函數(shù);
完成后,是這樣子的:
4、注釋掉不需要的輸入任務(wù)注冊(cè)
還是在 "lv_port_indev.c"?中,
向下滾動(dòng)至大約70行,找到輸入注冊(cè)函數(shù):lv_port_indev_init( ),
函數(shù)內(nèi)有5種輸入方式的任務(wù)注冊(cè):觸屏、鼠標(biāo)、鍵盤(pán)、編碼器、物理按鍵;
- 保留觸摸屏輸入的任務(wù)注冊(cè);
- 其它4種輸入任務(wù)的注冊(cè),注釋掉,;
完成后,是這個(gè)樣子的:
5、添加 觸摸檢測(cè)函數(shù)
還是在 "lv_port_indev.c"?中,
向下滾動(dòng)到大約209行,找到觸摸檢測(cè)函數(shù):touchpad_is_pressed(),
這個(gè)是:觸屏狀態(tài)檢測(cè)函數(shù),函數(shù)返回:0-未按下、1-按下;
- 第213行,原?return false,?注釋掉;
- 第212行,原來(lái)是空行,插入:return? XPT2046_IsPressed();
這個(gè)是魔女開(kāi)發(fā)板所提供的觸屏檢測(cè)函數(shù),返回值已符合:0-未按下、1-按下;
你可以替換成你的方式,如原子哥的變量值方式。各施各法,只要符合函數(shù)要求,都行;
完成后,是這個(gè)樣子的:
6、添加 坐標(biāo)獲取函數(shù)
還是在 "lv_port_indev.c"?中,
在剛才觸摸檢測(cè)函數(shù)的下方,找到坐標(biāo)獲取函數(shù):touchpad_get_xy();
本函數(shù)的作為:使LVGL能夠獲取到觸摸按下時(shí)的x、y坐標(biāo);
- 第221行,?修改為:(*x) = XPT2046_GetX();
- 第222行,?修改為:(*y) = XPT2046_GetY();
同上,可以各施各法,用各種方法賦值。
完成后,是這個(gè)樣子的:
7、額外的測(cè)試預(yù)埋
(這一步,是非必須的,可以選擇跳過(guò)。)
在后續(xù)的按鈕測(cè)試中,有可能發(fā)生觸摸坐標(biāo)與顯示坐標(biāo)不對(duì)應(yīng)的情況。
我們?cè)谶@里先預(yù)埋一個(gè)操作,當(dāng)后面發(fā)生問(wèn)題,不用傻傻的盲猜原因。
就在剛才的那個(gè) touchpad_get_xy( )?函數(shù)中,增加加一行畫(huà)點(diǎn)操作:
- 第222行下方,插入新行,畫(huà)點(diǎn)操作:LCD_DrawPoint( *x, *y, BLACK);
完成后,是這個(gè)樣子的:
這樣操作的目的,是令LVGL在獲取坐標(biāo)時(shí),也在這個(gè)坐標(biāo)上畫(huà)一個(gè)黑點(diǎn)。
如果發(fā)生觸摸坐標(biāo)與顯示坐標(biāo)不對(duì)應(yīng),就能直觀地發(fā)現(xiàn)問(wèn)題。
至此,觸摸屏的注冊(cè),已經(jīng)完成。
點(diǎn)擊編譯:0?Error。
提示:
參考過(guò)其它教程的細(xì)心的朋友,這里又會(huì)發(fā)現(xiàn),相比其它教程,這里又少了步驟!
在其它教程中,會(huì)把其它幾種輸入方式的相關(guān)獲取函數(shù),都注釋掉。
即:大約第230行~408行,鼠標(biāo)輸入、鍵盤(pán)輸入、編碼器輸入....,統(tǒng)統(tǒng)注釋掉。
不需要這樣操作!
你沒(méi)有為那些輸入方式進(jìn)行任務(wù)注冊(cè),也不調(diào)用它們,它們就不起任何作用。
而且,編譯器聰明著,編譯時(shí)將自動(dòng)忽略死代碼(即使是Level 0,死代碼也不會(huì)產(chǎn)生影響)。
八、添加 LVGL?的文件引用
之前的幾個(gè)部分,已修改完成了LVGL顯示、觸摸支持。
現(xiàn)在,正式在工程中“應(yīng)用” LVGL。
1、給工程,添加 LVGL 的頭文件
打開(kāi) main.c,在頂部, #include 三個(gè)頭文件:
- #include "lvgl.h"? ? ? ? ? ? ? ? ? ? ? ?// 它為整個(gè)LVGL提供了更完整的頭文件引用
- #include "lv_port_disp.h"? ? ? ? ?// LVGL的顯示支持
- #include "lv_port_indev.h" ? ? ? // LVGL的觸屏支持
完成后,是這個(gè)樣子的:
2、初始化LCD、觸摸屏
在main函數(shù)內(nèi)、?while?循環(huán)之前,調(diào)用LCD初始化函數(shù)、觸摸屏初始化函數(shù)。
刪除原示例中多余的顯示測(cè)試代碼、觸摸測(cè)試代碼。
下圖,是小篇所用開(kāi)發(fā)板的LCD驅(qū)動(dòng)函數(shù):
- LCD_Init();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //?初始化 LCD
- LCD_SetDir(1);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//?設(shè)置LCD的顯示方向:橫屏
- XPT2046_Init(xLCD.width, xLCD.height,? xLCD.dir);? ?//?初始化觸摸屏
還記得前幾步時(shí),我們沒(méi)有像其它教程那樣,給disp_init()?填入LCD的初始化函數(shù)。
現(xiàn)在,隨著其它的設(shè)備,把初始化放在一起,更符合習(xí)慣、更直觀。
完成后,是這個(gè)樣子的:
提示:
在上圖的第187行:W25Q128_Init();
它是外部Flash設(shè)備W25Q128的的初始化函數(shù)。
開(kāi)發(fā)板的觸摸屏校準(zhǔn)數(shù)據(jù),存儲(chǔ)在它里面。每次上電,要從它里面讀取之前的校準(zhǔn)數(shù)據(jù)。
如果你用的不是魔女開(kāi)發(fā)板,或者,有其它的儲(chǔ)存渠道,不用對(duì)它初始化。
3、初始化LVGL、顯示、觸屏
在硬件的初始化代碼之后,進(jìn)行LVGL的初始化:
- lv_init(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // LVGL 初始化
- lv_port_disp_init();? ? ? ? ? ? ? ? ? ? // 注冊(cè)LVGL的顯示任務(wù)
- lv_port_indev_init(); ? ? ? ? ? ? ? ? ?//?注冊(cè)LVGL的觸屏檢測(cè)任務(wù)
完成后,是這個(gè)樣子的:
4、顯示按鈕控件、文本控件
在LVGL的初始化之后,添加LVGL控件?,以測(cè)試LVGL的顯示:
- 添加一個(gè)按鈕
- 為按鈕添加文本
- 添加一個(gè)獨(dú)立的標(biāo)簽文本
具體代碼如下:
// 按鈕
lv_obj_t *myBtn = lv_btn_create(lv_scr_act()); // 創(chuàng)建按鈕; 父對(duì)象:當(dāng)前活動(dòng)屏幕
lv_obj_set_pos(myBtn, 10, 10); // 設(shè)置坐標(biāo)
lv_obj_set_size(myBtn, 120, 50); // 設(shè)置大小
// 按鈕上的文本
lv_obj_t *label_btn = lv_label_create(myBtn); // 創(chuàng)建文本標(biāo)簽,父對(duì)象:上面的btn按鈕
lv_obj_align(label_btn, LV_ALIGN_CENTER, 0, 0); // 對(duì)齊于:父對(duì)象
lv_label_set_text(label_btn, "Test"); // 設(shè)置標(biāo)簽的文本
// 獨(dú)立的標(biāo)簽
lv_obj_t *myLabel = lv_label_create(lv_scr_act()); // 創(chuàng)建文本標(biāo)簽; 父對(duì)象:當(dāng)前活動(dòng)屏幕
lv_label_set_text(myLabel, "Hello world!"); // 設(shè)置標(biāo)簽的文本
lv_obj_align(myLabel, LV_ALIGN_CENTER, 0, 0); // 對(duì)齊于:父對(duì)象
lv_obj_align_to(myBtn, myLabel, LV_ALIGN_OUT_TOP_MID, 0, -20); // 對(duì)齊于:某對(duì)象
完成后,是這個(gè)樣子的:
好了,已經(jīng)編寫(xiě)好讓LVGL顯示控件的代碼,LVGL馬上就要綻放了,還只差一步!
不急,先編譯一下!
0 Error, 35 Warning。
沒(méi)有 Error,可以哦!那35個(gè)警告,不用管它。
額外的裁剪探討:程序Flash和RAM的資源占用
按上面的編譯信息,我們這個(gè)程序:Flash占用190K, RAM占用80K
- FLASH 占用 = Code + RO-data + RW-data? = 163172 + 31808 + 592 = 190K
- RAM 占用? ? =? RW-data + ZI-data = 592 + 80504 = 80K
常用的幾款STM32芯片的Flash和RAM資源大小:
芯片型號(hào) Flash Ram STM32F103RC 256?K 48 K STM32F103VE 512 K ?64 K STM32F407VE 512 K 192 K STM32H750VB 128 K 1056 K 如果你用的是STM32F407VE,F(xiàn)lash和RAM都是妥妥的足夠。
而 F103RC、F103VE,RAM就遠(yuǎn)遠(yuǎn)不夠。H750VB,?Flash也是遠(yuǎn)遠(yuǎn)的不夠。
怎么辦?
第一:程序RAM > 硬件RAM,修改LVGL的內(nèi)存池大小
- lv_conf.h中,第52行,LV_MEM_SIZE,LVGL管理的內(nèi)存池大小,48U, 改為12U
修改后,重新編譯,一般,程序RAM占用會(huì)降至40K內(nèi).
如果修改后,還是 >硬件RAM,再來(lái):
- lv_port_disp.c中,第87、88行,顯存大小(刷屏用),原10行,修改為2行到5行左右;
修改后,重新編譯,一般,程序的RAM占用,會(huì)再減小幾K;
如果,還是 >硬件RAM,細(xì)心檢查一下程序中,是不是定義了全局有效的大數(shù)組。
第二:程序Flash占用?>?硬件Flash
常用芯片中,就F103C8、H750VB這兩款,硬件Flash是比較小的。
不過(guò)呢,它倆通常都有“隱藏”的Flash。
即在芯片生產(chǎn)中,F(xiàn)lash不止這么小,但各種原因,參數(shù)上定為128K了。
注意,這種情況,只是“通常”,而非“肯定”,具體情況,可以上百度八卦一下網(wǎng)友的驗(yàn)證。
直接燒錄試試,最有效!
九、LVGL 心跳、任務(wù)刷新
根據(jù)官方移植文檔的要求,我們還要處理兩個(gè)關(guān)于時(shí)間的問(wèn)題:
-
間隔精準(zhǔn)地,調(diào)用時(shí)基函數(shù):lv_tick_inc(),俗稱(chēng)心跳,讓LVGL精準(zhǔn)地知道時(shí)間的流逝;
-
間隔5ms左右,調(diào)用周期性任務(wù)函數(shù): lv_timer_handler() ,它的作用是檢查所有注冊(cè)任務(wù)的時(shí)間戳,執(zhí)行那些已經(jīng)到期的任務(wù),如:屏顯更新、動(dòng)畫(huà)更新、觸控、定時(shí)器事件等;
1、給LVGL一個(gè)心跳時(shí)基
LVGL心跳函數(shù)(時(shí)基函數(shù)):lv_tick_inc(),每隔1ms調(diào)用一次;
這個(gè)函數(shù)對(duì)于圖形界面的流暢運(yùn)行比較重要,它令?LVGL 知道執(zhí)行任務(wù)時(shí)流逝的時(shí)間。
如果 lv_tick_inc() 調(diào)用間隔不準(zhǔn)確,可能會(huì)導(dǎo)致顯示卡頓、任務(wù)處理不及時(shí)。
特別地,不建議使用滴答時(shí)鐘SysTick產(chǎn)生這個(gè)時(shí)基,因?yàn)樗3P枰挥糜赗TOS等。
建議使用TIM產(chǎn)生1ms中斷,設(shè)置它的中斷為高優(yōu)先級(jí),通過(guò)中斷函數(shù)調(diào)用LVGL心跳時(shí)基。
可以使用各個(gè)TIM、各種方法,產(chǎn)生1ms中斷,如寄存器操作、標(biāo)準(zhǔn)庫(kù)、手?jǐn)]HAL等等。
本篇,通過(guò)CubeMX配置TIM6,產(chǎn)生1ms中斷:
打勾TIM6的中斷,并設(shè)置中斷搶占級(jí)為:0,(默認(rèn)也是0);
讓CubeMX重新生成,令配置更新到工程代碼后,在main.c中調(diào)用HAL函數(shù):?jiǎn)?dòng)TIM6,并使能它的周期更新中斷。
- HAL_TIM_Base_Start_IT(&htim6);
?完成后,是這個(gè)樣子的:
然后,編寫(xiě)周期更新中斷的回調(diào)函數(shù)中,在里面調(diào)用lv_tick_inc( ),給LVGL提供心跳;
本篇,在main.c的尾部,編寫(xiě)這個(gè)函數(shù);
完成后,是這個(gè)樣子的:
回調(diào)函數(shù)解釋?zhuān)?/p>
這是一個(gè)TIM的周期更新中斷回調(diào)函數(shù),它是定義在***_hal_tim.c中的一個(gè)弱定義函數(shù)。
CubeMX、CubeID生成的工程,TIM發(fā)生周期更新中斷時(shí),都會(huì)統(tǒng)一調(diào)用它。
我們需要在期待的位置,重寫(xiě)這個(gè)函數(shù)。本篇寫(xiě)在了main.c的尾部。
在回調(diào)函數(shù)中,我們調(diào)用:lv_tick_inc(1),參數(shù)為1,即讓LVGL知道,1ms已經(jīng)過(guò)去了。
如果你設(shè)置TIM產(chǎn)生的是2ms的中斷,也可以:lv_tick_inc(2),效果是一樣的。
另外 :
在這個(gè)中斷回調(diào)函數(shù)中,我們額外地添加了LED每0.5ms閃爍的代碼。
目的是為了調(diào)試時(shí),可以肉眼判斷定時(shí)器TIM是否按預(yù)期正常工作。
只有TIM6按預(yù)期正常工作了,才能給?LVGL?一個(gè)準(zhǔn)確的心跳時(shí)基。
2、每隔5ms左右,調(diào)用任務(wù)刷新函數(shù): lv_timer_handler()
這個(gè)函數(shù)的作用:讓LVGL檢查所有已注冊(cè)任務(wù)的時(shí)間戳,執(zhí)行那些已經(jīng)到期的任務(wù),如刷屏、檢測(cè)觸摸等;
官方描述:大約5ms左右、在while循環(huán)中調(diào)用;
特別地:不要使用TIM產(chǎn)生5ms中斷去調(diào)用它,因?yàn)樗膱?zhí)行時(shí)間有點(diǎn)長(zhǎng),不適合霸占中斷資源。
- 在msin.c的while中,每隔5ms調(diào)用:lv_timer_handler()
完成后,是這樣子的:
至此,時(shí)間需求也處理完畢。
LVGL的移植,已全部完成?。
點(diǎn)擊編譯:0?Erros !
好了,現(xiàn)在開(kāi)始燒錄吧!
一百多K的程序,燒錄時(shí)間有點(diǎn)長(zhǎng),大約耗時(shí)十來(lái)秒。
運(yùn)行效果如下:
顯示正常,顯示部分已移植成功!
觸摸正常,按下時(shí)按鈕的狀態(tài)生產(chǎn)了變化,觸摸部分也移植成功!
恭喜你,運(yùn)氣太TM的好了。
但是,一次就成功的機(jī)率太低太低了。
更大可能出現(xiàn)的情況是:顯示正常,觸摸沒(méi)反應(yīng)!
5、觸摸沒(méi)反應(yīng)的排查
正常情況下:如上圖gif 所顯示的,點(diǎn)擊按鈕時(shí),按下、釋放,按鈕的狀態(tài)是不一樣的。
如果按鈕在按下時(shí)沒(méi)有反應(yīng)、不會(huì)產(chǎn)生狀態(tài)變化 ,主要排查3項(xiàng):
- 觸摸檢測(cè):lv_port_indev.c 第209行:touchpad_is_pressed(),返回值是否相符;
- 坐標(biāo)獲?。簂v_port_indev.c 第217行:touchpad_get_xy();
- 坐標(biāo)不符:觸摸屏坐標(biāo)與顯示屏坐標(biāo)不符,需要重新校準(zhǔn);
如果已按上面(七-7)的步驟,在touchpad_get_xy()函數(shù)預(yù)埋了畫(huà)點(diǎn)函數(shù),如下圖:
那么,可以這樣測(cè)試:在顯示屏空白的地方,用指甲,慢慢地,劃幾道線。
- 如果劃不出黑線(斷斷續(xù)續(xù)的黑線),排查硬件初始化,和上面的1、2兩項(xiàng);
- 如果劃出了黑線,但是坐標(biāo)不對(duì),那就是觸摸屏需要重新校準(zhǔn)了,即上面的3;
關(guān)于第一種錯(cuò)誤,檢查:觸摸檢測(cè)函數(shù)返回值,是否正確。
- 用printf大法!把觸摸返回值、坐標(biāo)獲取值,printf出來(lái),在串口助手上觀察;
- 必須確認(rèn):觸摸按下時(shí),有返回值,而且返回值正確(0-未按下、1-按下),
- 如果返回值對(duì)了,再確認(rèn)坐標(biāo)獲取是否正常,查對(duì)printf出來(lái)的數(shù)據(jù)。
- 最后,就是從本篇開(kāi)頭,一步步對(duì)歸照,是哪一步出現(xiàn)漏做了。
關(guān)于第二種,重新校準(zhǔn)
- 不同的開(kāi)發(fā)板,問(wèn)一問(wèn)屏的商家,如何重新校準(zhǔn)觸摸屏;
- 本篇使用的“魔女開(kāi)發(fā)板”在這個(gè)工程中,使用串口助手發(fā)送:XPT2046,? 即可進(jìn)入重新校準(zhǔn)。
十、控件的事件添加、響應(yīng)處理
當(dāng)上述問(wèn)題都解決了,按鈕能正常觸控后,再操作這一步部分。
LVGL的學(xué)習(xí),可以大概地為分兩部分:界面繪制、事件處理。
1、界面的繪制
我們這里不啰嗦,上面的示范代碼,你能看個(gè)明白即可,無(wú)需深挖。
因?yàn)長(zhǎng)VGL的界面繪制,更常規(guī)的操作:使用可視化工具進(jìn)行設(shè)計(jì),再把界面工程移植回STM32。
2、事件處理
可視化工具,能幫我們處理好:控件生成、布局、屏幕切換等;
但是,不能處理STM32上的事,如按下按鈕,發(fā)送某CAN通信等;
這些,都是需要回到Keil或者CubeIDE里,自行增加編寫(xiě)的。
所以,特意增加一章,示范:事件的添加、編寫(xiě)響應(yīng)處理函數(shù)。
明白了響應(yīng)和處理的操作,后面使用可視化工具時(shí),能有更好的理解。
下面,以按鈕的點(diǎn)擊為例,示范:事件添加、響應(yīng)處理。
回到main函數(shù),在添加按鈕的那三行代碼下方,增加一行,為控件添加事件:
- lv_obj_add_event_cb(myBtn, myBtn_event, LV_EVENT_CLICKED, NULL);? ? ?
這行有點(diǎn)復(fù)雜,對(duì)參數(shù)稍作解釋?zhuān)?/p>
myBtn:控件的名稱(chēng)(不限于按鈕);
myBtn_event:事件響應(yīng)時(shí),LVGL調(diào)用的處理函數(shù) (等一會(huì)兒要手動(dòng)編寫(xiě)這個(gè)函數(shù));
LV_EVENT_CLICKED:點(diǎn)擊事件; 不同的控件,有不同的事件類(lèi)型;
NULL:傳遞給回調(diào)函數(shù)的可選用戶(hù)數(shù)據(jù),這里暫時(shí)不用;
完成后,是這個(gè)樣子的:? ? ??
然后,開(kāi)始編寫(xiě)剛才說(shuō)的那個(gè)事件回調(diào)函數(shù)。
在main函數(shù)的上方,注釋BEGIN 0?與 END 0之間,編寫(xiě)回調(diào)函數(shù):myBtn_event();
// 按鈕的事件回調(diào)函數(shù)
static void myBtn_event(lv_event_t *event)
{
lv_obj_t *btn = lv_event_get_target(event); // 獲得調(diào)用這個(gè)回調(diào)函數(shù)的對(duì)象
if (event->code == LV_EVENT_CLICKED)
{
static uint8_t cnt = 0;
cnt++;
lv_obj_t *label = lv_obj_get_child(btn, NULL); // 獲取第1個(gè)子對(duì)象(我們?cè)谠O(shè)計(jì)時(shí),已安排了它的第1個(gè)子對(duì)象是一個(gè)label對(duì)象)
lv_label_set_text_fmt(label, "Button: %d", cnt); // 設(shè)置標(biāo)簽的文本,寫(xiě)法類(lèi)似printf
}
}
對(duì)函數(shù)內(nèi)的代碼行,上面已有注釋?zhuān)容^簡(jiǎn)單,這里也就不啰嗦了。
完成后,是這個(gè)樣子的:
編譯,燒錄,運(yùn)行效果如下:
?
十?一、幾個(gè)相關(guān)的小事情
上面,LVGL的移植,已經(jīng)完成的。
備份好這個(gè)工程吧,它能作為后續(xù)可視化設(shè)計(jì)移植的基礎(chǔ)工程。
本部分,探討一些輕松一點(diǎn)的、可能對(duì)你有用的玩法。
1、在右下角,顯示CPU使用率、FPS幀數(shù)
- lv_conf.h中第282行,找到:LV_USE_PERF_MONITOR,原值:0,?修改為:1
2、在左下角,顯示LVGL的內(nèi)存使用率
- lv_conf.h中第289行,找到:LV_USE_MEM_MONITOR,原值:0,?修改為:1
3、黑色主題
- lv_conf.h中第579行:找到:LV_THEME_DEFAULT_DARK,?原值:0,?修改為:1
4、獲取控件的各種玩法
LVGL的控件玩法,最好的網(wǎng)站,沒(méi)有之一:
LVGL 中文開(kāi)發(fā)手冊(cè) -- https://lvgl.100ask.net/master/index.html
有啥控件,控件的實(shí)現(xiàn)效果如何,都可以在這里找到答案。
如,想實(shí)現(xiàn)一個(gè)下拉列表:
打開(kāi)上面網(wǎng)址,?找到?EXamples / Widgets / Dropdown(下拉列表)。
點(diǎn)擊后,右側(cè)會(huì)展示各種下拉列表的效果(有點(diǎn)延時(shí),要稍等),還可以通過(guò)鼠標(biāo)點(diǎn)擊它。
先找到我們想要的控件效果,
在效果的下方,點(diǎn)擊 “Show C code",可以展開(kāi)這個(gè)效果的代碼,復(fù)制到工程中,即可測(cè)試。
?5、查詢(xún) LVGL 某個(gè)函數(shù)、某個(gè)變量的解釋?
直接問(wèn)AI,沒(méi)有更快了,下面是Kimi的網(wǎng)址:
https://kimi.moonshot.cn
十?二、顯示中文
如何顯示中文呢?如果使用代碼的方法,不外乎兩個(gè)方法:
- LVGL源文件中,自帶了一個(gè)中文字庫(kù)。
- 網(wǎng)上有各種的字模轉(zhuǎn)換方法、程序。
這兩種方法,使用過(guò)程都很煩瑣,都不建議使用。
小篇的建議是:通過(guò)Gui Guider實(shí)現(xiàn)。
Gui Guider除了能對(duì)界面進(jìn)行可視化設(shè)計(jì),還能輕松實(shí)現(xiàn)中文顯示,根本無(wú)需理會(huì)轉(zhuǎn)換的問(wèn)題。
在我們移植的這個(gè)LVGL工程基礎(chǔ)上,就能直接使用Gui Guider的移植。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-860226.html
Gui Guider?教程鏈接:LVGL_可視化設(shè)計(jì) (Gui Guider)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-860226.html
到了這里,關(guān)于【快速入門(mén) LVGL】-- 1、STM32 工程移植 LVGL的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!