Tutoriel · Débutant · 18 min de lecture

Balayer un capteur ultrasons au servo pour trouver l'espace libre

Montez un HC-SR04 sur un servo SG90 et balayez-le pour mesurer la distance sous plusieurs angles, puis tournez vers la direction la plus dégagée sans deviner.

Un unique capteur de distance face avant peut dire à un robot que quelque chose est devant, mais pas quel côté est libre. Donc il tourne aveuglément, souvent droit dans un autre obstacle ou une impasse. Monter le HC-SR04 sur un servo SG90 corrige cela : le robot s’arrête, regarde à gauche et à droite, et tourne vers la direction la plus dégagée — la même astuce qu’une personne à un coin aveugle.

Pourquoi un capteur fixe piège le robot

Imaginez un robot qui ne voit que droit devant. Il rencontre un mur, sait qu’il doit tourner, mais n’a aucune idée si gauche ou droite vaut mieux. Choisissez mal et il fonce dans le coin d’où il vient. Un capteur monté sur servo transforme un nombre (distance devant) en une petite carte de l’espace autour de l’avant du robot, pour que le virage soit un choix informé, pas un pile ou face.

Monter le capteur sur le servo

Fixez le HC-SR04 au palonnier du servo et montez le servo à l’avant-centre du châssis, orienté pour que le palonnier balaie le capteur de gauche à droite (pas de haut en bas). À la position centrale du servo, le capteur doit regarder pile devant. Cela donne un balayage propre d’un côté, en passant par tout droit, jusqu’à l’autre côté.

Balayez et échantillonnez

Faites avancer le servo par pas sur une poignée d’angles et prenez une mesure filtrée à chacun. Réutilisez la fonction de distance propre de la lecture du HC-SR04 — un ping brut en plein balayage est bien trop bruyant pour braquer dessus.

Vue de dessus style radar d'un robot en bas balayant un capteur ultrasons sur cinq angles de 30 à 150 degrés. De faibles arcs de portée marquent 20, 40 et 60 cm. Quatre rayons sont courts et finissent en points d'obstacles (22, 34, 18, 41 cm) ; un rayon à 120 degrés est long (68 cm), en surbrillance cyan et légendé 'le plus dégagé, tournez ici'.
Le balayage mesure la distance à chaque angle ; la lecture la plus longue est le relèvement le plus dégagé, donc le robot tourne par là au lieu de deviner. Télécharger le SVG
#include <Servo.h>
Servo scanner;

const int ANGLES[] = {30, 60, 90, 120, 150};
const int N = 5;

int bestAngle() {
  long best = -1;
  int bestA = 90;
  for (int i = 0; i < N; i++) {
    scanner.write(ANGLES[i]);
    delay(180);                 // let the servo REACH the angle first
    long d = medianCm();        // filtered read from the previous tutorial
    if (d > best) { best = d; bestA = ANGLES[i]; }
  }
  scanner.write(90);            // re-centre for driving
  return bestA;                 // > 90 = open to the left, < 90 = right
}

Ne lisez pas en plein déplacement

Le bogue le plus courant ici est de lire avant que le servo arrive. servo.write() revient aussitôt, mais le SG90 prend du temps pour pivoter au nouvel angle. Pingez trop tôt et la distance appartient à l’endroit où le capteur était, pas où vous lui avez demandé de pointer. Un court delay après chaque write() — assez pour que le servo se stabilise — rend chaque lecture fiable.

Transformer le balayage en décision

L’angle à la plus grande distance est la direction la plus dégagée. Quelques raffinements gardent cela sensé :

  • Préférez tout droit à égalité. Si deux directions lisent pareil, biaisez vers le centre pour que le robot ne louvoie pas inutilement.
  • Toutes directions proches ? C’est une impasse — reculez et rebalayez plutôt que de vous enfoncer.

Vitesse contre couverture

Plus d’angles donnent une image plus fine mais une boucle plus lente, et une boucle lente signifie que le robot réagit tard. Trois à cinq angles — gauche, devant, droite, plus quelques-uns entre — est le compromis usuel. Confiez cette décision de « le plus dégagé » à l’algorithme d’évitement d’obstacles, et essayez tout le comportement dans le simulateur d’évitement d’obstacles avant de le construire.

Combien de temps le servo prend-il vraiment ?

« Un court délai » est l’instruction la plus vague du code ci-dessus, et elle vaut d’être remplacée par un nombre mesuré. Un SG90 est spécifié à environ 0,1 s par 60° sous 4,8 V, à vide — donc le temps de stabilisation dépend de la distance parcourue :

Pas de balayage Temps de trajet (spécification) Plus stabilisation Utilisez
30° 50 ms ~30 ms 80 ms
60° 100 ms ~40 ms 140 ms
90° 150 ms ~40 ms 190 ms
180° (recentrage depuis un bout) 300 ms ~50 ms 350 ms

Deux choses rendent le chiffre réel pire que la spécification. La charge : un HC-SR04 et son câble sur le palonnier sont petits mais pas nuls, et cela ralentit le servo et ajoute du dépassement. La tension : à 4,0 V d’un pack affaissé, le même servo est nettement plus lent qu’à 6 V.

Mesurez le vôtre plutôt que de croire le tableau. Commandez un pas de 60°, et chronométrez jusqu’à ce que la mesure de distance cesse de changer :

void measureSettle() {
  scanner.write(60);  delay(500);            // known start
  scanner.write(120);                        // command the step
  const unsigned long t0 = millis();
  long prev = -1;
  for (int i = 0; i < 40; i++) {             // sample every 10 ms
    const long d = pingCm();
    Serial.print(millis() - t0); Serial.print(' '); Serial.println(d);
    delay(10);
  }
}

La lecture se stabilise à un moment défini. Utilisez ce nombre plus une petite marge, et vous avez retiré la plus grande source d’absurdités de tout le comportement.

Balayer dans un sens est plus vite que sauter partout

Le code ci-dessus avance 30 → 60 → 90 → 120 → 150, ce qui est déjà un balayage unique, et c’est délibéré. Lire les angles en ordre dispersé signifie que chaque pas est grand, et le coût de stabilisation est par pas :

Motif Déplacement total Coût de stabilisation à ~1,7 ms/degré
Balayage en ordre (30→150) 120° ~200 ms plus cinq courtes stabilisations
Centre-dehors (90, 60, 120, 30, 150) 300° ~510 ms
Retour au centre entre chacun 480° ~820 ms

Un balayage complet est un moment aveugle pour le robot — il est arrêté et ne progresse pas — donc diviser sa durée par deux est une vraie amélioration du ressenti du robot. Balayez en ordre, et si vous balayez à répétition, alternez le sens pour ne jamais payer le trajet de retour.

Balayage non bloquant

Le delay(180) de chaque pas signifie qu’un balayage à cinq angles bloque près d’une seconde. Sur un robot arrêté de toute façon c’est tolérable, mais cela signifie aussi que les pare-chocs ne sont plus lus et que tout autre comportement est gelé.

Le même traitement en machine à états que pour le ping s’applique ici :

enum ScanState { SCAN_IDLE, SCAN_MOVING, SCAN_READING };

ScanState scanState = SCAN_IDLE;
uint8_t   scanIndex = 0;
unsigned long scanSince = 0;
long      scanResult[N];
bool      scanComplete = false;

void updateScan() {
  switch (scanState) {
    case SCAN_IDLE:
      break;

    case SCAN_MOVING:
      if (millis() - scanSince < SETTLE_MS) return;      // measured, above
      scanState = SCAN_READING;
      break;

    case SCAN_READING:
      scanResult[scanIndex] = medianCm();
      if (++scanIndex >= N) { scanComplete = true; scanState = SCAN_IDLE; scanner.write(90); }
      else { scanner.write(ANGLES[scanIndex]); scanSince = millis(); scanState = SCAN_MOVING; }
      break;
  }
}

La boucle principale appelle updateScan() et continue. Les pare-chocs restent lus, la machine à états continue de tourner, et le balayage finit quand il finit.

Interpoler entre angles

Cinq lectures donnent cinq relèvements, mais la vraie ouverture est rarement exactement à l’un d’eux. Un barycentre pondéré sur les lectures dégagées donne un relèvement entre les échantillons, comme une barrette de réflectance donne une position de ligne entre capteurs :

// Weight each angle by how open it is, ignoring anything below the stop threshold.
float openBearing() {
  float weighted = 0, total = 0;
  for (int i = 0; i < N; i++) {
    if (scanResult[i] < STOP_CM) continue;             // blocked, contributes nothing
    const float w = scanResult[i] - STOP_CM;           // how much clearance beyond the threshold
    weighted += w * ANGLES[i];
    total    += w;
  }
  return (total > 0) ? (weighted / total) : 90.0f;     // 90 = straight ahead
}

C’est une vraie amélioration sur « prendre le maximum ». Prendre le maximum signifie qu’une porte échantillonnée légèrement décentrée envoie le robot au bord de la porte ; le barycentre l’envoie au milieu de la porte. Cela se dégrade aussi gracieusement — si deux angles adjacents sont tous deux dégagés, le résultat est entre eux plutôt qu’arbitrairement l’un d’eux.

Le cône limite ce qu’un balayage peut dire

Le faisceau d’un HC-SR04 a environ 15° de demi-angle, ce qui à 1 m fait un cône d’environ 53 cm de large. Échantillonner à intervalles de 30° chevauche donc lourdement — des lectures adjacentes regardent largement le même espace.

Espacement d’échantillons À 1 m Conséquence
15° Les cônes se recouvrent presque complètement Temps perdu ; aucune information nouvelle
30° Chevauchement substantiel Le compromis pratique
45° Légers vides entre cônes Acceptable ; peut rater un objet étroit
60° Vrais vides Un pied de chaise peut se tenir entre deux échantillons invisibles

La conséquence importante est qu’un balayage ne donne pas une résolution angulaire meilleure que le faisceau. Une lecture de 68 cm à 120° signifie « quelque chose est à 68 cm, quelque part à environ 15° de 120° » — ce qui suffit pour choisir un sens de virage, et ne sert à rien pour mesurer la largeur d’un passage. S’il faut ce dernier, un VL53L0X sur le même servo a un point effectivement étroit et donne un balayage vraiment résolu.

Dépannage

Symptôme Cause probable Correctif
Le robot tourne avec assurance dans un mur Lecture avant que le servo se stabilise Mesurez le temps de stabilisation et utilisez-le
Les lectures s’étalent sur les angles Idem, moins sévèrement Augmentez le délai de stabilisation
Le balayage prend plus d’une seconde Délais bloquants, ou ordre d’angles dispersé Machine à états non bloquante ; balayez en ordre
La carte redémarre pendant un balayage Courant du servo via l’Arduino Alimentation 5 V séparée, masse commune
Le robot vise le bord d’une porte Prise de l’angle maximal Utilisez le barycentre pondéré
La commande de vitesse moteur a cessé de marcher La bibliothèque Servo prend le Timer 1 (broches 9, 10) Passez la PWM moteur en 3, 5, 6 ou 11
Le servo tremble entre lectures Bruit d’alimentation, ou impulsions temporisées en logiciel Condensateur au servo ; un PCA9685 si cela persiste
Un pied de chaise passe indétecté Espacement d’échantillons plus large que le faisceau Espacement 30°, ou capteur à faisceau étroit
Le balayage va bien, le robot se coince quand même La navigation réactive n’a pas de mémoire Attendu — ajoutez une échappement à détecteur de blocage

Explorer le graphe

Fait partie de ces réalisations

Les projets et parcours d'apprentissage qui incluent ce tutoriel.

Pour aller plus loin

Références