【pomin】D1h开发板lvgl界面切换

博客链接:
pomin.top

界面设计

使用 NXP 的 GUI Guider 软件来对界面进行设计,这里设计了一个简单界面,两个页面可以通过按键来进入、返回

设置 Next、Back 两个按键的事件为点击按键后加载到另一个界面

然后点击生成代码即可,对于生成的代码中,查看工程目录

其中 generated 是生成的代码,lvgl-simulator 是 GUI Guider 模拟器的入口代码,打开lvgl-simulator/main.c 进行查看,可以看到加载生成的界面和事件代码只需要:

  • 一个全局变量 lv_ui guider_ui;
  • setup_ui(&guider_ui); 载入界面和事件
  • custom_init(&guider_ui); 自定义代码,这里没用到
lv_ui guider_ui;

int main(int argc, char ** argv)
{
    (void) argc;    /*Unused*/
    (void) argv;    /*Unused*/

    /*Initialize LittlevGL*/
    lv_init();

    /*Initialize the HAL (display, input devices, tick) for LittlevGL*/
    hal_init();

    /*Create a GUI-Guider app */
    setup_ui(&guider_ui);
	custom_init(&guider_ui);

    while(1) {
        /* Periodically call the lv_task handler.
         * It could be done in a timer interrupt or an OS task too.*/
        lv_task_handler();
#if LV_USE_VIDEO
        video_play(&guider_ui);
#endif
        usleep(5 * 1000);
    }

    return 0;
}

然后把生成的这些代码复制到 Linux 的工程下面,在 Makefile 中添加如下代码,把生成代码的 generated.mk 给加入进来

CFLAGS+=-Igenerated -Ilvgl
PRJ_DIR := .
include generated/generated.mk
CSRCS           +=$(GEN_CSRCS)

此时 HMI 的事件驱动已经适配好了,下面要做的就是使用 lvgl 的输入设备驱动框架来注册一个真实输入设备,这里也就是红外遥控器上面的两个按键

红外按键输入事件

在前文中,已经知道了红外接收头对应的输入为 event1,在 lvgl 的输入设备框架中注册为两个 button 按键,绑定两个红外按键到 lvgl 的两个触摸坐标 (200, 200), (1500, 150),对于初始化按键的代码片段如下

void lvgl_btn_init(void)
{
    /*------------------
     * Button
     * -----------------*/

    /*Initialize your button if you have*/
    button_init();
    static lv_indev_drv_t indev_drv;

    /*Register a button input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_BUTTON;
    indev_drv.read_cb = button_read;
    indev_button = lv_indev_drv_register(&indev_drv);

    /*Assign buttons to points on the screen*/
    static const lv_point_t btn_points[2] = {
        {200, 200},   /*Button 0 -> x:10; y:10*/
        {1500, 150},   /*Button 1 -> x:40; y:100*/
    };
    lv_indev_set_button_points(indev_button, btn_points);
}

然后再 main 函数中调用 lvgl_btn_init 来完成按键的初始化即可,最终的 main.c 代码如下,因为红外按键按下的时候会连续的发信号,为了避免按下松开太快,实现的是红外按键按下时界面按键按下,等待一段时间后界面按键松开,红外按键松开时无操作的逻辑

#include "lvgl/lvgl.h"
#include "lv_drivers/display/sunxifb.h"
#include "lvgl/demos/lv_demos.h"
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include "gui_guider.h"
#include <fcntl.h>
#include <linux/input.h>

lv_ui guider_ui;

static void button_init(void);
static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static int8_t button_get_pressed_id(void);
static bool button_is_pressed(uint8_t id);

lv_indev_t * indev_button;
int fd_ir;
const char *pDevice = "/dev/input/event1";
uint16_t sw_page_timeout[2];

/*------------------
 * Button
 * -----------------*/

/*Initialize your buttons*/
static void button_init(void)
{
    /*Your code comes here*/
    fd_ir = open(pDevice, O_RDWR | O_NONBLOCK);

    if(fd_ir == -1)
    {
        printf("ERROR Opening %s\n", pDevice);
        return;
    }
}

/*Will be called by the library to read the button*/
static void button_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{

    static uint8_t last_btn = 0;

    /*Get the pressed button's ID*/
    int8_t btn_act = button_get_pressed_id();

    if(btn_act >= 0) {
        data->state = LV_INDEV_STATE_PR;
        last_btn = btn_act;
    }
    else {
        data->state = LV_INDEV_STATE_REL;
    }

    /*Save the last pressed button's ID*/
    data->btn_id = last_btn;
}

/*Get ID  (0, 1, 2 ..) of the pressed button*/
static int8_t button_get_pressed_id(void)
{
    uint8_t i;

    /*Check to buttons see which is being pressed (assume there are 2 buttons)*/
    for(i = 0; i < 2; i++) {
        /*Return the pressed button's ID*/
        if(button_is_pressed(i)) {
            return i;
        }
    }

    /*No button pressed*/
    return -1;
}

/*Test if `id` button is pressed or not*/
static bool button_is_pressed(uint8_t id)
{

    /*Your code comes here*/

    return !!sw_page_timeout[id];
}

void lvgl_btn_init(void)
{
    /*------------------
     * Button
     * -----------------*/

    /*Initialize your button if you have*/
    button_init();
    static lv_indev_drv_t indev_drv;

    /*Register a button input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_BUTTON;
    indev_drv.read_cb = button_read;
    indev_button = lv_indev_drv_register(&indev_drv);

    /*Assign buttons to points on the screen*/
    static const lv_point_t btn_points[2] = {
        {200, 200},   /*Button 0 -> x:10; y:10*/
        {1500, 150},   /*Button 1 -> x:40; y:100*/
    };
    lv_indev_set_button_points(indev_button, btn_points);
}


int main(int argc, char *argv[])
{
    /*LittlevGL init*/
    lv_init();

    uint32_t rotated = LV_DISP_ROT_NONE;

    /*Linux frame buffer device init*/
    sunxifb_init(rotated);

    /*A buffer for LittlevGL to draw the screen's content*/
    static uint32_t width, height;
    sunxifb_get_sizes(&width, &height);

    static lv_color_t *buf;
    buf = (lv_color_t*) malloc(width * height * sizeof (lv_color_t));

    if (buf == NULL) {
        sunxifb_exit();
        printf("malloc draw buffer fail\n");
        return 0;
    }

    /*Initialize a descriptor for the buffer*/
    static lv_disp_draw_buf_t disp_buf;
    lv_disp_draw_buf_init(&disp_buf, buf, NULL, width * height);

    /*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   = sunxifb_flush;
    disp_drv.hor_res    = width;
    disp_drv.ver_res    = height;
    disp_drv.rotated    = rotated;
    lv_disp_drv_register(&disp_drv);

    lvgl_btn_init();

    int ret;
    struct input_event ev_ir;

    setup_ui(&guider_ui);
    // lv_demo_benchmark();

    /*Handle LitlevGL tasks (tickless mode)*/
    while(1) {
        lv_task_handler();

        memset(&ev_ir, 0, sizeof(struct input_event));
        ret = read(fd_ir, &ev_ir, sizeof(struct input_event));

        if(ret == sizeof(struct input_event))
        {
            if(ev_ir.type == 4 && ev_ir.code == 4)
            {
                if(ev_ir.value == 0x44) {
                    if(sw_page_timeout[0] == 0) {
                        printf("key 0 pressed\n");
                        sw_page_timeout[0] = 1;
                    }
                }
                if(ev_ir.value == 0x40) {
                    if(sw_page_timeout[1] == 0) {
                        printf("key 1 pressed\n");
                        sw_page_timeout[1] = 1;
                    }
                }
            }
        }
        if(sw_page_timeout[0] > 1000) {
            printf("key 0 releaseed\n");
            sw_page_timeout[0] = 0;
        }
        if(sw_page_timeout[1] > 1000) {
            printf("key 1 releaseed\n");
            sw_page_timeout[1] = 0;
        }
        if (sw_page_timeout[0] > 0) sw_page_timeout[0]++;
        if (sw_page_timeout[1] > 0) sw_page_timeout[1]++;
        usleep(100);
    }

    /*free(buf);*/
    /*sunxifb_exit();*/
    return 0;
}

/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if(start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }

    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;

    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}

烧录测试

编译一下然后用 adb 传到板子上面

make -j99
adb push demo /

测试现象,用红外遥控器可以切换页面