ESP32 External Button Not working

Hi Everyone,

I’m trying to use an external button using LVGL and ESP32. There is no specific project I only would like to test this feature for learning purposes.

I’m using Arduino IDE 2.0 and LVGL library v8. For display, I’m using 240x240 GMT130-V1 and TFT_eSPI to drive it.

So far, I’m able to create and display a simple screen containing one LED, one Button and one label. The display works just fine.

The next step is to use an external push button - the “BOOT” button on the ESP32 Doit-Dev Kit - as an external button to simulate the press of the on-screen button.

I’ve followed the steps in LVGL academy - which was for LVGL v7 - as well as the code present in “lv_port_indev.c” and the steps mentioned in LVGL documentation. However, the external button state doesn’t change. I’m suspecting that the call-back function is not being called periodically.

I tried reading the state of the push button from the “loop” function and try to change the LED state based on that. And this worked just fine. However, when using the callback function and the procedure mentioned in the documentation\examples, it never worked.

Your suggestions\Input is highly appreciated.

My Arduino code:


//Included libraries
#include <TFT_eSPI.h>       
#include <lvgl.h>           
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int button_pin = 0; // The button used as an external button - "BOOT" button on ESP32 dev kit
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//For LVGL
static const uint16_t screenWidth  = 240;
static const uint16_t screenHeight = 240;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[ screenWidth * 10 ];

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight);  // Invoke custom library

/* Display flushing */  //Not sure if required, need testing
void my_disp_flush( lv_disp_drv_t *disp, 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 );
}


//++++++++++++++++++++++++++++++++++++++++++++++//
//     LVGL global variables for testing
//++++++++++++++++++++++++++++++++++++++++++++++//
  lv_obj_t * btn1;
  lv_obj_t * led1;
  lv_indev_t * indev_button_1;
  

/*Initialize your buttons*/

static void button_init_1(void)
{
    pinMode(button_pin, INPUT);
}

/*Will be called by the library to read the button*/
static void button_read_1(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
  //Test Code:-
  int8_t btn_act = button_get_pressed_id_1();

  if(btn_act >= 0) // If any button is pressed
  {
    data->state = LV_INDEV_STATE_PR;
    lv_led_on(led1);
    lv_label_set_text(labelCounter, "Something Pressed !!!"); //For  debugging
  }

  else
  {
    data->state = LV_INDEV_STATE_REL;
    lv_led_off(led1);
    lv_label_set_text(labelCounter, "Nothing Pressed");//For  debugging
  }

  //Original Code Commented for easier testing:-
  /*

    static uint8_t last_btn = 0;

    //Get the pressed button's ID
    int8_t btn_act = button_get_pressed_id_1();

    if(btn_act >= 0) {
        data->state = LV_INDEV_STATE_PR;
        last_btn = btn_act;
    }
    else {
        data->state = LV_INDEV_STATE_REL;
    }

    //Save the last pressed button's ID
    data->btn_id = last_btn;
    */
}

/*Get ID  (0, 1, 2 ..) of the pressed button*/
static int8_t button_get_pressed_id_1(void)
{
    uint8_t i;

    /*Check the buttons see which is being pressed (i = number of buttons)*/
    for(i = 0; i < 1; i++) {
        /*Return the pressed button's ID*/
        if(button_is_pressed_1(i)) {
            return i;
        }
    }

    /*No button pressed*/
    return -1;
}

/*Test if `id` button is pressed or not*/
static bool button_is_pressed_1(uint8_t id)
{

    if(id >= 0)
    {
      lv_led_on(led1);
      //char * pressedID;
      //pressedID = char(id) + " is Pressed";
      lv_label_set_text(labelCounter, "Something Pressed !!!"); //For  debugging
    }
    else if(id == -1)
    {
      lv_led_off(led1);
      lv_label_set_text(labelCounter, "Nothing Pressed");//For  debugging
    }
 
    return false;
}

//----------------------------------------------------------------------------------------//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
void setup(void) {

  //LVGL code:-
    lv_init();
    tft.begin();          /* TFT init */
    tft.setRotation( 3 ); /* Landscape orientation, flipped */
    lv_disp_draw_buf_init( &draw_buf, buf, NULL, screenWidth * 10 );

    /*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 );
	
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++
	
    btn1 = lv_btn_create(lv_scr_act());
    led1 = lv_led_create(lv_scr_act());
    labelCounter = lv_label_create(lv_scr_act());
    lv_obj_align(labelCounter, LV_ALIGN_CENTER, 0, 90);
    lv_label_set_text(labelCounter, counterChar);

    lv_obj_add_event_cb(btn1, btn_event_handler, LV_EVENT_ALL, NULL);
    lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -30);
    lv_obj_set_size(btn1, 180, 60);

    lv_obj_t * label;
    label = lv_label_create(btn1);
    lv_label_set_text(label, "Button");
    lv_obj_center(label);

    //Create one LED to test the external button input
    lv_obj_align(led1, LV_ALIGN_CENTER, 0, -90);
    lv_led_set_color(led1, lv_color_hex(0xff0000));
    lv_led_off(led1); // Make it Off by default

    /*------------------
    * Button
    * -----------------*/

    static lv_indev_drv_t indev_drv_1;

    /*Initialize your button if you have*/
    button_init_1();

    /*Register a button input device*/
    lv_indev_drv_init(&indev_drv_1);
    indev_drv_1.type = LV_INDEV_TYPE_BUTTON;
    indev_drv_1.read_cb = button_read_1;
    indev_button_1 = lv_indev_drv_register(&indev_drv_1);

    /*Assign buttons to points on the screen*/
    static const lv_point_t btn_points[1] = {

        {120, 90} //Button -> x:120; y:90

    };

    lv_indev_set_button_points(indev_button_1, btn_points);

}//End of Setup Function


void loop() 
{
  lv_timer_handler(); /* let the GUI do its work */
}//End of loop Function

Hi,

You should also set data->btn_id in the read_cb. Probably it’s not the problem as btn_id is set 0 by LVGL before calling the read_cb which is the same as what you would set it.

Please try adding something like a lv_led_toggle(led1); in button_read_1 to see it’s really called periodically.

If it’s not called probably you haven’t enabled LVGL’s tick in lv_conf.h. See here.

Thank you for your repose @kisvegabor,

Sorry I forgot to mention that I’ve already enabled LV_TICK_CUSTOM in ‘lv_conf.h’. “millis()” is used which should be correct.

Quoting from lv_conf.h:

/*Use a custom tick source that tells the elapsed time in milliseconds.
 *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
    #define LV_TICK_CUSTOM_INCLUDE "Arduino.h"         /*Header for the system time function*/
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())    /*Expression evaluating to current system time in ms*/
#endif   /*LV_TICK_CUSTOM*/

Adding “lv_led_toggle()” doesn’t work as well. As you mentioned, the callback function doesn’t seems to be called periodically at all. But I’ve noticed that when resetting the esp, the LED blinks for a fraction of a second directly when it boots up. Not sure if this is helpful.

Maybe is it crashing for some reason? Please, check the ESP’s log (serial output).

Hi @kisvegabor

I’ve enabled the logging and set it to “Trace” level.
attached is the log file beginning with the reset of the ESP32. I was pressing the external button few times while logging as well.

  • I’m not sure what exactly to look for in the logs. I searched for “Error” and “Warn” and found nothing.

  • I see “[Trace] (6.125, +6) lv_timer_exec: calling timer callback: 0x400ddb1c (in lv_timer.c line #312)” logged once. Not sure if this help or not.

Could you please let me know what I should look for in the logs ?
ESP32_LVGL_Arduino_LOG_Serial_Output_26Jan23.txt (195.3 KB)

Take a look at this line in the log (near to the end):

[Trace] (16.788, +11)    call_flush_cb: Calling flush_cb on (0;210)(239;219) area with 0x3ffc0068 image pointer         (in lv_refr.c line #1229)

It says: after 16.788 seconds it’s refreshing a screen wide area at y1=210, y2=219. If you check the other similar log entries you will see that the y coordinates are increasing by 10. It’s normal, LVGL is refreshing the screen in 10 line chunks.
But, already 16 seconds elapsed and it haven’t drawn the screen even one time.

So it seems somehow you system is super slooooow. Probably you haven’t seen it becasue when you reset the ESP the screens content is still there from the previous run. But if you turn it off, wait a few seconds, touch the pins of the ESP, turn it on, the screen should have random noise and you should see as it’s updated line-by-line.

Or is it only because of the logs? What if you set WARNING level logging and add only an LV_LOG_USER("flushing: %d, %d, %d, %d", area->x1, area->y1, area->x2, area->y2) in the flush_cb?

BTW, 10 lines of buffer is a little bit small for 240 vertical resolution. I usually recommend 1/10 screen size (so 24 lines), but it doesn’t make such a big difference.

@kisvegabor you’re a genius, I powered the ESP32 off for few seconds then powered it on again. As you mentioned, there was random noise on the display and the screen was being updated slowly line by line.

But I remember I was testing a meter with moving needle previously (just few days ago) and it was updating quickly. Now even when I run that sketch again ( a simple meter with a moving needle) it is slow and doesn’t update quickly.

I’ll have to see what have changed and why it is behaving differently now. I’ve already tested using a second ESP32 and the behavior is the same ( very slow). Maybe I should clean all Arduino and LVGL lib files and start again.

I’ve just suffered too much in pain and tear by issues like these :smiley:

I’m not an Arduino or ESP expert, but having a clean start really can be a good idea.

Hi @kisvegabor,
Thanks for your reply.

There are some advancements which I would like to share with you.

  1. By deleting the old LVGL library files and installing it again - and follow the required steps to set-up the library, now the screen refresh rate is back to normal. It takes less than a second to render the whole screen. I ran the meter and needle sketch and it is working just fine. So I think we can now eliminate the possibility that the external button is actually working fine but the screen isn’t updating.

  2. The issue with the external button detection remain the same. I don’t see anything indicating that the external button callback function “button_read_1()” is being called periodically.

/*Register a button input device*/
    lv_indev_drv_init(&indev_drv_1);
    indev_drv_1.type = LV_INDEV_TYPE_BUTTON;
    indev_drv_1.read_cb = button_read_1;
    indev_button_1 = lv_indev_drv_register(&indev_drv_1);
  1. I thought since the screen is updating without issues, it might be a good idea to use the screen flushing callback function to do some testing. So, I added the external button reading functions with some code to change the LED and a label on the screen whenever the button is pressed and it worked :slight_smile:
    Below is the modified flushing function.
void my_disp_flush( lv_disp_drv_t *disp, 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 );


//Remove the blow code - only for debugging
  if(digitalRead(button_pin) == HIGH)
  {
    lv_label_set_text(labelCounter, "Nothing Pressed");//For  debugging
    lv_led_off(led1);
  }

  else if(digitalRead(button_pin) == LOW)
  {
    lv_label_set_text(labelCounter, "Something Pressed !!!"); //For  debugging
    lv_led_on(led1);
  }
  
}

Conclusion:
I believe the issue might reside somewhere in the external button driver “lv_indev_drv_init” or maybe “lv_tick” not calling the external button callback function. I would appreciate your feedback and guidance to further troubleshoot.

I do not recommend updating any widgets in flush_cb. Asking to redraw widgets during rendering might mess up LVGL.

Here is something very simple you can try:

{
  LV_LOG_USER("%d\n", lv_tick_get()); 
  lv_timer_handler(); //Also try commenting 
}

Do you see the tick is counting the milliseconds at a normal pace?

Hi @kisvegabor,

Thanks for your feedback.

I added the mentioned log and compared lv_tick value against the elapsed time by the log itself. the interval is not always equal but the range is between 1 and 15 mSec. Below is a snap - attached the file for your reference as well.

When commenting lv_timer_handler(), the tickis almost stable at around 8 mSec. The screen only shows random noise in this case.

Do you think it is a good idea to test using older version of LVGL? for example the same version used in LVGL academy tutorials ?
I’m not sure if anyone tried the same combination (hardware\software) I’m using to test external button and it worked for him.

Currently I’m using:
Arduino IDE ver 2.0.
LVGL lib v8.3 (Latest)
ESP32-WROOM-32 - Doit Devkit

Log output:
lv_tick_LOG_timer_Handler_Enabled.txt (118.8 KB)

Quick update:
I have tried using LVGL libraries version 7.11 and 7.5 - with Arduino IDE ver 2.0 - but without success :frowning:

[Info] (18.938, +9) lv_obj_update_layout: Layout update begin (in lv_obj_pos.c line #314)

These lines show that the

  • tick is working
  • lv_timer_handler is working
  • the rendering is working

But I can see why it doesn’t want to call the indev read function.
lv_indev_drv_register should create an lv_timer. Can you debug what happens in that function?
Just add printfs to see if it returns somewhere and please also printf("%p\n", indev_button_1);

Thanks @kisvegabor,

I know this sounds stupid, but where can I find the actual lv_indev_drv_register function where I can add the printfs?

I’m using Arduino IDE 2.0, it takes me only to the definition of that function which is in lv_hal_indev.h.

OK that was a stupid question. I was looking at the header file :sweat_smile:.

Anyway, I’ve added many printfs in the lv_indev_drv_register function as well as two other functions which are being called from lv_indev_drv_register.

I see that the lv_timer_create() ran three times, once for the display, once for the indev and third time for something else. It returned three different pointers for each one.

[Info]	(0.024, +24)	 lv_init: begin 	(in lv_obj.c line #102)
Beggining of lv_timer create function: 
lv_tick_get = 25 
End of lv_timer create function: 
 Returning the new timer pointer = 0x3ffc377c 
Beggining of lv_timer create function: 
lv_tick_get = 577 
End of lv_timer create function: 
 Returning the new timer pointer = 0x3ffc39c8 
[Info]	(0.580, +556)	 lv_obj_create: begin 	(in lv_obj.c line #206)
[Info]	(0.584, +4)	 lv_obj_create: begin 	(in lv_obj.c line #206)
[Info]	(0.590, +6)	 lv_obj_create: begin 	(in lv_obj.c line #206)
Beggining of lv_indev_drv_register function  
creating lv_indev_t pointer 
LV_ASSERT_MALLOC 
LV_ASSERT_MALLOC(indev) successful 
size of lv_indev_t 96  
Beggining of lv_timer create function: 
lv_tick_get = 613 
End of lv_timer create function: 
 Returning the new timer pointer = 0x3ffc453c 
Returning indev, indev p = 0x3ffc44d0  
[Info]	(0.625, +35)	 lv_btn_create: begin 	(in lv_btn.c line #51)
[Info]	(0.632, +7)	 lv_led_create: begin 	(in lv_led.c line #56)
[Info]	(0.637, +5)	 lv_label_create: begin 	(in lv_label.c line #75)
[Info]	(0.645, +8)	 lv_label_create: begin 	(in lv_label.c line #75)
[Info]	(0.651, +6)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	(0.660, +9)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	(0.666, +6)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	(0.673, +7)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[Info]	(0.681, +8)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[User]	(0.747, +66)	 loop: tick value = 747
 	(in ESP32_240LCD_LVGL_Test_3.ino line #297)
[User]	(0.748, +1)	 loop: indev_button_1: 0x3ffc44d0
 	(in ESP32_240LCD_LVGL_Test_3.ino line #298)
[Info]	(0.754, +6)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[User]	(0.768, +14)	 loop: tick value = 768
 	(in ESP32_240LCD_LVGL_Test_3.ino line #297)
[User]	(0.769, +1)	 loop: indev_button_1: 0x3ffc44d0
 	(in ESP32_240LCD_LVGL_Test_3.ino line #298)
[User]	(0.778, +9)	 loop: tick value = 778
 	(in ESP32_240LCD_LVGL_Test_3.ino line #297)
[User]	(0.786, +8)	 loop: indev_button_1: 0x3ffc44d0

.

rest of the log is just a repetition of:

[Info]	(0.919, +9)	 lv_obj_update_layout: Layout update begin 	(in lv_obj_pos.c line #314)
[User]	(0.934, +15)	 loop: tick value = XXX
 	(in ESP32_240LCD_LVGL_Test_3.ino line #297)
[User]	(0.935, +1)	 loop: indev_button_1: 0x3ffc44d0
 	(in ESP32_240LCD_LVGL_Test_3.ino line #298)
[User]	(0.944, +9)	 loop: tick value =XXX
 	(in ESP32_240LCD_LVGL_Test_3.ino line #297)
[User]	(0.951, +7)	 loop: indev_button_1: 0x3ffc44d0
 	(in ESP32_240LCD_LVGL_Test_3.ino line #298)

Maybe you lost in lines. I read your first code and you miss read digital button.
Used functions do nothing

int8_t btn_act = button_get_pressed_id_1();

always return false and -1