LVGL Demos and XPT2046 setup

I am very new to LVGL, so I apologize in advance if I’m missing something obvious. I am trying to get the demos working on an ST7796/XPT2046 combo screen. Getting the ST7796 setup and working was very easy and straightforward, and the demo’s load and display correctly. What I am struggling with is the touch input. I don’t see any place in any of the configs to set the pins, select the driver, etc. Can anyone point me to the right place to get started? I’ve been googling for days and the only thing I can find is youtube video’s showing it working with zero info on how lol. Example LVGL widgets demo on ILI9488/XPT2046 on RBM33G - YouTube

I’m using a Teensy 4.1, so I have plenty of pins/inputs, I just need to know where in the code/config’s to tell it where I have everything plugged in. Do I need a driver that doesn’t come with LVGL? Am I missing an obvious section in the docs somewhere? Can the XPT2046 use the same SPI bus as the ST7796, or does it need its own?

Thanks for any help, I’m truly at a loss.
Joshua

EDIT:

I was able to figure this out, the key for me at least was to just dig into the lvgl docs on how to setup an input device, and after going through that it made sense what I needed to do to the demo to get everything working. Yes, you can use the same SPI bus for the XPT2046 as the SP7796 and it works great. Here is what I had to do

#define LV_USE_PERF_MONITOR 1
#include "lv_demo_widgets.h"

#include <XPT2046_Touchscreen.h>

#define CS_PIN 32
#define TIRQ_PIN 31

int x = 0;
int y = 0;

XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);

bool my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
       if (ts.touched()) {
        TS_Point p = ts.getPoint();
        x = map(p.x, 220, 3850, 1, 480); //
        y = map(p.y, 310, 3773, 1, 320); // Feel pretty good about this
        data->point.x = x;
        data->point.y = y;
        data->state = LV_INDEV_STATE_PR; 
      } else {
        data->state = LV_INDEV_STATE_REL; 
      }
    return false; /*No buffering now so no more data read*/
}

I had to add this import and this little function, and then I had to add one line below in the Dummy input setup section


      /* Initialize the (dummy) input device driver */
      static lv_indev_drv_t indev_drv;
      lv_indev_drv_init(&indev_drv);
      indev_drv.type = LV_INDEV_TYPE_POINTER;
      indev_drv.read_cb = my_input_read; // ADDED THIS LINE
      lv_indev_drv_register(&indev_drv);

      lv_demo_widgets();

      Serial.println("Setup done");

After that everything works great! Maybe this empty thread will be helpful to someone else in the future so I didn’t waste my time :slight_smile:

Happy to hear that you’ve found a solution! :slight_smile:

Is there something we can add to the docs to make things simpler to follow and understand?

That’s a great question. I think just a small blurp like my edit to the README for the demo’s as an example of getting touch working would have been super helpful. But I’m not great at documentation, so take that as a vague suggestion :slight_smile:

1 Like
#include <lvgl.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h> 

// Définition des broches de l'écran tactile
#define XPT2046_IRQ 9   // T_IRQ
#define XPT2046_MOSI MOSI  // T_DIN 11
#define XPT2046_MISO MISO  // T_OUT 13
#define XPT2046_CLK SCK   // T_CLK 12
#define XPT2046_CS 8    // T_CS

SPIClass touchscreenSPI = SPIClass(VSPI); // Initialisation du SPI pour l'écran tactile
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ); // Initialisation de l'écran tactile avec les broches

#define SCREEN_WIDTH 320  // Largeur de l'écran TFT
#define SCREEN_HEIGHT 480 // Hauteur de l'écran TFT

// Coordonnées tactiles et pression
int x, y, z;

#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8)) // Taille du buffer d'affichage
uint32_t draw_buf[DRAW_BUF_SIZE / 4]; // Définition du buffer d'affichage


// Fonction de journalisation pour afficher les messages de débogage
void log_print(lv_log_level_t level, const char * buf) {
  LV_UNUSED(level);
  Serial.println(buf); // Affiche le message sur le port série
  Serial.flush(); // S'assure que les messages sont envoyés
}

// Fonction pour lire les données de l'écran tactile
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
  if(touchscreen.tirqTouched() && touchscreen.touched()) { // Si l'écran est touché
    TS_Point p = touchscreen.getPoint(); // Récupère les coordonnées du point touché
    x = map(p.x, 200, 3700, 1, SCREEN_WIDTH); // Mappe les coordonnées X
    y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT); // Mappe les coordonnées Y
    z = p.z; // Récupère la pression

    data->state = LV_INDEV_STATE_PRESSED; // L'état de l'écran tactile est "appuyé"
    data->point.x = x; // Assigne la position X au point de données
    data->point.y = y; // Assigne la position Y au point de données
  }
  else {
    data->state = LV_INDEV_STATE_RELEASED; // L'état est "relâché" si aucun contact
  }
}

int btn1_count = 0;
// Callback appelé lorsqu'on clique sur le bouton btn1
static void event_handler_btn1(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e); // Récupère le code de l'événement
  if(code == LV_EVENT_CLICKED) { // Si le bouton est cliqué
    btn1_count++; // Incrémente le compteur de clics
    LV_LOG_USER("Button clicked %d", (int)btn1_count); // Affiche le nombre de clics
  }
}

// Callback appelé lorsqu'on clique ou modifie l'état du bouton btn2
static void event_handler_btn2(lv_event_t * e) {
  lv_event_code_t code = lv_event_get_code(e); // Récupère le code de l'événement
  lv_obj_t * obj = (lv_obj_t*) lv_event_get_target(e); // Récupère l'objet cible de l'événement
  if(code == LV_EVENT_VALUE_CHANGED) { // Si la valeur a changé (toggle)
    LV_UNUSED(obj); // Ignore l'objet dans ce cas
    LV_LOG_USER("Toggled %s", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "on" : "off"); // Affiche l'état du bouton
  }
}

static lv_obj_t * slider_label;
// Callback pour afficher la valeur du slider
static void slider_event_callback(lv_event_t * e) {
  lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e); // Récupère l'objet du slider
  char buf[8];
  lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider)); // Récupère la valeur du slider
  lv_label_set_text(slider_label, buf); // Met à jour le texte de l'étiquette du slider
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); // Aligne l'étiquette sous le slider
  LV_LOG_USER("Slider changed to %d%%", (int)lv_slider_get_value(slider)); // Affiche la valeur du slider
}

// Fonction pour créer un menu déroulant (dropdown)
static void create_dropdown(lv_obj_t * parent) {
  // Crée un menu déroulant
  lv_obj_t * dropdown = lv_dropdown_create(parent);
  lv_obj_align(dropdown, LV_ALIGN_CENTER, 0, 120); // Positionne le menu au centre
  
  // Ajoute des options au menu déroulant
  lv_dropdown_set_options(dropdown, "Option 5\nOption 2\nOption 3\nOption 4");
  lv_obj_set_width(dropdown, 150); // Définit la largeur du menu déroulant
  
  // Ajoute un callback pour l'événement de changement de valeur
  lv_obj_add_event_cb(dropdown, dropdown_event_handler, LV_EVENT_VALUE_CHANGED, NULL);
}

// Callback pour l'événement du menu déroulant
static void dropdown_event_handler(lv_event_t * e) {
  lv_obj_t * dropdown = (lv_obj_t*) lv_event_get_target(e); // Récupère l'objet dropdown
  char selected_option[64];  // Crée un buffer pour récupérer l'option sélectionnée
  
  // Récupère l'option sélectionnée
  lv_dropdown_get_selected_str(dropdown, selected_option, sizeof(selected_option));
  
  // Affiche l'option sélectionnée dans le journal de débogage
  LV_LOG_USER("Selected option: %s", selected_option);
}

// Fonction pour créer l'interface graphique principale
void lv_create_main_gui(void) {
  // Crée un label de texte "Hello, world!" aligné au centre en haut
  lv_obj_t * text_label = lv_label_create(lv_screen_active());
  lv_label_set_long_mode(text_label, LV_LABEL_LONG_WRAP);    // Permet le retour à la ligne
  lv_label_set_text(text_label, "Hello, world!"); // Texte du label
  lv_obj_set_width(text_label, 150);    // Définir une largeur plus petite pour forcer le retour à la ligne
  lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_CENTER, 0); // Aligne le texte au centre
  lv_obj_align(text_label, LV_ALIGN_CENTER, 0, -90); // Positionne le texte en haut du centre

  lv_obj_t * btn_label;
  // Crée un bouton (btn1)
  lv_obj_t * btn1 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn1, event_handler_btn1, LV_EVENT_ALL, NULL); // Ajoute l'événement de clic
  lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -50); // Positionne le bouton au centre
  lv_obj_remove_flag(btn1, LV_OBJ_FLAG_PRESS_LOCK); // Supprime le verrouillage de pression

  // Crée un label pour le bouton
  btn_label = lv_label_create(btn1);
  lv_label_set_text(btn_label, "Button"); // Texte du bouton
  lv_obj_center(btn_label); // Centre le label dans le bouton

  // Crée un bouton toggle (btn2)
  lv_obj_t * btn2 = lv_button_create(lv_screen_active());
  lv_obj_add_event_cb(btn2, event_handler_btn2, LV_EVENT_ALL, NULL); // Ajoute l'événement toggle
  lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 10); // Positionne le bouton au centre
  lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE); // Ajoute l'état checkable (toggle)
  lv_obj_set_height(btn2, LV_SIZE_CONTENT); // Définir la hauteur du bouton

  // Crée un label pour le bouton toggle
  btn_label = lv_label_create(btn2);
  lv_label_set_text(btn_label, "Toggle"); // Texte du bouton toggle
  lv_obj_center(btn_label); // Centre le label dans le bouton
  
  // Crée un slider (barre de défilement) aligné au bas du centre de l'écran
  lv_obj_t * slider = lv_slider_create(lv_screen_active());
  lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60); // Positionne le slider
  lv_obj_add_event_cb(slider, slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL); // Ajoute l'événement de changement de valeur
  lv_slider_set_range(slider, 0, 100); // Définit l'intervalle du slider
  lv_obj_set_style_anim_duration(slider, 2000, 0); // Définit la durée de l'animation pour le slider

  // Crée un label pour afficher la valeur actuelle du slider
  slider_label = lv_label_create(lv_screen_active());
  lv_label_set_text(slider_label, "0%"); // Valeur initiale du slider
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); // Positionne le label sous le slider

  // Crée le menu déroulant
  create_dropdown(lv_screen_active());
}

void setup() {
  String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
  Serial.begin(115200); // Initialisation de la communication série pour le débogage
  Serial.println(LVGL_Arduino); // Affiche la version de la bibliothèque LVGL
  

  // Initialise LVGL
  lv_init();
  // Enregistre la fonction de journalisation pour le débogage
  lv_log_register_print_cb(log_print);

  // Initialisation du SPI pour l'écran tactile
  touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  touchscreen.begin(touchscreenSPI); // Démarre l'écran tactile
  touchscreen.setRotation(1); // Configure la rotation de l'écran tactile

  // Crée un objet d'affichage TFT
  lv_display_t * disp;
  disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf)); // Initialisation de l'écran TFT
  lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270); // Définit la rotation de l'affichage
  
  // Crée un objet d'entrée LVGL pour l'écran tactile
  lv_indev_t * indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); // Définit l'entrée comme un pointeur (écran tactile)
  lv_indev_set_read_cb(indev, touchscreen_read); // Définit la fonction de lecture de l'écran tactile

  // Fonction pour dessiner l'interface graphique
  lv_create_main_gui();


}

void loop() {
  lv_task_handler();  // Exécute les tâches LVGL
  lv_tick_inc(5);     // Informe LVGL du temps écoulé
  delay(5);           // Délai pour permettre au temps de s'écouler
}

```The display works well and the touch screen too when they are separated but once I put them together in a code the touch screen no longer works please i need help