Simplest C example for fbdev and evdev (no SDL)?

Description

Looking for a simple example using evdev to get input from a touchscreen

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

OdroidN2 Linux SBC with Ubuntu 18.04 headless, no SDL, no X11, gcc 8.4.0, latest LittleVGL from github with all submodules

What do you want to achieve?

Trying to find a very simple demo app with 1 button to be clicked via touchscreen (which is a /dev/input/event device)

What have you tried so far?

I have the “Hello World!” demo app working on fbdev.
I have found that my touchscreen works with “evtest” and corresponds to /dev/input/event3
I configured lv_drv_conf.h to enable the EVDEV input driver.
But, I could not find a basic-level sample source code to learn the basics of touchscreen input (like: clicking a button and showing a text)

1 Like

If you have the evdev driver enabled, this code needs to be present when you initialize LittlevGL:

    evdev_init();
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = evdev_read;
    lv_indev_drv_register(&indev_drv);

After that, you can create buttons or other widgets as per the examples and they automatically get touchscreen input. If you want a simple example with one button you can take a look at the button documentation.

1 Like

Thank you very much.

Unfortunately, I can’t seem to have it working: the button won’t get pressed.

My source code:

#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/evdev.h"
#include <stdio.h>
#include <unistd.h>

#define DISP_BUF_SIZE 800*480*4UL // If smaller than screen memory, draw will occur in chunks

static void event_handler(lv_obj_t * obj, lv_event_t event)
{
    if(event == LV_EVENT_CLICKED) {
        printf("Clicked\n");
    }
    else if(event == LV_EVENT_VALUE_CHANGED) {
        printf("Toggled\n");
    }
}

void lv_ex_btn_1(void)
{
    lv_obj_t * label;
    lv_obj_t * btn1 = lv_btn_create(lv_scr_act(), NULL);
    lv_obj_set_event_cb(btn1, event_handler);
    lv_obj_align(btn1, NULL, LV_ALIGN_CENTER, 0, -40);
    label = lv_label_create(btn1, NULL);
    lv_label_set_text(label, "Button");
    lv_obj_t * btn2 = lv_btn_create(lv_scr_act(), NULL);
    lv_obj_set_event_cb(btn2, event_handler);
    lv_obj_align(btn2, NULL, LV_ALIGN_CENTER, 0, 40);
    lv_btn_set_toggle(btn2, true);
    lv_btn_toggle(btn2);
    lv_btn_set_fit2(btn2, LV_FIT_NONE, LV_FIT_TIGHT);
    label = lv_label_create(btn2, NULL);
    lv_label_set_text(label, "Toggled");
}

int main(void)
{
    /*LittlevGL init*/
    lv_init();
    /*Linux frame buffer device init*/
    fbdev_init();
    /*Small buffers for LittlevGL to draw the screen's content with double-buffering*/
    static lv_color_t buf1[DISP_BUF_SIZE];
    static lv_color_t buf2[DISP_BUF_SIZE];

    /*Initialize a descriptor for the buffer, with double buffering*/
    static lv_disp_buf_t disp_buf;
    lv_disp_buf_init(&disp_buf, buf1, buf2, DISP_BUF_SIZE);
    /*Initialize and register a display driver*/
    // Here we use the special callback function specific for the Linux Framebuffer Driver
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.buffer = &disp_buf;
    disp_drv.flush_cb = fbdev_flush;
    lv_disp_drv_register(&disp_drv);

    // Initialize EVDEV input for the 52pi touchscreen,
    // which luckily is supported as an "event input device" by the kernel
    evdev_init();
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = evdev_read;
    lv_indev_drv_register(&indev_drv);
        
    //Create a label. Style is NULL by default for labels    
    lv_obj_t * label1 = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_text(label1, "Function 1");
    lv_obj_t * label2 = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_text(label2, "Section A");

    // Alignment
    // NULL = align on parent (the screen)
    lv_obj_align(label1, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
    lv_obj_align(label2, NULL, LV_ALIGN_IN_TOP_MID, 0, 0);

    // Add a button
    lv_ex_btn_1();

    /*Handle LitlevGL tasks (tickless mode)*/
    // Mandatory, otherwise nothing will be displayed
    // It allows the callback functions to operate (for example to actually transfer bytes to screen framebuffer)
    while(1) 
    {
        lv_tick_inc(5);
        lv_task_handler();
        usleep(5000);
    }

    return 0;
}

The relevant section of my lv_drv_conf.h:

/*-------------------------------------------------
 * Mouse or touchpad as evdev interface (for Linux based systems)
 *------------------------------------------------*/
#ifndef USE_EVDEV
#define USE_EVDEV           1
#endif

#if USE_EVDEV
#define EVDEV_NAME   "/dev/input/event3"        /*You can use the "evtest" Linux tool to get the list of devices and test them*/
#  define EVDEV_SWAP_AXES         0               /*Swap the x and y axes of the touchscreen*/

#define EVDEV_SCALE             1               /* Scale input, e.g. if touchscreen resolution does not match display resolution */
#if EVDEV_SCALE
#define EVDEV_SCALE_HOR_RES     (16384)          /* Horizontal resolution of touchscreen */
#define EVDEV_SCALE_VER_RES     (9600)          /* Vertical resolution of touchscreen */
#endif  /*EVDEV_SCALE*/

#  define EVDEV_CALIBRATE         0               /*Scale and offset the touchscreen coordinates by using maximum and minimum values
#  if EVDEV_CALIBRATE
#    define EVDEV_HOR_MIN   3800                    /*If EVDEV_XXX_MIN > EVDEV_XXX_MAX the XXX axis is automatically inverted*/
#    define EVDEV_HOR_MAX   200
#    define EVDEV_VER_MIN   200
#    define EVDEV_VER_MAX   3800
#  endif  /*EVDEV_SCALE*/
#endif  /*USE_EVDEV*/

The output of evtest on /dev/input/event3:

Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x222a product 0x1 version 0x110
Input device name: "ILITEK ILITEK-TP"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 330 (BTN_TOUCH)
  Event type 3 (EV_ABS)
    Event code 0 (ABS_X)
      Value   8268
      Min        0
      Max    16384
      Resolution      34
    Event code 1 (ABS_Y)
      Value   4016
      Min        0
      Max     9600
      Resolution      36
    Event code 47 (ABS_MT_SLOT)
      Value      0
      Min        0
      Max        9
    Event code 53 (ABS_MT_POSITION_X)
      Value      0
      Min        0
      Max    16384
      Resolution      34
    Event code 54 (ABS_MT_POSITION_Y)
      Value      0
      Min        0
      Max     9600
      Resolution      36
    Event code 57 (ABS_MT_TRACKING_ID)
      Value      0
      Min        0
      Max    65535
Properties:
  Property type 1 (INPUT_PROP_DIRECT)
Testing ... (interrupt to exit)
Event: time 1587772644.579822, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 82
Event: time 1587772644.579822, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 6190
Event: time 1587772644.579822, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 6005
Event: time 1587772644.579822, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
Event: time 1587772644.579822, type 3 (EV_ABS), code 0 (ABS_X), value 6190
Event: time 1587772644.579822, type 3 (EV_ABS), code 1 (ABS_Y), value 6005
Event: time 1587772644.579822, -------------- SYN_REPORT ------------
Event: time 1587772644.649793, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value -1
Event: time 1587772644.649793, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 0
Event: time 1587772644.649793, -------------- SYN_REPORT ------------

What happens if you add printf statements inside evdev_read? Do you see any events getting read?

Added a debug print in evdev.c:

bool evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
    struct input_event in;

    fprintf(stdout, "\nevdev_read() DEBUG:\n\n");
    while(read(evdev_fd, &in, sizeof(struct input_event)) > 0) 
    {
        fprintf(stdout, "in.type=%d, in.code=%d, in.value=%d\n", in.type, in.code, in.value);
:
:

Touching on the button I get this:

evdev_read() DEBUG:

in.type=3, in.code=57, in.value=-1
in.type=1, in.code=330, in.value=0
in.type=0, in.code=0, in.value=0

so it is actually called, don’t know about the values…?

Thanks a lot for your support!

Solved!!

It was not enough to specify X and Y resolution in the EVDEV section of lv_drv_conf.h
Calibration points were needed too:

/*-------------------------------------------------
 * Mouse or touchpad as evdev interface (for Linux based systems)
 *------------------------------------------------*/
#ifndef USE_EVDEV
#define USE_EVDEV           1
#endif

#if USE_EVDEV
#define EVDEV_NAME   "/dev/input/event3"        /*You can use the "evtest" Linux tool to get the list of devices and test them*/
#  define EVDEV_SWAP_AXES         0               /*Swap the x and y axes of the touchscreen*/

#define EVDEV_SCALE             1               /* Scale input, e.g. if touchscreen resolution does not match display resolution */
#if EVDEV_SCALE
#define EVDEV_SCALE_HOR_RES     (16384)          /* Horizontal resolution of touchscreen */
#define EVDEV_SCALE_VER_RES     (9600)          /* Vertical resolution of touchscreen */
#endif  /*EVDEV_SCALE*/

#define EVDEV_CALIBRATE         1               /*Scale and offset the touchscreen coordinates by using maximum and minimum values for each axis*/
#  if EVDEV_CALIBRATE
#    define EVDEV_HOR_MIN   25 //3800                    /*If EVDEV_XXX_MIN > EVDEV_XXX_MAX the XXX axis is automatically inverted*/
#    define EVDEV_HOR_MAX   16383 // 200
#    define EVDEV_VER_MIN   45 //200
#    define EVDEV_VER_MAX   9592 // 3800
#  endif  /*EVDEV_SCALE*/
#endif  /*USE_EVDEV*/

I determined the extreme points (EVDEV_HOR_MIN etc.) using “evtest” and clicking the corners.

Thanks for tipping me in the right direction!

Tested the same solution on iMX7 (Cortex-A7) and also worked. Headless linux, no SLD, no X11, gcc 7.3.0.