Avant de se plonger dans la programmation et découvrir comment afficher et animer des images avec la META, nous devons examiner le périphérique d’affichage de la console et comprendre ce qui est mis à notre disposition par la bibliothèque Gamebuino-META pour exploiter ses capacités.
La META incorpore un écran TFT 1.8” très répandu, le ST7735R, que l’on retrouve sur bon nombre de périphériques électroniques, y compris sur d’autres consoles de jeux programmables comme le PyGamer d’Adafruit.
Ce petit écran dipose d’une mémoire interne pouvant stocker des informations caractérisant jusqu’à 162x132 pixels avec 18 bits de profondeur pour coder la couleur de chaque pixel. Plusieurs modes d’affichage sont disponibles, avec trois espaces colorimétriques différents : RGB444, RGB565 et RGB666. La META a été configurée pour exploiter une résolution de 160x128 pixels dans l’espace colorimétrique RGB565 (5 bits pour le rouge, 6 bits pour le vert et 5 bits pour le bleu), c’est-à-dire avec une profondeur de 5+6+5 = 16 bits. Par conséquent, chaque pixel de l’écran peut endosser une couleur parmi 216 = 65 536… Ce qui est déjà pas mal, et nous laisse le champ libre pour exprimer notre créativité. Voici un petit aperçu de la diversité des couleurs que l’on peut obtenir sur la META :
Les nuances entre ces différentes spécifications n’évoquent peut-être pas encore grand chose dans ton esprit, mais rassure-toi, on va éclaircir tout ça par la suite…
L’exploitation directe d’un écran TFT comme le ST7735R est relativement complexe et nécessite d’intégrer de nombreuses notions qui sont nécessaires pour parcourir et comprendre sa documentation technique. Pour nous faciliter la tâche, des développeurs expérimentés ont pris la peine de coder des bibliothèques logicielles spécialisées, qui servent d’interfaces avec ces périphériques complexes, et nous simplifient donc considérablement les choses.
La bibliothèque Gamebuino-META fournit une API consistante dédiée à l’affichage et, en particulier, à l’affichage des images : gb.display
. Cette API permet de gérer 3 modes d’affichage spécifiques, parmi les 4 disponibles sur la META :
Mode d’affichage | Résolution | Espace colorimétrique | |
---|---|---|---|
1 | DISPLAY_MODE_RGB565 | 80x64 | 216 couleurs |
2 | DISPLAY_MODE_INDEX | 160x128 | 16 couleurs indexées |
3 | DISPLAY_MODE_INDEX_HALFRES | 80x64 | 16 couleurs indexées |
4 | Mode réservé aux initiés | 160x128 | 216 couleurs |
Le 4e mode est un peu plus complexe à gérer, et s’adresse aux plus expérimentés d’entre nous, car il ne peut être exploité avec l’API gb.display
. Pour utiliser ce mode, il faudra plutôt se tourner vers une autre API, qui est aussi fournie par la bibliothèque Gamebuino-META : gb.tft
. C’est d’ailleurs une adaptation de la bibliothèque Adafruit-ST7735-library d’Adafruit. L’exploitation de l’API gb.tft
dépasse le cadre de ce tutoriel et nous ne l’aborderons pas ici.
Bien, mais alors pourquoi distinguer tous ces modes d’affichage ? Qu’est-ce qui fait leur spécificité et comment savoir lequel choisir ?
Prenons l’exemple du mode d’affichage n°4 qui nous permet d’exploiter pleinement les capacités du ST7735R sur la META. Dans ce mode, l’affichage est discrétisé par une matrice de 160x128 pixels dans l’espace colorimétrique RGB565. C’est à dire que 5+6+5 = 16 bits sont nécessaires pour stocker le code binaire de la couleur de chaque pixel.
Le référentiel spatial et colorimétrique de l’écran se présente de la façon suivante :
Chaque pixel est caractérisé par :
L’origine du référentiel spatial se trouve dans le coin supérieur gauche, aux coordonnées (0, 0).
Afficher un pixel à l’écran, aux coordonnées (x, y), revient à affecter une couleur à cette unité de surface. Autrement dit, nous aurons besoin d’un espace mémoire de 2 octets pour écrire et stocker les 16 bits du code couleur associé à ce pixel. Dans la mesure où la couleur de chaque pixel devra être stockée en mémoire, on peut en déduire la taille de cet espace de la façon suivante :
160 x 128 x 16 bits = 160 x 128 x 2 octets = 40 960 octets = 40 ko
Cet espace mémoire correspond à une zone de préparation de l’affichage que l’on appelle un tampon graphique. Cette zone sert à collecter les informations qui devront ensuite être transmises au contrôleur d’affichage pour être finalement visibles à l’écran et se traduire par une coloration particulière de chaque unité lumineuse qui compose l’écran. L’écran TFT fait partie de la famille des écrans LCD dans lesquels chaque pixel est commandé par un minuscule semi-conducteur qui contrôle la quantité de lumière devant passer. La lumière est émise à l’aide de 3 diodes électroluminescentes : une pour le rouge, une pour le vert et une pour le bleu. C’est le mélange de ces 3 couleurs primaires, avec des niveaux d’intensité lumineuse différenciés qui permet d’obtenir la couleur désirée, par composition additive.
Le tampon graphique servant à préparer l’affichage est nécessairement stocké en RAM durant l’exécution du programme, et est susceptible de changer d’état à tout moment, au fil de l’exécution des instructions qui viendront le modifier dynamiquement pour afficher ce que tu souhaites à l’écran, au moment où tu le souhaites.
La bibliothèque Gamebuino-META se charge elle-même de gérer ce tampon graphique et expose des routines simplifiées qui te permettent facilement de tracer une ligne, dessiner un rectangle ou un cercle, ou même carrément reproduire une image que tu auras dessinée dans ton éditeur de sprite préféré. C’est justement le rôle de l’API spécialisée gb.display
. Elle se charge de traduire les directives graphiques que tu invoques par la mise à jour appropriée de l’état du tampon graphique. Tu n’as pas à t’en soucier, même s’il reste toujours possible d’écrire directement sur ce tampon si tu sais comment faire.
Bien, revenons à notre petit calcul… Si l’on souhaite exploiter la totalité de l’espace colorimétrique RGB565 avec une résolution de 160x128 pixels, nous avons besoin d’un tampon graphique de 40 ko… Or, tu n’es pas censé ignorer que la META ne dispose, en tout et pour tout, que de 32 ko de RAM. On est donc confronté à un problème de taille : comment faire rentrer 40 ko dans 32 ko ?
Le tampon graphique de gb.display
ne le permet pas, tout simplement. C’est précisément pour cette raison que ce mode d’affichage ne peut être géré par l’API gb.display
. Ce n’est pas impossible dans l’absolu… mais il faut ruser… et passer par l’API gb.tft
, beaucoup plus rudimentaire, et donc plus compliquée à exploiter. C’est la raison pour laquelle ce mode d’affichage est réservé aux initiés.
Bon, et qu’en est-il des autres modes ?
C’est le mode d’affichage par défaut (qui sera appliqué automatiquement si tu n’en spécifies aucun). Dans ce mode, pour contourner la problématique de l’espace mémoire insuffisant, la surface du référentiel spatial de l’écran est divisée par 4 en divisant ses dimensions horizontale et verticale par 2. Le tampon graphique est donc réduit à un espace mémoire permettant de gérer un affichage dans une résolution de 80x64 pixels, toujours dans l’espace colorimétrique RGB565, donc avec une profondeur de 16 bits par pixel :
80 x 64 x 16 bits = 80 x 64 x 2 octets = 10 240 octets = 10 ko
Surface de pixels divisée par 4 ⇒ taille du tampon graphique divisée par 4… logique.
L’API gb.display
se chargera automatiquement de recouvrir les 160x128 pixels de l’écran avec les 80x64 pixels du tampon graphique par simple projection. Autrement dit, chaque pixel du tampon sera reproduit sur 4 pixels de l’écran, comme le montre la figure suivante :
Ici, l’astuce est différente : on ne touche pas aux dimensions spatiales (on reste en 160x128), mais on négocie sur l’espace colorimétrique en diminuant drastiquement le nombre de couleurs disponibles. En effet, on va se limiter à une palette de 16 couleurs (chacune pouvant être choisie dans l’espace RGB565). Chaque couleur est associée à un index compris entre 0 et 15 dans une table de référence. Et c’est précisément cet index qu’on va reporter sur le tampon graphique aux coordonnées du pixel qui porte la couleur associée (1 seul octet suffit ici pour encoder un entier entre 0 et 15). Mais l’astuce va plus loin : pour coder un nombre entier entre 0 et 15, en réalité, on a juste besoin de 4 bits (24 = 16 possibilités, donc 16 couleurs), n’est-ce pas ? Or, sur un octet (8 bits), on peut stocker 2 paquets de 4 bits. Autrement dit, avec un seul octet, on peut coder les couleurs respectives de 2 pixels voisins ! Du coup, ça permet encore de diviser par 2 le nombre d’octets nécessaires pour encoder les couleurs des 160x128 pixels, puisqu’on les code par paires de 4 bits. Par conséquent :
160 x 128 x 4 bits = 160 x 128 x ½ octets = 10 240 octets = 10 ko
Et on retombe sur nos pattes, comme dans le cas précédent.
Ici, le raisonnement est le même avec une surface d’affichage divisée par 4 (on repasse en 80x64) :
80 x 64 x 4 bits = 80 x 64 x ½ octets = 2 560 octets = 2.5 ko
Sans surprise : surface divisée par 4 ⇒ taille du tampon divisée par 4.
La recopie sur l’écran se fait par projection, comme dans le cas de DISPLAY_MODE_RGB565
.
Pour résumer, voici les contraintes techniques que tu dois garder en tête avant de choisir le mode d’affichage le plus approprié pour réaliser ton jeu. En effet, tu dois le choisir une bonne fois pour toutes, dès le départ. Il sera difficile de revenir en arrière…
Mode d’affichage | Résolution | Espace colorimétrique | Taille du tampon |
---|---|---|---|
DISPLAY_MODE_RGB565 | 80x64 | 216 couleurs | 10 ko |
DISPLAY_MODE_INDEX | 160x128 | 16 couleurs indexées | 10 ko |
DISPLAY_MODE_INDEX_HALFRES | 80x64 | 16 couleurs indexées | 2.5 ko |
Mode réservé aux initiés | 160x128 | 216 couleurs | 40 ko |
Attention
Avec les modes d’affichage à couleurs indexées, tu n’as que 16 couleurs en tout et pour tout ! Ça veut dire que tous les éléments graphiques de ton jeu devront partager l’unique palette de couleurs que tu auras définie. Donc, n’oublie pas d’en tenir compte au moment où tu dessineras tes sprites et les éléments de décor de ton jeu…
Et oui… on économise de l’espace mémoire, mais on perd du même coup le potentiel des couleurs qu’offre l’espace RGB565…
Pour démarrer notre projet, je te propose de créer un nouveau sketch avec l’IDE Arduino, et de le nommer my-stunning-game
. Ceci aura pour effet de générer le dossier suivant :
L’IDE Arduino impose de nommer le dossier du projet avec le même nom que le sketch principal.
Le mode d’affichage doit être défini dès le départ, quand tu démarres ton projet. Pour cela, tu dois créer un fichier de configuration, à la racine de ton projet, nommé config-gamebuino.h
.
Tu dois ensuite y définir la macro DISPLAY_MODE
, en lui associant la valeur correspondant au mode d’affichage que tu souhaites appliquer à ton projet. La bibliothèque Gamebuino-META fournit les trois macros suivantes pour cela :
DISPLAY_MODE_RGB565
DISPLAY_MODE_INDEX
DISPLAY_MODE_INDEX_HALFRES
Pour appliquer le mode d’affichage 80x64 à 216 couleurs (RGB565 au complet), déclare simplement :
config-gamebuino.h
#define DISPLAY_MODE DISPLAY_MODE_RGB565
Il s’agit ici du mode par défaut. Donc si ton projet ne comporte pas de fichier config-gamebuino.h
, ou si la macro DISPLAY_MODE
n’y est pas précisée, alors le mode d’affichage sera automatiquement défini par DISPLAY_MODE_RGB565
.
Pour appliquer le mode d’affichage 160x128 à 16 couleurs indexées, il faudra déclarer :
config-gamebuino.h
#define DISPLAY_MODE DISPLAY_MODE_INDEX
Dernière possibilité, pour appliquer le mode d’affichage 80x64 à 16 couleurs indexées, déclare :
config-gamebuino.h
#define DISPLAY_MODE DISPLAY_MODE_INDEX_HALFRES
Voici un petit code de démonstration pour vérifier l’espace mémoire disponible en RAM :
my-stunning-game.ino
#include <Gamebuino-Meta.h>
const char *FORMAT = "FREE RAM: %u";
void setup() {
gb.begin();
}
void loop() {
gb.waitForUpdate();
gb.display.clear();
gb.display.setFontSize(1);
gb.display.printf(2, 2, FORMAT, gb.getFreeRam());
}
Teste le dans chacun des modes d’affichage, et tu devrais obtenir les résultats suivants :
Les valeurs observées sont identiques pour DISPLAY_MODE_RGB565
et DISPLAY_MODE_INDEX
, ce qui est logique puisque la taille du tampon graphique est identique dans les deux cas. Par contre, si tu calcules la différence entre la valeur observée pour DISPLAY_MODE_INDEX_HALFRES
et celle pour les deux cas précédents, tu remarqueras que :
22 839 - 15 159 = 7 680 octets = 7.5 ko
Ce différentiel correspond exactement à la différence de taille de leurs tampons respectifs :
10 ko - 2.5 ko = 7.5 ko
Maintenant que les choses sont plus claires concernant le matériel de la META et sur les possibilités offertes par l’API gb.display
, nous allons voir dans le prochain chapitre comment préparer les images que l’on souhaitera afficher à l’écran.