Fadenzähler mit 0-Stellung

Du hast eine Idee für ein Projekt?
Antworten
leafboy
User
Beiträge: 20
Registriert: Freitag 26. Januar 2018, 18:12

Hallo.

Um die Fadenlänge von Fäden und Garnen zu ermitteln habe ich einen Counter mit Nullsetzung mit Python3 programmiert. Leider zeigt der Zähler manchmal wirre Zustände. Manchmal zählt er richtig und lässt sich auch wieder auf 0-setzen manchmal bleibt der Zähler einfach auf 0 und startet nicht.
Da ich noch ein Pythonanfänger bin wollte ich mal fragen, ob sich jemand meinen Code ansehen kann. Vielleicht ist da ein prinzipieller Fehler der mir das Leben schwer macht.
Zur Hardware selbst: Ich verwende einen induktiven Näherungsschalter der mir pro Umdrehung einen Impuls gibt. Diese Umdrehung multipliziere ich dann mit dem Umfang und ermittle so die Länge pro Umdrehung. Diese gebe ich dann auf eine LCD 2x16 aus. Die Rückstellung erfolgt mit einen Taster.

Sorry falls ich hier in einer falschen Rubrik gelandet bin. Bei der Suche habe ich mir sehr schwer getan das zuzuordnen.
Vielen Dank für jede Hilfe.
Leafboy

Code: Alles auswählen

#<Python 3 Programm

import RPi.GPIO as GPIO
import time
import Adafruit_CharLCD as LCD

# Warnungen ausschalten
GPIO.setwarnings(False)

#Raspberry-Pi Pins
lcd_rs = 24
lcd_en = 23
lcd_d4 = 17
lcd_d5 = 18
lcd_d6 = 27
lcd_d7 = 22


## Zeilen und Spalten fuer ein 16 x 2 LCD Display
lcd_columns = 16
lcd_rows = 2
lcd  =  LCD.Adafruit_CharLCD(lcd_rs, lcd_en, lcd_d4,  lcd_d5, lcd_d6,  lcd_d7, lcd_columns, lcd_rows)
	

GPIO.setmode(GPIO.BCM)
impuls = 21
reset = 20 # Pin 20 als Eingang verwenden
GPIO.setup (impuls, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup (reset, GPIO.IN)


counter=0


#Function that "add_event_detect" runs at input change
def counterPlus(channel):
    global counter
    if GPIO.input(channel) >0.5:    #Pin 40 = 3,3V
        counter += 1

    else:
       counter += 0



#On input change, run input_Chng function
GPIO.add_event_detect(impuls, GPIO.RISING, callback=counterPlus, bouncetime=100)

 
#Ausgabe Zaehlerstand an LCD pro sekunde 
try:
    while True:
        while GPIO.input (reset) == False: # Reset nicht gesetzt
            lcd.message ("Gesamtlaenge:")
            lcd.message ("\n      %.2f m" %(float(counter*1)))    
            lcd.home()

        else:    
            counter = 0                   # Reset Eingang setzt Anzeige (Zähler) auf 0
    
except KeyboardInterrupt:
    pass
finally:
    GPIO.cleanup()


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

Deine schnell drehende Hauptschleife & das explizite abfragen der Rückstellung in dieser Schleife sind eine ganz schlechte Idee. Warum du das machst obwohl du doch zum zählen einen Callback benutzt - das erschließt sich mir nicht.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und die Pins des Pi sind digital. Da gibt es niemals einen gebrochenen Wert zurück. Sondern immer nur 0 oder 1.
leafboy
User
Beiträge: 20
Registriert: Freitag 26. Januar 2018, 18:12

Ja, das sind eben Ideen eines Programmieranfängers der noch bei Null steht. Wie sollte man so etwas richtig lösen? Mir fehlt hier die Erfahrung.

Mir ist schon klar, dass es sich um digitale Impulse handelt. Ich möchte auch nur die Impulse zählen und dann mit dem Umfang multiplizieren. So komme ich dann auf die gezählte / gemessene Länge. Ohne den Zähler auf Null stellen funktioniert das auch aber diese Nullstellung funktioniert nicht oder nur ab und zu.

Könntest du mir bitte weiterhelfen wie ich so etws richtig programmiere?

Vielen Dank im Voraus.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie schon gesagt: du benutzt schon einen ereignis-basierten Mechanismus zum *hochzaehlen*. Benutz halt auch den gleichen Mechanismus zum zurueckstellen. Und wenn dir klar ist, dass es digitale Impulse sind, warum vergleichst du dann mit > 0.5? Last but not least, statt nur einen wirkungslosen Kommentar "pro Sekunde" zu schreiben, musst du in der Hauptschleife eben auch eine Sekunde warten.
leafboy
User
Beiträge: 20
Registriert: Freitag 26. Januar 2018, 18:12

Hallo __deets__

Sorry - aber ich kapier das nicht. Wo soll ich was korrigieren / einbauen. Kannst du mir bitte genauer angeben wo ich was machen muss. Leider fehlt mir der Plan.

Vielen Dank
Leafboy
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du HAST doch schon Code, der raufzaehlt. Wie macht der das denn? Was passiert denn da? Und genauso kannst du doch auch Code schreiben, der wieder auf 0 setzt. Wenn du den Code den du hast nicht verstehst, dann lies dir die Dokumentation zu den verwandten Funktionen durch, und vollzieh nach, was da passiert.

Und ich werde dir nicht den Code vorkauen. Wenn du ein Problem hast, das nur durch programmieren zu loesen ist, dann gibt es zwei Moeglichkeiten: du bist Anfaenger, der lernen will. Dann lern. Oder du willst, dass man dir dein Problem loest. Dann bezahl Geld.
leafboy
User
Beiträge: 20
Registriert: Freitag 26. Januar 2018, 18:12

@__deets__: Warum dieser Ton? Es war nie die Anfrage von "kann mir jemand das gratis programmieren". Als Anfänger weiß man meistens nicht warum sich ein Programmteil so verhält. Ich habe mir ein paar Grundlagenbücher angeschafft aber da komme ich nicht ganz klar. Darum bin ich eigentlich im Forum um von kleinen Tipps zu profitieren. Lernen wir nicht auch durch so etwas?

Ein "Schubser" in die richtige Richtung reicht doch meistens aus. Wenn du für so etwas Geld willst - ok.

Trotzdem vielen Dank für die Info.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

ich Schubse beständig. Ich habe dir erklärt, woran das Problem liegt, und Wege aufgezeigt, wie man es besser lösen kann. Mit Techniken, die in dem von dir vorgelegten Code schon verwandt werden. Nur muss man damit dann eben rumspielen. Es ausprobieren. Davon sehe ich aber nichts. Keinen neuen Code, der etwas anders macht. Du erklärst auch nicht, was von dem Code du wie verstehst. So das man diesem Verständnis auf die Sprünge helfen könnte. Genauso wenig arbeitest du Hinweise zu falschem Vorgehen wie mit der Fliesskommazahl ein. Alles in allem vermittelt das mir nicht das Bild von jemandem, der programmieren lernen will. Sondern jemand, der seine Basteleien mit dem passenden Code versorgen möchte.

Für Hilfestellung will ich kein Geld. Sonst wäre ich nicht hier. Für Auftragscode - ganz bestimmt. Davon lebe ich schließlich. Dein Job machst du doch auch nicht umsonst, oder?
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@leafboy: ``counter += 0`` macht nicht wirklich Sinn. Und ein ``else`` zu einer Schleife in der gar kein ``break`` vorkommt macht auch keinen Sinn.

Warnungen sollte man nicht ausschalten, sondern deren Ursache finden und beseitigen.

Konstanten werden per Konvention KOMPLETT_GROSS geschrieben. Und alles weitere in kleinbuchstaben_mit_unterstrichen, mit Ausnahme von Klassen (MixedCase). Also `counter_plus()` statt `counterPlus()`. Wobei Funktions- und Methodennamen üblicherweise die Tätigkeit beschreiben die ausgeführt wird. Also hier beispielsweise besser `increase_counter()`. Oder *noch* besser `increase_revolution_counter()` — dann weiss der Leser auch gleich *was* da gezählt wird.

Auf Modulebene sollte nur Code stehen, der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Dein Code mal ein bisschen aufgeräumt:

Code: Alles auswählen

#!/usr/bin/env python3
from RPi import GPIO
import Adafruit_CharLCD as LCD

LENGTH_PER_REVOLUTION = 1

REVOLUTION_IMPULS_PIN = 21
RESET_PIN = 20

LCD_RS_PIN = 24
LCD_EN_PIN = 23
LCD_D4_PIN = 17
LCD_D5_PIN = 18
LCD_D6_PIN = 27
LCD_D7_PIN = 22

LCD_COLUMN_COUNT = 16
LCD_ROW_COUNT = 2

#
# FIXME Must be protected by a lock!
# (In any case! Even if busy loop is replaced by event detecting.)
# 
# FIXME Remove global variable.
# 
revolution_count = 0


def increase_revolution_counter(_channel):
    global revolution_count
    revolution_count += 1


def main():
    global revolution_count
    
    lcd = LCD.Adafruit_CharLCD(
        LCD_RS_PIN,
        LCD_EN_PIN,
        LCD_D4_PIN,
        LCD_D5_PIN,
        LCD_D6_PIN,
        LCD_D7_PIN,
        LCD_COLUMN_COUNT,
        LCD_ROW_COUNT,
    )
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(REVOLUTION_IMPULS_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.setup(RESET_PIN, GPIO.IN)

        GPIO.add_event_detect(
            REVOLUTION_IMPULS_PIN,
            GPIO.RISING,
            callback=increase_revolution_counter,
            bouncetime=100,
        )
        while True:
            # 
            # TODO Replace busy loop by event detection.
            # 
            while not GPIO.input(RESET_PIN):
                lcd.message(
                    'Gesamtlaenge:\n      {:.2f} m'.format(
                        revolution_count * LENGTH_PER_REVOLUTION
                    )
                )
                lcd.home()
            
            revolution_count = 0
        
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()


if __name__ == '__main__':
    main()
Es stehen zwei FIXME-Kommentare drin: Man muss den Zähler auf jeden Fall mit gegen den nebenläufigen Zugriff schützen. Das `threading`-Modul hat da etwas passendes. Das würde ich auch dann machen, wenn Du auch das Rücksetzen so wie das Zählen löst. Es kann sein, dass das GPIO-Modul das die Rückrufe über nur einen Thread abwickelt, es kann aber auch sein, dass es das nicht tut. Ich würde mich da nicht drauf verlassen wollen.

Das zweite was Du dringend in Angriff nehmen solltest ist die globale Variable loswerden. Dazu müsstest Du Dich weiter in Python und objektorientierte Programmierung einarbeiten.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__: das Grundproblem bleibt so bestehen: der spinning loop beeintraechtigt den Hintegrund-Thread zur Abfrage des Zaehlers. Womit auch deine Frage beantwortet waere: ja, so macht GPIO das. Gleichzeitig wuerde das Umstellen auf den ja schon vorhandenen Ereignis-Ansatz die Notwendigkeit zur Synchronisation eliminieren: es ist nur ein Thread, und dadurch sind die Ereignisse immer serialisiert.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Für den Reset-Pin auch ein Callback anlegen, mit dem du halt den Zähler auf 0 zurücksetzt.
Bin mal gespannt, ob es da zu Problemen kommt.

Die Anzeige könnte man innerhalb eines Threads z.B. alle 100 ms updaten, falls sich der Wert geändert hat.
Da der Zugriff nur lesend ist (sein sollte), kann man da jetzt auch nicht ganz so viel falsch machen.
Jedenfalls wirst du dann auch mal alte, nicht mehr aktuelle Werte sehen, aber wer kann schon so schnell gucken.

Nachteil der Konstruktion: Du siehst immer nur ganze Umdrehungen. Normalerweise macht man das mit einem Inkrementalgeber, der zwei Ausgänge hat (A und B). B ist zu A 90° verschoben, was auch die Drehrichtungserkennung ermöglicht. Der Nachteil ist, dass man dann noch mehr Interrupts hat. Je schneller sich der Motor dreht, desto wahrscheinlicher ist es dann, dass man Schleppfehler hat. Umgangssprachlich: der verschluckt dann Schritte. Es gibt aber auch ganz billige Inkrementalgeber mit sehr wenig Schritten pro Umdrehung. Die hochwertigen wird man niemals an einem Raspberry PI anschließen. Die kommen dann eher an eine SPS oder Mikrocontroller.

Das Display würde ich so updaten:

Code: Alles auswählen

def update_display():
    local_position = 0
    while True:
        if local_position != revolution_count:
            local_position = revolution_count
            # Update Display mit local_position * umfang 
        time.sleep(0.1)

display_thread = threading.Thread(target=update_display, daemon=True)
display_thread.start()
Mit Threads kann man auch viel falsch machen.
Wenn du z.B. das daemon=False setzt und das Programm mit STRG+C beenden willst, wird der Thread noch weiterlaufen.
Der Python-Interpreter wird erst beendet, wenn alle nicht-Daemon-Threads beendet sind. Die Daemon-Threads werden dann gekillt, wenn der Mainthread beendet wird.
Ich hoffe mal, das ich es jetzt so richtig wieder gegeben habe.

Das Lernen können wir dir nicht abnehmen. Nicht aufgeben, wenn du Fehler machst, denn nur jemand, der Fehler macht, lernt auch etwas.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

@DeadEye: da die Ereignisse in einem extra Thread ankommen, kann man den Main Thread zur Aktualisierung nutzen. Kein Grund einen extra zu starten.
leafboy
User
Beiträge: 20
Registriert: Freitag 26. Januar 2018, 18:12

@blackjack | @DeaD_EyE: Vielen Dank für eure kontstruktiven Kommentare. Werde mir das die nächsten Tage gleich mal genauer ansehen und mich melden.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__deets__ hat geschrieben: Sonntag 7. Oktober 2018, 12:12 @DeadEye: da die Ereignisse in einem extra Thread ankommen, kann man den Main Thread zur Aktualisierung nutzen. Kein Grund einen extra zu starten.
Ja, stimmt. Die GPIO-Implementierung sollte automatisch in einem eigenen Thread laufen.
D.h. es würde ausreichen nur die Funktion aufzurufen, ohne diese als Thread zu starten.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
leafboy
User
Beiträge: 20
Registriert: Freitag 26. Januar 2018, 18:12

Hallo __blackjack__

Ich habe diese Woche etwas Theorie angeschaut und das Programm versucht durchzugehen. Ich muss gestehen mir fehlt total der Plan.

Lokale Variable - ok ist eine Varable in einer Funktion und eine globale eben ausserhalb einer Funktion. Leider verstehe ich nicht warum ich die globalen Variablen entfernen muss. usw...

Tut mir leid ich verstehe nur Bahnhof. Ich habe mit Funktionen etwas herumgespielt aber es scheint ich habe den Sinn hier noch nicht verstanden. Irgendwo habe ich gelesen DRY - don't repeat yourself. Wie ist das aber hier zu verstehen. Sorry, wie du siehst bin ich noch ganz ganz am Anfang.
Darf ich dich nochmals um einen Tipp bitten um mein Programm zum Laufen zu bringen?

Vielen Dank für deine Nachsicht mit einem Anfänger.
leafboy
Antworten