Raspberry-Pi-Projekte: Ultraschall-Entfernungsmessung mit HC-SR04

Prof. Jürgen Plate

Raspberry Pi: Ultraschall-Entfernungsmessung mit HC-SR04

Allgemeines

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 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:

PinFunktion
1VCC (Versorgungsspannung +5V)
2TRIG (Triggereingang, TTL-Pegel)
3ECHO (Ausgang, TTL-Pegel)
4GND (Masse)

Die Messung wird über den Trigger-Anschluss gestartet. Das Auslösen der Messung geschieht durch die fallende Flanke am Triggereingang eines mindestens 10 Mikrosekunden langen High-Impulses. Das Ultraschallmodul sendet daraufhin nach etwa 250 Mikrosekunden ein 40-kHz-Burst-Signal aus acht Impulsen (Dauer 200 Mikrosekunden)

Danach geht der Echo-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° CLaufzeit [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

Zu beachten ist, dass sich das Ultraschallsignal keulenförmig ausbreitet. Bei Entfernungen über 1 m ist es notwendig, den Sensor möglichst genau auf das zu messende Objekt auszurichten. Hindernisse, die sich im Sendekegel (ca. 15 Grad) befinden, können das Messergebnis beeinflussen. Auch können unerwünschte Reflexionen oder Dämpfungen das Ergebnis verfälschen.

Anschluss

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:

Statt der GPIOs 17 und 27 kann man natürlich auch andere freie Pins verwenden. Es muss dann nur das Progrmm entsprechend angepasst werden. Die Beschaltung mit dem Spannungsteiler für ECHO stellt sich dann so dar:

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.

Programmierung

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 klingt, 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 nimmt 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 Rechenzeit-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()

Um Fehlmessungen zu vermeiden lassen sich auch noch andere Maßnahmen zusätzlich zur Mittelwertbildung treffen. So könnte man alle Messwerte verwerfen, die nicht "in der Nähe" des letzten Werts liegen und beispielsweise mehr als 10% vom letzten Wert abweichen. Oder es wird statt des einfachen Mittelwerts ein gewichteter Mittelwert verwendet.

Links


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate und die Autoren
Letzte Aktualisierung: