ScreenR : Librairie Graphique libre et gratuite pour microcontrôleur


Projet GE4a-GE5a : Développement d'une librairie graphique pour microcontrôleur
Entreprise / Client : Renesas Electronics
Auteurs : Jérémie van der Sande
Responsable Projet : Michel James
Tuteur industriel : Jonathan Bernard


Résumé


Ce projet a été proposé aux étudiants de Polytech Clermont-Ferrand par l’entreprise Renesas Electronics. Il consiste à développer une librairie graphique destinée aux microcontrôleurs de la gamme RZA1 de Renesas.
La librairie doit être capable de réaliser toutes les fonctions habituelles d’un moteur graphique simple (dessin de formes, de textes et d’images, gestion des couleurs et du mouvement des objets). Elle doit de plus mettre à profit les différents périphériques du processeur RZA1 dédiée à la gestion vidéo.
Le but de ce projet est de fournir un outil aux ingénieurs Renesas leur permettant de démarcher plus facilement leurs clients, ainsi que de fournir auxdits clients un moyen simple d’interfacer un écran avec le processeur.


Abstract


This project is initiated by Renesas Electronics for students at Polytech Clermont-Ferrand. Its main goal is to develop a graphics library for the RZA1 microcontroller family. The library must be able to compute all classical functions of a simple graphics engine – drawing of forms, texts and images, objects’ color and movement handling. It must also harness the various peripherals dedicated to video handling.
The purpose of the project is to provide Renesas’ engineers with a tool enabling them to approach their clients, and to give those clients a simple way to connect a screen to the processor.


Introduction


Les microcontrôleurs sont désormais utilisés dans la quasi-totalité des objets qui nous entourent. Il est donc de plus en plus important de simplifier leur prise en main par les utilisateurs, en réalisant des interfaces homme-machine efficaces.
L’un des piliers de ces interfaces homme-machine est la possibilité d’afficher différentes informations sur un écran, qu’il s’agisse d’un simple afficheur textuel ou d’un moniteur plus complexe. Cela peut paraître banal désormais, tant les afficheurs
graphiques nous entourent - ne serait-ce que sur les écrans de nos téléphones. Cependant, à l'heure actuelle la gestion d'afficheurs passe souvent par le biais d'un système d'exploitation et n'est donc pas à la portée de systèmes plus simples basés sur des microcontrôleurs.
Renesas, une entreprise japonaise spécialisée dans la conception de microcontrôleurs, a donc axé le développement de la famille de microcontrôleur RZA1 dans une optique de gestion du multimédia afin de pouvoir construire ces interfaces.
C’est dans cette optique que se situe ce projet, destiné à fournir aux développeurs sur microcontrôleur RZA1/L une librairie fonctionnelle pour simplifier la mise en place d’interfaces graphiques, sans passer par un système d'exploitation complexe.
Cette librairie a pour but de faciliter la programmation d’un écran, et l’affichage sur celui-ci de formes diverses, allant des formes géométriques aux caractères de l’alphabet. Parmi les avantages présentés par cette librairie, on retrouvera entre autre le fait qu'elle soit entièrement gratuite et libre de droit. De plus, bien que développée sur la famille RZA1, elle reste compatible avec d'autres microcontrôleurs également.


Présentation du Sujet


La librairie ScreenR (Screen + Renesas) a été développée dans le cadre d'un projet d'étude pour l'entreprise Renesas. Afin d'assurer la gratuité du code, ainsi que sa cohérence et sa flexibilité, aucune base existante n'a été utilisée, et la totalité du code de la librairie a été rédigée durant le projet. ScreenR a été développée dans le but d'être claire, facile à prendre en main, flexible et portable, tout en restant efficace et propre. Un effort tout particulier a donc été porté sur la création des primitives graphiques sur lesquelles repose la librairie, ainsi qu'à l'organisation et au fonctionnement des éléments graphiques (drawables en anglais).
Afin de rendre le librairie plus facilement utilisable, un logiciel de support a également été développé. Celui-ci permet d'accéder à une documentation complète de la librairie, mais également à un utilitaire permettant de générer des images et des polices de caractères dans un format utilisable par la librairie.


Cahier des Charges


Le cahier des charges de ce projet comprend non seulement les lignes de développement de la librairie, mais également la nécessité de rendre celle-ci facilement utilisable au travers d'une documentation complète et rigoureuse. En effet, l'intérêt pour l'entreprise est de pouvoir fournir cette librairie à ses clients en même temps que leurs processeurs RZA1, proposant ainsi une formule fonctionnelle et facile à prendre en main. De plus, il est nécessaire de proposer un code d'exemple, à la fois pour que le client ait une base de travail, mais également pour permettre aux ingénieurs de chez Renesas de réaliser des démonstrations.

Les livrables du projet sont donc les suivants :

  • Une librairie graphique:
    • Fonctionnelle et gérant toutes les primitives attendues d'une telle librairie (couleurs, images, formes, textes...)
    • Libre d'utilisation et de distribution, et gratuite
  • Un code entièrement commenté
  • Un code d'exemple sous forme d'un projet complet compilable
  • Une documentation de la librairie expliquant son fonctionnement ainsi que chacune des primitives développées

Développement


Problématique


Le développement d'une telle librairie amène son lot de problématiques et de choix. Existe-t-il une base utilisable ? Quelles sont les primitives incontournables ? Comment rendre la librairie facile à utiliser ? Comment la rendre flexible ?

Lisibilité et ré-utilisabilité

Le principal enjeu du développement d'une librairie, est la particularité de l'utilisateur final. En effet, lors du développement d'un logiciel ou d'un système, l'utilisateur final n'a pas de raison particulière d'avoir accès au code : seul le résultat lors de l'exécution de celui-ci sera visible. La lisibilité et l'organisation du code sont donc à la discrétion et aux préférences du codeur/de l'équipe de codeurs. Dans le cas d'une librairie, il s'agit d'un code destiné à d'autres développeurs. Il doit donc être cohérent et accessible au plus grand nombre, ne pas empiéter sur des noms communs de fonctions et de variables et offrir une grande configuration. De plus, il s'agit d'une librairie libre et ouverte, le code doit donc être propre, clair et documenté afin de pouvoir être modifiable facilement.

Il est donc nécessaire d'apporter un grand soin à l'organisation et au style de codage.

Flexibilité et généricité

Le deuxième problème rencontré a été lié à la flexibilité du code. En effet, bien que développé pour la famille de processeurs RZA1, la librairie devait dans la mesure du possible être portable vers d'autres cibles afin de toucher le plus grand nombre. De plus, même en utilisant un RZA1, le type d'écran concerné (protocole de communication, résolution...) reste indéterminé. Il était donc nécessaire d'organiser le code de manière à dépendre le moins possible du matériel.

Un second élément lié à la flexibilité est l'ajout de fonctionnalité a posteriori. En effet, s'il est possible de penser à un certain nombre de primitives nécessaires pour une librairie graphique, il existe tellement d'utilisation potentiel qu'il n'est pas possible de tout prévoir. Il faut donc à la fois posséder toutes les primitives de bases sur lesquelles peuvent se reposer des constructions plus complexes, mais également laisser un moyen simple et efficace d'ajouter des fonctionnalités sans avoir besoin de se plonger dans le fonctionnement de la librairie.

Organisation du code


Afin d'être sûr que l'organisation du code soit cohérente dans la totalité de la librairie, il a été décidé de partir d'une base saine, et d'écrire la totalité de la librairie. De cette manière, toutes les conventions de nommage et de paramètres pouvait être normalisées dans le code. De plus, afin d'éviter des conflits de noms avec d'autres librairies, tous les éléments définis dans la librairie ont été préfixés par "sr", pour ScreenR.

Ensuite, dans l'optique de simplifier l'utilisation de la librairie, une organisation spéciale du code a été pensée afin de limiter le nombres de fonctions à appeler. La première étape de cette réflexion a été de créer les briques de bases pour construire la librairie.

Les briques de bases

Si l'on regarde dans sa globalité l'action d'une librairie graphique, on voit qu'elle doit permettre de dessiner sur un afficheur graphiques différents types de formes et de couleurs. Ceci peut paraître vaste, mais en y regardant de plus près, on remarque que la base d'une librairie se résume en une phrase : "je dois être capable de sélectionner un pixel sur l'écran, et d'y envoyer une couleur". Tout le reste repose sur cette fonctionnalité : les fonctions plus complexes permettront simplement de savoir quel pixel, et quelle couleur.

Les trois briques principales sont donc : les couleurs, les pixels et l'*écran*.

La première brique a donc été la gestion des couleurs. Il était nécessaire de facilement manipuler des couleurs, les créer, les comparer et les utiliser. Le premier type codé dans la librairie a donc été sr_Color. Ce type permet de gérer des couleurs au format classique RGB888. Ce format représente une couleur à l'aide de trois variables sur 8 bits : R, G et B, chacune des composantes pouvant aller de 0 à 255, et ainsi représenter un total de 16 millions de couleurs. Plusieurs fonctions ont été développées afin de récupérer la représentation de cette couleur dans un format exploitables par des écrans (RGB888 sur un long, RGB565 sur un short...).

Ensuite, la seconde brique a été les pixels. Pour cela, la question suivante s'est posée : un pixel est-il uniquement caractériser par ses coordonnées, ou également par sa capacité à afficher une couleur ? C'est la deuxième possibilité qui a été retenue. Plus particulièrement, le pixel a été considéré comme l'élément de base du tampon mémoire de l'écran : il s'agit de la représentation, en mémoire, de ce qui devrait être affiché sur l'écran. Etant la base de ce tampon, le pixel devait également aidé à améliorer les performances de la librairie en terme de taux de rafraîchissement et d'impact mémoire. Finalement, un type un peu particulier a été créé : sr_Pixel. Ce dernier gère en fait un carré de 4 pixels. Il stocke les quatre couleurs nécessaires, ainsi qu'une variable de statut permettant de savoir si les pixels nécessitent une mise à jour. sr_Pixel est destiné à être utilisé dans un tableau, et ses coordonnées dans ce tableau correspondent aux coordonnées du pixel sur l'écran.

Finalement, il a fallu implémenter un type gérant l'écran. Ce type repose sur les deux précédents, mais est la base visible de la librairie du point de vue de l'utilisateur. En effet, il est le point d'entrée de la librairie, l'élément sur lequel le travail va s'effectuer. Le type créé est "sr_Screen", et celui-ci contient le tableau de sr_Pixel nécessaire à la gestion de l'écran. Il contient également des informations sur l'écran lui même, telles que ses dimensions, et une fonction d'envoie de pixel faisant le lien entre la librairie et l'écran physique.

Fonctionnement de sr_Element

Une fois ces trois types définis, une quatrième brique de base apparaît : l'élément 'dessinable', ou 'drawable' en anglais. Un drawable est un élément permettant de dire à la librairie quels pixels modifier, et quelles couleurs appliquées, afin d'obtenir le résultat voulu. Il existe une grande quantité de drawable : des formes, des images, des textes... Cependant, tous les drawables ont des points communs, et ces points communs suffisent à les rendre manipulables par la librairie comme un seul type d'élément. Ce type est appelé "sr_Element", et est la base de la flexibilité de ScreenR.

En effet, tout ce qui peut être affiché sur un écran a des informations communes : quel partie de l'écran est impactée ? Que doit faire le programme si l'élément est cliqué ? A-t-il changé depuis qu'il a été dessiné ? Toutes ces informations sont stockées dans sr_Element. Cependant, un drawable a également besoin d'informations spécifiques. Pour ce faire, un système d'héritage a été mis en place pour permettre à d'autres types d'hériter de sr_Element.

Ce système d'héritage repose sur une spécificité du C en matière d'organisation de structures. Par exemple, on peut considérer les trois structures suivantes :

typedef struct {
char a;
short b;
} s1;
typedef struct {
char a;
short b;
short b2;
} s2;
typedef struct {
char a;
short b;
long b3;
}

Toutes ces structures partagent deux attributs : a et b. Ici, l'important n'est pas tellement que leurs noms soient les mêmes (cela aide juste à la lisibilité), mais plutôt que leur type et leur position soient équivalentes. Le point vraiment important est maintenant la façon dont un pointeur vers ces structures va agir sur ces attributs. Et effet, un pointeur vers s1 aura accès à "->a" et à "->b". L'adresse de ->b sera "adresse de la variable + taille prise par s1.a". Vu que s2.a est de même type que s1.a, l'adresse de ->b dans le cas d'un pointeur vers s2 sera exactement la même, et pointera également vers un short. Cela signifie plus particulièrement que le code suivant est tout à fait correct :

s2 my_s2;
s3 my_s3;
s1 * to_s1;
to_s1 = (s1 *) &my_s2;
to_s1->b = 0xAAAA; // equivalent à my_s2.b = 0xAAAA
to_s1 = (s1 *) &my_s3;
to_s1->a = 0x55, // equivalent à my_s3.a = 0x55

Ici, nous pouvons utiliser to_s1->b car s1 possède un attribut appelé b. Comme nous avons organisé nos structures correctement, il se trouve que cet argument est placé en mémoire au même endroit que se serait trouvé l'argument b d'une structure s2. Dans notre cas, il se trouve qu'il s'agit bien de l'attribut b de la variable "my_s2". Cela marche de la même manière pour l'attribut a de la variable "my_s3".

L'important ici est que, tant que nous n'avons besoin que des attributs a et b, les structures s2 et s3 peuvent être utilisées comme des pointeurs sur s1.

sr_Element repose sur cette fonctionnalité. La structure sr_Element ne contient que le strict minimum requis par un drawable pour qu'il soit ajouté à un sr_Screen puis dessiné sur un écran. De cette manière, toutes les fonctions gérant des drawables n'ont besoin d'être écrites qu'une seule fois en prenant en paramètre un pointeur vers sr_Element. Tous les drawables sont ensuite des structures commençant par les mêmes attributs qu'un sr_Element, complétées par les informations spécifiques à ce drawable. L'un des attributs de sr_Element appelé 'class' permet de connaitre le vrai type même une fois transtypé en "sr_Element *"

Ajout de fonctionnalités


Les trois briques de base définies plus haut garantissent une base saine pour la librairie permettant d'être le plus indépendant possible du matériel tout en laissant beaucoup de marges aux primitives plus élevées. La façon dont les drawables sont gérés permet quant à elle de limiter le nombre de fonctions à appeler pour utiliser la librairie et garantie donc une meilleure lisibilité des codes utilisant la librairie.

Le dernier élément manquant est la possibilité de rajouter des fonctionnalités. En effet, grâce à la structure des drawables, il est tout à fait possible d'en coder un nouveau et de l'intégrer dans la librairie. Cependant, certains utilisateurs pourraient préférer ne pas avoir à toucher au code même de la librairie pour s'assurer de ne pas corrompre son fonctionnement. Une alternative a donc été mise en place.

En effet, le point commun de tous les drawables est qu'ils possèdent une fonction qui leur est propre et permet de les dessiner. Cette fonction est appelé par la librairie en fonction de la valeur de leur attribut 'class'. Un type de drawable spécial, sr_Drawable, est le seul type a ne pas posséder de fonction de dessin par défaut. A la place, il possède un pointeur vers une fonction de dessin codable par l'utilisateur. Ainsi, un sr_Drawable peut prendre n'importe quelle fonction de dessin et assure que l'utilisateur pourra, s'il le veut, réaliser exactement le drawable dont il a besoin sans avoir à modifier la librairie.

Code d'exemple


Le développement d'un code d'exemple était essentiel à ce projet, à la fois pour valider le fonctionnement de la librairie développée, mais également pour permettre aux ingénieurs de Renesas de fournir à leurs clients une base fonctionnelle et utilisable. Ce code devait donc à le fois montrer les capacités de la librairie en terme de dessin en utilisant un maximum de fonctionnalités, mais également mettre l'accent sur la partie gérant les interactions afin que le procédé de gestion d'événements extérieurs soit illustré et exemplifié.

La démonstration fournie fonctionne sur le processeur RZA1L et a été développée sur "Stream It!", une carte de développement pour RZA1L pouvant accessoirement être équipée d'un écran LCD QVGA (320x240 pixels) recouvert d'une dalle tactile.
Elle met à profit la dalle tactile pour illustrer les interactions, et se présente comme une interface faisant penser à l'écran d'accueil d'un smartphone.

Différentes fonctionnalités de la librairie sont mise en avant. Premièrement, l'écran est séparé en trois parties : la zone principale, une barre d'action équipée d'un bouton permettant de revenir à l'accueil, et un clavier pouvant être affiché ou non. Ceci illustre la capacité de la librairie à géré des écrans virtuels pouvant agir simultanément sur le même écran physique. La démonstration présente ensuite quatre scènes accessibles depuis l'écran d'accueil en cliquant sur des icônes. Chaque scène est codée dans son propre écran virtuel afin de simplifier l'agencement des éléments.

La principale différence entre l'utilisation d'écran virtuels différents pour les trois parties de l'écran et pour les scènes repose sur la zone mémoire utilisée. En effet, les trois parties de l'écran utilisent des zones mémoires séparées et sont destinées à être utilisées simultanément. Au contraire, chaque scène est liée au même tampon mémoire, et montrent ainsi que la librairie peut efficacement recycler des ressources pour réduire son impact mémoire. Ainsi, toutes les scènes ne nécessitant pas d'affichage simultané peuvent occuper la même zone mémoire pour leur tampon de pixel.

Finalement, les quatre scènes ont été choisies pour démontrer les capacités de la librairie. La première scène présente est une zone de prise de note. Celle-ci permet de tester le clavier tactile développé en exemple, et illustre la capacité de la librairie à gérer des chaines de caractères, y compris dynamiquement et en prenant en compte des éléments tels que le retour à la ligne qu'il soit manuel ou automatique. La seconde scène est un écran vide permettant à l'utilisateur de dessiner sur l'écran en choisissant une couleur et une taille de crayon. Ceci permet de montrer comment la librairie permet de réagir non seulement aux touchers de l'utilisateur, mais aussi de stocker ses actions afin de modifier d'autres éléments. Une troisième scène permet à l'utilisateur de jouer à une version simplifier d'un jeu de type Space Invaders, et permet de mettre en avant les interactions entre objets et la fluidité de l'affichage. Finalement, une dernière scène permet, à l'aide d'un sr_Drawable, d'intégrer la sortie d'une caméra sur l'écran.

Ces quatre scènes ont pour objectif de montrer de manière simple et visuelle les capacités de la librairie.

Logiciel de support



Etat d'avancement

---
A la fin du temps imparti pour le projet, la librairie graphique développée pour Renesas est terminée et fonctionnelle, comme le valide le code de démonstration. Elle permet de fournir aux développeurs toutes les primitives nécessaires à la création d'interfaces graphiques, aussi bien au niveau de la gestion des formes et des textes, mais également des réactions à des événements extérieurs comme l'utilisation d'un écran tactile.

La librairie est également accompagnée d'un logiciel de support permettant d'en tirer pleinement parti notamment grâce à sa documentation, mais également à des outils permettant de facilement utiliser des images et des polices de caractères variées.


Bilan


Le développement d'une librairie graphique pour microcontrôleur est un sujet vaste faisant appel à de nombreuses notions de programmation et d'utilisation de processeurs. Etant donné qu'il s'agît d'un code destiné à être utilisé par des développeurs et non simplement exécuté sur une machine, son développement implique aussi une forte cohérence dans le type de programmation et le choix des dénominations.

D'un point de vue technique, ce projet aura permis de mettre en pratique de nombreux aspects du langage C jusqu'alors peu abordés en cours, en nécessitant par exemple une forte utilisation de types composés et surtout de pointeurs. Il s'agît également d'un code pouvant rapidement avoir un impact mémoire conséquent (représentation en mémoire de l'écran, structure de gestion de chaines de caractères, stockage des images et polices...), ce qui implique la nécessité de choisir judicieusement les types mais également les espaces mémoires à utiliser.

Sur le plan personnel, il a été très intéressant d'apprendre les mécanismes du C utilisés dans ce développement, et de valider que la puissance de calcul des microprocesseurs récents permet non seulement de gérer efficacement des afficheurs graphiques, mais également de créer des interfaces complètes fonctionnelles et réactives.


Notes d'application


Mécanismes avancés du C et leur application dans la rédaction de librairies génériques

Note d'application

note_d_application.pdf (560 KB) axel BARRIEUX, 03/31/2021 03:06 PM