Robo-Pointer #3 : Mon Parcours pour Stabiliser la Communication Robot à 30 Hz

Salut les passionnés de code et de robots !
Si vous avez suivi l'épisode précédent, vous savez que j'avais enfin les formules magiques (la FK/IK) pour dire à mon robot SO-100 comment bouger intelligemment. Sur le papier, tout était prêt pour le grand saut : intégrer ça dans ROS 2 et voir la magie opérer. J'étais gonflé à bloc ! Mais voilà, la robotique, c'est souvent l'art de se confronter au réel... et le réel a vite montré les dents. Ce billet, c'est l'histoire de cette confrontation : comment j'ai dû batailler avec le matériel et les protocoles pour simplement réussir à parler de manière fiable avec le robot.
Mur #1 : Le casse-tête de la lecture d'état ("Où diable es-tu ?")
Avant de commander, il faut savoir d'où l'on part. Simple, non ? Pas tant que ça. Obtenir les angles réels et calibrés des moteurs s'est avéré être mon premier vrai défi logiciel.
Le Bug Sournois du Wrap-Around : Ma fonction apply_calibration (qui convertit les "steps" moteurs en degrés) avait un défaut : elle paniquait au passage 0°/360°. Résultat ? Des lectures parfois délirantes, avec des erreurs pouvant dépasser 45° ! Imaginez piloter un robot avec un GPS aussi faux... Quelques lignes de code plus tard (et pas mal de réflexion sur les angles), l'erreur max est redescendue à ~1-2° (confirmé au rapporteur et par la cohérence des calculs suivants). Ouf !
Calibration "Maison" Obligatoire : Pour couronner le tout, le script lerobot calibrate censé générer le fichier de calibration me sortait des valeurs étranges. Plan B : retrousser les manches et créer mon manual_calibration_follower.json à la main. Mesurer les steps à 0°, vérifier les sens de rotation... Un travail un peu fastidieux mais essentiel pour avoir une base saine.
Avoir une lecture fiable était déjà une victoire, mais la route était encore longue.
Mur #2 : Communication instable ("Tu m'entends, au moins ?")
Le robot savait (à peu près) où il était. Super ! Maintenant, il fallait lui envoyer les ordres. J'ai donc intégré la logique d'écriture (write2ByteTxRx) dans ma boucle de contrôle, tournant à environ 30 Hz. Et là... avalanche d'erreurs :
# Des messages comme ça, tout le temps...
[WARN] [FeetechMotor] WRITE FAILED Res:-6, ID:2
[WARN] [FeetechMotor] packet error: Res: -6
Clairement, quelque chose clochait sérieusement dans la communication en écriture. Le robot réagissait n'importe comment. Impossible de tester quoi que ce soit de logique dans ce chaos.
L'enquête a commencé, méthodique :
La faute à ROS 2 ? Test avec un simple Timer ROS envoyant des commandes fixes => Non, mêmes erreurs.
La faute à l'environnement ROS global ? Test avec un script Python minimaliste, hors de ROS, faisant juste des écritures en boucle => Non, mêmes erreurs !
Le coupable semblait donc être l'interaction scservo_sdk / pyserial / matériel lors d'écritures répétées.
Le Pivot Stratégique : Et si ce n'était pas l'outil, mais moi ?
L'outil graphique officiel C++ fonctionnait. L'idée de basculer en C++ pour cette partie critique germait. Mais une petite voix me disait : "Attends... un SDK aussi utilisé, vraiment 'cassé' pour une simple écriture en boucle ? N'aurais-tu pas raté quelque chose dans ton setup, ta façon de faire ?" Cette remise en question critique a été décisive.
Décision : Avant de me lancer dans le C++, valider les fondamentaux. Prouver que Python pouvait faire une tâche simple de manière stable. L'objectif : réussir à faire fonctionner la téléopération LeRobot standard. Si ça marchait, le SDK Python n'était pas le problème principal. Après une réinstallation propre et l'utilisation de mon JSON manuel... Bingo ! La téléopération fonctionnait !
https://www.youtube.com/watch?v=sSHuxW4PNwI
Quel soulagement ! Ça prouvait que le matériel et Python pouvaient s'entendre. Le problème d'écriture venait donc bien de comment je communiquais en boucle rapide.
La Solution Technique : GroupSyncWrite et le Silence Radio
Retour à la doc du protocole Feetech. La lumière est venue en comprenant le fonctionnement du bus série half-duplex : par défaut, chaque moteur répond après une écriture. Sur un bus partagé et sollicité, ces réponses se marchent dessus et créent des erreurs. Imaginez une salle de classe où chaque élève crie "Reçu !" après chaque consigne du prof...
La solution, classique mais efficace :
GroupSyncWrite : Envoyer les commandes pour plusieurs moteurs en un seul paquet optimisé. Moins de trafic = moins de collisions.
Status_Return_Level = 0 : Demander aux moteurs de rester silencieux après une écriture. Le silence radio pour éviter la cacophonie.
# Extrait clé de l'initialisation dans real_robot_interface.py
# (Assurez-vous d'avoir importé GroupSyncWrite, etc.)
# Dire aux moteurs de ne pas répondre après écriture (SRL=0)
SCS_STATUS_RETURN_LEVEL = 5 # Adresse du registre
for motor_id in self.motor_ids_to_control:
# Mettre SRL à 0 (ne répond que sur PING)
self.packetHandler.write1ByteTxRx(self.portHandler, motor_id,
SCS_STATUS_RETURN_LEVEL, 0)
# Minimal error check here...
# Préparer l'outil pour les écritures groupées
SCS_GOAL_POSITION_L = 42 # Adresse Goal Pos (Low byte)
groupSyncWrite = GroupSyncWrite(self.portHandler, self.packetHandler,
SCS_GOAL_POSITION_L, 2) # 2 bytes/pos
# --- Dans la boucle de contrôle ---
# groupSyncWrite.clearParam() # Important avant d'ajouter !
# for motor_id, target_steps in commands.items():
# step_bytes = [target_steps & 0xFF, (target_steps >> 8) & 0xFF]
# # Vérifier addParam succès si nécessaire
# groupSyncWrite.addParam(motor_id, step_bytes)
# # Envoyer le paquet groupé
# groupSyncWrite.txPacket()
⚠️ ATTENTION : Utiliser Status_Return_Level = 0 est très efficace pour stabiliser la communication, mais cela désactive le retour d'erreur standard des moteurs. C'est un compromis acceptable en développement pour débloquer la situation, mais risqué en production. Sans ce retour, vous ne savez pas si un moteur a réellement exécuté l'ordre ou s'il est en erreur. Une approche plus robuste nécessiterait SRL=1 ou 2 et une gestion fine du timing, ou des lectures de confirmation.
(Note : J'ai aussi vérifié au multimètre que l'alim 5V restait stable sous charge, environ 5.0V ± 0.05V, pour écarter cette piste. Les infos de la doc Feetech SCS a été précieux.)
Résultats : Communication Enfin Stabilisée !
Le changement a été spectaculaire :
Métrique | Avant Correction | Après Correction (GroupSyncWrite + SRL=0) |
Erreurs d'Écriture | Fréquentes (>5%/cmd) | 0 / ~10 min de test continu |
Fréq. Contrôle Stable | < 10 Hz (instable) | Stable à 30 Hz |
Temps Débogage (Comm only) | facile 3 soirées | - |
La communication était enfin domptée !
Conclusion (Partie 1) : Les Leçons du Matériel
Ce que j'ai retenu de cette phase intense :
Le matériel ment rarement : toujours le vérifier en premier.
Le débogage bas niveau demande méthode et patience (isoler, simplifier, tester).
Il faut savoir remettre en question ses propres hypothèses ("l'outil est forcément cassé").
Comprendre un minimum les protocoles sous-jacents, ça sauve des vies (ou au moins, des projets !).
Le chemin est rarement linéaire : ce récit ordonne le chaos, mais la réalité fut faite d'essais/erreurs !
Avec une communication enfin stable, j'étais prêt à voir si ma logique FK/IK allait enfin pouvoir s'exprimer. Mais le robot me réservait une dernière farce, purement logique cette fois... Racontée dans le prochain article : le mystère du mouvement "toujours descendant" !
Et vous ? Ces galères de communication vous parlent ? Quelles sont vos techniques pour déboguer les bus série capricieux ? Partagez vos expériences en commentaires ! Le code est dispo sur mon GitHub.
Subscribe to my newsletter
Read articles from Koensgen Benjamin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
