Ratgeber · 17 Min. Lesezeit

Hindernisvermeidender Roboter mit Arduino: HC-SR04 und Servo

Hindernisvermeidender Roboter mit Arduino: HC-SR04 auf einem Servo, L298N oder L293D verdrahten, Stopp-Rückwärts-Drehen-Logik, IR-Sensoren und Bluetooth-Modus.

Ein hindernisvermeidender Roboter ist ein selbstfahrendes Roboterauto, das eine Wand, einen Stuhl oder eine Kiste vor sich erkennt, ohne dagegenzufahren, und sich einen neuen Weg sucht. Der verbreitetste Aufbau besteht aus einem Arduino Uno, einem HC-SR04-Ultraschallsensor, einem SG90-Servomotor, der den Sensor nach links und rechts dreht, zwei Getriebemotoren und einem L298N (oder L293D) als Motortreiber. Der Roboter fährt geradeaus, hält an, wenn er einem Hindernis nahekommt, setzt ein Stück zurück, dreht seinen Kopf zu beiden Seiten, misst und fährt dann in die freieste Richtung weiter.

In dieser Anleitung machen wir aus dem 2WD-Auto aus unserem Artikel Arduino-Roboterauto einen autonomen Roboter und behalten dabei die Pinbelegung bei. Schritt für Schritt geht es um die Anschlusstabellen, die Berechnung der Abstandsschwellen, einen Code, der in drei Stufen wächst, die Oberflächen, die der Sensor nicht sieht, wie du diese Lücken mit Infrarotsensoren schließt, und schließlich um den Wechsel in den manuellen Modus per Bluetooth vom Handy aus. Den Code, um denselben Roboter mit einem ESP32 oder einem Raspberry Pi Pico zu fahren, findest du am Ende des Artikels.

HC-SR04-Ultraschall-Abstandssensor
HC-SR04: Der mit T beschriftete Wandler sendet einen 40-kHz-Ton, der mit R beschriftete horcht auf das Echo. Das ist das Auge des Roboters; er misst 2–400 cm in einem schmalen Winkel von etwa 15 Grad.

Wie funktioniert ein Roboter, der Hindernissen ausweicht?

Jeder autonome Roboter wiederholt dieselbe Schleife aus drei Schritten dutzende Male pro Sekunde: wahrnehmen, entscheiden, handeln. Das Wahrnehmen übernimmt der HC-SR04: Er sendet einen kurzen 40-kHz-Ton, den dein Ohr nicht hört, und misst, wie lange das Echo zurück braucht; teilst du diese Zeit in Mikrosekunden durch 58, erhältst du den Abstand in Zentimetern. Das Entscheiden übernimmt der Arduino, indem er den gemessenen Abstand mit den von dir festgelegten Schwellen vergleicht. Für das Handeln sorgen der L298N und die Motoren. Weil der Sensor auf einem Servo sitzt, kann der Roboter nach links und rechts "schauen", ohne seinen Körper zu drehen, und trifft dadurch viel bessere Lenkentscheidungen als ein Roboter mit festem Sensor. Wie der Sensor im Detail funktioniert, erfährst du im Artikel HC-SR04 Abstandssensor.

BauteilAufgabeHinweis
Arduino Uno (oder Nano)Gehirn: misst, entscheidet, steuert die MotorenESP32 oder Pico gehen auch
HC-SR04Auge: misst den Abstand zum Hindernis davorLäuft mit 5 V, 2–400 cm
SG90-ServoHals: dreht den Sensor nach links und rechtsDer Sensor wird mit einem Kunststoffhalter am Servohorn befestigt
L298N oder L293D-ShieldMuskel: gibt den Motoren Richtung und TempoEin Arduino-Pin liefert keinen Motorstrom
2 TT-Getriebemotoren und StützradDifferentialantriebGelenkt wird, indem sich beide Räder gegenläufig drehen
2 × 18650-Akku (7,4 V)EnergieEin 9-V-Block reicht nicht
2 IR-Hindernissensoren (optional)Erkennen Hindernisse an Ecken und SeitenModule mit LM393 wie der FC-51
HC-05 (optional)Manuelle Steuerung per HandyFunktioniert nur mit Android
Arduino Uno
Arduino Uno
L298N
L298N
TT motor
TT motor
18650 Li-ion
18650 Li-ion

Stromversorgung: Damit der Servo keinen Reset auslöst

Weil beim hindernisvermeidenden Roboter zu den Motoren noch ein Servo kommt, ist die Stromverteilung wichtiger als bei einem gewöhnlichen Roboterauto. Ein SG90 zieht im Stillstand etwa 10 mA; sobald er sich bewegt, steigt der Strom auf 100–250 mA, unter Last nähert er sich einem halben Ampere. Fällt diese Spitze mit dem Anlaufstrom der Motoren zusammen, bricht die 5-V-Schiene des Arduino kurz ein und das Board startet neu. Die Lösung ist einfach: Die Motoren laufen über den L298N am Akku, der Arduino über VIN am Akku, und Servo, HC-SR04, IR-Sensoren und HC-05 teilen sich den 5V-Pin des Arduino; direkt an die Versorgungsleitungen des Servos kommt ein 470-µF-Kondensator. Selbst wenn sich der Servo bewegt, bleibt die Gesamtlast auf der 5-V-Schiene unter einem halben Ampere, und weil sie nur kurz anliegt, schafft das der Spannungsregler des Uno. Um die Akkulaufzeit mit deinen eigenen Werten zu berechnen, nutze das Werkzeug Akkulaufzeit.

TeilVersorgt überTypischer Strom
2 TT-MotorenL298N-Klemme 12V ← 2 × 18650 (7,4 V)0,3–0,6 A beim Fahren, bis zu 2 A beim Anfahren
Arduino UnoVIN ← Pluspol des Akkus (nach dem Schalter)Etwa 50 mA
SG90-ServoArduino 5V + 470-µF-Kondensator~10 mA im Stillstand, 100–250 mA in Bewegung
HC-SR04Arduino 5VEtwa 15 mA
2 IR-SensorenArduino 5VJe 20–40 mA
HC-05Arduino 5V30–40 mA beim Koppeln, etwa 10 mA verbunden
Verbinde alle GNDs miteinander (Akku-Minus, L298N, Arduino, Servo, Sensoren); ohne gemeinsame Masse liefert der Sensor unsinnige Abstände und der Servo zittert. Versorge den Servo nicht über dasselbe dünne Jumperkabel wie die Motoren. Wenn du einen stärkeren Servo wie den MG90S oder MG996R verwendest, speise ihn nicht aus dem Arduino, sondern aus einem separaten, auf 5 V eingestellten Step-down-Modul (z. B. LM2596).

Schaltplan: Arduino Uno, ESP32 und Pico

Die Motorpins sind dieselben wie in der Anleitung zum Roboterauto; so kannst du zu diesem Projekt wechseln, indem du nur den Sensorkopf ergänzt, ohne dein Auto zu zerlegen. Dass die Geschwindigkeitspins auf 5 und 6 liegen, ist Absicht: Auf dem Uno nutzt die Servo-Bibliothek Timer1 und schaltet dadurch PWM an den Pins 9 und 10 ab; lägen die Motorgeschwindigkeiten dort, wäre die Drehzahlregelung kaputt, sobald der Servo angeschlossen ist. HC-SR04 und IR-Sensoren kommen an A0–A3. Die Analogpins des Uno arbeiten bei Bedarf auch als Digitalpins 14–19, und diese Wahl erleichtert es, denselben Code mit einem L293D-Shield zu verwenden, denn das Shield nutzt diese Pins nicht. Die Pinbelegung deines Boards siehst du im Werkzeug Pinout.

VerbindungArduino UnoESP32Raspberry Pi Pico
L298N ENA (Tempo linker Motor)D5GPIO 25GP15
L298N IN1, IN2D7, D8GPIO 27, 33GP14, GP13
L298N ENB (Tempo rechter Motor)D6GPIO 26GP10
L298N IN3, IN4D9, D4GPIO 32, 4GP12, GP11
Servo-Signal (orange)D3GPIO 13GP16
Servo + (rot), − (braun)5V, GNDL298N 5V, GNDL298N 5V, GND
HC-SR04 TRIGA0GPIO 18GP17
HC-SR04 ECHOA1 (direkt)GPIO 19, über 1-kΩ/2-kΩ-TeilerGP18, über 1-kΩ/2-kΩ-Teiler
HC-SR04 VCC5VL298N 5VL298N 5V
IR links OUT, IR rechts OUTA2, A3GPIO 23, 22GP19, GP20
IR-Sensor VCC5V3V33V3
HC-05 TXD, RXDD10, D11 (RXD über Teiler)Nicht nötig (Bluetooth eingebaut)GP1, GP0 (UART0)
Versorgung des BoardsVIN ← Akku (nach dem Schalter)5V (VIN) ← L298N 5VVSYS ← L298N 5V
Der ECHO-Pin des HC-SR04 gibt ein 5-V-Signal aus, die Pins von ESP32 und Pico arbeiten dagegen mit 3,3 V und sind nicht 5-V-tolerant. Baue einen Spannungsteiler nach dem Schema ECHO → 1 kΩ → Abgriff zum Board → 2 kΩ → GND: 5 V × 2 / 3 ≈ 3,3 V. TRIG braucht keinen Teiler; ein 3,3-V-Signal reicht zum Auslösen. Bei ESP32 und Pico teilen sich Board, Servo und HC-SR04 denselben 5V-Ausgang des L298N, sodass du an die Grenze von etwa 0,5 A dieses kleinen Reglers kommen kannst; treten Resets auf, versorge den Servo aus einer separaten 5-V-Quelle.

Sensorkopf montieren und testen

SG90-Mikroservo
SG90: ein 9-Gramm-Mikroservo, der bei 4,8 V 60 Grad in etwa 0,1 Sekunden dreht. Der Kunststoffhalter mit dem HC-SR04 wird auf das Servohorn geschraubt.
  1. Schließe den Servo ohne Horn an und lade den Test-Sketch unten hoch. Am Ende jedes Schwenks fährt der Code den Servo auf 90 Grad und wartet 2 Sekunden; zieh in dieser Pause das USB-Kabel ab, der Servo bleibt stehen. Steck das Horn so auf die Welle, dass der Sensor genau nach vorn schaut; so bedeutet 90 Grad "geradeaus".
  2. Befestige den HC-SR04 mit einem Kunststoffhalter oder Heißkleber am Horn. Die Sensorkabel müssen lang genug sein, um sich beim Drehen des Servos nicht zu spannen; ein Kabel, das beim 140-Grad-Schwenk am Chassis hängen bleibt, ist ein häufiger Fehler.
  3. Montiere den Sensor 8–12 cm über dem Boden und nahe am vordersten Punkt des Gehäuses. Zu tief empfängt er Echos vom Boden und von der Teppichstruktur, zu hoch sieht er niedrige Hindernisse nicht.
  4. Neige den Sensor um 3–5 Grad nach oben. Ein ganz waagerechter, tief sitzender Sensor kann besonders auf rauen Böden ein Echo vom Boden für ein Hindernis halten.
  5. Öffne den Seriellen Monitor mit 9600 Baud. Siehst du kleine Werte in den Zeilen für 125 und 160 Grad, wenn du die Hand links neben den Sensor hältst, liegt links bei den großen Winkeln; ist es umgekehrt, setze im erweiterten Sketch SERVO_DIR auf -1 und vertausche im ersten Sketch die Werte von LEFT_ANGLE und RIGHT_ANGLE.
Arduino (C++)
1#include <Servo.h>
2
3const int SERVO_PIN = 3;
4const int TRIG = A0, ECHO = A1;        // analog pinler dijital pin olarak / analog pins used as digital pins
5Servo head;
6
7int pingCm() {
8  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
9  digitalWrite(TRIG, HIGH); delayMicroseconds(10);   // 10 µs tetik darbesi / 10 µs trigger pulse
10  digitalWrite(TRIG, LOW);
11  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);    // en fazla 25 ms bekle (~4,3 m) / wait at most 25 ms (~4.3 m)
12  return t / 58;                                     // 0 = yankı yok / 0 = no echo
13}
14
15void setup() {
16  Serial.begin(9600);
17  pinMode(TRIG, OUTPUT);
18  pinMode(ECHO, INPUT);
19  head.attach(SERVO_PIN);
20  head.write(90);                                    // önce ortala / center first
21  delay(1000);
22}
23
24void loop() {
25  const int angles[] = {20, 55, 90, 125, 160};
26  for (int a : angles) {
27    head.write(a);
28    delay(400);                                      // servo yerine otursun / let the servo settle
29    Serial.print(a);
30    Serial.print(F(" derece / degrees: "));
31    Serial.print(pingCm());
32    Serial.println(F(" cm"));
33  }
34  Serial.println();
35  head.write(90);
36  delay(2000);
37}

Jede Zeile zeigt einen Winkel und den Abstand in diese Richtung. Ist im Umkreis von etwa 4 Metern vor dem Sensor nichts, liest du 0 cm: pulseIn() liefert 0, wenn innerhalb von 25 ms kein Echo kommt. Im Hauptcode werten wir 0 als "Weg frei", aber auch ein Sensor mit losem Kabel oder vertauschten TRIG- und ECHO-Pins liest meist 0. Halte deshalb deine Hand auf 10, 30 und 60 cm, bevor du den Roboter auf den Boden stellst, und prüfe, ob sich die Werte wirklich ändern; ein ungetesteter Roboter fährt ungebremst gegen die Wand.

Der Entscheidungsalgorithmus: anhalten, zurücksetzen, in die freieste Richtung drehen

Das Verhalten des Roboters nach Abstand in Zonen aufzuteilen, macht den Code einfach und zuverlässig. Ohne Hindernis fährt er mit voller Geschwindigkeit, kommt ein Hindernis näher, wird er langsamer, und an der Stoppschwelle hält er an und scannt. Beim Scannen schaut der Sensor 70 und 35 Grad nach links und 35 und 70 Grad nach rechts; in jede Richtung werden drei Messungen gemacht und deren Median verwendet. Der Roboter dreht sich um genau diesen Winkel in die Richtung mit dem größten Abstand. Ist keine Richtung frei, etwa weil er in eine Ecke gefahren ist, setzt er ein Stück zurück und dreht um 180 Grad.

Gemessener AbstandZoneWas der Roboter tut
Über 60 cm (oder kein Echo)FreiVolle Fahrt voraus (PWM 200)
25–60 cmAnnäherungWird abstandsabhängig langsamer (PWM 200 bis 130)
12–25 cmHindernisHält an, bestätigt die Messung, scannt beide Seiten, dreht in die freieste Richtung
Unter 12 cmZu nahSetzt erst 350 ms zurück, dann scannt er
Alle Richtungen unter 35 cmSackgasseSetzt zurück und dreht um 180 Grad

Diese Schwellen sind nicht zufällig gewählt. Der Anhalteabstand ist die Summe aus drei Reserven: der Reaktionsreserve (Weg zwischen zwei Messungen), der Bremsreserve (wie weit der Roboter nach dem Abschalten der Motoren rollt) und der Drehreserve (damit seine Nase beim Drehen auf der Stelle das Hindernis nicht berührt). Ein Auto mit TT-Motoren an 6 V fährt etwa 0,5–0,7 m/s und legt in einem Messintervall von 60 ms 3–4 cm zurück. Die Bremsreserve beträgt etwa 5 cm, bei einem 15 cm breiten Gehäuse, das auf der Stelle dreht, liegt die Drehreserve um 10 cm. Zusammen ergibt das etwa 18–20 cm; mit ein paar Zentimetern Sicherheitsreserve obendrauf ist STOP_CM = 25 ein guter Startwert. Ist dein Roboter schneller oder der Boden rutschig, erhöhe die Schwelle auf 30–35 cm. In engen Fluren führt eine zu große Schwelle dagegen dazu, dass der Roboter ständig anhält und scannt; die Feinabstimmung machst du am besten auf deiner eigenen Strecke.

Code Schritt für Schritt 1: dein erster hindernisvermeidender Roboter

Die erste Version ist das Gerüst des Algorithmus: vorwärts fahren, bei 25 cm anhalten, zurücksetzen, nach links und rechts schauen, zur freieren Seite drehen. Die Motorfunktion drive(links, rechts) ist dieselbe wie in der Anleitung zum Roboterauto und nimmt Werte von -255 bis 255 an; positiv heißt vorwärts, negativ rückwärts. Stell das Auto nach dem Hochladen zuerst so auf eine Kiste, dass die Räder frei drehen, und beobachte, wie es anhält und dreht, wenn du die Hand dem Sensor näherst; passt alles, stell den Roboter auf den Boden.

Arduino (C++)
1#include <Servo.h>
2
3const int ENA = 5, IN1 = 7, IN2 = 8;            // sol motor / left motor
4const int ENB = 6, IN3 = 9, IN4 = 4;            // sağ motor / right motor
5const int SERVO_PIN = 3;
6const int TRIG = A0, ECHO = A1;
7
8const int STOP_CM = 25;                         // bu mesafede dur / stop at this distance
9const int SPEED = 170;                          // düz gidiş hızı / cruising speed
10const int TURN = 170;                           // dönüş hızı / turning speed
11const int LEFT_ANGLE = 160, RIGHT_ANGLE = 20;   // ters ise yer değiştir / swap if reversed
12
13Servo head;
14
15void drive(int left, int right) {               // her biri -255 … 255 / each -255 … 255
16  digitalWrite(IN1, left > 0);  digitalWrite(IN2, left < 0);
17  digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
18  analogWrite(ENA, abs(left));
19  analogWrite(ENB, abs(right));
20}
21
22int pingCm() {
23  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
24  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
25  digitalWrite(TRIG, LOW);
26  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
27  if (t == 0) return 400;                       // yankı yok: önü açık say / no echo: treat as open
28  return t / 58;
29}
30
31int lookAt(int angle) {
32  head.write(angle);
33  delay(400);                                   // servo dönsün ve dursun / let the servo turn and settle
34  return pingCm();
35}
36
37void setup() {
38  int pins[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
39  for (int p : pins) pinMode(p, OUTPUT);
40  pinMode(ECHO, INPUT);
41  drive(0, 0);
42  head.attach(SERVO_PIN);
43  head.write(90);
44  delay(3000);                                  // yere koyman için süre / time to put it on the floor
45}
46
47void loop() {
48  int front = pingCm();
49  if (front > STOP_CM) {
50    drive(SPEED, SPEED);                        // yol açık: ileri / path clear: forward
51    delay(60);                                  // ölçümler arası 60 ms / 60 ms between readings
52    return;
53  }
54
55  drive(0, 0);                                  // 1) dur / 1) stop
56  delay(150);
57  drive(-SPEED, -SPEED);                        // 2) biraz geri çekil / 2) back off a little
58  delay(300);
59  drive(0, 0);
60
61  int left = lookAt(LEFT_ANGLE);                // 3) iki yana bak / 3) look both ways
62  int right = lookAt(RIGHT_ANGLE);
63  head.write(90);
64  delay(300);
65
66  if (left < STOP_CM && right < STOP_CM) {      // iki yan kapalı: geri dön / both blocked: turn around
67    drive(-TURN, TURN);
68    delay(700);
69  } else if (left > right) {                    // 4) daha açık tarafa dön / 4) turn to the more open side
70    drive(-TURN, TURN);
71    delay(350);
72  } else {
73    drive(TURN, -TURN);
74    delay(350);
75  }
76  drive(0, 0);
77  delay(100);
78}

Dieser Code erfüllt seinen Zweck, hat aber einige Schwächen: Eine einzige Fehlmessung lässt den Roboter grundlos anhalten, er nähert sich Hindernissen immer mit demselben Tempo, schaut nur in zwei Richtungen, und seine Drehzeiten hängen nicht vom Winkel ab. Und weil er mit delay() wartet, kann er während des Scannens auf nichts reagieren. Die Einzelheiten zum Warten findest du im Artikel millis() und delay(); die nächste Version behebt die meisten dieser Probleme.

Code Schritt für Schritt 2: Filter, sanftes Abbremsen, IR und Bluetooth

Die erweiterte Version funktioniert mit derselben Verdrahtung, und es macht nichts, wenn du IR-Sensoren und HC-05 noch nicht angeschlossen hast. Weil die IR-Eingänge mit INPUT_PULLUP eingerichtet sind, liest ein nicht angeschlossener Sensor "kein Hindernis" (HIGH); ohne HC-05 startet der Roboter einfach im Automatikmodus. Was die einzelnen Teile des Codes tun, steht in der Tabelle unten.

Arduino (C++)
1#include <Servo.h>
2#include <SoftwareSerial.h>
3
4// Pinler / Pins
5const int ENA = 5, IN1 = 7, IN2 = 8;     // sol motor / left motor
6const int ENB = 6, IN3 = 9, IN4 = 4;     // sağ motor / right motor
7const int SERVO_PIN = 3;
8const int TRIG = A0, ECHO = A1;
9const int IR_LEFT = A2, IR_RIGHT = A3;   // LOW = engel (takılı değilse HIGH) / LOW = obstacle (HIGH if not fitted)
10SoftwareSerial bt(10, 11);               // RX, TX → HC-05 TXD, RXD (RXD bölücüyle / RXD via divider)
11
12// Ayarlar / Settings
13const int BACK_CM = 12;                  // daha yakınsa geri çekil / closer: back off first
14const int STOP_CM = 25;                  // dur ve tara / stop and scan
15const int SLOW_CM = 60;                  // yavaşlamaya başla / start slowing down
16const int GUARD_CM = 15;                 // manuel modda çarpışma koruması / collision guard in manual mode
17const int MAX_CM = 400;
18const int CRUISE = 200, SLOW = 130, TURN = 170;   // PWM hızları / PWM speeds
19const int SERVO_CENTER = 90;             // sensör öne bakmıyorsa düzelt / adjust if not facing ahead
20const int SERVO_DIR = 1;                 // +70 sola bakmıyorsa -1 yap / set to -1 if +70 doesn't look left
21const float MS_PER_DEG = 4.0;            // derece başına ms, kalibre et / ms per degree, calibrate
22const unsigned long BT_TIMEOUT = 600;    // manuelde komut kesilirse dur / stop if commands stop
23
24Servo head;
25bool autoMode = true;
26int manualLeft = 0, manualRight = 0, manualSpeed = 200;
27unsigned long lastCmd = 0, lastPing = 0;
28
29void drive(int left, int right) {        // her biri -255 … 255 / each -255 … 255
30  left = constrain(left, -255, 255);
31  right = constrain(right, -255, 255);
32  digitalWrite(IN1, left > 0);  digitalWrite(IN2, left < 0);
33  digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
34  analogWrite(ENA, abs(left));
35  analogWrite(ENB, abs(right));
36}
37
38int pingCm() {
39  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
40  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
41  digitalWrite(TRIG, LOW);
42  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);  // ~4,3 m zaman aşımı / ~4.3 m timeout
43  return t == 0 ? MAX_CM : (int)(t / 58);          // yankı yok = açık alan / no echo = open space
44}
45
46int distanceCm() {                       // 3 ölçümün ortancası / median of 3 readings
47  int a = pingCm(); delay(60);
48  int b = pingCm(); delay(60);
49  int c = pingCm();
50  return max(min(a, b), min(max(a, b), c));
51}
52
53bool irBlocked(int pin) {
54  return digitalRead(pin) == LOW;
55}
56
57int lookAt(int offset) {                 // + sol, - sağ (derece) / + left, - right (degrees)
58  head.write(SERVO_CENTER + SERVO_DIR * offset);
59  delay(300);                            // servo yerine otursun / let the servo settle
60  return distanceCm();
61}
62
63void turnBy(int deg) {                   // + sola, - sağa / + left, - right
64  if (deg > 0) drive(-TURN, TURN);
65  else drive(TURN, -TURN);
66  delay((unsigned long)(abs(deg) * MS_PER_DEG));
67  drive(0, 0);
68  delay(100);
69}
70
71void avoid() {
72  drive(0, 0);                           // 1) dur / 1) stop
73  delay(120);
74  int front = distanceCm();              // ortancayla doğrula / confirm with the median
75  bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
76  if (front >= STOP_CM && !sides) return;          // yanlış alarm / false alarm
77
78  if (front < BACK_CM || sides) {        // 2) çok yakın: geri çekil / 2) too close: back off
79    drive(-TURN, -TURN);
80    delay(350);
81    drive(0, 0);
82  }
83
84  const int offsets[] = {70, 35, -35, -70};        // 3) tara / 3) scan
85  int bestOffset = 0, bestCm = 0;
86  for (int off : offsets) {
87    int cm = lookAt(off);
88    if (cm > bestCm) { bestCm = cm; bestOffset = off; }
89  }
90  head.write(SERVO_CENTER);
91  delay(200);
92
93  if (bestCm < STOP_CM + 10) {           // her yön kapalı: çıkmaz / every way blocked: dead end
94    drive(-TURN, -TURN);
95    delay(400);
96    turnBy(180);
97  } else {
98    turnBy(bestOffset);                  // 4) en açık yöne dön / 4) turn to the most open direction
99  }
100}
101
102void autoStep() {
103  if (millis() - lastPing < 60) return;  // 60 ms'de bir ölç / measure every 60 ms
104  lastPing = millis();
105  int d = pingCm();
106  bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
107
108  if (d < STOP_CM || (l && r)) {
109    avoid();
110  } else if (l) {                        // sol köşede engel: sağa kaç / front-left: dodge right
111    drive(TURN, -TURN);
112    delay(150);
113  } else if (r) {                        // sağ köşede engel: sola kaç / front-right: dodge left
114    drive(-TURN, TURN);
115    delay(150);
116  } else if (d < SLOW_CM) {
117    int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE);   // yaklaştıkça yavaşla / slow down as you get closer
118    drive(s, s);
119  } else {
120    drive(CRUISE, CRUISE);
121  }
122}
123
124void manualStep() {
125  if (millis() - lastCmd > BT_TIMEOUT) {           // komut yok: dur / no commands: stop
126    manualLeft = 0;
127    manualRight = 0;
128  }
129  static int front = MAX_CM;
130  if (millis() - lastPing >= 60) {
131    lastPing = millis();
132    front = pingCm();
133  }
134  bool forward = manualLeft > 0 && manualRight > 0;
135  if (forward && front < GUARD_CM) drive(0, 0);    // çarpışma koruması / collision guard
136  else drive(manualLeft, manualRight);
137}
138
139void handleBluetooth() {
140  while (bt.available()) {
141    char c = bt.read();
142    Serial.println(c);                   // gelen harfi göster / show the incoming letter
143    lastCmd = millis();
144    if (c == 'A') {                      // otomatik mod / auto mode
145      autoMode = true;
146      continue;
147    }
148    if (c == 'M') {                      // manuel mod / manual mode
149      autoMode = false;
150      manualLeft = manualRight = 0;
151      drive(0, 0);
152      head.write(SERVO_CENTER);
153      continue;
154    }
155    if (autoMode) continue;              // otomatikte yön harflerini yok say / ignore in auto mode
156    int s = manualSpeed;
157    switch (c) {
158      case 'F': manualLeft = s;  manualRight = s;  break;   // ileri / forward
159      case 'B': manualLeft = -s; manualRight = -s; break;   // geri / backward
160      case 'L': manualLeft = -s; manualRight = s;  break;   // yerinde sola / spin left
161      case 'R': manualLeft = s;  manualRight = -s; break;   // yerinde sağa / spin right
162      case 'S': manualLeft = 0;  manualRight = 0;  break;   // dur / stop
163      default:
164        if (c >= '0' && c <= '9') manualSpeed = map(c - '0', 0, 9, 100, 240);   // hız kademesi / speed level
165    }
166  }
167}
168
169void setup() {
170  int outs[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
171  for (int p : outs) pinMode(p, OUTPUT);
172  drive(0, 0);
173  pinMode(ECHO, INPUT);
174  pinMode(IR_LEFT, INPUT_PULLUP);
175  pinMode(IR_RIGHT, INPUT_PULLUP);
176  Serial.begin(9600);
177  bt.begin(9600);                        // HC-05 veri modu hızı / HC-05 data mode baud rate
178  head.attach(SERVO_PIN);
179  head.write(SERVO_CENTER);
180  delay(3000);                           // yere koyman için süre / time to put it on the floor
181}
182
183void loop() {
184  handleBluetooth();
185  if (autoMode) autoStep();
186  else manualStep();
187}
Funktion oder EinstellungWas sie macht
pingCm()Macht eine Messung; liefert 400 cm (freier Raum), wenn innerhalb von 25 ms kein Echo kommt
distanceCm()Macht 3 Messungen im Abstand von 60 ms und liefert den Median; ein einzelner Ausreißer fällt heraus
autoStep()Misst alle 60 ms; fährt je nach Abstand und IR-Sensoren weiter, bremst, weicht aus oder ruft avoid() auf
avoid()Hält an, bestätigt die Messung, setzt bei Bedarf zurück, schaut in 4 Richtungen und dreht in die freieste
turnBy(Grad)Dreht den Roboter um den angegebenen Winkel auf der Stelle; die Zeit wird mit MS_PER_DEG berechnet
manualStep()Setzt die Richtung vom Handy um; hält bei Vorwärtsfahrt bei 15 cm an und stoppt, wenn 600 ms kein Befehl kommt
SERVO_CENTER, SERVO_DIRDer Winkel, bei dem der Sensor genau nach vorn schaut, und die Richtung für links
STOP_CM, SLOW_CM, BACK_CMSchwellen zum Anhalten, Abbremsen und Zurücksetzen

Für einen hindernisvermeidenden Roboter ist ein Medianfilter besser als ein Mittelwert: Bei drei Messungen wie 24, 26 und 400 cm ergibt der Mittelwert 150 cm und führt den Roboter in die Irre, der Median wählt dagegen 26 cm. Wenn du tiefer einsteigen willst, lies den Artikel Sensordaten filtern. Die Trennung zwischen Automatik- und manuellem Modus ist eigentlich auch ein kleiner Zustandsautomat; diesen Ansatz erklären wir ausführlich in der Anleitung Arduino State Machine. Stimme den Roboter nach dem Hochladen in dieser Reihenfolge auf deinen Raum ab:

  1. Servomitte: Steht der Roboter rechtwinklig vor einer glatten Wand und der Sensor zeigt nicht genau darauf, ändere SERVO_CENTER in kleinen Schritten, etwa auf 85 oder 95.
  2. Drehzeit: Füge am Ende von setup() vorübergehend die Zeile turnBy(360); ein und beobachte, ob der Roboter eine volle Umdrehung macht. Dreht er 300 statt 360 Grad, setze MS_PER_DEG auf 4,0 × 360 / 300 = 4,8. Weil sich dieser Wert mit Akku und Boden ändert, stimme ihn mit vollem Akku auf dem Boden ab, auf dem der Roboter fahren soll.
  3. Stoppschwelle: Lass den Roboter auf eine Wand zufahren und miss, wie weit seine Nase dort, wo er anhält, von der Wand entfernt ist. Sind es weniger als 10 cm, erhöhe STOP_CM um 5 cm.
  4. Langsames Tempo: Liegt SLOW unter der Anlaufschwelle der Motoren, bleibt der Roboter in der Annäherungszone stehen; erhöhe SLOW dann auf 150.

Tote Winkel des HC-SR04: weiche Oberflächen, schräge Wände und dünne Hindernisse

Ein Ultraschallsensor sieht Oberflächen gut, die Schall zurückwerfen, aber nicht jedes Hindernis schickt den Schall zum Sensor zurück. Der Hersteller empfiehlt für gute Ergebnisse ein Ziel von mindestens 0,5 m², das möglichst glatt ist. In einem echten Raum trifft der Roboter dagegen auf Vorhänge, Sofas, Stuhlbeine und Zimmerecken. Schall prallt wie eine Billardkugel im selben Winkel ab, in dem er ankommt: Ein Sensor, der senkrecht auf eine Wand schaut, bekommt ein starkes Echo, weicht seine Blickrichtung aber um mehr als etwa 30–40 Grad von der Senkrechten ab, wird der Schall weggelenkt und kommt nie zurück. Dann liefert pulseIn() 0 und der Code hält den Weg für frei. Das ist der häufigste Grund, warum Roboter an Wänden entlangschrammen.

Hindernis oder OberflächeWarum es nicht erkannt wirdLösung
Vorhänge, Sofas, Kissen, Kleidung, TierfellWeiche, poröse Oberflächen schlucken den Schall, das Echo kommt sehr schwach zurückIR-Sensor für kurze Distanz ergänzen, Roboter langsam fahren
Schräge Annäherung an eine WandDer Schall wird weggelenkt, das Echo erreicht den Sensor nichtNach außen geneigte IR-Sensoren an den vorderen Ecken, engere Scanwinkel
Stuhl- und Tischbeine, dünne StangenDie reflektierende Fläche ist winzig, das Echo schwachTempo senken, Medianfilter nutzen, seitlich IR-Sensoren ergänzen
Niedrige Hindernisse unter dem Sensor (Kabel, Türschwellen, Hausschuhe)Sie liegen unter dem SchallkegelNach unten gerichteten IR-Sensor oder Mikroschalter an der Frontstoßstange
Tischkanten, TreppenabsätzeDer Ultraschallsensor schaut nach vorn und sieht keine Lücke im BodenAbsturzerkennung mit einem zum Boden gerichteten TCRT5000
Gegenstände näher als 2 cmTotzone des Sensors: Das Echo kommt zurück, bevor der Schallimpuls beendet istRoboter so einstellen, dass er bei 12 cm zurücksetzt
Ecken und enge FlureDer Schall prallt zwischen zwei Wänden hin und her und kommt spät zurück, der Abstand wirkt größerMedianfilter und Messungen aus mehreren Winkeln
Was ein Sensor nicht sieht, mit einem anderen Sensor abzudecken, ist immer wirksamer, als einen einzelnen Sensor perfekt machen zu wollen. Der erste Schritt sollte aber sein, langsamer zu fahren: Ein Roboter mit 0,3 m/s prallt nach derselben Fehlmessung nicht so hart auf wie einer mit 0,7 m/s, und es bleibt Zeit für eine zweite Messung. Die Stärken und Schwächen verschiedener Abstandssensoren haben wir im Artikel Arduino-Abstandssensoren im Vergleich zusammengestellt.

Verstärkung mit IR-Sensoren

Ultraschall- und Infrarotsensoren (IR) gleichen gegenseitig ihre Schwächen aus. IR-Hindernissensoren wie der FC-51 bestehen aus einer Infrarot-LED und einem Empfänger; ist das reflektierte Licht stark genug, zieht der Komparator LM393 den Ausgang auf LOW und die LED auf der Platine leuchtet. Die Reichweite lässt sich per Trimmpoti von etwa 2 bis 30 cm einstellen, aber der Sensor misst keinen Abstand, er meldet nur "Hindernis" oder "kein Hindernis". Setzt du zwei IR-Sensoren an die vorderen Ecken des Roboters, um 30–45 Grad nach außen geneigt, erfassen sie Stuhlbeine und schräge Wände außerhalb des schmalen Ultraschallkegels. Im erweiterten Code weicht der Roboter kurz nach rechts aus, wenn der linke IR-Sensor auslöst, und nach links, wenn der rechte auslöst; lösen beide gleichzeitig aus, hält er an und scannt, als stünde ein Hindernis vor ihm.

EigenschaftHC-SR04 (Ultraschall)FC-51 (IR-Hindernis)TCRT5000 (IR-Reflexion)
AusgabeAbstand (cm)Ja / nein (digital)Analogwert und Digitalausgang
Reichweite2–400 cmEtwa 2–30 cm (einstellbar)Etwa 1–15 mm
ErfassungswinkelEtwa 15°Etwa 35°Sehr schmal
SchwachstelleWeiche, schräge und dünne OberflächenSchwarze, matte Flächen, SonnenlichtHöhenänderungen, Sonnenlicht
Sieht gutGlas, schwarze Gegenstände, große glatte FlächenStoff, Ecken, nahe HindernisseBodenfarbe und Tischkanten
Platz am RoboterHauptauge, vom Servo geschwenktVordere EckenUnter der Frontstoßstange, zum Boden gerichtet
TCRT5000-Infrarot-Reflexionssensor
TCRT5000: Nach unten gerichtet und 5–10 mm über dem Boden montiert, erkennt er Tischkanten und Treppenabsätze; sobald die Reflexion verschwindet, kann der Roboter anhalten und zurücksetzen.

Für Roboter, die auf einem Tisch unterwegs sind, ist das gefährlichste Hindernis eigentlich ein Abgrund. Baust du ein zum Boden gerichtetes TCRT5000-Modul unter die Frontstoßstange, merkst du sofort, wenn der Boden darunter verschwindet. Verbinde den Digitalausgang des Moduls mit D2, der am Uno noch frei ist; prüfe zuerst im Seriellen Monitor, welchen Pegel der Ausgang ohne Boden annimmt, lies dann diesen Pin ganz am Anfang von autoStep() und stoppe bei fehlendem Boden sofort die Motoren und setze zurück. Diese Prüfung muss vor der Abstandsmessung kommen; ein Absturz ist immer teurer als ein Zusammenstoß.

Per Bluetooth in den manuellen Modus wechseln

Manchmal willst du den autonomen Roboter von Hand fahren: um ihn von einem Zimmer ins andere zu bringen, ihn vor ein Hindernis zu stellen und seine Reaktion zu testen oder einfach zum Spielen. Die HC-05-Verbindung im erweiterten Code macht das möglich. Sendet das Handy den Buchstaben A, wechselt der Roboter in den Automatikmodus, bei M in den manuellen Modus. Im manuellen Modus gelten die Steuerbuchstaben aus der Anleitung zum Roboterauto (F, B, L, R, S), mit einem Unterschied: Sieht der Roboter bei Vorwärtsfahrt ein Hindernis näher als 15 cm, hält er trotz deines Befehls an. Dieser Kollisionsschutz lässt dich die Sensoren des autonomen Roboters auch beim manuellen Fahren nutzen. Kopplung und AT-Befehle des HC-05 findest du im Artikel HC-05 Bluetooth-Modul.

BuchstabeFunktionIn welchem Modus?
AIn den Automatikmodus wechselnIn beiden Modi
MIn den manuellen Modus wechseln, Roboter anhaltenIn beiden Modi
F / BVorwärts / rückwärtsManuell
L / RAuf der Stelle links / rechts drehenManuell
SAnhaltenManuell
0–9Geschwindigkeitsstufe (PWM 100–240)Manuell
  1. Verbinde den TXD-Pin des HC-05 mit D10, den RXD-Pin über einen 1-kΩ/2-kΩ-Teiler mit D11, VCC mit 5V und GND mit GND.
  2. Kopple den HC-05 in den Bluetooth-Einstellungen deines Android-Handys mit der PIN 1234 (oder 0000, falls das nicht klappt).
  3. Installiere aus dem Play Store eine Bluetooth-Auto-Fernsteuerungs-App und verbinde sie mit dem HC-05. Lässt du den Arduino am USB und öffnest den Seriellen Monitor mit 9600 Baud, gibt der Code jeden Buchstaben aus, der vom Handy kommt.
  4. Die meisten Fernsteuerungs-Apps haben Zusatztasten wie Licht oder Hupe, und jede sendet einen eigenen Buchstaben. Lies diese Buchstaben im Seriellen Monitor ab und setze sie im Code anstelle von 'A' und 'M' ein; willst du die Tasten selbst festlegen, kannst du mit der Anleitung App Inventor Arduino Bluetooth deine eigene Fernsteuerung bauen.
  5. Zieh das USB-Kabel ab und leg den Schalter um; nach 3 Sekunden fährt der Roboter im Automatikmodus los. Wechsle in den manuellen Modus und steuere ihn per Handy.
Auf dem Uno hält SoftwareSerial andere Interrupts für etwa 1 ms zurück, während es ein Byte empfängt; in dieser Zeit können sich die Impulse der Servo-Bibliothek verlängern und der Sensorkopf zuckt leicht. Das stört das Verhalten des Roboters nicht, aber wenn es dich nervt, kannst du auf das eingebaute Bluetooth des ESP32 umsteigen. HC-05 und HC-06 funktionieren nicht mit dem iPhone; dafür brauchst du ein BLE-Modul oder einen ESP32, der für BLE programmiert ist.

Aufbau mit einem L293D-Motor-Shield

L293D-Motortreiber-Shield
L293D-Motor-Shield: Es wird direkt auf einen Arduino Uno gesteckt; vier Gleichstrommotoren kommen an die Klemmen M1–M4, zwei Servos an die Stiftleisten in der Ecke.

Wenn du keinen Kabelsalat magst, kannst du statt des L298N ein L293D-Motor-Shield (Adafruit Motor Shield V1 und seine Klone) verwenden, das direkt auf den Arduino Uno gesteckt wird. Das Shield kann vier Gleichstrommotoren treiben, hat zwei Servo-Anschlüsse und versorgt die Servos aus der 5-V-Schiene des Arduino. Die Motoren hängen nicht direkt an Arduino-Pins, sondern am Schieberegister 74HC595 auf der Platine; deshalb steuerst du sie mit der Adafruit Motor Shield library (der für V1, nicht V2), also AFMotor, die du über den Bibliotheksverwalter installierst. Das Shield belegt D3, D4, D5, D6, D7, D8, D11 und D12 für die Motoren und D9 und D10 für die Servos; A0–A5, D2 und D13 bleiben frei. Da HC-SR04 und IR-Sensoren schon an A0–A3 hängen, musst du nur den Motorcode, den Servo-Pin und die HC-05-Pins ändern.

EinstellungMit L298NMit L293D-Shield
MotoranschlussKlemmen OUT1–OUT4Klemmen M1 (links) und M2 (rechts)
MotorcodedigitalWrite und analogWrite in drive()AFMotor in drive()
SERVO_PIN39 oder 10, je nach Anschluss, an dem der Servo steckt
HC-05D10, D11A4 (RX) und A5 (TX): SoftwareSerial bt(A4, A5)
Strom pro Kanal2 A dauerhaft0,6 A dauerhaft, 1,2 A Spitze
MotorversorgungKlemme 12VKlemme EXT_PWR; mit gestecktem PWR-Jumper läuft auch der Arduino an derselben Quelle
Arduino (C++)
1// L293D shield: ENA–IN4 tanımlarını ve setup()'taki pinMode listesinden bu pinleri sil, drive() fonksiyonunu aşağıdakiyle değiştir
2// L293D shield: delete the ENA–IN4 definitions, remove them from the pinMode list in setup() and replace drive() with the one below
3// Ayrıca / Also: SERVO_PIN = 9 ya da / or 10, SoftwareSerial bt(A4, A5)
4#include <AFMotor.h>                     // Adafruit Motor Shield V1 kütüphanesi / library
5
6AF_DCMotor leftMotor(1);                 // M1 klemensi / M1 terminal
7AF_DCMotor rightMotor(2);                // M2 klemensi / M2 terminal
8
9void drive(int left, int right) {        // her biri -255 … 255 / each -255 … 255
10  leftMotor.setSpeed(min(abs(left), 255));
11  rightMotor.setSpeed(min(abs(right), 255));
12  leftMotor.run(left > 0 ? FORWARD : (left < 0 ? BACKWARD : RELEASE));
13  rightMotor.run(right > 0 ? FORWARD : (right < 0 ? BACKWARD : RELEASE));
14}

Die Grenze von 0,6 A pro Kanal reicht beim L293D für normales Fahren eines 2WD-Roboters mit TT-Motoren, aber wenn ein Rad gegen ein Hindernis drückt und blockiert, kann der Motor über 1 A ziehen und der Chip wird schnell heiß. Ein Scan-Algorithmus, der den Roboter anhält, bevor er gegen ein Hindernis drückt, verlängert deshalb auch die Lebensdauer des Shields. Die Jumper-Einstellungen des Shields und einen Sketch für ein Auto mit vier Motoren findest du im Artikel L293D Motor Shield, den inneren Aufbau des L298N in der Anleitung Gleichstrommotor mit dem L298N steuern.

Hindernisvermeidender Roboter mit dem ESP32

Beim ESP32 gibt es zwei wichtige Unterschiede. Erstens funktioniert die Servo-Bibliothek des Arduino auf dem ESP32 nicht; wir erzeugen das Servosignal deshalb direkt mit LEDC, der PWM-Einheit des Boards: Bei einem 50-Hz-Signal entspricht ein Impuls von 0,544–2,4 ms 0–180 Grad. Weil die Motoren mit 1 kHz und der Servo mit 50 Hz laufen, werden zuerst die Motorkanäle und danach der Servokanal eingerichtet; so bekommt der Servo seinen eigenen Timer. Zweitens hat der ESP32 klassisches Bluetooth eingebaut, ein HC-05 ist also überflüssig; der Roboter erscheint auf dem Handy als "BlueGrays-Robot", und die Richtungs- und Modusbuchstaben sind dieselben wie in der Uno-Version. Der Code ist für Version 3.x des ESP32-Arduino-Cores geschrieben (ledcAttach und ledcWrite arbeiten mit Pinnummern) und lässt sich, weil er klassisches Bluetooth braucht, für ESP32-S3 und ESP32-C3 nicht kompilieren.

Arduino (C++)
1// ESP32 Arduino çekirdeği 3.x / ESP32 Arduino core 3.x
2#include "BluetoothSerial.h"
3
4const int ENA = 25, IN1 = 27, IN2 = 33;   // sol motor / left motor
5const int ENB = 26, IN3 = 32, IN4 = 4;    // sağ motor / right motor
6const int SERVO_PIN = 13;
7const int TRIG = 18, ECHO = 19;           // ECHO 1 kΩ / 2 kΩ bölücüyle / ECHO via a 1 kΩ / 2 kΩ divider
8const int IR_LEFT = 23, IR_RIGHT = 22;    // LOW = engel / LOW = obstacle
9
10const int BACK_CM = 12, STOP_CM = 25, SLOW_CM = 60, GUARD_CM = 15, MAX_CM = 400;
11const int CRUISE = 200, SLOW = 130, TURN = 170;
12const int SERVO_CENTER = 90, SERVO_DIR = 1;
13const float MS_PER_DEG = 4.0;             // kalibre et / calibrate
14
15BluetoothSerial bt;
16bool autoMode = true;
17int manualLeft = 0, manualRight = 0;
18unsigned long lastCmd = 0, lastPing = 0;
19
20void drive(int left, int right) {         // her biri -255 … 255 / each -255 … 255
21  left = constrain(left, -255, 255);
22  right = constrain(right, -255, 255);
23  digitalWrite(IN1, left > 0);  digitalWrite(IN2, left < 0);
24  digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
25  ledcWrite(ENA, abs(left));
26  ledcWrite(ENB, abs(right));
27}
28
29void servoWrite(int angle) {              // 0–180° → 544–2400 µs
30  angle = constrain(angle, 0, 180);
31  uint32_t us = map(angle, 0, 180, 544, 2400);
32  ledcWrite(SERVO_PIN, us * 16384 / 20000);   // 14 bit, 20 ms periyot / 14-bit, 20 ms period
33}
34
35int pingCm() {
36  digitalWrite(TRIG, LOW);  delayMicroseconds(2);
37  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
38  digitalWrite(TRIG, LOW);
39  unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
40  return t == 0 ? MAX_CM : (int)(t / 58);
41}
42
43int distanceCm() {                        // 3 ölçümün ortancası / median of 3 readings
44  int a = pingCm(); delay(60);
45  int b = pingCm(); delay(60);
46  int c = pingCm();
47  return max(min(a, b), min(max(a, b), c));
48}
49
50bool irBlocked(int pin) { return digitalRead(pin) == LOW; }
51
52void turnBy(int deg) {                    // + sola, - sağa / + left, - right
53  if (deg > 0) drive(-TURN, TURN);
54  else drive(TURN, -TURN);
55  delay((unsigned long)(abs(deg) * MS_PER_DEG));
56  drive(0, 0);
57  delay(100);
58}
59
60void avoid() {
61  drive(0, 0);
62  delay(120);
63  int front = distanceCm();
64  bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
65  if (front >= STOP_CM && !sides) return;            // yanlış alarm / false alarm
66  if (front < BACK_CM || sides) {                    // geri çekil / back off
67    drive(-TURN, -TURN);
68    delay(350);
69    drive(0, 0);
70  }
71  const int offsets[] = {70, 35, -35, -70};
72  int bestOffset = 0, bestCm = 0;
73  for (int off : offsets) {
74    servoWrite(SERVO_CENTER + SERVO_DIR * off);
75    delay(300);
76    int cm = distanceCm();
77    if (cm > bestCm) { bestCm = cm; bestOffset = off; }
78  }
79  servoWrite(SERVO_CENTER);
80  delay(200);
81  if (bestCm < STOP_CM + 10) {                       // çıkmaz / dead end
82    drive(-TURN, -TURN);
83    delay(400);
84    turnBy(180);
85  } else {
86    turnBy(bestOffset);
87  }
88}
89
90void setup() {
91  int outs[] = {IN1, IN2, IN3, IN4, TRIG};
92  for (int p : outs) pinMode(p, OUTPUT);
93  pinMode(ECHO, INPUT);
94  pinMode(IR_LEFT, INPUT_PULLUP);
95  pinMode(IR_RIGHT, INPUT_PULLUP);
96  ledcAttach(ENA, 1000, 8);               // motor PWM: 1 kHz, 8 bit
97  ledcAttach(ENB, 1000, 8);
98  ledcAttach(SERVO_PIN, 50, 14);          // servo: 50 Hz, 14 bit
99  drive(0, 0);
100  servoWrite(SERVO_CENTER);
101  bt.begin("BlueGrays-Robot");            // telefonda görünecek ad / name shown on the phone
102  delay(3000);
103}
104
105void loop() {
106  while (bt.available()) {
107    char c = bt.read();
108    lastCmd = millis();
109    if (c == 'A') autoMode = true;                                  // otomatik / auto
110    else if (c == 'M') {                                            // manuel / manual
111      autoMode = false;
112      manualLeft = manualRight = 0;
113      servoWrite(SERVO_CENTER);
114    }
115    else if (!autoMode) {
116      if (c == 'F')      { manualLeft = 200;  manualRight = 200; }  // ileri / forward
117      else if (c == 'B') { manualLeft = -200; manualRight = -200; } // geri / backward
118      else if (c == 'L') { manualLeft = -200; manualRight = 200; }  // sola / left
119      else if (c == 'R') { manualLeft = 200;  manualRight = -200; } // sağa / right
120      else if (c == 'S') { manualLeft = 0;    manualRight = 0; }    // dur / stop
121    }
122  }
123
124  if (millis() - lastPing < 60) return;               // 60 ms'de bir / every 60 ms
125  lastPing = millis();
126  int d = pingCm();
127
128  if (!autoMode) {
129    if (millis() - lastCmd > 600) manualLeft = manualRight = 0;     // komut yok: dur / no command: stop
130    bool forward = manualLeft > 0 && manualRight > 0;
131    if (forward && d < GUARD_CM) drive(0, 0);                       // çarpışma koruması / collision guard
132    else drive(manualLeft, manualRight);
133    return;
134  }
135
136  bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
137  if (d < STOP_CM || (l && r)) avoid();
138  else if (l) { drive(TURN, -TURN); delay(150); }
139  else if (r) { drive(-TURN, TURN); delay(150); }
140  else if (d < SLOW_CM) { int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE); drive(s, s); }
141  else drive(CRUISE, CRUISE);
142}

Hindernisvermeidender Roboter mit dem Raspberry Pi Pico (MicroPython)

Auf dem Raspberry Pi Pico funktioniert die MicroPython-Funktion time_pulse_us() wie pulseIn() beim Arduino; kommt kein Echo, liefert sie einen negativen Wert, den wir als 400 cm, also freien Raum, werten. Für den Servo nutzen wir 50-Hz-PWM und duty_ns(), das die Impulsbreite in Nanosekunden erwartet. Der Servo an GP16 und die Motor-Geschwindigkeitspins an GP15 und GP10 liegen auf verschiedenen PWM-Slices und stören sich deshalb nicht in der Frequenz. Die Geschwindigkeiten werden diesmal in Prozent angegeben. Der Pico hat kein Bluetooth; willst du den manuellen Modus, schließe einen HC-05 wie in der Anleitung zum Roboterauto an UART0 (GP0, GP1) an.

MicroPython
1from machine import Pin, PWM, time_pulse_us
2import time
3
4ena = PWM(Pin(15)); ena.freq(1000)          # sol motor hızı / left motor speed
5enb = PWM(Pin(10)); enb.freq(1000)          # sağ motor hızı / right motor speed
6in1, in2 = Pin(14, Pin.OUT), Pin(13, Pin.OUT)
7in3, in4 = Pin(12, Pin.OUT), Pin(11, Pin.OUT)
8servo = PWM(Pin(16)); servo.freq(50)        # servo sinyali, 50 Hz / servo signal, 50 Hz
9trig = Pin(17, Pin.OUT, value=0)
10echo = Pin(18, Pin.IN)                      # 1 kΩ / 2 kΩ bölücü üzerinden / via a 1 kΩ / 2 kΩ divider
11ir_left = Pin(19, Pin.IN, Pin.PULL_UP)      # 0 = engel / 0 = obstacle
12ir_right = Pin(20, Pin.IN, Pin.PULL_UP)
13
14STOP_CM, BACK_CM = 25, 12
15CRUISE, TURN = 75, 65                       # hız yüzdesi / speed in percent
16MS_PER_DEG = 4                              # kalibre et / calibrate
17SERVO_CENTER = 90                           # öne bakmıyorsa düzelt / adjust if not facing ahead
18
19def drive(left, right):                     # -100 … 100 (%)
20    in1.value(left > 0); in2.value(left < 0)
21    in3.value(right > 0); in4.value(right < 0)
22    ena.duty_u16(int(min(abs(left), 100) * 655))
23    enb.duty_u16(int(min(abs(right), 100) * 655))
24
25def head(offset):                           # + sol, - sağ / + left, - right
26    angle = max(0, min(180, SERVO_CENTER + offset))
27    servo.duty_ns((544 + angle * 1856 // 180) * 1000)   # 0,544–2,4 ms darbe / 0.544–2.4 ms pulse
28
29def ping_cm():
30    trig.value(1); time.sleep_us(10); trig.value(0)     # 10 µs tetik / 10 µs trigger
31    t = time_pulse_us(echo, 1, 25000)
32    return 400 if t < 0 else t // 58                    # negatif = yankı yok / negative = no echo
33
34def distance_cm():                          # 3 ölçümün ortancası / median of 3 readings
35    r = []
36    for _ in range(3):
37        r.append(ping_cm())
38        time.sleep_ms(60)
39    return sorted(r)[1]
40
41def turn_by(deg):                           # + sola, - sağa / + left, - right
42    if deg > 0:
43        drive(-TURN, TURN)
44    else:
45        drive(TURN, -TURN)
46    time.sleep_ms(int(abs(deg) * MS_PER_DEG))
47    drive(0, 0)
48    time.sleep_ms(100)
49
50def avoid():
51    drive(0, 0)
52    time.sleep_ms(120)
53    front = distance_cm()
54    sides = not ir_left.value() or not ir_right.value()
55    if front >= STOP_CM and not sides:
56        return                              # yanlış alarm / false alarm
57    if front < BACK_CM or sides:            # geri çekil / back off
58        drive(-TURN, -TURN); time.sleep_ms(350); drive(0, 0)
59    best_off, best_cm = 0, 0
60    for off in (70, 35, -35, -70):          # tara / scan
61        head(off)
62        time.sleep_ms(300)
63        cm = distance_cm()
64        if cm > best_cm:
65            best_off, best_cm = off, cm
66    head(0)
67    time.sleep_ms(200)
68    if best_cm < STOP_CM + 10:              # çıkmaz / dead end
69        drive(-TURN, -TURN); time.sleep_ms(400)
70        turn_by(180)
71    else:
72        turn_by(best_off)
73
74drive(0, 0)
75head(0)
76time.sleep(3)                               # yere koyman için süre / time to put it down
77while True:
78    d = ping_cm()
79    left_hit, right_hit = not ir_left.value(), not ir_right.value()
80    if d < STOP_CM or (left_hit and right_hit):
81        avoid()
82    elif left_hit:
83        drive(TURN, -TURN); time.sleep_ms(150)   # sağa kaç / dodge right
84    elif right_hit:
85        drive(-TURN, TURN); time.sleep_ms(150)   # sola kaç / dodge left
86    else:
87        drive(CRUISE, CRUISE)
88    time.sleep_ms(60)                        # ölçümler arası / between readings

Projektideen mit dem hindernisvermeidenden Roboter

  • Radaranzeige: Schick die Winkel und Abstände aus dem Scan über die serielle Schnittstelle und zeichne am Computer einen grünen Radarbildschirm; Details im Artikel Arduino-Radar-Projekt.
  • Linie und Hindernis: Lass den Roboter wie einen Linienfolger-Roboter einer Strecke folgen, um eine Kiste auf dem Weg herumfahren und zur Linie zurückkehren; eine typische Wettbewerbsaufgabe.
  • Wandfolger: Dreh den Sensor dauerhaft um 60 Grad nach rechts und fahr in konstant 20 cm Abstand an der Wand entlang; der erste Schritt zum Lösen von Labyrinthen.
  • Roboter mit Display: Zeig Abstand und Modus auf einem kleinen OLED-Display an; die Fehlersuche wird viel einfacher.
  • Saugroboter-Prototyp: Entwirf einen Putzroboter, der mit zufälliger Richtungswahl, Stoßschaltern und Absturzsensor durch die Wohnung fährt.
  • Intelligenter Blindenstock: Mach aus derselben Abstandsmessung eine Vibrationswarnung für sehbehinderte Menschen; siehe Arduino-Blindenstock.
  • Schul- oder Wettbewerbsprojekt: Präsentiere ihn mit Hindernisparcours, Zeitmessung und einer Vorführung von Automatik- und manuellem Modus; Ideen findest du im Artikel Arduino-Projektideen für Wettbewerbe.

Häufige Fehler und Lösungen

ProblemWahrscheinliche UrsacheLösung
Der Roboter fährt ungebremst gegen die WandDer Sensor liest 0 (Kabelbruch, TRIG und ECHO vertauscht) und der Code wertet das als freien RaumTest-Sketch ausführen, mit der Hand 10–60 cm prüfen, TRIG- und ECHO-Pins kontrollieren
Er schrammt an der Wand, wenn er schräg ankommtDer Schall wird von der schrägen Fläche weggelenktIR-Sensoren an den vorderen Ecken ergänzen, Tempo senken
Der Arduino startet neu, wenn er zum Scannen anhältServo- und Motorstrom lassen die 5-V-Schiene einbrechen470-µF-Kondensator am Servo, volle 18650-Akkus; notfalls Servo aus separater 5-V-Quelle versorgen
Der Roboter hält grundlos an und scanntEine einzelne Fehlmessung oder ein Echo vom BodenSensor auf 8–12 cm Höhe setzen und leicht nach oben neigen; Median-Bestätigung nutzen
Der Roboter dreht zur blockierten SeiteServo andersherum montiert, links und rechts vertauschtSERVO_DIR auf -1 setzen
Drehungen sind viel zu kurz oder zu langMS_PER_DEG nicht kalibriert oder Akku schwachMit turnBy(360) messen und mit vollem Akku neu einstellen
Er dreht in einer Ecke ständig hin und herBeide Seiten liegen nahe an der Schwelle, die Entscheidung kippt ständigSackgassen-Schwelle (STOP_CM + 10) erhöhen oder die zuletzt gewählte Drehrichtung bevorzugen
Der Sensorkopf zucktSchwache Servoversorgung oder SoftwareSerial-InterruptsServokabel kürzen, Kondensator ergänzen, nicht unnötig oft Bluetooth-Befehle senden
Am schnellsten findest du die Ursache eines Problems, wenn du den Roboter am USB lässt, ihn mit frei drehenden Rädern laufen lässt und die Werte von pingCm() im Seriellen Monitor ausgibst. Sind die Werte plausibel, liegt das Problem in der Mechanik oder bei der Stromversorgung. Weitere Gründe, warum ein Arduino ständig neu startet, findest du im Artikel Arduino startet ständig neu.

Weil er Sensoren auslesen, Filtern, Entscheiden und Motorsteuerung in einem Projekt vereint, ist der hindernisvermeidende Roboter der beste Einstieg in die Robotik. Beobachte zuerst mit dem ersten Sketch das Grundverhalten, stimme dann mit der erweiterten Version die Schwellen auf deinen Raum ab und rüste deinen Roboter zum Schluss mit IR-Sensoren und Bluetooth auf. In der BlueGrays-App findest du den hindernisvermeidenden Roboter, das per Bluetooth gesteuerte Auto mit L298N, Abstandsmessung mit dem HC-SR04 und Einparkhilfe als Projekte Schritt für Schritt mit Schaltplänen und Code.

Häufige Fragen

Was braucht man für einen Roboter, der Hindernissen ausweicht?

Für einen einfachen hindernisvermeidenden Roboter brauchst du einen Arduino Uno, einen HC-SR04-Ultraschallsensor, einen SG90-Servo, einen L298N- oder L293D-Motortreiber, einen 2WD-Chassis-Bausatz mit zwei Getriebemotoren und zwei 18650-Akkus. Zwei IR-Hindernissensoren für die Ecken und ein HC-05-Bluetooth-Modul für die Steuerung per Handy kannst du ergänzen.

Geht ein hindernisvermeidender Roboter auch ohne Servo?

Ja. Der Sensor schaut fest nach vorn; sieht der Roboter ein Hindernis, dreht er sich zuerst in eine Richtung und misst erneut, ist der Weg weiter versperrt, dreht er in die andere. Das ist günstiger, aber der Roboter entscheidet langsamer, weil er für jeden Versuch seinen ganzen Körper drehen muss; mit einem Servo findet er die freieste Richtung schon vor dem Drehen.

Warum erkennt der HC-SR04 manche Hindernisse nicht?

Ein Ultraschallsensor misst über das Echo. Weiche Oberflächen wie Vorhänge und Sofas schlucken den Schall, schräge Wände lenken ihn in eine andere Richtung, und dünne Gegenstände wie Stuhlbeine liefern nur ein sehr schwaches Echo. Hindernisse außerhalb oder unterhalb seines etwa 15 Grad breiten Kegels sieht der Sensor ebenfalls nicht; diese Lücken schließt du mit IR-Sensoren.

Bei welchem Abstand sollte der Roboter anhalten?

25 cm sind ein guter Startwert: Weg zwischen zwei Messungen, Bremsweg und die Reserve zum Drehen auf der Stelle ergeben zusammen etwa 20 cm, dazu kommt eine kleine Sicherheitsreserve. Bei schnelleren Robotern und rutschigem Boden erhöhst du auf 30–35 cm; ist ein Hindernis näher als 12 cm, sollte der Roboter zuerst zurücksetzen.

Was ist besser für einen Roboter, L298N oder L293D-Shield?

Der L298N liefert 2 A pro Kanal, ist dadurch robuster und lässt dich die Pins frei wählen. Das L293D-Shield wird auf den Uno gesteckt, spart Verdrahtung und kann vier Motoren treiben, liefert aber dauerhaft nur 0,6 A pro Kanal und belegt viele Pins. Für einen kleinen 2WD-Roboter mit TT-Motoren reichen beide.

Kann man einen hindernisvermeidenden Roboter per Handy steuern?

Ja. Ergänze ein HC-05-Bluetooth-Modul und sende Buchstaben aus einer Fernsteuerungs-App auf einem Android-Handy, um zwischen Automatik- und manuellem Modus zu wechseln. Nutzt du den Ultraschallsensor im manuellen Modus als Kollisionsschutz, kannst du den Roboter nicht gegen die Wand fahren. Mit einem ESP32 brauchst du dank eingebautem Bluetooth kein separates Modul.

Alle Projekte ansehen
HC-SR04 Abstandsmessung (serielle Anzeige)
#27Anfänger

HC-SR04 Abstandsmessung (serielle Anzeige)

Der Sensor HC-SR04 funktioniert wie eine Fledermaus, die im Dunkeln ihren Weg findet: Er sendet einen Ton, den du nicht hörst, lauscht auf das Echo und misst, wie weit der Gegenstand vor ihm entfernt

4.6(45)
68
Roboter zur Hindernisvermeidung
#81Fortgeschritten
Premium

Roboter zur Hindernisvermeidung

Wie wäre es, ein intelligentes Fahrzeug zu bauen, das seinen Weg selbst findet? Der Obstacle Avoiding Robot scannt dank des Ultraschallsensors vor ihm ständig seine Umgebung. Wenn er auf ein Hindernis

5.0(38)
49
Bluetooth-gesteuertes Auto mit L298N
#80Fortgeschritten
Premium

Bluetooth-gesteuertes Auto mit L298N

Mach dein Handy zur Fernbedienung und steuere dein eigenes Roboterauto. Der Arduino empfängt über ein Bluetooth-Modul die Befehle einer Fernsteuer-App auf dem Handy und treibt die Räder über einen Mot

5.0(38)
65
Verwendung von Servomotoren
#35Anfänger
Premium

Verwendung von Servomotoren

Der Arm eines kleinen Servomotors dreht sich nacheinander in drei Stellungen: ganz an den Anfang, genau in die Mitte und ganz ans Ende, mit jeweils zwei Sekunden Pause. Der Arduino sagt dem Motor, bei

4.7(48)
67
Parksensor mit HC-SR04
#28Mittel
Premium

Parksensor mit HC-SR04

Eine Mini-Ausgabe der Einparkhilfe im Auto: Je näher etwas kommt, desto schneller piept es. Der Abstandssensor misst das Hindernis vor ihm; ist es weiter als 20 Zentimeter entfernt, leuchtet die grüne

4.8(64)
84
Reflexion (Nähe) mit TCRT5000 messen
#77Mittel
Premium

Reflexion (Nähe) mit TCRT5000 messen

Mit einem kleinen Sensor, der unsichtbares Licht aussendet und misst, wie viel davon zurückkommt, erkennst du die Fläche vor ihm. Je näher ein Gegenstand oder eine helle Fläche kommt, desto mehr Licht

4.6(33)
43
TCRT5000 Schwarz-Weiß-Untergrunderkennung
#78Mittel
Premium

TCRT5000 Schwarz-Weiß-Untergrunderkennung

Mit dem kleinen Sensor, der bei Linienfolger-Robotern als Auge dient, erkennst du, ob der Boden schwarz oder weiß ist. Der TCRT5000 schickt unsichtbares Licht auf den Boden und prüft, wie viel davon z

4.7(40)
49
ESP32 HC-SR04 Ultraschall-Entfernungsmesser
#116Mittel

ESP32 HC-SR04 Ultraschall-Entfernungsmesser

Der Ultraschallsensor HC-SR04 berechnet die Entfernung, indem er die Zeit misst, die die von ihm ausgesendete Schallwelle benötigt, um auf ein Objekt zu treffen und zurückzukehren (Time of Flight). In diesem Projekt senden wir mit dem ESP32 einen 10-Mikrosekunden-Impuls an den TRIG-Pin und lesen die vom ECHO-Pin kommende Echozeit mit `pulseIn()`. Die berechnete Entfernung geben wir in cm auf einem 16x2-I2C-LCD aus. Da der HC-SR04 mit 5 V betrieben wird, ist ein **Spannungsteiler** am ECHO-Pin zw

4.9(28)
44