作者:出出啊
链接:
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 初始化
-
ge2dInit(16,800,480,(void*)u8FrameBufPtr);// 这里的参数根据实际屏幕参数而定
-
ge2dClearScreen(0x0);
第二步,修改 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 ,删掉了很多配置项。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)