?
?作者:出出啊
鏈接:
https://club.rt-thread.org/ask/article/64a11e0669eabd42.html
?
前言
嚴(yán)格講,這一篇不涉及 rt-thread 驅(qū)動,但是它是 LVGL 和 rt-thread 的接口。LVGL 在 rt-thread 上運(yùn)行的基石。
移植過程
前人植樹,后人納涼。首先得感謝 Meco 大佬做的工作,他給我們帶了全新的 LVGL 接口。
第一步,打開 menuconfig。定位到 LVGL: powerful and easy-to-use embedded GUI library
選擇 “LVGL (official)” 以及 “Enable LVGL music player demo for RT-Thread” 兩項(xiàng),不用選 “LittlevGL2RTT (legacy)” 。
保存退出。
第二步,執(zhí)行?pkgs --update
?下載 LVGL 及 demo 包。
第三步,在 bsp 目錄下搜索 lvgl 文件夾。隨便選一個拷貝到自己項(xiàng)目的 Application 目錄下。筆者這里從 “nuvoton k-980iot” 下拷貝了一份。如果你使用 RT-Studio 拷貝過來就可以了,如果是使用 keil,需要之后手動把 這個文件夾的文件添加的項(xiàng)目,并添加頭文件路徑。
第四步,找到 lv_port_indev.c 文件,先注釋掉?#include "touch.h"
?及?nu_touch_inputevent_cb
?兩個函數(shù),input 驅(qū)動先放一放。
第五步,打開 lv_port_disp.c 文件,注釋掉?lv_port_disp_init
?部分與 lcd_device 相關(guān)的部分,因?yàn)?drv_lcd 里并不一定實(shí)現(xiàn)了 lcd 設(shè)備注冊。修改 draw buffer 的申請內(nèi)存,以及屏幕尺寸
-
draw_buf1 =(void*)rt_malloc(LCD_WIDTH *50*sizeof(lv_color_t));
-
lv_disp_draw_buf_init(&disp_buf, draw_buf1, RT_NULL, LCD_WIDTH *50);
-
...
-
disp_drv.hor_res = LCD_WIDTH;
-
disp_drv.ver_res = LCD_HEIGHT;
這個 draw buffer 不一定要按照全屏尺寸申請緩存,可以只申請十分之一行的,但是這樣一整屏數(shù)據(jù)刷新會分 10 次,優(yōu)點(diǎn)兒就是內(nèi)存占用少。筆者的屏幕是 480*272 的,試過只申請 20 行的緩存,刷新顯示沒壓力。
第六步,添加?flush_cb
?回調(diào)函數(shù)。flush_cb
?的原型是?void (*flush_cb)(struct _lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
?。
-
staticvoid tft_flush(lv_disp_drv_t*disp_drv,constlv_area_t*area,lv_color_t*color_p)
-
{
-
rt_int32_t x, y;
-
lv_coord_t hres = disp_drv->hor_res;
-
lv_coord_t vres = disp_drv->ver_res;
-
?
-
/*Return if the area is out the screen*/
-
if(area->x2 <0|| area->y2 <0|| area->x1 > hres -1|| area->y1 > vres -1){
-
lv_disp_flush_ready(disp_drv);
-
return;
-
}
-
?
-
for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++){
-
for(x = area->x1; x <= area->x2 && x < disp_drv->hor_res; x++){
-
((UINT16*)u8FrameBufPtr)[y * disp_drv->hor_res + x]= lv_color_to16(*color_p);
-
color_p++;
-
}
-
}
-
?
-
lv_disp_flush_ready(disp_drv);
-
}
同時,指定?disp_drv.flush_cb = tft_flush;
?指向我自己的?flush_cb
?。
第七步,顯示驅(qū)動搞好了,是不是可以運(yùn)行起來了呢?編譯、調(diào)試運(yùn)行。demo 應(yīng)該可以跑起來了。
GE2D 的使用
但是上面的?tft_flush
?回調(diào)函數(shù)是用 for 循環(huán)拷貝顯示數(shù)據(jù)的。這樣效率會不會低?NUC97x 系列帶 GE2D 圖形加速引擎,是否可以把它用上?
第一步,修改 drv_lcd ,初始化 LCM 的時候執(zhí)行?vpostVAStartTrigger
?之前,添加 GE2D 初始化
-
ge2dInit(16,800,480,(void*)u8FrameBufPtr);// 這里的參數(shù)根據(jù)實(shí)際屏幕參數(shù)而定
-
ge2dClearScreen(0x0);
第二步,修改?tft_flush
?函數(shù)
-
staticvoid tft_flush(lv_disp_drv_t*disp_drv,constlv_area_t*area,lv_color_t*color_p)
-
{
-
unsignedint cmd32 =0xcc410000;
-
unsignedint src_x =0;
-
unsignedint src_y =0;
-
unsignedint src_width = lv_area_get_width(area);
-
unsignedint src_height = lv_area_get_height(area);
-
unsignedint dst_x = area->x1;
-
unsignedint dst_y = area->y1;
-
?
-
sysFlushCache(I_D_CACHE);
-
?
-
outpw(REG_GE2D_CTL, cmd32);
-
?
-
switch(LV_COLOR_DEPTH){
-
case8:
-
cmd32 =0x00;
-
break;
-
case16:
-
cmd32 =0x10;
-
break;
-
case32:
-
cmd32 =0x20;
-
break;
-
}
-
cmd32 |=(inpw(REG_GE2D_MISCTL)&0xFFFFFFF8);
-
outpw(REG_GE2D_MISCTL, cmd32);
-
?
-
outpw(REG_GE2D_SDPITCH,((disp_drv->hor_res <<16)| src_width));
-
outpw(REG_GE2D_SRCSPA,0);
-
outpw(REG_GE2D_DSTSPA,((dst_y <<16)| dst_x));
-
outpw(REG_GE2D_RTGLSZ,((src_height <<16)| src_width));
-
outpw(REG_GE2D_XYSORG,(unsignedint)draw_buf1);// 上文提到的 draw_buf1,需要改成全局指針變量
-
outpw(REG_GE2D_XYDORG,(unsignedint)u8FrameBufPtr);
-
?
-
outpw(REG_GE2D_TRG,1);
-
?
-
while((inpw(REG_GE2D_INTSTS)&0x1)==0);
-
outpw(REG_GE2D_INTSTS,1);
-
?
-
lv_disp_flush_ready(disp_drv);
-
}
第三步,試跑一下,不錯!
不對?。?!有時候顯示有異常,但是過一會又好了,下圖左邊是異常顯示,右邊是正常顯示。
GD2D 的工作模式
經(jīng)過長時間的調(diào)試,摸排,對比,最后發(fā)現(xiàn)(這一句話,怎能概括這幾天偶的辛苦!
當(dāng)?
lv_area_get_width(area)
?的值是奇數(shù)的時候,就是左邊的樣子,是偶數(shù)的時候是右邊的樣子。
跟官方技術(shù)支持郵件溝通,他們提示筆者 TRM 里有一段話描述。
HostBLT is executed through eight 32-bit MMIO data ports for bit block data transfer. The host must perform 32-bit word-aligned accesses when writing data to, or reading data from the Graphics Engine.
大意就是,GE2D 在 HostBLT 工作方式下,傳輸?shù)臄?shù)據(jù)是需要 32-bit 對齊的。說的好像挺有道理。但是筆者的屏幕使用的 RGB565 顏色格式 16bit BPP 的,如果對齊到 32bit?
順著 TRM 的這段描述,筆者大膽猜測了一下 HostBLT 的工作模式。
1、它是按行搬運(yùn)數(shù)據(jù)的,雖然源數(shù)據(jù)?color_p
?是連續(xù)內(nèi)存區(qū)域,我們可以把它當(dāng)作一維數(shù)組,或者二維數(shù)組都行。顯存的目標(biāo)區(qū)域不是連續(xù)的,是行連續(xù)的。
2、當(dāng)搬運(yùn)第一行數(shù)據(jù)的時候,源數(shù)據(jù)?color_p
?的第一個數(shù)據(jù)(或者說?color_p
?這個指針)地址是 32bit 對齊的。這一行搬運(yùn)的數(shù)據(jù)總量等于 width * bpp / 8。
3、當(dāng)搬運(yùn)第二行的時候,情況就變了,因?yàn)榱袛?shù) width 是奇數(shù),上一行搬運(yùn)的數(shù)據(jù)總量是 2 的倍數(shù),不是 4 的倍數(shù)。當(dāng)前行第一個像素點(diǎn)的數(shù)據(jù)所在的地址也就不是 32bit 對齊的了——上一行最后一個像素點(diǎn)的數(shù)據(jù)才是 32bit 對齊的。HostBLT 拷貝這一行數(shù)據(jù)的時候,行首地址被強(qiáng)行抹去了 2 變成上一行最后一個數(shù)據(jù)的地址。造成奇數(shù)行顯示正常,偶數(shù)行行首像素是上一行最后一個像素,其它像素均向后錯了一個的怪象。
?
筆者廣求各路大佬,有測試條件的,有 NUC97x 系列或者 N9H30 系列開發(fā)板,或者自己做的帶 TFT 顯示屏的,可以 RGB565 模式顯示的,都可以測試一下。筆者提供測試源碼。
實(shí)驗(yàn)驗(yàn)證
筆者設(shè)計了個實(shí)驗(yàn),用于驗(yàn)證上述工作模式的描述。
首先構(gòu)造一個 19列 28 行的數(shù)組 fill1 表示顯示數(shù)據(jù)。以及一個 18列 28 行的數(shù)組 fill2 做比對項(xiàng)。
fill1 數(shù)組的第一列數(shù)據(jù)編輯成 0xF800 (紅色),最后一列 0x07FF (或者其它非紅色值)。fill2 可以和 fill1 一樣,也可以不一樣。
對這兩個數(shù)組分別執(zhí)行操作
-
ge2dSpriteBlt_Screen(x, y,19,28, fill1);// 1
-
ge2dBitblt_ScreenToScreen(x, y, x+20, y,19,28);// 2
-
ge2dBitblt_ScreenToScreen(x, y, x+40, y,18,28);// 3
-
ge2dSpriteBlt_Screen(x, y+30,18,28, fill2);// 4
-
ge2dBitblt_ScreenToScreen(x, y+30, x+20, y+30,18,28);// 5
-
ge2dBitblt_ScreenToScreen(x, y+30,70, y+30,19,28);// 6
1 4 分別從數(shù)組搬運(yùn)數(shù)據(jù)到顯存。2 3 是將 1 搬運(yùn)后顯存中的數(shù)據(jù)在顯存內(nèi)再次搬運(yùn)兩次。5 6 是將 2 搬運(yùn)后顯存中的數(shù)據(jù)在顯存內(nèi)再次搬運(yùn)兩次。測試結(jié)果
1、對比 1 4 我們發(fā)現(xiàn),同樣是從外部內(nèi)存搬運(yùn)到顯存,數(shù)據(jù)列數(shù)不同,顯示效果不一樣,當(dāng)列數(shù)為奇數(shù)的時候(1)顯示異常,出現(xiàn)像素錯位。
2、橫向觀察 1 2 3 從顯存搬運(yùn)到顯存,雖然數(shù)據(jù)列數(shù)同樣是奇數(shù),但是并沒有二次像素錯位。橫向比較 4 5 6 可以得出同樣的結(jié)論。
3、2 3 顯示和 1 一樣是像素錯位的,表明顯存中的數(shù)據(jù)已經(jīng)是錯的了。
4、修改上述代碼中的 x 坐標(biāo)值,我們還能發(fā)現(xiàn)一個事情,那就是,x 坐標(biāo)是奇數(shù)的時候,總有一半行的第一個像素值所在的顯存地址不是 4 的整數(shù)倍,但是顯存內(nèi)部數(shù)據(jù)搬運(yùn)并沒有出現(xiàn)像素錯位?。?!顯存內(nèi)部數(shù)據(jù)搬運(yùn)不遵守 32bit 對齊限制?!
修改 LVGL 解決眼前的問題
筆者對這類 gpu 沒有研究,不清楚這種工作模式是在所有架構(gòu)上都這樣的,還是 GE2D 的設(shè)計缺陷。
如果要解決這個問題,目前只能要求 LVGL 在 16bit 顏色深度的時候,把 area 處理成偶數(shù)列的。
但是吧,lvgl 被移植應(yīng)用到無數(shù)芯片上了吧,也肯定有很多用 gpu 加速的,他們都是怎么處理這個問題的?如果在其它芯片上 gpu 都是這樣工作的,LVGL 難道就不考慮這個問題?
LVGL 局部刷新時,強(qiáng)制偶數(shù)列刷新修改。lv_refr.c 文件的?lv_refr_area
?函數(shù)中有兩處處理 sub_area 的地方,對 sub_area 做如下補(bǔ)丁。
#if LV_COLOR_DEPTH == 16 ~~ if ((sub_area.x2 - sub_area.x1) % 2 == 0) {~~ ~~ if (sub_area.x1 == 0) {~~ ~~ sub_area.x2++;~~ ~~ } else {~~ ~~ sub_area.x1 &= ~0x1;~~ ~~ }~~ ~~ }~~ #endif
PS: 以上修改位置是錯了,可能會引起內(nèi)存溢出。嚴(yán)謹(jǐn)?shù)男薷娜缦拢?修改?lv_refr_areas
?函數(shù),在調(diào)用?lv_refr_area
?之前
-
disp_refr->driver->draw_buf->last_part =0;
-
#if LV_COLOR_DEPTH == 16
-
if((disp_refr->inv_areas[i].x2 - disp_refr->inv_areas[i].x1)%2==0){
-
if(disp_refr->inv_areas[i].x1 ==0){
-
disp_refr->inv_areas[i].x2++;
-
}else{
-
disp_refr->inv_areas[i].x1--;
-
}
-
}
-
#endif
-
lv_refr_area(&disp_refr->inv_areas[i]);
結(jié)束語
GE2D 的效率:實(shí)測,啟用?full_refresh
?的時候 GE2D 有 30% 的 FPS 提升。不啟用的時候 FPS 提升就沒那么明顯了,可能只有 10%。
rt-thread 的 LVGL 接口還有些欠缺的地方。LVGL 8.x 有兩個 conf.h ,lv_conf.h lv_drv_conf.h lv_demo_conf.h。能用上 lvgl 提供的模板文件,從模板文件修改是最好的了。而不是把這幾個文件合并成一個 lv_rt_thread_conf.h ,刪掉了很多配置項(xiàng)。
評論
查看更多