外部按键对接之ESP32

一、任务目标

ui创建有三个button的页面,通过外部实体按键可在这三个button间实现聚焦与切换,点击button切换至有滑动条的页面,通过按键使滑动条数值增加。

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

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

2)控件选择(LV_KEY_ENTER)

3)控件数值变化(LV_KEY_RIGHT)

二、准备工作

芯片平台:ESP32S3 LVGL版本:V8.3 ESP-IDF版本:v5.2

  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 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 
/**
* @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(这里原理差不多,后边自己使用了MQTT传送过来也是可行)

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/**
* @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 的实现

1
2
3
4
5
6
7
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控件添加进这个组中,并将输入设备的句柄与组相关联。

1
2
3
4
/*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()中添加默认组,这样后续创建的控件将会自动加入默认组。

1
2
3
4
5
6
7
8
9
10
11
12
   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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#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();
}
}

六、其它

1.按键驱动参考

hal_btn.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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:

1
2
3
4
5
6
7
8
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事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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按钮的点击事件了(滑动条控件需要在其所在页面被第一个创建)。

演示效果

esp32_keypad_indev


相关链接(侵删)

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

=================我是分割线=================

欢迎到公众号来唠嗑: