mardi 3 juillet 2012

Un adaptateur de pads consoles universel - Le pad Mégadrive



Oui je sais, du temps a passé depuis le premier article, mais promis on s'y met.
Donc nous allons parler aujourd'hui du pad Megadrive. Pourquoi lui en premier? Parce que la Megadrive est la console de mon adolescence bercé que j'ai été à Sonic, Streets of Rage ou Landstalker.
Coup de chance pour ma pomme, c'est aussi une des plus simples à interfacer et qui plus est, les pads Megadrive sont équipés d'une prise standard, une DB9 femelle. Pour les connaisseurs, c'est la prise employée sur les ports série, donc très facile à trouver. Nous verrons plus tard que c'est loin d'être aussi simple pour les autres consoles.


Mais revenons au pad Mégadrive, nous avons une croix directionnelle soit 4 touches (Haut, Bas, Gauche, Droite), 3 boutons de tir A, B et C et le bouton Start, si utile pour faire une pause dans les jeux sans sauvegarde de l'époque.


J'avais prévu dans un article précédent d'émuler les pads sans joysticks en mode "clavier". Je n'avais pas pensé à la limitation à 6 touches simultanées des claviers USB. Nous allons donc uniquement utiliser le mode "Contrôleur de jeu".


L'interfaçage électronique du pads 3 boutons est relativement simple. Une sortie est dédiée au bouton haut, une au bouton bas. Les touches A-B et Start-C se partagent chacune une sortie. Pour choisir entre l'une et l'autre, une entrée Select doit être basculée. Si vous avez bien compté, il manque Gauche et Droite. Celles-ci ont leur sortie dédiée, mais étrangement, il faut mettre Select à 1 pour récupérer l'état de la touche. Si Select est à 0, on récupérera aussi des 0 sur ces broches, quelque-soit l'état de la touche.
Broche du connecteur
Rôle
Branchement sur le Teensy
1
Haut
PIN_C0
2
Bas
PIN_C1
3
0 ou Gauche
PIN_C2
4
0 ou Droite
PIN_C3
5
Alimentation 5V (Vcc)
Vcc
6
A ou B
PIN_C4
7
Select
PIN_C6
8
Masse (GND)
GND
9
Start ou C
PIN_C5


Petit aparté qui a son importance, lorsque une touche est appuyée, elle renvoie 0. Si elle est est relâchée, on lit un 1. Il est important de s'en rappeler, car la classe Joystick marche de façon inverse et on signale qu'une touche est appuyée en envoyant à 1.


Je vous laisse le soin de choisir votre méthode de fabrication de votre système, sur carte d'essai, sur plaque Labdec, sur un PCB fait pour. Pour ma part, j'ai récupéré une carte d'essai d'un précédent projet qui comportait un support pour le Teensy et des tas de connecteurs HE-10. Il ne me restait donc qu'à faire la liaison entre le Teensy et les connecteurs HE-10 et le câble d'adaptation HE-10 vers le connecteur de la manette. Vu l'état de délabrement de cette carte, soudée, dessoudée et ressoudée de partout, vous ne la verrez pas pour le moment, mais promis, je vous fais une photo d'une future version présentable.


En attendant, voici un schéma extrêmement complexe du montage :


La dénomination des broches est en fait celle du Teensy++ sans la surcouche Arduino. Je préfère cette version aux simples numéros utilisés sur Arduino car on s'y retrouve plus facilement pour accéder rapidement aux entrées-sorties (en utilisant les registres du microcontrôleur plutôt que les fonctions Arduino relativement lourdes). Heureusement, PJRC, le concepteur du Teensy permet de nommer les broches par leur nom de port dans la surcouche Teensyduino. Ainsi le bit 3 du port C, nommé PC3 sur le schéma sera identifié par PIN_C3 dans le soft Teensyduino....


Nous avons donc nos liaisons (Vcc et GND sont reprises sur les broches du Teensy), il reste à écrire le programme.


Nous allons commencer par définir des constantes qui remplacent les noms des broches, peu explicites, par les rôles de ces broches:




// Pins used for Genesis pad
#define GEN_UP PIN_C0
#define GEN_DOWN PIN_C1
#define GEN_LEFT PIN_C2
#define GEN_RIGHT PIN_C3
#define GEN_AB PIN_C4
#define GEN_SC PIN_C5
#define GEN_SELECT PIN_C6


Nous allons maintenant créer une poignée de variables globales qui vont refléter l'état du joystick:


unsigned int Joy_X,Joy_Y;
unsigned char Joy_Buttons[32];
C'est dans ces variables que nous stockerons l'état du pad, avant de les envoyer effectivement au PC dans le programme principal.


Maintenant que les broches sont définies, que les variables globales sont créées, écrivons la fonction qui va lire l'état du pad:


void read_Genesis()
{
digitalWrite(GEN_SELECT,LOW);
if (digitalRead(GEN_AB)==LOW)
   Joy_Buttons[0]=1;
if (digitalRead(GEN_SC)==LOW)
   Joy_Buttons[3]=1;
digitalWrite(GEN_SELECT,HIGH);
if (digitalRead(GEN_AB)==LOW)
   Joy_Buttons[1]=1;
if (digitalRead(GEN_SC)==LOW)
   Joy_Buttons[2]=1;
if (digitalRead(GEN_UP)==LOW)
   Joy_Y=0;
if (digitalRead(GEN_DOWN)==LOW)
   Joy_Y=1023;
if (digitalRead(GEN_LEFT)==LOW)
   Joy_X=0;
if (digitalRead(GEN_RIGHT)==LOW)
   Joy_X=1023;
}
Si vous avez bien suivi l'explication au-dessus, vous devriez sans problèmes comprendre ce code. La seule "astuce"  est de faire croire, quand on appuie sur la croix directionnelle, que le joystick est envoyée en butée sur ses axes soit 0 et 1023.


Nous allons également ajouter deux fonctions Joy_Clear et Joy_Update qui vont respectivement ré-initialiser les variables d'état du joystick et envoyer les données au PC.


void Joy_Clear()
{
unsigned char i;
Joy_X=512;
Joy_Y=512;
for(i=0;i<32;i++)
   Joy_Buttons[i]=0;
}


void Joy_Update()
{
unsigned char i;
Joystick.X(Joy_X);
Joystick.Y(Joy_Y);
for(i=0;i<32;i++)
   Joystick.button(i+1,Joy_Buttons[i]);
}

Comme vous pouvez le remarquer, nous envoyons l'état des 32 boutons. C'est évidemment superflu pour le pad Mégadrive, mais ça nous permettra de réutiliser ces fonctions quand nous nous attaquerons à des pads plus évolués et plus fournis en boutons. Je garde aussi sous le coude un autre montage qui devrait faire plaisir aux nostalgiques d'un certain jeu Mégadrive, mais vous n'en saurez pas plus pour le moment, c'est une surprise....


Reste deux petites choses à écrire, les fonctions de l'Arduino Init(), qui va servir à initialiser le système et en particulier les entrées-sorties et Loop() qui est le programme principal


void setup() {
   pinMode(GEN_SELECT,OUTPUT);
}


void loop() {
   Joy_Clear();
   read_Genesis();
   Joy_Update();
   delay(20);
}
Ces fonctions sont d'une simplicité déconcertante. Dans setup(), nous n'initialisons que la sortie Select. En fait, les broches sont en entrée par défaut, nous n'avons donc rien à faire pour les autres broches.


Dans le programme principal, le loop(), nous commençons par ré-initialiser les variables. Il vaut mieux ne pas l'oublier car la fonction de lecture de la manette ne va pas vérifier si aucune touche n'est appuyée et donc, si vous appuyez une fois sur Droite, la touche sera comme bloquée.... Très pratique pour Sonic, un peu moins pour d'autres jeux.


Ensuite vient la lecture du pad proprement dit puis l'envoi au PC.
Enfin, nous faisons une pause de 20ms pour éviter de scruter trop souvent pour pas grand-chose le pad.


Dans le prochain article, nous nous attaquerons à un autre monument du jeu vidéo, à peine plus compliqué...le pad NES.


Pour ceux qui n'auraient pas le courage de recoller les morceaux, voici le programme complet, à copier-coller:


// Pins used for Genesis pad
#define GEN_UP PIN_C0
#define GEN_DOWN PIN_C1
#define GEN_LEFT PIN_C2
#define GEN_RIGHT PIN_C3
#define GEN_AB PIN_C4
#define GEN_SC PIN_C5
#define GEN_SELECT PIN_C6


// Global functions where datas from pads are written. Tranfered to joystick in Joy_Update()
unsigned int Joy_X,Joy_Y;
unsigned char Joy_Buttons[32];


void setup() {
pinMode(GEN_SELECT,OUTPUT);
}


void read_Genesis()
{
digitalWrite(GEN_SELECT,LOW);


if (digitalRead(GEN_AB)==LOW)
   Joy_Buttons[0]=1;
if (digitalRead(GEN_SC)==LOW)
   Joy_Buttons[3]=1;


digitalWrite(GEN_SELECT,HIGH);


if (digitalRead(GEN_AB)==LOW)
   Joy_Buttons[1]=1;
if (digitalRead(GEN_SC)==LOW)
   Joy_Buttons[2]=1;
if (digitalRead(GEN_UP)==LOW)
   Joy_Y=0;
if (digitalRead(GEN_DOWN)==LOW)
   Joy_Y=1023;
if (digitalRead(GEN_LEFT)==LOW)
   Joy_X=0;
if (digitalRead(GEN_RIGHT)==LOW)
   Joy_X=1023;
}


void Joy_Clear()
{
unsigned char i;
Joy_X=512;
Joy_Y=512;
for(i=0;i<32;i++)
   Joy_Buttons[i]=0;
}


void Joy_Update()
{
unsigned char i;
Joystick.X(Joy_X);
Joystick.Y(Joy_Y);
for(i=0;i<32;i++)
   Joystick.button(i+1,Joy_Buttons[i]);
}


void loop() {
Joy_Clear();
read_NES();
read_Genesis();
Joy_Update();
delay(20);
}


A très vite pour le pad NES....

Aucun commentaire:

Enregistrer un commentaire