This parking assistant uses ultrasonic sensors and an ESP32 to detect obstacles while reversing or parking. The system monitors distances from rear and front bumpers, displaying real-time measurements on an OLED screen and providing audio feedback through a piezo buzzer that increases in urgency as obstacles approach.
The guide includes a complete wiring diagram, parts list, and step-by-step assembly instructions for mounting sensors to the bumpers, connecting the automotive power supply with proper fusing, integrating the reverse-gear detection circuit, and programming the ESP32 firmware. Builders will learn to set up I2C communication, configure ultrasonic sensor timing, and implement distance-based alert logic.
Wiring diagram

Gather all the parts
Assemble it in 8 steps
1. Stromversorgung sicher vorbereiten
Klemme vor Arbeiten an der Fahrzeugverkabelung den Minuspol der 12-V-Batterie ab. Setze den 1-A-Sicherungshalter power_fuse so nah wie möglich an die 12-V-Abnahme und verbinde seine Eingangsseite mit einer fachkundig geprüften 12-V-Zündungsplusleitung, nicht mit dauerhaftem Batterieplus. Prüfe, dass die Versorgung bei ausgeschalteter Zündung aus ist; eine konkrete Toyota-Leitung oder Sicherungsnummer ist hier nicht festgelegt. Verbinde die Ausgangsseite mit VIN+ des Wandlers vehicle_buck; VIN- des Wandlers kommt an Fahrzeugmasse.
- Montiere Sicherung und Wandler trocken im Kofferraum hinter einer Verkleidung.
- Verwende gecrimpte oder gelötete, isolierte Verbindungen und befestige die Kabel gegen Scheuern.
- Arbeite nie an ungesicherter Fahrzeugspannung: ein kurzgeschlossenes Kabel kann heiß werden oder einen Fahrzeugbrand verursachen.
- Leitungen von Airbags, Gurtstraffern und gelben Steckern nicht berühren; dort darf diese Schaltung nicht angeschlossen werden.
2. Controller und Sensorstrom anschließen
Verbinde den 5-V-Ausgang VOUT+ des vehicle_buck mit VIN beziehungsweise 5V des ESP32 und mit VCC beider Sensoren rear_sensor_left und rear_sensor_right. Verbinde VOUT- mit GND des ESP32 und mit GND beider Sensoren. Die Sensoren erhalten damit 5 V, der ESP32 erzeugt daraus seine sichere 3,3-V-Logik.
- Lege alle Elektronikteile in ein festes, spritzwassergeschütztes Gehäuse im Kofferraum.
- Die Sensorleitungen erst nach dem mechanischen Einbau auf die richtige Länge bringen.
- Die 5-V-Leitung niemals an einen 3,3-V-Pin des ESP32 anschließen; falsche Versorgung kann den Controller beschädigen.
3. Die hinteren Sensoren am Stoßfänger befestigen
Bohre zwei passende Öffnungen links und rechts in den hinteren Stoßfänger und setze die Schallköpfe von rear_sensor_left und rear_sensor_right waagerecht ein. Lasse zwischen den Sensoren und dem Boden genug Abstand, damit sie nicht auf die Straße messen. Führe ihre Kabel durch vorhandene Gummitüllen in den Kofferraum.
- Vor dem Bohren von innen prüfen, dass hinter dem Stoßfänger keine Verstrebung oder Leitung getroffen wird.
- Die beiden Sensoren möglichst gleich hoch und gerade nach hinten ausrichten.
- Ein Sensor, der zum Boden, Auspuff oder Reifen zeigt, meldet falsche Hindernisse und ist keine zuverlässige Einparkhilfe.
4. Die sicheren Sensorsignale verdrahten
Verbinde TRIG von rear_sensor_left mit GPIO25 und TRIG von rear_sensor_right mit GPIO26. Verbinde ECHO links zuerst mit echo_left_top; dessen anderer Anschluss geht zu GPIO27. Verbinde echo_left_bottom zwischen denselben GPIO27-Anschluss und GND. Wiederhole dies rechts: ECHO zu echo_right_top, dessen anderer Anschluss zu GPIO33 und echo_right_bottom von diesem Punkt zu GND. So machen die 10-kΩ- und 20-kΩ-Widerstände aus dem 5-V-Echo ein ungefährliches 3,3-V-Signal.
- Halte die Widerstände zusammen mit dem ESP32 im Gehäuse, nicht draußen am Stoßfänger.
- Markiere links und rechts vor dem Schließen der Verkleidung.
- ECHO nicht direkt mit dem ESP32 verbinden: die 5 V vom Sensor können den 3,3-V-Eingang beschädigen.
5. Display, Ton und Fronttaste montieren
Montiere parking_oled gut sichtbar am Armaturenbrett oder nahe dem Innenspiegel. Verbinde VCC mit 3V3, GND mit GND, SDA mit GPIO21 und SCL mit GPIO22. Verbinde SIG von warning_buzzer mit GPIO14 und seinen zweiten Anschluss mit GND. Verbinde einen Anschluss von front_enable_button mit GPIO13 und den anderen mit GND.
- Wähle für das Display einen Ort, der die Sicht auf die Straße nicht verdeckt.
- Die Taste schaltet die Frontsensoren für maximal 15 Sekunden ein; erneutes Drücken schaltet sie aus. Im Rückwärtsgang wird die Taste ignoriert; hinten hat Vorrang.
- VCC und GND am Display nicht vertauschen, vertauschte Versorgung kann das Display beschädigen.
6. Rückwärtsgang sicher erkennen
Verbinde die positive Leitung der Rückfahrleuchte über reverse_input_resistor mit Anode des reverse_opto. Verbinde die Kathode des reverse_opto mit Fahrzeugmasse. Auf der sicheren ESP32-Seite verbindest du Collector mit GPIO32 und Emitter mit GND. Der Optokoppler trennt das 12-V-Signal des Autos von der empfindlichen Logik.
- Miss die Leitung der Rückfahrleuchte mit einem Multimeter: sie darf nur bei eingelegtem Rückwärtsgang 12 V führen.
- Setze den Abzweig mit einem geeigneten Fahrzeug-Steckverbinder, nicht mit blank verdrilltem Draht.
- Das Rückfahrlicht niemals direkt an GPIO32 anschließen; 12 V und Spannungsspitzen können den ESP32 sofort beschädigen.
7. Vor dem Schließen testen
Schließe den Batterie-Minuspol wieder an. Prüfe zuerst, dass der ESP32 und das Display mit 5 V vom Wandler laufen. Lege bei gesichertem Fahrzeug und getretener Bremse den Rückwärtsgang ein: das Display muss die zwei hinteren Abstände zeigen und der Summer bei einem nahen Karton schneller piepen. Erst danach Verkleidungen schließen und alle Kabel mit Kabelbindern sichern.
- Teste zunächst mit einem Karton, nicht mit einer Person oder einem Fahrzeug.
- Die Einparkhilfe ist eine Zusatzhilfe; beim Rangieren weiterhin Kamera, Spiegel und direkte Sicht benutzen.
- Die Anzeige ersetzt keine Sichtprüfung: kleine, sehr schmale oder schallabsorbierende Hindernisse können Ultraschallsensoren übersehen.
8. Zwei Frontsensoren ergänzen
Montiere front_sensor_left und front_sensor_right gerade nach vorne. Nur die Sensorköpfe sind wasserfest; die Platinen müssen trocken und geschützt bleiben. Verbinde beide VCC mit geregelten 5 V und beide GND mit ESP32-GND. TRIG vorne links geht an GPIO18, TRIG vorne rechts an GPIO19. ECHO links geht über echo_front_left_top (10 kΩ) an GPIO16. Vom selben Verbindungspunkt geht echo_front_left_bottom (20 kΩ) an GND. ECHO rechts geht über echo_front_right_top (10 kΩ) an GPIO17. Vom selben Verbindungspunkt geht echo_front_right_bottom (20 kΩ) an GND. Die Widerstände nahe am ESP32 montieren. Vorhandene hintere Anschlüsse bleiben unverändert.
- Vor dem Bohren Leitungen und Verstrebungen prüfen. Kabel fern von Hitze, beweglichen Teilen und Airbags führen und gegen Scheuern sichern.
- Zuerst am Tisch mit USB testen: Taste drücken, vorne links und rechts mit einem Karton in 50 bis 150 cm Abstand prüfen. Nach 15 Sekunden oder erneutem Tastendruck muss BEREIT erscheinen. Prüfe den Dauerton bei 30 cm und den Unsicherheitshinweis bei fehlendem Echo; ungerundete Werte steuern den Warnton mit 5 cm Hysterese beim Entwarnen.
- GPIO32 mit GND verbinden simuliert Rückwärtsgang: hinten muss Vorrang haben. Nach Lösen müssen vorne 15 Sekunden lang Entfernungen erscheinen. Den Fahrzeugwandler beim USB-Test abtrennen.
- Die ECHO-Ausgänge niemals direkt an GPIO16 oder GPIO17 anschließen. Jeder Sensor benötigt seinen eigenen Spannungsteiler.
- Unter etwa 25 cm sind Messwerte unzuverlässig. -- bedeutet keine gültige Messung, nicht freie Fahrt. Nach drei ungültigen Messungen eines aktiven Sensors erscheint MESSUNG UNSICHER. Das kann auch bei freiem Raum ohne Echo auftreten und ist keine eindeutige Fehlerdiagnose. Kein zertifiziertes Fahrzeug-Sicherheitssystem.
- Vor Arbeiten an der Fahrzeugelektrik die Versorgung trennen. Einbau und geschützte Fahrzeugversorgung im Zweifel fachkundig prüfen lassen.
Review all connections
1. Connections between "power_fuse" and "ESP32"
2. Connections between "vehicle_buck" and "ESP32"
3. Connections between "rear_sensor_left" and "ESP32"
4. Connections between "echo_left_bottom" and "ESP32"
5. Connections between "rear_sensor_right" and "ESP32"
6. Connections between "echo_right_bottom" and "ESP32"
7. Connections between "parking_oled" and "ESP32"
8. Connections between "warning_buzzer" and "ESP32"
9. Connections between "front_enable_button" and "ESP32"
10. Connections between "reverse_input_resistor" and "ESP32"
11. Connections between "reverse_opto" and "ESP32"
12. Connections between "echo_left_top" and "ESP32"
13. Connections between "echo_right_top" and "ESP32"
14. Connections between "vehicle_battery" and "ESP32"
15. Connections between "front_sensor_left" and "ESP32"
16. Connections between "echo_front_left_top" and "ESP32"
17. Connections between "echo_front_left_bottom" and "ESP32"
18. Connections between "front_sensor_right" and "ESP32"
19. Connections between "echo_front_right_top" and "ESP32"
20. Connections between "echo_front_right_bottom" and "ESP32"
Deploy the firmware
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
constexpr int LEFT_TRIG_PIN = 25;
constexpr int RIGHT_TRIG_PIN = 26;
constexpr int LEFT_ECHO_PIN = 27;
constexpr int RIGHT_ECHO_PIN = 33;
constexpr int FRONT_LEFT_TRIG_PIN = 18;
constexpr int FRONT_RIGHT_TRIG_PIN = 19;
constexpr int FRONT_LEFT_ECHO_PIN = 16;
constexpr int FRONT_RIGHT_ECHO_PIN = 17;
constexpr int REVERSE_PIN = 32;
constexpr int BUZZER_PIN = 14;
constexpr int FRONT_BUTTON_PIN = 13;
constexpr int OLED_SDA_PIN = 21;
constexpr int OLED_SCL_PIN = 22;
constexpr uint8_t SCREEN_WIDTH = 128;
constexpr uint8_t SCREEN_HEIGHT = 64;
constexpr unsigned long SAMPLE_INTERVAL_MS = 65;
constexpr unsigned long FRONT_HOLD_MS = 15000;
constexpr unsigned long ECHO_TIMEOUT_US = 25000;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
int lastLeftCm = -2;
int lastRightCm = -2;
bool lastUncertain = false;
bool buzzerRunning = false;
int warningLevel = 0;
unsigned long lastSampleMs = 0;
unsigned long buzzerOffAtMs = 0;
unsigned long nextBuzzerAtMs = 0;
int readDistanceCm(int trigPin, int echoPin) {
digitalWrite(trigPin, LOW);
delayMicroseconds(3);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
const unsigned long pulseLength = pulseIn(echoPin, HIGH, ECHO_TIMEOUT_US);
if (pulseLength == 0) {
return -1;
}
const int distance = static_cast<int>(pulseLength / 58UL);
if (distance < 25 || distance > 400) {
return -1;
}
return distance;
}
int displayDistance(int cm) {
if (cm < 0) {
return -1;
}
return ((cm + 2) / 5) * 5;
}
void drawDistanceCell(int x, int y, int cm, bool active) {
display.setCursor(x, y);
if (!active) display.print("AUS");
else if (cm < 0) display.print("-- cm");
else { display.print(cm); display.print(" cm"); }
const int barY = y + 9;
display.drawRect(x, barY, 43, 6, SSD1306_WHITE);
if (active && cm >= 0) {
const int fill = map(200 - constrain(cm, 25, 200), 0, 175, 0, 41);
if (fill > 0) display.fillRect(x + 1, barY + 1, fill, 4, SSD1306_WHITE);
}
}
void drawOverview(int mode, int leftCm, int rightCm, bool uncertain) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(uncertain ? "MESSUNG UNSICHER" :
mode == 2 ? "HINTEN AKTIV" :
mode == 1 ? "VORNE AKTIV" : "BEREIT");
display.setCursor(37, 10);
display.print("LINKS RECHTS");
display.setCursor(0, 22);
display.print("VORN");
drawDistanceCell(37, 20, leftCm, mode == 1);
drawDistanceCell(85, 20, rightCm, mode == 1);
display.drawLine(0, 38, 127, 38, SSD1306_WHITE);
display.setCursor(0, 46);
display.print("HINT");
drawDistanceCell(37, 44, leftCm, mode == 2);
drawDistanceCell(85, 44, rightCm, mode == 2);
display.display();
}
void drawSensorScreen(int leftCm, int rightCm, bool front, bool uncertain = false) {
drawOverview(front ? 1 : 2, leftCm, rightCm, uncertain);
}
void drawIdleScreen(bool frontNotice) {
drawOverview(0, -1, -1, false);
}
void stopBuzzer() {
if (buzzerRunning) noTone(BUZZER_PIN);
buzzerRunning = false;
buzzerOffAtMs = 0;
}
void startBuzzer() {
if (!buzzerRunning) tone(BUZZER_PIN, 4000);
buzzerRunning = true;
}
void updateBuzzer(int nearestCm, unsigned long now) {
int target = 0;
if (nearestCm >= 0) {
target = nearestCm <= 30 ? 4 : nearestCm <= 50 ? 3 :
nearestCm <= 80 ? 2 : nearestCm <= 120 ? 1 : 0;
// More urgent warnings immediately; reduce urgency only 5 cm past
// the old threshold. No averaging that could hide a close obstacle.
const int limits[] = {0, 120, 80, 50, 30};
if (target < warningLevel && nearestCm <= limits[warningLevel] + 5)
target = warningLevel;
}
if (target != warningLevel) {
stopBuzzer();
nextBuzzerAtMs = now;
warningLevel = target;
}
if (warningLevel == 0) {
stopBuzzer();
return;
}
if (warningLevel == 4) {
startBuzzer();
buzzerOffAtMs = 0;
return;
}
if (buzzerRunning && static_cast<long>(now - buzzerOffAtMs) >= 0)
stopBuzzer();
const unsigned long gapMs = warningLevel == 3 ? 110 : warningLevel == 2 ? 260 : 550;
if (static_cast<long>(now - nextBuzzerAtMs) >= 0) {
startBuzzer();
buzzerOffAtMs = now + 70;
nextBuzzerAtMs = now + gapMs;
}
}
void setup() {
pinMode(LEFT_TRIG_PIN, OUTPUT);
pinMode(RIGHT_TRIG_PIN, OUTPUT);
pinMode(LEFT_ECHO_PIN, INPUT);
pinMode(RIGHT_ECHO_PIN, INPUT);
pinMode(FRONT_LEFT_TRIG_PIN, OUTPUT);
pinMode(FRONT_RIGHT_TRIG_PIN, OUTPUT);
digitalWrite(FRONT_LEFT_TRIG_PIN, LOW);
digitalWrite(FRONT_RIGHT_TRIG_PIN, LOW);
pinMode(FRONT_LEFT_ECHO_PIN, INPUT);
pinMode(FRONT_RIGHT_ECHO_PIN, INPUT);
pinMode(REVERSE_PIN, INPUT_PULLUP);
pinMode(FRONT_BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(LEFT_TRIG_PIN, LOW);
digitalWrite(RIGHT_TRIG_PIN, LOW);
Wire.begin(OLED_SDA_PIN, OLED_SCL_PIN);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
drawIdleScreen(false);
}
void loop() {
const unsigned long now = millis();
const bool reverseActive = digitalRead(REVERSE_PIN) == LOW;
static bool previousReverse = false;
static bool frontTimerActive = false;
static unsigned long frontStartedMs = 0;
static int previousMode = -1;
static bool measureLeft = true;
static int leftCm = -1;
static int rightCm = -1;
static int nearestCm = -1;
static uint8_t leftInvalid = 0;
static uint8_t rightInvalid = 0;
static bool buttonRaw = false;
static bool buttonStable = false;
static unsigned long buttonChangedMs = 0;
const bool pressed = digitalRead(FRONT_BUTTON_PIN) == LOW;
if (pressed != buttonRaw) {
buttonRaw = pressed;
buttonChangedMs = now;
}
if (now - buttonChangedMs >= 30 && buttonStable != buttonRaw) {
buttonStable = buttonRaw;
if (buttonStable && !reverseActive) {
frontStartedMs = now;
frontTimerActive = !frontTimerActive;
}
}
if (previousReverse && !reverseActive) {
frontStartedMs = now;
frontTimerActive = true;
}
previousReverse = reverseActive;
if (frontTimerActive && now - frontStartedMs >= FRONT_HOLD_MS) {
frontTimerActive = false;
}
const int mode = reverseActive ? 2 : (frontTimerActive ? 1 : 0);
if (mode != previousMode) {
stopBuzzer();
nearestCm = -1;
warningLevel = 0;
leftInvalid = rightInvalid = 0;
lastUncertain = false;
leftCm = rightCm = -1;
lastLeftCm = lastRightCm = -2;
measureLeft = true;
nextBuzzerAtMs = now;
if (mode == 0) drawIdleScreen(false);
else drawSensorScreen(-1, -1, mode == 1);
previousMode = mode;
}
// Only one sensor transmits per slot, avoiding mutual echoes.
if (mode != 0 && now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
lastSampleMs = now;
const bool front = mode == 1;
if (measureLeft) {
leftCm = readDistanceCm(
front ? FRONT_LEFT_TRIG_PIN : LEFT_TRIG_PIN,
front ? FRONT_LEFT_ECHO_PIN : LEFT_ECHO_PIN);
leftInvalid = leftCm < 0 ? min(3, int(leftInvalid) + 1) : 0;
} else {
rightCm = readDistanceCm(
front ? FRONT_RIGHT_TRIG_PIN : RIGHT_TRIG_PIN,
front ? FRONT_RIGHT_ECHO_PIN : RIGHT_ECHO_PIN);
rightInvalid = rightCm < 0 ? min(3, int(rightInvalid) + 1) : 0;
const int shownLeft = displayDistance(leftCm);
const int shownRight = displayDistance(rightCm);
const bool uncertain = leftInvalid >= 3 || rightInvalid >= 3;
if (shownLeft != lastLeftCm || shownRight != lastRightCm || uncertain != lastUncertain) {
drawSensorScreen(shownLeft, shownRight, front, uncertain);
lastLeftCm = shownLeft;
lastRightCm = shownRight;
lastUncertain = uncertain;
}
nearestCm = -1;
if (leftCm >= 0) nearestCm = leftCm;
if (rightCm >= 0 && (nearestCm < 0 || rightCm < nearestCm)) nearestCm = rightCm;
}
measureLeft = !measureLeft;
}
if (mode != 0) updateBuzzer(nearestCm, millis());
delay(5);
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




