
Contenus de la page
1- Introduction
1.1- Présentation du sujet
Ce projet a pour objectif de concevoir un jeu de bille interactif reposant sur un plateau incliné. Le principe est le suivant.
- Une bille évolue librement sur un plateau comportant plusieurs zones distinctes réparties dans l’espace de jeu.
- Le jeu prend la forme d’un parcours chronométré. Le joueur doit guider la bille de zone en zone dans un ordre imposé, en cherchant à optimiser son temps de parcours. Lorsqu’une zone est atteinte (zone n), la prochaine destination (zone n+1) est indiquée au joueur, ce qui introduit une dimension de réactivité et de prise de décision rapide.
- Le contrôle de la bille s’effectue indirectement : le joueur ne déplace pas la bille elle-même, mais agit sur l’inclinaison du plateau grâce à un joystick. En modifiant l’angle du plateau selon différents axes, il influence ainsi la trajectoire et la vitesse de la bille.
- Le terrain de jeu est également personnalisable. L’utilisateur peut configurer le plateau en y ajoutant des obstacles fixes. Ces éléments constituent des contraintes supplémentaires : la bille peut s’y heurter, ce qui modifie sa trajectoire et complexifie le parcours. Cette possibilité permet de varier les niveaux de difficulté et d’enrichir l’expérience de jeu.
Ce projet combine conception mécanique pour la réalisation du plateau de jeu ainsi qu’électronique et programmation pour l’inclinaison motorisée du plateau et les capteurs.
1.2- Liste des composants
- Plateau et support de jeu réalisés en MDF 3mm et PMMA 3mm à la découpeuse laser
- Bille métallique
- 2 servomoteurs MG996R
- 25 Capteurs tactiles TTP223
- 25 supports des capteurs réalisés en impression 3D
- Obstacles réalisés en impression 3D
- Ruban à LED
- Microcontrôleur ESP32
- 2 multiplexeurs 16 canaux HP4067
- Fils de connexion
2- Conception Mécanique
Nous avons modélisé le support du jeu intégralement sur Onshape. Ce support est constitué de 3 blocs :
- Le plateau de jeu, qui doit intégrer les capteurs, le câblage et une fixation des obstacles éventuels
- Le cadre intermédiaire, qui assure l’un des 2 sens de rotation du plateau
- Le cadre extérieur, qui assure l’autre sens de rotation et qui accueille le matériel du contrôle électronique du jeu

2.1- Modélisation du plateau de jeu


On a opté pour un plateau de surface 32cm*32cm en PMMA (3 mm). Ce matériau, non opaque, permet la transmission de la lumière émise par les LED pour suivre la trajectoire de la bille.
Les différentes zones du jeu sont matérialisées sous le plateau par des séparations en MDF (3 mm). Contrairement au PMMA, ce matériau est opaque, ce qui permet de confiner la lumière émise par chaque LED à sa zone spécifique, évitant ainsi toute diffusion lumineuse vers les zones adjacentes. Des vias sont cependant nécessaires au câblage des capteurs et LED.
Le système retenu pour la fixation des obstacles repose sur l’intégration, à intervalles réguliers sur le plateau, d’emplacements de 5 × 5 mm de section et de 3 mm de profondeur. Ces logements permettent d’insérer et de maintenir les obstacles de manière stable. Par conséquent, les obstacles doivent être conçus en respectant précisément ces dimensions afin de pouvoir s’emboîter correctement dans le plateau.
Les bords du plateau sont dimensionnés de manière à garantir le maintien de la bille à l’intérieur de la surface de jeu, tout en permettant d’atteindre la partie inférieure où sont installés les capteurs et les LED. Par ailleurs, ces bords intègrent des perçages spécifiques destinés à accueillir, dans une étape ultérieure, un moteur et un roulement à billes.
2.2- Modélisation du cadre intermédiaire

Le cadre intermédiaire entoure directement le plateau de jeu et constitue le premier axe de rotation du système. Il permet d’incliner le plateau selon une direction donnée, assurant ainsi un premier degré de liberté dans le contrôle du mouvement de la bille.
Ce cadre présente un encombrement de 41,9 × 39,2 × 2,7 cm. L’un de ses côtés est volontairement allongé afin de permettre l’intégration d’un premier moteur. Les fils d’alimentation de ce dernier sont acheminés à travers un via spécialement prévu à cet effet sur la face supérieure du cadre (cf. image).
Par ailleurs, le cadre intègre deux emplacements fonctionnels distincts : le premier permet le passage de l’arbre du moteur afin d’assurer la transmission du mouvement jusqu’au plateau de jeu ; le second est destiné à accueillir l’arbre du deuxième moteur, situé à l’extérieur de ce cadre.
2.3- Modélisation du cadre extérieur

Le second cadre, positionné à l’extérieur du cadre intermédiaire, constitue le deuxième axe de rotation du système. Il permet d’incliner l’ensemble du dispositif selon une direction perpendiculaire à celle assurée par le cadre intermédiaire, offrant ainsi un second degré de liberté pour le contrôle du mouvement de la bille.
Ce cadre supporte le cadre intermédiaire et assure sa rotation autour d’un axe horizontal. Sa structure doit donc garantir une bonne rigidité afin de maintenir la stabilité de l’ensemble. Afin de rendre ces rotations possibles, le cadre secondaire est équipé de pieds qui surélèvent l’ensemble du dispositif. Cette élévation libère l’espace nécessaire au débattement des cadres.
Le cadre intègre également les composants nécessaires au pilotage du système : des emplacements spécifiques sont prévus pour accueillir le microcontrôleur ainsi que le joystick. Enfin, il comprend les éléments mécaniques indispensables à son fonctionnement (le second moteur et les roulements).
2.4- Modélisation du support de capteur

Afin d’assurer un positionnement optimal des capteurs, des supports spécifiques ont été conçus et réalisés par impression 3D. Ces éléments permettent de surélever les capteurs jusqu’au niveau de la surface du plateau de jeu, garantissant ainsi une détection fiable du passage de la bille.
Ces supports assurent un maintien stable des capteurs tout en respectant un alignement précis avec les zones de détection définies sur le plateau.
Par ailleurs, un espace est volontairement aménagé sous ces supports afin de permettre le passage et l’organisation du câblage. Cette disposition facilite l’intégration des capteurs et limite les contraintes sur les fils.
2.5- Modélisation des obstacles

Afin d’enrichir l’expérience de jeu et d’introduire une dimension de personnalisation, le plateau est conçu pour accueillir des obstacles amovibles. Nous avons choisi de modéliser ces obstacles sous la forme de barrières d’une longueur de 6,5 cm.
L’utilisateur a la possibilité de positionner librement ces barrières sur le plateau en fonction du parcours qu’il souhaite créer. Cette modularité permet de varier la difficulté du jeu et de renouveler les configurations possibles, rendant chaque partie potentiellement différente.
Pour assurer leur fixation, le plateau est équipé d’un réseau de cavités régulièrement espacées, de section 5 × 5 mm et de profondeur 3 mm. Les barrières sont conçues avec des éléments d’emboîtement compatibles avec ces dimensions, permettant une insertion simple des obstacles.
Lors de la progression de la bille, ces obstacles agissent comme des contraintes physiques : ils modifient sa trajectoire en cas de collision et obligent le joueur à anticiper davantage ses mouvements. Ils participent ainsi pleinement à la complexité et à l’intérêt du jeu.
3- Conception Electronique
L’ensemble du système électronique est piloté par un microcontrôleur ESP32. Il permet à la fois de traiter les informations issues des capteurs, de contrôler l’allumage des LED pour le retour visuel, et de piloter les servomoteurs responsables de l’inclinaison du plateau. Ainsi, l’ESP32 assure la liaison entre la détection, l’interface utilisateur et l’action mécanique.


Les sous-parties suivantes détaillent le fonctionnement de ces différents aspects.
3.1- Système de détection et de signalisation (capteurs et LED)
L’implémentation de la logique de jeu repose sur une boucle interactive simple : l’ESP32 sélectionne aléatoirement une cible, l’illumine via la bande LED adressable, et scrute le capteur correspondant jusqu’à la détection de la bille. Pour la détection, vingt-cinq modules capacitifs TTP223 ont été choisis pour leur aspect prêt à l’emploi, garantissant une détection fiable à travers la structure sans nécessiter la conception d’un circuit sur mesure. L’enjeu technique majeur était d’interfacer ces vingt-cinq capteurs avec l’ESP32. Pour ce faire, deux multiplexeurs à seize canaux (74HC4067) ont été intégrés avec une optimisation matérielle cruciale : les deux puces partagent exactement les quatre mêmes broches d’adressage du microcontrôleur. Cette mise en parallèle permet de réduire drastiquement l’encombrement des broches (GPIO). La lecture s’opère de manière séquentielle, chaque multiplexeur renvoyant le statut de ses capteurs vers une broche de lecture qui lui est propre. Côté signalisation, un compromis énergétique strict a été appliqué sur les cinquante LED RGBW. Pour éviter de surcharger et de mettre en sécurité l’alimentation USB du système logique, le programme bride l’éclairage en n’allumant que la paire de LED de la cible active.
3.2- Système d’actionnement (commande des moteurs)
L’actionnement du plateau est assuré par deux servomoteurs MG996R contrôlant les axes X et Y via les signaux d’un joystick analogique. Ce choix matériel illustre un compromis assumé entre le couple et la précision : ces actionneurs à engrenages métalliques offrent la force brute indispensable pour incliner la structure en bois et ses obstacles, mais manquent de résolution fine. Pour corriger cette imprécision et éliminer la dérive (drift) causée par le jeu mécanique du ressort du joystick, le code impose une zone morte logicielle autour du point central de la manette. De plus, afin d’assurer une réactivité immédiate et sans latence, le contrôle n’est pas proportionnel : le programme incrémente l’angle du moteur d’une valeur fixe et constante à chaque cycle où le joystick est incliné. Pour protéger le matériel, ces angles de consigne sont strictement bornés informatiquement afin d’empêcher tout forçage mécanique. Enfin, sur le plan électrique, l’alimentation des servomoteurs est totalement isolée sur une source de puissance externe, partageant uniquement une masse commune (GND) avec la logique. Cette séparation est vitale pour empêcher les pics de courant générés par les moteurs de provoquer des chutes de tension et des redémarrages de l’ESP32.
Code
#include <ESP32Servo.h>
#include <Adafruit_NeoPixel.h>
// ==========================================
// PIN CONFIGURATIONS
// ==========================================
#define NUM_LEDS 50
#define LED_PIN 26
#define JOY_X 34
#define JOY_Y 35
#define JOY_SW 25
#define SERVO_X_PIN 18
#define SERVO_Y_PIN 19
// Multiplexer Pins
const int muxS[] = {13, 12, 14, 27}; // S0, S1, S2, S3
#define MUX1_SIG 32
#define MUX2_SIG 33
// ==========================================
// GAME AND MOTOR PARAMETERS
// ==========================================
Servo servoX, servoY;
// Adjust here the angle where your table is perfectly flat (horizontal)
int ZERO_POINT_X = 90;
int ZERO_POINT_Y = 90;
float angleX = ZERO_POINT_X;
float angleY = ZERO_POINT_Y;
const float angleLimit = 20.0;
// Veeeery low sensitivity for a subtle movement
const float sensitivity = 0.005;
Adafruit_NeoPixel pixels(NUM_LEDS, LED_PIN, NEO_GRBW + NEO_KHZ800);
volatile bool isPlaying = false;
unsigned long lastDebounce = 0;
int targetCell = -1;
// ==========================================
// DICTIONARY: SENSOR -> FIRST LED
// ==========================================
const int ledMap[25] = {
// COLUMN 1 (Sensors 0 to 4) - Left
0, 18, 20, 38, 40,
// COLUMN 2 (Sensors 5 to 9)
2, 16, 22, 36, 42,
// COLUMN 3 (Sensors 10 to 14) - Middle
4, 14, 24, 34, 44,
// COLUMN 4 (Sensors 15 to 19)
6, 12, 26, 32, 46,
// COLUMN 5 (Sensors 20 to 24) - Right
8, 10, 28, 30, 48
};
// ==========================================
// AUXILIARY FUNCTIONS
// ==========================================
void IRAM_ATTR handleButton() {
if (millis() - lastDebounce > 300) {
isPlaying = !isPlaying;
lastDebounce = millis();
}
}
bool readSensor(int channel) {
int address = (channel < 15) ? channel : (channel - 15);
for(int i = 0; i < 4; i++) {
digitalWrite(muxS[i], bitRead(address, i));
}
delayMicroseconds(5);
if (channel < 15) return digitalRead(MUX1_SIG);
else return digitalRead(MUX2_SIG);
}
void processMovement() {
int readingX = analogRead(JOY_X);
int readingY = analogRead(JOY_Y);
float velX = 0, velY = 0;
if (abs(readingX - 2048) > 200) velX = (readingX - 2048) / 2048.0;
if (abs(readingY - 2048) > 200) velY = (readingY - 2048) / 2048.0;
angleX += velX * sensitivity;
angleY += velY * sensitivity;
// Locks the tilt based on the defined zero point
angleX = constrain(angleX, ZERO_POINT_X - angleLimit, ZERO_POINT_X + angleLimit);
angleY = constrain(angleY, ZERO_POINT_Y - angleLimit, ZERO_POINT_Y + angleLimit);
servoX.write(angleX);
servoY.write(angleY);
}
// Blinks strong green (only on the target cell's pair)
void successEffect(int cell) {
int firstLed = ledMap[cell];
for(int j = 0; j < 4; j++) {
pixels.clear();
pixels.setPixelColor(firstLed, pixels.Color(0, 255, 0, 0)); // MAX Green
pixels.setPixelColor(firstLed + 1, pixels.Color(0, 255, 0, 0));
pixels.show();
delay(150);
pixels.clear();
pixels.show();
delay(150);
}
}
// Lights up one pair at a time strolling through the cells
void idleEffect() {
static int currentCell = 0;
int firstLed = ledMap[currentCell];
pixels.clear();
pixels.setPixelColor(firstLed, pixels.Color(255, 255, 255, 0)); // Strong White
pixels.setPixelColor(firstLed + 1, pixels.Color(255, 255, 255, 0));
pixels.show();
currentCell++;
if (currentCell >= 25) currentCell = 0; // Back to start
delay(80); // Controls the sequence speed
}
// ==========================================
// SETUP AND MAIN LOOP
// ==========================================
void setup() {
Serial.begin(115200);
for(int i = 0; i < 4; i++) pinMode(muxS[i], OUTPUT);
pinMode(MUX1_SIG, INPUT);
pinMode(MUX2_SIG, INPUT);
pinMode(JOY_SW, INPUT_PULLUP);
attachInterrupt(JOY_SW, handleButton, FALLING);
ESP32PWM::allocateTimer(0);
servoX.setPeriodHertz(50);
servoY.setPeriodHertz(50);
servoX.attach(SERVO_X_PIN, 500, 2400);
servoY.attach(SERVO_Y_PIN, 500, 2400);
servoX.write(ZERO_POINT_X);
servoY.write(ZERO_POINT_Y);
pixels.begin();
pixels.show();
Serial.println("System Started!");
}
void loop() {
if (isPlaying) {
if (targetCell == -1) {
targetCell = random(0, 25);
Serial.printf("New target: Cell %d\n", targetCell);
int firstLed = ledMap[targetCell];
pixels.clear();
pixels.setPixelColor(firstLed, pixels.Color(255, 255, 255, 0));
pixels.setPixelColor(firstLed + 1, pixels.Color(255, 255, 255, 0));
pixels.show();
}
processMovement();
if (readSensor(targetCell) == HIGH) {
Serial.println("Success!");
successEffect(targetCell); // Passes the current cell to blink only it
targetCell = -1;
}
} else {
idleEffect();
angleX = ZERO_POINT_X;
angleY = ZERO_POINT_Y;
servoX.write(ZERO_POINT_X);
servoY.write(ZERO_POINT_Y);
targetCell = -1;
}
}
Conclusion
En conclusion, ce projet de labyrinthe interactif nous a permis de mettre en pratique de manière concrète nos connaissances en mécanique, en électronique et en programmation autour de l’ESP32. Bien que nous ayons rencontré des défis techniques importants, notamment la gestion des appels de courant des moteurs et la complexité du câblage des vingt-cinq capteurs, nous avons su concevoir des solutions matérielles et logicielles adaptées. L’utilisation de multiplexeurs pour optimiser l’usage des broches, la séparation stricte des circuits d’alimentation et l’ajustement du code pour pallier les imprécisions du joystick nous ont permis d’aboutir à un prototype à la fois fonctionnel, stable et ludique. L’objectif initial est donc pleinement atteint, et le système développé constitue une base technique solide pour de futures itérations.