LVGL移植

一、设置项目

获取LVGL图形库

https://github.com/lvgl/lvgl

也可以克隆它或从 GitHub 下载最新版本的库。

git clone https://github.com/lvgl/lvgl

图形库本身是 lvgl 目录,应将其复制到你的项目中。

获取显示和输入设备驱动程序

https://github.com/lvgl/lv_drivers

修改配置文件

有一个名为 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”变量初始化,如下所示:

/*A static or global variable to store the buffers*/
static lv_disp_draw_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). With only one buffer use NULL instead buf_2 */
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,则默认启用。

rotatedsw_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);      /*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);

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_indevlv_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);   /*睡眠5毫秒*/
        lv_tick_inc(5);      /*告诉LVGL已经过了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) {
  /*不活动时间<1秒时正常运行(无睡眠)*/
  if(lv_disp_get_inactive_time(NULL) < 1000) {
	  lv_task_handler();
  }
  /*休息1秒钟后再睡眠*/
  else {
	  timer_stop();   /*在调用lv_tick_inc()的位置停止计时器*/
	  sleep();		    /*让MCU睡眠*/
  }
  my_delay_ms(5);
}

你还应该将以下几行添加到你的输入设备读取功能中,以表示发生了唤醒(按下、触摸或点击等):

lv_tick_inc(LV_DISP_DEF_REFR_PERIOD);  /*唤醒时强制执行任务*/
timer_start();                         /*重新启动调用lv_tick_inc()的计时器*/
lv_task_handler();                     /*手动调用'lv_task_handler()'来处理唤醒事件*/

除了 lv_disp_get_inactive_time() 之外,你还可以检查 lv_anim_count_running() 以查看是否所有动画都已完成。

五、LVGL完整初始化

int main(int argc,char *argv[])
{
    /*LittlevGL init*/
    lv_init();
    
   /*Linux frame buffer device init*/
    fbdev_init();

   /*A small buffer for LittlevGL to draw the screen's content*/
    static lv_color_t buf[DISP_BUF_SIZE];
    static lv_color_t buf1[DISP_BUF_SIZE];

   /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf, buf1, DISP_BUF_SIZE);

   /*Initialize and register a display driver*/
    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);

    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = (void (*)(struct _lv_indev_drv_t *, lv_indev_data_t *))evdev_read; // defined in lv_drivers/indev/evdev.h
    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;
    }
    
   /*Handle LitlevGL tasks (tickless mode)*/
    while(1) {
        lv_task_handler();
        usleep(5000);
    }
    return 0;
}

你可能感兴趣的:(linux)