?
?作者:出出啊
鏈接:
https://club.rt-thread.org/ask/article/64a11e0669eabd42.html
?
前言
嚴(yán)格講,這一篇不涉及 rt-thread 驅(qū)動(dòng),但是它是 LVGL 和 rt-thread 的接口。LVGL 在 rt-thread 上運(yùn)行的基石。
移植過(guò)程
前人植樹(shù),后人納涼。首先得感謝 Meco 大佬做的工作,他給我們帶了全新的 LVGL 接口。
第一步,打開(kāi) 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 文件夾。隨便選一個(gè)拷貝到自己項(xiàng)目的 Application 目錄下。筆者這里從 “nuvoton k-980iot” 下拷貝了一份。如果你使用 RT-Studio 拷貝過(guò)來(lái)就可以了,如果是使用 keil,需要之后手動(dòng)把 這個(gè)文件夾的文件添加的項(xiàng)目,并添加頭文件路徑。
第四步,找到 lv_port_indev.c 文件,先注釋掉?#include "touch.h"
?及?nu_touch_inputevent_cb
?兩個(gè)函數(shù),input 驅(qū)動(dòng)先放一放。
第五步,打開(kāi) lv_port_disp.c 文件,注釋掉?lv_port_disp_init
?部分與 lcd_device 相關(guān)的部分,因?yàn)?drv_lcd 里并不一定實(shí)現(xiàn)了 lcd 設(shè)備注冊(cè)。修改 draw buffer 的申請(qǐng)內(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;
這個(gè) draw buffer 不一定要按照全屏尺寸申請(qǐng)緩存,可以只申請(qǐng)十分之一行的,但是這樣一整屏數(shù)據(jù)刷新會(huì)分 10 次,優(yōu)點(diǎn)兒就是內(nèi)存占用少。筆者的屏幕是 480*272 的,試過(guò)只申請(qǐng) 20 行的緩存,刷新顯示沒(méi)壓力。
第六步,添加?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);
-
}
同時(shí),指定?disp_drv.flush_cb = tft_flush;
?指向我自己的?flush_cb
?。
第七步,顯示驅(qū)動(dòng)搞好了,是不是可以運(yùn)行起來(lái)了呢?編譯、調(diào)試運(yùn)行。demo 應(yīng)該可以跑起來(lái)了。
GE2D 的使用
但是上面的?tft_flush
?回調(diào)函數(shù)是用 for 循環(huán)拷貝顯示數(shù)據(jù)的。這樣效率會(huì)不會(huì)低?NUC97x 系列帶 GE2D 圖形加速引擎,是否可以把它用上?
第一步,修改 drv_lcd ,初始化 LCM 的時(shí)候執(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);
-
}
第三步,試跑一下,不錯(cuò)!
不對(duì)?。?!有時(shí)候顯示有異常,但是過(guò)一會(huì)又好了,下圖左邊是異常顯示,右邊是正常顯示。
GD2D 的工作模式
經(jīng)過(guò)長(zhǎng)時(shí)間的調(diào)試,摸排,對(duì)比,最后發(fā)現(xiàn)(這一句話,怎能概括這幾天偶的辛苦!
當(dāng)?
lv_area_get_width(area)
?的值是奇數(shù)的時(shí)候,就是左邊的樣子,是偶數(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 對(duì)齊的。說(shuō)的好像挺有道理。但是筆者的屏幕使用的 RGB565 顏色格式 16bit BPP 的,如果對(duì)齊到 32bit?
順著 TRM 的這段描述,筆者大膽猜測(cè)了一下 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í)候,源數(shù)據(jù)?color_p
?的第一個(gè)數(shù)據(jù)(或者說(shuō)?color_p
?這個(gè)指針)地址是 32bit 對(duì)齊的。這一行搬運(yùn)的數(shù)據(jù)總量等于 width * bpp / 8。
3、當(dāng)搬運(yùn)第二行的時(shí)候,情況就變了,因?yàn)榱袛?shù) width 是奇數(shù),上一行搬運(yùn)的數(shù)據(jù)總量是 2 的倍數(shù),不是 4 的倍數(shù)。當(dāng)前行第一個(gè)像素點(diǎn)的數(shù)據(jù)所在的地址也就不是 32bit 對(duì)齊的了——上一行最后一個(gè)像素點(diǎn)的數(shù)據(jù)才是 32bit 對(duì)齊的。HostBLT 拷貝這一行數(shù)據(jù)的時(shí)候,行首地址被強(qiáng)行抹去了 2 變成上一行最后一個(gè)數(shù)據(jù)的地址。造成奇數(shù)行顯示正常,偶數(shù)行行首像素是上一行最后一個(gè)像素,其它像素均向后錯(cuò)了一個(gè)的怪象。
?
筆者廣求各路大佬,有測(cè)試條件的,有 NUC97x 系列或者 N9H30 系列開(kāi)發(fā)板,或者自己做的帶 TFT 顯示屏的,可以 RGB565 模式顯示的,都可以測(cè)試一下。筆者提供測(cè)試源碼。
實(shí)驗(yàn)驗(yàn)證
筆者設(shè)計(jì)了個(gè)實(shí)驗(yàn),用于驗(yàn)證上述工作模式的描述。
首先構(gòu)造一個(gè) 19列 28 行的數(shù)組 fill1 表示顯示數(shù)據(jù)。以及一個(gè) 18列 28 行的數(shù)組 fill2 做比對(duì)項(xiàng)。
fill1 數(shù)組的第一列數(shù)據(jù)編輯成 0xF800 (紅色),最后一列 0x07FF (或者其它非紅色值)。fill2 可以和 fill1 一樣,也可以不一樣。
對(duì)這兩個(gè)數(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)兩次。測(cè)試結(jié)果
1、對(duì)比 1 4 我們發(fā)現(xiàn),同樣是從外部?jī)?nèi)存搬運(yùn)到顯存,數(shù)據(jù)列數(shù)不同,顯示效果不一樣,當(dāng)列數(shù)為奇數(shù)的時(shí)候(1)顯示異常,出現(xiàn)像素錯(cuò)位。
2、橫向觀察 1 2 3 從顯存搬運(yùn)到顯存,雖然數(shù)據(jù)列數(shù)同樣是奇數(shù),但是并沒(méi)有二次像素錯(cuò)位。橫向比較 4 5 6 可以得出同樣的結(jié)論。
3、2 3 顯示和 1 一樣是像素錯(cuò)位的,表明顯存中的數(shù)據(jù)已經(jīng)是錯(cuò)的了。
4、修改上述代碼中的 x 坐標(biāo)值,我們還能發(fā)現(xiàn)一個(gè)事情,那就是,x 坐標(biāo)是奇數(shù)的時(shí)候,總有一半行的第一個(gè)像素值所在的顯存地址不是 4 的整數(shù)倍,但是顯存內(nèi)部數(shù)據(jù)搬運(yùn)并沒(méi)有出現(xiàn)像素錯(cuò)位!??!顯存內(nèi)部數(shù)據(jù)搬運(yùn)不遵守 32bit 對(duì)齊限制?!
修改 LVGL 解決眼前的問(wèn)題
筆者對(duì)這類(lèi) gpu 沒(méi)有研究,不清楚這種工作模式是在所有架構(gòu)上都這樣的,還是 GE2D 的設(shè)計(jì)缺陷。
如果要解決這個(gè)問(wèn)題,目前只能要求 LVGL 在 16bit 顏色深度的時(shí)候,把 area 處理成偶數(shù)列的。
但是吧,lvgl 被移植應(yīng)用到無(wú)數(shù)芯片上了吧,也肯定有很多用 gpu 加速的,他們都是怎么處理這個(gè)問(wèn)題的?如果在其它芯片上 gpu 都是這樣工作的,LVGL 難道就不考慮這個(gè)問(wèn)題?
LVGL 局部刷新時(shí),強(qiáng)制偶數(shù)列刷新修改。lv_refr.c 文件的?lv_refr_area
?函數(shù)中有兩處處理 sub_area 的地方,對(duì) 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: 以上修改位置是錯(cuò)了,可能會(huì)引起內(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é)束語(yǔ)
GE2D 的效率:實(shí)測(cè),啟用?full_refresh
?的時(shí)候 GE2D 有 30% 的 FPS 提升。不啟用的時(shí)候 FPS 提升就沒(méi)那么明顯了,可能只有 10%。
rt-thread 的 LVGL 接口還有些欠缺的地方。LVGL 8.x 有兩個(gè) conf.h ,lv_conf.h lv_drv_conf.h lv_demo_conf.h。能用上 lvgl 提供的模板文件,從模板文件修改是最好的了。而不是把這幾個(gè)文件合并成一個(gè) lv_rt_thread_conf.h ,刪掉了很多配置項(xiàng)。
評(píng)論