Projet informatique SE3 IdO

Jérémie Dequidt, Julien Forget, Xavier Redon et Thomas Vantroys

2023 - 2024

1  Objectif

Le but de ce projet est de vous faire concevoir et réaliser deux programmes. Un programme classique sur PC et un programme tournant sur un système embarqué. Les deux programmes vont utiliser des bibliothèques, à la fois des bibliothèques USB (libusb-1.0, bibliothèque joystick et LUFA) mais aussi des bibliothèques que vous allez devoir concevoir et écrire.

2  Matériel

Pour cette année vous construisez vous-même votre manette. Cette manette comporte des boutons pour diriger votre vaisseau et des LED pour afficher votre nombre de vies restantes.

En cas de microcontrôleur non initialisé ou briqué, il est toujours possible de flasher l’ATMega32u4 au travers du connecteur ICSP prévu. Un connecteur ICSP dispose de toutes les lignes permettant de flasher un ATMega, à savoir l’alimentation, le reset et les lignes du bus SPI. Si vous en arrivez là, il vous faudra un Arduino à utiliser comme programmateur et quelques câbles pour connecter l’Arduino à la manette.

3  Le jeu : envahisseurs de l’espace

Vous allez programmer un très classique jeu des envahisseurs de l’espace (space invader en patoi). Le terrain est en deux dimensions. Les envahisseurs arrivent par le haut de l’écran en rangées qui se déplacent de gauche à droite et descendent d’un cran quand elles arrivent en butée sur un bord du terrain. Le joueur contrôle un vaisseau pouvant se déplacer horizontalement en bas du terrain, le vaisseau peut s’abriter derrière des boucliers. Les envahisseurs laissent tomber aléatoirement et verticalement des bombes. Le vaisseau peut envoyer des missiles. Plusieurs bombes et plusieurs missiles peuvent être présents sur le terrain simultanément. Un envahisseur peut être détruit par un ou plusieurs missiles, un missile ne peut toucher qu’un envahisseur. Les bombes peut ébrécher les boucliers et endommager le vaisseau, si le vaisseau est trop endommagé il explose et la partie est terminée. Si un envahisseur touche le vaisseau, il explose.

3.1  Gestion des niveaux

Sur destruction du vaisseau, le score est remis à zéro et le jeu reprend sur la liste des plus hauts scores. Ces scores sont stockés dans un fichier. Si tous les envahisseurs d’un niveau sont détruits, un niveau supérieur est proposé. Les niveaux doivent être de difficulté croissante (augmentation de la vitesse de descente des rangées, nombre de missiles nécessaire à tuer un type d’envahisseur, etc.).

3.2  Structuration du jeu

Prévoyez les répertoires suivants avec un système de makefile pour compiler l’ensemble du projet :

3.3  Structures de données

Le vaisseau est facilement représenté par une structure cartésienne contenant sa position, son état, etc. Les boucliers font partie intégrante du décor et l’effet des bombes est directement appliqué au dit décor.

Pour les autres objets : envahisseurs, bombes et missiles, une représentation plus complexe est nécessaire. Ces objets sont clairement multiples et dynamiques. Aucune structure simple (pile ou file) ne convient : un missile tiré après un autre peut être amené à disparaître avant le premier, les bombes les plus récemment lancées peuvent aussi devoir être effacées avant d’autres lancées plus anciennement. Enfin les envahisseurs peuvent être détruits totalement aléatoirement. La seule structure qui convient est donc la liste chaînée. Les structures chaînées doivent inclure la position et l’état des objets.

3.4  Gestion des bombes

La fonction de gestion des bombes doit respecter les points suivants :

3.5  Gestion des missiles

La fonction de gestion des missiles doit respecter les points suivants :

3.6  Gestion des envahisseurs

Une première fonction va initialiser la liste chaînée des envahisseurs selon le niveau.

La seconde fonction de gestion des envahisseurs doit déplacer tous les envahisseurs selon le principe cité plus haut. Aléatoirement un ou plusieurs envahisseurs sont choisis pour lancer une bombe.

3.7  Moteur du jeu sur un niveau

L’algorithme du moteur de jeu pour un niveau donnée comporte les étapes suivantes :

4  Bibliothèques dynamiques pour les contrôleurs

Votre jeu doit pouvoir être contrôlé par plusieurs types de contrôleurs (clavier, manette, joystick, etc.). Dans un but de modularité maximale, le type de périphérique doit être déterminé en début de jeu et va donner lieu au chargement d’une bibliothèque dynamique (fonction dlopen et ses amies).

Chaque bibliothèque dynamique de gestion de contrôleur doit comporter une fonction d’initialisation du périphérique, une fonction d’interrogation du périphérique et une fonction de cloture de l’utilisation du périphérique. Concevez ces fonctions avec des prototypes permettant d’utiliser une large gamme de contrôleurs.

Pour tester vos prototypes écrivez la bibliothèque dynamique utilisant le clavier.

Pour charger la bibliothèque correspondant aux voeux de l’utilisateur vous pouvez, soit sélectionner la bibliothèque via un paramêtre de l’exécutable du jeu, soit passer par un item du menu principal du jeu.

5  Manette USB

Vous devez programmer l’ATMega32u4 de la manette pour gérer le bus USB et implanter un périphérique USB. Vous utiliserez avr-gcc comme compilateur C. Le téléchargement du programme sur le microcontrôleur se fera en utilisant l’utilitaire dfu-programmer capable de programmer l’ATMega32u4 via le bus USB. Vous n’aurez pas à programmer directement les registres de l’ATMega32u4 pour gérer l’USB, vous utiliserez une bibliothèque nommée LUFA.

Sur le principe vous devez implanter un périphérique USB HID (périphérique d’interface homme-machine, Human Interface Device en patoi). Plus précisement vous devez implanter un périphérique USB HID joystick. Un tel périphérique comporte une interface de classe HID avec au minimum un point d’accès entrant (IN). Il vous est demandé d’utiliser deux boutons pour simuler un axe du Joystick et un autre bouton dans son rôle normal de bouton.

Vous ajouterez à ce périphérique USB une interface supplémentaire pour gérer les LED. L’interface supplémentaire doit présenter un point d’accès de type interruption en écriture (endpoint OUT). Les données envoyéies sur ce point d’accès doivent avoir une taille d’un octet. Organisez les bits correspondant aux LED comme vous l’entendez.

5.1  Programmation initiale de la carte

Si vous avez un microcontrôleur sans chargeur DFU/USB vous allez devoir l’installer. Pour cela utilisez un programmateur AVR sous la forme d’un Arduino Uno avec quelques câbles et programmé avec le programme ArduinoISP.

Commencez par modifier les fuses du microcontrôleur pour lui demander d’utiliser une horloge externe (à partir de 8Mhz) et permettre d’appeler le chargeur avec le bouton HWB :

avrdude -c stk500v1 -p atmega32u4 -P /dev/ttyACM0 -b 19200 -U lfuse:w:0xFF:m -U efuse:w:0xF7:m

Ensuite il faut télécharger le chargeur DFU/USB à partir du site Microchip https://www.microchip.com/en-us/product/atmega32u4 (cherchez "Embedded Software"). Une fois le fichier ATMega32U4-usbdevice_dfu-1_0_0.hex sous la main, flashez le sur le microcontrôleur :

avrdude -c stk500v1 -p atmega32u4 -P /dev/ttyACM0 -b 19200 -U flash:w:ATMega32U4-usbdevice_dfu-1_0_0.hex

Enfin connectez votre carte en USB et vérifiez qu’elle est bien en mode DFU/USB avec la commande lsusb.

5.2  Téléversement du programme

Pour téléverser votre programme sur l’ATMega32u4, le paquetage Debian dfu-programmer est nécessaire. Installez le au besoin. La procédure à suivre pour charger votre programme sur l’ATMega32u4 est la suivante :

5.3  Prise en main

Commencez par un test basique de la manette : faites clignoter des LED. Testez aussi les boutons. Pour le programme de test, vous pouvez partir du programme que vous avez écrit dans le module "microprocesseur".

5.4  Code LUFA d’exemple

Sur le site http://www.fourwalledcubicle.com/LUFA.php, téléchargez la dernière version de la bibliothèque USB LUFA. Créez un répertoire PolytechLille au même niveau que les répertoires Demos et Projects. Dans ce répertoire, copiez la démonstration du sous-chemin Device/LowLevel/Joystick sous le nom, par exemple, Manette. Renommez aussi les fichiers dans le répertoire et modifiez le makefile. Au niveau du makefile indiquez atmega32u4 comme microcontrôleur, NONE comme platine, ajustez le chemin d’accès LUFA_PATH et passez à 8Mhz.

5.5  Adaptation du code à la manette

Modifiez le fichier makefile. Modifiez ensuite le fichier Manette.c, en particulier la fonction CALLBACK_HID_Device_CreateHIDReport pour créer des rapports HID en fonction des valeurs retournées par les boutons.

5.6  Gestion des actionneurs

En toute théorie, il serait possible d’intégrer la gestion des LED sous la classe USB HID. Le moins que l’on puisse dire c’est que cela complexifie considérablement le descripteur HID et encore il faut faire de l’ingéniérie inverse pour le constituer. Nous allons donc procéder autrement. Vous allez ajouter, à l’interface standard HID, une autre interface de classe spécifique au vendeur (USB_CSCP_VendorSpecificClass, pas de descripteur supplémentaire nécessaire) peuplée d’un point d’accès de type interruption en écriture (endpoint OUT). La communication sur ce point d’accès se fera sur un octet. Ce octet doit commander les LED de la carte.  
 
Pour la gestion des points d’accès interruption, vous pouvez vous aider de la démonstration Keyboard dans le répertoire Device/LowLevel. La déclaration des interfaces et des points d’accès est clairement montrée dans les fichiers Descriptors.c et Descriptors.h de cette démonstration. La gestion de points d’accès de type interruption est montrée dans l’autre source C de la démonstration, cherchez les fonctions Endpoint_ConfigureEndpoint, Endpoint_SelectEndpoint, etc.  
 
N’hésitez pas à consulter la documentation de la bibliothèque sur Internet.

6  Utilisation en tant que manette dans le jeu

Pour utiliser votre manette dans le jeu "envahisseurs" que vous programmez utilisez le pilote joystick SDL.

Vous pouvez vous appuyer sur le tutoriel : https://alexandre-laurent.developpez.com/tutoriels/sdl/joysticks/.

7  Utilisation des LED de la manette dans le jeu

Nous souhaitons enfin pouvoir utiliser les actionneurs de la manette (LED). Nous n’avons pas intégré ces actionneurs à l’interface HID mais nous avons créé une interface spécifique pour les contrôler.

Nous allons donc utiliser la bibliothèque USB libusb-1.0 pour écrire des fonctions C permettant de contrôler les LED. Vous avez le manuel complet de la bibliothèque à l’URL http://libusb.sourceforge.net/api-1.0/modules.html. Vous trouverez un résumé de la bibliothèque à l’URL https://rex.plil.fr/Enseignement/Systeme/Systeme.PSE/. Pour cette utilisation assez basique nous utiliserons les fonctions de communication bloquantes.

7.1  Enumération des périphériques USB

Ecrivez une fonction pour examiner tous les périphériques USB disponibles sur les bus USB de votre machine de TP. Dès qu’un périphérique s’avère être un périphérique USB du type cherché, sauvez la "poignée" (obtenue avec libusb_open) vers ce périphérique dans un tableau statique global de type libusb_device_handle* periphs[MAX_PERIPH].  
 
Vous pouvez utiliser la commande lsusb pour trouver les identifiants USB de votre type de périphérique. Pour chaque périphérique sélectionné, faites afficher le numéro du bus sur lequel se trouve le périphérique ainsi que son adresse sur ce bus.

7.2  Appropriation des interfaces spécifiques

Une fois un périphérique détecté, il faut récupérer sa configuration. La fonction libusb_get_active_config_descriptor qui permet cela nécessite un pointeur sur périphérique de type libusb_device *. Vous pouvez retrouver ce pointeur en fonction de la "poignée" par la fonction libusb_get_device. Faites afficher la valeur de cette configuration.  
 
Il vous reste ensuite, à réclamer les interfaces nécessaires pour votre usage. Attention, la fonction libusb_claim_interface nécessite le numéro de l’interface et pas son indice. Vous allez donc parcourir la structure arborescente de description de configuration du périphérique de la façon suivante :

Affichez l’indice et le numéro de chaque interface détectée et réclamée. Affichez aussi le numéro du point d’accès trouvé.

7.3  Fermeture du périphérique USB

Ecrivez une fonction qui libère les périphériques détectés. Pour cela, relachez toutes les interfaces réclamées puis fermez la "poignée". Vous allez devoir, à nouveau, explorer la structure arborescente de description de configuration du périphérique ou sauver les numéros des interfaces dans le tableau global. Affichez l’indice et le numéro de chaque interface libérée.

7.4  Gestion des actionneurs

Vous pouvez maintenant ajouter le contrôle des LED sous la forme d’une bibliothèque dynamique (répertoire à part dans votre projet "envahisseurs"). Les fonctions de cette bibliothèque sont a appeler dans votre jeu.

L’état du vaisseau doit être affiché, sous la forme d’un barre de vie, sur les LED de la manette.


Ce document a été traduit de LATEX par HEVEA