HC-SR04 Ultraschall Sensor

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
elchico
User
Beiträge: 29
Registriert: Dienstag 10. März 2015, 00:06

Hallo zusammen,

ich möchte den HC-SR04 Sensor benutzen um Abstände zu messen (das ist wohl der Sinn des Sensors). Er soll später mal an einem autonom fahrenden Roboter installiert werden.
Nun habe ich im Internet dieses Beispielskript gefunden und wollte es ausprobieren, allerdings gibt der Sensor komische Werte wieder. Das Skript ist minimal angeglichen (ein print-Kommando mehr, eins weniger und natürilch die GPIOs angeglichen). Das Problem habe ich inzwischen etwas eingrenzen können: Es liegt daran, dass quasi "start" = 0 gesetzt wird und beim Aufruf der Defintion "measure" dann der else-Fall eintritt, weil scheinbar GPIO.input(ECHO) != 1.
Wisst Ihr, woran das liegen könnte?

LG
Michi

Skript:

Code: Alles auswählen

#!/usr/bin/python

import RPi.GPIO as GPIO
import time
import datetime

# GPIOs fuer den US-Sensor
TRIG = 37
ECHO = 38

# 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.BOARD)
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
    print "delta: " + str(delta)    
    


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.1)
  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()
Ausgabe Raspberry:

Code: Alles auswählen

delta: 1588870572.82
delta: 1588870572.82
delta: 0.000179052352905
delta: 0.000204086303711
delta: 0.000200033187866
delta: 0.000202178955078
delta: 1588870573.52
delta: 1588870573.52
delta: 0.000200033187866
delta: 0.000203847885132
delta: 0.000202894210815
delta: 0.000200986862183
delta: 0.000197172164917
delta: 0.00026798248291
delta: 0.000114917755127
delta: 0.000204086303711
delta: 0.000200033187866
delta: 0.000210046768188
delta: 0.000209093093872
delta: 1588870575.21
delta: 1588870575.21
delta: 0.000121116638184
delta: 0.000200033187866
delta: 0.000202894210815
delta: 0.000204086303711
delta: 0.000290155410767
delta: 0.000201940536499
delta: 0.000203132629395
Range = 3244155937697.6 cm
^CBye!
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Print in deinem “interrupt callback” ist eine ganz bescheidene Idee. Solltest du sofort loswerden. Und ansonsten gilt: nicht RPi.GPIO nehmen, sondern pigpio. Das sampelt die GPIOs mit einem DMA Kanal, und liefert damit dann einen Tick pro event, der zuverlässiger ist als das Ungewisse “irgendwann entscheidet der Kernel Python zurück zu rufen, und dann messe ich mal die Zeit” in dem du sich jetzt befindest. Das ist Mist.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Python2 sollte man nicht mehr verwenden. Vergiss, dass es ›global‹ gibt, das ist nie eine Lösung. Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht 2. In einem callback darf nur so wenig wie möglich gemacht werden, während einer Messung erst recht.
Ein `remove_event_detect` ohne ein `add_event_detect` ist unsinnig. `cleanup` sollte immer aufgerufen werden, nicht nur, wenn man Ctrl+C drückt.

Code: Alles auswählen

#!/usr/bin/env python3
import time
from queue import Queue
import RPi.GPIO as gpio

# GPIOs fuer den US-Sensor
TRIG = 37
ECHO = 38

# Dauer Trigger-Impuls
PULSE = 0.00001

# Anzahl Messwerte fuer Mittelwertbildung
BURST = 25

# Schallgeschwindigkeit/2
SPEED_2 = 17015

def setup():
    # BCM GPIO-Referenen verwenden (anstelle der Pin-Nummern)
    # und GPIO-Eingang definieren
    gpio.setmode(gpio.BOARD)
    gpio.setup(TRIG, gpio.OUT)
    gpio.setup(ECHO, gpio.IN)
    gpio.output(TRIG, 0)
    # Setup-Zeit fuer Sensor
    time.sleep(1)

def pulse():
    # Triggerimpuls  erzeugen
    GPIO.output(TRIG, 1)       
    time.sleep(PULSE)
    GPIO.output(TRIG, 0)

def measure_range():
    # Bildet Mittelwert von BURST Messungen
    try:
        queue = Queue()
        gpio.add_event_detect(ECHO, gpio.BOTH, callback=lambda _:queue.put(time.time()))
        for i in range(0, BURST):
            # Messung starten
            pulse()
            # Warten, bis Messung zuende
            time.sleep(0.040)
    finally:
        gpio.remove_event_detect(ECHO)
    values = []
    while not queue.isempty():
        values.append(- queue.get() + queue.get())
    print(values)
    return sum(values) / len(values) * SPEED_2

def main():
    try:
        while True:
            distance = measure_range()
            print("Range = %1.1f cm" % distance)
            time.sleep(1)
    except KeyboardInterrupt:
        # reset GPIO settings if user pressed Ctrl+C
        print("Bye!")
    finally:
        gpio.cleanup()

if __name__ == '__main__':
    main()
elchico
User
Beiträge: 29
Registriert: Dienstag 10. März 2015, 00:06

@ __deets__: Danke, werde ich mir merken. Ist aber nur ein Bespielsskript, bei dem ich so gut wie nichts geändert habe. Ich wollte nur wissen, ob der Sensor was tut =)

@ Sirius3: Ich habe dein Skript probiert, nach ein paar Angleichungen funktioniert es (setup wird nicht aufgerufen und GPIO ist nun gpio). Danke dafür erstmal.
Allerdings misst er jetzt nur ein paar Mal (mal 0x, dann 15x), dann hängt sich python auf (ist mit STRG+C immer noch abbrechbar, allerdings werden keine neuen Werte ausgegeben und im Putty Fenster kommt quasi keine neue Zeile). Weißt Du, woran das liegt? Ich habe den Pi B+ (ca 5 Jahre alt, allerdings 4.5 Jahre davon nicht benutzt), vielleicht ist der zu schwach auf der Brust?
Außerdem stimmen die Werte nicht (schwankt zwischen 25 cm und 7.8 cm, meist ist der Wert bei 7.5 - 9 cm, obwohl ich einmal die gegenüberliegende Wand (ca. 2.50 m) und einmal etwas direkt vor den Sensor (3 cm) gestellt habe. Und alles dazwischen). Hast Du eine Idee, was da das Problem sein könnte? Würde ein kaputter Sensor so etwas ausgeben möglicherweise?

Danke und LG
Michi
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@elchico: Das ist noch Python 2, das sein Lebensende erreicht hat. Dafür wird es weder Bugfixes noch Sicherheitsupdates mehr geben. Damit sollte man keine neuen Projekte mehr anfangen.

Eingerückt wird mit vier Leerzeichen pro Ebene, nicht zwei.

Python braucht kein Semikolon als Zeilenabschluss. Das hat wohl jemand programmiert der gewöhnlich ein einer anderen Sprache unterwegs ist. Das eine Liste in den Kommentaren als Array bezeichnet wird, spricht da auch für.

Die `measure_range()`-Funktion in der das Semikolon steht, ist auch etwas eigenartig. Die `values`-Liste wird gar nicht wirklich verwendet. Da werden zwar Elemente angehängt, aber dann wird auch gleich immer das letzte Element auf eine Gesamtsumme addiert. Das kann man auch einfach immer mit dem aktuellen Wert machen ohne sich die Werte in einer Liste zu merken mit der man nichts macht. Und `sum` ist kein guter Name denn das ist bereits der Name der eingebauten `sum()`-Funktion.

`datetime` wird importiert, aber nirgends verwendet. ``as`` wird bei Importen zum umbenennen verwendet — `GPIO` wird aber gar nicht umbenannt.

Namen sollte nicht kryptisch abgekürzt werden. Wenn man `TRIGGER` meint, sollte man nicht mit `TRIG` aufhören.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. ``global`` hat in einem sauberen Programm nichts zu suchen.

Faustregel für Kommentare: Die beschreiben nicht *was* der Code tut, denn das steht da ja bereits als Code, sondern warum er das so tut, sofern das nicht offensichtlich ist.

Diese ausgerichteten Kommentare an Zeilenenden sind Mist, weil die aufwändig zu warten sind.

`remove_event_detect()` bevor da ein `add_event_detect()` stattgefunden hat, macht keinen Sinn.

Das `GPIO.cleanup()` will man in *jedem* Fall, und nicht nur wenn der Benutzer Strg+C gedrückt hat. Das gehört also in einen ``finally``-Block. Und der ``try``-Block dazu muss mehr umfassen, damit das auch wirkliche alle Änderungen in jedem Fall aufräumen kann.

Mir ist nicht so wirklich klar warum `stopp` ausserhalb von `measure()` existiern müsste‽ Und auch `distance` bei jedem `pulse` mit 0 zu initialisieren macht nicht wirklich Sinn. Und wenn dann ist die Reihenfolge fragwürdig: erst den Impuls erzeugen und dann `distance` auf 0 setzen kann doch dazu führen das die Messung davor passiert und danach das Messergebnis dann mit dieser 0 wieder überschrieben wird‽ Im Kommentar wird es ja entgehen dem Code auch anders herum beschrieben. Das sind übrigens so mit die schlimmsten Kommentare die man haben kann: welche die offen dem Code widersprechen. Die klären dann nichts, sondern werfen im Gegenteil mehr Fragen auf.

Anstelle von `time.time()` nimmt man besser `time.monotonic()`.

Der Kommentar ``# Warten, bis Messung zuende`` enthält ein wenig Wunschdenken. Da wird nicht gewartet bis die Messung zuende ist, sondern es wird gewartet und man hofft das die Messung danach zuende ist. Das sollte man mit weniger Glauben und mit mehr Wissen machen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time
from threading import Event, Lock

from RPi import GPIO

#
# GPIOs fuer den Ultraschall-Sensor.
#
TRIGGER_PIN = 37
ECHO_PIN = 38
#
# Trigger-Impuls-Dauer.
#
PULSE_LENGTH = 0.00001
#
# Anzahl Messwerte für Mittelwertbildung.
#
BURST = 25

HALF_SPEED_OF_SOUND = 17015


def pulse():
    GPIO.output(TRIGGER_PIN, True)
    time.sleep(PULSE_LENGTH)
    GPIO.output(TRIGGER_PIN, False)


class EchoHandler:
    def __init__(self):
        self.lock = Lock()
        self.distances = list()
        self.start = None
        self.measurement_done_event = Event()

    def on_echo(self, _):
        if GPIO.input(ECHO_PIN):
            self.start = time.monotonic()
        else:
            delta = time.monotonic() - self.start
            with self.lock:
                self.distances.append(delta * HALF_SPEED_OF_SOUND)
            self.measurement_done_event.set()
            print("delta: " + str(delta))

    def get_average_distance(self):
        with self.lock:
            average_distance = sum(self.distances) / len(self.distances)
            self.distances = list()
        return average_distance

    def measure_range(self):
        for _ in range(BURST):
            self.measurement_done_event.clear()
            pulse()
            self.measurement_done_event.wait()
            time.sleep(0.1)
        return self.get_average_distance()


def main():
    try:
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(TRIGGER_PIN, GPIO.OUT)
        GPIO.setup(ECHO_PIN, GPIO.IN)
        GPIO.output(TRIGGER_PIN, False)
        time.sleep(1)  # Setup-Zeit für Sensor.

        echo_handler = EchoHandler()
        GPIO.add_event_detect(
            ECHO_PIN, GPIO.BOTH, callback=echo_handler.on_echo
        )

        while True:
            distance = echo_handler.measure_range()
            print(f"Range = {distance:.1f} cm")
            time.sleep(1)

    except KeyboardInterrupt:
        print("Bye!")
    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
elchico
User
Beiträge: 29
Registriert: Dienstag 10. März 2015, 00:06

Hallo zusammen,

erst einmal danke für die ganzen Inputs. Werde ich nach und nach abarbeiten =)

Zur Lösung: Nutze jetzt Python3 und pigpio. Das ist ja ein Traum! Damit reduziert sich das ganze auf:

Code: Alles auswählen

from gpiozero import DistanceSensor
from time import sleep

sensor = DistanceSensor(16, 26)

while True:
    print('Distance to nearest object is', sensor.distance*100, 'cm')
    sleep(0.5)
Vielen Dank!
LG
Michi
Antworten