Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
LittlevGL_LVGL
RT-Thread一般讨论
rt-thread 驱动篇 之 NUC97x 移植 LVGL
发布于 2022-03-26 12:55:42 浏览:3161
订阅该版
[tocm] ## 前言 严格讲,这一篇不涉及 rt-thread 驱动,但是它是 LVGL 和 rt-thread 的接口。LVGL 在 rt-thread 上运行的基石。 ### 移植过程 前人植树,后人纳凉。首先得感谢 [Meco](https://github.com/mysterywolf) 大佬做的工作,他给我们带了全新的 LVGL 接口。 第一步,打开 menuconfig。定位到 LVGL: powerful and easy-to-use embedded GUI library ![lvgl-menu-path.png](https://oss-club.rt-thread.org/uploads/20220326/7c391720de810112177845c5d495ada7.png) 选择 "LVGL (official)" 以及 "Enable LVGL music player demo for RT-Thread" 两项,不用选 "LittlevGL2RTT (legacy)" 。 保存退出。 第二步,执行 `pkgs --update` 下载 LVGL 及 demo 包。 第三步,在 bsp 目录下搜索 lvgl 文件夹。随便选一个拷贝到自己项目的 Application 目录下。笔者这里从 "nuvoton\nk-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);` 。 ``` static void tft_flush(lv_disp_drv_t *disp_drv, const lv_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` 函数 ``` static void tft_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { unsigned int cmd32 = 0xcc410000; unsigned int src_x = 0; unsigned int src_y = 0; unsigned int src_width = lv_area_get_width(area); unsigned int src_height = lv_area_get_height(area); unsigned int dst_x = area->x1; unsigned int dst_y = area->y1; sysFlushCache(I_D_CACHE); outpw(REG_GE2D_CTL, cmd32); switch(LV_COLOR_DEPTH) { case 8: cmd32 = 0x00; break; case 16: cmd32 = 0x10; break; case 32: 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, (unsigned int)draw_buf1); // 上文提到的 draw_buf1,需要改成全局指针变量 outpw(REG_GE2D_XYDORG, (unsigned int)u8FrameBufPtr); outpw(REG_GE2D_TRG, 1); while((inpw(REG_GE2D_INTSTS) & 0x1) == 0); outpw(REG_GE2D_INTSTS, 1); lv_disp_flush_ready(disp_drv); } ``` 第三步,试跑一下,不错! 不对!!!有时候显示有异常,但是过一会又好了,下图左边是异常显示,右边是正常显示。 ![lvgl-show-err.png](https://oss-club.rt-thread.org/uploads/20220326/dd56e971426d67b6dc81dae6cf03c6c5.png.webp) ![lvgl-show-ok.png](https://oss-club.rt-thread.org/uploads/20220326/6618b8203d39a26ad8f422e27c107869.png.webp) ### 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 搬运后显存中的数据在显存内再次搬运两次。测试结果 ![lvgl-blterr.png](https://oss-club.rt-thread.org/uploads/20220328/54808381a1a6f0330154ff8928fef744.png.webp) 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 ,删掉了很多配置项。
23
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
出出啊
恃人不如自恃,人之为己者不如己之自为也
文章
43
回答
1517
被采纳
342
关注TA
发私信
相关文章
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
freemodbus
PWM
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
中断
编译报错
Debug
SFUD
rt_mq_消息队列_msg_queue
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
a1012112796
10
个答案
1
次被采纳
踩姑娘的小蘑菇
4
个答案
1
次被采纳
红枫
4
个答案
1
次被采纳
张世争
4
个答案
1
次被采纳
Ryan_CW
4
个答案
1
次被采纳
本月文章贡献
catcatbing
3
篇文章
5
次点赞
YZRD
2
篇文章
5
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
Woshizhapuren
1
篇文章
5
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部