Verständnisfrage µPython RASPI- PICO Pin-irq

  • Guten Tag,


    Für eine einfache Anzeigeneinheit mit einem 20x4 LCD Display bin ich auf einige Probleme gestoßen, wo ich jetzt erst einmal nicht weiter komme.
    Mit diesem Display (Affiliate-Link) habe ich so meine Nöte :wallbash:
    Dazu habe ich wie in der Anleitung diesen I²C Adapter angelötet und verwende auch einen Levelshifter (Affiliate-Link) für SDA und SCL. Sobald ich die gesamte Schaltung unter Spannung setze geht wie eingestellt die Hintergrundbeleuchtung an. Ebenso wenn ich einen Bus-Scan ausführe wird mir die korrekte Busadresse angezeigt.
    Dazu habe ich mir dieses beiden Libraries herunter geladen und auf das Pico kopiert. Verwende ich nun statt des 20x4 ein 16x2 Display funktioniert die Anzeige auf dem Display einwandfrei. Nur bei dem 20x4 passiert rein gar nichts. Auch das Programm gibt keinerlei Fehlermeldungen aus.
    Das zum ersten teil ;)
    Nun möchte ich über einen einzigen Taster zwei Schaltfunktionen umsetzen.
    Zum einen habe ich eine While Schleife die sich wiederum in 5 Funktionsblöcke aufteilt.
    Diese erste übergeordnet While Schleife soll bei einem längeren Tastendruck verlassen werden, und das Programm zum Ende kommen.
    Jetzt bin ich auf die Funktion irq bei der Pin Nutzung gestossen. Da bedeutet doch, dass ich in der aufzurufenden Funktion nun sagen könnte, gehe zur nächsten Funktion über !?

    Das heißt mit jedem Tastendruck soll die die Abfrage des aktuellen Sensors und dessen Ausgabe auf dem Display beendet werden, und zum nächsten Programmteil gesprungen oder aufgerufen werden, wo wieder eine andere Sensorgrööße abgefragt und angezeigt wird. Wie würde man sagen: "Ein ewiger Kreislauf " bis irgendwann einmal durch einen längeren Tastendruck das gesamte Programm beendet wird.

    Wie bekomme ich das nun hin, dass ich mit dieser irq Funktionalität beide Aufgabenstellungen umsetzen kann ? Zwischenrein in den Abfrage-Funktionsblöcken auch immer wieder den Taster abzufragen erscheint mir hier nicht zielführend, da ich damit auch nicht die übergeordnete While Schleife stoppen kann.
    Ist es hier sinnvoll Global Variablen zu verwenden, oder gibt es dafür auch noch einen anderen Lösungsansatz ? Zudem wie kann man damit, oder darüber die Zeit eines solchen Tastendrucks erfassen ?

    es grüßt euer
    Willy

  • Von der Struktur her würde ich das so organisieren:


    Testen musst du aber selbst. Ich habe kein LCD-Display mit I2C.


    Falls so ein Fehler ausgegeben wird, dann stimmt etwas mit der Verkabelung nicht oder die falschen Pins sind belegt.

    Angeblich soll das auch ohne Levelshifter funktionieren. Da ich die Spannungen nicht selbst messen kann, kann ich keine Aussage darüber treffen.

    Quote

    OSError: [Errno 5] EIO


    PS: Code zum entprellen könntest du auch verwenden. Mit einem RC-Glied ist es aber einfacher zu lösen.

  • Hallo Willy,


    zu Python will ich Dir nichts erzählen.


    Es gibt immer zwei Lösungen einer kombinierten Hardware-Software-Aufgabe. Du ahnst es sicherlich:

    1. Software
    2. Hardware


    Obwohl ich der Software näher stehe als der Hardware, möchte ich Dir erstmal ein anderes Bauteil nahelegen: Der Dreh-Encoder.


    Dann möchte ich Dich von der Sache mit den Interrupt-Requests abbringen.


    Ein klassischer Interrupt löst eine Aktion aus, deren Bearbeitung nicht sehr lange dauern darf, also

    • keine Ausgabe
    • kein Speichern
    • keine Verzögerung
    • keine umfangreichen Berechnungen
    • keine Schleifen
    • auf kein anderes Ereignis warten
    • etc.


    Vieles von dem wäre in einer Interrupt-Service-Routine, wie Du sie Dir ausmalst, erforderlich.


    Der Knackpunkt ist der, dass ein Interrupt nur dann zum Ende kommt, wenn in der Zwischenzeit kein weiterer Interrupt passiert ist. Wie wahrscheinlich das ist, kannst Du selber beurteilen, wenn Du mal einen Blick in das Thema der prellenden Taster wirfst.

    Das heisst, in der Regel wird eine ganze Kaskade an Interrupts erzeugt, die wie bei rekursiven Funktionsaufrufen abgearbeitet werden. Die Reaktion auf den ersten Interrupt wird definitiv am Ende der Kaskade erfolgen. Wenn es das ist, was Du möchtest, dann los!


    Ein Interrupt ist aber super, wenn Du z.B. einen Zähler hochzählen möchtest und einfach nur eine Status-Variable auf den einen oder den anderen Wert setzen möchtest. Also etwas, das in sehr sehr wenigen Programmzeilen erledigt ist. Da bist Du sehr schnell wieder raus, bevor ein neuer Interrupt entdeckt werden kann. Und mit diesem neuen Wert wird dann in der Endlosschleife weiter geackert.


    Dann gehe mal in Dich und überlege, ob die Hardware-Lösung für Dich eine mögliche Lösung darstellt. Dann hat das auch Einfluss auf die Software-Umsetzung. Die wird wesentlich einfacher umsetzbar werden!


    Eigentlich sollte die Software so aussehen, dass innerhalb einer Endlosschleife mögliche Ereignisse abgefragt werden und für jedes Ereignis eine sog. Callback-Prozedur aufgerufen wird.

    Im Extremfall (das wird bei Dir sehr wahrscheinlich sein), kannst Du die Aktion auch direkt innerhalb der Endlosschleife durchführen. Es geht ja nur darum einen Sensor auszulesen und den Wert an irgendeiner Position des Displays zu setzen. Das geht in einer Zeile... also nix mit Callbacks.


    Eines der Ereignisse besteht darin, die Endlosschleife zu verlassen, ggf. alle offenen Dateien zu schliessen, alle angemeldete Hardware wieder abzumelden etc. und danach das Programm zu beenden.



    Beste Grüsse


    Andreas


    -

    Ich bin wirklich nicht darauf aus, Microsoft zu zerstören. Das wird nur ein völlig unbeabsichtigter Nebeneffekt sein.
    Linus Torvalds - "Vater" von Linux

    • Icon-Tutorials (IDE: Geany) - GPIO-Library - µController-Programmierung in Icon! - ser. Devices - kein Support per PM / Konversation

    Linux is like a wigwam, no windows, no gates, but with an apache inside dancing samba, very hungry eating a yacc, a gnu and a bison.

    Edited 2 times, last by Andreas ().

  • Guten Abend euch beiden,
    und danke für diese Ausführungen.

    Dennoch möchte ich euch der Reihe nach antworten.

    @DeaD_EyE:

    Deine Programmcode hat nach einigen kleinen Anpassungen, die du zugegebener Maßen, weil nicht getestet, nicht wissen konntest, oder dir nicht aufgefallen sind - Ja, dein Programm läuft nun.
    Nun aber das große ABER ;)
    Da ich noch ein 16x2 Display mit offenen Connectoren hatte, habe ich zwar mit dem bedauerlichen Verlust des angelöteten I²C Enoders dieses 20x4 Display mit einer Buchsenleiste ausgestattet, und auch ein 16x2 Display so ausgestattet. So konnte ich aus der Bastelramschkiste mit einen werksneuen Encoder beide Displays testen. Der PICO war nur USB gespeist und die Vc des Encoders musste ich damit auch die Hintergrundbeleuchtung mit angeht an den Pin 40 VBUS hängen. Dann der Test noch mit dem 16x2 Display, hier dann die ernüchternde Feststellung ohne Levelshifter geht es wirklich nicht. Dazu muss oder sollte ich noch erwähnen, dass die USB Verbindung über ein PI 3A+ erfolgte, welches mit dem originalen 2,5 A Netzteil betrieben wird.
    Mit dem 16x2 Display alles so wie es sein sollte. Dann schnell Spannungsfrei das Display umgesteckt, schnell noch die Anpassungen in deinem Programm vorgenommen, genauer gesagt die Parametrierung in Zeile 40 angepasst. Ohne das eine Fehlermeldung auftrat - die Anzeigeelemente blieben dunkel. Nun noch die Gegenprobe nach diesem Schema direkt am 3A+ getestet, und siehe da, dass Display ist offensichtlich doch nicht defekt, obwohl ich das schon vermutet hatte.
    Irgendwo scheint es ein Bug in Verbindung mit des i²C Encodern und diesem Displaytyp 20x4 zu geben. Selbstverständlich könnte ich mich jetzt hinsetzen und diesen ursprünglichen Python Code in einen µPython Code übersetzen. Nur damit verliere ich am Pico auch wieder einige GPIOs.

    Dann zu deinen Programmteil mit dem Taster, das entspricht ja fast dem Original wie auf dem Link. Nun habe ich noch diese Entprellfunktion wie auf elektronik-kompendium.de dazugebastelt. Auch das funktioniert recht gut.
    Danke noch einmal für diese Erläuterung. :danke_ATDE:

    Bezugnehmend auf Andreas Anmahnung zum Umfang einer solchen Interruptroutine


    könnte man ja in der Modifizierten Routine die dort noch Update heißt, auch Boolsche Merker ummodeln ;)

    Das müsste doch soweit funktionieren, wenn in diesen Blöcken nicht nur die einzelnen Abfragen stehen, sondern auch die Ausgabefunktion enthalten ist ?

    Nur wie bekomme ich dann eine weitere Abfrage geregelt, damit ich zB nach 5 Sekunden Tasterbetätigung die Variable running auf False bekommen ?

    es grüßt euer
    Willy

  • Hallo,


    ich denke dein Beispiel geht in die falsche Richtung.

    Nutze, wie DeaD_EyE gezeigt hat, ‚irg‘ und messe die Zeit zwischen steigender und fallender Flanke. So kannst du die Länge des Tastendrucks bestimmen und entsprechend reagieren.

    Dazu gibt es sowas wie ‚ticks_ms‘, ‚ticks_diff‘ etc.


    Grüße

    Dennis

    “Ich benötige Informationen. Eine Meinung bilde ich mir selbst.”

  • Guten Morgen Dennis89,

    Danke für deinen Hinweis.
    Nur ist mir jetzt nicht ganz klar, wie ich aus diesem Beispielcode von DeaD_EyE der ja einige Elemente enthält, wie diese Display Ausgabe, die an dieser Stelle gar von mir gar nicht benötigt wird, diese wie du sagst Zeiterfassung der Tasterbetätigung machen soll ?
    Ich habe ja eigentlich nur 2 Entscheidungszustände,: Taster wurde kürzer als die Zeit X betätigt, und dann wird nur der Funktionsblock umgeschaltet, und dann die Zeit wurde überschritten, dann ist es das gewollte Programmende.

    Damit müsste dieser Funktionsblock zwei unterschiedliche Rückmeldungen geben ? Einmal das der Taster nur kurz gedrückt wurde, und einmal das die geforderte Betätigungszeit für die erweiterte Funktion überschritten wurde ?

    Jetzt ist für mich noch unklar, Ich hatte den Beispiel Code nur mal so reingestellt, wie ich mir den Ablauf vorstellen könnte. Im Prinzip funktioniert dieser "Kreislauf" ja auch, wenn ich dieses boolsche Array nutze. Macht es Sinn für jede der 5 Funktionsblöcke eine eigene Funktion ( def ) zu schreiben, die dann immer wieder nur und innerhalb der While Func_Pointer aufgerufen wird ?

    es grüßt euer
    Willy

  • Hallo,


    ich meinte, dass du IRQ verwenden kannst um auf Änderungen zu reagieren. Also auf die steigende und fallende Flanke deines Tasters.


    Gibt dir folgender Code die länge eines Tastendrucks aus (PIN muss angepasst werden)?

    Wenn ja, dann kannst du das erweitern. Du musst ja die Länge nicht ausgeben, sondern sie mit deiner "Kurzer-Tastendruck-Zeit" und deiner "Langen-Tastendruck-Zeit" vergleichen und dann eine entsprechende Funktion aufrufen.

    Da sind dann keine Listen mit True und False nötig, keine globalen Variablen(die meist eh nur Verwirrung stiften, anstatt zu helfen) und auch ansonsten ist es relativ übersichtlich.

    Der Code ist von mir nicht getestet, ich bin mir auch nicht sicher ob es geht, aber wenn du schon alles aufgebaut hast, geht der Test ja kurz.


    Grüße

    Dennis

    “Ich benötige Informationen. Eine Meinung bilde ich mir selbst.”

  • Guten Abend Dennis89


    Entschuldige bitte, das ist das Ergebnis !? :conf:

    Ich habe leider keine Ahnung wo ich nun anfangen soll in deinem Beispielcode zu suchen.
    So wie ich das lese und verstehe, wird wird einer Funktion zwei Argumente Übergeben, obwohl nur ein Argument erwartet wird.
    Das könnte ja nur Zeile 18 sein :helpnew:

    keypress_time = ticks_diff(ticks_us, start) oder ? :dau2:


    Ja das fiel mir dann noch ein, als ich schon auf Absenden geklickt hatte. Der Fehler kommt beim Loslassen des Tasters.

    es grüßt euer
    Willy

  • Code
        def measure_time(self):

    Der Callback wird mit einem Argument (der Pin instanz) aufgerufen.

    Da es eine Methode einer Klasse ist, bekommt die Methode zwei Argumente.

    Das erste Argument self ist die Instanz der Klasse und das zweite ist vom IRQ die Pin-Instanz.


    Code
        def measure_time(self, pin):
  • Guten Abend DeaD_EyE und Dennis89,

    Ich mache es kurz , weil gar nichts so funktionierte habe ich das Programm mal noch etwas modifiziert, weil außer den Fehlermeldungen, wurde auch keine "Pressed Time" ausgegeben.
    Also habe ich die "Show" Funktion mit in diese Class aufgenommen, und als dann auch nicht wirklich etwas passierte, habe ich noch zwei "print" Zeilen eingefügt um überhaupt eine Interaktion zu erreichen.
    Dazu nun das modifizierte Programm und der dazugehörige Bildschirmprint.



    Hardcopy


    So wie sich das für mich darstellte, funktioniert der erste IEQ Vektor auf steigende Flanke.
    Der Rest wird wie es aussieht mit der Ergänzung der PRINT Ausgaben gar nicht erst aufgerufen :wallbash:

    Zumindest die anfänglichen Fehlermeldungen sind weg.

    es grüßt euer
    Willy

  • Hallo,


    stimmt das 'Pin'-Argument hatte ich vergessen. Das weitere Problem ist, dass der erste IRQ durch den zweiten überschrieben wird.

    Wie siehts damit aus?

    Sorry hab leider nichts zum testen hier.


    Grüße

    Dennis

    “Ich benötige Informationen. Eine Meinung bilde ich mir selbst.”

  • Fast gute Nacht Dennis89 ;)

    Danke erst einmal für deine Bemühungen. :danke_ATDE:

    Ich musste noch eine ABS() in der Ausgabe Funktion ergänzen. Erst hatte er mir nur negative Werte ausgespuckt.
    Das Programm gibt keinen Fehler aus, es läuft. Jedoch hat es irgendwo noch eine Macke, die ich mir nicht erklären kann.

    Vielleicht noch ergänzend der Taster ist mit einem externen PullUp 12Kohm gegen V3.3 Out beschaltet. Er schaltet gegen GND.

    Aus irgendwelchen Gründen - ja es sind weniger als 1mSek - kommt es bei der Ausgabe immer wieder und sehr unregelmäßig zu solchen Ausgaben mit e-05



    Dann weiß ich auch nicht ob diese Ausgaben in Sekunden sind ? Ich habe auch mal probiert mit einem anderen µController in C und millis() diesen Tastereingang für jeweils 1 Sek zu triggern / schalten. Mit dessen eigener Ungenauigkeit 1.000mSek ist hier dann die Ausgabe irgendwo oder irgendwie doch sehr weit gestreut !? Ich wollte einfach mal wissen was diese Ausgabe bedeutet. :saint:
    Zwischen ca. 0,6 und 1,2 bekomme ich alle möglichen Werte angezeigt wenn ich ein Rechtecksignal 2 Sek High zu 1 Sek Low auf diesen Eingang gebe !?

    Dennis89 ich will jetzt auf keinen Fall meckern, dass Programm oder dieser Code läuft ja. Wenn dort ein Ergebnis größer 5 Ausgespuckt wird, entspricht das schon dem längeren Tastendruck. Nur diese ja, wie nennt man diese Extrem- Kurz-Ausweicher die definitiv nicht von mir erzeugt wurden müsste ich dann noch Filtern. Ich denke mal, das bekomme ich dann alleine hin.

    Ich sage noch einmal herzlichen Dank, auch an DeaD_EyE, und Andreas für die Unterstützung. :danke_ATDE:

    es grüßt euer
    Willy

  • Gemessen wird in Mikrosekunden, siehe: https://docs.micropython.org/e…y/time.html#time.ticks_us


    Wenn ich richtig geteilt habe sollten Sekunden ausgegeben werden.


    Zu der Streuung kann ich so nichts handfestes sagen. Vielleicht weis da jemand anders mehr.


    Weiterhin viel Erfolg bei deinem Projekt.


    Grüße

    Dennis


    Edit:

    Ich musste noch eine ABS() in der Ausgabe Funktion ergänzen

    Du kannst auch die Argumente von ticks_diff tauschen, dann ist die Differenz nicht mehr negativ.

    “Ich benötige Informationen. Eine Meinung bilde ich mir selbst.”

    Edited once, last by Dennis89 ().

  • Guten Morgen Dennis89,,


    Ich möchte nur mal einen kurzen Zwischen- oder Endbericht geben.
    Schlussendlich macht es keinen wirklichen Unterschied in der Auswertung, vielleicht von eine paar CPU Takten mehr die für diese Routine benötigt werden, ob ich diese Werte vertausche, oder die ABS() Funktion nutze ;)
    Dann habe ich per PM einen Hinweis bekommen. Dieser wahr sehr aufschlussreich. Beim Testaufbau auf einem Steackboard wären solche "Fehlfunktionen" normal. Dazu habe ich diesem Hinweis folgend eine kleine Platine zusammengelötet mit einem Steckplatz für dieses µC Board, und den Taster "ordentlich" verdrahtet angeschlossen. Auf einmal waren diese "Mikro-Impuls-Fehler" verschwunden. Dazu habe ich diesen neuen Aufbau einen ganzen Tag mit deiner Routine laufen lassen, und die Finger von dem Taster gelassen. Das Ergebnis, wie soll ich sagen, in dem Kommandfenster von Thonny keine einzige Auslösung, auch keine Fehlauslösung. Damit bin ich erst einmal sehr zufrieden.

    :danke_ATDE: Nochmals für deine tolle Unterstützung.

    Daraufhin habe ich nun versucht mehr schlecht als recht, diese / deine einzeln und fehlerfrei laufende Routine in das MAIN Hauptprogramm zu integrieren, und wollte dabei deinem Ratschlag folgend auf globale Variablen verzichten. Entweder ich habe einen Denkfehler oder ich bin wirklich zu blöd. Wahrscheinlich so nehme ich an, wenn dieser Taster-Interrupt mit der Zeit-gesteuerten Aktualisierung des Displays irgendwie zusammen fällt und dabei eine Umschaltung, oder der Sprung in eine andere Abfrage-Anzeige Routine angestoßen wird entsteht auf den Display ein Zeichenkuddelmuttel. Welcher erst bei der zweiten Aktualisierung innerhalb des neuen Programmteils wieder aufgehoben oder beseitigt wird. Kann es sein, weil eine andere Erklärung hätte ich nicht, dass diese Interruptbehandlung auch wenn diese nur sehr kurz stattfindet, es zu einer Art TIMEOUT bei der I2C Übertragung kommt ? Eigentlich wenn meine Denkweise richtig ist, müsste doch erst noch die Übertragung zum Display rein von der Chronologie im Programmablauf angeschlossen werden, bevor in den neuen Programmteil gesprungen wird ?
    Dazu habe ich noch zwei Versuche unternommen um diesen Darstellungsfehler zu beseitigen. Einmal, nachdem in den neuen Programmteil gesprungen wird, fülle ich alle Display Zeilen einfach mit "*" und nach einer Verweilzeit von sleep_ms(50) bringe ich erst das eigentliche Meßergebnis zur Anzeige. Zum Zweiten bin ich dann dem alten Trott mit Global folgend, deine Routine weider über das booslche Zeiger-Array folgend in einen Thread ausgelagert. Die letztere Variante funktioniert ohne das ich zuvor ein Löschen oder Überschreiben des Displayinhalts durchführen muss.
    Gut beide Varianten funktionieren. Die erste gefällt mir zwar nicht, aber - wie soll ich sagen, sie entspricht eher deiner Aussage. Du sagtest ja schon mehrfach GLOBAL nimmt man normalerweise nicht, und würde immer keine Krücke sein.
    Jetzt ist es aber für meine Begriffe eine Krücke, weil ich ohne diesen "Display-Löschvorgang" je nach Aktualisierungsrate immer mal wieder eine fehlerhafte Displaydarstellung habe. Sie tritt nicht immer auf, nur dann wenn beide Events zusammenfallen, aber trotzdem ist es nicht schön.

    Hier wäre nun die Frage von einem Lernenden der jeden Hinweis und Vorschlag in sich aufsaugt, kann man mit einer Programm-Krücke die GLOABL verwendet auch leben, oder sollte man grundsätzlich auf Global verzichten, um dann im Testbetrieb auf Fehler stoßen die man durch zusätzlichen Programmcode ausmerzen muss ?
    Die Frage ist jetzt mehr in Richtung Philosophie der Python-Programmierung gerichtet.

    Wie gesagt ich habe jetzt zwei Lösungen, beide Funktionieren, die eine mit Global gefällt mir dahingehend besser, weil ich mich nicht um die Darstellungsfehler kümmern muss. Die andere folgt deiner Aussage einer klareren nicht überladenen Einzelschrittprogrammierung in klar definierten Funktionen mit einem definiert eingeschränkten Funktionsumfang. Aber eben mit der Fehleranfälligkeit bei der Darstellung.

    Ich möchte das Thema jetzt auch nicht endlos weiter in die Länge ziehen, würde dich aber bitten nochmal ein kurzes Statement dazu abzugeben. Danke.

    es grüßt euer
    Willy

  • Hallo,


    der Code kann durch aus bis an das Ende deiner Lebtage mit globalen Variablen funktionieren. Dadurch schreibst du aber einen Code, der zum einen schlecht wartbar ist und zum anderen auch unverständlich ist. Unabhängig von der Programmiersprache will man nicht das jede Funktion einfach so Variablen ändern kann, da verliert man schnell den Überblick, bei der Fehlersuche wird man verrückt und sauber erweiterbar ist das Programm auch nicht, bzw. man handelt sich bei Erweiterungen/Änderungen etc. schnell Fehler ein.

    Die Idee ist, das man Funktionen so schreibt, das alle unabhängig voneinander testbar sind. Die bekommen dann alles was sie benötigen als Argumente übergeben und geben neue Objekte zurück. Dann ist das sehr strukturiert, weil man genau sieht was da passiert. Wenn du in drei Funktionen globale Variablen verarbeitest, dann ist das schlecht nachvollziehbar, schlecht zu testen und schlecht zu erweitern.

    Ich schreibe diesen Hinweis immer, auch bei kleineren Programmen, bei denen man trotz globaler Variablen die Logik noch verfolgen kann, weil es häufig nicht bei kleineren Programmen bleibt. Und wenn man sich mal was angewöhnt hat, dann wird es schwierig das wieder abzulegen. Gerade bei kleineren Programmen kann man das Übergeben von Argumenten sehr gut lernen. So war es zu mindest bei mir.

    Hier ist auch noch einiges beschrieben:

    https://stackoverflow.com/ques…id-using-global-variables


    Deine restliche Beschreibung kann ich nur bedingt nachvollziehen, da fehlt mir Code um zu sehen was eigentlich gemacht wird.


    Grüße

    Dennis

    “Ich benötige Informationen. Eine Meinung bilde ich mir selbst.”