Architecture générale & le PIC16F18877
Vue d'ensemble du système, sous-systèmes et flux de données, puis le microcontrôleur central et sa configuration via MPLAB Code Configurator.
Présentation générale
Ce chapitre présente une vue d'ensemble du système réalisé pour le jeu de type Asteroids embarqué. L'objectif est de décrire comment les différents éléments matériels s'organisent autour d'un microcontrôleur unique, comment l'information circule entre eux et selon quel principe logiciel le jeu est piloté. Cette description constitue le socle sur lequel s'appuieront les chapitres suivants, plus détaillés, consacrés au logiciel graphique, aux mathématiques du jeu et à la carte son.
Le système est construit autour d'un microcontrôleur PIC16F18877 de Microchip, un composant 8 bits en boîtier 40 broches PDIP, qui joue le rôle de chef d'orchestre. Toute l'intelligence du jeu réside dans ce seul circuit : il lit les commandes du joueur, calcule la position des objets à l'écran, dessine l'image et déclenche les sons. Le microcontrôleur fonctionne sur son oscillateur interne HFINTOSC à une fréquence FOSC de 32 MHz, ce qui correspond à un cycle d'instruction de FOSC/4 = 8 MHz, soit un temps de cycle Tcy de 125 ns. Il faut garder à l'esprit que ce PIC16 ne possède pas d'unité de calcul en virgule flottante matérielle : les opérations sur les nombres réels (cos, sin, sqrt) sont donc émulées de manière logicielle et restent lentes. Cette contrainte est le fil rouge de toute la conception, comme on le verra dans les chapitres dédiés au logiciel.
Le PIC16F18877 est un microcontrôleur 8 bits dépourvu d'unité de calcul flottant. Tout le programme est conçu pour ménager ses ressources : éviter les flottants, précalculer les constantes trigonométriques et comparer des distances au carré plutôt que d'extraire des racines carrées.
Autour de ce cœur, on distingue quatre sous-ensembles reliés au PIC :
- l'écran TFT ILI9488, qui affiche le jeu, piloté par le bus SPI ;
- la manette de commande (joystick à deux axes plus boutons poussoirs), lue par le convertisseur analogique-numérique et des entrées tout-ou-rien ;
- la carte son à base de LM386, attaquée par la sortie du périphérique NCO1 ;
- l'alimentation 5 V, qui fournit l'énergie à l'ensemble.
Le schéma bloc ci-dessous résume cette organisation et place le PIC au centre des échanges.
Les sous-systèmes
Module de contrôle (le PIC16F18877)
C'est l'unité centrale du système. Programmé en langage C sous l'environnement MPLAB X IDE avec le compilateur XC8, il a vu l'initialisation de ses périphériques générée à l'aide de l'outil MCC (MPLAB Code Configurator). Le microcontrôleur exécute en continu la boucle de jeu : lecture des entrées, mise à jour de l'état des objets, rendu graphique et gestion du son. Il est alimenté en +5 V (pattes 11 et 32), sa masse VSS est reliée aux pattes 12 et 31, et son entrée de réinitialisation MCLR se trouve sur la patte 1. Le recours à MCC est un choix méthodologique important : l'outil produit automatiquement les fonctions d'initialisation (SYSTEM_Initialize, configuration de l'ADCC, ouverture du SPI1, définition du NCO1 et des macros de broches), ce qui fiabilise le paramétrage des registres et laisse au binôme le temps de se concentrer sur la logique du jeu.
Module d'affichage (l'écran TFT ILI9488)
L'écran est un afficheur couleur de résolution native 320x480 pixels, utilisé ici en mode paysage (setRotation(3)) soit 480x320 pixels exploitables. La couleur est transmise sur 18 bits, sous la forme de trois octets correspondant aux composantes rouge, verte et bleue. L'écran est commandé par le bus SPI1 (périphérique MSSP1) configuré en maître, en mode SPI 0. La communication est à sens unique : seul le PIC écrit vers l'écran via la ligne MOSI. La ligne MISO de retour existe physiquement sur le module mais n'est pas exploitée par le programme, ce qui simplifie le câblage et le code. Au-delà des trois lignes du bus série proprement dit (SCK, MOSI et la sélection de boîtier CS), l'afficheur réclame deux signaux de contrôle particuliers : la ligne DC (Data/Command), qui indique si l'octet transmis est une commande ou une donnée, et la ligne RST de réinitialisation matérielle, utilisée lors de la séquence d'initialisation.
Module de commande (la manette)
Le joueur agit sur le jeu au moyen d'un joystick analogique à deux axes et de boutons poussoirs. Les deux axes du joystick sont lus par le convertisseur ADCC sur 10 bits, fournissant des valeurs comprises entre 0 et 1023 : l'axe X est câblé sur RC7 (patte 22) et l'axe Y sur RC6 (patte 21). Les boutons poussoirs sont actifs à l'état bas, grâce à une résistance de tirage (pull-up) : un bouton relâché renvoie 1, un bouton appuyé renvoie 0. Le bouton B (RD6, patte 29) commande le tir, le bouton C (RD5, patte 28) sert à jouer, revenir ou retourner au menu, et le bouton D (RD4, patte 27) ouvre les réglages. Le bouton A (RD7, patte 30) n'est pas utilisé dans cette version.
Module audio (la carte son)
Le son est produit par le périphérique NCO1 (Numerically Controlled Oscillator) du PIC, dont la sortie est dirigée vers la patte RA6 (patte 14). Ce signal carré est ensuite amplifié par une carte son dédiée, conçue spécifiquement pour le projet sous KiCad 9. Il s'agit d'un amplificateur audio classique à base du circuit intégré LM386 (boîtier DIP-8), avec réglage de volume par un potentiomètre de 10 kOhm en entrée et un réseau de stabilisation en sortie. Le gain de l'étage est fixé à 20, valeur par défaut du LM386 obtenue en laissant les broches 1 et 8 non pontées. La carte, de format 45 mm x 45 mm en double face, regroupe quatre blocs fonctionnels : l'alimentation et son découplage, l'étage d'entrée avec couplage et réglage de volume, l'amplification proprement dite et l'étage de sortie. La photographie de rendu 3D ci-dessous montre cette carte avant brasage.
Flux de données et interconnexions
Le fonctionnement du système repose sur une circulation ordonnée de l'information entre la manette, le PIC, l'écran et la carte son. On peut le décrire selon le sens des échanges.
En entrée, la position du joystick est transformée par le convertisseur analogique-numérique ADCC en deux valeurs numériques (axe X et axe Y). Le programme exploite ces valeurs pour déterminer directement la direction de visée du vaisseau, sans recourir à des fonctions trigonométriques coûteuses : le vecteur issu du joystick, une fois normalisé, fournit déjà le couple (cos, sin) de l'angle de pointage. Toujours en entrée, les boutons poussoirs sont lus comme de simples entrées tout-ou-rien, et leur état bas déclenche les actions correspondantes (tir, navigation entre les écrans, accès aux réglages).
En sortie, le PIC produit deux flux distincts. Le premier est le flux vidéo : le microcontrôleur envoie, via le bus SPI, les commandes et les données de pixels vers l'écran ILI9488, qui affiche le jeu. Le second est le flux audio : le PIC pilote le périphérique NCO1, dont le signal sort sur RA6, traverse la carte son (couplage d'entrée, réglage de volume, amplification par le LM386, réseau de Zobel et couplage de sortie) avant d'attaquer le haut-parleur. L'alimentation 5 V, transverse, fournit l'énergie à l'ensemble et n'intervient pas dans le traitement de l'information.
Le périphérique NCO1 mérite une précision sur le plan numérique. Sa source d'horloge est FOSC/4, soit 8 MHz, et son accumulateur est codé sur 20 bits. La fréquence de sortie obéit à la relation :
f = (8 000 000 x NCO1INC) / 2^20 = NCO1INC x 7,6294 Hz
Pour calculer rapidement la valeur à charger dans le registre, le code écrit NCO1INC = frequence >> 3, c'est-à-dire une division par 8 par simple décalage de bits. Le ton effectivement joué vaut alors environ frequence x 0,9537, donc très proche de la fréquence demandée. Le décalage >>3 (qui revient à multiplier par 0,125) est une approximation rapide du facteur exact 0,1311, choisie parce qu'un décalage de bits s'exécute en une instruction là où une vraie division serait nettement plus coûteuse sur ce microcontrôleur 8 bits. On retrouve ici le souci constant de ménager un processeur lent.
Le tableau ci-dessous récapitule l'ensemble des liaisons entre le PIC et ses sous-systèmes.
| Sous-système | Type de liaison | Broches du PIC (patte) |
|---|---|---|
| Écran ILI9488 | Bus SPI1 (maître, mode 0) | CS=RC0(15), RST=RC1(16), DC=RC2(17), SCK=RC3(18), MOSI=RC5(20) |
| Joystick (axes analogiques) | Entrées ADC 10 bits | X=RC7(22), Y=RC6(21) |
| Boutons poussoirs | Entrées TOR, actives à l'état bas (pull-up) | B/TIR=RD6(29), C/MENU=RD5(28), D/RÉGLAGES=RD4(27), A non utilisé=RD7(30) |
| Carte son | Sortie NCO1 | SON=RA6(14) |
| Alimentation | Continue 5 V | VDD=11 et 32, VSS=12 et 31, MCLR=1 |
Principe de fonctionnement du jeu et machine à états
Le jeu reprend le principe d'Asteroids adapté à la contrainte d'un affichage centré. Le vaisseau, représenté par un triangle, reste fixe au centre de l'écran (coordonnées CX=240, CY=172) et ne fait que pivoter sur place pour viser, sous la commande du joystick. Lorsque le joueur appuie sur le bouton de tir, le vaisseau émet des projectiles dans la direction visée. Des astéroïdes, dessinés sous forme d'octogones, surgissent des bords de l'écran et foncent vers le centre. Détruire un astéroïde rapporte 10 points au score ; en revanche, si un astéroïde atteint le vaisseau, la partie est perdue.
L'ensemble du déroulement est organisé par une machine à états comportant quatre écrans distincts, décrits par le type énuméré EtatJeu :
ETAT_MENU: l'écran d'accueil, où le joueur peut lancer une partie ou consulter les réglages ;ETAT_REGLAGES: l'écran qui rappelle les commandes du jeu ;ETAT_JEU: la partie proprement dite, où s'exécute la boucle de rendu et de simulation ;ETAT_GAMEOVER: l'écran de fin de partie, affichant le résultat.
La déclaration de ce type énuméré se présente ainsi :
typedef enum { ETAT_MENU, ETAT_REGLAGES, ETAT_JEU, ETAT_GAMEOVER } EtatJeu;Cette ligne définit un nouveau type EtatJeu qui ne peut prendre que l'une des quatre valeurs énumérées. En interne, le compilateur leur attribue des entiers consécutifs (0 à 3), ce qui permet de stocker l'état courant dans une simple variable de petite taille et de comparer ou commuter dessus très efficacement. Ce choix évite de manipuler des chaînes de caractères ou des indicateurs multiples, et reste donc économe en RAM et en temps de calcul, conformément au fil rouge du projet.
La navigation entre ces états s'effectue au moyen des boutons C (jouer, retour, menu) et D (réglages). Cette structure en machine à états permet de séparer clairement les différentes phases de l'application et de n'exécuter, à chaque instant, que le code pertinent pour l'écran courant. Elle constitue l'ossature logicielle du programme, dont le détail (boucle de jeu, rendu incrémental, détection des collisions) sera développé dans les chapitres suivants. Conformément au fil rouge du projet, cette organisation vise toujours le même objectif : tirer le meilleur parti d'un microcontrôleur 8 bits aux ressources limitées, en évitant les calculs inutiles et en ne redessinant que ce qui change réellement à l'écran. Cette dernière idée, le rendu incrémental, est essentielle : plutôt que de repeindre toute l'image à chaque tour de boucle (ce qui saturerait le bus SPI), le programme efface, déplace puis redessine uniquement les objets ayant changé de position, ce qui limite le volume de données transmis à l'écran et préserve la fluidité du jeu.
Présentation du PIC16F18877
Le cœur du système embarqué est le microcontrôleur PIC16F18877 du fabricant Microchip. C'est lui qui exécute le programme du jeu, lit le joystick et les boutons, pilote l'écran TFT et génère le son. Avant d'aborder le logiciel proprement dit, ce chapitre décrit le composant choisi, son horloge, les périphériques matériels mobilisés et l'outil de configuration MPLAB Code Configurator (MCC) qui en facilite la mise en œuvre. Cette présentation est essentielle pour comprendre une contrainte qui structure tout le reste du projet : le PIC16F18877 est un microcontrôleur 8 bits relativement lent, dépourvu d'unité de calcul flottant, et l'ensemble du programme est conçu pour ménager ses ressources.
Le PIC16F18877 appartient à la famille PIC16F1 de Microchip. Il s'agit d'un microcontrôleur 8 bits, ce qui signifie que son unité de traitement manipule nativement des données codées sur 8 bits (un octet). Le composant retenu se présente dans un boîtier de 40 broches au format PDIP (Plastic Dual In-line Package), c'est-à-dire un boîtier traversant à deux rangées de pattes, parfaitement adapté au prototypage sur carte à souder. Ce grand nombre de broches offre un confort appréciable : il reste suffisamment d'entrées/sorties pour câbler simultanément l'écran (bus SPI plus les lignes de contrôle), le joystick analogique (deux axes), les boutons poussoirs et la sortie son, sans avoir à multiplexer les signaux entre plusieurs fonctions.
Comme tout microcontrôleur, le PIC16F18877 intègre sur une même puce un processeur, de la mémoire et des périphériques. Son architecture est de type Harvard : la mémoire programme et la mémoire de données sont physiquement séparées et accessibles par des bus distincts. La mémoire programme est une mémoire Flash non volatile, dans laquelle le code reste gravé après coupure de l'alimentation ; la mémoire de données (RAM) sert aux variables pendant l'exécution et perd son contenu à la mise hors tension. Ces deux mémoires sont limitées au regard d'un ordinateur classique, ce qui impose une certaine sobriété. C'est notamment pour économiser la RAM que le code emploie systématiquement des types entiers de taille fixe (uint8_t, uint16_t, int8_t) issus de <stdint.h> : choisir explicitement un entier sur 8 bits là où un octet suffit (par exemple l'indicateur active d'un projectile) évite de consommer inutilement des octets précieux. Le jeu d'instructions du cœur est par ailleurs réduit : c'est un processeur de type RISC, qui ne dispose que d'opérations élémentaires sur des entiers (chargement, addition, décalage, opérations logiques, branchements). Il n'existe aucune instruction matérielle de multiplication étendue ou de calcul flottant : toute opération plus complexe est décomposée en une suite d'instructions simples par le compilateur.
Le PIC16 ne sait pas calculer directement sur des nombres à virgule flottante : chaque opération en float (addition, multiplication, et plus encore cos, sin, sqrt) est émulée par une bibliothèque logicielle, donc lente, car traduite en de longues séquences d'instructions entières. On cherche en permanence à éviter les calculs flottants pendant le jeu, à précalculer les constantes trigonométriques et à comparer des distances au carré plutôt que d'extraire des racines carrées.
L'horloge du système
Le rythme d'exécution du microcontrôleur est donné par son horloge. Le projet utilise l'oscillateur interne haute fréquence HFINTOSC, ce qui évite d'ajouter un quartz externe et simplifie la carte. La fréquence de l'oscillateur, notée FOSC, est réglée à 32 MHz.
Sur l'architecture PIC16, une instruction ne s'exécute pas en un seul battement d'horloge : le cycle d'instruction vaut FOSC divisé par 4. On a donc :
F_cycle = FOSC / 4 = 32 MHz / 4 = 8 MHzLa durée d'un cycle d'instruction, notée Tcy, est l'inverse de cette fréquence :
Tcy = 1 / 8 MHz = 125 nsAutrement dit, le cœur exécute la majorité de ses instructions en 125 nanosecondes. Cela peut paraître rapide, mais il faut le comparer au coût d'un calcul flottant : une fonction comme cos ou sqrtf, émulée en logiciel sur 8 bits, demande un grand nombre de cycles. À 8 MHz, répéter de tels calculs à chaque tour de la boucle de jeu et pour chaque objet à l'écran coûterait beaucoup trop cher en temps et ferait chuter la fluidité. C'est la conséquence directe et concrète de l'horloge choisie : la rapidité perçue du jeu dépend de notre capacité à remplacer les flottants par des entiers et par des valeurs précalculées. On retrouve ainsi le même fil conducteur que pour l'absence de FPU. À noter que cette horloge de 8 MHz (FOSC/4) sert également de source de cadencement au périphérique de son, comme on le verra plus bas.
Les périphériques utilisés et configurés via MCC
Le PIC16F18877 embarque de nombreux périphériques matériels. Quatre d'entre eux, plus les ports d'entrée/sortie, sont mis à profit dans ce projet ; tous sont paramétrés à l'aide de MCC.
- L'oscillateur : configuré pour fournir les 32 MHz du HFINTOSC interne décrits ci-dessus. C'est la base de temps de tout le système, dont découlent à la fois le cycle d'instruction et l'horloge des périphériques.
- Le module SPI1 (MSSP1) : il pilote l'écran TFT ILI9488. Il est configuré en mode Maître (c'est le PIC qui impose l'horloge SCK et cadence les échanges) et en SPI Mode 0 (polarité d'horloge au repos à l'état bas et échantillonnage sur le premier front, conformes à ce qu'attend l'écran). La communication est à sens unique, du PIC vers l'écran : seule la ligne MOSI transporte des données. La ligne MISO de l'écran existe physiquement mais n'est pas exploitée par le programme, car le jeu n'a jamais besoin de relire l'état de l'afficheur. Le SPI étant un bus série cadencé matériellement, le module se charge de sérialiser chaque octet bit à bit à la fréquence SCK : cela permet d'envoyer rapidement les nombreux octets de couleur nécessaires au remplissage des formes, en confiant au périphérique un travail qui serait beaucoup trop lent à réaliser bit par bit en logiciel.
- Le convertisseur ADCC : il numérise les deux tensions issues du joystick analogique. La conversion se fait sur 10 bits, ce qui donne des valeurs entières comprises entre 0 et 1023 pour chaque axe. L'axe X est lu sur la broche RC7 et l'axe Y sur la broche RC6. Le convertisseur étant unique, le programme sélectionne tour à tour le canal voulu avant chaque conversion : on lit ainsi successivement les deux axes à partir d'un seul bloc analogique-numérique.
- Le NCO1 (Numerically Controlled Oscillator) : ce périphérique génère le son sur la broche RA6. Sa source d'horloge est FOSC/4, soit 8 MHz. Le NCO fonctionne par accumulation sur 20 bits : à chaque cycle il ajoute une valeur d'incrément NCO1INC à un accumulateur de 20 bits, et c'est le débordement périodique de cet accumulateur qui fait basculer la sortie, produisant une fréquence proportionnelle à l'incrément. La relation est :
f = (8 000 000 x NCO1INC) / 2^20 = NCO1INC x 7,6294 HzPour obtenir un ton donné sans calcul flottant, le code écrit NCO1INC = frequence >> 3, c'est-à-dire une division par 8 réalisée par un simple décalage de bits (opération très rapide pour le cœur 8 bits, qui évite une multiplication ou une division logicielle). Le ton effectivement joué vaut alors environ frequence x (8e6 / (8 x 2^20)), soit frequence x 0,9537 : on reste très proche de la fréquence demandée. Le décalage >>3 (diviser par 8, c'est-à-dire multiplier par 0,125) est en réalité une approximation rapide du facteur exact 0,1311, jugée parfaitement suffisante pour un effet sonore de jeu où la justesse absolue n'a pas d'importance. On reconnaît encore le fil rouge : remplacer une division flottante exacte par un décalage entier approché, mais quasi gratuit.
- Les ports d'entrée/sortie : ils gèrent les boutons poussoirs et les lignes de contrôle de l'écran. Les boutons sont configurés en entrée avec résistance de tirage interne (pull-up), si bien qu'au repos la broche lit 1 et qu'à l'appui elle lit 0 : c'est une logique active à l'état bas. Les lignes de contrôle de l'écran (CS pour la sélection, RST pour le reset, DC pour distinguer commande et donnée) sont configurées en sortie numérique. Inversement, les broches du joystick (RC6 et RC7) sont configurées en entrées analogiques afin d'être routées vers le convertisseur.
Le rôle de MPLAB Code Configurator (MCC)
La chaîne de développement repose sur l'environnement MPLAB X IDE, le compilateur XC8 et l'outil MPLAB Code Configurator (MCC). MCC est un assistant graphique : plutôt que d'écrire à la main les valeurs de tous les registres de configuration des périphériques (oscillateur, SPI, ADCC, NCO, broches), on les règle dans une interface visuelle, et MCC génère automatiquement le code d'initialisation correspondant dans le dossier mcc_generated_files.
Concrètement, MCC produit la fonction SYSTEM_Initialize, qui est appelée au démarrage du programme et qui met en place l'horloge et tous les périphériques d'un seul coup. Il génère également des fonctions spécifiques comme ADCC_Initialize (et les fonctions de conversion associées) pour le convertisseur, ainsi que SPI1_Open pour ouvrir le canal SPI vers l'écran. MCC fabrique enfin des macros lisibles pour manipuler les broches : par exemple BTN_B_GetValue pour lire l'état du bouton de tir, ou TFT_CS_Pin_SetLow pour abaisser la ligne de sélection de l'écran. Ces noms parlants rendent le code du jeu plus clair et évitent de manipuler directement les registres binaires des ports, sources fréquentes d'erreurs.
On gagne du temps et on réduit les risques d'erreur de configuration, tout en gardant un code généré cohérent et documenté. Le programmeur peut alors se concentrer sur la logique du jeu plutôt que sur les détails bas niveau du silicium. C'est d'autant plus appréciable que le couplage avec les bibliothèques de l'écran (le pilote ili9488 et la bibliothèque graphique GFX_Library) se fait au travers de ces mêmes macros et fonctions générées.
Tableau de brochage complet
Le tableau ci-dessous reprend l'affectation des broches utilisées par le projet, du point de vue du boîtier 40 broches PDIP : pour chaque signal sont indiqués le port du PIC, le numéro de patte et la fonction.
| Port | Patte (PDIP 40) | Fonction |
|---|---|---|
| RA6 | 14 | Sortie SON (NCO1) vers la carte son / buzzer |
| RC0 | 15 | Écran : CS (sélection) |
| RC1 | 16 | Écran : RST (reset) |
| RC2 | 17 | Écran : DC (Data/Command) |
| RC3 | 18 | Écran : SCK (horloge SPI) |
| RC5 | 20 | Écran : SDI/MOSI (données SPI) |
| RC6 | 21 | Joystick axe Y (entrée ADC) |
| RC7 | 22 | Joystick axe X (entrée ADC) |
| RD4 | 27 | Bouton D (RÉGLAGES) |
| RD5 | 28 | Bouton C (JOUER / RETOUR / MENU) |
| RD6 | 29 | Bouton B (TIR) |
| RD7 | 30 | Bouton A (non utilisé) |
La figure suivante représente le brochage complet du composant et permet de visualiser la répartition des signaux autour du boîtier.
On remarque que les signaux sont regroupés de manière logique : la sortie son est isolée sur le port A (RA6), tout ce qui concerne l'écran et le joystick se concentre sur le port C (RC0 à RC7), et les quatre boutons occupent le haut du port D (RD4 à RD7). Ce regroupement par fonction facilite à la fois le câblage et la relecture du programme.
Pour terminer, il faut citer les broches indispensables au fonctionnement du composant mais qui ne portent aucune fonction applicative. L'alimentation positive VDD (+5 V) est appliquée sur les pattes 11 et 32, et la masse VSS (GND) sur les pattes 12 et 31 : ce doublement des broches d'alimentation est habituel pour bien découpler le circuit et stabiliser la tension. Enfin, la broche MCLR (patte 1) est l'entrée de reset (Master Clear) : maintenue à l'état haut en fonctionnement normal, elle force le redémarrage du microcontrôleur lorsqu'elle est tirée à la masse. Ces trois signaux constituent le minimum vital qui doit être câblé avant même de raccorder le moindre périphérique.