![]() |
Raspberry-Pi-Projekte: Ultraschall-Entfernungsmessung mit HC-SR04Prof. Jürgen Plate |
nter Ultraschall versteht man akustische oder mechanische Schwingungen oberhalb von 20 kHz. Der technische Anwendungsbereich von Ultraschall bei Über\-tra\-gun\-gen im Medium Luft reicht von ca. 30 kHz bis etwa 300 kHz. Geräusche im Ultraschallbereich werden auch von ausströmenden Gasen, Sprüh- oder Korona-Entladungen verursacht und können von Ultraschall-Sensoren detektiert werden.
Ultraschall-Sensoren werden als Abstands- oder Entfernungs-Sensoren verwendet. Sie bestehen aus einem Ultraschall-Lautsprecher und einem darauf abgestimmten Ultraschall-Mikrofon, beides meistens in Piezo-Technik. Der Lautsprecher sendet einen kurzen Schallimpuls aus. Dessen Laufzeit durch den Raum, bis er vom Mikrofon aufgenommen wird, ist proportional zum Weg, den das Ultraschallsignal zurückgelegt hat. Die zu erfassenden Objekte können auch optisch transparent (Glas) oder flüssig sein. Schallabsorbierende Oberflächen und Medien reflektieren den Schall diffus und beeinflussen somit den Erfassungsbereich des US-Sensors. Die Sensoren sind weitgehend feuchtigkeitsunempfindlich. Sehr starke Luftströme mit Windgeschwindigkeiten über 10 m/s können die Ultraschallkeule ablenken. Ebenso beeinträchtigen heiße Objekte die Ausbreitung des Ultraschalls.
Die Schallgeschwindigkeit (etwa 320\dots 350 m/Sekunde; 343 m/s für 20 °C in Luft) ist relativ langsam, misst man sie an der Geschwindigkeit elektronischer Schaltkreise. Sie lässt sich daher leicht elektronisch auswerten. Probleme gibt es eher durch mangelhafte Reflexion am Messobjekt, durch störende Echos und durch Ultraschall-Signale fremder Quellen, weshalb die Ultraschall-Entfernungsmessung als nicht besonders zuverlässig gilt. Ein sehr typisches Einsatzgebiet ist die Einparkhilfe bei Mittelklassefahrzeugen, erkennbar an den diskret in den Stossfänger eingelassenen Ultraschallwandlern.
Ein Vorteil von US-Sensoren ist ihre Unempfindlichkeit gegenüber Umgebungsschall, da sie in einem weit höheren Frequenzbereich als der Hörbereich arbeiten. Da Ultraschallwellen eine Wellenlänge von nur 1 cm haben können, sind auch Echos von schmalen Objekten feststellbar. Sollen große Reichweiten erzielt werden, müssen Ultraschallsender und Empfänger zueinander in Sichtkontakt ausgerichtet sein bzw. sich auf derselben Achse befinden. Entfernungen bis zu 10 ... 30 m können durchschnittlich damit überbrückt werden.
Das Ultraschall Modul HC-SR04, das es inzwischen sogar bei Amazon und Ebay gibt, eignet sich zur Entfernungsmessung im Bereich zwischen zwei Zentimetern und ca. drei Metern mit einer Auflösung von etwa 30 Millimetern. Es benötigt nur eine einfache Versorgungsspannung von +5 Volt bei einer Stromaufnahme von etwa 15 mA. Der Öffnungswinkel des Ultraschallsensors beträgt 15°. Nach Triggerung mit einer fallenden Flanke (TTL-Pegel) misst das Modul innerhalb von 20 ms selbstständig die Entfernung und wandelt diese in eine Art PWM-Signal um.

Das Sensor-Modul besitzt nur vier Anschlüsse:
| Pin | Funktion |
|---|---|
| 1 | VCC (Versorgungsspannung +5V) |
| 2 | TRIG (Triggereingang, TTL-Pegel) |
| 3 | ECHO (Ausgang, TTL-Pegel) |
| 4 | GND (Masse) |
Das Auslösen der Messung geschieht durch die fallende Flanke am Triggereingang eines mindestens 10 Mikrosekunden langen High-Impulses. Das Modul sendet darauf ein 40-kHz-Burst-Signal aus acht Impulsen.

Danach geht der Ausgang sofort auf High-Pegel, und das Modul wartet auf den Empfang des Echos. Sobald ein Echo detektiert wurde geht der Ausgang wieder auf Low. Die gemessene Entfernung ist proportional zur Echo-Puls-Weite am Ausgang. 20 ms nach der Triggerung kann eine weitere Messung stattfinden. Wird jedoch kein Echo empfangen, bleibt der Ausgang für insgesamt 38 ms auf High-Pegel und zeigt so den Misserfolg an. Das Verhalten läßt sich auch gut auf dem Oszilloskop beobachten.

Die besten Messergebnisse ergeben sich, wie bei allen US-Sensoren, bei Reflexion an glatten, ebenen Flächen. Bei Distanzen bis 1 m ist das Material der Fläche recht unkritisch. Auch recht kleine Objekte werden zuverlässig erkannt. Kleine Objekte werden noch auf eine Distanz von ca. 30 cm sicher erfassen. Die gemessene Entfernung kann durch die folgende Formel berechnet werden:
Entfernung = (Schallgeschwindigkeit * Laufzeit)/2 [m, s]Der Faktor 2 kommt hinzu, weil das Signal ja den doppelten Weg zurücklegt: hin zum Objekt und wieder zurück. Unter Berücksichtigung dieser Tatsache und wenn man die Schallgeschwindigkeit bei ca. 20° C verwendet, kann die Formel vereinfacht werden:
Entfernung = Laufzeit [μs] / 58,3 [cm]Ein weiterer Faktor ist die Temperaturabhängigkeit der Schallgeschwindigkeit in der Luft. Näherungsweise kann man die Schallgeschwindigkeit in Abhängigkeit der Temperatur im Bereich von -20° C bis +40° C mit folgender Formel berechnen:
v = 331,5 + 0,6 * t [m/s]wobei t die Umgebungstemperatur in Grad Celsius ist.
Für 20° C Raumtemperatur ergibt sich also: v = 331,5 + ( 0,6 * 20 ) = 343,5 [m/s]. Die folgende Tabelle enthält einige Werte die rechnerisch für die Laufzeit zu erwarten sind:
| Entfernung [m] | Laufzeit [ms] bei 20° C | Laufzeit [ms] bei 0° C |
|---|---|---|
| 0,02 | 0,117 | 0,121 |
| 0,10 | 0,583 | 0,603 |
| 0,50 | 2,915 | 3,017 |
| 1,00 | 5,831 | 6,033 |
| 2,00 | 11,662 | 12,066 |
| 3,00 | 17,492 | 18,100 |
Da das Ultraschallmodul mit einer Versorgungsspannung von 5 V arbeitet, wird am Ausgang ECHO des HC-SR04 ein Spannungsteiler benötigt, damit der GPIO-Eingang des Raspberry Pi nicht mehr als 3,3 V Eingangsspannung bekommt, da er sonst zerstört werden könnte. Rein willkürlich wurden zwei GPIO-Pins für TRIG und ECHO ausgewählt:

Die maximale Spannung Ua an Pin 13 ergibt sich bei max. 5 V (Ue) am ECHO-Ausgang des HC-SR04 nach dem Ohm'schen Gesetz zu:
Ua = (Ue * 6,8)/(3,9 + 6,8) = 34/10,7 ~ 3,18 [V]Prinzipiell könnte man die Widerstände und die Verbindungskabel zum RasPi am HC-SR04 anlöten. Dann kann das Modul aber nicht mehr für andere Zwecke (z. B. Anschluss am Arduino) verwendet werden. Deshalb habe ich eine kleine Adapter-Platine mit Buchsen- und Stiftleiste gelötet.

Zur Ansteuerung des Ultraschallsensors und zum Messen von Entfernungen verwendet das Programm folgenden Algorithmus: Zuerst gibt es auf den Trigger-Pin einen kurzen "1"-Impuls. Dadurch wird der Sensor aktiv und sendet das Ultraschallsignal aus. Gleichzeitig speichert das Programm den Zeitpunkt, zu dem das Echo-Signal auf "1" ging. Detektiert der Empfänger das reflektierte Ultraschallsignal, setzt der Sensor seinen Echo-Pin wieder auf "0". Diesen Pegelwechsel registriert das Programm ebenfalls. Aus dem Zeitunterschied zwischen Senden (steigende Flanke von ECHO) und Empfangen (fallende Flanke von ECHO) sowie der Schallgeschwindigkeit lässt sich die Distanz zum Objekt errechnen. So einfach das kling, so hat die Programmierung doch einige Haken - allen voran die Tatsache, dass bei einem Multitasking-Betriebssystem wie es Raspbian/Linux ist, es ungünstig ist, per Programm in einer Schleife auf die Änderung des Pegels eines Ports zu warten. Dieses sogenannte Busy-Waiting nimmt nämlich beträchtlich Rechenzeit auf und behindert andere laufende Programme. Bei Controllern ohne Betriebssystem (z. B. Arduino) ist das hingegen kein Problem, weil ja immer nur ein einziges Programm läuft.
Leider findet man im Web und sogar in Fachzeitschriften (z. B. "Elektor", Mai 2016) immer nur die Lösung mit Busy-Waiting. Nur aus diesem Grund zeige ich hier eine solche Variante, die jedoch noch eine weitere Eigenschaft besitzt. Da die Echos manchmal um diverse Ecken herum ankommen, gibt es ab und zu Fehlmessungen. Um das auszugleichen, macht das Programm mehrere Messungen und bildet dann einen Mittelwert. Zu Kontrollzwecken werden die Einzelmessungen auch ausgegeben (wer das nicht will, kommentiert die entsprechende print-Anweisung aus). So ein Messzyklis sieht dann folgendermaßen aus:
Messwert: 59.0 Messwert: 60.7 Messwert: 60.5 Messwert: 60.5 Messwert: 63.1 Messwert: 60.1 Messwert: 60.8 Messwert: 59.8 Messwert: 59.7 Messwert: 59.3 Messwert: 60.5 Messwert: 60.6 Messwert: 60.5 Messwert: 60.6 Messwert: 59.2 Messwert: 59.4 Messwert: 59.2 Messwert: 60.2 Messwert: 58.6 Messwert: 59.8 Messwert: 59.9 Messwert: 60.0 Messwert: 60.2 Messwert: 60.2 Messwert: 61.3 Range = 60.1 cm
Doch nun zum Programm mit Busy-Waiting. Wenn es läuft, liegt der "load average" bei 0,14 und das Programm mimmt knapp 6% der Rechenzeit auf.
#!/usr/bin/python
import RPi.GPIO as GPIO
import time
import datetime
# GPIOs fuer den US-Sensor
TRIG = 17
ECHO = 27
# Dauer Trigger-Impuls
PULSE = 0.00001
# Anzahl Messwerte fuer Mittelwertbildung
BURST = 25
# Schallgeschwindigkeit/2
SPEED_2 = 17015
# BCM GPIO-Referenen verwenden (anstelle der Pin-Nummern)
# und GPIO-Eingang definieren
GPIO.setmode(GPIO.BCM)
GPIO.setup(TRIG,GPIO.OUT)
GPIO.setup(ECHO,GPIO.IN)
GPIO.output(TRIG, False)
time.sleep(1) # Setup-Zeit fuer Sensor
def measure(): # Messung ausfuehren
GPIO.output(TRIG, True) # Trigger-Impuls
time.sleep(PULSE)
GPIO.output(TRIG, False)
starttime = time.time() # Zeit initialisieren
stopp = starttime
start = starttime
# (Busy) Warten auf steigende Flanke, max. 2 Sekunden
while GPIO.input(ECHO) == 0 and start < starttime + 2:
start = time.time() # Startzeit
# (Busy) Warten auf fallende Flanke, max. 2 Sekunden
while GPIO.input(ECHO) == 1 and stopp < starttime + 2:
stopp = time.time() # Endezeit
delta = stopp - start # Zeitdifferenz und Entfernung berechnen
distance = delta * SPEED_2
return distance
def measure_range(): # Bildet Mittelwert von BURST Messungen
values = []
sum = 0
for i in range(0, BURST):
values.append(measure()) # Messung starten
sum = sum + values[i] # Wert im Array speichern und aufsummieren
print("Messwert: %1.1f" % values[i]) # Kontrollausgabe
time.sleep(0.05)
return sum/BURST; # Mittelwert zurueckgeben
# do it
try:
while True:
Distance = measure_range()
print("Range = %1.1f cm" % Distance)
time.sleep(1)
# reset GPIO settings if user pressed Ctrl+C
except KeyboardInterrupt:
print("Bye")
GPIO.cleanup()
Das zweite Programm verwendet für die Flankendetektierung die Interruptsteuerung des GPIO-Moduls. Mit dem Befehl GPIO.add_event_detect(ECHO, GPIO.BOTH, callback=measure) wird die Funktion measure() als Interrupt-Serviceroutine für steigende und fallende Flanke (GPIO.BOTH) eingetragen. Innerhalb der Routine wird dann der ECHO-Port abgefragt. Hat er den Wert "1", war eine steigende Flanke Auslöser und die globale Variable start speichert die aktuelle Zeit. Im anderen Fall war die fallende Flanke der Auslöser und es werden die Zeitdifferenz und die Entfernung berechnet (ebenfalls globale Variablen). Alles andere ist wie gehabt. Wenn das zweite Programm läuft, liegt der "load average" bei 0,2 und der Rechzeit-Anteil bei 1%. Ein weiterer Vorteil: Das Programm ist auch noch kürzer und schneller als das erste.
#!/usr/bin/python
import RPi.GPIO as GPIO
import time
import datetime
# GPIOs fuer den US-Sensor
TRIG = 17
ECHO = 27
# Dauer Trigger-Impuls
PULSE = 0.00001
# Anzahl Messwerte fuer Mittelwertbildung
BURST = 25
# Schallgeschwindigkeit/2
SPEED_2 = 17015
# BCM GPIO-Referenen verwenden (anstelle der Pin-Nummern)
# und GPIO-Eingang definieren
GPIO.setmode(GPIO.BCM)
GPIO.setup(TRIG,GPIO.OUT)
GPIO.setup(ECHO,GPIO.IN)
GPIO.remove_event_detect(ECHO)
GPIO.output(TRIG, False)
time.sleep(1) # Setup-Zeit fuer Sensor
stopp = 0 # Variableninit
start = 0
distance = 0
def pulse(): # Funktion zum Starten der Messung
global start
global stopp
global distance
GPIO.output(TRIG, True) # Triggerimpuls erzeugen
time.sleep(PULSE)
GPIO.output(TRIG, False)
stopp = 0 # Werte auf 0 setzen
start = 0
distance = 0 # und Event starten
def measure(x): # Callback-Funktion fuer ECHO
global start
global stopp
global distance
if GPIO.input(ECHO) == 1: # steigende Flanke, Startzeit speichern
start = time.time()
else: # fallende Flanke, Endezeit speichern
stopp = time.time()
delta = stopp - start # Zeitdifferenz und Entfernung berechnen
distance = delta * SPEED_2
def measure_range(): # Bildet Mittelwert von BURST Messungen
values = []
sum = 0
for i in range(0, BURST):
pulse() # Messung starten
time.sleep(0.040) # Warten, bis Messung zuende
values.append(distance) # Wert im Array speichern und aufsummieren
sum = sum + values[i]
print("Messwert: %1.1f" % distance) # Kontrollausgabe
time.sleep(0.05)
return sum/BURST; # Mittelwert zurueckgeben
# do it
try:
GPIO.add_event_detect(ECHO, GPIO.BOTH, callback=measure)
while True:
Dist = measure_range()
print("Range = %1.1f cm" % Dist)
time.sleep(1)
# reset GPIO settings if user pressed Ctrl+C
except KeyboardInterrupt:
print("Bye!")
GPIO.cleanup()