系列文章目录
ESP32系列之LVGL(一):ESP32S3+ST7789点屏
ESP32系列之LVGL(二):ESP32S3移植LVGL(v8.3)
目录
前言
由于屏幕没有触摸屏,所以在工程上只能使用外部实体按键来作为输入设备对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上的应用。