Skyduino:~#
Articles
arduino, programmation, tutoriel

[Tuto] Arduino et gamepad de SNES

Bonjour tout le monde !

En attendant l’arrivée de mes circuits pour mon contrôleur de matrices de leds RGB, j’ai décidé de continuer à travailler sur le reste de mon projet de table basse interactive.

Et justement pour ce projet j’ai besoin d’un moyen simple et efficace de donner le contrôle du système à l’utilisateur.
La solution la plus simple est d’utiliser une manette de jeu. La question est donc : quelle manette utiliser ?

Le choix de la manette

DSCF2562

Au début je voulais utiliser des manettes de NES (la console de jeu 8 bits de Nintendo).
Ces manettes sont de grands classiques dans le domaine du rétro-gaming. Elles sont faciles à utiliser avec une carte électronique style Arduino et ont un design simple, mais ultra old-school.

Le problème c’est que des manettes de NES sur ebay ça se trouve certes assez facilement … mais à des prix bien trop élevés.
En plus, trouver une manette dans un état correct, pas trop jauni et en état de marche n’est pas si simple.
J’ai bien trouvé des versions "made in china" récentes, mais uniquement en USB, ce qui ne va pas du tout pour mon projet …

En cherchant un peu plus je suis tombé sur des versions "made in china" récentes de manettes de Super NES, la grande sœur de la NES.
Allez savoir pourquoi, ce type de manette est facilement trouvable en version USB ou en version d’origine sur ebay pour un prix dérisoire.

Alors oui, ce n’est pas exactement les mêmes manettes que les versions NES premières du nom, mais ça ira très bien pour mon projet.
J’en ai donc pris quatre comme on peut le voir sur la photo un peu plus haut :)

La seule réelle différence entre ces versions chinoises et une vraie manette de SNES est l’absence du logo Nintendo sur les versions chinoises.
Pour le reste : connecteur, forme, protocole, etc. c’est exactement la même chose qu’une manette Nintendo d’origine (bon le plastique est un peu plus cheap qu’une vraie manette, mais tant pis).

La connectique

DSCF2569

L’avantage avec les manettes de vieilles consoles de jeu c’est quelles sont ultra basique, donc facile utilisable.
À part le connecteur propriétaire tout le reste est facilement utilisable sans prise de tête.

DSCF2566

Justement pour le connecteur, j’avais besoin d’un connecteur femelle à encastrer dans ma table basse.
J’ai finalement trouvé mon bonheur auprès d’un vendeur ebay en Grande-Bretagne qui vend des câbles d’extension pour SNES.

DSCF2556DSCF2555

En coupant le câble en plein milieu et en ajoutant un connecteur au pas 2.54mm (le tout sécurisé par de la gaine thermo), j’obtiens un câble facilement inscrutable dans du bois d’un côté et facilement enfichable dans une breadboard de l’autre.
Le montage parfait en somme :)

Montage sauce Arduino

Pour faire mon montage et le code Arduino qui va avec, je me suis basé sur les informations présentes sur ce site :
http://www.gamefaqs.com/snes/916396-snes/faqs/5395
(allez directement au chapitre 4 pour les informations sur les manettes de SNES)

Pour résumé voici le câblage du connecteur de manette de SNES :

DSCF2572

Vous remarquerez les lignes : DATA, LATCH et CLOCK, en plus des alimentations +5v et GND.

Les manettes de SNES ne sont en fait que de simples registres à décalage sur 16 bits.
A l’époque on ne se cassait pas la tête avec des protocoles cryptés.
Un simple port SPI, voilà ce qu’utilise la SNES ;)

Le protocole de communication est vraiment trivial :
1) Envoyer une pulsation sur la ligne LATCH pour capturer l’état courant des boutons de la manette.
2) Récupérer 16 bits en SPI en utilisant les lignes DATA et CLOCK
3) Lire l’état des bits reçu (1 bit = 1 bouton)

Les 16 bits reçus sont organisés comme suit :

Bit 15 (MSB) Bit 14 Bit 13 Bit 12 Bit 11 Bit 10 Bit 9 Bit 8 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 (LSB)
"1" "1" "1" "1" R L X A Droite Gauche Bas Haut START SELECT Y B

Remarque : les boutons ont tous une résistance de tirage à +VCC en interne.
Un bit à "1" signifie donc que le bouton n’est PAS appuyé. C’est lorsque le bit du bouton est à "0" que celui-ci est appuyé.
Comme c’est peu pratique de travailler en logique inversée mon code ci-dessous fait la conversion 1-0 / 0-1 ;)

DSCF2575

En partant du schéma de câblage des manettes, j’obtiens le montage Arduino ci-dessus.

NB : La résistance de tirage à la masse sur la ligne DATA n’est pas là pour faire jolie ;)
Normalement les 4 bits de poids fort sont à "1" quand la manette est connectée (voir tableau plus haut).
Avec cette résistance de tirage, tous les bits sont lus comme étant à "0" quand aucune manette n’est connectée.
Ainsi si le code détecte qu’un de ces 4 bits à "1" n’est justement pas à "1" cela signifie qu’aucune manette n’est connectée.
Astucieux hein ;)

Quelques photos du montage :

DSCF2544

DSCF2548

DSCF2550

Le code

Le code est vraiment basique.
C’est quasiment un copier / coller du code de la fonction Arduino shiftIn(), mais en version 16 bits au lieu de 8 bits.

/* Constantes des bits de chaque bouton */
#define BTN_A 256
#define BTN_B 1
#define BTN_X 512
#define BTN_Y 2
#define BTN_SELECT 4
#define BTN_START 8
#define BTN_UP 16
#define BTN_DOWN 32
#define BTN_LEFT 64
#define BTN_RIGHT 128
#define BTN_L 1024
#define BTN_R 2048
#define NO_GAMEPAD 61440

/* Pin mapping */
static const byte PIN_LATCH = 2;
static const byte PIN_CLOCK = 3;
static const byte PIN_DATA = 4;

/** Setup() */
void setup() {
  
  /* Initialisation du port série */
  Serial.begin(9600);

  /* Initialisation des broches */
  pinMode(PIN_LATCH, OUTPUT);
  pinMode(PIN_CLOCK, OUTPUT);
  pinMode(PIN_DATA, INPUT); 
}

/** Loop() */
void loop() {
  static uint16_t oldBtns = 0;      // Anciennes valeurs des boutons
  uint16_t btns = getSnesButtons(); // Valeurs actuelles des boutons

  /* Affiche l'état des boutons uniquement en cas de changement */
  if(oldBtns != btns)
    oldBtns = btns;
  else
    return;
    
  /* Détecte la présence (ou non) d'une manette */
  if(btns & NO_GAMEPAD) {
    Serial.println(F("No gamepad connected"));
    return;
  }
  
  /* Affiche l'état de chaque bouton */
  if(btns & BTN_A)
    Serial.print(F("A "));
  else
    Serial.print(F("- "));

  if(btns & BTN_B)
    Serial.print(F("B "));
  else
    Serial.print(F("- "));

  if(btns & BTN_X)
    Serial.print(F("X "));
  else
    Serial.print(F("- "));

  if(btns & BTN_Y)
    Serial.print(F("Y "));
  else
    Serial.print(F("- "));

  if(btns & BTN_SELECT)
    Serial.print(F("SELECT "));
  else
    Serial.print(F("------ "));

  if(btns & BTN_START)
    Serial.print(F("START "));
  else
    Serial.print(F("----- "));

  if(btns & BTN_UP)
    Serial.print(F("UP "));
  else
    Serial.print(F("-- "));

  if(btns & BTN_DOWN)
    Serial.print(F("DOWN "));
  else
    Serial.print(F("---- "));

  if(btns & BTN_LEFT)
    Serial.print(F("LEFT "));
  else
    Serial.print(F("---- "));

  if(btns & BTN_RIGHT)
    Serial.print(F("RIGHT "));
  else
    Serial.print(F("----- "));

  if(btns & BTN_L)
    Serial.print(F("L "));
  else
    Serial.print(F("- "));

  if(btns & BTN_R)
    Serial.println(F("R"));
  else
    Serial.println(F("-"));
    
  /* Buttton debounce */
  delay(25);
}

/** Retourne l'état de chaque bouton sous la forme d'un entier sur 16 bits. */
uint16_t getSnesButtons() {

  /* 1 bouton = 1 bit */
  uint16_t value = 0;

  /* Capture de l'état courant des boutons */
  digitalWrite(PIN_LATCH, HIGH);
  digitalWrite(PIN_LATCH, LOW);

  /* Récupère l'état de chaque bouton (12 bits + 4 bits à "1") */
  for(byte i = 0; i < 16; ++i) {

    /* Lit l'état du bouton et décale le bit reçu pour former l'entier sur 16 bits final */
    value |= digitalRead(PIN_DATA) << i;

    /* Pulsation du signal d'horloge */
    digitalWrite(PIN_CLOCK, HIGH);
    digitalWrite(PIN_CLOCK, LOW);
  }

  /* Retourne le résultat sous une forme facile à manipuler (bouton appuyé = bit à "1") */
  return ~value;
}

Résultat sans manette connectée :

no_gamepad

Résultat avec une manette connectée et un idiot aux commandes :

gamepad_test

About these ads

Discussion

4 réflexions sur “[Tuto] Arduino et gamepad de SNES

  1. Je crois que tu vas devoir refaire le ménage dans tes ports com … :-)

    Publié par seb | 23 février 2014, 21 h 40 min
  2. Ce ne serait pas plus facile d’utiliser une interruption? La lecture des boutons ne peut pas se faire en permanence et si tu es dans une fonction d’affichage, tu risques de louper un appui sur un bouton.
    A moins que tu n’utilises un arduino dédié à la lecture des manettes, et que celui-ci envoie une interruption vers un autre arduino (gérant le programme principal/jeu).

    Publié par sebecam2000 | 25 février 2014, 1 h 09 min
    • Les manettes en question ne sont pas maître mais esclave SPI.
      Et vu qu’elles ne fournissent pas d’interruption en cas d’appui sur un bouton, une interruption "de lecture" n’est physiquement pas possible à mettre en place (à moins d’avoir une carte juste pour ça).

      Dans la NES/SNES à chaque VBLANK (interruption de fin d’affichage, typiquement à 60Hz) le processeur fait une lecture des boutons.
      C’est pas une erreur de conception mais une véritable volonté de faire comme ça.

      Bonus : 60Hz = période de 16ms = temps de rebond d’un bouton mécanique.
      Tu ne perds donc aucun appuis sur les boutons, par contre tu ignores de fait les rebonds des boutons ;)
      (moi j’ai pris un temps de rebond de 25ms, made in chinois, tout ça)

      Publié par skywodd | 1 mars 2014, 16 h 28 min

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Archives

Wow. Dogecoin. Amaze.

Laissez un tip en Dogecoin

DMMNFk6WBVTpx2Wu1Z35GL61QXSd6r6WQx

Suivre

Recevez les nouvelles publications par mail.

Rejoignez 730 autres abonnés