How to use interrupts with Python on the Raspberry Pi and RPi.GPIO

http://raspi.tv/2013/how-to-use-interrupts-with-python-on-the-raspberry-pi-and-rpi-gpio

Mar172013

 

Circuit for simple button press interrupt

The latest big news in the world of Raspberry Pi Python GPIO programming is that Ben Croston has released an update for RPi.GPIO. Why is that a big deal? Because this version has interrupts. “What’s an interrupt?” I hear you say. It’s a way of waiting for something to happen without checking constantly whether or not it’s happening.

Imagine that you’re waiting for a delivery – something you’re really excited about – like a Pi camera.You spend far too much time looking down the street in eager anticipation of the postman’s arrival. You can’t fully focus on what you’re supposed to be doing because you know it’s imminent. Every time you hear something in the street, you jump up and look out of the window. Woe betide any door-knocking salesman who calls when you’re expecting a delivery.

What I’ve just described in human terms is a bit like polling. Polling is continually checking for something. For example, if you want to make sure your program reacts as quickly as possible to a button press, you can check the button status about ten thousand times per second. This is great if you need a quick reaction, but it uses quite a bit of the computer’s processing power. In the same way that you can’t fully focus when you’re expecting a delivery, a large part of your CPU is used up with this polling.

There has to be a better way, right?

Yes. And there is. It’s interrupts. This is the first in a series of articles which aim to show you how to use this new interrupt facility in Python.

Interrupts are a much more efficient way of handling the “wait for something to happen and react immediately when it does” situation. They free up the resources you would have wasted on polling, so that you can use them for something else. Then, when the event happens, the rest of your program is “interrupted” and your chosen outcome occurs.

So, to carry on our human example…
An interrupt is like having an automatic postman detector that will tell you for sure when the postman arrives, so you can get on with something else. You now know you will not miss that knock on the door and end up with one of those “we tried to deliver your item but you were out and the collection office is closed for the next two days, so enjoy the wait” cards.

So interrupts are good, as you can set them up to wait for events to occur without wasting system resources.

So how do you code them?

I’m going to show a simple “wait for a button press” example in this blog article and follow up with other examples in subsequent articles. But before you try this, you will quite likely need to update your RPi.GPIO package. You can check what version of RPi.GPIO you have in the command line with…

sudo python
import RPi.GPIO as GPIO
GPIO.VERSION

This should show you what RPi.GPIO version you have. You need 0.5.1 or higher for this example.
You can exit the python environment with CTRL+Z

Install RPi.GPIO version 0.5.1 for simple interrupts

If you need to, you can install 0.5.1 or later with
sudo apt-get update
sudo apt-get dist-upgrade
 (This will update all your Raspbian packages and may take up to an hour)

or, from the command line prompt (this will only update RPi.GPIO)…
wget http://raspberry-gpio-python.googlecode.com/files/python-rpi.gpio_0.5.1a-1_armhf.deb
wget http://raspberry-gpio-python.googlecode.com/files/python3-rpi.gpio_0.5.1a-1_armhf.deb
sudo dpkg -i python-rpi.gpio_0.5.1a-1_armhf.deb
sudo dpkg -i python3-rpi.gpio_0.5.1a-1_armhf.deb

And now the circuit

Circuit for simple button press interrupt

It’s simply a question of rigging up a button connecting 23 to GND when pressed.

And now onto the code

I’ve put most of the explanation in the code, so that if you use it, you will still have it.

  1. #!/usr/bin/env python2.7
  2. # script by Alex Eames http://RasPi.tv/
  3. # http://raspi.tv/2013/how-to-use-interrupts-with-python-on-the-raspberry-pi-and-rpi-gpio
  4. import RPi.GPIO as GPIO
  5. GPIO.setmode(GPIO.BCM)
  6. # GPIO 23 set up as input. It is pulled up to stop false signals
  7. GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  8. print ”Make sure you have a button connected so that when pressed”
  9. print ”it will connect GPIO port 23 (pin 16) to GND (pin 6)\n”
  10. raw_input(“Press Enter when ready\n>”)
  11. print ”Waiting for falling edge on port 23″
  12. # now the program will do nothing until the signal on port 23
  13. # starts to fall towards zero. This is why we used the pullup
  14. # to keep the signal high and prevent a false interrupt
  15. print ”During this waiting time, your computer is not”
  16. print ”wasting resources by polling for a button press.\n”
  17. print ”Press your button when ready to initiate a falling edge interrupt.”
  18. try:
  19.     GPIO.wait_for_edge(23, GPIO.FALLING)
  20.     print ”\nFalling edge detected. Now your program can continue with”
  21.     print ”whatever was waiting for a button press.”
  22. except KeyboardInterrupt:
  23.     GPIO.cleanup()       # clean up GPIO on CTRL+C exit
  24. GPIO.cleanup()           # clean up GPIO on normal exit

Decoding the code

lines 4-5 import the RPi.GPIO module and set up the BCM port numbering scheme

line 8 sets GPIO 23 as an input with the pullup resistor set to UP.
This means that the signal will be HIGH all the time until the button is pressed connecting the port to GND, which makes it LOW. This avoids false event detection.

lines 10-11 print some instructions
line 12 waits for user to hit enter before starting. This gives an opportunity to check the wiring

lines 14-21 further instructions and documentation

line 22 try: & line 26 except KeyboardInterrupt:
This allows us to run the program and exit cleanly if someone presses CTRL-C to stop the program. If we didn’t do this, the ports would still be set when we forcefully exit the program.

line 23 sets up the “wait for the signal on port 23 to start falling towards 0″

lines 24-25 further on-screen instructions

line 27 cleans up the GPIO ports we’ve used during this program when CTRL-C is pressed
line 28 cleans up the GPIO ports we’ve used during this program when the program exits normally

Two ways to get the above code on your Pi

If you are in the command line on your Pi, type…
nano interrupt1.py
Then click “copy to clipboard” (above) and paste into the nano window.
CTRL+O
Enter
CTRL+X

Alternatively, you can download this directly to your Pi using…
wget http://raspi.tv/download/interrupt1.py.gz
gunzip interrupt1.py.gz

Then you can run it with…
sudo python interrupt1.py

That’s cool, what next?

So that was a simple “wait for a button press” interrupt. There’s a lot more you can do with them, as I will show you in the next article, which will cover “threaded callback”, which allows us to use the spare capacity we’ve freed up by not polling continually.

Click here for the next article (part 2)
Or check out the official documentation here and press on by yourself.

Share this:

 39 Responses to “How to use interrupts with Python on the Raspberry Pi and RPi.GPIO”

  1. Interesting… Is there any way to set it up so that you’re waiting to see if either button A _or_ button B was pressed?

  2. Great to see the latest version of RPi.GPIO now handles interrupts better. I had tried to use interrupts with RPi.GPIO on a project a few months ago but ended up using Python’s epoll API, instead.

    I do have a question. I notice the sleep function can’t be interrupted until it times out. Then the interrupt is serviced. Do you know if this is a condition of Python, Raspian or RPi.GPIO? I was thinking sleep could be interrupted running Python under Windows. Not sure now.

    Looking forward to the next article on threads.

    • I will check if sleep can be interrupted with a threaded callback. I rather think it can be, but give me 10 minutes to check it out…

      Part 2 will be out on Wednesday. :)

      • Hee hee. It only took 4 minutes and I was right. It works. You can interrupt time.sleep() using threaded callback. :-D

        • Excellent! :yes: Probably seemed like a strange question, but sometimes sleep might be part of another completely separate function that is running until interrupted.

          • Well, you can run another function during the sleep (the callback function runs in a second thread), but I’m not sure if you can actually stop the sleep in the main thread. That will require more than 10 minutes because I’m off to bed. :-)

            Depends what you want to achieve. But you could use a wait_for_edge() instead of a sleep perhaps?

  3. Hi Alex
    A huge thank you for this article and your work in general..

    My old brain is slow to understand all this new stuff but your style of
    explaining things suits me fine..

    Your efforts are very much appreciated :-) )

    Philip

    • You’re welcome Philip. It’s knowing people find it useful that makes it all worthwhile, so thank you for your comment. :)
      Part 2 will be out tomorrow and Part 3 on Friday.

  4. [...] Raspberry Pi and RPi.GPIO » RasPi.TV March 19, 2013 By Jack Zimmermann · Leave a CommentHow to use interrupts with Python on the Raspberry Pi and RPi.GPIO » RasPi.TV:We’ve finally got interrupt capability on the Raspberry Pi for GPIO programming in Python [...]

  5. [...] How to use interrupts with Python on the Raspberry Pi and RPi.GPIO – part 2  Input and Output, interfacing, python programming, raspberry pi  Add comments Mar 202013   Interrupts are an efficient way for a program to be able to respond immediately to a specific event. In the previous article I explained the basics of using interrupts in RPi.GPIO and gave an example of a simple “wait for an event” interrupt program. [...]

  6. [...] How to use interrupts with Python on the Raspberry Pi and RPi.GPIO – part 3  Input and Output, interfacing, python programming, raspberry pi  Add comments Mar 222013   Multiple threaded callback interrupts in Python [...]

  7. [...] How to use interrupts with Python on the Raspberry Pi and RPi.GPIO This is the first in a series of articles which aim to show you how to use this new interrupt facility in Python. [...]

  8. [...] RPi.GPIO, now at version 0.5.2a, has interrupts and threaded callback capability. You will probably have seen my three recent posts showing how to use those. [...]

  9. [...] It struck me the other day that I’ve published some fairly advanced RPi.GPIO tutorials, (e.g. interrupts and PWM) but not done anything more basic, apart from the Gertboard examples. So here’s an [...]

  10. Steve Whitlow says:

    I’m getting the terminology mixed up here and it’s hurting my ability to take this basic tutorial and work it for my project. I have a simple switch that plays an mp3 when I press it. Yeah, works great. Now I’ve replaced the switch with a photo interrupt switch (http://www.seeedstudio.com/depot/photo-interrupter-os25b10-p-541.html) and want the mp3 to play when something passes thru it.

    So for this tutorial, the basic switch is open until the user closes it, therefore the voltage to pin 23 is at zero until the user presses and it rises to 3.3+/-, correct?

    If I use my photo interrupt, it’s the opposite, light passes thru the sensor so 3.25V is delivered to pin 23 continuously UNTIL an object passes thru the sensor and it drops down to 0.2V. I’ve tried reversing the logic of this tutorial but I’m missing something basic. How would I change this tutorial to detect the voltage dropping when an object blocks the sensor? Thanks! This tutorial has gotten me further along that anything else I’ve found!

    • No – exactly the wrong way round. The input port is pulled high (3.3V) with the internal pullup, causing 3v3 as the default setting. (That’s what the pullupdown bit in line 8 does.)

      When the button is pressed, it connects to ground which makes it go LOW (0V) which is what the interrupt detects. It detects falling voltage.

      As it’s written it should work with no changes. But it will only detect one event, unless you put line 23 in a loop (which would probably work quite well in your application).

      Hope this helps.

      • Thanks for the reply! Ok, I understand the tutorial and it makes sense, but I’m confused with the photo interrupt switch, its closed by default, ie 3.3v is being applied to the pin. When the object passes thru the sensor it opens the switch and the voltage drops to about 0.2 (it’s a light sensor so some light is making its way thru). So my switch is opposite of the type of switch you used in the tutorial, and that leads to my question, how to turn the logic in the script around. Would I want to make a pulldown and then watch it fall? My problem is this is my first project like this and I’m probably using the terminology incorrectly! I’m googling like a madman trying to find lots of samples. Thanks!

        • If you’re waiting for the value to drop from 3.3V to 0.2V, then you’re still waiting for a FALLING edge, and so Alex’s script given on the page above should work fine and not need modifying at all? Which I think is also what Alex was saying?

          If we’re still talking at cross-purposes, then maybe you could draw a circuit diagram of exactly what you’ve connected to where?

          • Steve Whitlow says:

            Here is a picture of the breadboard.

            http://i.imgur.com/I1pDkPv.jpg

            I’ve got the anode connected to the 5V rail and the Cathode thru a 220 Ohm resistor to ground. On the other side of the sensor I’ve got the Emitter going thru a resistor divider, here a 33K Ohm resistor and then to ground. On the Collector side it’s powered from the 5V rail thru a 22k Ohm resistor and the signal off to pin 23.

            When I test this with a voltmeter, I’m showing 3.25V to pin 23 when the sensor is not blocked (sensor’s default state) and about 0.2V when I pass an object between the sensor posts.

            Here’s a circuit diagram…hope I got it drawn right! Thanks for responses so far!

          • Steve Whitlow says:

            Woops, forgot the circuit diagram…

            http://i.imgur.com/YzckSYE.jpg

          • Steve Whitlow says:

            In the picture it looks like the GND pin (#6) is connected to Cathode but the red wire from the 6 pin goes to the ground rail on the left side of the breadboard.

          • Using red for GND just to confuse us? ;)

            Are all your GNDs connected? If your circuit works as you say, the program above should work “as is”, but only once. Can you measure the voltage at GPIO 23 while it’s connected? Maybe we’ve got an issue there?

          • Steve Whitlow says:

            Here’s a movie. I have one end on the 5v rail going into the colector and the signal going to the pin.

            http://www.youtube.com/watch?v=QV-Rd6FW0NI

            I’m also a little confused about the difference between BCM and BOARD but since the tutorial here used BCM and showed the switch being hooked up to the same pin (23) I was using, I figured that part has to be right.

          • The first thing I noticed was that you are measuring between 5V and the GPIO23 pin for your readings.
            Shouldn’t you be measuring between GPIO 23 and GND to get a Voltage reading at the pin?

            I’m sorry. I don’t really understand your circuit.

          • Steve Whitlow says:

            Alex, that’s awesome, I don’t even know what I’m measuring! This is my first project like this. Obviously I’ve got something backwards. Measuring it as you mentioned shows 1.6V at pin 23 and 4.0V when I block the sensor. I’m guessing my resistor divider is doing a little too much dividing!

          • You don’t really want to be sending 4.0V down a pin designed for 3.3V I’m not sure what the tolerances are, but that’s either getting a bit close to damaging the port, or it might already be there.

            Voltages are always measured against GND, generally wiring is done so that positive is red and GND is black.

          • Steve Whitlow says:

            I knew not to send more than 3.3, the way I was reading it it looked fine but obviously that is not the case. Any suggestions on how to get 5V down to 3.3 in the diagram I made? I was using the 22K and 33K resistors, I’ll try switching them around to see what happens. I got that from another thread on how to step 5 down to 3.3. Thanks for the help so far.

        • This reply may be a bit late but I’ve only just discovered this website. If you still need a solution then first you need to rewire your opto sensor.

          Having looked at your circuit diagram, what you need to do is connect the emitter directly to ground, not through R3. Then remove R2 (so neither resistor is needed, R3 needs to be a link). Leave the collector connected to the Pi input. That input already has an internal pull up resistor and the opto transistor will pull it down when the slot is clear.

          With no object in the opto sensor slot the input to pin 23 will be low. When an object is in the slot the input to pin 23 will be high, so you are right in thinking than the logic is reversed and the code will need changing to reflect that. I am new to Pi so can’t help with the code but it must be quit simple to some people on here.

          Hope this helps!

  11. Great articles … but I’m having difficulty with the GPIO module.

    It imports OK , but GPIO.VERSION throws an error

    Here’s a result of using the find command

    sudo find / -name ‘*RPi*’ -print
    /usr/lib/pyshared/python2.6/RPi
    /usr/lib/pyshared/python2.7/RPi
    /usr/lib/python2.6/dist-packages/RPi
    /usr/lib/python2.6/dist-packages/RPi.GPIO-0.5.3a.egg-info
    /usr/lib/python2.7/dist-packages/RPi
    /usr/lib/python2.7/dist-packages/RPi.GPIO-0.5.3a.egg-info
    /usr/local/lib/python2.7/dist-packages/RPi.GPIO-0.3.1a-py2.7-linux-armv6l.egg
    /usr/local/lib/python2.7/dist-packages/RPi.GPIO-0.3.1a-py2.7-linux-armv6l.egg/RPi

    It looks like /usr/local might have version 0.3.1 ?

    Any suggestions as to how to proceed?

    jim

  12. Borreltje says:

    Alex, great write-up, helped me out some.
    just a question, what if I want the button to do more than just print something on-screen?
    I tried this code:

    def my_callback(channel):
    device.emit(uinput.KEY_N, 1) # PRESS N-KEY
    print “falling edge detected on 17″

    def my_callback2(channel):
    device.emit(uinput.KEY_P, 1) # PRESS P-KEY
    print “falling edge detected on 23″

    and it kinda works, once. If I press the button it will do the ‘n’ on screen once, every other time I press the button it only prints out the text in terminal but not the ‘n’.

  13. Borreltje says:

    Sorry, put it in the wrong place, should be in part 2 of the series, still the question remains open, I cannot get it to work.
    everytime I press the button it prints “falling edge detected on 17″ in the terminal and the first time it also prints “n” on the screen, but
    that is only the first time, the second time no more “n” on screen.

    • Well if the text is being printed multiple times then “device.emit(uinput.KEY_N, 1)” is definitely being called multiple times, so the problem must be with with your “device.emit” function (whatever that might be) being called multiple times, rather than with the GPIO interrupt handling.

      If you could post the full source code (rather than just a snippet) that might give us a better idea of what is going on :)

      • Borreltje says:

        Here is the full code:

        #!/usr/bin/env python2.7
        import RPi.GPIO as GPIO
        import uinput
        import pygame, sys, os, time
        from time import sleep

        GPIO.setmode(GPIO.BCM)

        device = uinput.Device([uinput.KEY_N])

        GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)

        def my_callback1(channel):
        device.emit(uinput.KEY_N, 1) # PRESS N-KEY
        print “falling edge detected on 17″

        GPIO.add_event_detect(17, GPIO.FALLING, callback=my_callback1, bouncetime=300)

        while True:
        print ‘Waiting for input.’
        sleep(60);

        Nothing fancy, just waiting for a button to be pushed, and it should act as if the letter ‘N’ was pushed on a keyboard.
        prints ‘n’ once on the screen, and then just “falling edge detected on 17″ in terminal on every other push of the button.