目录
LVGL文档中文网站
系统总览
应用 程序 创建 GUI 并处理特定任务的应用程序。
LVGL 图形库本身。您的应用程序可以与库通信以创建 GUI。它包含一个 HAL(硬件抽象层)接口来注册您的显示和输入设备驱动程序。
驱动程序 除了您的特定驱动程序之外,它还包含驱动您的显示器的功能,可选择驱动到 GPU 并读取触摸板或按钮。
根据 MCU 的不同,有两种典型的硬件设置。一个带有内置 LCD/TFT 驱动器外围,另一个没有它。在这两种情况下,都需要一个帧缓冲区来存储屏幕的当前图像。
-
带TFT/LCD 驱动 的MCU 如果您的MCU 有TFT/LCD 驱动外围,那么您可以通过RGB 接口直接连接显示器。在这种情况下,帧缓冲区可以位于内部 RAM(如果 MCU 有足够的 RAM)或外部 RAM(如果 MCU 有存储器接口)中。
-
外部显示控制器 如果 MCU 没有 TFT/LCD 驱动器接口,则必须使用外部显示控制器(例如 SSD1963、SSD1306、ILI9341)。在这种情况下,MCU 可以通过并行端口、SPI 或 I2C 与显示控制器通信。帧缓冲区通常位于显示控制器中,可为 MCU 节省大量 RAM。
设置项目
获取图书馆
LVGL 图形库可在 GitHub 上获得:https : //github.com/lvgl/lvgl。
您可以克隆它或从 GitHub 下载最新版本的库。
图形库是lvgl目录,应复制到您的项目中。
配置文件
有一个名为lv_conf.h 的 LVGL配置头文件。它设置库的基本行为,禁用未使用的模块和功能,在编译时调整内存缓冲区的大小等。
将lvgl/lv_conf_template.h复制到lvgl目录旁边,并将其重命名为lv_conf.h。打开文件并将开头更改为以启用其内容。
#if 0
#if 1
lv_conf.h也可以复制到其他地方,但是您应该将
LV_CONF_INCLUDE_SIMPLE
定义添加到您的编译器选项(例如-DLV_CONF_INCLUDE_SIMPLE
对于 gcc 编译器)并手动设置包含路径。在配置文件的注释中解释了选项的含义。至少检查这三个配置选项并根据您的硬件进行修改:
-
LV_HOR_RES_MAX显示器的水平分辨率。
-
LV_VER_RES_MAX显示器的垂直分辨率。
-
LV_COLOR_DEPTH 8 表示(RG332),16 表示(RGB565)或 32 表示(RGB888 和 ARGB8888)。
初始化
要使用图形库,您必须初始化它和其他组件。初始化的顺序是:
-
调用lv_init()。
-
初始化您的驱动程序。
-
lv_tick_inc(x)
在x
中断中每毫秒调用一次以告诉经过的时间。了解更多。 -
lv_task_handler()
每隔几毫秒定期调用以处理 LVGL 相关任务。了解更多。显示界面
要设置显示的
lv_disp_buf_t
和lv_disp_drv_t
变量的初始化。-
lv_disp_buf_t包含内部图形缓冲区。
-
lv_disp_drv_t包含回调函数来与显示交互和操作绘图相关的东西。
显示缓冲区
lv_disp_buf_t
可以这样初始化:/*A static or global variable to store the buffers*/ static lv_disp_buf_t disp_buf; /*Static or global buffer(s). The second buffer is optional*/ static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; static lv_color_t buf_2[MY_DISP_HOR_RES * 10]; /*Initialize `disp_buf` with the buffer(s) */ lv_disp_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES*10);
关于缓冲区大小,有 3 种可能的配置:
-
一个缓冲区LVGL 将屏幕内容绘制到一个缓冲区中并将其发送到显示器。缓冲区可以小于屏幕。在这种情况下,较大的区域将在多个部分中重新绘制。如果只有小区域发生变化(例如按下按钮),则只会刷新这些区域。
-
具有两个缓冲区的两个非屏幕大小的缓冲区LVGL 可以将其绘制到一个缓冲区中,而将另一个缓冲区的内容发送到后台显示。应该使用DMA或其他硬件将数据传输到显示器,让CPU同时绘制。这样,显示的渲染和刷新变得并行。与One buffer类似,如果缓冲区小于要刷新的区域,LVGL 将分块绘制显示内容。
-
两个屏幕大小的缓冲区。与两个非屏幕大小的缓冲区相比,LVGL 将始终提供整个屏幕的内容,而不仅仅是块。通过这种方式,驱动程序可以简单地将帧缓冲区的地址更改为从 LVGL 接收到的缓冲区。因此,当 MCU 具有 LCD/TFT 接口且帧缓冲区只是 RAM 中的一个位置时,此方法效果最佳。
您可以使用基准示例来衡量显示配置的性能。
显示驱动程序
一旦缓冲区初始化准备就绪,就需要初始化显示驱动程序。在最简单的情况下,只
lv_disp_drv_t
需要设置以下两个字段:-
指向初始化
lv_disp_buf_t
变量的缓冲区指针。 -
flush_cb一个回调函数,用于将缓冲区的内容复制到显示器的特定区域。
lv_disp_flush_ready()
需要在冲洗准备好时调用。LVGL 可能会以多个块呈现屏幕,因此会flush_cb
多次调用。要查看哪个是渲染的最后一块,请使用lv_disp_flush_is_last()
.
有一些可选的数据字段:
-
hor_res 显示器的水平分辨率。(
LV_HOR_RES_MAX
默认来自lv_conf.h)。 -
ver_res 显示器的垂直分辨率。(
LV_VER_RES_MAX
默认来自lv_conf.h)。 -
color_chroma_key将在镀铬键控图像上绘制为透明的颜色。
LV_COLOR_TRANSP
默认情况下来自lv_conf.h)。 -
user_data驱动程序的自定义用户数据。它的类型可以在 lv_conf.h 中修改。
-
抗锯齿使用抗锯齿(边缘平滑)。
LV_ANTIALIAS
默认来自lv_conf.h。 -
旋转和sw_rotate请参阅下面的旋转部分。
-
screen_transp如果
1
屏幕可以具有透明或不透明样式。LV_COLOR_SCREEN_TRANSP
需要在lv_conf.h 中启用。
要使用 GPU,可以使用以下回调:
-
gpu_fill_cb用颜色填充内存区域。
-
gpu_blend_cb使用不透明度混合两个内存缓冲区。
-
gpu_wait_cb如果有任何 GPU 函数返回,当 GPU 仍在 LVGL 工作时,将在需要时使用此函数,确保 GPU 渲染已准备就绪。
请注意,这些函数需要直接绘制到内存 (RAM) 而不是您的显示器。
一些其他可选的回调,使处理单色、灰度或其他非标准 RGB 显示器更容易、更优化:
-
rounder_cb四舍五入要重绘的区域的坐标。例如,2x2 像素可以转换为 2x8。如果显示控制器只能刷新具有特定高度或宽度的区域(单色显示器通常为 8 像素高度),则可以使用它。
-
set_px_cb一个自定义函数来写入显示缓冲区。如果显示器具有特殊的颜色格式,它可用于更紧凑地存储像素。(例如 1 位单色、2 位灰度等)这样使用的缓冲区
lv_disp_buf_t
可以更小,以仅容纳给定区域大小所需的位数。set_px_cb
不适用于显示缓冲区配置。Two screen-sized buffers
-
monitor_cb一个回调函数告诉多少像素在多少时间内被刷新。
-
clean_dcache_cb用于清除与显示相关的任何缓存的回调
要设置lv_disp_drv_t变量的字段,它需要用
lv_disp_drv_init(&disp_drv)
. 最后lv_disp_drv_register(&disp_drv)
需要调用为 LVGL 注册一个显示。放在一起看起来像这样:
lv_disp_drv_t disp_drv; /*A variable to hold the drivers. Can be local variable*/ lv_disp_drv_init(&disp_drv); /*Basic initialization*/ disp_drv.buffer = &disp_buf; /*Set an initialized buffer*/ disp_drv.flush_cb = my_flush_cb; /*Set a flush callback to draw to the display*/ lv_disp_t * disp; disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/
这里有一些简单的回调示例:
void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/ int32_t x, y; for(y = area->y1; y <= area->y2; y++) { for(x = area->x1; x <= area->x2; x++) { put_px(x, y, *color_p) color_p++; } } /* IMPORTANT!!! * Inform the graphics library that you are ready with the flushing*/ lv_disp_flush_ready(disp_drv); } void my_gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, const lv_area_t * dest_area, const lv_area_t * fill_area, lv_color_t color); { /*It's an example code which should be done by your GPU*/ uint32_t x, y; dest_buf += dest_width * fill_area->y1; /*Go to the first line*/ for(y = fill_area->y1; y < fill_area->y2; y++) { for(x = fill_area->x1; x < fill_area->x2; x++) { dest_buf[x] = color; } dest_buf+=dest_width; /*Go to the next line*/ } } void my_gpu_blend_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa) { /*It's an example code which should be done by your GPU*/ uint32_t i; for(i = 0; i < length; i++) { dest[i] = lv_color_mix(dest[i], src[i], opa); } } void my_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area) { /* Update the areas as needed. Can be only larger. * For example to always have lines 8 px height:*/ area->y1 = area->y1 & 0x07; area->y2 = (area->y2 & 0x07) + 8; } void my_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) { /* Write to the buffer as required for the display. * Write only 1-bit for monochrome displays mapped vertically:*/ buf += buf_w * (y >> 3) + x; if(lv_color_brightness(color) > 128) (*buf) |= (1 << (y % 8)); else (*buf) &= ~(1 << (y % 8)); } void my_monitor_cb(lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px) { printf("%d px refreshed in %d ms\n", time, ms); } void my_clean_dcache_cb(lv_disp_drv_t * disp_drv, uint32) { /* Example for Cortex-M (CMSIS) */ SCB_CleanInvalidateDCache(); }
回转
LVGL 支持以 90 度为增量旋转显示器。您可以选择是要软件轮换还是硬件轮换。
如果您选择软件旋转(
sw_rotate
标志设置为 1),LVGL 将为您执行旋转。您的驱动程序可以并且应该假设屏幕宽度和高度没有改变。只需像往常一样将像素刷新到显示器即可。软件轮换在您的flush_cb
回调中不需要额外的逻辑。在软件中执行轮换需要大量的开销,这就是硬件轮换也可用的原因。在这种模式下,LVGL 将绘制到缓冲区中,就好像您的屏幕现在具有反转的宽度和高度一样。您有责任自己旋转提供的像素。
初始化时显示的默认旋转可以使用
rotated
标志设置。可用选项为LV_DISP_ROT_NONE
,LV_DISP_ROT_90
,LV_DISP_ROT_180
,或LV_DISP_ROT_270
。旋转值与顺时针方向旋转物理显示器的方式有关。因此,LV_DISP_ROT_90
意味着您将硬件顺时针旋转 90 度,显示器逆时针旋转 90 度以进行补偿。(从 7.10.0 及更早版本升级的用户请注意:这些新的旋转枚举值与旧的 0/1 系统匹配,用于旋转 90 度,因此遗留代码应继续按预期工作。默认情况下,软件旋转也被禁用以实现兼容性.)
也可以在运行时使用API更改显示旋转。
lv_disp_set_rotation(disp, rot)
支持软件轮换是一项新功能,因此根据您的配置可能存在一些故障/错误。如果您遇到问题,请在GitHub 上打开一个问题。
应用程序接口
显示驱动 HAL 接口头文件
类型定义
类型定义结构_disp_drv_t
lv_disp_drv_t
HAL 要注册的显示驱动程序结构
类型定义结构_disp_t
lv_disp_t
显示结构。
笔记
lv_disp_drv_t
应该是结构的第一个成员。枚举
枚举
lv_disp_size_t
价值观:
枚举器
LV_DISP_SIZE_SMALL
枚举器
LV_DISP_SIZE_MEDIUM
枚举器
LV_DISP_SIZE_LARGE
枚举器
LV_DISP_SIZE_EXTRA_LARGE
职能
无效
lv_disp_drv_init
(lv_disp_drv_t *驱动程序)使用默认值初始化显示驱动程序。它用于在字段中具有已知值而不是内存中的垃圾。之后,您可以安全地只设置您需要的字段。
参数
驱动程序——指向要初始化的驱动程序变量的指针
void
lv_disp_buf_init
( lv_disp_buf_t * disp_buf , void * buf1 , void * buf2 , uint32_t size_in_px_cnt )初始化显示缓冲区
参数
-
disp_buf --
lv_disp_buf_t
要初始化的指针变量 -
buf1 -- LVGL 用来绘制图像的缓冲区。始终必须指定且不能为 NULL。可以是用户分配的数组。例如或外部 SRAM 中的存储器地址
static lv_color_t disp_buf1[1024 * 10]
-
buf2 -- 可选地指定第二个缓冲区,以使图像渲染和图像刷新(发送到显示器)并行。在这种情况下,
disp_drv->flush
您应该使用 DMA 或类似的硬件将图像发送到后台的显示器。它允许 LVGL 在发送前一帧时将下一帧渲染到另一个缓冲区中。NULL
如果未使用,请设置为。 -
size_in_px_cnt -尺寸
buf1
和buf2
像素数。
lv_disp_t *
lv_disp_drv_register
( lv_disp_drv_t *驱动程序)注册一个初始化的显示驱动程序。自动将第一个显示设置为活动。
参数
驱动程序——指向初始化的“lv_disp_drv_t”变量的指针(可以是局部变量)
退货
指向新显示的指针或出现错误时为 NULL
void
lv_disp_drv_update
( lv_disp_t * disp , lv_disp_drv_t * new_drv )在运行时更新驱动程序。
参数
-
disp -- 指向显示的指针。( 的返回值
lv_disp_drv_register
) -
new_drv -- 指向新驱动程序的指针
无效
lv_disp_remove
(lv_disp_t * disp)移除显示器
参数
disp -- 显示指针
无效
lv_disp_set_default
(lv_disp_t * disp)设置默认屏幕。默认情况下,将在其上创建新屏幕。
参数
disp -- 指向显示的指针
lv_disp_t *
lv_disp_get_default
(无效)获取默认显示
退货
指向默认显示的指针
lv_coord_t
lv_disp_get_hor_res
( lv_disp_t * disp )获取显示器的水平分辨率
参数
disp -- 指向显示的指针(NULL 使用默认显示)
退货
显示器的水平分辨率
lv_coord_t
lv_disp_get_ver_res
( lv_disp_t * disp )获取显示器的垂直分辨率
参数
disp -- 指向显示的指针(NULL 使用默认显示)
退货
显示器的垂直分辨率
bool
lv_disp_get_antialiasing
( lv_disp_t * disp )获取是否为显示器启用抗锯齿
参数
disp -- 指向显示的指针(NULL 使用默认显示)
退货
true:启用抗锯齿;假:禁用
lv_coord_t
lv_disp_get_dpi
( lv_disp_t * disp )获取显示器的 DPI
参数
disp -- 指向显示的指针(NULL 使用默认显示)
退货
显示器的 dpi
lv_disp_size_t
lv_disp_get_size_category
( lv_disp_t * disp )根据它的 hor 获取显示器的尺寸类别。资源 和 dpi。
参数
disp -- 指向显示的指针(NULL 使用默认显示)
退货
LV_DISP_SIZE_SMALL/MEDIUM/LARGE/EXTRA_LARGE
lv_disp_t *
lv_disp_get_next
( lv_disp_t * disp )获取下一个显示。
参数
disp -- 指向当前显示的指针。NULL 进行初始化。
退货
下一个显示或 NULL 如果没有更多。参数为NULL时给出第一个显示
lv_disp_buf_t *
lv_disp_get_buf
( lv_disp_t * disp )获取显示器的内部缓冲区
参数
disp -- 指向显示的指针
退货
指向内部缓冲区的指针
uint16_t
lv_disp_get_inv_buf_size
( lv_disp_t * disp )获取缓冲区中的区域数
退货
无效区域数
void
_lv_disp_pop_from_inv_buf
( lv_disp_t * disp , uint16_t num )从缓冲区弹出(删除)最后一个 'num' 无效区域
参数
num -- 要删除的区域数
bool
lv_disp_is_double_buf
( lv_disp_t * disp )检查驱动程序的配置,如果是双缓冲(包括
buf1
和buf2
设置)参数
disp -- 指向要检查的显示的指针
退货
真:双缓冲;false:不是双缓冲
bool
lv_disp_is_true_double_buf
( lv_disp_t * disp )检查驱动程序的配置,如果是真正的双缓冲(两者
buf1
和buf2
设置,并size
为屏幕大小)参数
disp -- 指向要检查的显示的指针
退货
真:双缓冲;false:不是双缓冲
结构
#include <lv_hal_disp.h>lv_disp_buf_t
用于保存显示缓冲区信息的结构。
公众会员
无效*
buf1
第一个显示缓冲区。
无效*
buf2
第二个显示缓冲区。
无效*
buf_act
uint32_t
size
lv_area_t
area
整数
flushing
整数
flushing_last
uint32_t
last_area
uint32_t
last_part
结构
#include <lv_hal_disp.h>_disp_drv_t
HAL 要注册的显示驱动程序结构
公众会员
lv_coord_t
hor_res
水平分辨率。
lv_coord_t
ver_res
垂直分辨率。
lv_disp_buf_t *
buffer
指向用 初始化的缓冲区的指针
lv_disp_buf_init()
。LVGL 将使用这个缓冲区来绘制屏幕内容uint32_t
antialiasing
1:在此显示器上启用抗锯齿。
uint32_t
rotated
1:将显示屏旋转 90 度。
警告
不为你更新坐标!
uint32_t
screen_transp
如果屏幕没有纯色 (opa == LV_OPA_COVER) 背景,则处理。仅在需要时使用,因为它较慢。
uint32_t
dpi
显示器的 DPI(每英寸点数)。默认设置为
LV_DPI
fromlv_Conf.h
。无效( *
flush_cb
) (结构_disp_drv_t * disp_drv ,常量lv_area_t *面积,lv_color_t * color_p )强制性:将内部缓冲区 (VDB) 写入显示器。完成后必须调用“lv_disp_flush_ready()”
无效( *
rounder_cb
) (结构_disp_drv_t * disp_drv , lv_area_t * area )可选:扩展无效区域以匹配
y
单色显示器上的显示驱动程序要求,例如舍入到 8、16 ..)无效( *
set_px_cb
) (结构_disp_drv_t * disp_drv , uint8_t * buf , lv_coord_t buf_w , lv_coord_t x , lv_coord_t y , lv_color_t color , lv_opa_t opa )可选:根据显示器的特殊要求在缓冲区中设置一个像素 可用于 LittelvGL 不支持的颜色格式。例如 2 位 -> 4 灰度
笔记
比使用支持的颜色格式绘制要慢得多。
无效( *
monitor_cb
) (结构_disp_drv_t * disp_drv , uint32_t时间, uint32_t px )可选:在每个刷新周期后调用以告知渲染和刷新时间 + 刷新像素数
无效( *
wait_cb
) (结构_disp_drv_t * disp_drv )可选:在 lvgl 等待操作完成时定期调用。例如刷新或 GPU 用户可以在这里执行非常简单的任务或产生任务
无效( *
clean_dcache_cb
) (结构_disp_drv_t * disp_drv )可选:当 lvgl 需要任何影响渲染的 CPU 缓存被清理时调用
无效( *
gpu_wait_cb
) (结构_disp_drv_t * disp_drv )可选:在 gpu 工作时调用等待
无效( *
gpu_blend_cb
) (结构_disp_drv_t * disp_drv , lv_color_t * dest ,常量lv_color_t * src , uint32_t长度, lv_opa_t opa )可选:使用不透明度混合两个内存(仅限 GPU)
无效( *
gpu_fill_cb
) (结构_disp_drv_t * disp_drv , lv_color_t * dest_buf , lv_coord_t dest_width ,常量lv_area_t * fill_area , lv_color_t颜色)可选:用颜色填充内存(仅限 GPU)
lv_color_t
color_chroma_key
在 CHROMA_KEYED 图像上,此颜色将是透明的。
LV_COLOR_TRANSP
默认。(lv_conf.h)lv_disp_drv_user_data_t
user_data
自定义显示驱动程序用户数据
结构
#include <lv_hal_disp.h>_disp_t
显示结构。
笔记
lv_disp_drv_t
应该是结构的第一个成员。公众会员
lv_disp_drv_t
driver
< 显示驱动程序 定期检查脏区并刷新它们的任务
lv_task_t *
refr_task
lv_ll_t
scr_ll
显示器的屏幕
结构_lv_obj_t *
act_scr
此显示器上的当前活动屏幕
结构_lv_obj_t *
prev_scr
上一个画面。在屏幕动画期间使用
结构_lv_obj_t *
scr_to_load
lv_scr_load_anim 中准备加载的画面
结构_lv_obj_t *
top_layer
看
结构_lv_obj_t *
sys_layer
看
uint8_t
del_prev
1:当屏幕加载动画准备好时自动删除上一屏
lv_color_t
bg_color
屏幕透明时的默认显示颜色
常量无效*
bg_img
显示为墙纸的图像源
lv_opa_t
bg_opa
背景颜色或墙纸的不透明度
lv_area_t
inv_areas
[LV_INV_BUF_SIZE
]无效(标记为重绘)区域
uint8_t
inv_area_joined
[LV_INV_BUF_SIZE
]uint32_t
inv_p
uint32_t
last_activity_time
上次在此显示器上有活动
输入设备接口
输入设备的类型
要设置输入设备
lv_indev_drv_t
,必须初始化一个变量:lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); /*Basic initialization*/ indev_drv.type =... /*See below.*/ indev_drv.read_cb =... /*See below.*/ /*Register the driver in LVGL and save the created input device object*/ lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv);
类型可以是
-
LV_INDEV_TYPE_POINTER触摸板或鼠标
-
LV_INDEV_TYPE_KEYPAD键盘或小键盘
-
LV_INDEV_TYPE_ENCODER编码器,带左、右、推选项
-
LV_INDEV_TYPE_BUTTON外部按键按下屏幕
read_cb是一个函数指针,它将被定期调用以报告输入设备的当前状态。它还可以缓冲数据并
false
在没有更多数据要读取或true
缓冲区不为空时返回。访问输入设备以了解有关一般输入设备的更多信息。
触摸板、鼠标或任何指针
可以点击屏幕点的输入设备属于这一类。
indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_input_read; ... bool my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data) { data->point.x = touchpad_x; data->point.y = touchpad_y; data->state = LV_INDEV_STATE_PR or LV_INDEV_STATE_REL; return false; /*No buffering now so no more data read*/ }
重要的
即使状态为LV_INDEV_STATE_REL ,触摸板驱动程序也必须返回最后的 X/Y 坐标。
要设置鼠标光标,请使用. (是 的返回值)
lv_indev_set_cursor(my_indev, &img_cursor)
my_indev
lv_indev_drv_register
键盘或键盘
带有所有字母的全键盘或带有几个导航按钮的简单键盘都属于这里。
要使用键盘/小键盘:
-
read_cb
用LV_INDEV_TYPE_KEYPAD
类型注册一个函数。 -
LV_USE_GROUP
在lv_conf.h 中启用 -
必须创建一个对象组: 并且必须将对象添加到其中
lv_group_t * g = lv_group_create()
lv_group_add_obj(g, obj)
-
所创建的组必须被分配给一个输入设备:(是的返回值)
lv_indev_set_group(my_indev, g)
my_indev
lv_indev_drv_register
-
用于
LV_KEY_...
在组中的对象之间导航。查看lv_core/lv_group.h
可用的键。
indev_drv.type = LV_INDEV_TYPE_KEYPAD; indev_drv.read_cb = keyboard_read; ... bool keyboard_read(lv_indev_drv_t * drv, lv_indev_data_t*data){ data->key = last_key(); /*Get the last pressed or released key*/ if(key_pressed()) data->state = LV_INDEV_STATE_PR; else data->state = LV_INDEV_STATE_REL; return false; /*No buffering now so no more data read*/ }
编码器
使用编码器,您可以做 4 件事:
-
按下它的按钮
-
长按它的按钮
-
转左
-
右转
简而言之,编码器输入设备的工作方式如下:
-
通过转动编码器,您可以专注于下一个/上一个对象。
-
当您在一个简单的对象(如按钮)上按下编码器时,它将被点击。
-
如果您按下复杂对象(如列表、消息框等)上的编码器,该对象将进入编辑模式,从而转动编码器您可以在对象内部导航。
-
要退出编辑模式,请长按按钮。
要使用编码器(类似于键盘),应将对象添加到组中。
indev_drv.type = LV_INDEV_TYPE_ENCODER; indev_drv.read_cb = encoder_read; ... bool encoder_read(lv_indev_drv_t * drv, lv_indev_data_t*data){ data->enc_diff = enc_get_new_moves(); if(enc_pressed()) data->state = LV_INDEV_STATE_PR; else data->state = LV_INDEV_STATE_REL; return false; /*No buffering now so no more data read*/ }
其他特性
另外
read_cb
一个feedback_cb
回调中可以同时指定lv_indev_drv_t
。feedback_cb
当输入设备发送任何类型的事件时调用。(与其类型无关)。它允许为用户提供反馈,例如在 上播放声音LV_EVENT_CLICK
。以下参数的默认值可以在lv_conf.h 中设置,但默认值可以在
lv_indev_drv_t
.-
drag_limit在实际拖动对象之前要滑动的像素数
-
drag_throw 拖投减速 [%]。更大的价值意味着更快的减速
-
long_press_time按下发送时间
LV_EVENT_LONG_PRESSED
(以毫秒为单位) -
long_press_rep_time发送间隔
LV_EVENT_LONG_PRESSED_REPEAT
(毫秒) -
read_task指向
lv_task
读取输入设备的指针。它的参数可以通过lv_task_...()
函数改变
每个输入设备都与一个显示器相关联。默认情况下,新的输入设备会添加到最后创建的或明确选择(使用
lv_disp_set_default()
)的显示中。相关的显示被存储并且可以disp
在驱动程序的字段中更改。应用程序接口
输入设备 HAL 接口层头文件
类型定义
类型定义uint8_t
lv_indev_type_t
类型定义uint8_t
lv_indev_state_t
类型定义uint8_t
lv_drag_dir_t
类型定义uint8_t
lv_gesture_dir_t
类型定义结构_lv_indev_drv_t
lv_indev_drv_t
由用户初始化并由 'lv_indev_add()' 注册
类型定义结构_lv_indev_proc_t
lv_indev_proc_t
输入设备的运行时数据 由库内部使用,您不需要触摸它。
类型定义结构_lv_indev_t
lv_indev_t
带有驱动程序、运行时数据 ('proc') 和一些附加信息的主要输入设备描述符
枚举
枚举[匿名的]
可能的输入设备类型
价值观:
枚举器
LV_INDEV_TYPE_NONE
未初始化状态
枚举器
LV_INDEV_TYPE_POINTER
触摸板、鼠标、外部按钮
枚举器
LV_INDEV_TYPE_KEYPAD
键盘或键盘
枚举器
LV_INDEV_TYPE_BUTTON
分配给屏幕特定点的外部(硬件按钮)
枚举器
LV_INDEV_TYPE_ENCODER
只有左转、右转和一个按钮的编码器
枚举[匿名的]
输入设备的状态
价值观:
枚举器
LV_INDEV_STATE_REL
枚举器
LV_INDEV_STATE_PR
枚举[匿名的]
价值观:
枚举器
LV_DRAG_DIR_HOR
对象可以水平拖动。
枚举器
LV_DRAG_DIR_VER
对象可以垂直拖动。
枚举器
LV_DRAG_DIR_BOTH
可以向各个方向拖动对象。
枚举器
LV_DRAG_DIR_ONE
对象只能拖动一个方向(第一次移动)。
枚举[匿名的]
价值观:
枚举器
LV_GESTURE_DIR_TOP
手势向上。
枚举器
LV_GESTURE_DIR_BOTTOM
手势向下。
枚举器
LV_GESTURE_DIR_LEFT
手势方向离开。
枚举器
LV_GESTURE_DIR_RIGHT
手势正确。
职能
无效
lv_indev_drv_init
(lv_indev_drv_t *驱动程序)使用默认值初始化输入设备驱动程序。它用于在字段 ant 中具有已知值而不是内存垃圾。之后,您可以设置字段。
参数
驱动程序——指向要初始化的驱动程序变量的指针
lv_indev_t *
lv_indev_drv_register
( lv_indev_drv_t *驱动程序)注册一个初始化的输入设备驱动程序。
参数
驱动程序——指向初始化的“lv_indev_drv_t”变量的指针(可以是局部变量)
退货
指向新输入设备的指针或出现错误时为 NULL
void
lv_indev_drv_update
( lv_indev_t * indev , lv_indev_drv_t * new_drv )在运行时更新驱动程序。
参数
-
indev -- 指向输入设备的指针。( 的返回值
lv_indev_drv_register
) -
new_drv -- 指向新驱动程序的指针
lv_indev_t *
lv_indev_get_next
( lv_indev_t * indev )获取下一个输入设备。
参数
indev -- 指向当前输入设备的指针。NULL 进行初始化。
退货
下一个输入设备或 NULL 如果没有更多。参数为NULL时给出第一个输入设备
bool
_lv_indev_read
( lv_indev_t * indev , lv_indev_data_t *数据)从输入设备读取数据。
参数
-
indev -- 指向输入设备的指针
-
data -- 输入设备将其数据写入这里
退货
false:没有更多数据;true:有更多数据要读取(缓冲)
结构
#include <lv_hal_indev.h>lv_indev_data_t
传递给输入驱动程序以填充的数据结构
公众会员
lv_point_t
point
对于 LV_INDEV_TYPE_POINTER 当前按下的点
uint32_t
key
对于 LV_INDEV_TYPE_KEYPAD 当前按下的键
uint32_t
btn_id
对于 LV_INDEV_TYPE_BUTTON 当前按下的按钮
int16_t
enc_diff
对于 LV_INDEV_TYPE_ENCODER 自上次读取以来的步数
lv_indev_state_t
state
LV_INDEV_STATE_REL 或 LV_INDEV_STATE_PR
结构
#include <lv_hal_indev.h>_lv_indev_drv_t
由用户初始化并由 'lv_indev_add()' 注册
公众会员
lv_indev_type_t
type
< 输入设备类型 读取输入设备数据的函数指针。如果有更多数据要读取(缓冲),则返回“true”。大多数驱动程序可以安全地返回“false”
布尔( *
read_cb
) (结构_lv_indev_drv_t * indev_drv , lv_indev_data_t *数据)无效( *
feedback_cb
) (结构_lv_indev_drv_t * , uint8_t )在输入设备上发生操作时调用。第二个参数是事件来自
lv_event_t
lv_indev_drv_user_data_t
user_data
结构_disp_t *
disp
< 指向分配的显示任务的指针,以读取周期性读取输入设备
lv_task_t *
read_task
在实际拖动对象之前要滑动的像素数
uint8_t
drag_limit
拖投减速 [%]。更大的价值意味着更快的减速
uint8_t
drag_throw
至少这种差异应该在两点之间评估为手势
uint8_t
gesture_min_velocity
至少这个区别应该是发送手势
uint8_t
gesture_limit
长按时间(毫秒)
uint16_t
long_press_time
长按重复触发周期 [ms]
uint16_t
long_press_rep_time
结构
#include <lv_hal_indev.h>_lv_indev_proc_t
输入设备的运行时数据 由库内部使用,您不需要触摸它。
公众会员
lv_indev_state_t
state
输入设备的当前状态。
lv_point_t
act_point
输入设备的当前点。
lv_point_t
last_point
输入设备的最后一点。
lv_point_t
vect
act_point
和之间的区别last_point
。lv_point_t
drag_sum
lv_point_t
drag_throw_vect
结构_lv_obj_t *
act_obj
结构_lv_obj_t *
last_obj
结构_lv_obj_t *
last_pressed
lv_gesture_dir_t
gesture_dir
lv_point_t
gesture_sum
uint8_t
drag_limit_out
uint8_t
drag_in_prog
lv_drag_dir_t
drag_dir
uint8_t
gesture_sent
结构_lv_indev_proc_t :: [匿名] :: [匿名]
pointer
lv_indev_state_t
last_state
uint32_t
last_key
结构_lv_indev_proc_t :: [匿名] :: [匿名]
keypad
联盟_lv_indev_proc_t :: [匿名]
types
uint32_t
pr_timestamp
按下的时间戳
uint32_t
longpr_rep_timestamp
长按重复时间戳
uint8_t
long_pr_sent
uint8_t
reset_query
uint8_t
disabled
uint8_t
wait_until_release
结构
#include <lv_hal_indev.h>_lv_indev_t
带有驱动程序、运行时数据 ('proc') 和一些附加信息的主要输入设备描述符
公众会员
lv_indev_drv_t
driver
lv_indev_proc_t
proc
结构_lv_obj_t *
cursor
LV_INPUT_TYPE_POINTER 的光标
结构_lv_group_t *
group
键盘目标组
常量lv_point_t *
btn_points
分配给按钮 () 屏幕的阵列点将在此处被按钮按下
时钟接口
LVGL 需要一个系统滴答来了解动画和其他任务所用的时间。
您需要
lv_tick_inc(tick_period)
定期调用该函数并以毫秒为单位告诉调用周期。例如,lv_tick_inc(1)
每毫秒调用一次。lv_tick_inc
应该在比lv_task_handler()
(例如在中断中)更高优先级的例程中调用以精确知道经过的毫秒数,即使执行lv_task_handler
需要更长的时间。使用 FreeRTOS
lv_tick_inc
可以在vApplicationTickHook
.在基于 Linux 的操作系统上(例如在 Raspberry Pi 上)
lv_tick_inc
可以在线程中调用,如下所示:void * tick_thread (void *args) { while(1) { usleep(5*1000); /*Sleep for 5 millisecond*/ lv_tick_inc(5); /*Tell LVGL that 5 milliseconds were elapsed*/ } }
应用程序接口
以 1 毫秒的分辨率提供对系统滴答的访问
功能
uint32_t
lv_tick_get
(无效)获取自启动以来经过的毫秒数
返回
经过的毫秒数
uint32_t
lv_tick_elaps
( uint32_t prev_tick )获取自上一个时间戳以来经过的毫秒数
参数
prev_tick -- 前一个时间戳(lv_tick_get() 的返回值)
返回
自“prev_tick”以来经过的毫秒数
-
- while(1) of main() function
- timer interrupt periodically (low priority then
lv_tick_inc()
) - an OS task periodically
时间并不重要,但它应该是大约 5 毫秒以保持系统响应。
-
例子:
while(1) { lv_task_handler(); my_delay_ms(5); }
要了解有关任务的更多信息,请访问任务部分。
睡眠管理
当没有用户输入发生时,MCU 可以进入睡眠状态。在这种情况下,主要内容
while(1)
应如下所示:while(1) { /*Normal operation (no sleep) in < 1 sec inactivity*/ if(lv_disp_get_inactive_time(NULL) < 1000) { lv_task_handler(); } /*Sleep after 1 sec inactivity*/ else { timer_stop(); /*Stop the timer where lv_tick_inc() is called*/ sleep(); /*Sleep the MCU*/ } my_delay_ms(5); }
如果发生唤醒(按下、触摸或点击等),您还应该在输入设备读取功能中添加以下几行:
lv_tick_inc(LV_DISP_DEF_REFR_PERIOD); /*Force task execution on wake-up*/ timer_start(); /*Restart the timer where lv_tick_inc() is called*/ lv_task_handler(); /*Call `lv_task_handler()` manually to process the wake-up event*/
除此之外,
lv_disp_get_inactive_time()
您还可以检查lv_anim_count_running()
是否每个动画都完成了。操作系统和中断
默认情况下,LVGL不是线程安全的。
但是,在以下情况下调用 LVGL 相关函数是有效的:
在事件中。在活动中了解更多信息。
在lv_tasks 中。在任务中了解更多信息。
中断
尽量避免从中断中调用 LVGL 函数(除了
lv_tick_inc()
和lv_disp_flush_ready()
)。但是,如果您需要这样做,您必须禁用在lv_task_handler
运行时使用 LVGL 函数的中断。设置标志或某个值并定期在lv_task
.日志记录
LVGL 有内置的日志模块来通知用户库中发生的事情。
日志级别
要启用日志记录,请在lv_conf.h 中设置并设置为以下值之一:
LV_USE_LOG 1
LV_LOG_LEVEL
-
LV_LOG_LEVEL_TRACE大量日志给出详细信息
-
LV_LOG_LEVEL_INFO 记录重要事件
-
LV_LOG_LEVEL_WARN 记录是否发生了不想要的事情但没有引起问题
-
LV_LOG_LEVEL_ERROR仅关键问题,当系统可能出现故障时
-
LV_LOG_LEVEL_NONE 不记录任何内容
级别高于设置的日志级别的事件也将被记录。例如,如果你
LV_LOG_LEVEL_WARN
,错误也将被记录。使用 printf 记录
如果您的系统支持
printf
,您只需要LV_LOG_PRINTF
在lv_conf.h 中启用以发送带有printf
.自定义日志功能
如果您无法使用
printf
或想使用自定义函数进行日志记录,您可以使用 .log 注册一个“记录器”回调lv_log_register_print_cb()
。例如:
void my_log_cb(lv_log_level_t level, const char * file, uint32_t line, const char * fn_name, const char * dsc) { /*Send the logs via serial port*/ if(level == LV_LOG_LEVEL_ERROR) serial_send("ERROR: "); if(level == LV_LOG_LEVEL_WARN) serial_send("WARNING: "); if(level == LV_LOG_LEVEL_INFO) serial_send("INFO: "); if(level == LV_LOG_LEVEL_TRACE) serial_send("TRACE: "); serial_send("File: "); serial_send(file); char line_str[8]; sprintf(line_str,"%d", line); serial_send("#"); serial_send(line_str); serial_send(": "); serial_send(fn_name); serial_send(": "); serial_send(dsc); serial_send("\n"); } ... lv_log_register_print_cb(my_log_cb);
添加日志
您还可以通过
LV_LOG_TRACE/INFO/WARN/ERROR(description)
函数使用日志模块。任务和线程
如果你需要使用真正的任务或线程,你需要一个互斥锁,它应该在调用之前调用
lv_task_handler
并在调用之后释放。此外,您必须在其他任务和线程中围绕每个 LVGL (lv_...
) 相关函数调用和代码使用相同的互斥锁。这样你就可以在真正的多任务环境中使用 LVGL。只需使用互斥锁来避免并发调用 LVGL 函数。 -