Je suis ton servo
Chevalier du câble DupontApprenti Sith du câble Dupont ★★ 🗯️ « Fais-le, ou ne le fais pas. Mais ne force pas l'axe à la main. »🗯️ Pas de manipulation manuelle : nous avons des protocoles. Et des engrenages.
📡 Transmission de l'Alliance · R2-DuPont
« Là-bas, tu apprendras une règle ancienne : un servo ne se force pas. On lui donne un angle, on l'observe, et lorsqu'il commence à faire grrrrr, on coupe l'alimentation et on réfléchit à ses choix. »
🛰️ Haut commandement · Opération Contre-Circuit
« Le mécanisme d'orientation est la prochaine cible. Reprends le contrôle du servo. Angle correct. Signal correct. Pas de manipulation manuelle. Nous avons des protocoles. Et accessoirement, des engrenages. »
Objectif : faire bouger le servo Geekservo EF09085 (270°) en douceur, comme une porte de hangar : l'ouvrir et la fermer à la manette, la piloter au joystick, régler sa vitesse, et ne jamais la laisser forcer en bout de course.
🧭 Notions de la mission : Le servo · Le PWM · L'intensité · Anatomie d'un programme · Le temps
Équipement de mission¶
🧰 Matériel
- ESP32 sur son support + câble USB de données
- Geekservo EF09085 (gris, 270°)
- 3 câbles Dupont mâle-mâle
- Pièces LEGO Technic pour la porte
- Manette PS4 chargée
- En option : un écran OLED, voir l'holo-écran
💻 Logiciels
- Arduino IDE 2 + cartes « ESP32 + Bluepad32 »
- Bibliothèque ESP32Servo
🌐 Connexion internet
Oui : pour installer ESP32Servo
🔓 Missions à réussir avant
Mission 1 (la manette)
🛠️ Outils
Petit tournevis plat (bornier du support)
⏱️ Durée
1 h 30, montage LEGO compris
Comment ça marche ?¶
Un servo, c'est un petit moteur qui sait où il est. À l'intérieur : un moteur, des engrenages qui le ralentissent et le rendent costaud, un capteur qui mesure la position de l'axe, et une petite électronique qui compare sans arrêt « où je suis » et « où on me demande d'aller ». On ne lui dit donc pas « tourne », mais « va à tel angle », et il y va tout seul, puis tient la position.
L'ESP32 lui donne la consigne par une impulsion répétée 50 fois par seconde : plus elle est longue, plus l'angle est grand. Tout est expliqué dans la fiche Le servo, et le Geekservo est présenté dans l'Arsenal.
Étape 1 · Installer ESP32Servo¶
- Croquis → Inclure une bibliothèque → Gérer les bibliothèques…
- Chercher ESP32Servo.
- Installer « ESP32Servo » par Kevin Harrington (la version 3 ou plus récente, faite pour notre carte).
Étape 2 · Câbler (USB débranché !)¶
Le Geekservo a un câble à 3 fils terminé par un petit connecteur noir. Des câbles Dupont mâle-mâle s'enfoncent d'un côté dans ce connecteur, de l'autre dans les connecteurs extérieurs du support.
| Fil du servo | Son rôle | ESP32 |
|---|---|---|
| Orange | le signal (les impulsions) | D18 |
| Rouge | le + : l'alimentation 5 V | VIN |
| Marron | le − : la masse | GND |
Vérifie les couleurs de ton servo
Orange, rouge, marron est le code le plus courant pour ces servos. Si les tiens ont d'autres couleurs, le fil du milieu est presque toujours le +, et le plus foncé (marron ou noir) le −.
🎓 Académie : Le servo · La tension · L'intensité
Étape 3 · Premier test : le balayage¶
Ce premier programme fait aller et venir le servo lentement, sans manette. Il reste prudemment dans la zone de 1 à 2 millisecondes, que tous les servos acceptent.
- Nouveau croquis, coller le programme, enregistrer sous mission3_balayage, téléverser.
- Le bras du servo (la pièce jaune) doit tourner doucement dans un sens, puis dans l'autre.
Le programme · cliquer pour l'afficher
// Mission 3 · Test du servo : un lent balayage, sans manette
#include <ESP32Servo.h>
const int PIN_SERVO = 18;
const int US_MIN = 500; // impulsion supposée pour 0° (en microsecondes)
const int US_MAX = 2500; // impulsion supposée pour 270°
Servo servo;
void setup() {
Serial.begin(115200);
servo.setPeriodHertz(50); // 50 impulsions par seconde
servo.attach(PIN_SERVO, US_MIN, US_MAX);
}
void loop() {
for (int us = 1000; us <= 2000; us += 10) { // de 1 ms à 2 ms : la zone sûre
servo.writeMicroseconds(us);
delay(20);
}
Serial.println("aller terminé");
for (int us = 2000; us >= 1000; us -= 10) {
servo.writeMicroseconds(us);
delay(20);
}
Serial.println("retour terminé");
}
writeMicroseconds, c'est quoi ?
servo.writeMicroseconds(1500) envoie une impulsion de 1 500 microsecondes (1,5 ms). C'est le langage « brut » du servo. Le reste de la mission traduit des angles en microsecondes, pour que ce soit plus parlant.
Étape 4 · Calibrer : trouver les bons angles¶
Chaque montage est différent : il faut trouver l'angle « porte fermée » et l'angle « porte ouverte » de ta porte. Ce programme fait aller le servo à l'angle que tu tapes au clavier.
- Téléverser le programme de calibrage ci-dessous.
- Ouvrir le moniteur série, 115200 bauds, et choisir « Nouvelle ligne » dans la liste à côté de la vitesse.
- Le servo se place au milieu (135°). C'est le moment de monter le bras LEGO sur l'axe, porte à moitié ouverte : ainsi, il reste de la marge des deux côtés.
- Taper un angle (par exemple
100), puis Entrée : le servo y va. Recommencer jusqu'à trouver les positions de ta porte. - Noter les valeurs, en gardant 5° de marge avant les butées.
Le programme de calibrage · cliquer pour l'afficher
// Mission 3 · Calibrage : tape un angle dans le moniteur série, le servo y va
#include <ESP32Servo.h>
const int PIN_SERVO = 18;
const int US_MIN = 500; // impulsion supposée pour 0°
const int US_MAX = 2500; // impulsion supposée pour 270°
Servo servo;
int versMicrosecondes(int angle) { // 0..270° → 500..2500 µs
return map(angle, 0, 270, US_MIN, US_MAX);
}
void setup() {
Serial.begin(115200);
servo.setPeriodHertz(50);
servo.attach(PIN_SERVO, US_MIN, US_MAX);
servo.writeMicroseconds(versMicrosecondes(135)); // on démarre au milieu
Serial.println("Calibrage prêt. Tape un angle entre 0 et 270, puis Entrée.");
}
void loop() {
if (Serial.available()) {
String ligne = Serial.readStringUntil('\n'); // lit ce qui a été tapé
ligne.trim(); // enlève espaces et retours
if (ligne.length() == 0) return; // ligne vide : on ignore
int angle = constrain(ligne.toInt(), 0, 270);
int us = versMicrosecondes(angle);
servo.writeMicroseconds(us);
Serial.printf("→ %d° demandés : impulsion de %d µs\n", angle, us);
}
}
| Position | Angle noté |
|---|---|
Porte fermée (FERME) |
… |
Porte ouverte (OUVERT) |
… |
Le servo bourdonne et force ?
Il est en butée : la porte cogne contre quelque chose, ou on a demandé un angle hors de sa portée. Il consomme alors beaucoup de courant et chauffe. Redemande vite un angle plus proche du milieu, et garde ta marge de 5°.
Pour les curieux : 0° et 270° existent-ils vraiment ?
La plage d'impulsions exacte de notre Geekservo n'est pas publiée par son fabricant. Le programme suppose 500 µs pour 0° et 2 500 µs pour 270°, la convention la plus courante. Si en tapant 0 ou 270 ton servo s'arrête avant, bourdonne, ou ne bouge plus entre deux valeurs proches, c'est la limite réelle : note-la, on adaptera US_MIN et US_MAX.
Étape 5 · Monter la porte¶
Le manuel du mécano explique le montage : le Geekservo se fixe au châssis par ses trous ronds avec des pins, et l'essieu passe dans un trou en croix du bras de la porte.
Monter au milieu
Si le bras a été monté pendant l'étape 4, servo au milieu, la porte a de la marge dans les deux sens. Si tu le remontes plus tard, relance le calibrage pour le replacer à 135° d'abord.
Étape 6 · Le programme Dagoservo¶
Qui fait quoi¶
| Commande | Effet | Barre lumineuse |
|---|---|---|
| ✕ | ouvrir, en douceur | orange pendant le mouvement, puis verte |
| ○ | fermer, en douceur | orange, puis rouge |
| △ | aller à mi-chemin | orange |
| Joystick gauche ← → | la « manivelle » : la porte avance tant qu'on pousse | |
| ↑ / ↓ | vitesse du mouvement (20 à 240 degrés par seconde) | |
| R1 | arrêt d'urgence : la porte s'immobilise où elle est | |
| (automatique) | petite vibration quand la porte arrive au bout |
Le code¶
- Nouveau croquis, coller le programme, ou télécharger le fichier.
- Remplacer
FERMEetOUVERTpar tes angles notés à l'étape 4. - Enregistrer : mission3_dagoservo.
Le programme · cliquer pour l'afficher
// Mission 3 · Dagoservo : la porte du hangar, pilotée à la manette
// ✕ ouvre, ○ ferme, △ position du milieu, joystick gauche = manivelle,
// ↑ ↓ vitesse, R1 = arrêt d'urgence. Barre lumineuse : rouge fermé, vert ouvert.
#include <Bluepad32.h>
#include <ESP32Servo.h>
// ================= Réglages (à adapter après le calibrage) =================
const int PIN_SERVO = 18;
const int US_MIN = 500; // impulsion pour 0°
const int US_MAX = 2500; // impulsion pour 270°
const int FERME = 30; // ← ton angle « porte fermée », noté au calibrage
const int OUVERT = 150; // ← ton angle « porte ouverte »
Servo porte;
ControllerPtr manette = nullptr;
float position = FERME; // où est la porte maintenant (en degrés)
float cible = FERME; // où elle doit aller
int vitesse = 60; // en degrés par seconde
unsigned long dernierTemps = 0;
int etatPrecedent = -1; // pour ne signaler un changement qu'une fois
// ================= Petits outils =================
enum { B_CROIX, B_ROND, B_TRIANGLE, B_R1, B_HAUT, B_BAS, NB_BOUTONS };
bool avant[NB_BOUTONS];
bool vientDAppuyer(int numero, bool appuye) {
bool nouveau = appuye && !avant[numero];
avant[numero] = appuye;
return nouveau;
}
int versMicrosecondes(float angle) {
return US_MIN + (int)(angle * (US_MAX - US_MIN) / 270);
}
int angleMin() { return min(FERME, OUVERT); } // marche même si FERME > OUVERT
int angleMax() { return max(FERME, OUVERT); }
// ================= Connexion =================
void onConnectedController(ControllerPtr ctl) {
if (manette != nullptr) return;
manette = ctl;
etatPrecedent = -1; // pour recolorer la barre
Serial.println("🎮 Manette connectée : la porte est à tes ordres.");
}
void onDisconnectedController(ControllerPtr ctl) {
if (manette == ctl) {
manette = nullptr;
cible = position; // par sécurité, la porte s'arrête
Serial.println("❌ Manette déconnectée : la porte s'arrête.");
}
}
// ================= Lecture de la manette =================
void lireManette() {
if (vientDAppuyer(B_CROIX, manette->a())) { cible = OUVERT; Serial.println("✕ Ouverture"); }
if (vientDAppuyer(B_ROND, manette->b())) { cible = FERME; Serial.println("○ Fermeture"); }
if (vientDAppuyer(B_TRIANGLE, manette->y())) { cible = (FERME + OUVERT) / 2; Serial.println("△ Position du milieu"); }
if (vientDAppuyer(B_R1, manette->r1())) { cible = position; Serial.println("R1 : arrêt d'urgence"); }
uint8_t f = manette->dpad();
if (vientDAppuyer(B_HAUT, f & DPAD_UP)) {
vitesse = min(vitesse + 20, 240);
Serial.printf("Vitesse : %d °/s\n", vitesse);
}
if (vientDAppuyer(B_BAS, f & DPAD_DOWN)) {
vitesse = max(vitesse - 20, 20);
Serial.printf("Vitesse : %d °/s\n", vitesse);
}
// Le joystick gauche, comme une manivelle : plus on pousse, plus la cible avance vite
int x = manette->axisX(); // de -508 à 512
if (abs(x) > 80) {
cible += x / 512.0 * 2.0; // jusqu'à 2° par tour de loop()
}
cible = constrain(cible, angleMin(), angleMax()); // butées logicielles
}
// ================= Le mouvement en douceur =================
void bouger() {
unsigned long maintenant = millis();
float pas = vitesse * (maintenant - dernierTemps) / 1000.0; // degrés autorisés depuis le dernier tour
dernierTemps = maintenant;
if (abs(cible - position) <= pas) position = cible; // arrivé !
else if (cible > position) position += pas;
else position -= pas;
porte.writeMicroseconds(versMicrosecondes(position));
}
// ================= Ce que voit et sent le pilote =================
void signaler() {
int etat; // 0 fermée, 1 ouverte, 2 entre les deux
if (abs(position - FERME) < 1) etat = 0;
else if (abs(position - OUVERT) < 1) etat = 1;
else etat = 2;
if (etat == etatPrecedent || !manette) return; // rien de nouveau
etatPrecedent = etat;
if (etat == 0) {
manette->setColorLED(255, 0, 0);
manette->playDualRumble(0, 150, 0, 200); // « clac » : porte fermée
Serial.println("Porte fermée");
} else if (etat == 1) {
manette->setColorLED(0, 255, 0);
manette->playDualRumble(0, 150, 0, 200);
Serial.println("Porte ouverte");
} else {
manette->setColorLED(255, 120, 0); // orange : en mouvement
}
}
// ================= Programme principal =================
void setup() {
Serial.begin(115200);
porte.setPeriodHertz(50);
porte.attach(PIN_SERVO, US_MIN, US_MAX);
porte.writeMicroseconds(versMicrosecondes(position));
dernierTemps = millis();
BP32.setup(&onConnectedController, &onDisconnectedController);
BP32.forgetBluetoothKeys(); // à mettre en commentaire une fois que tout marche
Serial.println("Dagoservo prêt. Maintiens SHARE + PS pour coupler la manette.");
}
void loop() {
BP32.update();
if (manette && manette->isConnected()) lireManette();
bouger();
signaler();
delay(10);
}
Comment il est construit¶
| Bloc | Son travail |
|---|---|
position et cible |
deux nombres à virgule (float) : où est la porte, et où elle doit aller |
lireManette |
les boutons changent la cible ; le joystick la fait avancer petit à petit |
constrain(cible, …) |
les butées logicielles : la cible ne sort jamais de la plage de la porte |
bouger |
rapproche position de cible, d'un petit pas à chaque tour, selon la vitesse et le temps écoulé |
versMicrosecondes |
traduit un angle (0 à 270°) en impulsion (500 à 2 500 µs) |
signaler |
couleur de la barre lumineuse et vibration, seulement quand l'état change |
Ce qu'on apprend ici
- Séparer l'ordre et le mouvement : les boutons fixent une cible, une autre fonction s'occupe d'y aller en douceur. Le servo n'est jamais brusqué.
- Le temps écoulé : le pas de chaque tour est calculé avec
millis(), donc la vitesse en degrés par seconde reste juste, même siloop()ralentit. - Les butées logicielles : le programme protège la mécanique, même si le pilote insiste.
🎓 Académie : Le servo · Le temps · Anatomie d'un programme
Étape 7 · Téléverser et jouer¶
Dagoservo prêt. Maintiens SHARE + PS pour coupler la manette.
🎮 Manette connectée : la porte est à tes ordres.
Porte fermée
✕ Ouverture
Porte ouverte
Vitesse : 80 °/s
○ Fermeture
Porte fermée
Option · L'holo-écran¶
Avec l'holo-écran de la mission 1 bis, la porte du hangar a son tableau de bord : son état, un petit dessin de la porte qui bouge en même temps qu'elle, son angle, la cible, la vitesse et la durée de l'impulsion envoyée au servo.


Aperçus calculés sur ordinateur, à partir du vrai programme et de la vraie bibliothèque graphique d'Adafruit : c'est ce que l'écran devrait afficher, avec sa bande jaune en haut et le reste en bleu.
Brancher l'écran¶
Les quatre fils de la mission 1 bis, USB débranché : VCC → 3V3, GND → GND, SCL → D22, SDA → D21. Le servo garde ses trois fils (D18, VIN, GND). L'écran se plante sur la plaque d'essai, comme à la mission 1 bis.
Ce qu'il affiche¶
| Zone | Ce qu'on y voit |
|---|---|
| En haut | HANGAR, et la batterie de la manette |
| En grand | FERMEE, OUVERTE ou BOUGE |
| Angle, Cible | où est la porte, et où elle va, en degrés |
| deg/s, us | la vitesse réglée avec ↑ ↓, et la durée de l'impulsion envoyée au servo, en microsecondes (µs) |
| Le dessin | la porte vue de côté : couchée quand elle est fermée, debout quand elle est ouverte ; un petit rond montre sa cible |
Le code¶
Nouveau croquis, coller le programme ou télécharger le fichier, enregistrer sous mission3_dagoservo_ecran. C'est le programme de la mission, avec l'écran en plus : les commandes ne changent pas.
Le programme holo-écran · cliquer pour l'afficher
// Mission 3 · Dagoservo, version holo-écran
// Le même programme que Dagoservo, avec en plus l'écran OLED qui affiche :
// l'état de la porte, un dessin de la porte qui bouge, son angle, la cible,
// la vitesse et la durée de l'impulsion envoyée au servo.
// Sans écran branché, tout marche quand même.
#include <Bluepad32.h>
#include <ESP32Servo.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ================= Réglages (à adapter après le calibrage) =================
const int PIN_SERVO = 18;
const int US_MIN = 500; // impulsion pour 0°
const int US_MAX = 2500; // impulsion pour 270°
const int FERME = 30; // ← ton angle « porte fermée », noté au calibrage
const int OUVERT = 150; // ← ton angle « porte ouverte »
Servo porte;
ControllerPtr manette = nullptr;
float position = FERME; // où est la porte maintenant (en degrés)
float cible = FERME; // où elle doit aller
int vitesse = 60; // en degrés par seconde
unsigned long dernierTemps = 0;
int etatPrecedent = -1; // pour ne signaler un changement qu'une fois
// ================= Petits outils =================
enum { B_CROIX, B_ROND, B_TRIANGLE, B_R1, B_HAUT, B_BAS, NB_BOUTONS };
bool avant[NB_BOUTONS];
bool vientDAppuyer(int numero, bool appuye) {
bool nouveau = appuye && !avant[numero];
avant[numero] = appuye;
return nouveau;
}
int versMicrosecondes(float angle) {
return US_MIN + (int)(angle * (US_MAX - US_MIN) / 270);
}
int angleMin() { return min(FERME, OUVERT); } // marche même si FERME > OUVERT
int angleMax() { return max(FERME, OUVERT); }
// ================= Connexion =================
void onConnectedController(ControllerPtr ctl) {
if (manette != nullptr) return;
manette = ctl;
etatPrecedent = -1; // pour recolorer la barre
Serial.println("🎮 Manette connectée : la porte est à tes ordres.");
}
void onDisconnectedController(ControllerPtr ctl) {
if (manette == ctl) {
manette = nullptr;
cible = position; // par sécurité, la porte s'arrête
Serial.println("❌ Manette déconnectée : la porte s'arrête.");
}
}
// ================= Lecture de la manette =================
void lireManette() {
if (vientDAppuyer(B_CROIX, manette->a())) { cible = OUVERT; Serial.println("✕ Ouverture"); }
if (vientDAppuyer(B_ROND, manette->b())) { cible = FERME; Serial.println("○ Fermeture"); }
if (vientDAppuyer(B_TRIANGLE, manette->y())) { cible = (FERME + OUVERT) / 2; Serial.println("△ Position du milieu"); }
if (vientDAppuyer(B_R1, manette->r1())) { cible = position; Serial.println("R1 : arrêt d'urgence"); }
uint8_t f = manette->dpad();
if (vientDAppuyer(B_HAUT, f & DPAD_UP)) {
vitesse = min(vitesse + 20, 240);
Serial.printf("Vitesse : %d °/s\n", vitesse);
}
if (vientDAppuyer(B_BAS, f & DPAD_DOWN)) {
vitesse = max(vitesse - 20, 20);
Serial.printf("Vitesse : %d °/s\n", vitesse);
}
// Le joystick gauche, comme une manivelle : plus on pousse, plus la cible avance vite
int x = manette->axisX(); // de -508 à 512
if (abs(x) > 80) {
cible += x / 512.0 * 2.0; // jusqu'à 2° par tour de loop()
}
cible = constrain(cible, angleMin(), angleMax()); // butées logicielles
}
// ================= Le mouvement en douceur =================
void bouger() {
unsigned long maintenant = millis();
float pas = vitesse * (maintenant - dernierTemps) / 1000.0; // degrés autorisés depuis le dernier tour
dernierTemps = maintenant;
if (abs(cible - position) <= pas) position = cible; // arrivé !
else if (cible > position) position += pas;
else position -= pas;
porte.writeMicroseconds(versMicrosecondes(position));
}
// ================= Ce que voit et sent le pilote =================
void signaler() {
int etat; // 0 fermée, 1 ouverte, 2 entre les deux
if (abs(position - FERME) < 1) etat = 0;
else if (abs(position - OUVERT) < 1) etat = 1;
else etat = 2;
if (etat == etatPrecedent || !manette) return; // rien de nouveau
etatPrecedent = etat;
if (etat == 0) {
manette->setColorLED(255, 0, 0);
manette->playDualRumble(0, 150, 0, 200); // « clac » : porte fermée
Serial.println("Porte fermée");
} else if (etat == 1) {
manette->setColorLED(0, 255, 0);
manette->playDualRumble(0, 150, 0, 200);
Serial.println("Porte ouverte");
} else {
manette->setColorLED(255, 120, 0); // orange : en mouvement
}
}
// ================= L'holo-écran =================
Adafruit_SSD1306 ecran(128, 64, &Wire, -1);
bool ecranPret = false; // faux si l'écran n'est pas branché : le reste marche quand même
unsigned long derniereImage = 0;
const int PERIODE_ECRAN = 100; // une image toutes les 100 ms : 10 images par seconde
void demarrerEcran() {
Wire.begin(21, 22); // SDA sur D21, SCL sur D22
ecranPret = ecran.begin(SSD1306_SWITCHCAPVCC, 0x3C);
if (!ecranPret) {
Serial.println("📟 Pas d'écran trouvé : on continue sans lui.");
return;
}
ecran.setTextColor(SSD1306_WHITE);
ecran.setTextWrap(false);
}
// Une jauge horizontale : un cadre, rempli selon la valeur (0 à maxi)
void dessinerJauge(int x, int y, int largeur, int valeur, int maxi) {
ecran.drawRect(x, y, largeur, 7, SSD1306_WHITE);
int plein = (largeur - 2) * constrain(valeur, 0, maxi) / maxi;
ecran.fillRect(x + 1, y + 1, plein, 5, SSD1306_WHITE);
}
// Le titre dans la bande jaune (lignes 0 à 15), avec la batterie de la manette à droite (ou « ... » qui clignote sans manette)
void dessinerTitre(const char* titre) {
ecran.setTextSize(1);
ecran.setCursor(0, 3);
ecran.print(titre);
if (manette && manette->battery() > 0) dessinerJauge(100, 3, 28, manette->battery(), 255);
else if (!manette && (millis() / 500) % 2) { ecran.setCursor(110, 3); ecran.print("..."); }
ecran.drawFastHLine(0, 13, 128, SSD1306_WHITE); // la bande jaune s'arrête à la ligne 15
}
void mettreAJourEcran() {
if (!ecranPret || millis() - derniereImage < PERIODE_ECRAN) return;
derniereImage = millis();
ecran.clearDisplay();
dessinerTitre("HANGAR");
// L'état de la porte, en grand
ecran.setTextSize(2);
ecran.setCursor(0, 16);
if (abs(position - FERME) < 1) ecran.print("FERMEE");
else if (abs(position - OUVERT) < 1) ecran.print("OUVERTE");
else ecran.print("BOUGE");
// Les chiffres
ecran.setTextSize(1);
ecran.setCursor(0, 34);
ecran.printf("Angle %d deg", (int)position);
ecran.setCursor(0, 44);
ecran.printf("Cible %d deg", (int)cible);
ecran.setCursor(0, 54);
ecran.printf("%d deg/s %d us", vitesse, versMicrosecondes(position));
// La porte vue de côté : fermée = couchée, ouverte = debout
const int px = 122, py = 44, longueur = 24; // le pivot et la longueur de la porte
float fraction = (position - FERME) / (float)(OUVERT - FERME); // 0 = fermée, 1 = ouverte
float angle = fraction * PI / 2; // de 0 à 90° à l'écran
ecran.drawLine(px, py, px - longueur * cos(angle), py - longueur * sin(angle), SSD1306_WHITE);
ecran.drawLine(px + 1, py, px + 1 - longueur * cos(angle), py - longueur * sin(angle), SSD1306_WHITE);
ecran.fillCircle(px, py, 2, SSD1306_WHITE);
float fc = (cible - FERME) / (float)(OUVERT - FERME) * PI / 2; // un petit rond : là où elle va
ecran.drawCircle(px - longueur * cos(fc), py - longueur * sin(fc), 2, SSD1306_WHITE);
ecran.drawFastHLine(90, py + 3, 38, SSD1306_WHITE); // le sol
ecran.display();
}
// ================= Programme principal =================
void setup() {
Serial.begin(115200);
porte.setPeriodHertz(50);
porte.attach(PIN_SERVO, US_MIN, US_MAX);
porte.writeMicroseconds(versMicrosecondes(position));
dernierTemps = millis();
demarrerEcran();
BP32.setup(&onConnectedController, &onDisconnectedController);
BP32.forgetBluetoothKeys(); // à mettre en commentaire une fois que tout marche
Serial.println("Dagoservo prêt. Maintiens SHARE + PS pour coupler la manette.");
}
void loop() {
BP32.update();
if (manette && manette->isConnected()) lireManette();
bouger();
signaler();
mettreAJourEcran();
delay(10);
}
Ce qui change par rapport au programme de base¶
| Ajout | Son travail |
|---|---|
#include <Wire.h>, <Adafruit_GFX.h>, <Adafruit_SSD1306.h> |
les bibliothèques de l'écran, déjà installées à la mission 1 bis |
demarrerEcran(), dans setup() |
démarre le bus I2C et l'écran ; s'il n'est pas branché, ecranPret reste faux et le programme continue sans lui |
mettreAJourEcran(), dans loop() |
redessine l'écran, mais toutes les 100 ms seulement (10 images par seconde) |
dessinerTitre(), dessinerJauge() |
les petites fonctions de dessin, les mêmes dans toutes les missions |
versMicrosecondes(position) |
la fonction de la mission, réutilisée pour afficher l'impulsion |
cos() et sin() |
dessinent la porte inclinée : on transforme un angle en un point au bout de la porte |
Pourquoi seulement 10 images par seconde ?
Envoyer une image à l'écran prend environ 23 ms (le calcul est dans la fiche I2C). Pendant ce temps, le programme ne fait rien d'autre. En redessinant toutes les 100 ms, l'écran ne prend qu'un quart du temps, et la manette et le mouvement en douceur restent réactifs. Pour des chiffres, 10 images par seconde, c'est largement assez.
À essayer · L'angle et l'impulsion
- Tourne la manivelle (joystick gauche) et regarde les microsecondes changer : vérifie un ou deux chiffres avec la formule 500 + angle × 2 000 ÷ 270 (fiche Le servo).
- Baisse la vitesse avec ↓ et regarde l'angle grimper doucement vers la cible.
🎓 Académie : Le bus I2C · Le temps · Le servo
Si ça coince¶
| Symptôme | Cause probable | Solution |
|---|---|---|
| Le servo ne bouge pas du tout | fil orange pas sur D18, ou GND manquant | vérifier les 3 fils ; le marron doit aller au GND |
| La carte redémarre quand le servo démarre | le servo demande trop de courant d'un coup | baisser la vitesse ; un seul servo sur l'USB ; plus tard, une alimentation externe |
| Le servo tremble à l'arrêt | alimentation faible ou signal parasité | câble USB court et de bonne qualité ; fils bien enfoncés |
| La porte s'ouvre quand on demande de fermer | FERME et OUVERT inversés |
échanger les deux valeurs |
| Le servo bourdonne en fin de course | butée mécanique | réduire OUVERT ou augmenter FERME de 5° |
ESP32Servo.h: No such file or directory |
bibliothèque non installée | étape 1 |
La règle n°3 du code de l'atelier
Ne jamais forcer l'axe à la main quand le servo est alimenté. Il résiste de toutes ses forces pour tenir sa position : ses petits engrenages en plastique peuvent casser.
Défis bonus¶
Les solutions sont rangées dans des holocrons scellés : essaie d'abord, l'indice est là si tu bloques, et le code d'accès du maître de l'atelier ouvre la solution.
Défi 1 · La position mémorisée¶
SHARE enregistre la position actuelle de la porte ; OPTIONS l'y ramène.
Indice
Une variable de plus suffit, comme un marque-page. Pour y retourner, pas besoin de toucher au servo : change seulement la cible.
Défi 2 · La porte qui se referme toute seule¶
Une fois grande ouverte, la porte attend 5 secondes, prévient en faisant clignoter la barre lumineuse en orange pendant la dernière seconde, puis se referme.
Indice
Il faut un chrono qui démarre au moment où la porte arrive en position ouverte (un événement !). Ensuite, compare le temps écoulé avec 4 000 et 5 000 ms.
Défi 3 · La tourelle qui balaie¶
L2 met en marche (ou arrête) un balayage automatique entre les deux extrémités, comme une tourelle de garde.
Indice
Une variable vrai/faux qui s'inverse à chaque appui. Quand le balayage est en marche et que la porte est arrivée à sa cible, donne-lui l'autre extrémité comme nouvelle cible.
Validation¶
- Le balayage de test fonctionne
- Les angles « fermé » et « ouvert » de ma porte sont notés
- ✕ ouvre et ○ ferme, en douceur, avec la bonne couleur
- Le joystick fait avancer la porte sans dépasser les butées
- R1 arrête la porte, ↑ ↓ changent la vitesse
- Bonus : au moins un défi réussi