How to use esp32 hardware button to implement on lv_event_get_target(e)

Description

What MCU/Processor/Board and compiler are you using?

I am using T-Embed esp32s3 and platformio as compiler.

What LVGL version are you using?

I am using the latest version 9.2.2

What do you want to achieve?

I want to use external/hardware buttons to interact with the slider button, etc.

What have you tried so far?

For now I have read the documentation on getting started to setup lvgl on esp32.
I did watched some videos but nothing much help me. The only thing I couldn’t understand is the LV_EVENT_… and LV_EVENT_GET_TARGET(E). How to trigger it or atleast call the callback function.

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

The code block(s) should be formatted like:

#include <lvgl.h>
#include <TFT_eSPI.h>
#include "pin_config.h"
#include <WiFiClientSecure.h>
#include <SD.h>
#include <SD_MMC.h>
#include <FFat.h>
#include "lv_conf.h"
#include <demos/lv_demos.h>
#include <examples/lv_examples.h>
#include <RotaryEncoder.h>
#include "OneButton.h"

/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
#define DRAW_BUF_SIZE (LV_SCREEN_WIDTH * LV_SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
#define TFT_ROTATION LV_DISP_ROTATION_270

void *draw_buf;
unsigned long lastTickMillis = 0;

TFT_eSPI tft = TFT_eSPI();

OneButton button;

static void lv_disp_flush(lv_disp_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
  lv_disp_flush_ready(disp);
}


static int increment_value = 0;

void onButtonPress()
{
  increment_value++; // Increment the value
  Serial.print("Button pressed, value: ");
  Serial.println(increment_value);
}

// LVGL input driver callback function
void button_read(lv_indev_t *indev, lv_indev_data_t *data)
{
  static uint32_t last_btn = 0; /*Store the last pressed button*/
  int btn_pr = my_btn_read();   /*Get the ID (0,1,2...) of the pressed button*/
  if (btn_pr >= 0)
  {                                       /*Is there a button press? (E.g. -1 indicated no button was pressed)*/
    last_btn = btn_pr;                    /*Save the ID of the pressed button*/
    data->state = LV_INDEV_STATE_PRESSED; /*Set the pressed state*/
  }
  else
  {
    data->state = LV_INDEV_STATE_RELEASED; /*Set the released state*/
  }

  data->btn_id = last_btn; /*Save the last button*/
}

void setup()
{
  String LVGL_Arduino = "LVGL ";
  LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

  Serial.begin(115200);

  pinMode(PIN_POWER_ON, OUTPUT);
  pinMode(PIN_ENCODE_BTN, INPUT_PULLUP);
  digitalWrite(PIN_POWER_ON, HIGH);

  lv_init();

  draw_buf = heap_caps_malloc(DRAW_BUF_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);

  lv_display_t *disp = lv_tft_espi_create(LV_SCREEN_HEIGHT, LV_SCREEN_WIDTH, draw_buf, DRAW_BUF_SIZE);
  lv_display_set_rotation(disp, (lv_display_rotation_t)TFT_ROTATION);
  // lv_display_set_flush_cb(disp, my_flush_cb);

  lv_indev_t *indev = lv_indev_create();          /* Create input device connected to Default Display. */
  lv_indev_set_type(indev, LV_INDEV_TYPE_BUTTON); /* Touch pad is a pointer-like device. */
  lv_indev_set_read_cb(indev, button_read);       /* Set driver function. */

  button.setup(PIN_ENCODE_BTN, INPUT_PULLUP, true);
  button.attachClick(onButtonPress);
  // lv_demo_benchmark();
  lv_example_event_click();
}

void loop()
{
  lv_timer_handler(); /* let the GUI do its work */
  unsigned int tickPeriod = millis() - lastTickMillis;
  lv_tick_inc(tickPeriod);
  lastTickMillis = millis();
  button.tick();
  delay(10);
}

static void event_cb(lv_event_t *e)
{
  LV_LOG_USER("Clicked");

  static uint32_t cnt = 1;
  lv_obj_t *btn = (lv_obj_t *)lv_event_get_target(e);
  lv_obj_t *label = lv_obj_get_child(btn, 0);
  lv_label_set_text_fmt(label, "%" LV_PRIu32, cnt);
  cnt++;
}

/**
 * Add click event to a button
 */

int my_btn_read()
{
  return increment_value;
}

void lv_example_event_click(void)
{
  lv_obj_t *btn = lv_button_create(lv_screen_active());
  lv_obj_set_size(btn, 100, 50);
  lv_obj_center(btn);
  lv_obj_add_event_cb(btn, event_cb, LV_EVENT_CLICKED, NULL);

  lv_obj_t *label = lv_label_create(btn);
  lv_label_set_text(label, "Click me!");
  lv_obj_center(label);
}

This is what I have for now. I will still continue to read and understand the documentation. But for a while I leave this here. Thank You!

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

How much buttons you need to use?

As I see in your code you want to use LV_INDEV_TYPE_BUTTON, so my_btn_read() should return pressed button code. Even in your code there is comment

/*Get the ID (0,1,2...) of the pressed button*/

So if you pressed button 0 my_btn_read() should return 0, if button 1 pressed my_btn_read() should return 1, etc. If there is no pressed buttons - return negative value. But in your code my_btn_read() returns increment_value which is incremented every time button pressed - this is wrong.

In your example - you use single button. So my_btn_read() should return 0 if button pressed and negative if unpressed.

You also have to think about what code to return if multiple buttons are pressed at the same time.

1 Like

Also you have to set points_array to assign buttons with specific coordinates of the screen. In this case pressing a physical button will simulate pressing at some point on the screen.

1 Like
int my_btn_read()
{
  // Read the current state of the button
  int currentButtonState = digitalRead(PIN_ENCODE_BTN);

  // Check if the button is pressed (LOW state, because we're using INPUT_PULLUP)
  if (currentButtonState == LOW && lastButtonState == HIGH)
  {
    // Button was just pressed
    buttonState = 1; // Button pressed
    // Serial.println("Button Pressed");
  }
  // Check if the button is released (HIGH state)
  else if (currentButtonState == HIGH && lastButtonState == LOW)
  {
    // Button was just released
    buttonState = -1; // Button released
    // Serial.println("Button Released");
  }

  // Save the current state for the next loop iteration
  lastButtonState = currentButtonState;

  return buttonState;
}

Forgot to remove that, This is what I did basically

I kinda understand a bit of this. But my button still not getting callback or trigger. Maybe something with my initialize?

did you associate hw-button with ui-button-widget on the screen ?

Not sure what you mean by that. I followed the instruction to initialize input device and use the example button.

  lv_indev_t *indev = lv_indev_create();          /* Create input device connected to Default Display */
  lv_indev_set_type(indev, LV_INDEV_TYPE_BUTTON); /* Button input type */
  lv_indev_set_read_cb(indev, button_read);       /* Set the read callback function */
  const lv_point_t points_array[] = {{100, 50}};
  lv_indev_set_button_points(indev, points_array);

#if LV_BUILD_EXAMPLES && LV_USE_SWITCH

static void event_cb(lv_event_t * e)
{
    LV_LOG_USER("Clicked");

    static uint32_t cnt = 1;
    lv_obj_t * btn = lv_event_get_target(e);
    lv_obj_t * label = lv_obj_get_child(btn, 0);
    lv_label_set_text_fmt(label, "%"LV_PRIu32, cnt);
    cnt++;
}

/**
 * Add click event to a button
 */
void lv_example_event_click(void)
{
    lv_obj_t * btn = lv_button_create(lv_screen_active());
    lv_obj_set_size(btn, 100, 50);
    lv_obj_center(btn);
    lv_obj_add_event_cb(btn, event_cb, LV_EVENT_CLICKED, NULL);

    lv_obj_t * label = lv_label_create(btn);
    lv_label_set_text(label, "Click me!");
    lv_obj_center(label);
}

#endif

I use this as a test to see if I will trigger the button

I suggest set points_array value as center of btn-object. As far as I understand btn located at the center of the screen, so i think it should be SCREEN_WIDTH/2, SCREEN_HEIGHT/2

Hey man, I finally figured it out anyways thanks for the help. I use the 8.3.x version because I see it mostly used on youtube video.

This example code below is reference for people finding the same problem as me.

LVGL version 8.3.3
USING EXTERNAL BUTTON TO TRIGGER EVENT CODE BELOW:
#include <lvgl.h>
#include "pin_config.h"
#include "lv_conf.h"
#include <demos/lv_demos.h>
#include <examples/lv_examples.h>
#include <TFT_eSPI.h>
#include <OneButton.h>

#define TEST_DEBUG 0
#define DEMO_BM 0

/*Change to your screen resolution*/
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 170;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];

lv_obj_t *btn; // Declare the button object globally

TFT_eSPI tft = TFT_eSPI(); /* TFT instance */

OneButton button;

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
  uint32_t w = (area->x2 - area->x1 + 1);
  uint32_t h = (area->y2 - area->y1 + 1);

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp_drv);
}

#if LV_USE_LOG != 0
/* Serial debugging */
void my_print(const char *buf)
{
  Serial.printf(buf);
  Serial.flush();
}
#endif

void Click()
{
  LV_LOG_USER("x2");

  lv_event_send(btn, LV_EVENT_CLICKED, NULL);

} // doubleClick

void setup()
{
  Serial.begin(115200); /* prepare for possible serial debug */

  pinMode(PIN_POWER_ON, OUTPUT);
  digitalWrite(PIN_POWER_ON, HIGH);

  button.setup(PIN_ENCODE_BTN, INPUT_PULLUP, true);

  button.attachClick(Click);

  lv_init();

  tft.begin();        /* TFT init */
  tft.setRotation(3); /* Landscape orientation, flipped */

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

#if LV_USE_LOG != 0
  lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif

  /*Initialize the display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  /*Change the following line to your display resolution*/
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

#if 0
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);         /*Basic initialization*/
  indev_drv.type = LV_INDEV_TYPE_BUTTON; /*See below.*/
  indev_drv.read_cb = button_read;     /*See below.*/
  /*Register the driver in LVGL and save the created input device object*/
  lv_indev_t *my_indev = lv_indev_drv_register(&indev_drv);
#endif
#if TEST_DEBUG

  String LVGL_Arduino = "Hello Arduino! ";
  LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

  Serial.println(LVGL_Arduino);
  Serial.println("I am LVGL_Arduino");

  /* Create simple label */
  lv_obj_t *label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Hello Ardino and LVGL!");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

#endif
#if DEMO_BM

  lv_demo_benchmark();

#endif

  // lv_example_anim_2();
  lv_example_get_started_1();

  Serial.println("Setup done");
}

void loop()
{
  lv_timer_handler(); /* let the GUI do its work */
  button.tick();
  delay(5);
}

#if 0 
void button_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
  static uint32_t last_btn = 0; /*Store the last pressed button*/
  int btn_pr = my_btn_read();   /*Get the ID (0,1,2...) of the pressed button*/
  if (btn_pr >= 0)
  {                                       /*Is there a button press? (E.g. -1 indicated no button was pressed)*/
    last_btn = btn_pr;                    /*Save the ID of the pressed button*/
    data->state = LV_INDEV_STATE_PRESSED; /*Set the pressed state*/
  }
  else
  {
    data->state = LV_INDEV_STATE_RELEASED; /*Set the released state*/
  }

  data->btn_id = last_btn; /*Save the last button*/
}
#endif

static void btn_event_cb(lv_event_t *e)
{
  lv_event_code_t code = lv_event_get_code(e);
  btn = lv_event_get_target(e);
  if (code == LV_EVENT_CLICKED)
  {
    static uint8_t cnt = 0;
    cnt++;

    /*Get the first child of the button which is the label and change its text*/
    lv_obj_t *label = lv_obj_get_child(btn, 0);
    lv_label_set_text_fmt(label, "Button: %d", cnt);
  }
}

/**
 * Create a button with a label and react on click event.
 */
void lv_example_get_started_1(void)
{
  btn = lv_btn_create(lv_scr_act());                              /*Add a button the current screen*/
  lv_obj_set_pos(btn, 10, 10);                                    /*Set its position*/
  lv_obj_set_size(btn, 120, 50);                                  /*Set its size*/
  lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL); /*Assign a callback to the button*/

  lv_obj_t *label = lv_label_create(btn); /*Add a label to the button*/
  lv_label_set_text(label, "Button");     /*Set the labels text*/
  lv_obj_center(label);
}

USING ROTARY ENCODER TO TRIGGER EVENT CODE BELOW:
#include <lvgl.h>
#include "pin_config.h"
#include "lv_conf.h"
#include <demos/lv_demos.h>
#include <examples/lv_examples.h>
#include <TFT_eSPI.h>
#include <OneButton.h>
#include <RotaryEncoder.h>

#define TEST_DEBUG 0
#define DEMO_BM 0

/*Change to your screen resolution*/
static const uint16_t screenWidth = 320;
static const uint16_t screenHeight = 170;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];

lv_obj_t *btn; // Declare the button object globally
lv_obj_t *slider_label;
lv_obj_t *slider;

TFT_eSPI tft = TFT_eSPI(); /* TFT instance */

OneButton button;

RotaryEncoder *encoder = nullptr;

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
  uint32_t w = (area->x2 - area->x1 + 1);
  uint32_t h = (area->y2 - area->y1 + 1);

  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t *)&color_p->full, w * h, true);
  tft.endWrite();

  lv_disp_flush_ready(disp_drv);
}

#if LV_USE_LOG != 0
/* Serial debugging */
void my_print(const char *buf)
{
  Serial.printf(buf);
  Serial.flush();
}
#endif

void Click()
{
  LV_LOG_USER("x2");

  lv_event_send(btn, LV_EVENT_CLICKED, NULL);

} // doubleClick

void checkPosition()
{
  encoder->tick(); // just call tick() to check the state.
}

void encoder_read()
{
  int16_t encoderDelta = encoder->getPosition(); // Get the encoder's delta (how much it has rotated)
  // Update the slider based on encoder movement

  if (encoderDelta > 100)
  {
    encoder->setPosition(100); // Stop the position at 100
    encoderDelta = 100;        // Update the local position variable
  }
  else if (encoderDelta < 0)
  {
    encoder->setPosition(0); // Stop the position at 0
    encoderDelta = 0;        // Update the local position variable
  }

  if (encoderDelta >= 0 && encoderDelta <= 100)
  {
    // Get the current slider value
    int currentValue = encoderDelta;

    LV_LOG_USER(currentValue);

    lv_slider_set_value(slider, currentValue, LV_ANIM_OFF); // Update the slider value
    lv_label_set_text_fmt(slider_label, "%d%%", currentValue);
  }
}

void setup()
{
  Serial.begin(115200); /* prepare for possible serial debug */

  pinMode(PIN_POWER_ON, OUTPUT);
  digitalWrite(PIN_POWER_ON, HIGH);

  button.setup(PIN_ENCODE_BTN, INPUT_PULLUP, true);

  button.attachClick(Click);

  encoder = new RotaryEncoder(PIN_ENCODE_A, PIN_ENCODE_B, RotaryEncoder::LatchMode::TWO03);

  // register interrupt routine
  attachInterrupt(digitalPinToInterrupt(PIN_ENCODE_A), checkPosition, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_ENCODE_B), checkPosition, CHANGE);

  lv_init();

  tft.begin();        /* TFT init */
  tft.setRotation(3); /* Landscape orientation, flipped */

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);

#if LV_USE_LOG != 0
  lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif

  /*Initialize the display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  /*Change the following line to your display resolution*/
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

#if 0
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);          /*Basic initialization*/
  indev_drv.type = LV_INDEV_TYPE_ENCODER; /*See below.*/
  indev_drv.read_cb = encoder_read;       /*See below.*/
  /*Register the driver in LVGL and save the created input device object*/
  lv_indev_t *my_indev = lv_indev_drv_register(&indev_drv);
#endif
#if TEST_DEBUG

  String LVGL_Arduino = "Hello Arduino! ";
  LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

  Serial.println(LVGL_Arduino);
  Serial.println("I am LVGL_Arduino");

  /* Create simple label */
  lv_obj_t *label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Hello Ardino and LVGL!");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

#endif
#if DEMO_BM

  lv_demo_benchmark();

#endif
  lv_example_slider_1();
  Serial.println("Setup done");
}

void loop()
{
  lv_timer_handler(); /* let the GUI do its work */
  button.tick();
  encoder->tick();
  encoder_read();
  delay(5);
}

#if 0 
void button_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
  static uint32_t last_btn = 0; /*Store the last pressed button*/
  int btn_pr = my_btn_read();   /*Get the ID (0,1,2...) of the pressed button*/
  if (btn_pr >= 0)
  {                                       /*Is there a button press? (E.g. -1 indicated no button was pressed)*/
    last_btn = btn_pr;                    /*Save the ID of the pressed button*/
    data->state = LV_INDEV_STATE_PRESSED; /*Set the pressed state*/
  }
  else
  {
    data->state = LV_INDEV_STATE_RELEASED; /*Set the released state*/
  }

  data->btn_id = last_btn; /*Save the last button*/
}
#endif

void lv_example_slider_1(void)
{
  /*Create a slider in the center of the display*/
  slider = lv_slider_create(lv_scr_act());
  lv_obj_center(slider);
  lv_slider_set_range(slider, 0, 100); // Set slider range
  lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

  /*Create a label below the slider*/
  slider_label = lv_label_create(lv_scr_act());
  lv_label_set_text(slider_label, "0%");
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}

static void slider_event_cb(lv_event_t *e)
{
  slider = lv_event_get_target(e);
  char buf[8];
  lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
  lv_label_set_text(slider_label, buf);
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}