Beim Code für den HC-SR04 lässt sich natürlich auch noch was machen, denn aktuell verwenden wir die sog. "Polling"-Methode, dh wir rufen selbstständig den aktuellen GPIO-Status ab und handeln entsprechend. Das ist nicht wirklich effektiv und kostet auch weitaus mehr Rechenleistung, weil die while ungebremst extrem schnell rotiert.
Effektiver und auch präziser wäre die Flankendetektierung via Interrupt... Dafür definiert man eine sog. Interrupt-Service-Routine (ISR), die für jede steigende und fallende Flanke (GPIO.BOTH) automatisch aufgerufen und der ausgelöste GPIO übergeben wird. Innerhalb der Routine wird dann der ECHO-Pin abgefragt. Hat er den Wert 1 (was mit GPIO.HIGH gleichzusetzen ist) war eine steigende Flanke Auslöser und eine Variable speichert die aktuelle Zeit. Im anderen Fall war die fallende Flanke der Auslöser und es werden die Zeitdifferenz und die Entfernung berechnet - also genau so wie bisher auch, nur dass diese ISR automatisch im Hintergrund in einem separaten Thread läuft und die Handhabung komplett vom RPi.GPIO Module übernommen wird. Einziges Manko: Um eine IPC muss man sich trotzdem selber kümmern. Hier würde ich auch wieder die Verwendung eines Queue zum speichern des Distanz-Werts empfehlen, da dann tatsächlich nur der aktuelle Wert ausgegeben wird und nicht evtl. noch ein alter Wert.
Zu beachten ist auch dass durch GPIO.add_event_detect() auch wieder ein separater Thread ausgeführt wird... Allerdings nur ein einziger Thread, nicht für jede Callback ein extra Thread... Details sind aber erst mal egal, das sprengt das Thema hier. Siehe dazu auch https://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/
Auch hier gäbe es erneut mehrere Möglichkeiten - wie immer. Am besten wäre eine eigene Klasse zu schreiben die dann mehrere Methoden hätte und innerhalb dieser Methoden kann man dann nämlich auf "self." definierte Variablen anderer Methoden problemlos zugreifen, was das ganze vereinfacht.
Hierdurch ergibt sich dann auch die Gelegenheit auf das threading Module verzichten zu können und stattdessen multiprocessing zu verwenden
Und im gleichen Zuge würde ich auch für die Motor-Steuerung eine eigene Klasse erstellen - was den Umgang ebenfalls erleichtert.
Ein weiterer Nebeneffekt dieses Umbaus: Falls ein Echo 'out of range' sein sollte, läuft das Script normal weiter.
Würde beim alten Vorgehen niemals nie eine Antwort vom HC-SR04-Ping zurück kommen, würde das alte Script stecken bleiben weil es eben so lange wartet bis ECHO einen Flankenwechsel erfährt...
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# (c) 2018 by meigrafd
#
import sys
import time
import multiprocessing as mp
from datetime import datetime
from RPi import GPIO
class sonar_ranger(object):
"""
This class encapsulates a type of acoustic ranger. In particular
the type of ranger with separate trigger and echo pins.
A pulse on the trigger initiates the sonar ping and shortly
afterwards a sonar pulse is transmitted and the echo pin
goes high. The echo pins stays high until a sonar echo is
received (or the response times-out). The time between
the high and low edges indicates the sonar round trip time.
"""
def __init__(self, gpio, trigger, echo, telemetry, status, settings, timeout=5.0):
self.gpio = gpio
self.trig = trigger
self.echo = echo
self.telemetry = telemetry
self.status = status
self.settings = settings
self.timeout = timeout
self.range_min = 2
self.range_max = 400
gpio.setup(trig, gpio.OUT)
gpio.setup(echo, gpio.IN)
gpio.add_event_detect(echo, gpio.BOTH, self.callback)
def callback(self, channel):
if self.gpio.input(self.echo) == 1:
self.start_time = time.time()
else:
self.stop_time = time.time()
self.time_elapsed = self.stop_time - self.start_time
self.distance = self.time_elapsed / 0.000058
self.telemetry["distance"] = self.distance
def average(self, count=3):
c=1
distance=0
while (c <= count):
self.ping()
time.sleep(0.040)
if self.distance:
distance = distance + self.distance
time.sleep(0.05)
c+=1
return distance/count
def ping(self, pulse=0.00005):
self.start_time=time.time()
self.stop_time=time.time()
self.distance=None
self.gpio.output(self.trig, True)
time.sleep(pulse)
self.gpio.output(self.trig, False)
def print_distance(self):
if (int(time.time()) - self.status["announce_time"]) >= self.settings["distance_announce_time"]:
if self.distance > self.range_max or self.distance < self.range_min:
print("Distance: out of range")
else:
print("Distance: %.2f cm" % self.telemetry["distance"])
self.status["announce_time"] = int(time.time())
def run(self):
while True:
time.sleep(self.settings["distance_measure_time"])
if not self.status["stop_ping"]:
self.ping()
self.print_distance()
class motor_control(object):
"""
http://www.14core.com/wiring-driving-the-l298n-h-bridge-on-2-to-4-dc-motors/
"""
def __init__(self, gpio, status, telemetry, motor_a_in1, motor_a_in2, motor_b_in3, motor_b_in4):
self.motor_a_in1 = motor_a_in1
self.motor_a_in2 = motor_a_in2
self.motor_b_in3 = motor_b_in3
self.motor_b_in4 = motor_b_in4
self.status = status
self.telemetry = telemetry
self.telemetry["steerTo"]=0
gpio.setup(motor_a_in1, gpio.OUT)
gpio.setup(motor_a_in2 gpio.OUT)
gpio.setup(motor_b_in3, gpio.OUT)
gpio.setup(motor_b_in4, gpio.OUT)
def stop(self):
gpio.output(self.motor_a_in1, False)
gpio.output(self.motor_a_in2, False)
gpio.output(self.motor_b_in3, False)
gpio.output(self.motor_b_in4, False)
self.telemetry["steerTo"] = "stop"
print("Motor: {}".format(self.telemetry["steerTo"]))
def forward(self):
gpio.output(self.motor_a_in1, True)
gpio.output(self.motor_a_in2, False)
gpio.output(self.motor_b_in3, True)
gpio.output(self.motor_b_in4, False)
self.telemetry["steerTo"] = "forward"
print("Motor: {}".format(self.telemetry["steerTo"]))
def reverse(self):
gpio.output(self.motor_a_in1, False)
gpio.output(self.motor_a_in2, True)
gpio.output(self.motor_b_in3, False)
gpio.output(self.motor_b_in4, True)
self.telemetry["steerTo"] = "reverse"
print("Motor: {}".format(self.telemetry["steerTo"]))
def turn_left(self):
gpio.output(self.motor_a_in1, True)
gpio.output(self.motor_a_in2, False)
gpio.output(self.motor_b_in3, False)
gpio.output(self.motor_b_in4, True)
self.telemetry["steerTo"] = "left"
print("Motor: {}".format(self.telemetry["steerTo"]))
def turn_right(self):
gpio.output(self.motor_a_in1, False)
gpio.output(self.motor_a_in2, True)
gpio.output(self.motor_b_in3, True)
gpio.output(self.motor_b_in4, False)
self.telemetry["steerTo"] = "right"
print("Motor: {}".format(self.telemetry["steerTo"]))
def main():
motor_a_in1 = 11
motor_a_in2 = 7
motor_b_in3 = 13
motor_b_in4 = 15
sonar_trigger = 38
sonar_echo = 40
GPIO.setmode(GPIO.BOARD)
status = mp.Manager().dict() # shared dictionary
telemetry = mp.Manager().dict() # shared dictionary
settings=dict() # static dictionary
settings["distance_measure_time"] = 0.5 # sec
settings["distance_announce_time"] = 1 # sec
status["stop_ping"]=False
status["announce_time"]=int(time.time())
telemetry["distance"]=None
motor = motor_control(GPIO, status, telemetry, motor_a_in1, motor_a_in2, motor_b_in3, motor_b_in4)
# Spawn child process for running measurement
radar = sonar_ranger(GPIO, sonar_trigger, sonar_echo, telemetry, status, settings)
radarProcess = mp.Process(target=radar.run)
radarProcess.daemon = True
radarProcess.start()
try:
while True: ## ..robots brain..
if telemetry["distance"] and telemetry["distance"] < 15:
if not telemetry["steerTo"] == "left":
motor.turn_left()
else:
if not telemetry["steerTo"] == "forward":
motor.forward()
time.sleep(0.01) # lowers CPU usage
except (KeyboardInterrupt, SystemExit):
if radarProcess.is_alive(): radarProcess.terminate()
GPIO.cleanup()
if __name__ == '__main__':
main()
Alles anzeigen
...ich weiß nicht ob die GPIO-Zuweisung so korrekt ist... das ist das Problem wenns weder Kommentare noch vernünftige Benennung von Variablen/Konstanten gibt, wie soll das "jemand anderes" nachvollziehen.
Das wäre die nächste Stufe das Programm zu verbessern... Als nächstes könnte man die Klassen in einzelne Dateien auslagern.
Was dann noch wichtig werden könnte ist einen Mittelwert für jeden Messvorgang zu ermitteln, da der HC-SR04 nicht 100% fehlerfrei arbeitet und es fatal/nervig werden könnte wenn eine Messung etwas falsches ergibt.
Siehe dazu auch:
http://www.netzmafia.de/skripten/hardw…hall/index.html
http://blog404de.github.io/RasPiUltraschallEntfernungsmesser/