Tutorial · Einsteiger · 18 Min.

Einen Ultraschallsensor per Servo scannen, um Freiraum zu finden

Einen HC-SR04 auf ein SG90-Servo montieren und schwenken, um unter mehreren Winkeln zu messen – damit dein Roboter zur offensten Richtung dreht statt zu raten.

Ein einzelner geradeaus schauender Entfernungssensor kann einem Roboter sagen, dass etwas voraus ist, aber nicht, wohin frei ist. Also dreht er blind – oft gerade in ein weiteres Hindernis oder eine Sackgasse. Den HC-SR04 auf ein SG90-Servo zu montieren behebt das: Der Roboter stoppt, schaut links und rechts und dreht dorthin, wo es am offensten ist – derselbe Trick, den ein Mensch an einer blinden Ecke tut.

Warum ein fester Sensor den Roboter fängt

Man stelle sich einen Roboter vor, der nur geradeaus sieht. Er trifft eine Wand, weiß, dass er drehen muss, hat aber keine Ahnung, ob links oder rechts besser ist. Falsch gewählt und er fährt in die Ecke, aus der er gerade kam. Ein servomontierter Sensor verwandelt eine Zahl (Entfernung voraus) in eine kleine Karte des Raums vor dem Roboter – die Drehung wird also eine informierte Wahl, kein Münzwurf.

Den Sensor aufs Servo montieren

Den HC-SR04 ans Servohorn schrauben und das Servo vorne-mittig aufs Chassis setzen – ausgerichtet, sodass das Horn den Sensor links-rechts schwenkt (nicht hoch-runter). In Servomittenstellung sollte der Sensor genau geradeaus zeigen. Das ergibt einen sauberen Schwenk von einer Seite über geradeaus zur anderen.

Schwenken und abtasten

Das Servo über eine Handvoll Winkel schreiten und an jedem eine gefilterte Messung nehmen. Die saubere Entfernungsfunktion aus den HC-SR04 auslesen wiederverwenden – ein roher Ping mitten im Schwenk ist viel zu verrauscht, um danach zu lenken.

Radarartige Draufsicht eines Roboters unten, der einen Ultraschallsensor über fünf Winkel von 30 bis 150 Grad schwenkt. Schwache Entfernungskreise markieren 20, 40 und 60 cm. Vier Strahlen sind kurz und enden in Hindernispunkten (22, 34, 18, 41 cm); ein Strahl bei 120 Grad ist lang (68 cm), cyanfarben hervorgehoben und beschriftet ‚am offensten, hier drehen‘.
Der Schwenk misst Entfernung an jedem Winkel; die längste Messung ist die offenste Peilung – der Roboter dreht also dorthin statt zu raten. SVG herunterladen
#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
}

Nicht mitten in der Bewegung lesen

Der häufigste einzelne Fehler hier ist Lesen, bevor das Servo ankommt. servo.write() kehrt instant zurück – doch das SG90 braucht Zeit, um auf den neuen Winkel zu schwenken. Zu früh gepingt und die Entfernung gehört dorthin, wo der Sensor war, nicht wohin man ihn zeigen ließ. Ein kurzes delay nach jedem write() – genug, damit sich das Servo setzt –, macht jede Messung vertrauenswürdig.

Den Scan in eine Entscheidung verwandeln

Der Winkel mit der größten Entfernung ist die offenste Richtung. Ein paar Verfeinerungen halten sie vernünftig:

  • Bei Gleichstand geradeaus bevorzugen. Lesen zwei Richtungen ähnlich, zur Mitte neigen, damit der Roboter nicht unnötig webt.
  • Alle Richtungen nah? Das ist eine Sackgasse – zurücksetzen und erneut scannen statt weiter hineinzuschnüffeln.

Tempo gegen Abdeckung

Mehr Winkel liefern ein feineres Bild, aber eine langsamere Schleife – und eine langsame Schleife heißt: Der Roboter reagiert spät. Drei bis fünf Winkel – links, voraus, rechts, plus ein paar dazwischen – sind der übliche Sweet Spot. Diese „am offensten“-Entscheidung an den Hindernisvermeidungsalgorithmus übergeben und das Gesamtverhalten erst im Hindernisvermeidungssimulator ausprobieren, bevor man es baut.

Wie lange braucht das Servo wirklich?

„Ein kurzes Delay“ ist die vageste Anweisung im Code oben – und lohnt Ersetzen durch eine gemessene Zahl. Ein SG90 ist mit grob 0,1 s pro 60° bei 4,8 V spezifiziert, unbelastet – die Beruhigungszeit hängt also davon ab, wie weit es fährt:

Schwenkschritt Fahrzeit (Angabe) Plus Beruhigung Nehmen
30° 50 ms ~30 ms 80 ms
60° 100 ms ~40 ms 140 ms
90° 150 ms ~40 ms 190 ms
180° (Zurückstellen von einem Ende) 300 ms ~50 ms 350 ms

Zwei Dinge machen die echte Zahl schlechter als die Angabe. Last: Ein HC-SR04 mit Kabel am Horn ist klein, aber nicht null – und verlangsamt das Servo und fügt Überschwingen hinzu. Spannung: Bei 4,0 V aus sackendem Pack ist dasselbe Servo merklich langsamer als bei 6 V.

Eigenes messen statt der Tabelle trauen. Einen 60°-Schritt befehlen und stoppen, wie lange bis die Entfernungsmessung aufhört, sich zu ändern:

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);
  }
}

Die Messung setzt sich in einem bestimmten Moment. Diese Zahl plus kleine Marge verwenden – und die größte Unsinnsquelle im Gesamtverhalten ist beseitigt.

In eine Richtung schwenken ist schneller als herumspringen

Der Code oben schreitet 30 → 60 → 90 → 120 → 150 – bereits ein einzelner Schwenk, und das ist Absicht. Winkel in gestreuter Reihenfolge zu lesen heißt: Jeder Schritt ist ein großer – und die Beruhigungskosten fallen pro Schritt an:

Muster Gesamtfahrt Beruhigungskosten bei ~1,7 ms/Grad
Der Reihe nach schwenken (30→150) 120° ~200 ms plus fünf kurze Beruhigungen
Mitte-außen (90, 60, 120, 30, 150) 300° ~510 ms
Zwischen jedem zur Mitte zurück 480° ~820 ms

Ein vollständiger Scan ist ein blinder Moment für den Roboter – er steht und kommt nicht voran: Seine Dauer zu halbieren ist also eine echte Verbesserung, wie sich der Roboter anfühlt. Der Reihe nach schwenken – und bei wiederholtem Scannen Richtung abwechseln, damit man die Rückfahrt nie bezahlt.

Nicht blockierend scannen

Das delay(180) in jedem Schritt heißt: Ein Fünf-Winkel-Scan blockiert fast eine Sekunde. An einem Roboter, der ohnehin steht, erträglich – heißt aber auch: Bumper bleiben ungelesen und jedes andere Verhalten friert ein.

Dieselbe Zustandsmaschinenbehandlung wie für den Ping gilt hier:

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;
  }
}

Die Hauptschleife ruft updateScan() auf und fährt fort. Bumper werden weiter gelesen, die Zustandsmaschine läuft weiter, und der Scan beendet sich, wenn er sich beendet.

Zwischen Winkeln interpolieren

Fünf Messwerte liefern fünf Peilungen – doch die wahre Öffnung liegt selten exakt auf einer davon. Ein gewichteter Schwerpunkt über die offenen Messwerte liefert eine Peilung zwischen den Messwerten – so wie ein Reflexionsarray eine Linienposition zwischen Sensoren liefert:

// 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
}

Das ist eine echte Verbesserung gegenüber „das Maximum nehmen“. Das Maximum zu nehmen heißt: Eine leicht außermittig abgetastete Türöffnung schickt den Roboter an die Türkante; der Schwerpunkt schickt ihn in die Türmitte. Es degradiert außerdem graceful: Sind zwei Nachbarwinkel beide offen, liegt das Ergebnis dazwischen statt willkürlich auf einem davon.

Der Kegel begrenzt, was ein Scan sagen kann

Der Strahl eines HC-SR04 hat grob 15° Halbwinkel – was bei 1 m ein Kegel von etwa 53 cm Durchmesser ist. Bei 30°-Abstand abzutasten überlappt also stark – Nachbarmessungen schauen großteils in denselben Raum.

Abtastabstand Bei 1 m Folge
15° Kegel überlappen fast vollständig Vergeudete Zeit; keine neue Information
30° Erhebliche Überlappung Der praktische Sweet Spot
45° Leichte Lücken zwischen Kegeln Akzeptabel; kann ein schmales Objekt verfehlen
60° Echte Lücken Ein Stuhlbein kann ungesehen zwischen zwei Messwerten sitzen

Die wichtige Folge: Ein Scan liefert keine Winkelauflösung besser als der Strahl. Eine Messung von 68 cm bei 120° heißt „etwas ist 68 cm entfernt, irgendwo innerhalb etwa 15° um 120°“ – in Ordnung, um eine Drehrichtung zu wählen, und nutzlos, um die Breite einer Lücke zu messen. Wer Letzteres braucht: Ein VL53L0X auf demselben Servo hat einen effektiv engen Fleck und liefert einen wirklich aufgelösten Scan.

Fehlersuche

Symptom Wahrscheinliche Ursache Lösung
Roboter dreht selbstbewusst in eine Wand Vor Beruhigung des Servos gelesen Beruhigungszeit messen und verwenden
Messwerte verschmieren über Winkel Dasselbe, weniger stark Beruhigungsverzögerung erhöhen
Scan dauert über eine Sekunde Blockierende Delays oder gestreute Winkelreihenfolge Nicht blockierende Zustandsmaschine; der Reihe nach schwenken
Board setzt während eines Scans zurück Servostrom durch den Arduino Separate 5-V-Versorgung, gemeinsame Masse
Roboter steuert auf Türkante zu Maximalwinkel gewählt Gewichteten Schwerpunkt verwenden
Motordrehzahlsteuerung arbeitet nicht mehr Die Servo-Bibliothek belegt Timer 1 (Pins 9, 10) Motor-PWM auf 3, 5, 6 oder 11 verlegen
Servo zittert zwischen Messungen Versorgungsrauschen oder softwaregetaktete Impulse Kondensator am Servo; PCA9685, wenn es bleibt
Ein Stuhlbein bleibt unerkannt Abtastabstand breiter als der Strahl 30°-Abstand oder Engstrahlsensor
Scan in Ordnung, Roboter bleibt trotzdem stecken Reaktive Navigation hat kein Gedächtnis Erwartet – Feststeckdetektor-Ausbruch dazu

Den Graphen erkunden

Teil dieser Bauprojekte

Projekte und Lernpfade, die dieses Tutorial enthalten.

Weiterführende Literatur

Quellen