Le banc de test sur PC

firmware/ratbot_firmware_card/test/ compile les vrais fichiers de la carte — pas une copie, pas une réécriture — avec du faux matériel, et les fait tourner sur le PC de développement.

cd firmware/ratbot_firmware_card/test
make
=== traction ===
  un tour complet rend bien 2*pi radians (1229 pas, pas 1024)
  en marche avant, la vitesse mesuree ne passe jamais negative
  ...
=== traction, XL430 ===
  XL430 : trois tours rendent 6*pi, sans secteur aveugle ni credit
  ...
  67 verifications, 0 echec(s)

Pourquoi il existe

L’estimateur de roue (Traction : estimateur de roue et odométrie) est du calcul pur : des comptes entrent, des radians sortent. Rien n’oblige à le déboguer sur le robot, où chaque essai coûte un flash, une batterie et un mètre ruban.

Surtout, ses défauts sont difficiles à provoquer à la demande : il faut que la roue s’arrête pile dans le secteur aveugle, ou qu’un fantôme apparaisse au bon moment. Sur le banc, on les écrit en trois lignes et ils se rejouent à chaque compilation.

Ce qu’il ne fait pas : il ne teste ni FreeRTOS, ni le DMA, ni le bus Dynamixel réel, ni micro-ROS. Il teste la logique. Pour le XL430 en particulier, l’échange réel sur le fil (délais, réception bourrée) reste à essayer avec les moteurs.

Comment il est bâti

Fichier

Rôle

faux/main.h, faux/cmsis_os.h

de quoi satisfaire les #include du firmware sans HAL ni FreeRTOS

faux_materiel.c

le bus Dynamixel simulé, HAL_GetTick(), la configuration, l’arrêt d’urgence

cadre.c

trois macros : CAS, VERIFIE, PROCHE. Rien de plus

test_traction.c

les cas, et le modèle du capteur AX-18A ; depuis le 2026-10-19, ceux du XL430 : odométrie sur trois tours, repli de la position à 2³¹, servo qui redémarre (remis en service), consigne écrêtée et jamais répétée, arrêt d’urgence, mise en service (EEPROM écrite une fois, couple coupé), modèle refusé

test_dxl_frame2.c

les trames du protocole 2.0 : les exemples du manuel ROBOTIS à l’octet près (CRC compris), les trames abîmées, et le bourrage dans les deux sens — une position de −32 tours s’écrit FF FF FD FF —, et la recherche de la réponse derrière l’écho, même décalée d’un parasite

test_tof.c

la tâche ToF avec un faux capteur : démarrage (8 × 8, 15 Hz, cible la plus proche), capteur absent puis branché, image convertie (colonnes retournées), panne puis redémarrage, image corrompue, température transmise, et la place laissée dans le diagnostic à ros_tof

test_config.c

la configuration en flash, avec une fausse flash : une version 4 (la calibration de ratbot-1) est relue et convertie, une v4 abîmée retombe sur les défauts

test_rearm_gesture.c

le geste de réarmement local — 5 appuis sur SW1 : fronts montants, fenêtre de 4 s, pas de second déclenchement, débordement de l’horloge

traction.c est inclus dans la compilation du test, ce qui donne accès à ses statiques. Les options y sont plus sévères que sur la carte — -Wextra et -Wconversion — parce que ces fichiers-là sont du calcul et qu’autant en profiter.

Warning

traction.c doit figurer dans les dépendances de la cible, et pas seulement dans la ligne de compilation. Sans cela, une modification du firmware ne reconstruit rien et le banc rejoue l’ancienne version.

C’est arrivé : il a annoncé « 0 échec » sur un correctif qu’il n’avait pas compilé.

Le modèle du capteur

capteur(angle_rad) reproduit l’AX-18A : 1024 pas sur 300 degrés, puis 60 degrés où le registre colle à 1023 en entrant et à 0 en sortant.

Deux détails qui ont chacun coûté une séance :

Le signe du servo. L’angle reçu est celui de la roue dans le repère du robot ; le servo tourne dans le sens que lui impose son montage. Le modèle applique donc wheel[0].sign, le même signe que le firmware. Sans cette ligne, le banc simulait encore les moteurs du banc d’essai et plus ceux du robot monté — cinq tests échouaient pour une mauvaise raison.

L’angle zéro est au milieu de la course visible, pas à son bord. Partir du bord mettait la roue simulée dans le secteur aveugle dès le premier échantillon, à vitesse nulle, et les scénarios de secteur aveugle passaient sans rien prouver — un robot à l’arrêt ne traverse jamais ce trou, il y reste.

Les cas

Cas

Ce qu’il empêche de revenir

un tour vaut 1229 pas

perdre un sixième de tour à chaque passage

la vitesse ne passe jamais négative en marche avant

la boucle qui pousse à fond en voyant une roue reculer

un fantôme ne crédite rien

le saut de −2 rad pris pour une sortie de trou

une roue débranchée puis rebranchée ne crédite rien

78 mm de déplacement fantôme à la reconnexion

l’intégrale s’arrête au plafond de sortie

la roue qui pousse 1,5 s après l’ordre de ralentir

une roue arrêtée dans le trou ne fait pas dériver l’odométrie

les 600 rad en cinq minutes, robot à l’arrêt

freinage actif puis coupure

les virages qui dépassent de 13 à 22 degrés

un fantôme au redémarrage n’est pas une sortie

les sauts de 3,4 rad au départ des virages

l’estime s’arrête avec la roue

107 plafonnements en un seul arrêt

remise à zéro roue garée dans le trou

les +6,9 degrés de cap du troisième carré UMBmark

chaque roue prend son propre rayon

qu’un des quatre usages du rayon reste sur l’autre roue

Chaque ligne de la colonne de droite est un défaut qui s’est réellement produit, avec sa mesure. C’est la règle du banc : on n’y ajoute pas de cas spéculatif, on y ajoute le cas qui vient de coûter une séance.

Vérifier qu’un test a des dents

Un test qui passe ne prouve rien tant qu’on n’a pas vu qu’il pouvait échouer. La pratique retenue est le test par mutation : on casse volontairement la ligne visée, on vérifie que le banc proteste, puis on restaure.

# Exemple : la roue droite emprunte le rayon de la gauche
sed -i 's|/ cfg->wheel_radius_right|/ cfg->wheel_radius_left|' ../App/traction.c
make          # doit afficher « 1 echec(s) »
git checkout ../App/traction.c
make          # doit repasser au vert

Sans cette vérification, un test peut être vide de sens et rassurer pour rien.

Ajouter un cas

static void cas_mon_scenario(void)
{
    CAS("ce que le cas demontre, en une phrase");
    roue_neuve();
    commande(4.6);                    /* consigne suivie, rad/s */

    lire(capteur(angle), 20u);        /* un pas de 20 ms        */

    PROCHE(wheel[0].position_rad, attendu, 0.01, "pourquoi c'est ca");
}

Puis l’appeler depuis main(). Les helpers disponibles :

  • roue_neuve() — remet la roue, le faux bus et les compteurs à zéro ;

  • commande(omega) — pose la consigne, que blind_estimate() consulte ;

  • lire(compte, dt_ms) — avance le temps, pose la réponse du capteur, appelle wheel_read_position() ;

  • capteur(angle_rad) — le modèle du capteur ;

  • faux_config_radii(gauche, droite) — les deux rayons de roue.

Voir aussi