Sensor-Signal per Interrupt abfragen

  • Hallo zusammen,

    ich möchte Euch zeigen, wie man das Signal eines Sensors in einer Interrupt-Routine auswertet.
    Meine Beschreibung bezieht sich jetzt auf die Programmierung in C, ich habe aber mal meine Fühler ausgestreckt und zwei Board-Gurus um eine Erweiterung in Python und in PHP zu bitten.

    //EDIT:
    Auf Anregung von dbv - Vorab noch eine kurze Erklärung, was mit Interrupt Service Routinen (ISR) gemeint ist:

    Manchmal ist es notwendig, einen Signalwechsel von HIGH auf LOW oder umgekehrt an einem der GPIOs abzufragen.
    Das ist z.B. bei einem Schalter der Fall, oder bei einem PIR-Sensor, einem Line-Hunter, einem Geräuschsensor, oder, oder, oder ...
    Oft wird diese Aufgabe mit einer Schleife gelöst, die den Pin abfragt und dann einen sleep() oder ähnlich ausführt, um das System nicht zu sehr zu belasten.
    Die elegantere Methode ist allerdings die Nutzung einer ISR. Sie hat den Vorteil, dass man sich nicht mehr grossartig um den Statuswechsel eines Pins kümmern muss.
    Ein Funktionsaufruf der API bewirkt, dass von der entsprechenden Bibliothek der Aufruf einer selbst definierten Funktion durchgeführt wird, sobald der Pin seinen Zustand ändert.
    Meist ist es möglich, den Statuswechsel näher zu definieren - also dass der Aufruf nur bei einem Wechsel von LOW auf HIGH erfolgt oder vice versa.
    Durch Einsatz der Interrupt-Routinen wird die Systemlast des Programms auf ein Minimum reduziert.


    Ich habe drei verschiedene APIs ausgetestet, und lediglich eine ist beim Thema Interrupt-Handling gescheitert.

    wiringPi:


    wiringPi herunterladen und nach Anleitung installieren falls noch nicht geschehen.

    Im Beispiel-Code seht ihr dann, dass nach der Initialisierung ein Pin als INPUT definiert und anschliessend eine ISR definiert wird.

    Das Programm läuft dann in einer Endlosschleife und wartet auf das Eintreten eine Interrupts.
    Es ist auch möglich, den Timeout zu ändern und einen sleep() in die Schleife einzubauen.



    bcm2835:

    ACHTUNG: Diese Bibliothek ist bei meinen Versuchen in Sachen Interrupt leider durchgefallen!
    Je nach Kernel-Version kann es sein, dass die Library das gesamte Programm blockiert. Der Autor lässt sich leider nicht darüber aus, ob er das behebt oder welche Kernel-Versionen davon betroffen sind.

    Also, nur der Vollständigkeit halber: die BCM2835 Library herunterladen und nach Anleitung installieren.

    Im Prinzip funktioniert das Ganze genau so, wie mit wiringPi. Ich habe Euch einen entsprechenden Beispiel-Code mal hier eingefügt.



    Und schliesslich noch meine Favoriten-Bibliothek: pigpio

    pigpio herunterladen und nach anleitung installieren, falls noch nicht geschehen.

    Auch hier läuft es nach demselben Schema: Initialisieren, Pin für den Interrupt definieren und Service Routine installieren.
    Ich finde es allerdings um einiges eleganter, weil die Service-Routine - wie es sich gehört - im Hintergrund läuft und man sich, im Gegensatz zu wiringPi, nicht mehr drum kümmern muss.



    Nun, das war's mal mit der C-Schnittstelle.

    Ich hoffe, dass das einigermassen hilfreich für den einen oder anderen ist.
    cu,
    -ds-

    • Offizieller Beitrag

    Board-Guru (thx @-ds-) Nr.1 meldet sich zu Wort und fasst das ganze zum Thema Python zusammen
    Eine sehr schöne, ausführliche und bebilderte Version von Alex Eames gibt es hier (englisch)

    Vorrausetzung
    Es wird mindest das python RPi.GPIO Modul in der Version 0.5.1 benötigt.
    Eine Installation mittels

    Code
    sudo apt-get install python-rpi.gpio


    sollte schon die Version 0.5.3a zu Tage bringen.

    Versions Check
    Folgendes im Terminal eingeben

    Code
    python
    import RPi.GPIO as GPIO
    GPIO.VERSION
    #es sollte >='0.5.3a' erscheinen
    #mit STRG+D beenden

    Beispiel Nr 1.
    Ein simples Programm was solange wartet bis ein Schalter gedrückt wird.
    Die CPU Auslastung tendiert, entgegen der Endlosschleifen-Version, gen Null.
    Der Schalter ist an GPIO 23 (PIN 16) sowie GND (PIN 6) angeschlossen.

    [code=php]
    #!/usr/bin/python
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM)
    #setzen des GPIO 23 auf HIGH
    GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    try:
    #Das Programm wartet an der dieser Stelle so lange bis der Schalter
    #gedrueckt wurde und der GPIO 23 dadurch auf LOW wechselt
    GPIO.wait_for_edge(23, GPIO.FALLING)
    print "Schalter wurde gedrueckt, nun kann es weiter gehen"
    #code der jetzt kommt wird erst nach dem Schalterdruck ausgefuehrt
    except KeyboardInterrupt:
    GPIO.cleanup()
    GPIO.cleanup()
    [/php]

    Was Macht das Program:
    Es importiert die GPIO Bibliothek, setzt GPIO 23 auf HIGH und wartet dann solange bis sich der Status GPIOs verändert

    Meistens wollen wir aber, dass unser Programm irgendwas macht und trotzdem bei Eintreten eines Ereignisses darauf reagiert, ohne das Hauptprogramm zu beeinträchtigen.
    Das ganze nennt sich Threading oder um genau zu sein Threaded Callback.

    Beispiel Nr 2.
    [code=php]
    #!/usr/bin/python
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    def meinetgethreadetefunktion(callback):
    print "Glueckwunsch, diese Funktion wurde ausgeführt obwohl"
    print "das Hauptprogramm noch lauft"
    GPIO.add_event_detect(23, GPIO.FALLING, callback=meinetgethreadetefunktion)
    While True:
    sleep(1)
    GPIO.cleanup()
    [/php]

    Was Macht das Program:
    Es importiert die GPIO Bibliothek, setzt GPIO 23 auf HIGH und setzt dann einen Event Handler, der beim erkennen des Statuswechsels des GPIOs die Funktion
    meinetgethreadetefunktion in einem anderen Thread aufruft, während das Hauptprogramm friedlich vor sich hin schläft.

    Anmerkung
    Wie immer ist direktes kopieren von python-code nicht zu empfehlen, da ihr mit ziemlicher Sicherheit Einrückungsfehler erhalten werdet.

    Der Unterschied zwischen Genie und Wahnsinn definiert sich im Erfolg.

    Einmal editiert, zuletzt von dbv (25. April 2014 um 08:43)

  • Hallo dreamshader,

    danke für dein Interrupt-Thema.

    Leider möchte ich mich hier einbringen, da ich Deine Ausführung so nicht teilen kann.
    Du schreibst, dass die wiringPi-Methode in Deinen Augen nicht optimal sei, da man sich hierbei ständig um die Abfrage kümmern müsse.
    Diese Feststellung ist nicht korrekt, du mischt in deinem Code zwei unterschiedliche wiringPi-Methoden, eine aktuelle und eine veraltete, von der auf der Website [font=".HelveticaNeueUI"]http://wiringpi.com/reference/prio…ts-and-threads/ bereits abgeraten wird.[/font]

    In deinem Beispielcode definierst du völlig korrekt eine Funktion (interrupt_0), die aufgerufen wird, wenn der Interrupt ausgelöst wird.

    Einige Zeilen später ruftst du auch völlig korrekt die wiringpi-Fubktion "wiringPiISR(pin, edgeType, funktionsname)" auf.

    Bis hierhin ist alles korrekt. Dann jedoch rufst du eine zweite, veraltete Interruptroutine auf (int waitForInterrupt) und das ist eben falsch. Diese Funktion ist die veraltete.

    Mit der aktuellen Funktion kannst du m.E. noch einfacher, als mit pigpio, eine Interruptsteuerung definieren.
    Nämlich 1. bei ausgelöstem Interrupt aufzurufende Funktion definieren
    Und 2. die Interruptsteuereung "wiringPiISR" mit den passenden Parametern aufrufen.
    Das war es. Ab jetzt kann dein normales Programm laufen, die Interruptabfrage läuft, wie bei pigpio, im Hintergrund!

    Ich arbeite seit einigen Wochen mit den Routinen von wiringPi und bin davon begeistert. Natürlich funktionieren auch andere!

    Hoffentlich waren meine Erläuterungen verständlich und nicht zu umfangreich.

    Gruß und einen schönen Abend,
    Heiko


  • Hoffentlich waren meine Erläuterungen verständlich und nicht zu umfangreich.
    Gruß und einen schönen Abend,
    Heiko

    ich bin sicher dreamshader ist dankbar für deine Ergänzung, ich bin es.....:thumbs1:

    lasst die PIs & ESPs am Leben !
    Energiesparen:
    Das Gehirn kann in Standby gehen. Abschalten spart aber noch mehr Energie, was immer mehr nutzen. Dieter Nuhr
    (ich kann leider nicht schneller fahren, vor mir fährt ein GTi)

    Einmal editiert, zuletzt von jar (26. September 2013 um 08:58)

  • Hi heikoho,

    na, dann hat der WiringPi Entwickler scheinbar selbst gemerkt, dass seine damalige Lösung nur suboptimal war ;) ...

    Ich habe die wiringPi vor einigen Monaten mal ausprobiert - noch vor der Umstellung auf die neue Version. Und das mit dem Wait4Interrupt aus den aktuellen Beispielen übernommen. Da gabs das vermutlich halt noch nicht.
    Gut zu wissen, dass das jetzt besser gelöst ist, aber mittlerweile habe ich mich für meinen Teil auf die pigpio eingeschossen.

    Aber danke für die Info,
    -ds-

  • Hallo zusammen,

    es ist halt auch die Frage, welche Ansprüche man an die Performance stellt - da gehöre ich eher zu den Perfektionisten. ;)

    Ich weiß nicht, ob der Prozessor eine eigene IRQ-Leitung für die GPIOs hat. Jedenfalls sieht es so aus, daß es nur über das IRQ Management des Kernels funktioniert und so scheint "wiringPi" zu arbeiten (was ja durchaus auch einer sauberen Linux Programmierung entspricht). Es wird ein thread mit Endlosschleife erzeugt und jeweils auf den Interrupt gewartet (äquivalent den C-Funktionen "select()" bzw. "poll()" mit den entsprechenden "GPIO value" Kernelschnittstellen.

    So laufen aber trotz "Interruptsteuerung" jede Menge CPU-Instruktionen ab!


    "pigpio" hat einen ganz anderen Ansatz. Auf das Interruptmanagement des Kernels wird verzichtet, man klinkt sich direkt in den DMA Takt ein. Somit erfolgen zwar nur zeit- und keine signalgesteuerten Interrupts, der Zustand der GPIOs wird mit diesem Takt gepolled, das scheint aber wesentlich effizienter zu sein als die obige, normalerweise empfohlene Methode - nachgewiesen auch durch entsprechende Tests. So lassen sich z.B. per PWM mehrere Servos mühelos gleichzeitig steuern und die CPU-Last bleibt trotzdem noch recht weit im grünen Bereich.

    Für die Abfrage von ein paar Buttons natürlich alles nicht sooo wichtig. ;)

    Gruß, mmi

    P.S.: "pigpio" ist (noch?) nicht quelloffen, "wiringPi" schon - für viele verständlicherweise auch ein Argument.

  • Ja mmi, mein Freund :) ... das ist aber eine schöne Überraschung ...

    Nun, Du hattest mich ja damals von pigpio überzeugt, und imho macht es keinen Sinn bei jeder neuen Version die Bibliotheken zu wechseln.
    Ausserdem war die Wahl ja neben der ISR noch die Pulsweiten-Modulation.

    So, erst noch ein bisschen kreative Pause, dann gehts mit dem uNodes weiter ;) ...

    ciao,
    -ds-

  • Servus ds,

    mir ging's bei meinem obigen Beitrag ja nur um die Darstellung, daß die unumgängliche IRQ Verwaltung des Kernels bei hohen Frequenzen nur mäßige Ergebnisse liefert (irgendwas zwischen 10 und 40 kHz lt. den Testergebnissen im Netz).

    Mit der DMA Taktung (pigpio) gibt es keinen signalgesteuerten Interrupt, hier wird einfach mit hoher Zeitauflösung interrupted, die GPIO-Abfrage erfolgt jeweils unabhängig von einer Signaländerung mit simplem polling - auf den ersten Blick eher die verwerfliche Methode. So werden aber Werte im MHz-Bereich erreicht - also deutlich schneller bei weniger CPU-Last. Die Kernelroutine hat also offensichtlich einigen Overhead, aber Linux ist ja auch kein RT System. Wie einfach und effizient ist da doch ein "attachInterrupt" mit direkter ISR Routine bei den uCs.

    Natürlich ist deshalb - rein performancebezogen - "pigpio" die erste Wahl.

    Wenn man aber lieber z.B. in Python programmiert und ein paar Buttons abfragen will, ist es nicht so wichtig, was man verwendet - und das meine ich jetzt keineswegs abwertend - jede Methode hat halt Vor- und Nachteile und solange man von einer Anwendung nicht zu mehr Effizienz gezwungen wird .... ;)

    Zitat: "... das ist aber eine schöne Überraschung ...":
    Nunja, mittlerweile gibt's hier ne Menge Leute mit "knoffhoff", die blitzschnell Problemlösungen bieten - das ist doch prima, da siehst Du mich nicht mehr so oft.

    Schönen Sonntag nach RO,
    mmi

  • Hallo zusammen,

    ich brauche eure Hilfe :helpnew:

    Vorweg: ich bin ein blutiger Anfänger was Programmierung angeht, aber ich bin Willens, etwas zu lernen :)

    Ich möchte gerne den Zustand eines GPIO-Pins abfragen und, wenn sich der Zustand ändert, eine Aktion auslösen.

    Derzeit läuft auf meinem Raspberry Pi B+ die Software 'Pi MusicBox', eine aufgebohrte Version von Mopidy. Ich möchte nun erreichen, dass ein an die Pins 6 (GND) und 7 (GPIO4) angeschlossenes Netzteil, welches durch einen Bewegungsmelder ein- oder ausgeschaltet wird, die Wiedergabe eines Streams startet bzw. stoppt.

    Das Netzteil ist ein 5V-Netzteil mit zwei in Reihe geschalteten 100 Ohm-Widerständen.

    Wie genau müsste eine Abfrageroutine nun für mich aussehen?

    Bin für eure Hilfe sehr dankbar!

    Viele Grüße
    cool400 :cool:

  • [quote="dbv" pid='33991' dateline='1380111355']
    Board-Guru (thx @-ds-) Nr.1 meldet sich zu Wort und fasst das ganze zum Thema Python zusammen
    Eine sehr.......

    Meistens wollen wir aber, dass unser Programm irgendwas macht und trotzdem bei Eintreten eines Ereignisses darauf reagiert, ohne das Hauptprogramm zu beeinträchtigen.
    Das ganze nennt sich Threading oder um genau zu sein Threaded Callback.

    Beispiel Nr 2. (ergänzt)
    [code=php]
    #!/usr/bin/python
    from time import sleep
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    def meinetgethreadetefunktion(callback):
    print "Glueckwunsch, diese Funktion wurde ausgeführt obwohl"
    print "das Hauptprogramm noch lauft"
    GPIO.add_event_detect(23, GPIO.FALLING, callback=meinetgethreadetefunktion)
    While True:
    sleep(1)
    GPIO.cleanup()
    [/php]

    Was Macht das Program:
    Es importiert die GPIO Bibliothek, setzt GPIO 23 auf HIGH und setzt dann einen Event Handler, der beim erkennen des Statuswechsels des GPIOs die Funktion
    meinetgethreadetefunktion in einem anderen Thread aufruft, während das Hauptprogramm friedlich vor sich hin schläft.

    Anmerkung

    ... bei kurzem ansehen des code Beispiels 2 habe ich bemerkt, dass jedenfalls folgende import Zeile fehlte:

    from time import sleep

    Sonst danke für den wertvollen Beitrag

    Einmal editiert, zuletzt von josef64 (13. August 2017 um 22:48)

  • @ josef64 : Dieser Thread hier ist über 4 Jahre alt!

    Deine Vorgehensweise ist mittlerweile auch überholt - man sollte in der Callback so wenig wie möglich abhandeln. Deine "Threaded Callback" wird durch die print's ausgebremst = nicht optimal, Flankenwechsel könnten verpasst/versäumt werden.

    Weiterführende Infos: FAQ => Nützliche Links / Linksammlung => Interrupt
    Zu bevorzugende Vorgehensweise: FAQ => Nützliche Links / Linksammlung => Interrupt => #8


    PS: Der Callback wird der ausgelöste Pin bzw Channel übergeben. "def meinetgethreadetefunktion(callback):" sorgt unnötig für Verwirrung

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!