Jeu labyrinthe avec touch pad : Différence entre versions

De Les Fabriques du Ponant
Aller à : navigation, rechercher
m (C++)
 
(35 révisions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
 
==Description==
 
==Description==
<div style="display:inline-block;">
+
Qui ne connaît pas le jeu du labyrinthe à bille : Un jeu qui consiste à faire sortir une bille d'un labyrinthe en utilisant deux molettes permettant d'incliner le plateau du labyrinthe vers le haut ou le bas, et vers la droite ou la gauche, le tout en évitant de faire tomber la bille dans les trous qui parsèment le chemin.
[[Fichier:Labyrinthe-retro-game.jpg|100px|thumb|right]]
 
Qui ne connaît pas le jeu du labyrinthe à bille : Un jeu qui consiste à faire sortir une bille d'un labyrinthe en utilisant deux molettes permettant de faire pencher le plateau du labyrinthe vers le haut ou le bas, et vers la droite ou la gauche, le tout en évitant de faire tomber la bille dans les trous qui parsèment le chemin.
 
  
 
Ce projet a donc eu pour objectif de pouvoir jouer au jeu du labyrinthe en commandant les inclinaisons du plateau grâce à un touch pad depuis un téléphone portable.
 
Ce projet a donc eu pour objectif de pouvoir jouer au jeu du labyrinthe en commandant les inclinaisons du plateau grâce à un touch pad depuis un téléphone portable.
 +
 +
<div class="center">
 +
Transformer la version originale [[Fichier:Labyrinthe-retro-game.jpg|400px]] en version 2.0  [[Fichier:Labyrinthe servo-moteur.jpg|400px]]
 
</div>
 
</div>
 
==Liste du matériel==
 
==Liste du matériel==
Ligne 10 : Ligne 11 :
 
* 1 x [https://www.wemos.cc/en/latest/d1/d1_mini.html Module Wifi D1 Mini Wemos]
 
* 1 x [https://www.wemos.cc/en/latest/d1/d1_mini.html Module Wifi D1 Mini Wemos]
 
* 2 x [https://opencircuit.fr/produit/towerpro-sg90-9g-micro-servo-motor-180 Servomoteurs 0-180°]
 
* 2 x [https://opencircuit.fr/produit/towerpro-sg90-9g-micro-servo-motor-180 Servomoteurs 0-180°]
* Câbles électriques, connecteurs et 1 câble USB/mini USB pour connecter le D1 mini Wemos à l'ordinateur
+
* 8 Câbles Dupont mâle/mâle, 2 connecteurs (ou 1 mini breadboard) et 1 câble USB/mini USB pour connecter le D1 mini Wemos à l'ordinateur
  
 
===Impression 3D===
 
===Impression 3D===
Ligne 21 : Ligne 22 :
 
La réalisation de ce projet est relativement simple. Elle se résume en 4 étapes :
 
La réalisation de ce projet est relativement simple. Elle se résume en 4 étapes :
 
# Initier les impressions 3D selon les modèles de fichiers listés dans la section '''[[#Impression 3D|Impression 3D]]''' ;
 
# Initier les impressions 3D selon les modèles de fichiers listés dans la section '''[[#Impression 3D|Impression 3D]]''' ;
# Réaliser le câblage du D1 mini Wemos avec les servomoteurs selon le schéma de câblage fourni ci-dessous dans la partie '''[[#Schéma|Schéma]]''' ;
+
# Réaliser le câblage du D1 mini Wemos avec les servomoteurs selon le schéma de câblage fourni dans la partie '''[[#Schéma|Schéma]]''' ;
 
# Télécharger le programme dans le D1 mini Wemos ;
 
# Télécharger le programme dans le D1 mini Wemos ;
 
# Jouer.
 
# Jouer.
 
===Schéma===
 
===Schéma===
 +
[[Fichier:Schéma circuit labyrinthe.jpg|600px|centré]]
 +
 
===Code===
 
===Code===
 
Le code nécessaire à ce projet se sépare en deux parties :
 
Le code nécessaire à ce projet se sépare en deux parties :
 
# Web (HTML/CSS/JavaScript) permettant d'afficher un touch pad sur le téléphone portable et de commander l'inclinaison du plateau du labyrinthe ;
 
# Web (HTML/CSS/JavaScript) permettant d'afficher un touch pad sur le téléphone portable et de commander l'inclinaison du plateau du labyrinthe ;
# C++ (Arduino) permettant de programmer le D1 mini Wemos pour qu'il ouvre une connexion WIFI et d'adresser les commandes reçues aux servomoteurs contrôlant l'inclinaison du plateau du labyrinthe.
+
# C++ (Arduino) permettant de programmer le D1 mini Wemos pour qu'il ouvre une connexion WIFI et adresse les commandes reçues aux servomoteurs contrôlant l'inclinaison du plateau du labyrinthe.
  
Les codes HTML, CSS et Javascript sont déjà insérés dans le code [[#C++|C++]]. Les codes HTML, CSS et JavaScript ont été ventilés ci-dessous afin d'en faciliter la lecture ainsi que celle des commentaires.
+
Les codes HTML, CSS et Javascript sont déjà insérés dans le code '''[[#C++|C++]]''' sans commentaire et sans mise en forme (C++ oblige). Cependant, les codes HTML, CSS et JavaScript ont été ventilés ci-dessous afin d'en faciliter la lecture et la compréhension ainsi que celles des commentaires.
  
Pour les codes [[#Javascript|Javascript]] et [[#C++|C++]], certaines lignes sont surlignées <span style="background:#ffffcc;">en jaune</span>. Ces lignes identifient les paramètres qui peuvent être modifiés sans risque afin de s'adapter aux spécificités et choix faits pour votre projet.
+
Pour les codes '''[[#Javascript|Javascript]]''' et '''[[#C++|C++]]''', certaines lignes sont surlignées <span style="background:#ffffcc;">en jaune</span>. Ces lignes identifient les paramètres qui peuvent être modifiés sans risque afin de s'adapter aux spécificités et choix faits pour votre projet.
 
====HTML====
 
====HTML====
 
<syntaxhighlight lang="HTML" line>
 
<syntaxhighlight lang="HTML" line>
Ligne 72 : Ligne 75 :
 
</syntaxhighlight>
 
</syntaxhighlight>
 
====JavaScript====
 
====JavaScript====
<syntaxhighlight lang="JavaScript" line highlight="8,18">
+
<syntaxhighlight lang="JavaScript" line highlight="8,10">
 
<script>
 
<script>
 
     const pad = document.getElementById('pad');
 
     const pad = document.getElementById('pad');
Ligne 81 : Ligne 84 :
 
     // Définit la vitesse max autorisée pour les deux axes - Valeur modifiable
 
     // Définit la vitesse max autorisée pour les deux axes - Valeur modifiable
 
     let vitesseMax = 4;
 
     let vitesseMax = 4;
 +
    // Définit la taille du touch pad en pixel - Valeur modifiable
 +
    let taillePad = 360;
 
     /***********************************************************
 
     /***********************************************************
 
     Détermine le nombre de vitesses qu'il y a sur chaque axe
 
     Détermine le nombre de vitesses qu'il y a sur chaque axe
Ligne 89 : Ligne 94 :
 
     ***********************************************************/
 
     ***********************************************************/
 
     let nbVitesses = vitesseMax * 2 + 1;
 
     let nbVitesses = vitesseMax * 2 + 1;
    // Définit la taille du touch pad en pixel - Valeur modifiable
 
    let taillePad = 360;
 
 
     // Détermine le ratio qu'occupe chaque vitesse sur le touch pad au niveau du diamètre
 
     // Détermine le ratio qu'occupe chaque vitesse sur le touch pad au niveau du diamètre
 
     let pallierVitesse = taillePad / nbVitesses;
 
     let pallierVitesse = taillePad / nbVitesses;
Ligne 111 : Ligne 114 :
 
     2/ Convertit les positions X et Y du doigt en vitesses demandées
 
     2/ Convertit les positions X et Y du doigt en vitesses demandées
 
     3/ Envoie les consignes de vitesses au D1 mini Wemos
 
     3/ Envoie les consignes de vitesses au D1 mini Wemos
     4/ Déclenche un timer afin d'adresser toutes les 10ms les consignes
+
     4/ Déclenche un timer afin d'adresser toutes les 30ms les consignes
     de vitesses au D1 mini Wemos (voir section explications infra)
+
     de vitesses au D1 mini Wemos (voir la section "explications")
 
     *******************************************************************/
 
     *******************************************************************/
 
     pad.addEventListener('touchstart', (position) => {
 
     pad.addEventListener('touchstart', (position) => {
Ligne 128 : Ligne 131 :
 
     *******************************************************************/
 
     *******************************************************************/
 
     pad.addEventListener('touchmove', (position) => {
 
     pad.addEventListener('touchmove', (position) => {
 +
        position.preventDefault();
 
         if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||
 
         if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||
 
             position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))
 
             position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))
Ligne 134 : Ligne 138 :
 
         vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
 
         vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
 
         vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
 
         vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
 
 
     });
 
     });
  
Ligne 152 : Ligne 155 :
 
</syntaxhighlight>
 
</syntaxhighlight>
 
====C++====
 
====C++====
<syntaxhighlight lang="C++" line highlight="8,18">
+
Les lignes 66 et 67 du code C++ surlignées <span style="background:#ffffcc;">en jaune</span> correspondent aux lignes 8 et 10 du code '''[[#JavaScript|JavaScript]]''' ci-dessus.
#include <ESP8266WiFi.h>
+
<syntaxhighlight lang="C++" line highlight="17,19,31,66,67,192,194">
#include <ESP8266WebServer.h>
 
#include <Servo.h>
 
 
/*****************************************************/
 
/*****************************************************/
 
/*** CODE PERMETTANT DE JOUER AU JEU DU LABYRINTHE ***/
 
/*** CODE PERMETTANT DE JOUER AU JEU DU LABYRINTHE ***/
 
/*** CE CODE EST PARAMETRE POUR FONCTIONNER AVEC  ***/
 
/*** CE CODE EST PARAMETRE POUR FONCTIONNER AVEC  ***/
/*** UN MODULE WIFI D1 MINI.                       ***/
+
/*** UN MODULE WIFI D1 MINI WEMOS.                ***/
/*** ELEMENTS A PERSONNALISER CI-DESSOUS          ***/
 
/*** LIGNES : 16, 18, 30, 202, 205                 ***/
 
 
/*****************************************************/
 
/*****************************************************/
 +
 +
// Ajout des bibliothèques permettant de gérer le module D1 mini Wemos et de créer un point d'accès Wifi
 +
#include <ESP8266WiFi.h>
 +
#include <ESP8266WebServer.h>
 +
// Ajout de la bibliothèque permettant de contrôler les servomoteurs
 +
#include <Servo.h>
  
 
//declaration du serveur web sur le port 80
 
//declaration du serveur web sur le port 80
Ligne 175 : Ligne 180 :
 
String pageWeb;
 
String pageWeb;
  
// nb de servo moteurs
+
// nb de servo moteurs, 1 pour les mouvements sur l'axe X et 1 pour l'axe Y
 
const byte nbServo = 2;
 
const byte nbServo = 2;
 
// stocke l'angle des servo
 
// stocke l'angle des servo
Ligne 215 : Ligne 220 :
 
</body>\
 
</body>\
 
<script>\
 
<script>\
let supportsPassive = false;\
 
try {\
 
let opts = Object.defineProperty({}, 'passive', {\
 
get: function() {\
 
supportsPassive = true;\
 
}\
 
});\
 
window.addEventListener('testPassive', null, opts);\
 
window.removeEventListener('testPassive', null, opts);\
 
} catch (e) {}\
 
 
const pad = document.getElementById('pad');\
 
const pad = document.getElementById('pad');\
 
const rect = pad.getBoundingClientRect();\
 
const rect = pad.getBoundingClientRect();\
Ligne 244 : Ligne 239 :
 
envoiMouvement();\
 
envoiMouvement();\
 
timerId = setInterval(envoiMouvement, 30);\
 
timerId = setInterval(envoiMouvement, 30);\
}, supportsPassive ? { passive: true } : false);\
+
});\
 
pad.addEventListener('touchmove', (position) => {\
 
pad.addEventListener('touchmove', (position) => {\
 +
position.preventDefault();\
 
if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||\
 
if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||\
 
position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))\
 
position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))\
Ligne 251 : Ligne 247 :
 
vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;\
 
vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;\
 
vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);\
 
vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);\
}, supportsPassive ? { passive: true } : false);\
+
});\
 
pad.addEventListener('touchend', (position) => {\
 
pad.addEventListener('touchend', (position) => {\
 
vitesse[0] = vitesse[1] = 0;\
 
vitesse[0] = vitesse[1] = 0;\
Ligne 262 : Ligne 258 :
 
}
 
}
  
// permet d'envoyer au client la page html permettant de commander les servo moteur
+
// Envoie au client la page html permettant de commander les servo moteurs
 
void handleIndex() {
 
void handleIndex() {
 
   Serial.println("index");
 
   Serial.println("index");
Ligne 273 : Ligne 269 :
 
// Applique les nouveaux angles aux servomoteurs
 
// Applique les nouveaux angles aux servomoteurs
 
void majMouvement() {
 
void majMouvement() {
   // On vérifie qu'on a des arguments qui ont été envoyés
+
   // On vérifie que des arguments ont été reçus
 
   if(!server.args()) return;
 
   if(!server.args()) return;
 
   // Le premier argument correspond au mouvement sur l'axe x.
 
   // Le premier argument correspond au mouvement sur l'axe x.
 
   // On vérifie que c'est bien le cas, et on récupère la valeur du mouvement.
 
   // On vérifie que c'est bien le cas, et on récupère la valeur du mouvement.
   if(server.argName(0) == String("x"))
+
   if(server.argName(0) == String("x")) {
  {
+
     // Conversion de la valeur reçue sous format texte en nombre entier et on l'ajoute ou soustrait à la valeur de l'angle du servoO
     // Conversion de la valeur reçue sous format texte en nombre entier et on l'ajoute à la valeur de l'angle du servoO
 
 
     angleServo[0] += server.arg(0).toInt();
 
     angleServo[0] += server.arg(0).toInt();
     // On vérifie si la valeur reçue est positive ou négative
+
     // Si le résultat de l'opération est supérieur à 180, alors angleServo0 est ramené soit à 0, soit à 180
     if(server.arg(0).toInt() > 0) {
+
     if(angleServo[0] > 180) {
       // si l'angle est supérieur à 180, on la ramène à 180
+
       // Si la valeur reçue est positive, alors on ramène angleServo0 à 180
       if(angleServo[0] > 180)  
+
       if(server.arg(0).toInt() > 0)
 
         angleServo[0] = 180;
 
         angleServo[0] = 180;
 +
      else // la valeur reçue est négative, alors on ramène angleServo0 à 0
 +
      /**************************************************************************************
 +
      La variable angleServo est déclarée avec un type "byte". Ce typage correspond
 +
      à une valeur non signée pouvant recevoir des valeurs comprises entre 0 et 255 inclus.
 +
      Cela signifie qu'elle ne peut pas être négative. Aussi, lorsqu'elle passe en-dessous
 +
      de 0 lors d'une opération mathématique, elle ne sera pas négative, mais correspondra
 +
      à la valeur max qu'elle peut stockée (255 ici) moins la valeur négative calculée.
 +
      Dans notre cas, lorsque la valeur reçue est négative (c.a.d server.arg(x) < 0) :
 +
          1. elle est soustraite à "angleServo" (voir ligne 119 ci-dessus) ;
 +
          2. si cette soustraction est sensée donnée un résultat négatif (ex: -5) ;
 +
          3. alors ce n'est pas le résultat négatif qui sera retourné, mais 250 (255 - 5)
 +
          4. dans ce cas, la valeur 0 sera assignée à "angleServo".
 +
      **************************************************************************************/
 +
        angleServo[0] = 0;
 
     }
 
     }
    else
 
      // la valeur reçue est négative
 
      // La variable angleServo est déclarée avec un type "byte". Ce typage correspond à une valeur non signée pouvant recevoir des valeurs comprises entre 0 et 255 inclus.
 
      // Cela signifie qu'elle ne peut pas être négative. Aussi, lorsqu'elle passe en-dessous de 0 lors d'une opération mathématique,
 
      // elle ne sera pas négative, mais correspondra à la valeur max qu'elle peut stockée (255 ici) moins la valeur négative calculée.
 
      // Cela est plus facile à visualiser si on utilise les nombres binaires (0-1).
 
      // Dans notre cas, lorsque la valeur reçue est négative, on la soustrait à "angleServo". Si la soustraction donne un résultat négatif,
 
      // "angleServo" vaudra 255 moins ce résultat négatif (donc supérieure à 18à). Si c'est le cas, alors on assigne la valeur 0 à "angleServo".
 
      if(angleServo[0] > 180)
 
        angleServo[0] = 0;
 
 
   }
 
   }
 
    
 
    
 
   // on refait le même travail, mais pour l'axe y cette fois-ci.
 
   // on refait le même travail, mais pour l'axe y cette fois-ci.
   if(server.argName(1) == String("y"))
+
   if(server.argName(1) == String("y")) {
  {
 
 
     angleServo[1] += server.arg(1).toInt();
 
     angleServo[1] += server.arg(1).toInt();
     if(server.arg(1).toInt() > 0) {
+
     if(angleServo[1] > 180) {
      if(angleServo[1] > 180)  
+
      if(server.arg(1).toInt() > 0)
 
         angleServo[1] = 180;
 
         angleServo[1] = 180;
 +
      else
 +
        angleServo[1] = 0;
 
     }
 
     }
    else
 
      if(angleServo[1] > 180)
 
        angleServo[1] = 0;
 
 
   }
 
   }
   // on applique les nouveaux angles aux servo moteurs.
+
   // on applique les nouvelles valeurs aux servo moteurs.
 
   monServo[0].write(angleServo[0]);
 
   monServo[0].write(angleServo[0]);
 
   monServo[1].write(angleServo[1]);
 
   monServo[1].write(angleServo[1]);
Ligne 345 : Ligne 342 :
 
   creerAccesWifi();
 
   creerAccesWifi();
 
   // On initialise les servo moteurs pour avoir les plateaux à plat
 
   // On initialise les servo moteurs pour avoir les plateaux à plat
   // Particularité avec les D1 mini, pour que le servo ait un débattement d'angle entre 0° et 180°, il faut configurer les délais d'impulsion des servos avec min à 500 et max à 2600.
+
   /**************************************************************************************
  // Cela est dû à la fréquence PWM du D1 mini qui est inconnue de la bibliothèque <Servo.h>, et qui applique du coup une configuration par défaut lorsqu'on appelle la fonction "Servo::attach"
+
  Pour le labyrinthe, il n'est pas nécessaire d'avoir une amplitude de débattement de
  // Pour le labyrinthe, il n'est pas nécessaire d'avoir toute cette amplitude de débattement (- 180°).
+
  à 180°. Donc, on va garder le nombre de pas de 0 à 180, mais sur un débattement
  // Donc, on va garder le nombre de pas de 0 à 180, mais sur un débattement d'environ 10° seulement pour chaque plateau du labyrinthe (+/- 5° par rapport aux 0° des plateaux).
+
  d'environ 10° seulement pour chaque plateau du labyrinthe soit +/- 5° par rapport aux
  // Ici, le plateau commandé par le servo0 a un 0° correspondant au 83° du servo0 (lorsque le servo0 a un débattement entre 0° et 180°), donc on va lui permettre un débattement entre 78° et 88° étalé sur 180 pas.
+
  0° des plateaux (voir la section "explications").
   // Pour le plateau commandé par le servo1, le 0° correspond au 93° du servo1, donc on va lui permettre un débattement entre 88° et 98° étalé sur 180 pas.
+
   **************************************************************************************/
  // Cela a pour conséquences :
+
   monServo[0].attach(pinServo[0], 1415, 1525);
  //      1- qu'en envoyant aux servos la commande Servo::write(90), les plateaux du labyrinthe seront sur leurs 0° ;
 
  //      2- que le débattement d'angle des servos sur les 10° qu'on leur a autorisé sera étalé sur 180 pas (avec pour le servo0 : "servo0.write(0)" = 78° d'angle, et "servo0.write(180)" = 88° d'angle)
 
   monServo[0].attach(pinServo[0], 1410, 1530);
 
 
   monServo[0].write(angleServo[0]);
 
   monServo[0].write(angleServo[0]);
   monServo[1].attach(pinServo[1], 1530, 1650);
+
   monServo[1].attach(pinServo[1], 1415, 1525);
 
   monServo[1].write(angleServo[1]);
 
   monServo[1].write(angleServo[1]);
 
}
 
}
Ligne 365 : Ligne 359 :
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 
===Explications===
 
===Explications===
==Pour aller plus loin==
+
====Chargement du programme====
 +
Voici les étapes à suivre afin de pourvoir charger le '''[[#C++|programme]]''' sur le D1 mini Wemos :
 +
* Lancez le programme ''Arduino IDE'' ;
 +
* Ajoutez une carte supplémentaire à partir de menu Fichier > Préférences ;
 +
* Dans l'onglet "Paramètres" cliquez sur le bouton situé en face de "URL de gestionnaire de cartes supplémentaires" (voir image ci-dessous) ;
 +
[[File:Config ide arduino esp8266.jpg|500px|center]]
 +
* Ajoutez le lien suivant dans la fenêtre qui s'est ouverte et validez : <nowiki>https://arduino.esp8266.com/stable/package_esp8266com_index.json</nowiki>
 +
* Ajoutez les librairies permettant de gérer les cartes à base d<nowiki>'</nowiki>''ESP8266'' (menu Outils > Carte > Gestionnaire de carte) ;
 +
* Dans l'onglet qui s'ouvre, tapez "ESP8266" dans la barre de recherche ;
 +
* Sélectionnez la carte "'''ESP8266''' par ESP8266 Community" et installez-la ;
 +
* Copiez le '''[[#C++|code C++]]''' dans l'interface ''Arduino IDE'' ;
 +
* Dans le menu "Outils > Carte", sélectionnez "esp8266 > LOLIN (WEMOS) D1 R2 & Mini", ainsi que le port série (menu Outils > Port) ;
 +
* Compilez et téléversez vers le D1 Mini Wemos.
 +
====Fonctionnement général====
 +
Lorsque le programme a été téléversé sur le D1 mini Wemos et que la diode built-in sur la broche D4 est allumée, cela signifie que le point d'accès Wifi est démarré.
 +
 
 +
Pour accéder au touch pad depuis un téléphone portable, il suffit alors de se connecter au réseau Wifi "''labyrinthe''" avec le mot de passe par défaut "''labyrinthe''". Ensuite, ouvrez un navigateur sur votre téléphone portable, et saisissez dans la barre de navigation l'url suivante : <nowiki>http://192.168.4.1</nowiki>. Le touch pad devrait alors s'afficher et permettre de contrôler l'inclinaison du labyrinthe.
 +
====Servomoteurs====
 +
Les servomoteurs ont un débattement de 180°.
 +
 
 +
Avant de fixer les servomoteurs sur les plateaux du labyrinthe, il faut s'assurer qu'ils ne soient pas en butée 0° ou 180°. Ainsi, vous pouvez utiliser le programme ci-dessous afin de placer le débattement des servomoteurs à mi-chemin de leur amplitude, soit 90° ('''Attention''' les broches D2 et D5 du programme sont à modifier si vous n'utilisez pas un D1 mini Wemos). Avec ce programme, le 0° des plateaux correspondra au 90° des servomoteurs (à quelques degrés près à cause de l'incertitude des jeux des éléments mobiles). Enfin, il n'est pas nécessaire que l'amplitude totale de 180° soit conservée pour l'inclinaison des plateaux. Aussi, une limitation de 10° de débattement (+/- 5° autour du 0° des plateaux) a été appliquée (voir '''[[#Code C++/Particularités D1 mini Wemos|Code C++/Particularités D1 mini Wemos]]''').
 +
<syntaxhighlight lang="C++" line>
 +
#include <Servo.h>
 +
void setup() {
 +
  Servo monServo;
 +
  monServo.attach(D2, 540, 2400);
 +
  monServo.write(90);
 +
  monServo.detach();
 +
  monServo.attach(D5, 540, 2400);
 +
  monServo.write(90);
 +
  monServo.detach();
 +
}
 +
void loop {
 +
  delay(100);
 +
}
 +
</syntaxhighlight>
 +
 
 +
====Code JavaScript====
 +
''Choix de la mise en place d'un timer''
 +
 
 +
La solution initialement mise en place pour contrôler les plateaux du labyrinthe n'était pas adaptée. Il s'agissait de l'envoi des consignes de vitesses dès que l'utilisateur déplaçait son doigt sur le touch pad. Le problème qui s'est présenté est que le D1 mini Wemos reçoit trop de requêtes par rapport à la vitesse d'exécution et surtout à la vitesse de déplacement des servomoteurs. Cela avait pour conséquence de mettre des requêtes en attente dans la mémoire du D1 mini Wemos et de créer au fur et à mesure une très forte latence entre le moment où le doigt était déplacé sur le touch pad et le moment où ce déplacement était effectivement réalisé par les servomoteurs.
 +
 
 +
Deux solutions étaient alors possibles :
 +
# Déclenchement d'un timer Javascript envoyant à intervalle régulier ces consignes ;
 +
# Régulation des requêtes web reçues par le D1 mini Wemos dans le code C++.
 +
 
 +
<u>Première solution : la mise en place d'un timer</u>
 +
 
 +
A intervalle régulier, le timer vient déclencher l'envoi des consignes de vitesses vers le D1 mini Wemos par rapport aux dernières informations connues quant à la position du doigt sur le touch pad. Après plusieurs essais, un intervalle de 30 millisecondes apparaît être un choix acceptable entre le taux de rafraîchissement de l'envoi des consignes, le temps de traitement de ces consignes par le D1 mini Wemos et surtout le temps de déplacement requis par les servomoteurs.
 +
 
 +
Quant à la seconde solution, puisque la première solution était satisfaisante, la régulation des requêtes web au niveau du D1 mini Wemos n'a pas été testée.
 +
 
 +
Voici ce que cela donne (extrait du code '''[[#JavaScript|JavaScript]]''') :
 +
<syntaxhighlight lang="JavaScript" line highlight="1,5,8">
 +
pad.addEventListener('touchstart', (position) => {
 +
    vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
 +
    vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
 +
    envoiMouvement();
 +
    timerId = setInterval(envoiMouvement, 30);
 +
});
 +
 
 +
pad.addEventListener('touchmove', (position) => {
 +
    position.preventDefault();
 +
    if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||
 +
        position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))
 +
        return;
 +
    vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
 +
    vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
 +
});
 +
</syntaxhighlight>
 +
*Lignes 1 & 5 : à partir du moment où l'utilisateur touche la zone correspondant au touch pad, cela déclenche un évènement "touchstart" qui va, entre autres, mettre en place un timer qui exécutera toutes les 30 millisecondes l'envoi des consignes de vitesses vers le D1 mini Wemos.
 +
*Ligne 8 : dès que l'utilisateur bouge le doigt, cela déclenche un évènement "touchmove". Cet évènement va uniquement réaliser une mise à jour des consignes de vitesses. Aucun envoi de ces nouvelles consignes vers le D1 mini Wemos n'est réalisé par cet évènement. Cet envoi est effectué par le déclenchement du timer toutes les 30 millisecondes.
 +
 
 +
====Code C++/Particularités D1 mini Wemos====
 +
Le D1 mini Wemos est un module qui prend en charge le Wifi. Pour ce faire, il s'appuie sur un microcontrôleur ESP8266. Ainsi, et afin de pouvoir utiliser ce module dans ce projet (valable également pour tout autre projet utilisant un module ESP8266), il est nécessaire d'ajouter les librairies permettant de gérer ce module au logiciel ''Arduino IDE'' (voir '''[[#Chargement du programme|Chargement du programme]]'''). Bien que les librairies liées à ce module ont été testées et soient compatibles avec le logiciel ''Arduino IDE'', il y a pour certaines d'entre-elles quelques différences. Ce qui est le cas pour la librairie '''<Servo.h>''' qui permet de mettre en œuvre et contrôler les servomoteurs.
 +
 
 +
Dans la libraire "standard" '''servo''' liée aux cartes ''Arduino'', le paramétrage des valeurs ''minimal pulse width'' et ''maximal pulse width'' est différent de celui de la librairie '''servo''' liée aux modules basés sur de l'ESP8266. Dans la librairie '''servo ''' ''ESP8266'' :
 +
* ''minimal pulse width'' est paramétrée par défaut avec la valeur 1000 microsecondes, alors que pour les cartes ''Arduino'' ce paramètre reçoit la valeur 540 microsecondes ;
 +
* ''maximal pulse width'' est paramétrée par défaut avec la valeur 2000 microsecondes, alors que pour les cartes ''Arduino'' ce paramètre reçoit la valeur 2400 microsecondes.
 +
 
 +
L'explication donnée dans la librairie '''servo''' ''ESP8266'' est justifiée par le fait de vouloir protéger les servomoteurs en faisant en sorte que le débattement des servomoteurs soit limité à un <u>intervalle sûr</u>. Et que cela est rendu possible car cet intervalle peut être modifié grâce à la fonction '''attach()''' de la librairie '''servo'''.
 +
 
 +
Ainsi, si dans un programme dédié à un module Wifi basé sur de l<nowiki>'</nowiki>''ESP8266'' les servomoteurs sont initialisés de la même manière que pour un programme dédié à une carte ''Arduino'', c'est à dire sans spécifier les durées maximale et minimale des impulsions transmettant les ordres de mouvements aux servomoteurs, alors le débattement effectif de ces mêmes servomoteurs seront réduits à un intervalle compris entre 45° et 141°, soit 86° de débattement total.
 +
 
 +
Aussi, si le débattement maximal (0° ; 180°) est recherché pour un servomoteur contrôlé par un module basé sur de l<nowiki>'</nowiki>''ESP8266'' (type D1 mini Wemos), alors ce servomoteur devra être initialisé avec ''minimal impulsion width'' égale à 540 et ''maximal impulsion width'' égale à 2400 tel que :
 +
<syntaxhighlight lang="C++" line highlight="8">
 +
#include <Servo.h>
 +
 
 +
Servo monServo;
 +
byte servoPin = D2;
 +
 
 +
void setup {
 +
  ...
 +
  monServo.attach(servoPin, 540, 2400);
 +
  ...
 +
}
 +
 
 +
...
 +
</syntaxhighlight>
 +
 
 +
Dans le cadre de ce projet, les servomoteurs vont être initialisés de la même manière à la différence près que l'on va volontairement paramétrer les durées d'impulsion maximale et minimale avec des valeurs adaptées à notre besoin. Comme dit dans la section '''[[#Servomoteurs|Servomoteurs]]''', les plateaux du labyrinthe seront limités à un angle de débattement total de +/- 5° autour de leur point 0° respectif. En partant du principe que, par défaut, l'angle de 90° de chaque servomoteur correspond au 0° de chaque plateau du labyrinthe, on obtient le paramétrage suivant des servomoteurs pour ce projet :
 +
 
 +
<u>Extrait du '''[[#C++|Code C++]]''' :</u>
 +
<syntaxhighlight lang="C++" line start="191" highlight="2,4">
 +
  ...
 +
  monServo[0].attach(pinServo[0], 1415, 1525);
 +
  monServo[0].write(angleServo[0]);
 +
  monServo[1].attach(pinServo[1], 1415, 1525);
 +
  monServo[1].write(angleServo[1]);
 +
  ...
 +
</syntaxhighlight>
 +
Ces valeurs sont obtenues par un simple produit en croix, sachant que :
 +
* la valeur 540 correspond au 0° du servomoteur ;
 +
* la valeur 2400 correspond au 180° du servomoteur ;
 +
* l'angle minimal de débattement souhaité est 85° ;
 +
* l'angle maximal de débattement souhaité est 95°.
 +
 
 +
Cela donne pour le produit en croix :
 +
* pour 85° -> 85 x (2400-540)/180 = 1415 ;
 +
* pour 95° -> 95 x (2400-540)/180 = 1525.
 +
 
 +
A noter que les angles 85° et 95° seront à adapter selon les jeux des éléments mobiles de votre projet.
 +
 
 +
==Evolution à étudier==
 +
Afin d'aller plus loin et d'améliorer ce projet, il serait intéressant d'utiliser une connexion Bluetooth plutôt qu'une connexion Wifi pour contrôler les servomoteurs.
 +
 
 +
Cela permettrait tout particulièrement :
 +
* d'éviter à devoir déconnecter le téléphone mobile de son réseau Wifi s'il était connecté ;
 +
* de ne pas à avoir à saisir le nom du réseau Wifi et le mot de passe associé ;
 +
* de diminuer la consommation électrique et le rayonnement électromagnétique.
 +
 
 +
De plus, il n'y a pas de besoin particulier quant à avoir une connexion Wifi pour ce type de projet. Que cela soit aussi bien pour le débit, l'accès à Internet que la portée ou bien encore la sécurité. Domaines dans lesquels où la technologie Wifi est à largement privilégier par rapport au Bluetooth.
 +
 
 +
Aussi, dans cet objectif, les pistes à étudier pour adapter ce projet avec un support Bluetooth sont :
 +
* pour la partie matérielle, remplacer le D1 mini Wemos par un [https://store.arduino.cc/products/arduino-nano-33-ble Arduino nano BLE] ;
 +
* pour la partie logicielle, créer une application mobile à l'aide des sites [https://thunkable.com/ Thunkable] ou [https://ai2.appinventor.mit.edu/ AppInventor].
 +
 
 +
A vous de jouer ...
 
[[Catégorie:Arduino]]
 
[[Catégorie:Arduino]]
 
[[Catégorie:Jeu]]
 
[[Catégorie:Jeu]]
 
[[Catégorie:Fiche à Valider]]
 
[[Catégorie:Fiche à Valider]]

Version actuelle datée du 5 avril 2024 à 09:52

Description

Qui ne connaît pas le jeu du labyrinthe à bille : Un jeu qui consiste à faire sortir une bille d'un labyrinthe en utilisant deux molettes permettant d'incliner le plateau du labyrinthe vers le haut ou le bas, et vers la droite ou la gauche, le tout en évitant de faire tomber la bille dans les trous qui parsèment le chemin.

Ce projet a donc eu pour objectif de pouvoir jouer au jeu du labyrinthe en commandant les inclinaisons du plateau grâce à un touch pad depuis un téléphone portable.

Transformer la version originale Labyrinthe-retro-game.jpg en version 2.0 Labyrinthe servo-moteur.jpg

Liste du matériel

Électronique

Impression 3D

Les fichiers nécessaires à l'impression 3D du modèle utilisé dans ce tutoriel sont accessibles A COMPLETER

Coût

Pour l'ensemble du projet, il faut compter un coût global électronique + impression 3D de 15 euros environ.

Réalisation du projet

La réalisation de ce projet est relativement simple. Elle se résume en 4 étapes :

  1. Initier les impressions 3D selon les modèles de fichiers listés dans la section Impression 3D ;
  2. Réaliser le câblage du D1 mini Wemos avec les servomoteurs selon le schéma de câblage fourni dans la partie Schéma ;
  3. Télécharger le programme dans le D1 mini Wemos ;
  4. Jouer.

Schéma

Schéma circuit labyrinthe.jpg

Code

Le code nécessaire à ce projet se sépare en deux parties :

  1. Web (HTML/CSS/JavaScript) permettant d'afficher un touch pad sur le téléphone portable et de commander l'inclinaison du plateau du labyrinthe ;
  2. C++ (Arduino) permettant de programmer le D1 mini Wemos pour qu'il ouvre une connexion WIFI et adresse les commandes reçues aux servomoteurs contrôlant l'inclinaison du plateau du labyrinthe.

Les codes HTML, CSS et Javascript sont déjà insérés dans le code C++ sans commentaire et sans mise en forme (C++ oblige). Cependant, les codes HTML, CSS et JavaScript ont été ventilés ci-dessous afin d'en faciliter la lecture et la compréhension ainsi que celles des commentaires.

Pour les codes Javascript et C++, certaines lignes sont surlignées en jaune. Ces lignes identifient les paramètres qui peuvent être modifiés sans risque afin de s'adapter aux spécificités et choix faits pour votre projet.

HTML

 1 <!DOCTYPE html>
 2 <html lang='fr'>
 3 <head>
 4     <meta charset='UTF-8'>
 5     <meta name='viewport' content='width=device-width, initial-scale=1.0'>
 6     <title>Jeu du labyrinthe</title>
 7 </head>
 8 <body>
 9     <!-- Affiche le touch pad -->
10     <div id='pad'>
11         <!-- Affiche une croix au centre du touch pad -->
12         <div id='centrePad'>+</div>
13     </div>
14 </body>
15 </html>

CSS

 1 <style>
 2     /* Dessine la forme/couleur du touch pad */
 3     #pad {
 4         background-color: #f0f0f0;
 5         border-radius: 50%;
 6         position: relative;
 7         user-select: none;
 8     }
 9     /* Positionne la croix au milieu du touch pad et définit la taille de la croix */
10     #centrePad {
11         position: absolute;
12         top: 50%;
13         left: 50%;
14         transform: translate(-50%, -50%);
15         font-size: 24px;
16     }
17 </style>

JavaScript

 1 <script>
 2     const pad = document.getElementById('pad');
 3     // Récupère le rectangle dans lequel s'inscrit le touch pad par rapport au 0,0 (angle supérieur gauche) de la page web
 4     const rect = pad.getBoundingClientRect();
 5     // Permet de stocker la vitesse sur les axes x et y par rapport à la position du doigt sur le touch pad
 6     let vitesse = [0, 0];
 7     // Définit la vitesse max autorisée pour les deux axes - Valeur modifiable
 8     let vitesseMax = 4;
 9     // Définit la taille du touch pad en pixel - Valeur modifiable
10     let taillePad = 360;
11     /***********************************************************
12      Détermine le nombre de vitesses qu'il y a sur chaque axe
13      Il faut prévoir aussi bien le mouvement en positif qu'en
14      négatif, et ne pas oublier 0 (pas de mouvement).
15      Avec vitesseMax = 4, on obtient 9 vitesses possibles :
16      [-4 ; -3 ; -2 ; -1 ; 0 ; 1 ; 2 ; 3 ; 4]
17     ***********************************************************/
18     let nbVitesses = vitesseMax * 2 + 1;
19     // Détermine le ratio qu'occupe chaque vitesse sur le touch pad au niveau du diamètre
20     let pallierVitesse = taillePad / nbVitesses;
21     let timerId = null;
22 
23     // Applique la taille au touch pad
24     pad.style.width = pad.style.height = taillePad + 'px';
25 
26     // Fonction envoyant la consigne de vitesse sur les axes X et Y au D1 mini Wemos
27     function envoiMouvement() {
28         const xhr = new XMLHttpRequest();
29         xhr.open('Mouvement', 'mouvement?x=' + vitesse[0] + '&y=' + vitesse[1], true);
30         xhr.send();
31     }
32 
33     /*******************************************************************
34      Evènement qui se déclenche lorsque le doigt touche le touch pad
35      1/ Récupère la position relative du doigt par rapport au
36      touch pad et ne garde que la partie entière
37      2/ Convertit les positions X et Y du doigt en vitesses demandées
38      3/ Envoie les consignes de vitesses au D1 mini Wemos
39      4/ Déclenche un timer afin d'adresser toutes les 30ms les consignes
40      de vitesses au D1 mini Wemos (voir la section "explications")
41     *******************************************************************/
42     pad.addEventListener('touchstart', (position) => {
43         vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
44         vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
45         envoiMouvement();
46         timerId = setInterval(envoiMouvement, 30);
47     });
48 
49     /*******************************************************************
50      Evènement qui se déclenche lorsque le doigt bouge sur l'écran
51      1/ Vérifie si le doigt est toujours sur le touch pad
52      1.1/ Si non, ne fait rien
53      1.2/ Si oui, actualise les consignes de vitesse X et Y
54     *******************************************************************/
55     pad.addEventListener('touchmove', (position) => {
56         position.preventDefault();
57         if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||
58             position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))
59                 return;
60 
61         vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
62         vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
63     });
64 
65     /*******************************************************************
66      Evènement qui se déclenche lorsque le doigt ne touche plus l'écran
67      1/ Met les consignes de vitesses X et Y à 0
68      2/ Stoppe le timer qui envoie les consignes de vitesses au D1 mini
69      3/ Envoie les consignes de vitesses à 0 au D1 mini
70     *******************************************************************/
71     pad.addEventListener('touchend', (position) => {
72         vitesse[0] = vitesse[1] = 0;
73         clearInterval(timerId);
74         envoiMouvement();
75         timerId = null;
76     });
77 </script>

C++

Les lignes 66 et 67 du code C++ surlignées en jaune correspondent aux lignes 8 et 10 du code JavaScript ci-dessus.

  1 /*****************************************************/
  2 /*** CODE PERMETTANT DE JOUER AU JEU DU LABYRINTHE ***/
  3 /*** CE CODE EST PARAMETRE POUR FONCTIONNER AVEC   ***/
  4 /*** UN MODULE WIFI D1 MINI WEMOS.                 ***/
  5 /*****************************************************/
  6 
  7 // Ajout des bibliothèques permettant de gérer le module D1 mini Wemos et de créer un point d'accès Wifi
  8 #include <ESP8266WiFi.h>
  9 #include <ESP8266WebServer.h>
 10 // Ajout de la bibliothèque permettant de contrôler les servomoteurs
 11 #include <Servo.h>
 12 
 13 //declaration du serveur web sur le port 80
 14 ESP8266WebServer server(80);
 15 
 16 //Nom du réseau wifi local (Ce qu'on appelle le SSID).
 17 const char* nomDuReseau = "labyrinthe";
 18 //Mot de passe du réseau wifi local
 19 const char* motDePasse = "labyrinthe";
 20 
 21 // Contiendra la page web que l'on renverra au client
 22 String pageWeb;
 23 
 24 // nb de servo moteurs, 1 pour les mouvements sur l'axe X et 1 pour l'axe Y
 25 const byte nbServo = 2;
 26 // stocke l'angle des servo
 27 byte angleServo[nbServo] = {90, 90};
 28 // créer nbServo objets "monServo" pour les contrôler
 29 Servo monServo[nbServo];
 30 // Correspond aux pins des servo
 31 byte pinServo[nbServo] = {D2, D5}; // mettre le numéro des broches sur lesquelles sont connectées les servo moteurs
 32 
 33 // On forge la page html qui va permettre de commander les servo moteurs à distance via le wifi
 34 void pageIndex() {
 35   pageWeb = "<!DOCTYPE html>\
 36 <html lang='fr'>\
 37 <head>\
 38 <meta charset='UTF-8'>\
 39 <meta name='viewport' content='width=device-width, initial-scale=1.0'>\
 40 <title>Jeu du labyrinthe</title>\
 41 <style>\
 42 /* Styles pour le pad de direction */\
 43 #pad {\
 44 background-color: #f0f0f0;\
 45 border-radius: 50%;\
 46 position: relative;\
 47 user-select: none;\
 48 }\
 49 #centrePad {\
 50 position: absolute;\
 51 top: 50%;\
 52 left: 50%;\
 53 transform: translate(-50%, -50%);\
 54 font-size: 24px;\
 55 }\
 56 </style>\
 57 </head>\
 58 <body>\
 59 <div id='pad'>\
 60 <div id='centrePad'>+</div>\
 61 </div>\
 62 </body>\
 63 <script>\
 64 const pad = document.getElementById('pad');\
 65 const rect = pad.getBoundingClientRect();\
 66 let vitesseMax = 4;\
 67 let taillePad = 360;\
 68 let vitesse = [0, 0];\
 69 let nbVitesses = vitesseMax * 2 + 1;\
 70 let pallierVitesse = taillePad / nbVitesses;\
 71 let timerId = null;\
 72 pad.style.width = pad.style.height = taillePad + 'px';\
 73 function envoiMouvement() {\
 74 const xhr = new XMLHttpRequest();\
 75 xhr.open('Mouvement', 'mouvement?x=' + vitesse[0] + '&y=' + vitesse[1], true);\
 76 xhr.send();\
 77 }\
 78 pad.addEventListener('touchstart', (position) => {\
 79 vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;\
 80 vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);\
 81 envoiMouvement();\
 82 timerId = setInterval(envoiMouvement, 30);\
 83 });\
 84 pad.addEventListener('touchmove', (position) => {\
 85 position.preventDefault();\
 86 if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||\
 87 position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))\
 88 return;\
 89 vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;\
 90 vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);\
 91 });\
 92 pad.addEventListener('touchend', (position) => {\
 93 vitesse[0] = vitesse[1] = 0;\
 94 clearInterval(timerId);\
 95 envoiMouvement();\
 96 timerId = null;\
 97 });\
 98 </script>\
 99 </html>";
100 }
101 
102 // Envoie au client la page html permettant de commander les servo moteurs
103 void handleIndex() {
104   Serial.println("index");
105   // prépare le html qui sera envoyé au client
106   pageIndex();     
107   // envoie la page HTML au client
108   server.send(200,"text/html", pageWeb);
109 }
110 
111 // Applique les nouveaux angles aux servomoteurs
112 void majMouvement() {
113   // On vérifie que des arguments ont été reçus
114   if(!server.args()) return;
115   // Le premier argument correspond au mouvement sur l'axe x.
116   // On vérifie que c'est bien le cas, et on récupère la valeur du mouvement.
117   if(server.argName(0) == String("x")) {
118     // Conversion de la valeur reçue sous format texte en nombre entier et on l'ajoute ou soustrait à la valeur de l'angle du servoO
119     angleServo[0] += server.arg(0).toInt();
120     // Si le résultat de l'opération est supérieur à 180, alors angleServo0 est ramené soit à 0, soit à 180
121     if(angleServo[0] > 180) {
122       // Si la valeur reçue est positive, alors on ramène angleServo0 à 180
123       if(server.arg(0).toInt() > 0)
124         angleServo[0] = 180;
125       else // la valeur reçue est négative, alors on ramène angleServo0 à 0
126       /**************************************************************************************
127        La variable angleServo est déclarée avec un type "byte". Ce typage correspond
128        à une valeur non signée pouvant recevoir des valeurs comprises entre 0 et 255 inclus.
129        Cela signifie qu'elle ne peut pas être négative. Aussi, lorsqu'elle passe en-dessous
130        de 0 lors d'une opération mathématique, elle ne sera pas négative, mais correspondra
131        à la valeur max qu'elle peut stockée (255 ici) moins la valeur négative calculée.
132        Dans notre cas, lorsque la valeur reçue est négative (c.a.d server.arg(x) < 0) :
133            1. elle est soustraite à "angleServo" (voir ligne 119 ci-dessus) ;
134            2. si cette soustraction est sensée donnée un résultat négatif (ex: -5) ;
135            3. alors ce n'est pas le résultat négatif qui sera retourné, mais 250 (255 - 5)
136            4. dans ce cas, la valeur 0 sera assignée à "angleServo".
137       **************************************************************************************/
138         angleServo[0] = 0;
139     }
140   }
141   
142   // on refait le même travail, mais pour l'axe y cette fois-ci.
143   if(server.argName(1) == String("y")) {
144     angleServo[1] += server.arg(1).toInt();
145     if(angleServo[1] > 180) {
146       if(server.arg(1).toInt() > 0)
147         angleServo[1] = 180;
148       else
149         angleServo[1] = 0;
150     }
151   }
152   // on applique les nouvelles valeurs aux servo moteurs.
153   monServo[0].write(angleServo[0]);
154   monServo[1].write(angleServo[1]);
155 }
156 
157 // pour le debug
158 void handleInconnu() {
159 }
160 
161 // crée le point d'accès wifi
162 void creerAccesWifi() {
163   WiFi.mode(WIFI_AP);
164   WiFi.softAP(nomDuReseau, motDePasse);
165   Serial.print("@IP du module wifi : ");
166   // permet d'afficher l'@IP du point d'accès sur le port série
167   Serial.println(WiFi.softAPIP());
168   // permet de traiter les requêtes HTML reçues
169   server.on("/", handleIndex);
170   server.on("/mouvement", majMouvement);
171   server.onNotFound(handleInconnu);
172   // on démarre le point d'accès
173   server.begin();
174   // Configure la broche D4 (GPIO 2) sur la quelle est branchée la LED_BUILTIN en sortie
175   pinMode(LED_BUILTIN, OUTPUT);
176   // Allume la LED_BUILTIN, signifiant que le wifi est activé
177   digitalWrite(LED_BUILTIN, LOW);  
178 }
179 
180 void setup() {
181   // on utilise le port série pour afficher l'adresse ip du point d'accès wifi (normalement : 192.168.4.1)
182   Serial.begin(9600);
183   // on crée le point d'accès wifi
184   creerAccesWifi();
185   // On initialise les servo moteurs pour avoir les plateaux à plat
186   /**************************************************************************************
187    Pour le labyrinthe, il n'est pas nécessaire d'avoir une amplitude de débattement de 
188    0° à 180°. Donc, on va garder le nombre de pas de 0 à 180, mais sur un débattement
189    d'environ 10° seulement pour chaque plateau du labyrinthe soit +/- 5° par rapport aux
190    0° des plateaux (voir la section "explications").
191   **************************************************************************************/
192   monServo[0].attach(pinServo[0], 1415, 1525);
193   monServo[0].write(angleServo[0]);
194   monServo[1].attach(pinServo[1], 1415, 1525);
195   monServo[1].write(angleServo[1]);
196 }
197 
198 void loop() {
199   // permet de gérer les connexions des clients
200   server.handleClient();
201 }

Explications

Chargement du programme

Voici les étapes à suivre afin de pourvoir charger le programme sur le D1 mini Wemos :

  • Lancez le programme Arduino IDE ;
  • Ajoutez une carte supplémentaire à partir de menu Fichier > Préférences ;
  • Dans l'onglet "Paramètres" cliquez sur le bouton situé en face de "URL de gestionnaire de cartes supplémentaires" (voir image ci-dessous) ;
Config ide arduino esp8266.jpg
  • Ajoutez le lien suivant dans la fenêtre qui s'est ouverte et validez : https://arduino.esp8266.com/stable/package_esp8266com_index.json
  • Ajoutez les librairies permettant de gérer les cartes à base d'ESP8266 (menu Outils > Carte > Gestionnaire de carte) ;
  • Dans l'onglet qui s'ouvre, tapez "ESP8266" dans la barre de recherche ;
  • Sélectionnez la carte "ESP8266 par ESP8266 Community" et installez-la ;
  • Copiez le code C++ dans l'interface Arduino IDE ;
  • Dans le menu "Outils > Carte", sélectionnez "esp8266 > LOLIN (WEMOS) D1 R2 & Mini", ainsi que le port série (menu Outils > Port) ;
  • Compilez et téléversez vers le D1 Mini Wemos.

Fonctionnement général

Lorsque le programme a été téléversé sur le D1 mini Wemos et que la diode built-in sur la broche D4 est allumée, cela signifie que le point d'accès Wifi est démarré.

Pour accéder au touch pad depuis un téléphone portable, il suffit alors de se connecter au réseau Wifi "labyrinthe" avec le mot de passe par défaut "labyrinthe". Ensuite, ouvrez un navigateur sur votre téléphone portable, et saisissez dans la barre de navigation l'url suivante : http://192.168.4.1. Le touch pad devrait alors s'afficher et permettre de contrôler l'inclinaison du labyrinthe.

Servomoteurs

Les servomoteurs ont un débattement de 180°.

Avant de fixer les servomoteurs sur les plateaux du labyrinthe, il faut s'assurer qu'ils ne soient pas en butée 0° ou 180°. Ainsi, vous pouvez utiliser le programme ci-dessous afin de placer le débattement des servomoteurs à mi-chemin de leur amplitude, soit 90° (Attention les broches D2 et D5 du programme sont à modifier si vous n'utilisez pas un D1 mini Wemos). Avec ce programme, le 0° des plateaux correspondra au 90° des servomoteurs (à quelques degrés près à cause de l'incertitude des jeux des éléments mobiles). Enfin, il n'est pas nécessaire que l'amplitude totale de 180° soit conservée pour l'inclinaison des plateaux. Aussi, une limitation de 10° de débattement (+/- 5° autour du 0° des plateaux) a été appliquée (voir Code C++/Particularités D1 mini Wemos).

 1 #include <Servo.h>
 2 void setup() {
 3   Servo monServo;
 4   monServo.attach(D2, 540, 2400);
 5   monServo.write(90);
 6   monServo.detach();
 7   monServo.attach(D5, 540, 2400);
 8   monServo.write(90);
 9   monServo.detach();
10 }
11 void loop {
12   delay(100);
13 }

Code JavaScript

Choix de la mise en place d'un timer

La solution initialement mise en place pour contrôler les plateaux du labyrinthe n'était pas adaptée. Il s'agissait de l'envoi des consignes de vitesses dès que l'utilisateur déplaçait son doigt sur le touch pad. Le problème qui s'est présenté est que le D1 mini Wemos reçoit trop de requêtes par rapport à la vitesse d'exécution et surtout à la vitesse de déplacement des servomoteurs. Cela avait pour conséquence de mettre des requêtes en attente dans la mémoire du D1 mini Wemos et de créer au fur et à mesure une très forte latence entre le moment où le doigt était déplacé sur le touch pad et le moment où ce déplacement était effectivement réalisé par les servomoteurs.

Deux solutions étaient alors possibles :

  1. Déclenchement d'un timer Javascript envoyant à intervalle régulier ces consignes ;
  2. Régulation des requêtes web reçues par le D1 mini Wemos dans le code C++.

Première solution : la mise en place d'un timer

A intervalle régulier, le timer vient déclencher l'envoi des consignes de vitesses vers le D1 mini Wemos par rapport aux dernières informations connues quant à la position du doigt sur le touch pad. Après plusieurs essais, un intervalle de 30 millisecondes apparaît être un choix acceptable entre le taux de rafraîchissement de l'envoi des consignes, le temps de traitement de ces consignes par le D1 mini Wemos et surtout le temps de déplacement requis par les servomoteurs.

Quant à la seconde solution, puisque la première solution était satisfaisante, la régulation des requêtes web au niveau du D1 mini Wemos n'a pas été testée.

Voici ce que cela donne (extrait du code JavaScript) :

 1 pad.addEventListener('touchstart', (position) => {
 2     vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
 3     vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
 4     envoiMouvement();
 5     timerId = setInterval(envoiMouvement, 30);
 6 });
 7 
 8 pad.addEventListener('touchmove', (position) => {
 9     position.preventDefault();
10     if(position.touches[0].clientX < rect.left || position.touches[0].clientX > (taillePad + rect.left) ||
11         position.touches[0].clientY < rect.top || position.touches[0].clientY > (taillePad + rect.top))
12         return;
13     vitesse[0] = Math.trunc((position.touches[0].clientX - rect.left)/pallierVitesse) - vitesseMax;
14     vitesse[1] = vitesseMax - Math.trunc((position.touches[0].clientY - rect.top)/pallierVitesse);
15 });
  • Lignes 1 & 5 : à partir du moment où l'utilisateur touche la zone correspondant au touch pad, cela déclenche un évènement "touchstart" qui va, entre autres, mettre en place un timer qui exécutera toutes les 30 millisecondes l'envoi des consignes de vitesses vers le D1 mini Wemos.
  • Ligne 8 : dès que l'utilisateur bouge le doigt, cela déclenche un évènement "touchmove". Cet évènement va uniquement réaliser une mise à jour des consignes de vitesses. Aucun envoi de ces nouvelles consignes vers le D1 mini Wemos n'est réalisé par cet évènement. Cet envoi est effectué par le déclenchement du timer toutes les 30 millisecondes.

Code C++/Particularités D1 mini Wemos

Le D1 mini Wemos est un module qui prend en charge le Wifi. Pour ce faire, il s'appuie sur un microcontrôleur ESP8266. Ainsi, et afin de pouvoir utiliser ce module dans ce projet (valable également pour tout autre projet utilisant un module ESP8266), il est nécessaire d'ajouter les librairies permettant de gérer ce module au logiciel Arduino IDE (voir Chargement du programme). Bien que les librairies liées à ce module ont été testées et soient compatibles avec le logiciel Arduino IDE, il y a pour certaines d'entre-elles quelques différences. Ce qui est le cas pour la librairie <Servo.h> qui permet de mettre en œuvre et contrôler les servomoteurs.

Dans la libraire "standard" servo liée aux cartes Arduino, le paramétrage des valeurs minimal pulse width et maximal pulse width est différent de celui de la librairie servo liée aux modules basés sur de l'ESP8266. Dans la librairie servo ESP8266 :

  • minimal pulse width est paramétrée par défaut avec la valeur 1000 microsecondes, alors que pour les cartes Arduino ce paramètre reçoit la valeur 540 microsecondes ;
  • maximal pulse width est paramétrée par défaut avec la valeur 2000 microsecondes, alors que pour les cartes Arduino ce paramètre reçoit la valeur 2400 microsecondes.

L'explication donnée dans la librairie servo ESP8266 est justifiée par le fait de vouloir protéger les servomoteurs en faisant en sorte que le débattement des servomoteurs soit limité à un intervalle sûr. Et que cela est rendu possible car cet intervalle peut être modifié grâce à la fonction attach() de la librairie servo.

Ainsi, si dans un programme dédié à un module Wifi basé sur de l'ESP8266 les servomoteurs sont initialisés de la même manière que pour un programme dédié à une carte Arduino, c'est à dire sans spécifier les durées maximale et minimale des impulsions transmettant les ordres de mouvements aux servomoteurs, alors le débattement effectif de ces mêmes servomoteurs seront réduits à un intervalle compris entre 45° et 141°, soit 86° de débattement total.

Aussi, si le débattement maximal (0° ; 180°) est recherché pour un servomoteur contrôlé par un module basé sur de l'ESP8266 (type D1 mini Wemos), alors ce servomoteur devra être initialisé avec minimal impulsion width égale à 540 et maximal impulsion width égale à 2400 tel que :

 1 #include <Servo.h>
 2 
 3 Servo monServo;
 4 byte servoPin = D2;
 5 
 6 void setup {
 7   ...
 8   monServo.attach(servoPin, 540, 2400);
 9   ...
10 }
11 
12 ...

Dans le cadre de ce projet, les servomoteurs vont être initialisés de la même manière à la différence près que l'on va volontairement paramétrer les durées d'impulsion maximale et minimale avec des valeurs adaptées à notre besoin. Comme dit dans la section Servomoteurs, les plateaux du labyrinthe seront limités à un angle de débattement total de +/- 5° autour de leur point 0° respectif. En partant du principe que, par défaut, l'angle de 90° de chaque servomoteur correspond au 0° de chaque plateau du labyrinthe, on obtient le paramétrage suivant des servomoteurs pour ce projet :

Extrait du Code C++ :

191   ...
192   monServo[0].attach(pinServo[0], 1415, 1525);
193   monServo[0].write(angleServo[0]);
194   monServo[1].attach(pinServo[1], 1415, 1525);
195   monServo[1].write(angleServo[1]);
196   ...

Ces valeurs sont obtenues par un simple produit en croix, sachant que :

  • la valeur 540 correspond au 0° du servomoteur ;
  • la valeur 2400 correspond au 180° du servomoteur ;
  • l'angle minimal de débattement souhaité est 85° ;
  • l'angle maximal de débattement souhaité est 95°.

Cela donne pour le produit en croix :

  • pour 85° -> 85 x (2400-540)/180 = 1415 ;
  • pour 95° -> 95 x (2400-540)/180 = 1525.

A noter que les angles 85° et 95° seront à adapter selon les jeux des éléments mobiles de votre projet.

Evolution à étudier

Afin d'aller plus loin et d'améliorer ce projet, il serait intéressant d'utiliser une connexion Bluetooth plutôt qu'une connexion Wifi pour contrôler les servomoteurs.

Cela permettrait tout particulièrement :

  • d'éviter à devoir déconnecter le téléphone mobile de son réseau Wifi s'il était connecté ;
  • de ne pas à avoir à saisir le nom du réseau Wifi et le mot de passe associé ;
  • de diminuer la consommation électrique et le rayonnement électromagnétique.

De plus, il n'y a pas de besoin particulier quant à avoir une connexion Wifi pour ce type de projet. Que cela soit aussi bien pour le débit, l'accès à Internet que la portée ou bien encore la sécurité. Domaines dans lesquels où la technologie Wifi est à largement privilégier par rapport au Bluetooth.

Aussi, dans cet objectif, les pistes à étudier pour adapter ce projet avec un support Bluetooth sont :

  • pour la partie matérielle, remplacer le D1 mini Wemos par un Arduino nano BLE ;
  • pour la partie logicielle, créer une application mobile à l'aide des sites Thunkable ou AppInventor.

A vous de jouer ...