一、设置项目
获取LVGL图形库
https://github.com/lvgl/lvgl
也可以克隆它或从 GitHub 下载最新版本的库。
git clone https://github.com/lvgl/lvgl
图形库本身是 lvgl 目录,应将其复制到你的项目中。
修改配置文件
有一个名为 lv_conf.h 的 LVGL 配置头文件。在这里,你可以设置库的基本行为、禁用未使用的模块和功能、在编译时调整内存缓冲区的大小等。
复制 lvgl 目录旁边的 lvgl/lv_conf_template.h 并将其重命名为 lv_conf.h。打开文件并将开头的“#if 0”更改为“#if 1”以启用其内容。
lv_conf.h 也可以复制到另一个地方,但是你应该添加 LV_CONF_INCLUDE_SIMPLE 定义到你的编译器选项(例如 -DLV_CONF_INCLUDE_SIMPLE 用于 gcc 编译器)并手动设置包含路径。 在这种情况下,LVGL 将尝试使用 #include “lv_conf.h” 简单地包含 lv_conf.h。
在配置文件的注释中解释了选项的含义。请务必根据显示器的颜色深度至少设置“LV_COLOR_DEPTH”。
初始化
要使用图形库,必须初始化它和其他组件。初始化的顺序是:
1.调用lv_init()。
2.初始化驱动程序。
3.在 LVGL 中注册显示和输入设备驱动程序。
4.在中断中每隔 x 毫秒调用 lv_tick_inc(x) 以告知经过的时间。
5.每隔几毫秒调用lv_timer_handler()来处理LVGL相关的任务。
二、显示接口
要为 LVGL 注册一个显示器,必须初始化一个 lv_disp_draw_buf_t 和一个 lv_disp_drv_t 变量。
lv_disp_draw_buf_t 包含称为绘制缓冲区的内部图形缓冲区。
lv_disp_drv_t 包含与显示交互和操作绘图相关事物的回调函数。
绘制缓冲区
绘制缓冲区是 LVGL 用来渲染屏幕内容的简单数组。 一旦渲染准备就绪,绘制缓冲区的内容将使用显示驱动程序中设置的 flush_cb 函数发送到显示器(见下文)。
绘制绘制缓冲区可以通过“lv_disp_draw_buf_t”变量初始化,如下所示:
static lv_disp_draw_buf_t disp_buf;
static lv_color_t buf_1[MY_DISP_HOR_RES * 10];
static lv_color_t buf_2[MY_DISP_HOR_RES * 10];
lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES*10);
请注意,lv_disp_draw_buf_t 需要是静态的、全局的或动态分配的,而不是超出范围时销毁的局部变量。
绘制缓冲区可以小于屏幕。在这种情况下,较大的区域将被重新绘制为适合绘制缓冲区的较小部分。 如果只有一个小区域发生变化(例如按下按钮),则只会刷新该区域。
更大的缓冲区会导致更好的性能,但超过 1/10 屏幕大小的缓冲区没有显着的性能改进。 因此,建议选择绘制缓冲区的大小至少为屏幕大小的 1/10。
如果只使用一个缓冲区,LVGL 将屏幕内容绘制到该绘制缓冲区中并将其发送到显示器。 这样 LVGL 需要等到缓冲区的内容发送到显示器,然后再在其中绘制新内容。
如果使用两个缓冲区,LVGL 可以绘制到一个缓冲区中,而另一个缓冲区的内容被发送到后台显示。 应使用 DMA 或其他硬件将数据传输到显示器,让 MCU 同时绘制。 这样,显示的渲染和刷新变得并行。
在显示驱动程序(lv_disp_drv_t)中,可以启用 full_refresh 位以强制 LVGL 始终重绘整个屏幕。这适用于 one buffer 和 two buffers 模式。
如果启用 full_refresh 并提供 2 个屏幕大小的绘制缓冲区,LVGL 的显示处理就像“传统”双缓冲一样工作。
这意味着在 flush_cb 中只有帧缓冲区的地址需要更改为提供的指针(color_p 参数)。 如果 MCU 具有 LCD 控制器外围设备而不是外部显示控制器(例如 ILI9341 或 SSD1963),则应使用此配置。
显示驱动程序
一旦缓冲区初始化准备好,lv_disp_drv_t 显示驱动程序需要
用lv_disp_drv_init(&disp_drv)初始化
它的字段需要设置
需要在LVGL中用lv_disp_drv_register(&disp_drv)注册
请注意,lv_disp_drv_t 也需要是静态的、全局的或动态分配的,而不是超出范围时销毁的局部变量。
必须要适配的部分
在最简单的情况下,只需要设置lv_disp_drv_t的以下字段:
指向初始化的 lv_disp_draw_buf_t 变量的 draw_buf 指针。
hor_res 显示器的水平分辨率(以像素为单位)。
ver_res 显示器的垂直分辨率(以像素为单位)。
flush_cb 一个回调函数,用于将缓冲区的内容复制到显示器的特定区域。
lv_disp_flush_ready(&disp_drv) 需要在刷新准备好时调用。 LVGL 可能会以多个块呈现屏幕,因此多次调用 flush_cb。要查看当前是否是渲染的最后一个块,请使用 lv_disp_flush_is_last(&disp_drv)。
可选的部分
有一些可选的数据字段:
color_chroma_key 将在镀铬键控图像上绘制为透明的颜色。从lv_conf.h默认设置为LV_COLOR_CHROMA_KEY。
anti_aliasing 使用抗锯齿(边缘平滑)。如果在 lv_conf.h 中将 LV_COLOR_DEPTH 设置为至少 16,则默认启用。
rotated 和 sw_rotate 请参阅下面的 Rotation 部分。
screen_transp 如果 1 屏幕本身也可以具有透明度。 LV_COLOR_SCREEN_TRANSP 需要在 lv_conf.h 中启用并且需要 LV_COLOR_DEPTH 32。
user_data 驱动程序的自定义void 用户数据…
一些其他可选的回调,使处理单色、灰度或其他非标准 RGB 显示器更容易、更优化:
rounder_cb 四舍五入要重绘的区域的坐标。例如。 2x2 px 可以转换为 2x8。 如果显示控制器只能刷新具有特定高度或宽度的区域(单色显示器通常为 8 像素高度),则可以使用它。
set_px_cb 一个自定义函数来写入绘制缓冲区。如果显示器具有特殊的颜色格式,它可用于将像素更紧凑地存储在绘图缓冲区中。 (例如 1 位单色、2 位灰度等) 这样,lv_disp_draw_buf_t 中使用的缓冲区可以更小,以仅容纳给定区域大小所需的位数。请注意,使用set_px_cb 渲染比普通渲染慢。
monitor_cb 一个回调函数,告诉我们在多长时间内刷新了多少像素。当最后一个块被渲染并发送到显示器时调用。
clean_dcache_cb 用于清理与显示相关的任何缓存的回调。
查看原文 LVGL 内置了对多个 GPU 的支持(参见 lv_conf.h),但如果需要其他功能,这些函数可用于使 LVGL 使用 GPU:
gpu_fill_cb 用颜色填充内存中的一个区域。
gpu_wait_cb 如果在 GPU 仍在工作时任何 GPU 函数返回,LVGL 将在需要时使用此函数以确保 GPU 渲染准备就绪。
示例
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = my_flush_cb;
disp_drv.hor_res = 320;
disp_drv.ver_res = 240;
lv_disp_t * disp;
disp = lv_disp_drv_register(&disp_drv);
三、输入设备接口
输入设备的类型
要注册输入设备,必须初始化一个 lv_indev_drv_t 变量:
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type =...
indev_drv.read_cb =...
lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv);
type 可以是
LV_INDEV_TYPE_POINTER 触摸板或鼠标
LV_INDEV_TYPE_KEYPAD 键盘或小键盘
LV_INDEV_TYPE_ENCODER 编码器,带有左/右转和推动选项
LV_INDEV_TYPE_BUTTON 外部按钮虚拟按下屏幕
read_cb 是一个函数指针,它将被定期调用以报告输入设备的当前状态。
触摸板、鼠标或任何指针
可以点击屏幕上的点的输入设备属于这一类。
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_input_read;
...
void my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
if(touchpad_pressed) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
要设置鼠标光标,请使用 lv_indev_set_cursor(my_indev, &img_cursor)。 (my_indev 是 lv_indev_drv_register 的返回值)
四、心跳接口
LVGL 需要一个系统滴答来了解动画和其他任务所用的时间。
需要定期调用 lv_tick_inc(tick_period) 函数并提供以毫秒为单位的调用周期。例如,lv_tick_inc(1) 每毫秒调用一次。
lv_tick_inc 应该在比 lv_task_handler() 更高优先级的例程中调用(例如在中断中),以精确知道经过的毫秒数,即使 lv_task_handler 的执行需要更多时间。
使用 FreeRTOS,可以在 vApplicationTickHook 中调用 lv_tick_inc。
在基于 Linux 的操作系统(例如在 Raspberry Pi 上)可以在如下线程中调用 lv_tick_inc:
void * tick_thread (void *args)
{
while(1) {
usleep(5*1000);
lv_tick_inc(5);
}
}
任务处理器
要处理 LVGL 的任务,您需要以下列方式之一定期调用 lv_timer_handler():
main() 函数的 while(1)
定时器定期中断(比 lv_tick_inc() 优先级低)
定期执行操作系统任务
时间并不重要,但它应该是大约 5 毫秒以保持系统响应。
示例:
while(1) {
lv_timer_handler();
my_delay_ms(5);
}
睡眠管理
当没有用户输入发生时,MCU 可以进入睡眠状态。在这种情况下,主要的 while(1) 应该是这样的:
while(1) {
if(lv_disp_get_inactive_time(NULL) < 1000) {
lv_task_handler();
}
else {
timer_stop();
sleep();
}
my_delay_ms(5);
}
你还应该将以下几行添加到你的输入设备读取功能中,以表示发生了唤醒(按下、触摸或点击等):
lv_tick_inc(LV_DISP_DEF_REFR_PERIOD);
timer_start();
lv_task_handler();
除了 lv_disp_get_inactive_time() 之外,你还可以检查 lv_anim_count_running() 以查看是否所有动画都已完成。
五、LVGL完整初始化
int main(int argc,char *argv[])
{
lv_init();
fbdev_init();
static lv_color_t buf[DISP_BUF_SIZE];
static lv_color_t buf1[DISP_BUF_SIZE];
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf, buf1, DISP_BUF_SIZE);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = fbdev_flush;
disp_drv.hor_res = 1024;
disp_drv.ver_res = 600;
lv_disp_drv_register(&disp_drv);
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
evdev_init();
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = (void (*)(struct _lv_indev_drv_t *, lv_indev_data_t *))evdev_read;
lv_indev_t *lv_indev = lv_indev_drv_register(&indev_drv);
if (!lv_indev)
{
printf("lv_indev rregister error %d \r\n", __LINE__);
return 0;
}
while(1) {
lv_task_handler();
config_main_realtime();
usleep(5000);
}
return 0;
}
|