Projet Pacman
Rapport de projet (2013/2014) rédigé par les édudiantes :
- KONAK Sumeyye
- SAVRY Maelle
Le but de notre projet est de diriger un Pacman à l'aide de la manette Nunchuck, extension de la Wii. Le programme étant déjà fourni, nous nous occuperons de configurer, grâce au protocole I2C et d'un bus I2C la manette afin de mouvoir le Pacman à l'aide de l'accéléromètre intégré de la Nunchuck. Il faudra aussi bouger le Pacman avec le stick analogique et utiliser un des deux boutons C ou Z de la manette pour passer d'un mode "accéléromètre " à un mode "stick analogique". Nous relierons la manette au FPGA intégré dans la carte Spartan 3E afin de coder les mouvements du Pacman.Nous présenterons ci-dessous le fonctionnement d'un Nunchuck ainsi que celui du bus I2C.
Sommaire
Présentation du jeu PACMAN
Le Pac-Man est un jeu vidéo créé par Tōru Iwatani pour l’entreprise japonaise Namco, sorti au Japon le 22 mai 19802. Le jeu consiste à déplacer un personnage en forme de camembert, Pac-Man, à l’intérieur d’un labyrinthe, afin de lui faire manger toutes les pac-gommes qui s’y trouvent en évitant d’être touché par des fantômes. Il peut aussi manger des "fruits" qui lui rapporteront d'avantage de points et la possibilité de prendre en chasse les fantômes. Dans notre projet il n'y aura un Pacman dans un labyrinthe avec plusieurs pac-gommes(blanches), un seul fruit (gomme rouge) et deux fantômes, mais un seul de fonctionnel.
La manette Nunchuk
Qu'est ce que le Nunchuk ?
Il s'agit de l'extension de base de la Wiimote,elle sert notamment dans la plupart des jeux. elle doit son nom "Nunchuck" à sa ressemblance avec un nunchaku.
Relié à la Wiimote par le biais d'une connexion filaire, il comprend un stick analogique(joystick) deux axes, un accéléromètre trois axes et deux boutons tout ou rien "C" et "Z". Fourni dans la boîte de la Wii à l'achat, il est équipé d’accéléromètres pour la reconnaissance des mouvements, mais n'est ni équipé de haut- parleur, ni de moteur de vibration.
Qu'est ce que la Wiimote ?
Il s'agit de la manette de jeu de la console Wii de Nintendo. De forme rectangulaire , elle principalement utilisée à une main, la manette Nunchuck occupant la main libre de la personne. Sa grande particularité est qu'elle est capable de se repérer dans l'espace et de retranscrire ses mouvements à l'écran, tout ceci à l'aide de différent capteur présent sur la manette.( elle peut prendre différents rôles en fonction du jeu : arme, baguette de chef d'orchestre raquette).
Le projet sur lequel nous allons nous baser, permettait de gérer les mouvements du Pacman à l'aide du stick analogique, notre but sera de mouvoir le Pacman avec l'accéléromètre.
L'accéléromètre
Qu'est-ce qu'un accéléromètre ?
Un accéléromètre est un appareil électromécanique (un capteur) qui, fixé à un mobile ou tout autre objet,permet de mesurer l’accélération linéaire de ce dernier, en mesurant les forces d'accélération. Ces forces peuvent être :
-statiques (force d'attraction de la pesanteur),utilisé dans le cas du Nunchuk afin de déterminer ses différents angles d'inclinaison;
-dynamiques (lorsque l'accéléromètre bouge ou vibre). En mesurant l'accélération dynamique, il est possible d'analyser le déplacement de l'objet sur lequel il est intégré.
On parle encore d'accéléromètre même s'il s'agit en fait de 3 accéléromètres qui calculent les 3 accélérations linéaires selon 3 axes orthogonaux(X,Y, et Z).
Exemple d'utilisations autres que le Nunchuk
Ils peuvent être utilisés dans les voitures pour déclencher les airbags.
Récemment IBM et Apple ont intégré des accéléromètres dans leurs ordinateurs portables pour protéger les disques durs. L'accéléromètre détecte les chutes soudaines et met le disque dur à l'arrêt pour que les têtes ne s'écrasent pas sur le disque.
L'accéléromètre de la manette Nunchuk
Il s'agit d'un accéléromètre trois axes (x, y et z) de type capacitif ADXL330 fournis par Analog Devices. Il permet de déterminer l'accélération du mouvement fait avec le Nunchuk, mais aussi sa rotation.
Bien que l'accélération linéaire soit définie en m/s2 (SI), la majorité des documentations sur ces capteurs expriment l'accélération en « g » (accélération causée par la gravitation terrestre, soit environ g = 9,81 m/s2).
Le stick analogique
Le projet a pour but de déplacer le Pacman à l'aide de l'accéléromètre mais aussi grâce au stick analogique de la Nunchuck , en switchant à l'aide d'un des deux boutons C ou Z. Un stick analogique est un joystick miniature qui se manie avec le pouce. Celui de la Nunchuck est un joystick deux axes qui utilise un système analogique ce qui permet un plus grand nombre d'inclinaisons et par conséquent une meilleure réaction que le système numérique. Analogique signifie que l'information est obtenue grâce à la mesure de la variation d'une grandeur physique.
Fonctionnement d'un joystick
N'importe quel périphérique de jeu doté d’axes est parcouru par des circuits électriques. Ceux-ci font partie intégrante de la gestion des commandes analogiques. Il est question ici d’utiliser un composant électronique capable de créer une modulation analogique du courant, c’est-à-dire capable d’adopter une multitude de positions différentes. Le composant le plus couramment utilisé est le potentiomètre, ici utilisé comme une simple résistance variable. Le Joystick est le seul périphérique de jeux à commande analogique à cumuler deux axes se mouvant à l'aide d'une seule commande : le manche. L'axe X lui permet de prendre en compte les mouvements horizontaux, de gauche à droite, et l'axe Y les mouvements verticaux, de haut en bas. Il a donc fallu développer un dispositif capable de déplacer l’aiguille de deux potentiomètres différents, sur deux axes perpendiculaires.
Un manche de joystick s’incline autour de son point de rotation et est ramené au centre, en position initiale, par un système de ressorts. Le manche du joystick ne s’arrête pas à ce point de rotation et se poursuit par une pointe plus fine qui vient se loger dans la base du joystick où une structure est destinée à détecter ses mouvements. Elle est globalement composée de deux éléments perpendiculaires et superposés, non solidaires, dans lesquels la pointe du joystick peut coulisser librement. La pointe est toujours prisonnière des deux éléments d’une forme généralement proche d’un U à branches courtes et qui sont donc globalement en forme d’arcs.
Chacun des arcs est maintenu à la structure (à ses deux extrémités) par deux points de pivot, autour desquels ils peuvent, dans une certaine mesure, tourner. Les points de pivot des deux arcs sont situés sur un même plan horizontal, mais ceux-ci sont disposés de telle sorte que la pointe du joystick puisse se déplacer dans tout l’espace en faisant s’incliner les arcs. Si le manche est incliné vers la droite, l’arc vertical va alors être incliné sur la gauche par la pointe du joystick, la position de l’autre arc n’étant pas modifiée (la pointe coulisse dans sa glissière). Le premier arc tourne donc autour de ses deux points de pivot. C’est là qu’intervient le potentiomètre. Celui-ci est en effet relié à l’arc et constitue en réalité l’un des deux points de pivot. Ainsi, lorsque le manche est incliné vers la droite, l’axe du potentiomètre tourne : son aiguille est déplacée par l’arc vertical qui s’incline.Le mouvement est bien transcrit à l'aide d'une activité électrique détectable. Il ne reste plus qu’au convertisseur ADC d’associer la valeur de l’intensité relevée à une position sur l’axe x horizontal.
Fonctionnement du stick analogique de la Nunchuk
Il s'agit du même principe que le joystick, néanmoins les dimensions de ce type de stick sont sensiblement réduites par rapport à un véritable joystick, et les contraintes physiques subies nettement moindres. C'est pourquoi des éléments en plastique sont utilisés dont seul l’un des deux est en forme d’arc, orienté cette fois vers le haut. L’autre élément est, lui, rectiligne et sert de support au stick tout en permettant son inclinaison. Le fonctionnement de ce système est cependant tout à fait identique et l’on retrouve également deux petits potentiomètres fixés à une extrémité de chacun de ces éléments.
Le Protocole Utilisé: I²C
Présentation
Historique
Le bus I2C (Inter Integrated Circuit) a été développé au début des années 80 par Philips semi-conductors pour permettre de relier facilement à un microprocesseur les différents circuits d'un téléviseur moderne.
Caractéristiques
- Deux lignes uniquement (SDA et SCL) + Masse
- Une adresse unique pour chaque périphérique
- Bus multi-maître, détection des collisions et arbitrage
- Bus série, 8 bits, bidirectionnel à 100 kbps (standard mode), 400 kbps (fast mode), 3,2 Mbps (high-speed-mode)
- Filtrage intégré : réjection des pics parasites
- Nombre de circuits uniquement limité par la capacitance maximale du bus : 400 pF
Principe
Le premier fil, SDA (Signal DAta), est utilisé pour transmettre les données. L'autre fil, SCL (Signal Clock Line) est utilisé pour transmettre un signal d'horloge synchrone (signal qui indique le rythme d'évolution de la ligne SDA(Serial Data Line)). Les tensions associées aux niveaux logiques vont dépendre de la technologie des circuits en présence (CMOS, TTL). Il faudra que tous les circuits connectés au bus I²C utilisent les mêmes potentiels pour définir les niveaux haut et bas. En définitive, cela implique que tous les composants connectés à un même bus soient alimentés de façon identique. Cela ne signifie pas que les composants doivent utiliser la même source pour s'alimenter ; il suffit que la tension d'alimentation soit à la même valeur pour tous les composants, le fil de masse permettant d'unifier les références. Il reste maintenant un problème crucial. Comment permettre à plusieurs circuits logiques de connecter leurs sorties ensemble, sachant que certains circuits voudront imposer un niveau haut tandis que d'autres voudront imposer un niveau bas ? La réponse est connue: il faut utiliser des sorties à collecteur ouvert (ou à drain ouvert pour des circuits CMOS). Le niveau résultant sur la ligne est alors une fonction « ET » de toutes les sorties connectées.
Les résistances de rappel au potentiel VCC permettent aux signaux SDA et SCL d'être à 1 si toutes les sorties à collecteurs ouverts sont aussi au niveau 1 (résultat de la fonction « ET »). Si une ou plusieurs sorties tentent d'imposer un niveau bas sur une ligne, le ou les transistors associés vont conduire, ce qui entraîne un niveau bas sur la ligne correspondante (ce qui est conforme au résultat de la fonction « ET »).
En ce qui concerne la lecture des signaux SDA et SCL, cela ne pose pas de problème. Les signaux peuvent être lus en permanence sans risque d'interférer sur le niveau de la ligne.
Au repos, tous les circuits connectes doivent imposer un niveau haut sur leurs sorties respectives. Si les lignes SDA et SCL sont au niveau haut dans ces conditions, cela signifie qu'aucun circuit ne tente de prendre le contrôle du bus. Si une des lignes SDA ou SCL passe à un niveau bas dans les mêmes conditions, c'est qu'un des circuits désire prendre le contrôle du bus. Mais il peut aussi y avoir deux circuits qui tentent de prendre le contrôle du bus en même temps (ou à quelques nanosecondes d'écart près). Il faut donc mettre en place un protocole pour gérer les conflits possibles.
Protocole
La prise de contrôle du bus
Pour prendre le contrôle du bus, il faut que celui-ci soit au repos (SDA et SCL à '1'). Pour transmettre des données sur le bus, il faut donc surveiller deux conditions particulières :
- La condition de départ. (SDA passe à '0' alors que SCL reste à '1')
- La condition d'arrêt. (SDA passe à '1' alors que SCL reste à '1')
Lorsqu'un circuit, après avoir vérifié que le bus est libre, prend le contrôle de celui-ci, il en devient le maître. C'est toujours le maître qui génère le signal d'horloge.
Exemple de condition de départ et d'arrêt :
Transmission d’un octet
Le maître transmet le bit de poids fort D7 sur SDA. Il valide la donnée en appliquant un niveau ‘1’ sur SCL. Lorsque SCL retombe à ‘0’, il poursuit avec D6, etc. jusqu‘à ce que l’octet complet soit transmis. Il envoie le bit ACK à ‘1’ en scrutant l’état réel de SDA. L’esclave doit imposer un niveau ‘0’ pour signaler que la transmission s’est déroulée correctement. Le maître voit le ‘0’ (collecteur ouvert) et peut passer à la suite.
Dans cet exemple :
SCL : Horloge imposée par le maître (tout en haut).
SDAM : Niveaux de SDA imposés par le maître.
SDAE : Niveaux de SDA imposés par l'esclave.
SDAR : Niveaux de SDA réels résultants.
Transmission d’une adresse
Le nombre de composants qu'il est possible de connecter sur un bus I²C étant largement supérieur à deux, le maître doit pouvoir choisir quel esclave est censé recevoir les données. Dans ce but, le premier octet que transmet le maître n'est pas une donnée mais une adresse. Le format de l'octet d'adresse est un peu particulier puisque le bit D0 est réservé pour indiquer si le maître demande une lecture à l'esclave ou bien au contraire si le maître impose une écriture à l'esclave.
Chaque circuit connecté au bus I²C possède une adresse, qui doit être unique. L'adresse associée à un composant est définie en partie par l'état de broches de sélections et d'autre part par sa fonction. Par exemple, le circuit PCF8574, qui est un port d'entrées/sorties bidirectionnel 8 bits, décompose son adresse de la façon suivante : [0] [1] [0] [0] [A2] [A1] [A0] [R/W]. Les bits A2, A1 et A0 reflètent l'état des broches 1, 2 et 3 du circuit. Cela permet de placer 8 circuits PCF8574 sur le bus I²C. Lors de la conception d'un système, il faut donc veiller à l'unicité des adresses attribuées aux différents composants.
Une fois l'adresse envoyée sur le bus, l'esclave concerné doit répondre en plaçant le bit ACK à 0. Si le bit ACK vaut 1, le maître comprend qu'il y a une erreur de sélection et il génère la condition arrêt. En revanche, si le bit ACK vaut 0, le maître peut continuer les opérations.
Note: Les adresses 0000 0xxx et 1111 11xx sont réservées à des modes de fonctionnement particuliers (les adresses réservées).
Ecriture d'une donnée
Si le bit R/W précédemment envoyé était à 0, cela signifie que le maître doit transmettre un ou plusieurs octets de données. Après chaque bit ACK valide, le maître peut continuer d'envoyer des octets à l'esclave ou bien il peut décider de terminer le dialogue par une condition d'arrêt.
Lecture d'une donnée
Si le bit R/W transmis en même temps que l'adresse est à 1, cela signifie que le maître veut lire des données issues de l'esclave. C'est toujours le maître qui va générer le signal d'horloge SCL. En revanche, après le bit ACK de l'adresse, c'est l'esclave qui va garder le contrôle de la ligne SDA. Pour cela, le maître va placer sa propre sortie SDA au niveau haut pour permettre à l'esclave de prendre le contrôle de la ligne SDA. L'esclave doit alors scruter la ligne SCL et attendre le niveau bas pour changer l'état de la ligne SDA, faute de quoi le maître détectera une condition arrêt et abandonnera le transfert (l'électronique intégrée dans l'esclave se doit de détecter aussi qu'il y a eu une condition arrêt, bien entendu). Après que l'esclave ait transmis les 8 bits de données, c'est le maître, cette fois-ci, qui va générer un bit d'acquittement. Si le maître désire lire des octets supplémentaires, il placera le bit d'acquittement à 0. En revanche, si le maître décide que la lecture est terminée, il placera le bit ACK au niveau 1. L'esclave comprendra alors que le transfert est terminé. Cette fois-ci, bien que le bit ACK soit au niveau 1, cela ne correspond pas à une condition d'erreur mais à une fin de transfert.
Conflits
Problème
- La conception du bus I²C est destiné à accueillir plusieurs maîtres. Mais le problème c'est ce que tous les réseaux utilisent un canal de transmission unique: Comment arbitrer ?
- Chaque maître peut prendre possession du bus dès que celui-ci est libre : possibilité que deux maîtres prennent la parole en même temps.
- Pas de problème électrique -> collecteur ouvert
- Problème logique - >éviter la corruption des données due à la collision des bits transmis
Principe
Prise de contrôle du bus
- Vérifier que le bus est libre.
- Condition d’arrêt envoyée depuis au moins 4,7 µs.
- Prise de contrôle effectif, mais vérification de l’état des lignes SDA et SCL.
Plusieurs cas :
-Différents maîtres envoient les mêmes données en même temps : aucun conflit, cas rare.
-Un maître impose un ‘0’ : il relit obligatoirement un ‘0’ et continuera à transmettre.
-Un maître cherche à appliquer un ‘1’ sur le bus.
Alors,
S’il lit ‘1’, il continue à transmettre.
S’il lit ‘0’, un autre maître a pris la parole en même temps: Il perd l’arbitrage, arrête d’émettre, mais continue à lire.
Exemple
SCLR: Horloge résultante
SDA1: Niveaux de SDA imposés par le maître n°1
SDA2: Niveaux de SDA imposés par le maître n°2
SDAR: Niveaux de SDA réels résultants lus par les deux maîtres
Analyse:
Le premier octet est transmis normalement car les deux maîtres imposent les même données (Cas n°1). Le bit ACK est mis à '0' par l'esclave.
Lors du deuxième octet, le maître n°2 cherche à imposer un '1' (SDA2) , mais relit un '0' (SDAR), il perd alors le contrôle du bus et devient esclave (Cas n°3). Il reprendra le contrôle du bus, lorsque celui-ci sera de nouveau libre.
Le maître n°1 ne voit pas le conflit et continue à transmettre normalement Cas n°2).
Au total, l'esclave à reçu les données du maître n°1 sans erreurs et le conflit est passé inaperçu.
Spécificité
Les adresses réservées
Les adresses 0000 0xxx ne sont pas utilisées pour l’adressage des composants:
- L’adresse 0000 0000: Adresse d’appel général. Les circuits ayant la capacité de traiter ce type d’appel émettent un acquittement. Le deuxième octet définit le contenu de l’appel.
- L’adresse 0000 0110: RESET. Remet tous les registres des circuits connectés dans leur état initial. Les circuits qui le permettent rechargent leur adresse d’esclave.
- L’adresse 0000 0010: Les circuits qui le permettent rechargent leur adresse d’esclave.
- L’adresse 0000 0100: Les circuits définissant leur adresse de façon matérielle réinitialisent leur adresse d’esclave.
- L’adresse 0000 0000: Interdit.
- L’adresse xxxx xxx1: Joue le rôle d’interruption. xxxx xxx peut être l’adresse du circuit qui a généré l’interruption.
- L'adresse 0000 0001: Octet de start : utilisé pour synchroniser les périphériques lents avec les périphériques rapide.
- L'adresse 0000 001x: Permet de rendre sourds tous les circuits I2C présents sur le bus. On peut donc changer le protocole de transmission sans générer d’erreurs au niveau des circuits I2C. Le bus repasse en mode normal lors de la réception d’une condition d’arrêt.
- L'adresses 0000 0110 à 0000 1111: Non définies et ignorées par les circuits I2C. Elles peuvent être utilisées pour débugger un réseau multimasters par exemple.
I²C étendu
I2C Fast Mode :
- 400 kbits/s, Adressage sur 10 bits
- Paramètres physiques inchangés : protocole, niveaux, capacitance identiques : changement uniquement au niveau timing
- Abandon de la compatibilité CBUS
- Entrées à trigger de Schmitt
- Sorties haute impédance lorsque le périphérique n’est pas alimenté
- La résistance de tirage doit être adaptée
– Jusqu’à 200pF : résistance suffit
– De 200pF à 400pF : source de courant préférable
Adressage étendu:
- Espace d’adressage trop restreint en mode standard
- 2 octets d’adressage :
- Compatibilité assurée avec le mode standard
– R/W et ACK à la même position,
– 11111 permet de faire la différence entre mode standard et mode fast.
La Spartan 3E
Nous utiliserons le FPGA présent sur la carte suivante. Relié sur les pattes GND, VCC, IO8, IO7 à l'aide du bus.
Réalisation du Projet
Nous avons réalisé le bus permettant la liaison entre la Nunchuk, la carte Spartan 3E et le Pacman. De cette façon il nous a été permis de mouvoir le Pacman avec le stick analogique de la Nunchuk, nous avons ensuite configuré le programme de façon a le bouger avec l’accéléromètre. Nous avons ensuite créer 3 modes de jeux que l'on peut changer avec les deux boutons Z et C présents sur la manette :
- De base, le Pacman bouge latéralement avec l'accéléromètre et verticalement avec le stick analogique.
- En appuyant sur C, on gère complètement les mouvements avec le joystick.
- En appuyant sur Z, on gère entièrement les mouvements avec l'accéléromètre.
Nous avons commandé un arrêt du jeu dans le cas où toutes les gommes ont été mangées par le Pacman, et un autre dans le cas où elles ne seraient pas toutes mangées en l'espace de 9 vies.
Le Bus
Nous avons utilisé 4 fils afin de relier la Nunchuck aux entrées de la carte : GND, VCC, IO7 et IO8 ce qui permet les échanges entre la Nunchuk et le FPGA. Il faut ensuite assigner les entrées aux sorties correspondantes, ce qui correspondra à une modification du VHDL.
Le FIFO
Pour pouvoir utiliser la gestion d'accéléromètre de la manette Nunchuk nous avons du stocker l'ensemble de ses données. Alors pour effectuer ce stockage nous avons mis en place un FIFO(premier arrivé, premier sorti) qui stocke les données systématiquement pour que le processus puisse tout gérer ensuite.
Plus précisément: La méthode FIFO permet de décrire une méthode de gestion des données consistant à traiter la file d'attente(le bus I2C dans notre cas) des actions à effectuer dans l'ordre chronologique, ainsi qu'en logistique, dans la gestion des stocks.
Pour bien comprendre sa logique de fonctionnement; une petite animation est ci-dessous:
Le VHDL gérant les échanges Nunchuck-carte
Nous avons fait des modifications dans le fichier vhdl pour pouvoir gérer la Nunchuk.
- Premièrement:
Nous avons supprimé nunchuk3.vhd en laissant juste avr_fpga_s3e.ucf qui permettait de faire les liaisons des portes.
Par exemple le code qui permet de configurer les liaisons avec l'écran VGA;
# ==== VGA Port (VGA) ====
NET "blue" LOC = "G15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "green" LOC = "H15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "hsynch" LOC = "F15" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "red" LOC = "H14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
NET "vsynch" LOC = "F14" | IOSTANDARD = LVTTL | DRIVE = 8 | SLEW = FAST ;
- Deuxièmement:
Par rapport à l'année précédent, cette année nous avons utilisé un FIFO donc il nous fallait faire des changements pour mettre le FIFO dans le code VHDL. Pour ceci, dans le fichier io2.vhdl nous avons supprimé tout ce qui était avec le signal I_SWITCH(ce qui permettait de commander l’interrupteur de l'année précédente) et nous avons remplacé un code lié à un FIFO. Ce qui nous a donné un programme plus efficace et plus aéré.
Pour commencer, nous avons mis en place le FIFO dans Componant nunchukTop:
COMPONENT nunchukTop IS
PORT (
sys_clk : IN std_logic;
Init : IN std_logic;
--commande de bus
readFIFO,i_continue : in std_logic;
--les états du FIFO
data_present, full, half_full : out std_logic;
--les données de FIFO
O_data_nunchuk : out std_logic_vector(7 downto 0);
-- pour le bus i2c i/o
sda : INOUT std_logic;
scl : INOUT std_logic);
END COMPONENT;
Après avoir terminé les déclarations, dans cette partie l'ensemble du travail consistait donc à câbler et à ajouter des lignes dans les deux "cases" des deux processus de lecture et d'écriture dans port map.
nunchuk: nunchukTop port map (
sys_clk => I_CLK,
Init => I_CLR,
readFIFO => s_readFIFO,
i_continue => s_twen_b2,
O_data_nunchuk => s_data_nunchuk,
data_present => s_twcr_b7,
full => s_twcr_b6,
half_full => s_twcr_b5,
-- i2c i/o
sda => sda,
scl => scl);
Par exemple le changement fait pour l'adaptation au cœur Nunchuk:
Avant(2013)
I_SWITCH(7) <= s_data_nunchuk(3);
I_SWITCH(6) <= s_data_nunchuk(2);
I_SWITCH(5) <= s_data_nunchuk(5);
I_SWITCH(4) <= s_data_nunchuk(4);
I_SWITCH(3) <= s_data_nunchuk(7);
I_SWITCH(2) <= s_data_nunchuk(6);
I_SWITCH(1) <= s_data_nunchuk(0);
I_SWITCH(0) <= s_data_nunchuk(1);
Après(2014)
s_readFIFO <= I_RD_IO WHEN (I_ADR_IO = x"23") ELSE '0';
Code utilisé pour commander l'accéléromètre
On constate qu'afin de récupérer les données pour commander la Nunchuk avec l'accéléromètre on utilise nunData[0] et nunData[1]. La manette Nunchuk envoie ses informations comme 6 octets de données, lisibles . Les six octets de données sont :
Le byte 0 permet de choisir la direction sur l'axe des X, le byte 1, sur l'axe des Y au joystick.De la même façon le byte 2 permet de choisir la direction sur l'axe des X, le byte 3 sur l'axe des Y de accéléromètre et le byte 4 sur l'axe des Z. nous ajouterons un code en C dépendant du paramètre NunData[byte] qui permet de récupérer la valeur du byte ce qui permettra de gérer l’accéléromètre, nous l'expliquerons plus bas. Le byte 5 permet de gérer les deux bits de poids faible de AY et AZ, car comme on peut le constater dans le tableau, AY et AZ ont bien 8 valeurs possibles contenues de 9 à 2, il reste donc les deux bits de poids faible. Nous ne les utiliserons pas car les bits nous permette de mettre une sensibilité plus ou moins importante, or l'influence de ces bits de poids faible est minim. De plus, le byte 5 permet de gérer les deux boutons C et Z, que nous utiliserons pour passer d'un mode de jeu à un autre.
Le paramètre NunData nous permet de récupérer les données du byte entre crochets. Nous avons décidé qu'en appuyant sur C on passe en mode entièrement stick analogique, Sur Z en mode uniquement accéléromètre. Par défaut, on peut se déplacer latéralement avec l'accéléromètre,et verticalement avec le stick. Nous nous servirons des bytes 2 et 3 pour l'accéléromètre nous n'utiliserons pas l'axe Z.
- En appuyant sur C, uniquement joystick
//BC-joystick
if((nunData[5]&0x02)==0x00){ // SI on a 0 sur le bit 1 de l'octet 5 c'est-à-dire si C est appuyé
if (nunData[0]>0xC0) { dxf = 2; dyf = 0;vPORTB &= 0xFC;} //déplacement latéral vers la droite
if (nunData[0]<0x30) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;} //déplacement latéral vers la gauche
if (nunData[1]>0xC0) {dyf = -2; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;} //déplacement vertical vers le bas
if (nunData[1]<0x30) {dyf = 2; dxf = 0;vPORTB |= 0x03;} //déplacement vertical vers le haut
////nunData[0] et nunData[1] correspondent aux bytes X et Y de l'accéléromètre
}
- En appuyant sur Z, uniquement accéléromètre
//BZ-accelerometre
if((nunData[5]&0x01)==0x00){ // SI on a 0 sur le bit 0 de l'octet 5 c'est-à-dire si Z est appuyé
if (nunData[2]>0x80) {dxf = 2; dyf = 0;vPORTB &= 0xFC;} // déplacement latéral vers la droite
if (nunData[2]<0x70) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;} // déplacement latéral vers la gauche
if (nunData[3]>0x90) {dyf = -2; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;} // déplacement vertical vers le bas
if (nunData[3]<0x60) {dyf = 2; dxf = 0;vPORTB |= 0x03;}// déplacement vertical vers le haut
//nunData[2] et nunData[3] correspondent aux bytes X et Y de l'accéléromètre
}
- Configuration par défaut
//de base, accelerometre x, joystick y
else
{
if (nunData[2]>0xA0) {dxf = 2; dyf = 0;vPORTB &= 0xFC;}// déplacement latéral vers la droite (accéléromètre)
if (nunData[2]<0x60) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;}// déplacement latéral vers la gauche (accéléromètre)
if (nunData[1]>0xC0) {dyf = -2; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;} //déplacement vertical vers le bas(stick)
if (nunData[1]<0x30) {dyf = 2; dxf = 0;vPORTB |= 0x03;} //déplacement vertical vers le haut (stick)
}
if (nunData[2]<0x70) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;
On choisit le byte 2 (axe X de l'accéléromètre) , et si sa valeur est inférieur a 0111 0000 la position en x du pacman (dxf) se déplace de 2 vers la gauche. La valeur 0x70 correspond à la sensibilité de l'accéléromètre.
Autres modifications apportées
Affichage de "live"
Le projet fourni par l'enseignant comptait le nombre de fois que l'on perdait une rencontre avec un fantôme. Nous avons décidé de donner un nombre de touches maximum autorisées (au nombre de 9). On décomptera ces "vies" à chaque touche avec un fantôme, une arrivée à 0 stoppera le jeu et affichera un "game over".
- Afin d'afficher le mot LIVES à l'écran
unsigned char *adr_message;//on se place au bout à droite et on remonte de n lignes (n=2 ici)
adr_message = (unsigned char *)LIVESUNIT; //debut memoire+derniere ligne+offset dans ligne
//affichage de LIVES
*adr_message = lives + '0';
adr_message-=10;*adr_message = 'S';
adr_message-=2;*adr_message = 'E';
adr_message-=2;*adr_message = 'V';
adr_message-=2;*adr_message = 'I';
adr_message-=2;*adr_message = 'L';
- Afin de déclarer le nombre de vie, et de le décrémenter à chaque touche avec le fantôme
// dans la fonction main
unsigned char live=9 ;
[...]
// décrémentation dans la boucle if ((xe==x)&& (ye==y))
if ((xe==x) && (ye==y)) {
// if enemy catchs pacman
GhostState = ENEMYINHOME;
xe = 48; ye = 40; x = 8; y = 16;
setpacmanXY(x,y);
setenemyXY(xe,ye);
// BCD lives management
lives--;
// conversion BCD
if ((lives &0x0F) > 9) lives = lives-6;
if ((lives &0xF0) > 0x90) lives = lives-0x60;
putLives(lives);
//xe=44;ye=16;
softTimer=0;
waitStart();
}
}
La décrémentation se fait quand (xe==x)&& (ye==y) c'est-à-dire quand la position du pacman est égale à la position du fantôme : quand le pacman se fait manger.
elle se traduit par lives--;
On peut remplacer 9 par la valeur que l'on veut, nous avons choisi 9 pour laisser un jeu assez simple.
You Win et Game Over
- Si toutes les gommes sont mangées en moins de 9 vies, le jeu s'arrête et affiche ":)!YOU WIN!:)".
//si on a mangé toutes les gommes, on a gagné :
if(nb_gomme>120){
putMessage(":)!YOU WIN!:)");
while(1);
}
- Une arrivée à 0 du nombre de vie stoppera le jeu et affichera un ":(!GAME OVER!):".
//si on a 0 vie, on a perdu :
if(lives==0){
putMessage(":(!GAME OVER!):");
while(1);
}
La gomme rouge
L'année précédente la gomme rouge n'avait pas été gérée, nous avons décidé de pouvoir l'intégrer au jeu comme les autres gommes blanches sauf qu'elle fera gagner 100 points au lieu d'un. Par manque de temps nous n'avons pas pu programmer un retour à la maison du fantôme ou un gobage du fantôme si celui-ci est touché par le pacman après avoir ingéré la gomme rouge.
Nous avons donc rajouté le programme ci-dessous dans la fonction eat_Pac_Gomme :
void eat_Pac_Gomme(unsigned int addr, uint16_t *score, unsigned char *nb_gomme){
char* ptr;
ptr = (char *)(addr);
if(*ptr==4){
*ptr=0;
increaseScore(score,1);
*nb_gomme+=1;
}
else if(*ptr==5){
*ptr=0;
increaseScore(score,100);
*nb_gomme+=1;
}
}
Éventuelles améliorations à apporter
Pour aller plus loin dans ce jeu de Pacman, plusieurs modifications pourraient être apportées :
- Gérer les mouvements du second fantôme.
- Faire une réinitialisation du jeu après une victoire ou une défaite, actuellement après un "You Win" ou "Game Over", le jeu reste bloqué dans la boucle "while(1)", il faut de nouveau transférer le programme sur la carte pour recommencer.
- Gérer un mode faisant du pacman le chasseur et non plus le chassé après avoir eu la gomme rouge, et de ce fait gérer un chemin de retour vers la maison pour le fantôme.
- Actuellement le pacman se déplace deux fois plus lentement que le fantôme, s'ils se déplacent tout deux à la même vitesse c'est trop simple, il faudrait trouver un compromis entre les deux vitesses.
- Gérér un autre mode de calcul de trajectoire du fantôme vers le pacman, actuellement il prend le chemin le plus court vers le pacman, autrement dit s'il y a des mur entre lui et le pacman, il reste bloqué par l'obstacle jusqu'à ce que le pacman bouge.
Programme C complet
// ----------------------------------------------------------
// Projet TR Geii 2014 : PACMAN
// Réalisé par : Maëlle SAVRY, Sumeyye KONAK
// ----------------------------------------------------------
#include <avr/io.h>
#undef F_CPU
#define F_CPU 25000000UL
#include "util/delay.h"
/* Port A */
//#define PINA _SFR_IO8(0x19)
// Les ports ci-dessous n'existent pas dans l'ATMega8 de la vraie vie mais nous les avons ajouté dans notre coeur
// d'où leur présence ici : ne pas modifier les fichiers d'entête !
#define DDRA _SFR_IO8(0x1A)
#define PORTA _SFR_IO8(0x1B)
//******************************************************************************************************************************
// constants definitions (see VHDL files)
//******************************************************************************************************************************
#define SCOREUNIT 1945+1024+21+900 //debut memoire(2048)+2*derniere ligne(900)+2*offset dans ligne(21)
#define LIVESUNIT 1958+1024+34+900 //debut memoire(2048)+2*derniere ligne(900)+2*offset dans ligne(34)
#define GATEWAY 2454 //debut memoire(2048)+ 2* 3eme ligne(192)+2*offset dansligne(11)=2454
#define ENEMYINHOME 1
#define ENEMYINGATEWAY 2
#define ENEMYINMAZE 3
#define ENEMYISVULNERABLE 4
#define ENEMYISDEAD 5
//******************************************************************************************************************************
// functions prototypes
//******************************************************************************************************************************
void my_delay(unsigned int delay);
unsigned char PseudoAleat(uint16_t Lim);
void setpacmanXY(uint8_t x,unsigned char y);
void setenemyXY(uint8_t x,unsigned char y);
void setenemy2XY(uint8_t x,unsigned char y);
void putLives(uint8_t lives);
void putScore(uint16_t Score);
unsigned int computeTileAddress(unsigned char x,unsigned char y);
unsigned char computePossibleWays(unsigned char x,unsigned char y);
unsigned char computeDistance(unsigned char x,unsigned char y, unsigned char xe, unsigned char ye);
void increaseScore(uint16_t *score, uint8_t nb_points);
//déclaration de la fonction permettant la lecture des données dans le FIFO
void readFIFOandAskForNextData(unsigned char nunchukDATA[]);
void eat_Pac_Gomme(unsigned int addr, uint16_t *score/*, unsigned char *cycle*/, unsigned char *nb_gomme);
void pathFinding(uint8_t pacmanY, uint8_t pacmanX, uint8_t fantomeY, uint8_t fantomeX, unsigned char *vPORTB);
void putMessage(char str[]);
void waitStart();
void usart_init(void);
void usart_send(unsigned char ch);
char usart_receive(void);
//on déclare un tableau 2D de la taille du jeu (en char car nombres petits)
//11(nb lignes) et 32(nb colonnes) prenent en compte les murs autours !
int8_t map[11][32]={{0},{0}};
//tableau contenant le chemin à parcourir (taille prévue large pour l'instant)
unsigned char road_map[80]={
0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x04,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,
0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x04,0x02,0x02,0x02,0x02,0x02,
}; //on met dedans le chemin initial du pacman
//******************************************************************************************************************************
// global variables instead of local to save space in stack
//******************************************************************************************************************************
unsigned int score=0,softTimer=0;
unsigned char GhostState; // si cette variable est ajoutée en locale au main : plantage assuré !!!
//******************************************************************************************************************************
// main
//******************************************************************************************************************************
int main (void)
{
char dx=0,dy=0,dxf=0,dyf=0,*gateway_Tile;
unsigned char nunData[6];
// position pacman : sa position réelle est (x*2,y*2) à cause du matériel qui laisse tomber le poids faible
unsigned char x=8,y=16,distance;//,commande; // en haut à gauche du labyrinthe
unsigned char lives=9, theWay_pacman, theWay_enemy, vPORTB=0,xe,ye,aleat=0x0A;
unsigned char nb_gomme=0;
DDRB=0x0F;
PORTB=vPORTB;
/********************************************************/
//on met des murs tout autour de la map pour ne pas avoir de problèmes pour la suite
uint8_t i=0;
for(i=0;i<11;i++){
map[i][0]=-1;
map[i][31]=-1;
}
for(i=0;i<32;i++){
map[0][i]=-1;
map[10][i]=-1;
}
unsigned char *adr_message;
//on se place au bout à droite et on remonte de n lignes (n=2 ici)
adr_message = (unsigned char *)LIVESUNIT; //debut memoire+derniere ligne+offset dans ligne
//affichage de LIVES
*adr_message = lives + '0';
adr_message-=10;
*adr_message = 'S';
adr_message-=2;
*adr_message = 'E';
adr_message-=2;
*adr_message = 'V';
adr_message-=2;
*adr_message = 'I';
adr_message-=2;
*adr_message = 'L';
/************************************************************/
xe = 48;ye=40;
GhostState=ENEMYINHOME;
gateway_Tile = (char *) GATEWAY; // pointe sur pavé entrée maison enemi
// on ferme virtuellement la maison !
*gateway_Tile=0x20;gateway_Tile++;gateway_Tile++;*gateway_Tile=0x20;gateway_Tile = (char *) GATEWAY;
setpacmanXY(x,y);
setenemyXY(xe,ye);
setenemy2XY(xe+1,ye+1);
putLives(lives);
putScore(score);
usart_init();
waitStart();
while(1) {
putLives(lives);
putScore(score);
//******* gestion des directions avec mémorisation ************
readFIFOandAskForNextData(nunData);
/*-------------------------------------------14.02.2014------------------------------------------------------------*/
//BC-joystick
if((nunData[5]&0x02)==0x00){
if (nunData[0]>0xC0) {dxf = 2; dyf = 0;vPORTB &= 0xFC;}
if (nunData[0]<0x30) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;}
if (nunData[1]>0xC0) {dyf = -2; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;}
if (nunData[1]<0x30) {dyf = 2; dxf = 0;vPORTB |= 0x03;}
}
//BZ-accelerometre
if((nunData[5]&0x01)==0x00){
if (nunData[2]>0x80) {dxf = 2; dyf = 0;vPORTB &= 0xFC;}
if (nunData[2]<0x70) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;}
if (nunData[3]>0x90) {dyf = -2; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;}
if (nunData[3]<0x60) {dyf = 2; dxf = 0;vPORTB |= 0x03;}
}
//de base, accelerometre x, joystick y
else
{
if (nunData[2]>0xA0) {dxf = 2; dyf = 0;vPORTB &= 0xFC;}
if (nunData[2]<0x60) {dxf = -2; dyf = 0;vPORTB = (vPORTB&0xFD)|0x01;}
if (nunData[1]>0xC0) {dyf = -2; dxf = 0;vPORTB = (vPORTB &0xFE)|0x02;}
if (nunData[1]<0x30) {dyf = 2; dxf = 0;vPORTB |= 0x03;}
}
/*-------------------------------------------------------------------------------------------------------------------*/
theWay_enemy = computePossibleWays(xe,ye); // compute enemy's possible way
theWay_pacman = computePossibleWays(x,y); // compute pacman's possible way
if (((x & 0x03)==0x00) && ((y & 0x07)==0x00)) { // si au centre d'un pavé
dx=dxf;dy=dyf; // changement de direction seulement quand sur pavé
if (((theWay_pacman & 0x08)==0x00) && (dy == -2)) dy=0; // empêche de monter
if (((theWay_pacman & 0x04)==0x00) && (dy == 2)) dy=0; // empêche de descendre
if (((theWay_pacman & 0x02)==0x00) && (dx == -2)) dx=0; // empêche d'aller à gauche
if (((theWay_pacman & 0x01)==0x00) && (dx == 2)) dx=0; // empêche d'aller à droite
eat_Pac_Gomme(computeTileAddress(x,y),&score,&nb_gomme);
} //end if
y = y + dy;
x = x + dx;
// gestion des échapatoires latérales
if (x > 126) x = 4;
if(x < 4) x = 128;
// gestion des échapatoires verticales
if (y > 96) {
y = 4; x= 44; // il faut aussi déplacer x !
}
if (y < 4) {
y = 96; x=48; // il faut aussi déplacer x !
}
// Enfin on positionne pacmanxxxx
setpacmanXY(x,y);
// managing enemy
switch(GhostState) {
case ENEMYINHOME :
aleat = PseudoAleat(0x007F); //gene pseudoaleat
if ((theWay_enemy & aleat & 0x08)) ye -= 8; else
if ((theWay_enemy & aleat & 0x04)) ye += 8; else
if ((theWay_enemy & aleat & 0x02)) xe -= 4; else
if ((theWay_enemy & aleat & 0x01)) xe += 4;
// if (xe > 36 && xe < 48)
if(ye > 24 && ye < 32) ye += 8;
if (softTimer>=0x003F) {
GhostState = ENEMYINGATEWAY;
//softTimer = 0;
}
break;
case ENEMYINGATEWAY : // objectif : ye = 16; xe = 44;
if (xe > 44) xe -=4; else
if (xe < 44) xe +=4;
if (xe == 44) { // si destination en x on commence sur y
if (ye > 16) ye -=8; else
if (ye < 16) ye +=8;
}
if ((xe == 44) && (ye == 16)) GhostState = ENEMYINMAZE;
// on ouvre la maison !
// *gateway_Tile=0x00;gateway_Tile++;gateway_Tile++;*gateway_Tile=0x00;gateway_Tile = (char *) GATEWAY;
break;
case ENEMYINMAZE :
// on ferme virtuellement la maison !
// *gateway_Tile=0x20;gateway_Tile++;gateway_Tile++;*gateway_Tile=0x20;gateway_Tile = (char *) GATEWAY;
distance = computeDistance(x,y,xe,ye);
if ((computeDistance(x,y,xe,ye+8) < distance) && (theWay_enemy & 0x04)) ye += 8; else
if ((computeDistance(x,y,xe,ye-8) < distance) && (theWay_enemy & 0x08)) ye -= 8; else
if ((computeDistance(x,y,xe+4,ye) < distance) && (theWay_enemy & 0x01)) xe += 4; else
if ((computeDistance(x,y,xe-4,ye) < distance) && (theWay_enemy & 0x02)) xe -= 4; else {
// ici si enemy a attrappé pacman ou pas de possibilité de mouvement
// pacman catched ?
if ((xe==x) && (ye==y)) { // if enemy catchs pacman
GhostState = ENEMYINHOME;
xe = 48; ye = 40;
x = 8; y = 16;
setpacmanXY(x,y);
setenemyXY(xe,ye);
// BCD lives management
lives--;
// conversion BCD
if ((lives &0x0F) > 9) lives = lives-6;
if ((lives &0xF0) > 0x90) lives = lives-0x60;
putLives(lives);
//xe=44;ye=16;
softTimer=0;
waitStart();
} else { // probleme si l'on vient ici, on a toutes les chances de boucler => aleat
aleat = PseudoAleat(0x007F); //gene pseudoaleat
if ((theWay_enemy & aleat & 0x04)) ye += 8; else
if ((theWay_enemy & aleat & 0x08)) ye -= 8; else
if ((theWay_enemy & aleat & 0x02)) xe -= 4; else
if ((theWay_enemy & aleat & 0x01)) xe += 4;
}
}
break;
default : ye = 16; xe = 44;
} // switch
// Enemy never goes out of maze
if (ye < 16 ) ye = 16;
if (ye > 80 ) ye = 80;
if (xe < 8 ) xe = 8;
if (xe > 124 ) xe = 124;
setenemyXY(xe,ye);
vPORTB = vPORTB ^0x04;//toggle : basculement ouvert/fermé du pacman
PORTB = vPORTB;
my_delay(150);
softTimer++;
//si on a mangé toutes les gommes, on a gagné :
if(nb_gomme>120){
putMessage(":)!YOU WIN!:)");
while(1);
}
//si on a 0 vie, on a perdu :
if(lives==0){
putMessage(":(!GAME OVER!):");
while(1);
}
// putMessage(2);
} //end while(1)
} //end main
//******************************************************************************************************************************
// function setpacmanXY()
// purpose: put the pacman with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setpacmanXY(uint8_t x,unsigned char y){
// voir fichier io2.vhd pour comprendre
PORTA=x;
PORTC=y;
}
//******************************************************************************************************************************
// function setenemyXY()
// purpose: put the enemy with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setenemyXY(uint8_t x,unsigned char y){
// voir fichier io2.vhd pour comprendre
DDRA=x;
DDRC=y;
}
//******************************************************************************************************************************
// function setenemy2XY()
// purpose: put the second enemy with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return:
// note:
//******************************************************************************************************************************
void setenemy2XY(uint8_t x,unsigned char y){
ADCL=x;
ADCH=y;
}
//******************************************************************************************************************************
// function PseudoAleat()
// purpose: simple random generator
// arguments:
// corresponding modulo = nb different values :12 for us
// return:
// note:
//******************************************************************************************************************************
unsigned char PseudoAleat(uint16_t Lim){
static uint16_t Y = 1;
Y = (Y * 32719 + 3) % 32749;
return (Y % Lim); // aucun offset
// ci-dessous ne fonctionne pas
// static uint8_t Y = 1;
// Y++;
// return Y;
}
//******************************************************************************************************************************
// function my_delay()
// purpose: time consuming function to waste time
// arguments:
// corresponding delay
// return:
// note:
//******************************************************************************************************************************
void my_delay(unsigned int delay){
int i;
for(i=0;i<delay;i++) _delay_ms(1);
}
//******************************************************************************************************************************
// function putLives()
// purpose: put lives in the screen by writing in RAM (RAMB16_S9_S9)
// arguments:
// corresponding lives
// return:
// note:
//******************************************************************************************************************************
void putLives(uint8_t lives){
unsigned char *afflives;
afflives = (unsigned char *)LIVESUNIT; //debut memoire+derniere ligne+offset dans ligne +25???
*afflives = (lives &0x0F)+'0';
afflives--;afflives--;
*afflives = ((lives &0xF0)>>4)+'0';
}
//******************************************************************************************************************************
// function putSore()
// purpose: put score in the screen by writing in RAM (RAMB16_S9_S9)
// arguments:
// corresponding Score
// return:
// note: only four digits are managed at the moment
//******************************************************************************************************************************
void putScore(uint16_t Score){
unsigned char *affscore;
affscore = (unsigned char *) SCOREUNIT;//debut memoire+derniere ligne+offset dans ligne +25???
*affscore = (Score &0x000F)+'0';
affscore--;affscore--;
*affscore = ((Score &0x00F0)>>4)+'0';
affscore--;affscore--;
*affscore = ((Score &0x0F00)>>8)+'0';
affscore--;affscore--;
*affscore = ((Score &0xF000)>>12)+'0';
}
//******************************************************************************************************************************
// function computeTileAddress()
// purpose: compute the address of the tile where I am with x and y coordinates
// arguments:
// corresponding x and y coordinates
// return: address
// note: you cannot understand if you never read the corresponding VHDL code.
//******************************************************************************************************************************
unsigned int computeTileAddress(unsigned char x,unsigned char y){
unsigned char xc,yc; // coordonnées du centre du sprite xc,yc
unsigned int val,addr;
// obtention du nemero de pavage de l'écran VGA
xc = x + 1; //x + 8 en principe mais on a perdu volontairement les 2 poids faibles dans x
yc = y + 2; //Y + 16 en principe mais on a perdu volontairement les 2 poids faibles dans y
val = xc >> 2; // je garde les six bits de poids fort
addr = yc & 0x0078; // je garde les quatre bits de poids fort
addr <<= 3; // je laisse la place pour concaténation
addr = addr + (val & 0x3F); // J'ai mon numéro de pavage ici
// quelle est son adresse en RAMB16_S9_S9 ?
addr = (addr+1024)<<1; //debut de l'adresse du début de pavage
return addr;
}
//******************************************************************************************************************************
// function computePossibleWays()
// purpose: compute the possible ways starting from x and y coordinates
// arguments:
// corresponding x and y coordinates
// return: bits as Up in b3, Down in b2, Left in b1 and right in b0 : if set the corresponding way is free
// note:
//******************************************************************************************************************************
unsigned char computePossibleWays(unsigned char x,unsigned char y){
unsigned char *ramB16,res;
unsigned int addr;
res=0;
addr = computeTileAddress(x,y);
ramB16 = (unsigned char *)(addr - 128); // 64 en principe mais mémoire organisée ainsi : up
// ajouté l'espace (0x20) pour fermeture invisible a l'ecran 15/01/13
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x08; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr + 128); // 64 en principe mais mémoire organisée ainsi : down
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x04; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr - 2); // 1 en principe mais mémoire organisée ainsi : left
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x02; //si pas brique on peut y aller
ramB16 = (unsigned char *)(addr + 2); // 1 en principe mais mémoire organisée ainsi : right
if ((*ramB16 != 0x07)&&(*ramB16 != 0x20)) res=res|0x01; //si pas brique on peut y aller
return res;
}
//******************************************************************************************************************************
// function computeDistance()
// purpose: compute the distance between enemy and pacmanscore
// arguments:
// corresponding x and y coordinates and xe and ye
// return: a non-euclien distance
// note:
//******************************************************************************************************************************
unsigned char computeDistance(unsigned char x,unsigned char y, unsigned char xe, unsigned char ye){
unsigned char res;
char delta;
delta = xe-x; if (delta <0) res = -delta; else res = delta;
delta = ye-y; if (delta <0) res -= delta; else res += delta;
return res;
}
//******************************************************************************************************************************
// function eat_Pac_Gomme()
// purpose: eat a pac gomme if it exixt
// arguments:
// corresponding address, corresponding score, corresponding lives
// return: new score is updated
// note: you cannot understand if you never read the corresponding VHDL code.
//******************************************************************************************************************************
void eat_Pac_Gomme(unsigned int addr, uint16_t *score, unsigned char *nb_gomme){
char* ptr;
ptr = (char *)(addr);
if(*ptr==4){
*ptr=0;
increaseScore(score,1);
*nb_gomme+=1;
}
else if(*ptr==5){
*ptr=0;
increaseScore(score,100);
//*cycle=2;
*nb_gomme+=1;
}
}
void increaseScore(uint16_t *score, uint8_t nb_points){
uint8_t i=0;
for(i=0;i<nb_points;i++){
*score+=1;
if((*score&0x000F)>9) *score+=0x0006;
if(((*score&0x00F0)>>4)>9) *score+=0x0060;
if(((*score&0x0F00)>>8)>9) *score+=0x0600;
if(((*score&0xF000)>>12)>9) *score+=0x6000;
if(*score>39321) *score=0;
}
}
//******************************************************************************************************************************
// function waitStart()
// purpose: wait Z button start : Doesn't work properly at the moment !!!
// arguments:
//
// return:
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void waitStart(){
// les boutons sont inversés dans la Nunchuk
// Z button in 2
// je n'ai pas réussi à faire marcher les boutons !!!!
// while (!bit_is_set(PINB,PINB1)) ; // un coup sur droite pour débloquer
char ch;
ch=usart_receive();
}
//************************************************************************
// function usart_init()
// purpose: init first rs232 PORT
// arguments:
// no argument
// return:
// note: 38400,8,n,2 hard coded : transmission and reception
//************************************************************************
void usart_init(void) {
UCSRB = (1<<TXEN)|((1<<RXEN)); // transmission et reception
}
//************************************************************************
// function uart_send()
// purpose: put character in first rs232 PORT
// arguments:
// corresponding character
// return:
// note: 38400,8,n,2 hard coded
// initialisation uart prealable requise
//************************************************************************
void usart_send(unsigned char ch){
while(!(UCSRA & (1<<UDRE)));
UDR = ch;
}
//************************************************************************
// function uart_receive()
// purpose: read character in second rs232 PORT
// arguments:
// corresponding character
// return: non-blocking sub return 1 if no data present else return char
// note: 38400,8,n,2 hard coded, non-blocking sub return 0 if no data present
// initialisation uart prealable requise
//************************************************************************
char usart_receive(void){
if (UCSRA & (1<<RXC)) //attente tant que Data Present en réception
return UDR;
else return 0;
}
//******************************************************************************************************************************
// function readFIFOandAskForNextData()
// purpose: read FIFO and put data in array and start the request of next data
// arguments: array of unsigned char to store FIFO data
//
// return:
// note: you cannot understand if you never read the corresponding VHDL code (in io2.vhd)
//******************************************************************************************************************************
void readFIFOandAskForNextData(unsigned char nunchukDATA[]){
unsigned char i;
while(!(TWCR & (1<<TWINT))); // attente données dans le FIFO
for (i=0;i<6;i++) //lecture du Nunchuk du FIFO dans tableau
nunchukDATA[i]=TWDR;
// on demande une nouvelle lecture par le pérphérique : toutes données seront pour la prochaine fois
TWCR |= (1<<TWEN); //depart
TWCR &= ~(1<<TWEN);// arret pour attendre la prochaine fois : voir machine d'états pour comprendre
}
//******************************************************************************************************************************
// function putLives()
// purpose: put lives in the screen by writing in RAM (RAMB16_S9_S9)
// arguments:
// corresponding lives
// return:
// note:
//******************************************************************************************************************************
/*-------------------------------------------21.02.2014------------------------------------------------------------*/
void putMessage(char str[]){
unsigned char *afflives,i=0;
afflives = (unsigned char *)(LIVESUNIT -128-36); //debut memoire+derniere ligne+offset dans ligne +25???
while (str[i] != 0) {
*afflives = str[i];
i++;
afflives++;afflives++;
}
}
/*----------------------------------------------------------------------------------------------------------------*/
void pathFinding(uint8_t pacmanY, uint8_t pacmanX, uint8_t fantomeY, uint8_t fantomeX, unsigned char *vPORTB){
unsigned char i=0,j=0;
unsigned char cout=1,nb_mouvements=0;
unsigned char the_way=0; //à transformer en pointeur pour moins de mémoire ?
//on transforme les position x,y en case du tableau
pacmanY=(pacmanY-8)>>3; //pacmanY=(pacmanY-8)/8;
pacmanX=(pacmanX-4)>>2; //pacmanX=(pacmanX-4)/4;
fantomeY=(fantomeY-8)>>3;
fantomeX=(fantomeX-4)>>2;
//on commence par reset le tableau en gardant seulement les murs
for(i=1;i<31;i++){
for(j=1;j<10;j++){
if(map[j][i]!=-1) map[j][i]=0; //si pas de mur, on met la case à 0 (trajet possible)
}
}
//on reset aussi le road_map
for(i=0;i<80;i++){
road_map[i]=0;
}
//on marque la position du pacman avec un cout de 1
map[pacmanY][pacmanX]=1;
//on continue les opérations tant qu'on a pas atteint la case du pacman
while(map[fantomeY][fantomeX]==0){
for(i=1;i<31;i++){
for(j=1;j<10;j++){
//si la case a le cout actuel (le plus élevé) on donne un cout aux cases autour
if(map[j][i]==cout){
//on test les déplacements possible à partir de la case en cours
//the_way=computePossibleWays(((i*4)+4),((j*8)+8));
the_way=computePossibleWays(((i<<2)+4),((j<<3)+8)); //on test les posibilités quand on est dans la case (i,j)
//si la case n'a pas de cout on met cout+1 sinon on indique un mur (-1)
if(map[j][i+1]==0){ //case à droite
if((the_way&0x01)==0x01) map[j][i+1]=cout+1;
else map[j][i+1]=-1;
}
if(map[j][i-1]==0){ //case à gauche
if((the_way&0x02)==0x02) map[j][i-1]=cout+1;
else map[j][i-1]=-1;
}
if(map[j-1][i]==0){ //case en haut
if((the_way&0x08)==0x08) map[j-1][i]=cout+1;
else map[j-1][i]=-1;
}
if(map[j+1][i]==0){ //case en bas
if((the_way&0x04)==0x04) map[j+1][i]=cout+1;
else map[j+1][i]=-1;
}
}
}
}
//on incrémente le cout pour les cases plus loin
cout++;
}
//le chemin jusqu'au pacman est maintenant mappé, il faut donc récupérer les mouvements à effectuer
//on part de la case du pacman
i=fantomeX;
j=fantomeY;
while(cout>1){
//si le cout de la case autour = cout-1
//dans road_map on met le mouvement inverse car on fera le trajet dans l'autre sens
if(map[j+1][i]==(cout-1)){
road_map[nb_mouvements]=0x04; //on va en bas
j+=1; //on se met dans la case suivante
}
else if(map[j-1][i]==(cout-1)){
road_map[nb_mouvements]=0x08; //on va en haut
j-=1;
}
else if(map[j][i+1]==(cout-1)){
road_map[nb_mouvements]=0x01; //on va à droite
i+=1;
}
else if(map[j][i-1]==(cout-1)){
road_map[nb_mouvements]=0x02; //on va à gauche
i-=1;
}
nb_mouvements++;
cout--;
}
}