?
?作者:出出啊
鏈接:
https://club.rt-thread.org/ask/article/64a11e0669eabd42.html
?
前言
嚴格講,這一篇不涉及 rt-thread 驅動,但是它是 LVGL 和 rt-thread 的接口。LVGL 在 rt-thread 上運行的基石。
移植過程
前人植樹,后人納涼。首先得感謝 Meco 大佬做的工作,他給我們帶了全新的 LVGL 接口。
第一步,打開 menuconfig。定位到 LVGL: powerful and easy-to-use embedded GUI library
選擇 “LVGL (official)” 以及 “Enable LVGL music player demo for RT-Thread” 兩項,不用選 “LittlevGL2RTT (legacy)” 。
保存退出。
第二步,執行?pkgs --update
?下載 LVGL 及 demo 包。
第三步,在 bsp 目錄下搜索 lvgl 文件夾。隨便選一個拷貝到自己項目的 Application 目錄下。筆者這里從 “nuvoton k-980iot” 下拷貝了一份。如果你使用 RT-Studio 拷貝過來就可以了,如果是使用 keil,需要之后手動把 這個文件夾的文件添加的項目,并添加頭文件路徑。
第四步,找到 lv_port_indev.c 文件,先注釋掉?#include "touch.h"
?及?nu_touch_inputevent_cb
?兩個函數,input 驅動先放一放。
第五步,打開 lv_port_disp.c 文件,注釋掉?lv_port_disp_init
?部分與 lcd_device 相關的部分,因為 drv_lcd 里并不一定實現了 lcd 設備注冊。修改 draw buffer 的申請內存,以及屏幕尺寸
-
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 不一定要按照全屏尺寸申請緩存,可以只申請十分之一行的,但是這樣一整屏數據刷新會分 10 次,優點兒就是內存占用少。筆者的屏幕是 480*272 的,試過只申請 20 行的緩存,刷新顯示沒壓力。
第六步,添加?flush_cb
?回調函數。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
?。
第七步,顯示驅動搞好了,是不是可以運行起來了呢?編譯、調試運行。demo 應該可以跑起來了。
GE2D 的使用
但是上面的?tft_flush
?回調函數是用 for 循環拷貝顯示數據的。這樣效率會不會低?NUC97x 系列帶 GE2D 圖形加速引擎,是否可以把它用上?
第一步,修改 drv_lcd ,初始化 LCM 的時候執行?vpostVAStartTrigger
?之前,添加 GE2D 初始化
第二步,修改?tft_flush
?函數
-
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 的工作模式
經過長時間的調試,摸排,對比,最后發現(這一句話,怎能概括這幾天偶的辛苦!
當?
lv_area_get_width(area)
?的值是奇數的時候,就是左邊的樣子,是偶數的時候是右邊的樣子。
跟官方技術支持郵件溝通,他們提示筆者 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 工作方式下,傳輸的數據是需要 32-bit 對齊的。說的好像挺有道理。但是筆者的屏幕使用的 RGB565 顏色格式 16bit BPP 的,如果對齊到 32bit?
順著 TRM 的這段描述,筆者大膽猜測了一下 HostBLT 的工作模式。
1、它是按行搬運數據的,雖然源數據?color_p
?是連續內存區域,我們可以把它當作一維數組,或者二維數組都行。顯存的目標區域不是連續的,是行連續的。
2、當搬運第一行數據的時候,源數據?color_p
?的第一個數據(或者說?color_p
?這個指針)地址是 32bit 對齊的。這一行搬運的數據總量等于 width * bpp / 8。
3、當搬運第二行的時候,情況就變了,因為列數 width 是奇數,上一行搬運的數據總量是 2 的倍數,不是 4 的倍數。當前行第一個像素點的數據所在的地址也就不是 32bit 對齊的了——上一行最后一個像素點的數據才是 32bit 對齊的。HostBLT 拷貝這一行數據的時候,行首地址被強行抹去了 2 變成上一行最后一個數據的地址。造成奇數行顯示正常,偶數行行首像素是上一行最后一個像素,其它像素均向后錯了一個的怪象。
?
筆者廣求各路大佬,有測試條件的,有 NUC97x 系列或者 N9H30 系列開發板,或者自己做的帶 TFT 顯示屏的,可以 RGB565 模式顯示的,都可以測試一下。筆者提供測試源碼。
實驗驗證
筆者設計了個實驗,用于驗證上述工作模式的描述。
首先構造一個 19列 28 行的數組 fill1 表示顯示數據。以及一個 18列 28 行的數組 fill2 做比對項。
fill1 數組的第一列數據編輯成 0xF800 (紅色),最后一列 0x07FF (或者其它非紅色值)。fill2 可以和 fill1 一樣,也可以不一樣。
對這兩個數組分別執行操作
-
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 分別從數組搬運數據到顯存。2 3 是將 1 搬運后顯存中的數據在顯存內再次搬運兩次。5 6 是將 2 搬運后顯存中的數據在顯存內再次搬運兩次。測試結果
1、對比 1 4 我們發現,同樣是從外部內存搬運到顯存,數據列數不同,顯示效果不一樣,當列數為奇數的時候(1)顯示異常,出現像素錯位。
2、橫向觀察 1 2 3 從顯存搬運到顯存,雖然數據列數同樣是奇數,但是并沒有二次像素錯位。橫向比較 4 5 6 可以得出同樣的結論。
3、2 3 顯示和 1 一樣是像素錯位的,表明顯存中的數據已經是錯的了。
4、修改上述代碼中的 x 坐標值,我們還能發現一個事情,那就是,x 坐標是奇數的時候,總有一半行的第一個像素值所在的顯存地址不是 4 的整數倍,但是顯存內部數據搬運并沒有出現像素錯位!!!顯存內部數據搬運不遵守 32bit 對齊限制?!
修改 LVGL 解決眼前的問題
筆者對這類 gpu 沒有研究,不清楚這種工作模式是在所有架構上都這樣的,還是 GE2D 的設計缺陷。
如果要解決這個問題,目前只能要求 LVGL 在 16bit 顏色深度的時候,把 area 處理成偶數列的。
但是吧,lvgl 被移植應用到無數芯片上了吧,也肯定有很多用 gpu 加速的,他們都是怎么處理這個問題的?如果在其它芯片上 gpu 都是這樣工作的,LVGL 難道就不考慮這個問題?
LVGL 局部刷新時,強制偶數列刷新修改。lv_refr.c 文件的?lv_refr_area
?函數中有兩處處理 sub_area 的地方,對 sub_area 做如下補丁。
#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: 以上修改位置是錯了,可能會引起內存溢出。嚴謹的修改如下: 修改?lv_refr_areas
?函數,在調用?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]);
結束語
GE2D 的效率:實測,啟用?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 ,刪掉了很多配置項。
評論
查看更多