Kampis Elektroecke

Interrupts

Raspberry Pi

Hier möchte ich erklären, wie mit Hilfe des Python-Moduls RPi.GPIO Moduls die Interrupts der GPIO Pins des Raspberry Pi genutzt werden können.

Installieren des Moduls:

Um die Interrupts nutzen zu können, ist eine aktuelle Version des Moduls RPi.GPIO notwendig. Um dieses Modul nutzen zu können muss es ggf. installiert werden.

$ sudo apt-get update
$ apt-get install python-rpi.gpio

Bei den neueren Versionen des Betriebssystems Raspbian Wheezy ist dieses Python Modul bereits enthalten und muss nicht mehr installiert werden.

Ein erster Test:

Für den ersten Testlauf habe ich einen Taster an meinen Raspberry Pi angeschlossen. Die Beschaltung sieht wie folgt aus:

Das Ziel soll es sein ein einfaches Zählprogramm auf Interrupt-Basis zu schreiben.

Dazu wird als erstes der GPIO 24 als Eingang konfiguriert und der Pull-Down Widerstand aktiviert. Dadurch führt der I/O einen konstanten Low-Pegel, wenn der Taster nicht betätigt wird.

GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

Anschließend deklariere ich den Callback, also den Programmabschnitt der ausgeführt werden soll, sobald ein Interrupt ausgelöst wird:

def Interrupt(Channel):
    global Counter
    Counter = Counter + 1
    print "Counter " + str(Counter)

Durch die Zeile global Counter wird eine Referenz auf die globale Variable Counter erzeugt. Dadurch kann auf die Variable Counter, die im Hauptprogramm genutzt wird, in dem Callback zugegriffen werden. Die anderen beiden Zeilen erhöhen die Variable und geben den Inhalt der Variable aus.

Nun muss dem Programm nur noch die Bedingung für das Auslösen eines Interrupts mitgeteilt werden:

GPIO.add_event_detect(24, GPIO.RISING, callback = Interrupt, bouncetime = 200)

Jetzt wird der Interrupt von GPIO 24 aktiviert. Als Auslösekriterium gebe ich eine steigende Flanke an und mittels callback = Interrupt wird definiert, dass das Programm bei einem Interrupt in das Unterprogramm Interrupt springen soll. Der Zusatz bouncetime = 200 legt eine Prellzeit von 200 ms fest. Dies ist nur wichtig wenn Taster als Interruptquelle dienen.

Wenn dieses Programm nun ausgeführt wird, wird bei jedem Tastendruck die Variable Counter um eins erhöht und in der Konsole ausgegeben:

Wichtig ist, dass das Programm (bedingt durch bouncetime) nur alle 200 ms auf einen Tastendruck reagieren kann.

Das Programm gibt es in meinem Git-Repository zum Download.

Zurück zu GPIO

55 Kommentare

  1. Hallo,

    hab gerade zufällig deine Seite mit den Interrupts am Raspberry PI entdeckt.
    Möcht mich dafür bedanken, denn ich werd die paar Tips bei mir verwenden.

    lg

  2. Hallo,
    super Artikel. Bei mir hat es nur auf einem Raspberry Pi funktioniert. Die anderen 2 Raspberry Pis wollen irgendwie nicht. Die Shell spuckt dann in einem Rhythmus von ca 1 Sekunde Counter1, Counter2, Counter3, usw. aus. Ich habe einen 10k Widerstand dazwischen gehängt, aber es will immer noch nicht.
    Hast du eine Idee, wo es klemmen könnte?

    Gruß Lukas

    1. Hallo Lukas,

      das hört sich an als ob da ein nicht definierter Pegel an dem IO vorhanden ist.
      Hast du den Pull-Down Widerstand aktiviert?

      Gruß
      Daniel

  3. Hallo Daniel,
    ich habe dein Skript eins zu eins kopiert, nur den Port von 24 auf 23 geändert. Das Skript müsste den Inhalt mit dem Pull-Down Widerstand eigentlich beinhalten, oder liege ich da falsch?

    Gruß Lukas

      1. Hallo,
        mein Fehler war, das ich keinen richtigen Taster verwendet habe ;)
        Jetzt hab ich mir einen richtigen angeschafft und so langsam habe ich das Gefühl, als möge mich der Raspberry Pi nicht mehr…
        Naja, zumindest ist mein momentaner Fehler der, das wenn ich irgendeinen Lichtschalter im Haus einschalte, der Raspberry Pi komischerweise auch ein Signal bekommt. Anfangs waren es nur die Lichtschalter, jetzt sogar auch nur, wenn ich den Laptop ans Netzteil anschließe…. Das kann doch eigentlich nicht sein oder?
        Im Moment ist das etwas unpraktisch, da der Raspberry Pi eine Lampe schaltet und dann mitten in der Nacht das Licht angeht…
        Ich fange an, langsam zu verzweifeln…
        Weißt du zufälligerweise woran es liegen kann?
        Gruß Lukas und ein frohes neues Jahr :)

        1. Hey Lukas,

          hört sich für mich nach einem Schaltungsproblem an. Hast du den Aufbau mal überprüft (vor allem die Masseverbindungen!)?

          Gruß
          Daniel

  4. hallo lukas,

    ist eine sehr gute erklärund der interrupts unter python.
    aber kann man das ich auf einen ps3 controler übertragen? also ich habe da dan funktoinen z.b. j.get_button(x) = 0 / 1
    kann ich durch dies auch einen interupt auslösen lassen?

    mfg chris

    1. Hallo Chris,

      die Interrupts (so wie ich sie beschrieben habe) funktionieren nur bei den GPIOs.
      Wenn du per Interrupt den PS3-Controller auslesen willst, musst du dir was komplett eigenes machen.

      Gruß
      Daniel

  5. Hi,
    bin bei der suche nach einen Impulszähler auf deine Seite gestoßen.
    Jetzt hab ich mal ne Frage, wenn ich dies eingebe

    GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)

    kommt diese Meldung

    -bash: syntax error near unexpected token `24,‘

    und muß ich diesen Codeabschnitt mit in die isr.c Datei einfügen oder ersetzen

    def Interrupt(channel):
    global Counter
    Counter = Counter + 1
    print „Counter “ + str(Counter)

    Gruß Stefan

    1. Hallo Stefan,

      schick mir mal bitte deinen ganzen Code. Dann schaue ich mal nach dem Fehler.

      Die Funktion „Interrupt“ musst du in deinem Python-Code einfügen. Sie muss VOR die Aktivierung des entsprechenden Interrupts gesetzt werden.

      Gruß
      Daniel

  6. Hi,

    habe noch ein wenig gegooglet, versuche es grad mit diesem hier

    #!/usr/bin/python
    import RPi.GPIO as GPIO
    import time

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(18, GPIO.IN)

    if (GPIO.input(18)):
    global count
    count = count + 1

    print Count

    da gibt er mir bei print Count ne Meldung
    „NameError: global name ‚count‘ is not defined“
    Hab mir schon die Finger wund gegooglet, ich find irgendwie nix richtiges.
    Vielleicht noch kurz was dazu was ich überhaupt vor habe.
    Ich möchte gern mit einem Pin der GPIO´s einen Impuls eines Duchflussmessers zählen,
    eigentlich ganz einfach, habe ich gedacht, ich bin mit dem RaspberryPi und dem Arduino grad erst angefangen , muß gestehen das ich mir das etwas einfacher vorgestellt habe.

    Gruß Stefan

  7. Hi,
    könntest du dir mal das Script anschauen und sagen was ich da falsch gemacht habe.

    import time
    import RPi.GPIO as GPIO

    GPIO.setmode(GPIO.BCM)

    GPIO.setup(18, GPIO.IN)

    GPIO.setup(11, GPIO.OUT)

    while 1:

    if GPIO.input(18) == GPIO.HIGH:
    GPIO.output(11, GPIO.HIGH)

    if GPIO.input(18) == GPIO.LOW:
    GPIO.output(11, GPIO.LOW)

    beim ausführen kommt immer diese Meldung

    File „gpio.py“, line 14
    if GPIO.input(18) == GPIO.HIGH:
    ^
    IndentationError: expected an indented block

    Gruß und Frohe Weihnachten
    Stefan

    1. Hallo Stefan,

      du musst die Codezeilen, die zu einer while-Schleife oder einer if-Abfrage gehören, mit einem Tab einrücken.
      Dieser Fehler bedeutet, dass du ein Tab vergessen hast :)

      Gruß und ein frohes Fest
      Daniel

      1. Danke erst mal für deine Antwort!
        Jetzt must mir nur noch verraten was du mit „Tab“ meinst.
        Hab Google schon bemüht hab aber nix richtiges gefunden.

          1. Hatte es doch noch gefunden, das das nicht ganz links anfangen darf, jetzt hab ich noch ne frage, wie mache ich das die GPIO´s auch nach Neustart noch die vorher zugewiesene Config hat und wie mache ich es das das Script auch läuft wenn kein User angemeldet ist?

            Gruß

          2. Hallo Stefan,

            du kannst z.B. den Export der GPIOs und eine festgelegte Konfiguration über die Datei /etc/rc.local erzeugen.
            Wenn du nach einem Neustart die ursprüngliche Konfiguration wiederherstellen willst, musst du diese vor dem Neustart einmal speichern (z.B. über ein Skript).

            Du kannst das Skript im Hintergrund laufen lassen, wenn du ein „&“ hinten anhängst:

            python MeinProgramm.py &

            Gruß
            Daniel

  8. Hallo Kampi, cooles Tutorial. Habe mich schon öfter von deiner Seite inspirieren lassen. Kannst du mir sagen, wie ich den Counter wieder zurücksetze?

  9. Hallo,

    gut Seite!

    Ist es denn Möglich mit dem Pi mehrere Interupts gleichzeitig zu verarbeiten, also mehrere Durflussmesser gleichzeitig abzufragen?

    Vielen Dank

    Walter

    1. Hallo Walter,

      nein mehrere Interrupts funktioniert nicht (habe es letztens getestet…das macht Probleme).
      Aber du kannst aus mehreren Quellen in die selbe Interruptroutine springen und dann in der ISR eine Abfrage machen wer die ISR ausgelöst hat (einfach die übergebene Variable auswerten…die gibt den IO an der den Interrupt ausgelöst hat).

      Gruß
      Daniel

  10. hi. Die anleitung ist supi und hat mich bei meinem Projekt ein ganzes stück vorran gebracht. Dennoch bekomme ich immer einen runtime-error. kannst du mir sagen was ich da immernoch falsch mache?
    ich möchte ein signal auswerten, welches im bereich weniger millisekunden liegt, zwei positive flanken messen und deren Abstand messen

    def Interrupt (channel):
    i = 0
    i = i +1
    if i == 1:
    t1 = time.time()
    elif i == 2:
    t2 = time.time()
    d = d +1

    if d == 0:
    GPIO.add_event_detectet(ein, GPIO.RISING, callback = Interrupt)
    else:
    d = 0

    „ein“ habe ich vorher als eingangs-GPIO definiert

    1. Hallo Norbert,

      was soll die Zeile hier machen:

      f d == 0:
      GPIO.add_event_detectet(ein, GPIO.RISING, callback = Interrupt)
      else:
      d = 0

      Die Methode „add_event“ muss nur einmalig aufgerufen werden, da sie nur die ISR für den Interrupt festlegt.

      Gruß
      Daniel

      1. die zeilen mit dem d sind deshalb dashalb da, weil ich den befehl (GPIO.add…) nur einmal ausführen möchte. danach kommt noch ein timer. Wenn dieser abgelaufen ist, startet das Prgm von vorn und Temperaturen werden ausgelesen. Anhand dieser Temperaturen berechne ich die Drehzahl des Lüfters (via PWM).
        Und nun wollte ich die drehzahl auslesen. benutze den gpio Port 23 ( BCM) als eingang.

        habe sogar ein Pulldown widerstand mit eingebaut und ersteinmal den Eingang direkt an der PWM angeschlossen (mit vorwiederstand) und habe im oszi ein spannungspegel von 2,8V abgelesen. Sollte also auch ersteinmal reichen für den Eingang

        Kann es sein dass der Eingang zu langsam für solche Frequenzen ist?

        1. Hey Norbert,

          ich glaube du bringst da ein bisschen was durcheinander…
          Wenn du im Grunde eine Schleife machen willst, mach das so:

          GPIO.add…

          while(1):
          Programm
          time.sleep(1)

          Alles vor dem „while“ wird einmal ausgeführt und die while-Schleife ersetzt dann deinen Timer. Das „sleep“ sorgt für ein Delay.
          Ich denke du hast einfach nur ein Problem mit deiner ISR…die PWM wird etwas um die 10kHz schnell sein. Das sollte das Programm vielleicht noch können… (mess die am besten mal nach)

          Gruß
          Daniel

          1. Das GPIO.add… soll ja aber bei jedem durchlauf auch wieder mit ausgeführt werden. quasi um eine Regelung des Lüfters zu realisieren (bzw die pwm zu überwachen)

          2. Du verstehst das falsch.
            Dieser Befehl ist nur dafür da um den Interrupt einmalig im Programm zu initialisieren und zu aktivieren.
            Du musst ihn nicht bei jedem Programmdurchlauf neu aktivieren, da er bis zum Ende des Programms aktiv bleibt.

          3. sry das ich etwas auf dem schlauch stehe.
            ich habe jetzt den def Interrupt befehl und den GPIO.add… befehl vor meiner try: while schleife stehen.
            bei dem interrupt befehl einmal
            if I == 0:
            t1 = time.time()
            I = I +1

            und dann

            if I == 1:
            t2 = time.time()
            I = 0

            damit halt der Abstand zwischen zwei positiven flanken gemessen werden kann.
            jetzt kommt aber der Fehler:
            if I == 0:
            UnboundLocalError: local variable „i“ referenced bevor assignment.

            aber ich habe doch die variable ganz am anfang mit I = 0 definiert.

            glaube ich bin zu blöd dazu :(

            danke übrigens für deine Geduld mit mir ;)

  11. Hi Kampi,
    coole Hilfestellung! Vielen Dank!
    Bastel grad an einer Ausleseeinheit meines Wasserzählers rum. Hab es jetzt endlich geschafft, dass ein Liter einen Impuls gibt. Hurra!!! Soll mich keiner fragen wie lang das gedauert hat… Jetzt bin ich auf deinen Zähler gestossen und leider zählt der ab einem gewissen Punkt nicht mehr weiter. Hab grad leider keine Ahnung woran das liegt. Die Impulse werden eigentlich sauber und regelmäßig rausgegeben. EIne Idee?
    Andere Sache: Ich wollte ab einer gewissen Anzahl von Impulsen in einer bestimmten Zeit bestimmte Programme starten (Mail, Push-Nachricht etc.). Sprich, bei einem Rohrbruch möchte ich eine Pushnachricht bzw. Mail erhalten.
    Hast du eine Idee wie man sowas in dein Script einbettet. Hab zwar eine interessante Vorlage gegoogelt, aber ist leider nix geworden.
    Schon jetzt ein großes Danke!
    Frohe Ostern!
    Benito

    1. Noch zwei Sachen:
      Ein Problem ist, dass wenn der Zeiger des Wasserzählers exakt in der Lichtschranke stehenbleibt, der Counter fröhlich weiterläuft. Hab´s leider noch nicht rausbekommen, wie ich das anstelle, dass der Wert nur bei Veränderung gezählt wird. Zudem gibt´s eine Zählung, wenn der Zeiger in die Lichtschranke fährt UND wieder eine beim Rausfahren.
      Bin gestern auf Ubidots gestossen. Eine Verknüpfung hab ich angefangen. Wie cool ist dass denn!
      Viele Grüße!
      Benito

      1. Hallo Benito,

        schick mir mal dein Programm zu. Dann schaue ich mal drüber.
        Zu deinem zweiten Problem:
        Du zählst einfach die Impulse und startest dazu einen Timer, bzw. liest die Systemzeit regelmäßig aus. Bei jedem Timerevent schaust du auf den Zählerstand und wenn der zu hoch ist bearbeitest du dein Event was ausgelöst werden soll.

        Gruß
        Daniel

        1. Hi Daniel,
          vielen Dank für die schnelle Antwort!

          1. Das mit dem erwähnten Hänger im Programm war ein Verdrahtungsfehler.

          2. Hab jetzt ein kleines Script geschrieben, bei dem (bei „schnellen“ Impulsen) nur ein Ergeignis gezählt wird. Bei „langsamen“, sprich, wenn wenig Wasser verbraucht wird, gibt´s leider noch zwei. Diese Impulse werden dann addiert.
          Dein Vorschlag klingt toll. Leider scheitert es (auf Grund meines Einsteigerwissens) an der weiteren Umsetzung bzgl. Timer, Auslesen etc.. Googeln brachte heute leider keinen Erfolg.
          Bin für Vorschläge natürlich sehr dankbar!
          Den Keyboardinterrupt musste ich rausnehmen, weil der einen Fehler produziert…

          Hier noch mein stolzes Anfängerwerk:
          —————————————————————————–
          import RPi.GPIO as GPIO
          import time

          GPIO.setmode(GPIO.BOARD)
          GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)

          counter = 0

          while(1):
          GPIO.wait_for_edge(22, GPIO.FALLING)
          counter += 1
          print counter
          time.sleep(0.2)

          except:KeyboardInterrupt:

          GPIO.cleanup()

          GPIO.cleanup()

          ————————————————

          Viele Grüße!
          Benito

          1. Hallo Benito,

            das ist auch nicht ganz schön, da du da ein sleep im Programm hast. Sowas ist, wenn dein System auf externe Ereignisse reagieren soll, immer schlecht.
            Versuch das mal so wie in meinem Beispiel im Artikel gezeigt zu machen.
            Deine Problemstellung sieht quasi wie mein Beispiel aus, nur das du statt nem Taster einen Impulsgeber hast (der Taster ist im Prinzip auch ein Impulsgeber, nur halt manuell ;) ).
            Und du kannst natürlich den Parameter „debouncetime“ weglassen, da deine Impulse wahrscheinlich nicht prellen.

            Viel Erfolg und wenn Fragen aufkommen einfach eine Mail schreiben :)

            Gruß
            Daniel

  12. Hallo,
    sehr schöne Erklärung, um das mit den Interrupt. Hat auch so weit gut geklappt (hab ne Schaltung mit Pull-Down-Rs -> vier Schalter, die ich für unterschiedliches nutzen möchte). Bei den GPIOs 16, 20 und 21 klappt auch alles. Aber das gleiche Skript mit GPIO 3 macht Schwierigkeiten. Da gibt es ne Fehlermeldung. Woran kann das liegen?
    Gruß
    Oliver

        1. Hallo Oliver,

          sry für die späte Antwort…
          Ne den kannst du nicht umdefinieren. Der wird quasi vom OS auf für I²C deklariert und entsprechend geschaltet.

          Gruß
          Daniel

  13. Hallo Daniel,

    super blog, hat alles auf Anhieb funktioniert.
    Weiter oben hat jemand das Problem mit dem Lichtschalter geschildert. Sofort ausprobiert: Script ist sofort abgeraucht :-), scheint also über das Netzteil etwas herein zu kommen, das die interrupt-Routine nicht verträgt.
    Muss man später halt irgendwie entkoppeln, damit es stabil läuft.
    Im Endausbau wird ein Hitachi 2×16 Display angesteuert und zählt 5 verschiedene Taster (das wird ein rudimentärer Flipper-Nachbau:-))
    Allerbeste Grüße
    Michael

  14. Hallo alle zusammen,

    arbeite seit langem wie wild an einem DMX-Lichtpult mit Motorfadern. Ein RASPI steuert das gesamte Pult mit insgesamt 29 Motorfadern. Das Pult ist auf dem Desktop 1:1 abgebildet, kann mit der Maus einen Motorfader steuern. Umgekehrt kann ich einen Fader im Pult bedienen und auf dem Desktop wird entsprechender Fader geregelt. Die Abfrage der Fader im Lichtpult erfolgt durch einen ATMega328, programmiert in Assembler. Immer wenn ein Fader seinen Wert verändert, sendet er an einen GPIO ein Interruptsignal, das mit fallender Flanke eine Funktion auslöst.

    Wie in einigen Artikeln oben beschrieben, möchte ich den Interrupt im RASPI sperren, bis der RASPI seine Funktion abgearbeitet hat. Bei C und in Assembler gibt es entsprechende Befehle. Bei Python 2 finden ich keinen. Wo findet man den?
    Also: Wenn ein Interrupt über einen GPIO ausgelöst wurde, soll er gesperrt werden und erst dann wieder freigegeben werden, wenn die funktion abgarbeitet ist.
    Wer kann mir helfen?

    1. Hallo Theodor,

      du kannst den Interrupt innerhalb des Callbacks mittels GPIO.remove_event_detect() entfernen und dann ggf. wieder hinzufügen.

      Gruß
      Daniel

      1. Hallo Daniel,

        Danke ersteinmal. So schlau war ich auch, hatte diesen Befehl gefunden. Geht leider nicht. In dem Moment, wo dieser Befehl angefordert wird, stürzt das Programm ab, es endet einfach.
        Hast Du das auch schon probiert?
        Ich habe diesen Befehl zu Beginn in das Programm eingefügt, das vom Interrupt aufgerufen wird. Und dann endet das Programm einfach.

        Gruß,
        Theodor

        1. Hallo Theodor,

          ne getestet habe ich den Befehl nicht. Aber hast du ggf. das Problem, dass du den Handler entfernst und dann trotzdem noch die Funktion aufrufst?

          Gruß
          Daniel

  15. Hallo Daniel,

    ich habe einfach folgendes gemacht:
    1.) In meiner Hauptdatei habe ich folgenden Befehl eingefügt:
    GPIO.add_event_detect(gpio_1, GPIO.FALLING, callback=FaderFrag)
    Die aufzurufende Funktion ist
    2.) In dieser Funktion ist gleich zu Beginn der remove-Befehl eingefügt:
    *****************************************************************************“““
    def FaderFrag(z):
    GPIO.remove_event_detect(gpio_1)
    3.) Am Ende der Funktion entsprechend:
    GPIO.add_event_detect(gpio_1, GPIO.FALLING, callback=FaderFrag)
    „**************************************************************************“““

    Was kann falsch sein?

    Gruß,

    Theodor

      1. Hallo Daniel,

        jetzt startet das Programm eigenlich gar nicht. Nach dem Start spingt es wieder in die Python-Shell, wieso auch immer.

        Herzlichen Gruß,

        Theodor

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert