Skip to content

Commit

Permalink
esp_lvgl_port: Add support for buttons as an input device.
Browse files Browse the repository at this point in the history
  • Loading branch information
espzav committed Feb 17, 2023
1 parent 5e168be commit 71fae16
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 8 deletions.
5 changes: 5 additions & 0 deletions components/esp_lvgl_port/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
idf_component_register(SRCS "esp_lvgl_port.c" INCLUDE_DIRS "include" REQUIRES "esp_lcd" PRIV_REQUIRES "esp_timer")

idf_build_get_property(build_components BUILD_COMPONENTS)
if("espressif__button" IN_LIST build_components)
target_link_libraries(${COMPONENT_LIB} PRIVATE idf::espressif__button)
endif()
63 changes: 58 additions & 5 deletions components/esp_lvgl_port/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This part is necessary only in IDF 5.0 and older:
static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_t ** disp = (lv_disp_t **)user_ctx;
lvgl_port_flush_ready(*disp);
lvgl_port_flush_ready((*disp)->driver);
return false;
}

Expand Down Expand Up @@ -76,9 +76,12 @@ Main part of the code (IDF version independent):
}
};
disp = lvgl_port_add_disp(&disp_cfg);
```
/* ... the rest of the initialization ... */
**Note:** The screens added in this function are not removed in `lvgl_port_deinit`. They must be removed by `lvgl_port_remove_disp` before deinitialization. Otherwise, there can be memory leaks!
/* If deinitializing LVGL port, remember to delete all displays: */
lvgl_port_remove_disp(disp);
```

### Add touch input

Expand All @@ -94,10 +97,60 @@ Add touch input to the LVGL. It can be called more times for adding more touch i
.disp = disp_spi,
.handle = tp,
};
lvgl_port_add_touch(&touch_cfg);
lv_indev_t* touch_handle = lvgl_port_add_touch(&touch_cfg);

/* ... the rest of the initialization ... */

/* If deinitializing LVGL port, remember to delete all touches: */
lvgl_port_remove_touch(touch_handle);
```
### Add buttons input
Add buttons input to the LVGL. It can be called more times for adding more buttons inputs for different displays. This feature is available only when the component `espressif/button` was added into the project.
``` c
/* Buttons configuration structure */
const button_config_t bsp_button_config[] = {
{
.type = BUTTON_TYPE_ADC,
.adc_button_config.adc_channel = ADC_CHANNEL_0, // ADC1 channel 0 is GPIO1
.adc_button_config.button_index = 0,
.adc_button_config.min = 2310, // middle is 2410mV
.adc_button_config.max = 2510
},
{
.type = BUTTON_TYPE_ADC,
.adc_button_config.adc_channel = ADC_CHANNEL_0, // ADC1 channel 0 is GPIO1
.adc_button_config.button_index = 1,
.adc_button_config.min = 1880, // middle is 1980mV
.adc_button_config.max = 2080
},
{
.type = BUTTON_TYPE_ADC,
.adc_button_config.adc_channel = ADC_CHANNEL_0, // ADC1 channel 0 is GPIO1
.adc_button_config.button_index = 2,
.adc_button_config.min = 720, // middle is 820mV
.adc_button_config.max = 920
},
};
const lvgl_port_nav_btns_cfg_t btns = {
.disp = disp_spi,
.button_prev = &bsp_button_config[0],
.button_next = &bsp_button_config[1],
.button_enter = &bsp_button_config[2]
};
/* Add buttons input (for selected screen) */
lv_indev_t* buttons_handle = lvgl_port_add_navigation_buttons(&btns);
/* ... the rest of the initialization ... */
/* If deinitializing LVGL port, remember to delete all buttons: */
lvgl_port_remove_navigation_buttons(buttons_handle);
```

**Note:** The inputs added in this function are not removed in `lvgl_port_deinit`. They must be removed by `lvgl_port_remove_touch` before deinitialization. Otherwise, there can be memory leaks!
**Note:** When you use navigation buttons for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.

### LVGL API usage

Expand Down
186 changes: 184 additions & 2 deletions components/esp_lvgl_port/esp_lvgl_port.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ typedef struct {
} lvgl_port_touch_ctx_t;
#endif

#if __has_include ("iot_button.h")

typedef enum {
LVGL_PORT_NAV_BTN_PREV,
LVGL_PORT_NAV_BTN_NEXT,
LVGL_PORT_NAV_BTN_ENTER,
LVGL_PORT_NAV_BTN_CNT,
} lvgl_port_nav_btns_t;

typedef struct {
button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */
lv_indev_drv_t indev_drv; /* LVGL input device driver */
bool btn_prev; /* Button prev state */
bool btn_next; /* Button next state */
bool btn_enter; /* Button enter state */
} lvgl_port_nav_btns_ctx_t;
#endif

/*******************************************************************************
* Local variables
*******************************************************************************/
Expand All @@ -78,6 +96,11 @@ static void lvgl_port_update_callback(lv_disp_drv_t *drv);
#if __has_include ("esp_lcd_touch.h")
static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
#endif
#if __has_include ("iot_button.h")
static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_btn_down_handler(void *arg, void *arg2);
static void lvgl_port_btn_up_handler(void *arg, void *arg2);
#endif
static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa);
/*******************************************************************************
* Public API functions
Expand Down Expand Up @@ -297,6 +320,89 @@ esp_err_t lvgl_port_remove_touch(lv_indev_t *touch)
}
#endif

#if __has_include ("iot_button.h")
lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg)
{
esp_err_t ret = ESP_OK;
lv_indev_t *indev = NULL;
assert(buttons_cfg != NULL);
assert(buttons_cfg->disp != NULL);

/* Touch context */
lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t));
if (buttons_ctx == NULL) {
ESP_LOGE(TAG, "Not enough memory for buttons context allocation!");
return NULL;
}

/* Previous button */
if (buttons_cfg->button_prev != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}

/* Next button */
if (buttons_cfg->button_next != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}

/* Enter button */
if (buttons_cfg->button_enter != NULL) {
buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter);
ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!");
}

/* Button handlers */
for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) {
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, buttons_ctx));
ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx));
}

buttons_ctx->btn_prev = false;
buttons_ctx->btn_next = false;
buttons_ctx->btn_enter = false;

/* Register a touchpad input device */
lv_indev_drv_init(&buttons_ctx->indev_drv);
buttons_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER;
buttons_ctx->indev_drv.disp = buttons_cfg->disp;
buttons_ctx->indev_drv.read_cb = lvgl_port_navigation_buttons_read;
buttons_ctx->indev_drv.user_data = buttons_ctx;
buttons_ctx->indev_drv.long_press_repeat_time = 300;
indev = lv_indev_drv_register(&buttons_ctx->indev_drv);

err:
if (ret != ESP_OK) {
for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) {
if (buttons_ctx->btn[i] != NULL) {
iot_button_delete(buttons_ctx->btn[i]);
}
}

if (buttons_ctx != NULL) {
free(buttons_ctx);
}
}

return indev;
}

esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons)
{
assert(buttons);
lv_indev_drv_t *indev_drv = buttons->driver;
assert(indev_drv);
lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data;

if (buttons_ctx) {
free(buttons_ctx);
}

return ESP_OK;
}
#endif

bool lvgl_port_lock(uint32_t timeout_ms)
{
assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first");
Expand Down Expand Up @@ -402,7 +508,11 @@ static void lvgl_port_update_callback(lv_disp_drv_t *drv)
case LV_DISP_ROT_90:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
}
break;
case LV_DISP_ROT_180:
/* Rotate LCD display */
Expand All @@ -412,7 +522,11 @@ static void lvgl_port_update_callback(lv_disp_drv_t *drv)
case LV_DISP_ROT_270:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
}
break;
}
}
Expand Down Expand Up @@ -462,6 +576,74 @@ static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *
}
#endif

#if __has_include ("iot_button.h")
static uint32_t last_key = 0;
static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
assert(indev_drv);
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data;
assert(ctx);

/* Buttons */
if (ctx->btn_prev) {
data->key = LV_KEY_LEFT;
last_key = LV_KEY_LEFT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (ctx->btn_next) {
data->key = LV_KEY_RIGHT;
last_key = LV_KEY_RIGHT;
data->state = LV_INDEV_STATE_PRESSED;
} else if (ctx->btn_enter) {
data->key = LV_KEY_ENTER;
last_key = LV_KEY_ENTER;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->key = last_key;
data->state = LV_INDEV_STATE_RELEASED;
}
}

static void lvgl_port_btn_down_handler(void *arg, void *arg2)
{
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* PREV */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) {
ctx->btn_prev = true;
}
/* NEXT */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) {
ctx->btn_next = true;
}
/* ENTER */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) {
ctx->btn_enter = true;
}
}
}

static void lvgl_port_btn_up_handler(void *arg, void *arg2)
{
lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2;
button_handle_t button = (button_handle_t)arg;
if (ctx && button) {
/* PREV */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) {
ctx->btn_prev = false;
}
/* NEXT */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) {
ctx->btn_next = false;
}
/* ENTER */
if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) {
ctx->btn_enter = false;
}
}
}
#endif

static void lvgl_port_tick_increment(void *arg)
{
/* Tell LVGL how many milliseconds have elapsed */
Expand Down
2 changes: 1 addition & 1 deletion components/esp_lvgl_port/idf_component.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: "1.0.5"
version: "1.1.0"
description: ESP LVGL port
url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port
dependencies:
Expand Down
39 changes: 39 additions & 0 deletions components/esp_lvgl_port/include/esp_lvgl_port.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include "esp_lcd_touch.h"
#endif

#if __has_include ("iot_button.h")
#include "iot_button.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif
Expand Down Expand Up @@ -73,6 +77,18 @@ typedef struct {
} lvgl_port_touch_cfg_t;
#endif

#if __has_include ("iot_button.h")
/**
* @brief Configuration of the navigation buttons structure
*/
typedef struct {
lv_disp_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */
const button_config_t *button_prev; /*!< Navigation button for previous */
const button_config_t *button_next; /*!< Navigation button for next */
const button_config_t *button_enter; /*!< Navigation button for enter */
} lvgl_port_nav_btns_cfg_t;
#endif

/**
* @brief LVGL port configuration structure
*
Expand Down Expand Up @@ -152,6 +168,29 @@ lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg);
esp_err_t lvgl_port_remove_touch(lv_indev_t *touch);
#endif


#if __has_include ("iot_button.h")
/**
* @brief Add buttons as an input device
*
* @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_navigation_buttons for free all memory!
*
* @param buttons_cfg Buttons configuration structure
* @return Pointer to LVGL buttons input device or NULL when error occured
*/
lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg);

/**
* @brief Remove selected buttons from input devices
*
* @note Free all memory used for this input device.
*
* @return
* - ESP_OK on success
*/
esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons);
#endif

/**
* @brief Take LVGL mutex
*
Expand Down

0 comments on commit 71fae16

Please sign in to comment.