diff --git a/components/esp_lvgl_port/CMakeLists.txt b/components/esp_lvgl_port/CMakeLists.txt index d4a97bb4..6f24e0c9 100644 --- a/components/esp_lvgl_port/CMakeLists.txt +++ b/components/esp_lvgl_port/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/components/esp_lvgl_port/README.md b/components/esp_lvgl_port/README.md index ea3ae78f..9a54d7cf 100644 --- a/components/esp_lvgl_port/README.md +++ b/components/esp_lvgl_port/README.md @@ -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; } @@ -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 @@ -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 diff --git a/components/esp_lvgl_port/esp_lvgl_port.c b/components/esp_lvgl_port/esp_lvgl_port.c index 9a2ce332..fd713974 100644 --- a/components/esp_lvgl_port/esp_lvgl_port.c +++ b/components/esp_lvgl_port/esp_lvgl_port.c @@ -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 *******************************************************************************/ @@ -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 @@ -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"); @@ -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 */ @@ -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; } } @@ -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 */ diff --git a/components/esp_lvgl_port/idf_component.yml b/components/esp_lvgl_port/idf_component.yml index 277f3524..c4f88ea8 100644 --- a/components/esp_lvgl_port/idf_component.yml +++ b/components/esp_lvgl_port/idf_component.yml @@ -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: diff --git a/components/esp_lvgl_port/include/esp_lvgl_port.h b/components/esp_lvgl_port/include/esp_lvgl_port.h index 9fd036e0..ba12100a 100644 --- a/components/esp_lvgl_port/include/esp_lvgl_port.h +++ b/components/esp_lvgl_port/include/esp_lvgl_port.h @@ -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 @@ -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 * @@ -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 *