Gestion d’un pont tournant : Arduino, Écran TFT et Moteur Pas à Pas v1
Description :
Ce projet consiste en la création d’un système de pilotage automatisé pour un pont tournant ou une coulisse de triage destiné au modélisme ferroviaire. L’objectif principal est de permettre le déplacement précis d’une locomotive vers différentes voies de garage via une interface numérique intuitive.
Le cœur du système repose sur une carte Arduino qui orchestre trois fonctions majeures :
- Le Contrôle de Mouvement : Un moteur pas à pas (piloté par un driver de type TB6600) assure le déplacement de la plateforme. La précision du moteur permet d’aligner les rails avec une tolérance minimale, garantissant un passage fluide du train.
- L’Interface Utilisateur (HMI) : Un écran couleur TFT ST7789 affiche en temps réel la position actuelle et la voie cible. La sélection s’effectue à l’aide d’un encodeur rotatif, offrant une navigation fluide dans les menus.
- La Gestion du Point Zéro (Homing) : Le système intègre une procédure d’initialisation automatique. Au démarrage, le moteur cherche un capteur de fin de course pour définir sa position de référence, assurant ainsi que les coordonnées enregistrées dans le code correspondent parfaitement à la réalité physique du réseau.
Fonctionnement simplifié :
- Initialisation : Le pont se déplace seul vers sa position d’origine (Home).
- Sélection : L’utilisateur tourne l’encodeur pour choisir le numéro de la voie souhaitée sur l’écran.
- Exécution : Une pression sur le bouton valide le choix, et le moteur déplace le pont vers la coordonnée exacte enregistrée pour cette voie.
C’est une solution robuste qui remplace les systèmes mécaniques manuels par une électronique de précision, idéale pour moderniser un réseau ferroviaire miniature.
Prérequis :
- 1 x Carte Arduino Nano
- 1 x Moteur pas à pas 12V (ex. NEMA 17)
- 1 × Driver TB6600
- 1 x Écran LCD TFT 2.4 pouces, avec Module combiné d’encodeur rotatif EC11 et Interface SPI ST7789
- 1 x Speed Sensor Module LM393
- Fils de connexion
- 1 x Breadboard
Version IDE :
- Arduino IDE 2.3.5
Bibliothèque :
- Adafruit_GFX.h (version: 1.12.0 par Adafruit)
- Adafruit_ST7789.h (version: 1.11.0 par Adafruit)
- Adafruit BusIO (version: 1.7.14 par Adafruit)
STL :
Vidéo de démonstration :
Schéma de câblage :


Code :
#include <Adafruit_GFX.h> // Librairie de base pour les graphiques
#include <Adafruit_ST7789.h> // Librairie spécifique pour l'écran ST7789
// Définition des broches pour l'écran TFT
#define TFT_CS 8
#define TFT_DC 10
#define TFT_RST 9
// Initialisation de l'objet tft
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
// Définition des broches pour le moteur pas à pas (A4988 ou DRV8825)
#define stepPinM1 5 // Signal de pas
#define dirPinM1 6 // Signal de direction
#define enabledmotorM1 7 // Activation/Désactivation du moteur
#define InitPosition 4 // Capteur de fin de course (Homing)
#define BtInitPosition A3 // Bouton pour relancer l'initialisation
// Tableau des positions (en pas) pour chaque voie de train
long PositionVoies[] = { 0, 400, 800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200, 5600, -400, -800 };
long Position; // Position actuelle du moteur
// Définition des broches de l'encodeur rotatif et boutons
const int S1 = 2; // Signal A de l'encodeur (Interruption)
const int S2 = 3; // Signal B de l'encodeur
int PUSH = A2; // Bouton poussoir de l'encodeur pour valider
// Variables de gestion des voies
int nbPositionVoies = (sizeof(PositionVoies) / sizeof(PositionVoies[0])) - 1;
volatile int SelectVoie = 0; // Modifié par l'interruption de l'encodeur
int copieSelectVoie;
int dernierSelectVoie = 0;
int VoieActuel;
// Variables de temps et vitesse pour le mouvement sans blocage delay()
unsigned long currentTime;
unsigned long previousTimeM1;
int EtatstepPinM1;
int motorspeedInit = 1000; // Vitesse plus rapide pour l'initialisation (microsecondes)
int motorspeed = 5000; // Vitesse de croisière
int CorrectionInit = 445; // Décalage après avoir touché le capteur de fin de course
int Etape = 0; // Variable d'état pour la machine à états (switch case)
void setup() {
Serial.begin(9600);
// Configuration des entrées/sorties
pinMode(S1, INPUT_PULLUP);
pinMode(S2, INPUT_PULLUP);
// Attache une interruption sur S1 pour détecter la rotation de l'encodeur
attachInterrupt(digitalPinToInterrupt(S1), gestionEncodeur, FALLING);
pinMode(stepPinM1, OUTPUT);
pinMode(dirPinM1, OUTPUT);
pinMode(enabledmotorM1, OUTPUT);
digitalWrite(enabledmotorM1, HIGH); // Moteur désactivé par défaut
pinMode(InitPosition, INPUT_PULLUP);
pinMode(PUSH, INPUT);
pinMode(BtInitPosition, INPUT);
// Initialisation de l'écran TFT (240x320 pixels)
tft.init(240, 320);
tft.setRotation(2); // Orientation de l'affichage
tft.invertDisplay(false);
tft.fillScreen(ST77XX_BLACK);
// Texte de bienvenue
tft.setTextColor(ST77XX_GREEN, ST77XX_BLACK);
tft.setTextSize(2);
tft.setCursor(20, 5);
tft.print("projetsduino.com");
}
void loop() {
switch (Etape) {
case 0: // ÉTAPE : Initialisation (Homing)
tft.setTextSize(2);
tft.setCursor(30, 150);
tft.print("Initialisation");
tft.setCursor(60, 170);
tft.print("en cour...");
moteurInit(); // Cherche le capteur de fin de course
Position = 0; // Reset de la position interne
delay(1000);
moteur(CorrectionInit, 1); // Se place sur le point zéro réel
tft.setCursor(60, 170);
tft.print(" Terminer ");
delay(2000);
// Effacement zone texte
tft.setCursor(30, 150); tft.print(" ");
tft.setCursor(60, 170); tft.print(" ");
Position = 0; // Reset de la position interne
Etape++;
break;
case 1: // ÉTAPE : Affichage du menu principal
tft.setTextSize(2);
tft.setCursor(60, 60);
tft.print("Voie actuel");
tft.setTextSize(3);
tft.setCursor(90, 100);
tft.print(" 0 ");
tft.setTextSize(2);
tft.setCursor(20, 170);
tft.print("Selectionner une");
tft.setCursor(30, 190);
tft.print("voie de train :");
tft.setTextSize(3);
tft.setCursor(90, 230);
tft.print(" 0 ");
Etape++;
break;
case 2: // ÉTAPE : Attente de sélection utilisateur
// Si l'encodeur a tourné
if (SelectVoie != dernierSelectVoie) {
noInterrupts(); // Protection des variables volatiles
if (SelectVoie < 0) SelectVoie = 0;
if (SelectVoie > nbPositionVoies) SelectVoie = nbPositionVoies;
copieSelectVoie = SelectVoie;
dernierSelectVoie = copieSelectVoie;
// Mise à jour de l'affichage de la voie sélectionnée
tft.setTextSize(3);
if (copieSelectVoie < 10) {
tft.setCursor(90, 230);
tft.print(" "); tft.print(copieSelectVoie); tft.print(" ");
} else {
tft.setCursor(100, 230);
tft.print(copieSelectVoie);
}
interrupts();
}
// Si on appuie sur le bouton de validation
if (digitalRead(PUSH) == LOW) {
Etape = 3;
VoieActuel = copieSelectVoie;
}
// Si on appuie sur le bouton de reset position
if (digitalRead(BtInitPosition) == LOW) {
Etape = 6;
}
break;
case 3: // ÉTAPE : Mise à jour affichage avant mouvement
tft.setTextSize(3);
tft.setCursor(90, 100);
tft.print(" . "); // Indicateur de mouvement
tft.setTextSize(2);
tft.setCursor(20, 170);
tft.print(" Deplacement sur");
tft.setCursor(30, 190);
tft.print(" la voie : ");
Etape = 4;
break;
case 4: // ÉTAPE : Mouvement du moteur vers la voie choisie
moteur(PositionVoies[VoieActuel], 1);
Etape = 5;
break;
case 5: // ÉTAPE : Fin de mouvement, mise à jour affichage "Voie Actuelle"
tft.setTextSize(3);
if (VoieActuel < 10) {
tft.setCursor(90, 100);
tft.print(" "); tft.print(VoieActuel); tft.print(" ");
} else {
tft.setCursor(100, 100);
tft.print(VoieActuel);
}
Etape = 2; // Retour à l'écoute de l'encodeur
break;
case 6: // ÉTAPE : Reset complet
tft.fillScreen(ST77XX_BLACK);
Etape = 0;
break;
}
}
// Fonction de déplacement du moteur vers une position cible
void moteur(int PosDmd, int cpt) {
int sens;
// Détermination du sens de rotation
if (PosDmd > Position) {
digitalWrite(dirPinM1, LOW);
sens = 1;
} else {
digitalWrite(dirPinM1, HIGH);
sens = -1;
}
digitalWrite(enabledmotorM1, LOW); // Active le moteur
// Boucle de génération des pas (non-bloquante pour les interruptions)
while (PosDmd != Position) {
currentTime = micros();
if (currentTime - previousTimeM1 > motorspeed) {
if (sens == 1) {
Position++;
} else {
Position--;
}
previousTimeM1 = currentTime;
EtatstepPinM1 = !EtatstepPinM1; // Alterne l'état du pin pour créer un pulse
digitalWrite(stepPinM1, EtatstepPinM1);
}
}
digitalWrite(enabledmotorM1, HIGH); // Désactive le moteur (économise l'énergie/chauffe)
}
// Fonction de recherche de la position initiale (Homing)
void moteurInit() {
digitalWrite(enabledmotorM1, LOW);
digitalWrite(dirPinM1, LOW); // Direction vers le capteur
// Tourne jusqu'à ce que le capteur soit activé (LOW)
while (digitalRead(InitPosition) == LOW) {
currentTime = micros();
if (currentTime - previousTimeM1 > motorspeedInit) {
previousTimeM1 = currentTime;
EtatstepPinM1 = !EtatstepPinM1;
digitalWrite(stepPinM1, EtatstepPinM1);
}
}
digitalWrite(enabledmotorM1, HIGH);
}
// Fonction appelée automatiquement lors d'une rotation de l'encodeur
void gestionEncodeur() {
static unsigned long derniereInterruption = 0;
unsigned long tempsActuel = millis();
// Anti-rebond (debounce) logiciel de 2ms
if (tempsActuel - derniereInterruption > 2) {
if (digitalRead(S2) == HIGH) {
SelectVoie--;
} else {
SelectVoie++;
}
}
derniereInterruption = tempsActuel;
}

Un commentaire