Repository navigation
Feat/link layer and imu bench - #2
Merged
Merged
Conversation
… ROS 2 Le dossier tranche que le bus temps réel est un bus CAN. La carte de distribution n'existe pas, et le logiciel n'a pas à l'attendre : on sépare le protocole de son transport et on développe en série en écrivant le code comme si c'était déjà du CAN. Protocole protocol.yaml devient la source unique. Le générateur produit l'en-tête C du firmware, l'en-tête C++ de ROS 2 et la table de documentation. Un hash calculé sur le SENS du protocole — pas sur le texte du fichier — voyage dans chaque heartbeat : un nœud et un calculateur qui ne s'accordent pas se le disent, au lieu d'échanger des valeurs plausibles et fausses. Liaison Trame canonique de 8 octets et identifiant 11 bits, en série comme en CAN. Cadrage COBS avec CRC-16/CCITT-FALSE, 6 octets de surcoût, resynchronisation au délimiteur. Les deux transports CAN sont déjà écrits — un fichier de chaque côté, ce qui est la mesure de ce que l'abstraction vaut. La série ne fournit ni arbitrage, ni acquittement, ni retransmission, ni confinement de défaut. Aucune fonction de sécurité n'est validée dessus ; N4 et N5 restent matériels. Capteur BNO085 en SPI, pile SH-2 de CEVA en sous-module (Apache 2.0, non modifiée) ; le portage ESP-IDF est à nous. Le firmware publie ce que le capteur mesure, dans les axes du capteur : la rotation de montage est décrite dans l'URDF, pas dans le firmware. ROS 2 /imu/data en sensor_msgs/Imu conforme REP-103 et REP-145 — quaternion normalisé, accélération gravité comprise, magnétomètre en teslas, covariances renseignées à partir de la qualité rapportée par le capteur. Une covariance nulle ne signifie pas « inconnue » mais « confiance infinie », et c'est la manière la plus efficace de faire diverger un EKF. Journaux du firmware tunnellisés jusqu'à /rosout, diagnostics sur la liaison et sur le capteur, aides de banc pour Foxglove. Vérification 30 579 vérifications de cadrage et de protocole sur hôte, sans matériel. Les trois implémentations — C généré, C++ généré, Python lisant le YAML — comparées octet par octet sur les 28 trames. Vecteurs d'or pour figer le format du fil. Document : docs/architecture/13-architecture-logicielle-liaison.md, §AA à §AH. Recette du banc B1 en §AG. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Deux bloquants de compilation, un défaut SPI qui aurait bloqué la chaîne IMU en silence, et une série de points où le code et le commentaire disaient deux choses différentes. Bloquants board_config.h : SPI3_HOST n'était déclaré nulle part. main.c : CONFIG_RETRIEVER_IMU_ENABLE_MAG utilisé dans une expression C — Kconfig ne définit un booléen que lorsqu'il vaut y, donc l'option à n cassait la compilation. retriever_protocol : rt_framing.h inclut "retriever_protocol.h" sans préfixe, ce que le chemin d'inclusion du paquet ROS ne fournissait pas. retriever_link : ament_target_dependencies sans mot-clé mélangeait les deux signatures de target_link_libraries — erreur fatale à la configuration CMake. SPI spi_device_release_bus() ne désasserte pas CS, et une transaction de longueur nulle n'en produit aucune. Tout en-tête SHTP sans corps — le cas normal quand le BNO085 asserte H_INTN sans charge utile — laissait donc CS bas indéfiniment, et la liaison se désynchronisait sans qu'aucune fonction ne retourne d'erreur. Libération explicite ajoutée sur tous les chemins. Tampon de transit statique aligné : les tampons de la pile SH-2 ne le sont pas, et le pilote allouait un rebond au tas à chaque lecture, à 100 Hz. CAN twai_initiate_recovery() ne redémarre pas le contrôleur : il le laisse arrêté. Sans twai_start(), un nœud parti en bus-off se taisait pour de bon, en annonçant que tout allait bien. La reprise passe dans la boucle de réception — une fonction de statistiques ne doit pas avoir d'effet de bord. Le débit demandé est désormais réellement appliqué. Données Le game rotation vector publiait une précision d'orientation nulle là où le commentaire annonçait une valeur pessimiste. Zéro, pour un EKF, ne veut pas dire « inconnu » mais « parfait ». Sentinelle explicite, reconnue des deux côtés, avec son test. Le triplet IMU pouvait publier un quaternion périmé avec un gyromètre et une accélération plus récents : IMU_QUAT n'a pas de compteur, ses huit octets sont pleins. Son âge est maintenant borné, et les drapeaux de validité du firmware sont enfin lus. NodeStatus annonçait toujours un âge de battement nul, sur un topic latché : un panneau ouvert après une panne voyait un nœud en pleine santé. Concurrence Horodatages et compteurs partagés entre le fil de lecture et les minuteries : atomiques côté ROS, section critique côté firmware. Volatile ne donne pas l'atomicité d'un 64 bits sur un cœur 32 bits. SerialTransport::send tournait en attente active sans plafond sur un descripteur non bloquant, depuis le fil de l'exécuteur. Honnêteté Le tunnel de journalisation ne désactive pas la console : il détourne les ESP_LOGx et rien d'autre. Le bootloader, le gestionnaire de panique et tout printf continuent d'écrire en clair. La documentation le disait de travers ; elle le dit maintenant, avec ce que ça coûte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
git submodule update --init ne fait rien, et ne dit rien, quand le lien vers le sous-module a disparu de l'index. L'erreur ne se manifeste qu'au premier idf.py build, plusieurs minutes plus tard et plusieurs couches plus bas. Le script tente le sous-module, vérifie que sh2.c est bien là, se replie sur un clonage direct au tag épinglé sinon, et refuse de rendre la main sans avoir vérifié. Il signale aussi le cas où la compilation marchera localement alors qu'un clone neuf repartirait sans la bibliothèque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Les deux arrivaient par transitivité depuis driver/gpio.h. Une dépendance d'inclusion qu'on n'a pas écrite est une dépendance qu'une mise à jour d'IDF peut retirer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le commentaire de board_config.h associait SDA à MOSI. C'est l'inverse : la datasheet BNO08x donne la broche 20 (H_SDA en I2C) comme H_MISO, et la broche 17 (SA0, la sélection d'adresse I2C) comme H_MOSI. Selon les cartes, cette dernière est sérigraphiée ADDR, ADR, SA0, DI ou SI. L'erreur ne produit rien d'observable : le capteur ne répond simplement jamais, et on cherche du côté du mode SPI ou de la séquence PS0/PS1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Deux défauts révélés par le premier banc réel. La file urgente ne servait à rien. La tâche d'émission bloquait 20 ms sur la file normale, donc une trame urgente déposée juste après le début de cette attente y restait jusqu'à son expiration. Mesuré sur le banc : 52 ms d'aller-retour au lieu de quelques millisecondes — 20 ms d'émission, 20 ms de réception, le reste côté hôte. Ces 26 ms se seraient retrouvés, par latency_offset_ms, dans l'horodatage de chaque échantillon IMU. L'émetteur réveille maintenant la tâche par notification, et l'urgente repasse devant entre chaque trame normale. Le délai de scrutation de la réception passe de 20 ms à 2 ms : uart_read_bytes rend la main à expiration, donc ce délai EST la latence. Et le moniteur effaçait les journaux du firmware. Ils étaient émis, reçus, décodés, imprimés — puis le tableau repeignait l'écran par-dessus toutes les 500 ms. L'information la plus utile du banc était invisible. Elle s'affiche désormais sous le tableau, avec l'état de santé de l'IMU décodé et le nombre d'erreurs des dix dernières secondes, pour que le bruit du journal de démarrage à 115200 ne masque plus une ligne qui va vraiment mal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le capteur répond à son identification mais n'émet aucun rapport. Le journal dit « ecriture refusee : pas de reveil » : le protocole veut que l'hôte tire PS0/WAKE à la masse et attende que le capteur réponde en abaissant INT, et cette réponse n'arrive pas. Les lectures, elles, fonctionnent. Plutôt que de conclure par raisonnement, le portage compte désormais ses lectures, ses paquets, ses en-têtes vides, ses écritures et ses réveils sans réponse ; le pilote compte les événements décodés par grandeur. Un relevé toutes les cinq secondes dit où la chaîne s'arrête au lieu de le laisser déduire. L'avertissement de réveil est limité à une occurrence : sur une ligne PS0 non câblée, il noyait tout le reste. Et les 52 ms d'aller-retour venaient du moniteur, pas de la carte. read(4096) avec un délai de 50 ms attend systématiquement les 50 ms sur une liaison peu chargée. Une médiane à 52,09 avec 3 ms de dispersion était la signature d'une constante, pas d'un transport. Délai ramené à 5 ms, lecture de ce qui est déjà arrivé. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le banc rend 23287 paquets à la pile SHTP en cinq secondes et zéro événement capteur, INT restant bas en permanence. C'est la signature d'un paquet que le capteur repropose parce qu'on ne le consomme jamais vraiment. Deux causes possibles, indiscernables par le raisonnement : on lit du vrai SHTP avec une mauvaise longueur, ou on lit de la bouillie qui parse comme une longueur plausible. Un en-tête SHTP porte son canal — 0 à 5 — et un numéro de séquence : les afficher tranche en un flash. Les six premiers paquets, puis un sur dix mille. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le banc rendait 23000 paquets par seconde à la pile et zéro événement. Le vidage des en-têtes a tranché : du SHTP parfaitement valide, canal 0, charge utile 01 03 03 03 — le rapport de liste d'erreurs du composant, qui s'allongeait. Et la séquence ne passait que de 5 à 13 entre le paquet 6 et le paquet 10000 : le même paquet relu un millier de fois, jamais consommé. La cause est le motif de transfert. Le portage lisait l'en-tête puis le corps en gardant CS bas entre les deux. Le composant n'y voit pas une lecture en deux temps : il y voit une lecture suivie d'une ÉCRITURE, refuse le paquet malformé, empile une erreur, et ne consomme jamais ce qui attend. Le protocole réel, tel que l'implémente le pilote Adafruit qui fonctionne : une transaction complète pour l'en-tête, CS remonte, on attend que H_INTN se réabaisse, puis une seconde transaction complète qui relit le paquet ENTIER, en-tête compris. Quatre octets de plus par paquet, et un composant qui répond. Le bandeau du fichier décrit désormais ce motif en toutes lettres, avec le symptôme, pour que personne ne refasse l'optimisation qui paraît évidente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le SPI est full-duplex : il n'existe pas de transaction de lecture pour le BNO08x, seulement des echanges. Ce que le maitre sort sur MOSI pendant qu'il rentre un paquet, le composant le lit comme un en-tete SHTP venant de l'hote. ESP-IDF, quand tx_buffer vaut NULL, ne garnit pas la FIFO d'emission : MOSI rejoue le contenu precedent. Le composant voyait donc un paquet malforme a chaque lecture, en empilait une erreur sur le canal 0, ce qui maintenait H_INTN bas, ce qui relancait une lecture, donc une erreur de plus. Cela explique l'asymetrie observee au banc — l'annonce SHTP de 276 octets lue sans une faute, et pas un seul rapport capteur — que ni le cablage ni la sequence de reset n'expliquaient. Le pilote de reference Adafruit passe explicitement sendvalue = 0x00 a chaque lecture. Le tampon de zeros ajoute ici est le meme geste. Trois autres corrections dans le meme televersement : - l'en-tete est lu dans s_staging, aligne 32 bits, et non dans un tableau local : le DMA d'ESP-IDF exige une destination alignee et arrondit la longueur au multiple de 4 superieur ; - hal_write ne touche plus a PS0/WAKE, comme le pilote Adafruit : le composant n'est jamais mis en veille par ce firmware, et PS0 est aussi la broche de selection de protocole ; - l'horloge SPI passe de 3 MHz a 1 MHz, la valeur du pilote Adafruit. A 100 Hz la marge ne coute rien. Le portage decode desormais les listes d'erreurs du canal 0 et les journalise : la pile SH-2 vendue ne s'abonne pas a ce canal et les jetait en silence. Le compteur err_shtp apparait dans le diagnostic. Corrige aussi deux champs de compteur absents de l'en-tete (repeat_timeouts, oversize) qui empechaient la compilation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Apres le reset, le concentrateur dort (datasheet section 5.2.2) : il n'assertera H_INTN que si l'hote le lui demande, en tirant H_WAKEN — la broche PS0, repurposee apres le reset — a la masse. Attendre H_INTN sans reveiller, c'est attendre une fenetre qui ne s'ouvre que par hasard, quand le composant avait de lui-meme quelque chose a dire. Ce reveil avait ete retire au commit precedent, a tort. L'explication n'est venue qu'apres : tant que le defaut MOSI etait present, le composant avait toujours une erreur a signaler, donc H_INTN restait bas en permanence, donc le reveil ne servait a rien — et son absence ne se voyait pas. Les deux defauts se masquaient l'un l'autre. Le message « ecriture : H_INTN jamais bas » n'est apparu qu'une fois MOSI corrige. Le semaphore d'interruption est vide AVANT de tirer PS0 bas : une assertion anterieure, deja consommee par une lecture, ferait croire a un reveil instantane et l'ecriture partirait dans le vide. H_WAKEN est relache APRES le transfert, comme le demande la datasheet. Le moniteur, lui, mesurait toujours 53 ms d'aller-retour parce que le correctif de son delai de lecture n'avait jamais ete televerse : c'est le delai de pyserial qui etait mesure, pas la liaison. 5 ms, et lecture de in_waiting octets plutot que d'un bloc de 4096. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Aller-retour median 3,14 ms, max 3,88 ms, sur 100 echantillons a 921600 bauds, mesure au banc avec tools/link_monitor.py une fois le moniteur corrige. La valeur de 1,5 ms qui figurait la etait un ordre de grandeur pose faute de mesure ; elle se trouvait juste, ce qui ne la rendait pas moins inventee. Point 5 de la recette du banc B1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Deux documents produits hors de ce depot et restes non suivis : le handoff de saisie du schema safety_power et le plan de conception de la feuille P2 « rails d'alimentation ». Ils decrivent l'etat fige au 16 septembre 2026 et les points GATE qui bloquent la saisie EasyEDA. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
docs/DEMARRAGE.md part d'une machine vierge et s'arrete quand /imu/data sort a 100 Hz dans Foxglove. Il manquait : tout ce qui a ete appris au banc vivait dans des en-tetes de fichiers source ou dans un historique de conversation, ce qui ne se retrouve pas quand on reprend le projet trois semaines plus tard, et pas du tout depuis une autre machine. Contenu : les trois roles d'outillage et pourquoi on ne les melange pas, le cablage du BNO085 avec le piege de la broche ADDR qui devient MOSI, l'installation d'ESP-IDF v5.5 sur macOS et Linux, la construction et le flash, la verification sans ROS avec link_monitor.py, l'installation de ROS 2 Jazzy et la construction de l'espace de travail, le lancement du banc et sa verification, les tests hote, et un tableau de depannage des pannes reellement rencontrees. Les deux README pointent dessus. Trois points qui ont chacun coute du temps y figurent explicitement : PS0 et PS1 hauts avant le reset sinon le composant demarre en I2C sans le dire ; les erreurs de format des premieres secondes qui ne sont que le journal du bootloader en clair a 115200 ; et l'orientation annoncee « nulle » qui est un indice de confiance, pas une panne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le firmware n'appelait jamais sh2_setCalConfig. Le BNO085 sait corriger
ses biais en fonctionnement — c'est son interet principal face a une
centrale brute — mais il ne le fait que si on le lui demande, et il ne
conserve le resultat d'une mise sous tension a l'autre que si c'est ecrit
en flash. Sans les deux, il sort ses valeurs d'usine.
Mesure au banc du 20 septembre 2026, trois orientations franches :
a plat (0,270 ; -0,230 ; -10,152) norme 10,158
sur un chant (0,382 ; -10,188 ; -0,304) norme 10,200
sur l'autre (-0,270 ; 9,460 ; -1,266) norme 9,548
Les normes ne sont pas egales, donc ce n'est pas un facteur d'echelle.
Un seul vecteur de biais b = (+0,27 ; -0,378 ; -0,34) m/s^2 recale les
deux premieres a 3 millemes de 9,81 et la troisieme — la position
penchee, la moins stable — a 0,9 %. Norme du biais : 0,58 m/s^2. La meme
omission explique le cap : le capteur annoncait lui-meme pi radians
d'incertitude, soit 180 degres, faute de magnetometre jamais etalonne.
Ce que ce commit ajoute :
- sh2_setCalConfig sur accel, gyro et mag au demarrage, plus
sh2_setDcdAutoSave(true). Reinstalle apres chaque reset du capteur :
un reset efface cette configuration comme il efface les rapports, et
l'oublier donnerait un capteur qui s'etalonne jusqu'au premier reset
puis plus jamais, sans que rien ne le signale ;
- deux trames, IMU_CAL (0x215, 10 Hz) qui dit ce qui est actif et
combien de fois le DCD a ete ecrit, et IMU_CAL_CMD (0x321) qui permet
d'activer, figer, sauvegarder ou effacer depuis le banc ;
- tools/imu_cal.py, qui envoie ces commandes et affiche l'etat en
continu pendant les manipulations.
Deux points de conception qui ne sautent pas aux yeux :
- la commande arrive dans la tache de reception de la liaison mais
n'est qu'y deposee ; elle s'execute dans la tache de l'IMU. La pile
SH-2 n'est pas reentrante, et l'appeler depuis deux taches corromprait
l'etat SHTP au milieu d'un transfert — symptome : un capteur muet, sans
erreur ;
- SAVE utilise sh2_saveDcdNow et non sh2_saveDcdAndReset, qui
redemarre le capteur et coupe le flux une demi-seconde.
IMU_CAL_CMD porte un garde 0xCA, sur le modele de CONFIG. Effacer un DCD
coute dix minutes de manipulations a refaire ; ce n'est pas une chose
qu'une trame corrompue doit pouvoir declencher.
Le hash du protocole passe de 0xC1214F10 a 0xE8391C47 : firmware ET
espace de travail ROS sont a reconstruire tous les deux.
30 610 verifications de cadrage, 30 trames coherentes entre C, C++ et
Python, generation idempotente.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le dossier installe s'appelle esp-idf-v5.5, pas esp-idf : la version est dans le nom pour que deux versions puissent coexister, ce qui arrive des qu'un projet tiers en exige une autre. Le guide donnait ~/esp/esp-idf/export.sh, qui echoue silencieusement — le shell ne trouve pas le fichier, et l'erreur suivante est un idf.py introuvable, plusieurs commandes plus loin. Ajoute aussi l'alias recommande par Espressif plutot qu'une ligne dans le .zshrc, qui ralentit l'ouverture de chaque terminal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le composant retriever_imu ne dependait que de driver et esp_timer, et c'est voulu : il parle rt_imu_sample_t, et c'est main.c qui traduit vers le bus. Le commit precedent utilisait pourtant l'enumeration generee rt_imu_cal_action_e directement dans imu_bno085.c — la compilation le refuse, et elle a raison : lui faire inclure l'en-tete du protocole ferait du pilote de capteur un client du bus. Le composant a donc maintenant sa propre enumeration, aux memes valeurs. Une duplication de ce genre derive tot ou tard, alors elle n'est pas laissee a la vigilance : main.c, seul endroit qui voit les deux en-tetes, verifie leur accord par _Static_assert. Une divergence casse la compilation au lieu de passer inapercue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
…auve Le commit qui a ajoute IMU_CAL au firmware n'a rien donne au noeud ROS pour la lire : la trame arrivait toutes les 100 ms et tombait dans unknown_frames. Le hash du protocole, lui, concordait — les deux cotes ont bien la meme definition — ce qui montre exactement ce que ce compteur attrape et que le hash ne peut pas voir : une trame connue des deux cotes mais traitee d'aucun. ImuStatus gagne cal_enabled, cal_saves, cal_autosave et cal_last_result. Les deux premiers disent des choses differentes et les confondre coute une seance de banc : l'un dit que le capteur corrige ses biais EN CE MOMENT, l'autre que la correction a ete ecrite en flash et survivra a la mise hors tension. C'est aussi IMU_CAL, et non IMU_MAG, qui fait desormais autorite sur la qualite du magnetometre cote ROS : elle arrive meme quand le magnetometre est desactive, et c'est precisement dans ce cas qu'on veut savoir pourquoi le cap est mauvais. Deux avertissements de diagnostic, parce qu'aucun des deux etats ne ressemble a une panne dans les donnees : - etalonnage dynamique inactif — les biais ne sont pas corriges - etalonnage non sauvegarde — il sera perdu a l'extinction Le hash du protocole ne change pas : 0xE8391C47, deja publie par le commit precedent. Seul l'espace de travail ROS est a reconstruire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Le capteur annonce la fin de son initialisation par un evenement SH2_RESET, a chaque demarrage, sans exception. Le firmware le comptait comme les autres, donc sensor_resets valait 1 en permanence — et le diagnostic ROS passe en ERREUR des qu'un reset est signale. L'IMU apparaissait donc au rouge des le boot, sur une machine parfaitement saine. Un voyant toujours rouge ne dit plus rien. C'est pire que pas de voyant : le jour ou le capteur se reinitialise vraiment — alimentation instable, cablage SPI marginal —, personne ne le remarquera. Le drapeau s_running separe la sequence de demarrage du fonctionnement normal. Pendant rt_imu_init(), un reset est attendu et seulement journalise ; apres, c'est un incident, compte et publie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
La premiere version affichait « en attente d'une trame IMU_CAL » aussi bien devant un port muet que devant un flux valide dont elle ratait la trame. Les deux cas demandent des actions opposees et l'outil ne les distinguait pas — il a fallu lancer link_monitor a cote pour savoir lequel des deux on avait. Il compte desormais les octets recus, les trames valides, les erreurs de CRC et de format, et liste les identifiants effectivement vus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Sur une DevKitC, DTR et RTS ne sont pas decoratifs : ils pilotent EN (reset) et IO0 (mode de demarrage) a travers le circuit d'auto-reset du montage. Le noyau les assertit a l'ouverture du port et les abaisse a la fermeture (HUPCL), et pyserial fait pareil. La carte redemarre donc a chaque ouverture — et selon l'ordre des transitions elle peut rester dans le bootloader ROM, muette, sans la moindre erreur cote hote. C'est ce qu'on observait depuis ce matin, sur trois programmes differents et sans jamais le nommer : la liaison marchait apres un rebranchement physique, puis plus rien des qu'un second programme rouvrait le port. On a mis ca sur le compte du magnetometre, puis d'un bug de decodage, puis de l'alimentation USB. C'etait le port lui-meme. Le transport C++ enleve HUPCL et desassertit DTR et RTS par TIOCMBIC apres configuration ; si le pilote ne l'implemente pas, il le dit et continue plutot que de refuser un port qui marche peut-etre. Les outils Python passent par retriever_wire.open_port(), qui fait la meme chose et demande en plus un verrou exclusif : deux programmes lisant le meme port se partagent les octets et chacun signale des erreurs de format sur un flux tronque. Mieux vaut un refus net. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Une mise en page Foxglove vit dans le stockage local de l'application : elle n'est ni partagee entre machines, ni sauvegardee, et sa suppression est definitive — il n'y a pas d'annulation. Six panneaux reconstruits a la main apres chaque mise a jour, changement de poste ou fausse manoeuvre, c'est exactement le travail qu'un fichier versionne supprime. docs/foxglove/bench_imu.json contient les six panneaux de la recette B1 : vue 3D sur imu_world, les deux traces de /imu/data, les diagnostics, et les deux etats bruts imu_status et link_status. Il s'importe par le menu des mises en page, « Import from file… ». Le README qui l'accompagne dit dans quel ordre les lire, et rappelle que le panneau 3D ne montre rien sans bench.publish_tf — une aide de banc, a desactiver le jour ou l'EKF publiera cette meme arete. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Relacher DTR et RTS ne suffisait pas. Une carte deja partie dans le
bootloader ROM y reste jusqu'a une coupure d'alimentation : elle y
attend un televersement, a 115200, et reste donc muette pour un hote qui
lit a 921600 — sans qu'aucune erreur ne soit levee nulle part.
Le banc l'a montre : apres une impulsion de reset explicite lancee a la
main, la donnee revenait ; en relancant le noeud sans toucher au cable,
le diagnostic repassait de « aucune trame recue » a « taux d'erreur
superieur a 1 % », signature d'octets recus a un debit qui n'est pas le
bon.
Le transport execute donc la sequence d'esptool, a l'envers du mode
telechargement, et l'ordre est impose par le materiel :
IO0 haut (DTR relache) → « demarre l'application »
EN bas (RTS asserte) → reset
EN haut (RTS relache) → demarrage
Suivie d'une attente de 400 ms et d'une purge : ce que les deux
chargeurs emettent sort en clair a 115200 et ressemble a du bruit a
921600. Ca supprime au passage la bordee d'erreurs de cadrage qu'on
voyait a chaque demarrage du noeud.
Le parametre serial.reset_on_open permet de desactiver la sequence.
Redemarrer un noeud de securite parce que le calculateur ouvre un port
est une mauvaise idee sur un robot ; sur le banc c'est au contraire ce
qui garantit un etat connu. Sur CAN, la question disparait.
retriever_wire.open_port() fait la meme chose pour link_monitor.py et
imu_cal.py.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Deux lignes de depannage, parce que ce defaut a coute une demi-journee et qu'il ne ressemble a rien de ce qu'on cherche : la carte demarre en mode telechargement, attend un televersement a 115200, et reste muette pour un hote qui lit a 921600 — sans qu'aucune erreur ne soit levee nulle part. La deuxieme ligne note que le silence complet et le bruit illisible sont deux etats du meme probleme, pas deux pannes differentes. On a perdu du temps a les traiter separement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
docs/HANDOFF-banc-imu.md existe pour qu'on puisse reprendre ce travail dans trois semaines sans relire vingt-cinq commits : ce qui marche et avec quels chiffres, ce qui a ete casse et par quel mecanisme, ce qui reste et comment le faire. La partie qui a de la valeur est la deuxieme. Chacun des defauts trouves etait silencieux et aucun ne ressemblait a sa cause : le SPI full-duplex ou MOSI rejoue la transaction precedente, le reveil PS0 dont l'absence etait masquee par ce premier defaut, l'etalonnage jamais active qui donnait un biais de 0,58 m/s2 sans rien signaler, et le bootloader ROM ou la carte attend un televersement a 115200 en restant muette pour un hote qui lit a 921600. La troisieme partie note que tous les points restants — etalonnage, covariances mesurees, endurance, caracterisation magnetique — demandent la carte montee sur le robot. C'est une decision, pas un oubli : un etalonnage magnetique fait sur un banc serait a refaire une fois le capteur pres des moteurs. Les deux README y renvoient. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Rien n'est branche encore — tout ce qui peut etre ecrit avant de brancher l'est, pour que la seance de banc porte sur le capteur et non sur la configuration. retriever.repos epingle le pilote tiers sur la branche humble du depot officiel : master y est marquee « old version », et il n'existe pas de release binaire Jazzy. Meme discipline que le sous-module sh2 du firmware. YDLidar-SDK n'y figure pas : c'est une bibliotheque CMake, pas un paquet ROS, et le pilote la cherche par find_package. Les parametres sont ceux du X4 CLASSIQUE, releves dans la table par modele du pilote officiel : 128000 bauds, echantillonnage 5 kHz, 0,12-10 m, isSingleChannel false, moteur pilote par DTR. Le X4 Pro est mono-canal, ne pilote pas le moteur par DTR et porte a 12 m ; confondre les deux donne un lidar qui tourne sans jamais publier. Trois choix qui meritent d'etre dits : - frequence a 7 Hz et non 10. C'est la valeur du paragraphe 03, et la frequence s'obtient en pilotant le moteur, donc en consommant. Tant que USB_PWR n'a pas son alimentation 5 V dediee, inutile de demander au port USB du PC plus que le necessaire ; - invalid_range_is_inf a true. REP-117 : une mesure hors portee se dit +inf, pas 0.0. A false le pilote sort des zeros, sous range_min, que chaque consommateur doit filtrer — et celui qui oublie voit un obstacle colle au capteur ; - le nom du noeud est force dans le fichier de lancement, parce qu'il doit rester identique a la cle de premier niveau du YAML. La verification d'orientation est une mesure physique, pas une lecture : reversion et inverted sont exactement ce qui retourne ou miroite le scan. La procedure est dans le guide. La transformation base_link vers laser est une aide de banc, signalee comme telle : sur le robot c'est l'URDF qui decrit le montage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Une apres-midi d'hypotheses successives — alimentation, DTR, resolution fixe, frequence, intensite — chacune plausible, chacune fausse ou confondue avec une autre. Ce qui a fini par marcher : reproduire a l'identique tri_test, le programme d'exemple du SDK qui recevait des scans, puis s'en ecarter un parametre a la fois. Trois ecarts avec la documentation, tous mesures sur l'exemplaire du banc (serie 2021090300040777, firmware 1.10) : - isSingleChannel true, alors que la table du constructeur dit false pour le X4. Cet exemplaire livre ses informations produit comme un double canal mais ne delivre ses scans qu'en mono-canal ; - intensity_bit 10, qui n'est pas une largeur mais « cherche ». C'est ce qui declenche le sondage 16 → 8 → 0 et la resynchronisation du flux. A 0, le SDK attend des trames qui n'arrivent jamais ; - sample_rate 5, la ou le pilote ROS met 9 par defaut. Et une retractation. Le commit precedent affirmait support_motor_dtr false comme un fait etabli, parce que le moteur ne demarrait pas avec true. Mais isSingleChannel etait encore a false a ce moment-la : les deux avaient change ensemble et le resultat a ete attribue au mauvais. tri_test tourne avec true, le moteur va bien. C'est precisement l'erreur que la regle « un parametre a la fois » existe pour eviter, et elle a ete enfreinte deux fois dans la meme journee. Chaque valeur du fichier porte desormais sa provenance : prouvee dans la configuration qui marche, ecart volontaire inoffensif, ou a trancher par une mesure physique. reversion et inverted sont dans la troisieme categorie : ils ne changent rien a l'acquisition, ils retournent ou miroitent le scan, et seul un objet a un metre droit devant peut dire lesquels sont justes. Le tableau de depannage du guide est refait dans le meme esprit, avec la regle qui a debloque la journee en derniere ligne : quand on ne sait plus quel parametre a change, revenir a un temoin qui marche. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Mesure au banc : avec invalid_range_is_inf a true, les mesures sans echo sortent quand meme a 0.0 — 221 zeros sur 667 points, aucun +inf. Le reglage promet REP-117 et ne le tient pas. Deux consequences ecrites la ou on les cherchera : - tout consommateur de /scan doit filtrer sous range_min lui-meme. slam_toolbox et Nav2 le font ; un noeud maison qui l'oublierait verrait 221 obstacles colles au capteur ; - le taux de couverture se compte sous range_min, pas par isfinite(), qui donne 100 % et ne mesure rien. Le guide avait le mauvais critere. Releve du banc : 67 % de couverture, 0,31 a 5,19 m. Au-dessus du seuil de 60 % du paragraphe 03. Le tiers manquant est l'environnement, pas le capteur. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
…ncer Trois documents pour trois usages. docs/HANDOFF-banc-lidar.md, pendant du handoff IMU : les chiffres du banc (667 points par tour, ~7,5 Hz libre, 67 % de couverture, 0,31 a 5,19 m, orientation validee), les six defauts avec leur mecanisme, et ce qui reste — alimentation USB_PWR, diagnostics sur /scan, URDF, ignore_array, remplacement en phase 4. La section 2.1 consigne la methode qui a debloque la journee, parce qu'elle vaut plus que chaque correction prise separement : quand plus rien ne marche, revenir a un temoin qui marche et s'en ecarter un parametre a la fois. docs/DEMARRAGE.md gagne une fiche de lancement en tete — les deux commandes, IMU et lidar, la regle du seul pont Foxglove, et le piege du setup.bash regenere a chaque build qui fait croire a un paquet absent. C'etait la question posee : « la commande pour lancer », et pour l'IMU aussi. Elle etait dans le guide, mais en section 4, pas la ou on la cherche. Le dossier d'architecture recoit au paragraphe AH les corrections que le banc impose au texte — sept lignes, chacune une mesure qui contredit ce qui etait ecrit, pas une preference — et l'etat des mesures L1 a L6. La feuille de route AH.4 marque B1 et B1' faits. L'orientation du lidar, validee physiquement, passe de « a trancher » a « validee » dans la configuration, avec la consigne de refaire le test si la carte adaptatrice change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
…Foxglove Protocole : MOTOR_CMD 0x110 (quatre consignes i16 ×0,001, 50 Hz), MOTOR_ENABLE 0x111 (magic 0xEB), MOTOR_STATE 0x1A0 (appliqué, masque, drapeaux, âge), enum motor_flag ; HEARTBEAT_MOTION_FRONT passe en bench. Hash 0x670192A7. Firmware esp32_motion : table de quatre moteurs dans board_config.h (PWM 20 kHz + RC vers VR, DIR/STOP open-drain), CONFIG_RETRIEVER_MOTOR_COUNT=3 aujourd'hui, 4 quand m3 est câblé. Tâche 200 Hz, chien de garde 500 ms, pente à l'accélération, rien ne tourne sans MOTOR_ENABLE. ROS : messages MotorCommand/MotorEnable/MotorState ; le pont prend link.peer, imu.enabled et motors.* — un seul exécutable pour les deux bancs. Republication à 50 Hz avec délai côté PC, ESTOP sur /retriever/estop, diagnostics Moteurs. bench_motors.launch.py + motors_bench.yaml, mise en page Foxglove, tools/motor_bench.py sans ROS, DEMARRAGE §4 quater, HANDOFF-banc-moteurs. firmware/test/Makefile : dépendance explicite sur l'en-tête généré (un binaire périmé donnait 22 faux échecs croisés). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iSUBG2bteBnbqvcotEM9D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.