ESP32系列之LVGL(四):输入设备(外部按键)对接

系列文章目录

ESP32系列之LVGL(一):ESP32S3+ST7789点屏

ESP32系列之LVGL(二):ESP32S3移植LVGL(v8.3) 

ESP32系列之LVGL(三):Gui-Guider的使用 


目录

系列文章目录

前言

一、任务目标

二、准备工作

三、输入设备移植

1.创建输入设备组件

2.底层代码的实现

1.修改lv_port_indev.h

2. 修改lv_port_indev.c

3.CMakeLists.txt 的实现

四、目标工程的实现 

1.ui设计

2.组的添加

​编辑 3.修改main.c

五、演示效果

六、其它

1.按键驱动参考

2.lv_event_send 的使用

总结


前言

由于屏幕没有触摸屏,所以在工程上只能使用外部实体按键来作为输入设备对ui界面进行控制,本篇文章基于此目对按键输入设备对接lvgl的过程进行记录,仅供参考。


一、任务目标

由于笔者自己也是边学边用,因此对lvgl输入设备的种类、概念之类的也不再多做介绍,需要具体了解可以参考lvgl官方文档或其它博客介绍,这里只简单介绍一下本文的任务目标:ui创建有三个button的页面,通过外部实体按键可在这三个button间实现聚焦与切换,点击button切换至有滑动条的页面,通过按键使滑动条数值增加。

有三个实体按键,它们分别实现:

1)控件聚焦与切换(LV_KEY_NEXT)

2)控件选择(LV_KEY_ENTER)

3)控件数值变化(LV_KEY_RIGHT)


二、准备工作

芯片平台:ESP32S3

LVGL版本:V8.3

ESP-IDF版本:v4.4

1.已完成屏幕显示功能

2.已完成lvgl库移植到esp32s3

3.已完成底层按键驱动(最好)


三、输入设备移植

1.创建输入设备组件

在工程的components/lvgl/examples/porting/文件夹下找到 lv_port_indev_template.c 和lv_port_indev_template.h 两个文件,将其复制拷贝到新的 lv_port_indev 组件文件夹下并改名为 lv_port_indev.c 和 lv_port_indev.h,并创建CMakeLists.txt 文件。结构如下:

2.底层代码的实现

1.修改lv_port_indev.h

lv_port_indev.h 只需要修改把#if 0 改为 #if 1 即可。


/**
 * @file lv_port_indev_templ.h
 *
 */

/*Copy this file as "lv_port_indev.h" and set this value to "1" to enable content*/
#if 1

#ifndef LV_PORT_INDEV_H
#define LV_PORT_INDEV_H

#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/
#include "lvgl/lvgl.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 * GLOBAL PROTOTYPES
 **********************/
void lv_port_indev_init(void);


/**********************
 *      MACROS
 **********************/

#ifdef __cplusplus
} /*extern "C"*/
#endif

#endif /*LV_PORT_INDEV_TEMPL_H*/

#endif /*Disable/Enable content*/

2. 修改lv_port_indev.c

因为我们这里使用的输入方式是keypad,所以把其它几种输入方式的代码先删掉,只保留indev_keypad相关代码。其中,

keypad_init()是底层按键检测代码,需要用户自己实现,这里我们初始化了3个io按键。

keypad_get_key()是将实体按键与lvgl输入设备关联,这里我们选择关联LV_KEY_NEXT、LV_KEY_RIGHT和LV_KEY_ENTER。

/**
 * @file lv_port_indev_templ.c
 *
 */

/*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_indev.h"
#include "lvgl.h"

#include "hal_btn.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/


static void keypad_init(void);
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
static uint32_t keypad_get_key(void);



/**********************
 *  STATIC VARIABLES
 **********************/
lv_indev_t * indev_keypad;




// lv_group_t *key_group;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_indev_init(void)
{
    /**
     * Here you will find example implementation of input devices supported by LittelvGL:
     *  - Touchpad
     *  - Mouse (with cursor support)
     *  - Keypad (supports GUI usage only with key)
     *  - Encoder (supports GUI usage only with: left, right, push)
     *  - Button (external buttons to press points on the screen)
     *
     *  The `..._read()` function are only examples.
     *  You should shape them according to your hardware
     */

    static lv_indev_drv_t indev_drv;

    /*------------------
     * Keypad
     * -----------------*/

    /*Initialize your keypad or keyboard if you have*/
    keypad_init();

    /*Register a keypad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_KEYPAD;
    indev_drv.read_cb = keypad_read;
    indev_keypad = lv_indev_drv_register(&indev_drv);

    /*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
     *add objects to the group with `lv_group_add_obj(group, obj)`
     *and assign this input device to group to navigate in it:
     *`lv_indev_set_group(indev_keypad, group);`*/

    
}

/**********************
 *   STATIC FUNCTIONS
 **********************/



/*------------------
 * Keypad
 * -----------------*/

/*Initialize your keypad*/
static void keypad_init(void)
{
    btn_init(0,UP);
    btn_init(20,UP);
    btn_init(19,UP);
}

/*Will be called by the library to read the mouse*/
static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static uint32_t last_key = 0;

    /*Get the current x and y coordinates*/
    // mouse_get_xy(&data->point.x, &data->point.y);

    /*Get whether the a key is pressed and save the pressed key*/
    uint32_t act_key = keypad_get_key();
    // printf("key:%d\n",act_key);
    if(act_key != 0) {
        data->state = LV_INDEV_STATE_PR;

        /*Translate the keys to LVGL control characters according to your key definitions*/
        switch(act_key) {
            case 1:
                act_key = LV_KEY_NEXT;
                break;
            case 2:
                act_key = LV_KEY_PREV;
                break;
            case 3:
                act_key = LV_KEY_LEFT;
                break;
            case 4:
                act_key = LV_KEY_RIGHT;
                break;
            case 5:
                act_key = LV_KEY_ENTER;
                break;
        }

        last_key = act_key;
    }
    else {
        data->state = LV_INDEV_STATE_REL;
    }

    data->key = last_key;
}

/*Get the currently being pressed key.  0 if no key is pressed*/
static uint32_t keypad_get_key(void)
{
    if(btn_get_level(0)==0)
        return 1;
    else if(btn_get_level(20)==0)
        return 4;
    else if(btn_get_level(19)==0)
        return 5;

    return 0;
}


#endif

3.CMakeLists.txt 的实现

CMakeLists.txt 这里引用lvgl 以及自己的hal_btn组件(此组件的实现将放在文末做参考)。

file(GLOB_RECURSE srcs *.c)

idf_component_register(
    SRCS ${srcs}
    INCLUDE_DIRS .
    REQUIRES lvgl hal_btn
)

 到了这一步我们的lvgl 按键输入差不多就移植好了,接下来来实现我们的任务目标。


四、目标工程的实现 

1.ui设计

首先,我们需要创建好我们的ui并移植到我们的工程中。这里我们在screen页面创建三个不同颜色的button,其中,通过点击红色的btn1可跳转至页面screen_1,通过按键实现滑动条数值的增加,并通过点击back返回screen页面。

screen:

screen_1:

2.组的添加

通过lvgl的注释我们知道,想要让我们的按键控制ui的功能能实现还需要创建一个组(group),并且将我们的ui控件添加进这个组中,并将输入设备的句柄与组相关联。

/*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
     *add objects to the group with `lv_group_add_obj(group, obj)`
     *and assign this input device to group to navigate in it:
     *`lv_indev_set_group(indev_keypad, group);`*/

 在文件setup_scr_screen.c和setup_scr_screen_1.c里找到ui控件创建函数并添加相应的代码。

也可以直接在lv_port_indev_init()中添加默认组,这样后续创建的控件将会自动加入默认组。

    extern lv_indev_t * indev_keypad;
	lv_group_t *group=lv_group_create();
    lv_indev_set_group(indev_keypad, group);	//将组绑定到输入设备

    lv_group_set_editing(group, false);   //导航模式
	lv_group_add_obj(group ,ui->screen_btn_1);
	lv_group_add_obj(group ,ui->screen_btn_2);
	lv_group_add_obj(group ,ui->screen_btn_3);


    lv_group_add_obj(group ,ui->screen_1_btn_1);
	lv_group_add_obj(group ,ui->screen_1_slider_1);

 3.修改main.c

#include <stdio.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "lvgl.h"

#include "lvgl_helpers.h"

#include "gui_guider.h"
#include "custom.h"

#include "lv_port_indev.h"

#include "demos/lv_demos.h"


lv_ui guider_ui;



#define LVGL_TICK_MS 1


#define TAG "main"


void lv_tick_task(void *arg)
{
    lv_tick_inc(LVGL_TICK_MS);
}



void app_main(void)
{

    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }

    
    /* Initialize SPI or I2C bus used by the drivers */
    lvgl_driver_init();

    lv_init();

    lv_color_t *buf1 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf1 != NULL);
    static lv_color_t *buf2 = NULL;

    static lv_disp_draw_buf_t disp_buf;

    uint32_t size_in_px = DISP_BUF_SIZE;
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, size_in_px);
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = BOARD_LCD_H_RES;
    disp_drv.ver_res = BOARD_LCD_V_RES;
    disp_drv.flush_cb = disp_driver_flush;
    disp_drv.draw_buf = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"};
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 1 * 1000));

    lv_port_indev_init();  //输入设备初始化

    
    // lvgl demo演示
    // lv_demo_music();
    // lv_demo_stress();

    setup_ui(&guider_ui);
   
    while (1)
    {
        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        vTaskDelay(pdMS_TO_TICKS(10));
        lv_task_handler();
    }
}


 

五、演示效果

esp32_keypad_indev


六、其它

1.按键驱动参考

hal_btn.c:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "hal_btn.h"

static bool s_btn_disable = false;
static bool s_btn_level = false;
static btn_cb_t s_btn_cb = NULL;

void btn_init(gpio_num_t gpio_num, pull_mode_t pull_mode)
{
    gpio_pad_select_gpio(gpio_num);
    gpio_set_direction(gpio_num, GPIO_MODE_INPUT);
    gpio_pad_select_gpio(gpio_num);
    gpio_set_direction(gpio_num, GPIO_MODE_INPUT);
    switch (pull_mode)
    {
    case UP:
        gpio_set_pull_mode(gpio_num, GPIO_PULLUP_ONLY);
        break;
    case DOWN:
        gpio_set_pull_mode(gpio_num, GPIO_PULLDOWN_ONLY);
        break;
    case FLOATING:
        gpio_set_pull_mode(gpio_num, GPIO_FLOATING);
        break;
    default:
        break;
    }
}

void btn_set_disable(bool disable)
{
    s_btn_disable = disable;
}

void btn_send_level(void)
{
    s_btn_level = true;
}

int btn_get_level(gpio_num_t gpio_num)
{
    static int level = 0;
    if (s_btn_disable)
    {
        s_btn_level = false;
        return true;
    }
    if (s_btn_level)
    {
        s_btn_level = false;
        return false;
    }
    level = gpio_get_level(gpio_num);
    if (s_btn_cb != NULL && level == 0)
    {
        s_btn_cb();
    }
    return level;
}

void btn_set_callback(btn_cb_t btn_cb)
{
    s_btn_cb = btn_cb;
}

hal_btn.h:

#pragma once

#include "driver/gpio.h"

#ifdef __cplusplus
extern "C"
{
#endif
    typedef enum
    {
        UP = 0,
        DOWN,
        FLOATING
    } pull_mode_t;
    typedef void (*btn_cb_t)(void);
    void btn_init(gpio_num_t gpio_num, pull_mode_t pull_mode);
    void btn_set_disable(bool disable);
    void btn_send_level(void);
    int btn_get_level(gpio_num_t gpio_num);
    void btn_set_callback(btn_cb_t btn_cb);

#ifdef __cplusplus
}
#endif

2.lv_event_send 的使用

虽然通过上面的按键操作也可以实现通过点击back按钮返回上一页面的功能,但这种点击方式更常用于以触摸屏作为输入设备的时候,而在以外部按键作为输入设备的工程中,通常会有一个单独的实体按键来固定返回之前页面这一功能,而不会在ui上显示这一虚拟按钮,这是便需要使用lv_event_send 来手动发送事件。

比如,我们在一个实体按键的按键回调中使用lv_event_send:

static void button_short_down_cb(void *arg, void *data)
{
    ESP_LOGI(TAG, "BTN BUTTON_short_DOWN");
    lv_obj_t *root = lv_obj_get_child(lv_scr_act(), NULL);/*获得当前页面创建的第一个控件(按创建顺序)*/

    lv_event_send(root, LV_EVENT_CANCEL, NULL);//手动发送LV_EVENT_CANCEL事件

}

 在滑动条的事件中添加LV_EVENT_CANCEL事件:

case LV_EVENT_CANCEL:
	{
        //切换页面
		//Write the load screen code.
	    lv_obj_t * act_scr = lv_scr_act();
	    lv_disp_t * d = lv_obj_get_disp(act_scr);
	    if (d->prev_scr == NULL && (d->scr_to_load == NULL || d->scr_to_load == act_scr)) {
			lv_obj_clean(act_scr);
	        if (guider_ui.main_del == true) {
	          setup_scr_main(&guider_ui);
	        }
	        lv_scr_load_anim(guider_ui.main, LV_SCR_LOAD_ANIM_NONE, 200, 200, true);
	        guider_ui.main_del = true;
	    }
		break;

这样我们按下相应的实体按键即可返回之前的页面,而不再需要通过back按钮的点击事件了(滑动条控件需要在其所在页面被第一个创建)。


总结

本文主要简述了以按键作为lvgl输入设备在esp32s3上的应用。

  • 11
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值