P29 QT pour tablette à retour tactile
Tuteurs : Laurent Grisoni
Projet de fin d'étude réalisé par Vivian Senaffe et Valentin Beauchamp
Sommaire
- 1 Objectif du Projet
- 2 Description
- 3 Avancement du projet
- 3.1 Première réunion avec notre tuteur
- 3.2 1ère semaine
- 3.3 2ème semaine
- 3.4 Seconde réunion avec notre tuteur
- 3.5 3ème et 4ème semaines
- 3.6 5ème et 6ème semaines
- 3.7 Troisième réunion avec notre tuteur
- 3.8 7ème et 8ème semaines
- 3.9 9ème semaine
- 3.10 Cross Compilation de Qt
- 3.11 Architecture de Qt modifiée
- 3.12 Récupération des positions du doigt
- 3.13 Communication avec le DSP
- 3.14 Modification de l'architecture
- 4 Bibliographie
- 5 Annexes
Objectif du Projet
Il s'agira d'adapter une libraire de création d'application interactive à une tablette à retour d'information tactile
Description
Le dispositif EVITA, mise au point par l'équipe MINT (collaboration entre les laboratoires CRISTAL et L2EP) est une tablette tactile particulière, dans la mesure ou elle dispose d'actionneurs piézoelectriques permettant à l'application de faire vibrer à trés haute fréquence l'ecran, par exemple en fonction de la position du doigt de l'utilisateur. Ces vibrations permettant à l'utilisateur, via son doigt en contact, de percevoir de l'information, aux cotés de celles accessibles via l'oeil et l'oreille. Sur ce dispositif sont en cours à la fois des recherches pour étendre la gamme des ressentis tactiles accessibles, ainsi que plusieurs projets de développement pour identifier les niches d'usages ou cette tablette pourrait faire sense (citons par exemple un projet de livre interactif pour enfants; un projet prospectif sur l'interet du tactile pour l'apprentissage de la lecture chez l'enfant dyslexique; un projet en cours de montage pour un dispositif de médiation culturel autour de livres trés anciens). Nous souhaitons disposer, et c'est le sujet ici proposé, d'une base de programmation d'application, du type QT (ou une autre base de travail à déterminer), pouvant être combinée de manière simple à des informations de retour tactile. Idéalement, la solution logicielle proposée pourra etre adaptée à d'autres dispositifs à retour tactile que la tablette EVITA. On illustrera le travail logiciel réalisé sur un petit exemple dont le scénario sera défini au début du projet.
Avancement du projet
Première réunion avec notre tuteur
Travail à effectuer :
- Prise en main de Qt.
- Voir si il est possible de modifier les class de Qt (ajout de méthode ) sans modifier le fonctionnement par défaut de celles-ci. (Créer une méthode d’ajout de méthode, par exemple)
- Si le point précédent n’est pas possible, réfléchir à une autre approche du problème. (changer de technologie dans le pire des cas).
- Réfléchir à un moyen plus « pratique » d’implémenter les applications sur la tablette ( pour le moment codage sur la tablette elle-même), peut être fait plus tard.
1ère semaine
Durant la première semaine de travail, nous avons pris en main le codage en C++ qui nous était inconnu.
Afin de modifier la librairie QT nous envisageons plusieurs possibilités:
- Utilisation des fonctions statiques (très peu de maniabilité).
- Création de classes filles associées aux classes existantes. Cette solution est très maniable mais nécessite de bien comprendre la librairie existante de Qt et ensuite de créer une nouvelle librairie associée à celle-ci.
- Modification des classes existantes et introduction de nouveaux constructeurs. Cette méthode est semblable à la précédente mais sans créer de nouvelle librairie. Risque important de bugs. Si la librairie Qt vient à être mise à jour, les modifications apportées risquent fortement de ne plus fonctionner correctement.
- Créer "une classe mère" qui fonctionnerai pour toutes les méthodes de Qt existantes. Cette méthode risque d'avoir les mêmes problèmes que la précédente mais peut être rapide à mettre en place. La création d'un objet en C++ permet d'appeler si on le veut le constructeur de sa classe mère avant sa création afin de paramétrer celle-ci. Néanmoins nous ne pouvons pas prévoir à l'avance les "effets secondaires" de cette classe mère sur la bibliothèque Qt.
Pour la suite du projet, nous allons nous pencher vers la seconde méthode que nous pensons être la plus adaptée.
2ème semaine
Lors de notre seconde semaine de travail, nous avons testé les différentes méthodes citées la 1er semaine. Nous avons pu voir les différents avantages et inconvénients de chacune d'entre elles. Mais lors de la réunion avec M.Grisoni, nous avons compris que nous n'étions pas sur la voie souhaitée. Nous allons nous diriger vers la création d'un objet sous Qt qui gérera le système tactile des applications au lieu de modifier les fonctions existantes.
Seconde réunion avec notre tuteur
Lors de la seconde réunion, nous avons pu tester les différentes fonctions de la tablette tactile. Cette démonstration nous a permis de mieux appréhender la problématique posée. Cette réunion nous a permis de définir le travail que nous allons devoir effectuer pour la suite du projet:
- Voir si il est possible de créer un objet "tactile" à côté de la bibliothèque Qt qui va gérer le système tactile de l'application.
- Voir comment les événements sont traités sur Qt afin de les comprendre et les gérer pour éviter la latence sur le tactile.
- Voir comment récupérer les positions X et Y du doigt en valeurs absolues sur Qt pour faire le lien avec la tablette.
Test à effectuer:
- Créer un objet tel que précédemment qui permet un retour (visuel ou écrit) pour les tests en dehors de la tablette.
- Tester la récupération des positions X et Y du curseur de la souris sous Qt.
3ème et 4ème semaines
Nous avons travaillé sur le différents points aborder lors de notre seconde réunion avec M.Grisonni.
Nous nous sommes occupé dans un premier temps de la création d'un objet Qt. Celui-ci permet une génération de fonctions liées aux objets présents sur une application Qt. Ces fonctions pourront être à retour tactile dans le future.
Pour la position du "doigt", avec Qt, nous arrivons à récupérer les événements de la souris ( et donc du doigt ). Ainsi nous pouvons récupérer les positions X et Y de la souris.
Ainsi, dans cette vidéo, nous récupérons la position du curseur lors du passage du doigt, sur n'importe quel objet dans la fenêtre.
Média:mousetracking.avi
Dans cette seconde vidéo, nous récupérons la position du curseur en dehors du bouton, et lorsque l'on clique dessus, nous récupérons sa position
Média:evenement.avi
Nous ne détectons pas de temps de latence mais des tests sur la tablette seront nécessaire pour s'assurer de cela.
La bibliothèque Qt a été créée pour que chaque objet fonctionne en autonomie. Un objet gérant des fonctionnalités d'autres objets n'a pas été pensé. Il ne semble donc pas possible de savoir (au moyen d'un signal par exemple) si un objet a été rattaché a une fenêtre.
Nous devons donc appeler une méthode de l'objet créé pour pouvoir ajouter un autre objet souhaité à la liste des objets rendus "tactile".
Pour le moment nous ne voulons pas toucher à la bibliothèque Qt existante. La possible modification de la bibliothèque Qt devra être discuté avec notre tuteur.
5ème et 6ème semaines
Durant ces semaines, nous avons travaillé sur l'objet externe à Qt, qui doit attribuer un retour tactile aux objets présents sur une application. Pour mettre en place cet objet, nous avons rencontré deux principaux problèmes.
- Lors de la création d'un objet sur Qt, aucun signal n'est envoyé. Il est donc impossible de connaître les objets présents sur une application de manière directe. Pour connaître les objets présents sur une application, il nous faut parcourir la liste des objets rattachés à une fenêtre lors de sa création. Cette liste ne peut être obtenue que si les objets présents dans la fenêtre sont dans un état "actif" et "visible". Une fois cette liste complète, l'objet externe à Qt doit stocker et apporter les modifications aux objets de l'application selon leur classe et les spécificités apportées par le programmeur.
- cette méthode demande de très haute performance. Qt travaillant par évènement, il nous semble difficile d'obtenir le résultat souhaité avec cette solution.
Une conclusion à ces deux dernières semaines de projet est que la solution proposée par notre tuteur est sûrement trop lourde à mettre en œuvre. Elle risque de demander beaucoup de ressources à la tablette inutilement.
La solution qui nous semble donc la plus appropriée maintenant est celle de la modification de la bibliothèque Qt.
Troisième réunion avec notre tuteur
Lors de cette troisième réunion avec M.Grisoni, nous avons pu exposer le travail effectué lors des semaines précédentes. Nous avons ainsi pu lui expliquer les problèmes que nous avons rencontrés.
M.Grisoni a soutenu l'idée de modifier la bibliothèque Qt au lieu de créer un objet externe à celle-ci.
Pour la suite du projet, nous devrons donc comprendre le fonctionnement de Qt afin de pourvoir dans un premier temps, recompiler la librairie. Dans un second temps, apporter des modifications à celle-ci pour influencer le comportement des objets Qt.
7ème et 8ème semaines
A la suite de notre réunion avec notre tuteur, nous avons abandonné l'idée d'un objet externe à Qt.
Nous nous sommes concentrés sur la re-compilation de la librairie Qt. Pour cela nous avons du prendre en main les paramètres de compilation de Qt.
Afin d'éviter de modifier chaque classe de la bibliothèque, nous avons décidé d'apporter des changements seulement sur ces classes abstraites. Chaque objet bouton de Qt, par exemple, n'ayant pas besoin d'un comportement différent de base, il est inutile de leur apporter des modifications spécifiques. Pour nos tests, nous avons modifié la classe "QAbstractButton" de Qt afin de modifier le comportement des boutons. Une fois compiler avec notre librairie modifiée, nous avons pu observer l'ajout de fonctionnalité lors du clique sur le bouton.
9ème semaine
Ayant validé la méthode d'ajout de fonctionnalité à Qt, nous pouvons commencer à travail à l'IRCICA. Durant les semaines suivantes de projet nous allons prendre en main la tablette tactile afin de voir comment y intégrer notre travail.
Cross Compilation de Qt
Pour mettre en place des applications Qt sur la tablette, il nous fallait pouvoir cross compiler Qt pour l’architecture ARM. Qt embarque avec lui, des fonctionnalités permettant de cross-compiler facilement pour des devices spécifiques (comme la raspberryPi). La BananaPi ne faisant pas partie des devices "compatibles", nous devons cross compiler manuellement la bibliothèque afin de la faire fonctionner pour la tablette.
La cross-compilation pour plateforme ARM générique sortant des spécificités de Qt, nous nous sommes confrontés à des problèmes inattendus et des comportements hasardeux du compilateur de Qt. Pour cross-compiler Qt correctement, nous avons du créer un sysroot de la tablette sur nos machines. Un des comportements inattendu observable a été de voir que le linker du compilateur n'allait pas chercher ses paramètres dans le sysroot mais dans notre système de base. Ce comportement empêche le compilateur de trouver les librairies nécessaires à la bonne construction de Qt. Le seul message d'erreur sortant étant que le linker ne trouvait pas les librairies demandées, il a fallu un certain temps avant de nous rendre compte du problème.
Une fois Qt configuré, pour être compiler pour processeur ARM, le compilateur refusait d'utiliser le sysroot pour la compilation. Il semblerait que ceci soit du à un problème du compilateur que nous utilisions. En changent de compilateur le problème a été résolu, mais il n'a pas été facile de comprendre que le comportement venait du compilateur lui-même.
Pour rendre Qt portable, il est nécessaire de générer une librairie: libqxcb.so. Pour créer cette librairie, il faut installer une autre bibliothèque sur la tablette (libxcb) avant de créer notre sysroot. Dans un premier temps, nous avions utilisé la version 5.2 de Qt pour la cross-compilation. Or un bug connu de Qt empêche l'utilisation de la librairie libqxcb pour cette version, la rendant non portable. Pour corriger ce problème nous sommes simplement passés de la version 5.2 à la version 5.8 de Qt.
Une fois que nous avons réussi a cross-compiler Qt correctement, nous avons écrit un petit How To, afin d'aider de futures personnes souhaitant réaliser ceci.
Fichier:HOW TO CROSS BEAUCHAMP SENAFFE.pdf
Pour créer une application sur la tablette, nous avons configuré notre IDE comme expliqué dans le How To, puis créer un package pour nos applications qui fonctionnent comme voulu.
A posteriori, il est possible que le problème de linker pourrait être réglé par la mise à jour de notre compilateur. Des tests seront à effectuer pour vérifier cette hypothèse.
Architecture de Qt modifiée
En parallèle de la cross-compilation, nous avons travaillé sur l'architecture logiciel que nous allons mettre en place pour modifier Qt. Une idée, qui a été retenue et approuvée par Michel Amberg, est de modifier Qt afin que les applications à retour tactile ne passent plus par un service tiers. Celui-ci s'occupait de la captation des doigts et de l'envoie des informations au DSP. En effet, en ne passant plus par ce service, les applications seront plus performantes et les textures ressenties seront plus réalistes.
Afin de mettre en place ce système, nous avons pensé au lancement d'un premier thread au démarrage d'une application. Ce thread gère la communication i2c pour la captation des doigts ainsi que l'envoie des informations au DSP.
Pour savoir quelles actions doivent être effectuées sur la tablette, un thread est démarré à la création d'un widget. Ce thread analyse la position du doigt : en confrontant la position du widget et celle de l'utilisateur, le thread détermine une action tactile à effectuer.
Thread sous Qt
Sous Qt, il existe une bibliothèque QThread qui permet la création de thread de façon multiple. L'utilisation des Qthreads à l'intérieur de la bibliothèque Qt étant peu permissive, il nous a fallu essayer plusieurs méthodes afin de déterminer la solution la plus adéquate à notre utilisation. Une des solutions les plus reconnues est la création de thread par l'intermédiaire de méthode utilisant des événements. Or, la latence des événements n'étant pas contrôlable et souhaitant les meilleures performances possibles, nous avons décidé de limiter cette méthode.
La technique que nous avons retenu, est la ré-implémentation de QThread : en redéfinissant sa méthode "run", nous décrivons les actions que le thread effectuera. Devant connaître les informations de l'objet créant ce thread, nous implémentons d'autres méthodes permettant la passation de ces informations.
Timer
La récupération de la position du doigt doit s'effectuer tous les 18 ms comme nous l'a expliqué Michel Amberg. Afin de s'assurer de cette périodicité, nous avons décidé des QTimer. Les QTimer fonctionnent par signaux, pour les mêmes raisons que citées précédemment, nous ne sommes pas pour leur utilisation. Cependant, les signaux générés par QTimer ont un comportement qui se rapprochent des interruptions : ils ont donc une priorité supérieure aux autres et une latence moindre. Nous avons donc décidé de valider leur utilisation, étant la méthode la plus adéquate.
Lors de l'implémentation des QTimer, nous avons rencontré un soucis majeur : pour utiliser des signaux et des slots, nous devons passer par l'utilisation de "meta-objet". Or, après plusieurs tentatives et recherches, nous avons appris que la construction actuelle de Qt empêche l'ajout de meta-objets dans de nouvelle classe interne. Nous avons donc implémenter des nouveaux slots dans les classes créant les threads puisqu'ils utilisent les meta-objets. Si le temps nous le permet, nous nous pencherons sur l'ajout des méta-objet pour de nouvelle classe interne à Qt.
Récupération des positions du doigt
Toujours dans le soucis d'une performance la plus élevée possible, la récupération de la position des doigts sur l'écran tactile se fait par l'intermédiaire d'un bus i2c et non par le système d'exploitation de la bananaPi. Ainsi, la récupération de la position du doigt se fait en deux étapes : on interroge le bus i2c afin de recevoir les informations nécessaires, puis nous traitons les données reçues. Celles-ci se décomposent en sept octets de la manière suivante :
- octet 1 : ignoré
- octet 2 : ignoré
- octet 3 : présence du doigt
- octet 4 : 2 bits pour la force d’appuis + les 4 bits de poids fort pour la position en X
- octet 5 : les 4 bits de poids faible pour la position en X
- octet 6 : 4 bits pour le numero du doigt + les 4 bits de poids fort pour la position en Y
- octet 7 : les 4 bits de poids faible pour la position en Y
Ainsi, nous récupérons la position du doigt selon X et Y.
Pour alléger le traitement des données, si le flag de présence d'un doigt est à 0, alors on stop les vibrations de la tablette et les threads des widget ne traitent plus d'information.
Test
Pour tester la captation des doigts par Qt, nous avons écrit un programme avec deux boutons, à l'entrée du doigt sur un bouton celui-ci nous renvoie la position en X et Y du doigt.
Communication avec le DSP
Pour communiquer avec le DSP, la bananaPi utilise une liaison SPI et une broche GPIO.
Pour comprendre le fonctionnement, nous pouvons assimiler les données envoyées à un flux audio : le flux d'information doit être ininterrompu pour assurer la cohérence du ressenti de la tablette. Pour assurer la continuité de ce flux, il est nécessaire de surcharger le buffer du micro-contrôleur. Afin d'éviter une coupure, le DSP envoie un signal sur la broche du GPIO pour avertir un manque prochain de donnée. Dans le cas où le buffer est vidé, le DSP reprend le buffer en mémoire utilisé précédemment.
Le signal reçu par la broche GPIO peut-être interprété comme une interruption sur Qt. Il ne nous est donc plus nécessaire d'utiliser les QTimer pour analyser la position des doigts. Le traitement sera effectué lorsque le DSP exigera un nouvel ordre de vibration.
Intégration de la lib WiringPi
Pour pouvoir mettre en place cette communication, nous avons cherché à introduire la bibliothèque wiringPi dans celle de Qt. Après de multiples recherches, nous avons réussi à intégrer la bibliothèque à Qt. Pour ce faire, nous ajoutons le chemin de la librairie à l'intérieur du Makefile se situant dans le dossier /qtbase/src/widget. Ainsi, la bibliothèque est prise en compte lors de la compilation.
Fonctionnement du démon
Le démon qui permet la génération des ordres de vibrations et l'envoi des données au DSP, fonctionne de la manière suivante:
Une structure "Taxtel" contient les informations de position des éléments à retour tactile ainsi qu'une autre structure contenant pour sa part les informations de texture relative à cette "Taxtel".
Cette structure "textures" est constitué d'un ensemble d’éléments et de fonctions permettant la génération des signaux envoyés au DSP.
Lorsque le DSP réclame de nouvelles instructions, le démon effectue ces actions:
- Capture de la position des doigts
- Vérification si le doigt se trouve dans un taxtel
- Calculer les ordres à envoyer au DSP
- Envoi des données au DSP
Pour la suite du projet, nous allons suivre ce principe de fonctionnement.
Thread Principal
Lors du lancement d'une application, on génère une première texture NULL pour gérer le cas où la tablette ne vibre pas.
On lance ensuite le thread principal qui va prendre en compte les interruptions du port GPIO et effectuer les différents traitements souhaités pour mettre en action la tablette. Ces traitement sont similaires à ceux se trouvant dans le démon.
Construction des Textures
Lorsque l'on crée un widget, QAbstractButton dans notre cas, le constructeur du widget va l'ajouter à une liste de widget à retour tactile. Cette liste est une structure contient :
- la texture à simuler
- l'Id du widget
- deux flags pour savoir si l'utilisateur se trouve dans le widget et si une mise à jour a été effectuée
Le constructeur du widget appelle par la suite une fonction qui crée une texture de base à simuler puis lance un thread. Le thread appelé regarde toutes les 18ms si le doigt de l'utilisateur se trouve dans le widget et modifie les flags précédemment cités en fonction de ses calculs.
Envoi des données au DSP
Comme expliqué précédemment, nous calculons la position du doigt ainsi que les informations à envoyer au DSP lorsque celui-ci le demande, par le port GPIO. Lorsque le DSP fait une demande, une interruption est générée, on recalcule la position du doigt et on détermine sa vitesse de déplacement en fonction de son ancienne position connue. A ce moment, on "attend" que chaque widget se met à jour et on vérifie si le doigt de l'utilisateur se trouve dans l'un d'eux au travers des flags de position se trouvant dans la liste de structure. Lorsque l'on sait quelle texture appliquer à la tablette, on génère le signal à envoyer au DSP.
Modification de l'architecture
Après plusieurs essais, nous avons décidé de modifier l'architecture que nous avions mis en place. En effet, il est inutile et coûteux en temps d'attendre que les widgets se mettent à jour d'eux-mêmes. Nous avons donc modifié la structure de notre liste de widget et prend maintenant:
- L'id du Widget
- La texture appliquée
- Un pointeur vers le widget
Le thread dans le widget a été supprimé, rendu inutile, et lors de l'interruption on appelle une méthode du widget grâce à son pointeur. Cette méthode renvoie un bool true si l'utilisateur se trouve sur l’élément. Le reste du programme s’exécute comme précédemment.
Tests
Après avoir implémenter ces différentes fonctions, les tests effectués sont concluants et nous décidons de vérifier le temps de réponse du système à l'aide d'un oscilloscope.
Pour une texture correspondant à un signal carré équivalent, nous observons un temps de réponse meilleur dans notre application : il est en effet 3 ms plus rapide. Cette différence peut s’expliquer par la non-utilisation du service tiers et donc de la non communication entre les applications.
Sur la première courbe, nous observons le signal d’amplitude de l’ordre envoyé par le DSP. Sur la deuxième courbe, nous observons la connexion MOSI du SPI.
Nous pouvons donc remarquer que lorsque le trigger est déclenché, le temps de réponse avec Qt est 3 ms plus rapide avec Qt.
Bibliographie
- Tutoriel Qt / C++ : https://openclassrooms.com/
- Documentation Qt : http://doc.qt.io
- Forum Qt : https://qt.developpez.com/
- Wiki Qt : https://wiki.qt.io/Main