P-07 / DOC / D1
Guide d'utilisation du firmware
Source : Differential-Robot-Firmware.
Vue d’ensemble
Firmware pour robot différentiel de compétition (Coupe de France de Robotique) basé sur un ESP32-S3. Il gère la motorisation pas-à-pas, le LIDAR LD06, la détection d’adversaire, l’affichage OLED, les LEDs NeoPixel, les actionneurs I2C et les roues codeuses (affichage + calibration).
Matériel requis
| Composant | Détail |
|---|---|
| Microcontrôleur | ESP32-S3-DevKitC-1 |
| Moteurs | 2× pas-à-pas (200 pas/tr, 8 micropas) |
| Encodeurs | 2× AMT102V sur roues codeuses ∅50mm, 2048 PPR |
| LIDAR | LD06 |
| Écran | OLED SSD1306 128×64 I2C |
| LEDs | NeoPixel × 7 (pin 46) |
| Actionneurs servo | PCA9685 I2C (0x40) : 16 canaux PWM |
| I/O numérique | PCF8574 I2C (0x20) : 8 broches |
| Tirette | Pin 14, pull-up externe |
| Switch équipe | Pin 17, pull-up interne |
| Bouton init | Pin 3, pull-up interne |
Démarrage rapide
1. Configurer le matériel : src/config.h
// Roues motrices
#define DRIVE_WHEEL_DIAM_MM 57.53f // diamètre réel (pied à coulisse)
#define WHEELBASE_MM 145.6f // distance entre roues motrices
// Roues codeuses (passives, séparées des motrices : pour test et calibration)
#define ENC_WHEEL_DIAM_MM 50.58f
#define ENC_WHEELBASE_MM 189.0f // distance entre roues codeuses
// Dimensions robot
#define ROBOT_BACK_TO_CENTER_MM 80.9f // arrière → axe des roues
// Vitesse et accélération
#define DEFAULT_SPEED_MMS 2000.0f // mm/s
#define DEFAULT_ACCEL_MMS2 1500.0f // mm/s²
// Détection adversaire
#define OBS_DETECT_DIST_MM 500.0f // mm
// Zones aveugles LIDAR (poteaux structurels du robot)
#define LIDAR_BLIND_L_START 75.0f // début zone gauche (°, repère robot)
#define LIDAR_BLIND_L_END 105.0f
#define LIDAR_BLIND_R_START 255.0f // début zone droite
#define LIDAR_BLIND_R_END 285.0f
// Chrono de match
#define MATCH_DURATION_MS 100000UL // 100 s
#define MATCH_ENDGAME_MS 85000UL // repli à 85 s
2. Flasher et démarrer
pio run --target upload
pio device monitor
Séquence pré-match
Démarrage → moteurs DÉSENGAGÉS (robot poussable librement)
1. APPUYER INIT → positionner le robot, appuyer sur INIT
→ le robot effectue le calage et mémorise sa position
2. INSERER TIRETTE → insérer la tirette (pin 14 LOW)
3. PRET ! → le robot attend le retrait de la tirette
4. [Tirette retirée] → match lancé ! (chrono démarre)
Pendant WAIT_INIT, l’OLED affiche les valeurs encodeurs (R et L en mm) pour vérifier les roues codeuses. Le switch d’équipe peut être changé à tout moment avant l’étape 1.
Chrono de match
| Temps | Comportement |
|---|---|
| 0 → 85 s | Stratégie normale |
| 85 s | go()/turn() s’arrêtent → runNearEndYellow/Blue() lancé |
| 100 s | Moteurs et actionneurs désengagés |
Écrire une stratégie : src/strategy/strategy.cpp
Commandes robot
// ─── Déplacements step-based (open-loop, dead reckoning) ────────────────────
robot.go(500); // avance 500 mm (négatif = recule)
robot.turn(90); // tourne 90° à gauche (négatif = droite)
robot.gotoXY(1000, 500); // va aux coordonnées absolues
robot.gotoXY(1000, 500, 0); // idem avec cap final 0°
robot.goStall(-500); // recule + stoppe sur blocage (plaquage bordure)
// ─── Déplacements asservis encodeurs (closed-loop, recommandé) ──────────────
robot.goPID(500); // translation asservie
robot.turnPID(90); // rotation asservie
robot.gotoXYenc(1000, 500); // navigue + arrête à la cible
robot.gotoXYenc(1000, 500, 90); // avec orientation finale
robot.gotoXYenc(POI::startYellow, 90); // via POI nommé
// ─── API motion non-bloquante (taskMotionControl) ───────────────────────────
robot.setTarget(1000, 500, 90); // poste la cible, retour immédiat
robot.waitArrived(0); // bloquant : attend convergence
robot.waitArrived(150); // sort à 150 mm de la cible (préparation blend)
robot.holdPosition(); // mode HOLD : maintient la pose actuelle
robot.releaseMotion(); // mode IDLE : moteurs libres
// ─── Pose ───────────────────────────────────────────────────────────────────
robot.setPosition(x, y, theta_deg);
// ─── Vitesse ────────────────────────────────────────────────────────────────
robot.setSpeed(400);
robot.setSpeedPct(80, 80); // % de gCalib.defaultSpeed/defaultAccel
robot.resetSpeed(); // revient à gCalib.defaultSpeed/defaultAccel
// ─── Détection adversaire ───────────────────────────────────────────────────
robot.enableObstacle();
robot.disableObstacle();
// ─── Moteurs ────────────────────────────────────────────────────────────────
robot.disableMotors(); // libère aussi MotionController (→ IDLE)
robot.enableMotors();
// ─── Chrono ─────────────────────────────────────────────────────────────────
robot.isEndgame() // true après MATCH_ENDGAME_MS
robot.isMatchOver() // true après MATCH_DURATION_MS
robot.waitMatchTime(95000); // bloque jusqu'à la 95e seconde (ou fin match)
// ─── Délai FreeRTOS (dans strategy.cpp ou actuators.cpp) ────────────────────
#include "../utils.h"
wait(500); // attend 500ms en libérant le CPU
Recommandation : utiliser gotoXYenc / goPID / turnPID pour la navigation. Les variantes go / turn sont open-loop (pas de feedback encodeurs), utiles seulement pour goStall (plaquage bordure).
Repère de coordonnées
(0,0)──────────────────────────────→ X+ (3000 mm)
│
│ 90°
│ ↑
│ 180° ←─┼─→ 0°
│ ↓
│ 270°
↓
Y+ (2000 mm)
- Origine : coin haut-gauche de la table
- X+ = droite, Y+ = bas, 0° = face à droite
- Angles positifs = anti-horaire : 90° = haut (-Y), 270° = bas (+Y)
Prise et dépose de stock
// x, y = centre du stock, angleDeg = direction d'approche
takeStock (robot, 500, 800, 90); // approche, avance 210mm, séquencePrise()
deposeStock(robot, 200, 600, 180); // navigue, ouvre gripper, recule, ferme
// Constantes géométriques (config.h) :
// STOCK_TOOL_OFFSET_MM = 210mm (centre robot → centre stock en prise)
// STOCK_STAGING_MM = 120mm (recul avant approche finale)
// STOCK_DEPOSE_OFFSET_MM= 180mm (centre robot → centre stock en dépose)
Init : recalage bordure
void runInitYellow(Robot &robot) {
robot.enableMotors();
robot.disableObstacle();
initActuators();
robot.go(-300);
robot.setPosition(ROBOT_BACK_TO_CENTER_MM, 900, 0);
}
Calibration géométrique
Disponible via runCalibration(robot, encLeft, encRight) dans strategy.h.
Pour l’activer temporairement : dans main.cpp, remplacer runInitYellow/Blue(robot) par runCalibration(robot, encLeft, encRight).
Calibrer DRIVE_WHEEL_DIAM_MM (distances)
Appuie sur init → robot fait go(1000) → mesure encodeurs → affiche la correction dans le moniteur série.
Calibrer WHEELBASE_MM (angles)
Robot fait turn(720) (2 tours) → mesure encodeurs → calcule le WHEELBASE_MM corrigé.
Formule : WHEELBASE_new = WHEELBASE × (720 / angle_mesuré)
- angle mesuré < 720° → WHEELBASE trop petit → valeur augmente ✓
- angle mesuré > 720° → WHEELBASE trop grand → valeur diminue ✓
Actionneurs I2C : src/actuators/
Servomoteurs (PCA9685)
// actuators.h : déclarer :
extern Servo servoBras;
void deployerBras();
// actuators.cpp : définir :
Servo servoBras(pca, {0, 500, 2500, 0, 180}); // {canal, minUs, maxUs, minDeg, maxDeg}
void deployerBras() { servoBras.moveTo(150, 60); } // 150° à 60°/s
Commandes servo :
servoBras.setAngle(90); // direct
servoBras.setPercent(50); // 0-100% de la course
servoBras.moveTo(90, 45); // avec vitesse (bloquant)
servoBras.moveToPercent(100, 30);
servoBras.detach(); // relâche le PWM
PCF8574 (I/O numériques)
pcf.setPin(0, false); // LOW = actif
pcf.setPin(0, true); // HIGH = repos
bool e = pcf.getPin(3);
Délais dans les séquences
#include "../utils.h" // ou via actuators.h qui l'inclut déjà
wait(1000); // 1 seconde FreeRTOS (libère le CPU)
Zones aveugles LIDAR
Le LIDAR peut détecter les poteaux structurels du robot. Filtrer avec :
// config.h : angles en repère robot (0°=avant, 90°=gauche, 270°=droite)
#define LIDAR_BLIND_L_START 75.0f
#define LIDAR_BLIND_L_END 105.0f
#define LIDAR_BLIND_R_START 255.0f
#define LIDAR_BLIND_R_END 285.0f
Appliqué dans robot.cpp (détection obstacle) et leds.cpp (visualisation secteurs).
Calibrer en observant les points des poteaux sur Teleplot pendant WAIT_INIT.
Affichage OLED
| État | Affichage |
|---|---|
APPUYER INIT |
Équipe en grand + encodeurs (R/L mm) + LIDAR |
INSERER TIRETTE |
Équipe en grand + LIDAR |
PRET ! |
Équipe en grand + LIDAR |
| Match | État + X/Y/Cap + temps restant + CPU |
OBSTACLE! |
Distance + angle + secteur |
REPLI ! |
Endgame en cours |
MATCH FINI |
Fin de match |
LEDs NeoPixel
AVANT
[4] [5] [6]
[0] ← couleur d'équipe
[3] [2] [1]
ARRIÈRE
- LED 0 : couleur d’équipe
- LEDs 1-6 : secteurs LIDAR : rouge = obstacle, vert dim = libre
- Seuil =
OBS_DETECT_DIST_MM, rafraîchissement 100ms
Paramètres de calibration clés
| Paramètre | Effet |
|---|---|
DRIVE_WHEEL_DIAM_MM |
Distances parcourues |
WHEELBASE_MM |
Angles de rotation |
ENC_WHEEL_DIAM_MM |
Distances mesurées par les roues codeuses |
ENC_WHEELBASE_MM |
Angles calculés depuis les roues codeuses |
LIDAR_OFFSET_DEG |
Orientation secteurs LIDAR |
LIDAR_BODY_DIST_MM |
Filtre intérieur robot (80mm) |
LIDAR_BLIND_L/R_* |
Zones aveugles poteaux structurels |
OBS_DETECT_DIST_MM |
Distance détection adversaire |
MATCH_DURATION_MS |
Durée totale match |
MATCH_ENDGAME_MS |
Déclenchement repli |
STOCK_TOOL_OFFSET_MM |
Offset centre robot→stock en prise |
STOCK_DEPOSE_OFFSET_MM |
Offset centre robot→stock en dépose |
Architecture des tâches FreeRTOS
| Tâche | Cœur | Priorité | Rôle |
|---|---|---|---|
lidar |
0 | 2 | Lecture UART + parsing LD06 |
encoders |
0 | 3 | update() encodeurs 200Hz : gestion rollover PCNT |
strategy |
1 | 2 | Pré-match + match + chrono + actionneurs |
motion |
1 | 3 | taskMotionControl 50 Hz : PID continu, exécute Target |
display |
1 | 1 | Rafraîchissement OLED 2 Hz |
logwifi |
0 | 1 | Broadcast WebSocket (pose, lidar, motion, etc.) |
loop() |
1 | - | LEDs 100ms · WiFi telemetry 200ms · Field click poll · Log match 500ms |
Interface Web (data/index.html)
Le robot expose une interface Web accessible via WiFi (mode AP “Karibous” ou STA selon WIFI_USE_STA dans config.h). Adresse : http://192.168.4.1 (AP) ou IP affichée sur l’OLED (STA).
Layout
- Terrain central (canvas 900×600) : pose robot, marqueur cible MotionController, points LIDAR optionnels, POIs
- Pose bar sous le terrain : X / Y / θ / état / Nav
- Panneau ROBOT : 7 boutons de contrôle d’état
- Onglets droite : MOTION (debug + radar) / ACTIONNEURS (servos) / CALIBRATION (live) / LOGS
Click + drag goto
Cliquer sur le terrain envoie un goto au robot. Cliquer-glisser permet de spécifier l’orientation finale (flèche rouge de preview, label θ° en temps réel).
Autorisé uniquement : après init et hors match en cours. Le curseur devient crosshair quand autorisé.
Boutons ROBOT
| Bouton | Action |
|---|---|
| ⏹ Stop moteurs | Désactive les moteurs (toujours dispo) |
| ⚡ Activer moteurs | Réactive |
| ↩ WAIT_INIT | Retour boucle pré-match sans reset |
| ▶ Lancer Init | Rejoue runInitYellow/Blue |
| ▶ Lancer Match | Démarre le match (skip tirette) |
| ⏱ Stop match | Force la fin du match |
| ↺ Relancer Match | Reset + relance la stratégie |
Calibration live
Onglet CALIBRATION : modifier les paramètres (PID, géométrie, vitesses) en RAM sans recompiler. Une fois validés sur le robot, les copier dans config.h pour la persistance, sinon perdus au reboot.
Synchronisation POI
data/poi.js est auto-généré depuis src/strategy/poi.h par scripts/sync_poi.py à chaque build PIO. Ajouter un POI : éditer uniquement poi.h, puis ajouter une entrée dans POI_META (index.html) pour le label/couleur d’affichage.