Intégration du moteur graphique LVGL V8 avec la bibliothèque tactile TSLib sur Linux Embarqué
Cet article décrit la procédure d'intégration de la bibliothèque TSLib pour la gestion des événements tactiles avec le moteur graphique LVGL (version 8) sur une plateforme Linux embarquée. L'exemple est basé sur une carte de développement i.MX6U, démontrant comment adapter les pilotes d'entrée de LVGL pour supporter l'interface tactile via TSLib et afficher les widgets de démonstration.
Configuration du Makefile pour TSLib
Pour que le projet LVGL puisse utiliser TSLib, il est impératif d'inclure les chemins d'accès aux en-têtes et aux bibliothèques de TSLib dans le Makefiel de votre projet. Cela garantit que le compilateur et l'éditeur de liens trouvent les fichiers nécessaires lors de la compilation croisée pour votre cible embarquée.
Voici un extrait de Makefile illustrant comment définir ces chemins :
# Chemin d'installation de TSLib sur le système de développement
# Remplacez par le chemin réel de votre installation de TSLib pour la cible embarquée.
TSLIB_BASE_DIR = /opt/tslib_install
# Options d'inclusion (-I) pour les en-têtes TSLib
CFLAGS += -I$(TSLIB_BASE_DIR)/include
# Options de liaison (-L) pour les bibliothèques TSLib et la bibliothèque elle-même (-lts)
LDFLAGS += -L$(TSLIB_BASE_DIR)/lib -lts
Modification du pilote d'entrée LVGL pour TSLib (lv_drivers/indev/evdev.c)
Le fichier lv_drivers/indev/evdev.c est le pilote d'entrée générique de LVGL pour les périphériques d'entrée Linux (via /dev/input/eventX). Nous allons le modifier pour qu'il utilise directement TSLib, qui offre une abstraction et une calibration des périphériques tactiles, plutôt que l'accès direct aux événements bruts.
Les modifications consistent à remplacer les appels aux fonctions de lecture d'événements bruts par des appels à l'API TSLib (ts_setup, ts_read) et à mapper les données de toucher de TSLib aux structures d'entrée de LVGL.
/**
* @file evdev.c
*
* Pilote d'entrée LVGL adapté pour TSLib sur Linux.
*/
/*********************
* INCLUDES
*********************/
#include "evdev.h"
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
// #include <linux/input.h> // Non nécessaire si TSLib est le seul gestionnaire d'entrée
#include <tslib.h>
#include <stdlib.h> // Pour exit()
/**********************
* STATIC VARIABLES
**********************/
// Gestionnaire du périphérique TSLib
static struct tsdev *ts_device_handle;
// Dernières coordonnées X et Y lues par TSLib
static int current_x_position;
static int current_y_position;
// État du toucher (appuyé/relâché)
static lv_indev_state_t current_touch_state;
/**
* Initialise le pilote d'entrée LVGL pour TSLib.
* Ouvre le périphérique tactile et prépare la lecture.
*/
void evdev_init(void)
{
// Tente d'initialiser TSLib. Le chemin du périphérique peut être NULL pour utiliser TS_ABSOLUTE_DEVICES
ts_device_handle = ts_setup(NULL, O_RDONLY | O_NONBLOCK);
if (!ts_device_handle) {
perror("ERREUR: Échec de l'initialisation de TSLib (ts_setup). Vérifiez la configuration.");
exit(EXIT_FAILURE); // Arrête l'application en cas d'échec critique
}
// Initialisation des variables d'état
current_x_position = 0;
current_y_position = 0;
current_touch_state = LV_INDEV_STATE_RELEASED; // État initial: relâché
}
/**
* Lit les données du périphérique tactile et les formate pour LVGL.
* Cette fonction est appelée par LVGL pour obtenir les entrées.
* @param drv Pointeur vers le pilote d'entrée LVGL.
* @param data Structure où stocker les données d'entrée lues.
*/
void evdev_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
struct ts_sample sample_point; // Structure pour un échantillon de TSLib
// Lire les échantillons de toucher. La boucle assure que tous les événements en attente sont traités.
// ts_read retourne le nombre d'échantillons lus.
while (ts_read(ts_device_handle, &sample_point, 1) > 0) {
current_x_position = sample_point.x;
current_y_position = sample_point.y;
// Détermine l'état du toucher basé sur la pression
if (sample_point.pressure == 0) {
current_touch_state = LV_INDEV_STATE_RELEASED;
} else {
current_touch_state = LV_INDEV_STATE_PRESSED;
}
}
// Assignation des données lues à la structure LVGL
data->point.x = current_x_position;
data->point.y = current_y_position;
data->state = current_touch_state;
// Optionnel: Limiter les coordonnées pour s'assurer qu'elles restent dans les bornes de l'écran.
// LVGL effectue déjà cette vérification en interne, mais cela peut servir de garde-fou.
if (data->point.x < 0) data->point.x = 0;
if (data->point.y < 0) data->point.y = 0;
if (data->point.x >= drv->disp->driver->hor_res) data->point.x = drv->disp->driver->hor_res - 1;
if (data->point.y >= drv->disp->driver->ver_res) data->point.y = drv->disp->driver->ver_res - 1;
// Décommenter pour le débogage des coordonnées
// printf("LVGL X: %d, Y: %d, État: %s\n", data->point.x, data->point.y,
// data->state == LV_INDEV_STATE_PRESSED ? "Appuyé" : "Relâché");
}
Programme Principal (main.c)
Le fichier main.c orchestre l'initialisation de LVGL, du périphérique d'affichage (framebuffer Linux) et du périphérique d'entrée (TSLib via notre pilote modifié). Il lance ensuite une démonstration LVGL et maintient la boucle d'exécution du moteur graphique.
#include <stdio.h>
#include <unistd.h> // Pour usleep
#include <sys/time.h> // Pour gettimeofday
#include "lvgl.h" // Bibliothèque principale LVGL
#include "lv_drivers/display/fbdev.h" // Pilote d'affichage pour framebuffer Linux
#include "lv_drivers/indev/evdev.h" // Pilote d'entrée EVDEV (adapté pour TSLib)
// Déclaration de la fonction de démonstration LVGL
extern void lv_demo_widgets(void); // Par exemple, la démo des widgets de LVGL
// Définition de la taille du tampon d'affichage (par exemple, pour un écran 800x480)
#define DISPLAY_BUFFER_PIXELS (800 * 480)
int main(void)
{
printf("Démarrage de l'application LVGL avec support TSLib...\n");
// 1. Initialisation de la bibliothèque LVGL
lv_init();
// 2. Initialisation du périphérique framebuffer Linux (/dev/fb0)
// Assurez-vous que le pilote fbdev est configuré pour votre système.
fbdev_init();
// 3. Configuration du tampon de dessin pour LVGL
// LVGL dessine d'abord dans ce tampon, puis le contenu est transféré au framebuffer.
static lv_color_t lv_display_buffer[DISPLAY_BUFFER_PIXELS];
static lv_disp_draw_buf_t lv_draw_buf_descriptor;
lv_disp_draw_buf_init(&lv_draw_buf_descriptor, lv_display_buffer, NULL, DISPLAY_BUFFER_PIXELS);
// 4. Initialisation et enregistrement du pilote d'affichage LVGL
static lv_disp_drv_t lv_display_driver;
lv_disp_drv_init(&lv_display_driver);
lv_display_driver.draw_buf = &lv_draw_buf_descriptor;
lv_display_driver.flush_cb = fbdev_flush; // Fonction de rafraîchissement qui écrit sur le framebuffer
lv_display_driver.hor_res = 800; // Résolution horizontale de l'écran
lv_display_driver.ver_res = 480; // Résolution verticale de l'écran
lv_disp_drv_register(&lv_display_driver);
// 5. Initialisation du pilote d'entrée (TSLib via evdev.c modifié)
evdev_init(); // Notre fonction d'initialisation de tslib
// 6. Initialisation et enregistrement du pilote d'entrée LVGL
static lv_indev_drv_t lv_input_driver;
lv_indev_drv_init(&lv_input_driver);
lv_input_driver.type = LV_INDEV_TYPE_POINTER; // Déclare le type de périphérique comme un pointeur
lv_input_driver.read_cb = evdev_read; // La fonction de lecture que nous avons adaptée pour TSLib
lv_indev_t * registered_input_device = lv_indev_drv_register(&lv_input_driver); // Enregistre le pilote d'entrée auprès de LVGL
// 7. Lancement de la démonstration des widgets LVGL
lv_demo_widgets();
// Boucle principale d'exécution de l'application
while(1)
{
lv_task_handler(); // Traite les tâches internes de LVGL (dessin, animations, événements)
usleep(5000); // Pause de 5 ms pour économiser le CPU et laisser du temps au système
}
return 0;
}
/*
* Fonction personnalisée pour fournir le temps système à LVGL.
* Nécessaire pour les timers, animations et la gestion des événements de LVGL.
* Définie dans lv_conf.h via `LV_TICK_CUSTOM_SYS_TIME_EXPR`.
*/
uint32_t custom_lv_tick_get(void)
{
static uint64_t app_start_ms = 0;
if(app_start_ms == 0) {
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
app_start_ms = (uint64_t)tv_start.tv_sec * 1000 + (uint64_t)tv_start.tv_usec / 1000;
}
struct timeval tv_current;
gettimeofday(&tv_current, NULL);
uint64_t current_time_ms = (uint64_t)tv_current.tv_sec * 1000 + (uint64_t)tv_current.tv_usec / 1000;
return (uint32_t)(current_time_ms - app_start_ms);
}