Geschwindigkeits-Messung mit GPIO und Lichtschranke

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
McLovin42
User
Beiträge: 5
Registriert: Donnerstag 15. August 2019, 15:54

Hallo Zusammen,

ich habe folgenden Code verfasst, welcher die Geschwindigkeit eines kleinen Wagens misst und in einem Fenster ausgibt. (Erste Lichtschranke getriggert, erste Zeit. Zweite Lichtschranke getriggert, zweite Zeit und dann verrechnet )
In dem ersten Code wird das GUI gebaut und auf ein Event (Lichtschranke 1 trigger) gewartet das die Messung startet.
Der zweite Code ist in einer anderen Datei gespeichert und führt die Nötige Rechnung aus.

Nun zum Problem:
Häufig, aber nicht immer tritt das Problem auf, dass das Programm immer den nachstehenden Teil aus Code 2 wiederholt und dann nicht nachvollziehbare Ergebnisse anzeigt, wobei ich eigentlich gedacht habe, dass er durch das "return" wieder in den Hauptcode zurückkehrt und wieder auf ein Event wartet.
Warum häufig? Weil die Ergebnisse temporär plausible sind und dann mal wieder nicht, nur ich finde den Fehler nicht.

Vielleicht ist es Euch ja möglich mir weiter zu helfen, bin auch über jeden Vorschlag sehr dankbar. :D

Gruß McLovin42

Problemstelle:

Code: Alles auswählen

    else: 
        if time.time()-start_zeit >= TIMEOUT:
            print ("Wait!")
            time.sleep(0.5)
            print ("Zeit wurde zurueckgesetzt.")
            end_zeit= 0
            ZEIT = 0
            GESCHWINDIGKEIT = 0
            return   

1.

Code: Alles auswählen

try:
    import Tkinter as tk
except ImportError:
    import tkinter as tk

try:
    import ttk
    py3 = False
except ImportError:
    import tkinter.ttk as ttk
    py3 = True

import V_Messung_3_test as VM
import RPi.GPIO as GPIO
import time
from bluepy import btle
import Reading_BT
import lcddriver

#################################################
############### FENSTER ERSTELLEN ###############
#Ich erspare euch die vielen Zeilen

#################################################
####################SETUP########################
##GPIO SETUP
GPIO_START_PIN = 20
GPIO_END_PIN = 21
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_START_PIN, GPIO.IN)
##LCD SETUP
lcd = lcddriver.lcd()
lcd.lcd_display_string("v1: ", 1)
lcd.lcd_display_string("v2: ", 2)
##V_MESSUNG SETUP
DISTANZ = 0.1                               #Meter
TIMEOUT = 5                                 #Sekunden
START_ZEIT = 0.0
##BT SETUP
p = btle.Peripheral('f4:e1:1e:15:95:89')
mydele = Reading_BT.MyDelegate(0)
wait_bt = 10.0

#Messung
def v_rampe(GPIO_START_PIN):
    START_ZEIT = time.time()
    ##V_Messung
    messwert = VM.v_mess(GPIO_END_PIN, DISTANZ, TIMEOUT, START_ZEIT)
#    messwert = round(messwert, 3)
    messwert = "%.3f" %messwert
    v_1.set(messwert)
    lcd.lcd_display_string("v1: "+str(messwert), 1)
    
    ##BT_Empfangen
    messwert = ''
    p.setDelegate(mydele)
    if p.waitForNotifications(wait_bt):
        messwert = mydele.myData
        v_2.set(messwert)
        lcd.lcd_display_string("v2: "+str(messwert), 2)
        return
    elif messwert == '':
        print('dnf')
        v_2.set('dnf')
        lcd.lcd_display_string("v2: dnf", 2)
#        time.sleep(3)
#        v_2.set('')
    return
GPIO.add_event_detect(GPIO_START_PIN, GPIO.RISING, v_rampe())

root.mainloop()
2.

Code: Alles auswählen

import time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(GPIO_END_PIN, GPIO.IN)
#DISTANZ = 0.1           # (in m) Anpassen, falls notwendig
#TIMEOUT = 1             # sek
GESCHWINDIGKEIT = 0.0
#start_zeit = 0.0
end_zeit = 0.0

def v_mess(GPIO_END_PIN, DISTANZ, TIMEOUT, start_zeit):
#    start_zeit = time.time()
    while GPIO.input(GPIO_END_PIN) == 0 and time.time()-start_zeit < TIMEOUT:
        time.sleep(0.001)
    else: 
        if time.time()-start_zeit >= TIMEOUT:
            print ("Wait!")
            time.sleep(0.5)
            print ("Zeit wurde zurueckgesetzt.")
            end_zeit= 0
            ZEIT = 0
            GESCHWINDIGKEIT = 0
            return      
        else: 
            end_zeit = time.time()
            ZEIT = end_zeit - start_zeit
            GESCHWINDIGKEIT = DISTANZ / ZEIT
            print ("Geschwindigkeit: %.3f m/s") % GESCHWINDIGKEIT
            start_zeit= 0
            end_zeit= 0
            time.sleep(0.5)
            return GESCHWINDIGKEIT
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du wirst es nicht gerne hören - aber das muss alles sehr anders.

Die gesamte Architektur passt nicht, weil du Ereignisse mischst mit polling. Und dich dabei darauf verlässt, das Time.sleep wirklich so lange wartet wie angegeben. Tut es aber nicht. Das kehrt irgendwann zurück. Ggf so deutlich viel später, dass es eben zu fehlmessungen kommt.

Grund dafür sind Architektur-Eigenschaften von Linux, die dann auch noch durch gewisse Eigenarten von Python nicht verbessert werden.

Last but not least ist das von dir verwandte RPI.GPIO die mit Abstand schlechteste Wahl für die Verarbeitung der IO Pins.

Der erste Schritt besteht in der Umstellung auf pigpio. Das hat diverse Vorteile. Der wichtigste für dich: durch die geschickte Verwendung von DMA entkoppelt das die Erfassung von Zeitpunkten von Ereignissen vom Linux scheduler (der für die Fehler im sleep verantwortlich ist).

Damit bekommt du Event callbacks die gleich auch einen Tick, also einen Zeitpunkt, mitgeliefert bekommen.

Statt nur einem legst du dann zwei callbacks für beide Pins an. Und aus dem Abstand in Ticks kannst du dann die Zeit berechnen. Viel präziser und zuverlässiger.

Die Ergebnisse der callbacks solltest du in eine queue stecken, die du in tkinter periodisch per after abfragst. Und dadurch kannst du dann auch deine timeout Logik umsetzen.
McLovin42
User
Beiträge: 5
Registriert: Donnerstag 15. August 2019, 15:54

Danke für deine Antwort. Ich hatte in letzter Zeit viel zu tun und konnte mich gar nicht mehr damit befassen, daher meine späte Antwort.

Ich habe mir mal pigpio angeschaut und festgestellt, dass es in C programmiert wird. Ist es dann evt möglich auch zwei Callbacks mit Rpi.GPIO zu realisieren?
Dafür würde ich auch ungenauere Zeiten (statt 3,4 m/s , 3,2 m/s ) in kauf nehmen, aber dafür sich immer wiederholend gleiche Werte bekommen. (3,2 m/s und nach dem zehnten Durchlauf immer noch 3,2 m/s) nicht wie bei meinem Vorgänger Code da bekam ich nämlich in manchen Durchläufen Null-Komma-Werte raus obwohl nichts verändert wurde.

Ich hatte das mal ausprobiert jedoch klappte es nicht:

Code: Alles auswählen

import RPi.GPIO as GPIO
import time

zeit_1= 0.0
zeit_2 = 0.0 
distanz = 0.1 # Meter
zeit_final = 0.0
v_end = 0.0
counter_1 = 0
counter_2 = 0

startPin = 20
stopPin = 21

GPIO.setmode(GPIO.BCM)
GPIO.setup(startPin, GPIO.IN)
GPIO.setup(stopPin, GPIO.IN)
GPIO.add_event_detect(startPin, GPIO.RISING)
GPIO.add_event_detect(stopPin, GPIO.RISING)

def starten(zeit_1, counter_1):
    if counter_1 == 0:
        zeit_1 = time.time()
        counter_1 = counter_1 + 1
        return zeit_1, counter_1

def stoppen(zeit_2, counter_2):
    if counter_2 == 0:
        zeit_2 = time.time()
        counter_2 = counter_2 + 1
        return zeit_2, counter_2




if counter_1 == 1 and counter_2 == 1:
    zeit_final= zeit_2 - zeit_1
    v_end = distanz/zeit_final
    v_end = round(v_end, 3)
    print(v_end)

    # Auf BT Signal warten ? ? ?
    time.sleep(500)

elif counter_2 == 1 and counter_1 == 0:
    counter_1 = 0
    counter_2 = 0
    zeit_1 = 0.0
    zeit_2 = 0.0
    time.sleep(500)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@McLovin42: nein, es gibt auch eine Pythonanbindung für pigpio.
Warum sollte das, was Du da ausprobierst hast, klappen? Das macht ja nichts.
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es gibt eine Python-Bibliothek fuer pigpio. Und auch wenn es prinzipiell moeglich ist, callbacks in RPI.GPIO zu benutzen, so ist die Bibliothek technisch rueckstaendig und loest bestimmte, fundamentale Probleme bei Aufgaben wie der deinen einfach nicht. Darum nochmal: benutz pigpio. Am Status quo festzuhalten ist vergebens.
McLovin42
User
Beiträge: 5
Registriert: Donnerstag 15. August 2019, 15:54

Sirius3 hat geschrieben: Dienstag 3. September 2019, 12:03 @McLovin42: nein, es gibt auch eine Pythonanbindung für pigpio.
Warum sollte das, was Du da ausprobierst hast, klappen? Das macht ja nichts.
Hab es beim nochmal Durchschauen selbst gesehen. Danke

Dann werde ich mich mal an pigpio wagen.
McLovin42
User
Beiträge: 5
Registriert: Donnerstag 15. August 2019, 15:54

Ich habe mich mal dran gesetzt und bin mit pigpio überfordert.
Ist es eventuell möglich, dass ihr mir ein Besipiel zukommen lassen könnt, indem ein Event gestartet wird durch das triggern der Lichtschranke?

Danke
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Findet sich alles hier im Forum: viewtopic.php?t=39322

Du musst natuerlich auch den Tick auswerten, der hier ignoriert werden konnte.
McLovin42
User
Beiträge: 5
Registriert: Donnerstag 15. August 2019, 15:54

Hallo,
ich habe mich jetzt länger mit der Bibliothek beschäftigt, aber steige nicht dahinter.
Könntet Ihr da mal drüber schauen, ich kann den Code zurzeit nicht Testen und ansonsten bin ich mit meinem Latein am Ende.
Tut mir Leid wenn ich so viele dumme, nervige Fragen stelle , aber ich bin leider in das Projekt unfreiwillig reingerutscht und versuche mein bestmöglichstes.

Gruß

Code: Alles auswählen

import time
import pigpio

pi = pigpio.pi()
#pigpio.exceptions = False
zeit1 = None
zeit2 = None
distanz = 10 #cm | 0.1 m | 
geschwindigkeit = None
ls1 = 20
ls2 = 21


if not pi.connected:
    exit()

def cbf1(gpio, level, tick):
    #print(gpio, level, tick)
    global zeit1, zeit2, distanz
    zeit1 = pi.get_current_tick()
    #zeit1 = tick

def cbf2(gpio, level, tick):
    #print(gpio, level, tick)
    global zeit1, zeit2, distanz
    zeit2 = pi.get_current_tick()
    #zeit2 = tick
    zeit_diff = pigpio.tickDiff(zeit1, zeit2)
    geschwindigkeit = distanz / zeit_diff
    print(geschwindigkeit)


    

pi.set_mode(ls1, pigpio.INPUT)
pi.set_mode(ls2, pigpio.INPUT)

cb1 = pi.callback(ls1, pigpio.RISING_EDGE, cbf1)
cb2 = pi.callback(ls2, pigpio.RISING_EDGE, cbf2)

cb1.cancel()
cb2.cancel()

pi.stop()
__deets__
User
Beiträge: 14540
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da passiert ja auch nichts. Das Ding setzt alles auf, und stoppt es sofort, um sich zu beenden. Auch hier brauchst du eine zentrale Ereignisschleife, oder musst zumindest endlos warten wie zB mit signal.pause(). Und bestenfalls nach einem Keyboard-Interrupt cancel und stop aufrufen.

Und im callback deines Events bekommst du doch den tick. Warum fragst du denn dann get_current_tick ab? Der ganze Sinn und Zweck der Uebung besteht ja darin, den Zeitpunkt des Ereignisses zu erfahren, statt auf den zufaellig irgendwann ggf. viel viel spaeteren Moment zu messen, in dem das Programm den verarbeitet. Das sparst du dir also, und nimmst das Argument "tick". In beiden Faellen. Und das musst du dann schon selbst ausprobieren, ich zumindest habe keine Lichtschranke an zwei GPIOs zur Verfuegung.

Wenn das dann tut, koennen wir zum naechsten Schritt, Integration in die GUI, kommen.
Antworten