Dans l’article précédent, j’ai conçu une carte shield qui permet de remplacer par un Arduino Leonardo, la carte USB Joystick fournie avec le kit de joystick et de boutons achetés sur Aliexpress.

Cela fonctionne très bien, mais j’aimerais aussi contrôler les LED, par exemple pour voir quand on appuie sur les boutons, faire une petite animation lors du branchement la manette, déboguer le programme etc…
J’ai donc décidé d’améliorer la carte Arcade Shield.
PCBWay, le sponsor de la vidéo
L’article et la vidéo sont sponsorisés par PCBWay. Les PCB m’ont été envoyés gratuitement pour que je teste leurs services et partage mon expérience.

Nombre de de pins disponible
Pour contrôler directement les 10 LED (des 10 boutons), il faudrait 10 sorties. Malheureusement, il n’en reste pas suffisamment puisque le joystick et les boutons en utilisent 14. Si on enlève les pins 0 et 1 (utilisées pour communiquer avec l’ordinateur) il reste seulement les pins 13, 21, 22 et 23. On pourrait éventuellement utiliser les pins 14, 15 et 16 mais cela nécessiterait l’ajout d’un connecteur supplémentaire (ICSP, en bas du schéma).

Il serait tout à fait possible de réduire nombre de boutons, parce que 6 boutons (Start, Select, A, B, X et Y) sont suffisants pour jouer à la plupart des jeux. Et les 4 boutons restants seraient placés sur les bords de la manette (devant ou sur les cotés) mais reliés aux autres boutons. Cela permettrait par exemple de jouer au flipper dans de bonnes conditions.
On pourrait également utiliser des matrices pour brancher les boutons (comme sur les claviers) et les LED. Mais les branchements sont plus compliqués, il reste peu de place pour ajouter tous les composants électroniques nécessaires (diodes et contrôleur de LED).
Le registre à décalage
Finalement, j’ai choisi d’utiliser le registre à décalage MC74HC595. Cette puce électronique permet de contrôler 8 LED avec seulement 3 pins de l’Arduino. Il faut relier les LED au registre à décalage et ensuite indiquer dans le programme quelle LED doit être allumée.
shiftOut(21, 23, LSBFIRST, B00000000);
Par exemple, pour éteindre toutes les LED, il faut envoyer la valeur (binaire) B00000000 au registre à décalage, pour allumer seulement le 2ème LED, la valeur (binaire) B01000000 etc..

Si vous souhaitez plus d’informations, sachez que j’explique tout ça dans mon livre sur l’Arduino.
Malheureusement, mon registre à décalage peut contrôler seulement 8 LED (sur 10). Il serait possible de relier ensemble plusieurs puces MC74HC595 pour augmenter le nombre de LED. Mais ça complique quand même un peu le code et les branchements. J’ai donc décidé de faire un compromis en reliant seulement les 8 boutons de jeu (B1 à B8) au registre à décalage et en laissant les 2 boutons Start en Select allumés en permanence. Voilà le schéma de branchement :

Et le code complet :
// Arcade_Shield_v2
#include <Joystick.h>
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
10, 0, // Button Count, Hat Switch Count
true, true, false, // X and Y, but no Z Axis
false, false, false, // No Rx, Ry, or Rz
false, false, // No rudder or throttle
false, false, false); // No accelerator, brake, or steering
// Registre_a_decalage MC74HC595AN
// D21 pour l'Arduino Leonardo et D17 pour l'Arduino Uno
const int MEMORISATION = 23; // SER
// D22 pour l'Arduino Leonardo et D18 pour l'Arduino Uno
const int AFFICHAGE = 22; // RCLK
// D23 pour l'Arduino Leonardo et D19 pour l'Arduino Uno
const int BIT = 21; // SRCLK
// Mesure du temps
unsigned long tempsEcoule = millis();
void setup() {
// Boutons
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);
pinMode(6, INPUT_PULLUP);
pinMode(7, INPUT_PULLUP);
pinMode(8, INPUT_PULLUP);
pinMode(9, INPUT_PULLUP);
pinMode(10, INPUT_PULLUP);
pinMode(11, INPUT_PULLUP);
pinMode(12, INPUT_PULLUP);
// D18 pour l'Arduino Leonardo et D14 pour l'Arduino Uno
pinMode(18, INPUT_PULLUP);
// D19 pour l'Arduino Leonardo et D15 pour l'Arduino Uno
pinMode(19, INPUT_PULLUP);
// D20 pour l'Arduino Leonardo et D16 pour l'Arduino Uno
pinMode(20, INPUT_PULLUP);
//Registre_a_decalage
pinMode(BIT, OUTPUT);
pinMode(AFFICHAGE, OUTPUT);
pinMode(MEMORISATION, OUTPUT);
// Par sécurité, si bouton Start appuyé au démarrage
// la manette n'est pas activée
if (digitalRead(6) == 0) {
// Boucle infinie
while(digitalRead(6) == 0) {
clignoteTout(400);
}
}
// Animation (qui allume toutes les LED)
animationDebut(3000);
// Initialize Joystick Library
Joystick.begin();
Joystick.setXAxisRange(-1, 1);
Joystick.setYAxisRange(-1, 1);
}
int currentButtonState;
// Last state of the buttons
int lastButtonState[14] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0};
// Leonardo
int buttonMap[14] = {2,3,4,5,6,7,8,9,10,11,12,18,19,20};
// Uno
// int buttonMap[14] = {2,3,4,5,6,7,8,9,10,11,12,14,15,16};
// ButtonMap = 0, Pin 2 = UP
// ButtonMap = 1, Pin 3 = RIGHT
// ButtonMap = 2, Pin 4 = DOWN
// ButtonMap = 3, Pin 5 = LEFT
// ButtonMap = 4, Pin 6 = Start
// ButtonMap = 5, Pin 7 = Select
// ButtonMap = 6, Pin 8 = Button 1
// ButtonMap = 7, Pin 9 = Button 2
// ButtonMap = 8, Pin 10 = Button 3
// ButtonMap = 9, Pin 11 = Button 4
// ButtonMap = 10, Pin 12 = Button 5
// ButtonMap = 11, Pin A0 (18 ou 14) = Button 6
// ButtonMap = 12, Pin A1 (19 ou 15) = Button 7
// ButtonMap = 13, Pin A2 (20 ou 16) = Button 8
void loop() {
// Read pin values
for (int index = 0; index < 14; index++) {
currentButtonState = !digitalRead(buttonMap[index]);
if (currentButtonState != lastButtonState[index]) {
switch (index) {
case 0: // UP
if (currentButtonState == 1) {
Joystick.setYAxis(-1);
} else {
Joystick.setYAxis(0);
}
break;
case 1: // RIGHT
if (currentButtonState == 1) {
Joystick.setXAxis(1);
} else {
Joystick.setXAxis(0);
}
break;
case 2: // DOWN
if (currentButtonState == 1) {
Joystick.setYAxis(1);
} else {
Joystick.setYAxis(0);
}
break;
case 3: // LEFT
if (currentButtonState == 1) {
Joystick.setXAxis(-1);
} else {
Joystick.setXAxis(0);
}
break;
case 4: // Start Button
Joystick.setButton(0, currentButtonState);
break;
case 5: // Select Button
Joystick.setButton(1, currentButtonState);
break;
case 6: // Button 1
Joystick.setButton(2, currentButtonState);
affichageLED(B01111111);
break;
case 7: // Button 2
Joystick.setButton(3, currentButtonState);
affichageLED(B10111111);
break;
case 8: // Button 3
Joystick.setButton(4, currentButtonState);
affichageLED(B11011111);
break;
case 9: // Button 4
Joystick.setButton(5, currentButtonState);
affichageLED(B11101111);
break;
case 10: // Button 5
Joystick.setButton(6, currentButtonState);
affichageLED(B11110111);
break;
case 11: // Button 6
Joystick.setButton(7, currentButtonState);
affichageLED(B11111011);
break;
case 12: // Button 7
Joystick.setButton(8, currentButtonState);
affichageLED(B11111101);
break;
case 13: // Button 8
Joystick.setButton(9, currentButtonState);
affichageLED(B11111110);
break;
}
lastButtonState[index] = currentButtonState;
}
}
if ((millis() - tempsEcoule) >= 80){
affichageLED(B11111111);
}
delay(10);
}
void animationDebut(int duree){
affichageLED(B00000000);
delay(duree/8);
affichageLED(B10000000);
delay(duree/8);
affichageLED(B10100000);
delay(duree/8);
affichageLED(B10101000);
delay(duree/8);
affichageLED(B10101010);
delay(duree/8);
affichageLED(B10101011);
delay(duree/8);
affichageLED(B10101111);
delay(duree/8);
affichageLED(B10111111);
delay(duree/8);
affichageLED(B11111111);
}
void clignoteTout(int duree){
affichageLED(B11111111);
delay(duree/2);
affichageLED(B00000000);
delay(duree/2);
}
int affichageLED(int leds){
digitalWrite(AFFICHAGE,LOW);
shiftOut(BIT, MEMORISATION, LSBFIRST, leds);
digitalWrite(AFFICHAGE,HIGH);
tempsEcoule = millis(); //Mise à jour du temps
}
Arcade Shield v2
J’ai refais le schéma en remplaçant les connecteurs 2 pins par des connecteurs 3 pins afin d’alimenter individuellement chaque LED (avec le signal envoyé par le registre à décalage).

J’ai routé toutes les pistes, le circuit passe de 2 à 4 couches.

L’aperçu 3D de la carte semble correcte…

Et j’ai envoyé le fichier Gerber à PCBWay.

Si vous n’êtes pas trop pressé, je vous conseille de choisir le mode de transport Global Standard Shipping parce qu’avec DHL ou FedEx, vous risquez d’avoir des frais de douanes + les frais administratifs du transporteur + la TVA sur les frais administratifs…

J’ai reçu les PCB un peu plus d’une semaine plus tard. J’ai soudé les connecteurs et j’ai commencé à tester la carte shield.

Et j’ai découvert un gros problème : Lorsqu’on déplace le joystick sur la droite, cela appuie sur le 3ème bouton.

Effectivement, on voit clairement dans l’éditeur de PCB que la piste du joystick (déplacement vers la droite) est en contact avec le bouton B1.

Si l’erreur était sur la couche supérieure ou inférieure, il serait certainement possible de rafistoler la carte en coupant la mauvaise piste au cutter et en ajoutant un petit fil pour rétablir la bonne connexion. Malheureusement Nous sommes sur une des 2 couches centrales…
Arcade Shield v2.1
J’ai fini la vérification de la carte shield (sans détecter d’autres problèmes) et j’ai corrigé mon erreur.

Et rebelotte, j’ai commandé le nouveau PCB sur le site de PCBWay.

Je l’ai réceptionné une dizaine de jours plus tard (avec le transporteur Global Standard Shipping) et j’ai soudé les composants.

Ensuite, j’ai installé l’Arduino dans le boitier

Et testé la manette… Ouf, ça fonctionne parfaitement !

Et maintenant les touches clignotent quand j’appuie dessus…

Conclusion
Si j’ai un conseil à vous donner c’est de ne jamais faire directement une grosse commande (plusieurs dizaines de pièces ou plus). Mieux vaut commander un petit échantillon pour tester la carte et seulement si elle est validée, lancer la grosse commande.
Si cela vous intéresse, j’ai partagé le fichier Gerber sur le site de PCBWay