Mar 222013
 
Circuit for 2 threaded callbacks and one wait interrupt

Multiple threaded callback interrupts in Python

We’ve been learning about interrupts this week because of the brand new interrupt capabilities of RPi.GPIO. We covered a simple “wait for” interrupt in part 1, threaded callback interrupt and button debouncing in part 2 and today we’re getting sophisticated with multiple threaded callbacks. “WoooooooooOOOOOOOOOOOOOOOooooooooooo”, I hear you say. ;)

Well actually, we’re not doing much that’s very different from last time, except, now there’s more of it. We’ll add another button and another threaded callback function the same as the first one (but on a different GPIO port). This is just to show that you can do multiple threaded callbacks in one program. After that, your imagination is the limit. (Well actually the number of GPIO ports is probably the limit.)

We’re just building on what we did before and this is exactly how programs are made. You do a bit at a time, test it, fix it, make sure it does what it ought to do, then go on to the next bit.

Here’s the Circuit

This circuit is a bit different from the previous one. The top two buttons connect port 17 and port 23 to GND when pressed. These are the two which trigger callbacks. The bottom button, connecting port 24 to 3V3 on button press is the “wait for” interrupt this time. So when you press button 3 it’s “game over”, but buttons 1 and 2 just report that they’ve been pressed until button 3 is eventually pressed.

Circuit for 2 threaded callbacks and one wait interrupt

We’ve used all the same building blocks we developed in parts 1 and 2, including button debouncing.

Do you need to update RPi.GPIO?

If you didn’t do it for the first or second examples, 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.1a or higher for this example.
You can exit the python environment with CTRL+D

Install RPi.GPIO version 0.5.2a for multiple threaded callback interrupts

If you need to, you can install 0.5.2a 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)

Update July 2014
The best way to get the latest RPi.GPIO (currently 0.5.5) is to flash a new SD card with the latest NOOBS or Raspbian. This will give you a clean start with the latest version of RPi.GPIO.

And now onto the code

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

#!/usr/bin/env python2.7
# script by Alex Eames http://RasPi.tv
# http://RasPi.tv/how-to-use-interrupts-with-python-on-the-raspberry-pi-and-rpi-gpio-part-3
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)

# GPIO 23 & 17 set up as inputs, pulled up to avoid false detection.
# Both ports are wired to connect to GND on button press.
# So we'll be setting up falling edge detection for both
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# GPIO 24 set up as an input, pulled down, connected to 3V3 on button press
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

# now we'll define two threaded callback functions
# these will run in another thread when our events are detected
def my_callback(channel):
    print "falling edge detected on 17"

def my_callback2(channel):
    print "falling edge detected on 23"

print "Make sure you have a button connected so that when pressed"
print "it will connect GPIO port 23 (pin 16) to GND (pin 6)\n"
print "You will also need a second button connected so that when pressed"
print "it will connect GPIO port 24 (pin 18) to 3V3 (pin 1)\n"
print "You will also need a third button connected so that when pressed"
print "it will connect GPIO port 17 (pin 11) to GND (pin 14)"
raw_input("Press Enter when ready\n>")

# when a falling edge is detected on port 17, regardless of whatever 
# else is happening in the program, the function my_callback will be run
GPIO.add_event_detect(17, GPIO.FALLING, callback=my_callback, bouncetime=300)

# when a falling edge is detected on port 23, regardless of whatever 
# else is happening in the program, the function my_callback2 will be run
# 'bouncetime=300' includes the bounce control written into interrupts2a.py
GPIO.add_event_detect(23, GPIO.FALLING, callback=my_callback2, bouncetime=300)

try:
    print "Waiting for rising edge on port 24"
    GPIO.wait_for_edge(24, GPIO.RISING)
    print "Rising edge detected on port 24. Here endeth the third lesson."

except KeyboardInterrupt:
    GPIO.cleanup()       # clean up GPIO on CTRL+C exit
GPIO.cleanup()           # clean up GPIO on normal exit

Bounce

bouncetime=300 in lines 34 & 39 sets a time of 300 milliseconds during which time a second button press will be ignored. The code from example 2a has been incorporated into RPi.GPIO.

You can download this directly to your Pi using…
wget http://raspi.tv/download/interrupt3.py.gz
gunzip interrupt3.py.gz

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

Can I switch off an event detection?

There’s just one more thing. If you want to stop an event detection on a particular port, you can use the following command…
GPIO.remove_event_detect(port_number)

You now know all you need to know about how to create and manage interrupts in RPi.GPIO in Python on the Raspberry Pi. The official documentation is here if you want to check it out.

Have fun with interrupts. I hope this mini-series has been useful. Let us know, in the comments below, what you’re planning to use it for and then come back and tell us how it went. :)

For extra credit, can you tell me why I chose those specific GPIO ports for these experiments?

  87 Responses to “How to use interrupts with Python on the Raspberry Pi and RPi.GPIO – part 3”

  1. [...] hope you are enjoying this series. Click here for Part 3. Share this:  Posted by alex at 8:00 am  Tagged with: interrupts in python with [...]

  2. Thank you so much!

    I started fooling arround with the Raspberry Pi not long ago and that made me start learning Python. Your articles about interrupts were essencial.

    I made a little Python script that lights up a Green LED when I get new mail and a RED one when the temperature forecast to my city gets lower than 5ºC. But the forecast site free API only gives a limited number of updates a day. Because of that the temperature as well as the email are updated every 3 minutes.
    However, after reading this, I setted up a button that refreshes the e-mail status by pressing it using the callbacks.

    The next step will be making this information avaliable in a 16×2 LCD. (following one more of your posts :-D )

  3. This is a great tutorial; thanks for the example code as well. I am guessing that you have chosen the pins that you have due to them not having any special function, eg: UART, SPI etc.

    • Thanks John.

      Ref the port choices, that’s part of it, but not the whole story. :)

      • Mind sharing the details regarding your pin choices then, or should I stay tuned for a future tutorial :)

        As well, do we need to manually clear these interrupts under any circumstances, or is there a method to clear them that you are aware of? I ask because I am not sure if that may be my problem. I have written the attached code to allow an rotary incremental encoder (outputs 2 bit grey code changing with a change in angle) to control which stream in my mpd playlist is active, and the call backs are only triggered up until the if statement in the body of the while loop at the bottom is processed for the first time. If I cut the 2 If statements at the bottom and paste them into the call backs it works well. Any help would be greatly appreciated, I am still a bit green with Python and programming in general:

        import RPi.GPIO as GPIO
        import time
        import os

        GPIO.setmode(GPIO.BOARD)

        GPIO.setwarnings(False)

        GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

        counter = 5
        lastcounter = 5
        timestamp16 = time.time()
        timestamp18 = time.time()

        def callback16():
        global timestamp16
        global counter
        global lastcounter

        time_now = time.time()

        if (time_now – timestamp16) >= 0.1:
        current16 = GPIO.input(16)
        current18 = GPIO.input(18)

        if current16 == 0:
        if current18 == 0:
        counter = counter – 1
        else:
        counter = counter + 1
        else:
        if current18 == 0:
        counter = counter + 1
        else:
        counter = counter – 1

        print ‘callback 16′
        timestamp16 = time_now
        return

        def callback18():
        global timestamp18
        global counter
        global lastcounter

        time_now = time.time()

        if (time_now – timestamp18) >= 0.1:

        current16 = GPIO.input(16)
        current18 = GPIO.input(18)

        if current18 == 0:
        if current16 == 0:
        counter = counter + 1
        else:
        counter = counter – 1
        else:
        if current16 == 0:
        counter = counter – 1
        else:
        counter = counter + 1

        print ‘callback 18′
        timestamp18 = time_now
        return

        os.system(‘clear’)
        os.system(‘mpc’)

        GPIO.add_event_detect(16, GPIO.BOTH, callback=callback16)
        GPIO.add_event_detect(18, GPIO.BOTH, callback=callback18)

        while GPIO.input(7) == 0:

        time.sleep(.1)

        if counter > lastcounter + 4:
        os.system(‘mpc next’)
        os.system(‘clear’)
        os.system(‘mpc’)
        lastcounter = counter

        if counter < lastcounter – 4:
        os.system('mpc prev')
        os.system('clear')
        os.system('mpc')
        lastcounter = counter

        print str(GPIO.input(16)) + " " + str(GPIO.input(18))

        GPIO.cleanup()

        • Pin choices answer can be found in the comments for part 2 of this series. I’ll leave it there so as not to act as a spoiler in case anyone wants to have a guess. :)

          I’ll look at your code a little later as I have a headache right now. WordPress seems to have mangled it as well :(

          • Thanks for the offer to assist. I can upload the code somewhere if it’ll help you not have to rebuild the indentation.

          • You can zip it up and email it to me if you like. my_first_name @ mydomain ;) (a lot of email providers won’t pass .py files)

            I probably won’t be able to look until Tue/Wed though as I’m travelling tomorrow. I just might have a few minutes tomorrow am if things go well, or I feel like it on the plane :)

  4. Hi Alex, your description on how to use interrupts is very easy to understand. However I upgraded my gpio today to version 0.5.2a before I tried your lessons. Now lesson 2 and 3 have errors. When I change the sd card to one with version 0.5.1a it all works fine. My python skills are not that great yet, bit i’m learning. I had great fun getting a wii_mote controlling the robot used in the skutter project in the magpi.

    Andrew

    • I’m afraid it’s a known bug. Ben wrote to me about this at the weekend. 0.5.2a (a stands for alpha) has changed such that the callback functions will require channel number. Just add the channel number in your callback function definition and all should be good. I am going to fix the example code as soon as 0.5.2a goes into the Raspbian repo. So if you define a function for channel 25 it should look like this…
      def callback_25(25):

      I haven’t changed it yet because it will break the example for the previous versions.

      • Thanks Alex, I tried it but I keep getting an invalid syntax error on the (25): . I also tried the bouncetime function but can’t get past def callback_17(17):

        Andrew

        • I haven’t even had a chance to play with this version yet, so you’re ahead of me ;)

          In other news wiringpi2 for python just failed to install as well. I think I’m going to do something that actually works now. :-D

          • I’ve gone back to 0.5.1a now and wait until it’s in the repo.

          • It’s an awkward one because I will need to change the code, but don’t want to do it until the new version is the default (in the repo).

        • Try putting “self” instead of the channel number such as:

          def my_callback(self):

          This worked for me.

          • Yes using self instead of the channel number works for me too.

            I wonder if this is going to change in the near future? ?:-)

            I also notice that version 0.5.2a is now in the Raspbian repo. :yes:

          • Updated both this page and the previous examples and downloads to reflect the changes in RPi.GPIO from version 0.5.2a

            Basically you HAVE to include (channel) in your callback function definitions. Not channel number, but the variable called channel

            I didn’t want to do it until 0.5.2a was in the repo as it breaks the examples for previous version 0.5.1a.

            If you try to run the code from the old example (or the one in part 2) using 0.5.2, you will get this error when your callback function is called…

            TypeError: my_callback() takes no arguments (1 given)

          • Thanks Alex, I’m pleased to note that your new code is almost the same as what I did. I say almost as I have found that almost anything can be inserted between the () so long as it starts with a letter. You could use (raspberry) or even (raspi_tv) if you want and the two callbacks can even have different variables between the ().

          • That’s interesting, so any variable starting with a letter will do. :)

            Ben flagged up the changes to me about 10 days ago, but it seemed sensible to hold off updating until 0.5.2a was the “official” version. Didn’t want to get too many people stuck between versions, and didn’t want to have several different versions of these pages.

          • Alex I understand your dilemma, but this must be a bug and I’m sure that Ben will change it in the next release. Then you’ll have to change it again :silly:

          • We’ll see. Hopefully the code won’t be broken again. :-)

  5. [...] is the third part of a series sharing about “interrupts” on the Raspberry Pi, from RasPi.TV: We’ve been learning about interrupts this week because of the brand new interrupt [...]

  6. You chose GPIO18, GPIO23 and GPIO24 because of internal pull-up resistors. Am I right?

    • Good guess, but it’s not the whole story. :) It’s more to do with what’s next to each of those pins :)

      • They have interrupt vectors assigned at hardware level? I dare assume that not all pins have this capability.

        • They might do, but I wouldn’t have an idea. It’s simpler than that. ;)

          • Because those pins are not used by any of peripherals like UART, I2C, ADC? Or simply they are closer to the breadboard? :D

          • All good reasons, but not THE one. Big clue now. Look at the pins adjacent to the chosen ones and see if they have any useful properties. ;)

          • They have ground pins beside them, and therefore, if are configured to have internal pull-up resistors they can be connected to switches wired to simple 2 pin female connectors. ie: a switch (or button) between pin 9 and 11, one between 14 and 16, as well as 18 and 20.

          • There are those mysterious DNC pins (Do Not Connect). There must be some reason not to bother with them. May be some future functionality? Or may be my pinout chart is out of date already?

          • Two grounds and a 3V3 – just what we needed for this. The rest of it is as John says above. :) I didn’t use a breadboard for this, I used button switches connected to 2-way female connectors. Unfortunately the Fritzing Pi model won’t allow connections to the DNCs.

  7. Has anyone done any speed testing on this? I’ve been trying to work out whether I could turn a RPi into an ECU for my rather old car (bonkers I know). The stumbling block has been how to measure engine revs reasonably accurately.
    Getting one pulse per rev is pretty easy. Trigger an interrupt each time and log the timestamp. Subtract last timestamp from this timestamp and we have the time taken for 1 revolution but at 5000 RPM the pulses are arriving at 12 millisecond intervals.

    Am I being completely stupid or could Python on the Pi be fast enough?

    Thoughts?

    • It might be fast enough, but I doubt if Python would give you accurate enough timings for such a critical app. I think you’d be better off with a real-time microprocessor like ATMega or something else like it.

  8. Great tutorial! I wasn’t particularly fond of polling, so this was a handy find.

    I notice that you can call one function for both rising and falling edges using GPIO.BOTH, and you can call more than function using GPIO.add_event_callback, but can you call one function for rising and a different function for falling?

    When I try to do this I get an exception:
    RPi.GPIO.AddEventException: Edge detection already enabled for this GPIO channel.

    Is there a reason why this isn’t allowed?

    • You can do it on different channels. Surely there’s a workaround though? Do ‘both’ and the test for high or low in your callback function to determine what happens?

      Failing that, Ben’s the man to answer.

      • I had the same problem. I wanted to use this with two door sensors to detect if a chicken coop door is fully closed, fully open, or somewhere in between. For each sensor, I’d be able to tell if the door was arriving at that location or departing that location by the rising or falling edge.

        I tried your workaround… trigger the function on GPIO.BOTH and then do an if/else test in the callback function to see if the door was at that location (if it was, it had just arrived, if it was not, it had just left).

        Works great. Thanks for the tip! And thanks for the tutorials… I would not have gotten this working otherwise.

  9. Thank you for your great tutorial. it helped me alot for my little project.

  10. While the comments correctly indicate that port 24 is connected to 3v3, the diagram seems to indicate a connection to 5V. I don’t want anyone to smoke their RPi by mistake.

    • It doesn’t, but I can see how you could think that it did if you were using a small screen or something. I’ve found out how to “bend” wires in Fritzing now, so will try to avoid passing directly over ports in future.

  11. My RPi.GPIO package was not up to date so I followed your upgrade steps but every time I output my version it’s the old one. All updates run like they should without any errors. And in the logs it even shows the upgraded version.


    Preparing to replace python-rpi.gpio 0.5.2a-1 (using python-rpi.gpio_0.5.2a-1_mhf.deb) ...
    Unpacking replacement python-rpi.gpio ...
    Setting up python-rpi.gpio (0.5.2a-1) ...


    >>> import RPi.GPIO as GPIO
    >>> GPIO.VERSION
    '0.4.1a'

    Any idea what I’m missing or doing wrong?

    • It’s possible that if you’ve installed previous version of RPi.GPIO manually in the past, you might need to uninstall those before the newer version will “take”. Ben knows more about how to do this and is usually pretty quick to suggest how to test/do this in the Python section of the Pi forums.

  12. In your example about how to check the RPi.GPIO version, you show exiting with a ^Z.

    That puts the python task into the backround, without stopping it.

    root@mypi3:~/bin# python
    Python 2.7.3 (default, Jan 13 2013, 11:20:46)
    [GCC 4.6.3] on linux2
    Type “help”, “copyright”, “credits” or “license” for more information.
    >>> import RPi.GPIO as GPIO
    >>> GPIO.VERSION
    ’0.5.3a’
    >>> (typed a ^Z)
    [1]+ Stopped python

    Checking the backround jobs:

    root@mypi3:~/bin# bg
    [1]+ python &

    Bringing python back to the foreground:

    root@mypi3:~/bin# fg 1
    python
    >>>

    Really exit and stop python

    >>> exit()

    This will avoid having extra python jobs running in the backround.

    • Actually someone already mentioned this a while back (may have been on another page) and said that CTRL-D is better (EOF).

      Thanks for bringing it up. I’ll change it.

  13. Hi,
    thank you for your great tutorial. it helped me a lot.
    I am trying to create a project for a museum: pi to stream video to a screen.
    We must select a movie from a console with 8 pushbuttons.

    The console is connected to the GPIO port 4 and 18 for testing, but my script does not quite work.
    os is “RASPBIAN”, so the versions are:
    python: 2.7.3
    RPi.GPIO: 0.5.2a

    the script:
    ————————————————————————————-
    #!/usr/bin/env python2.7

    import RPi.GPIO as GPIO
    import os
    from time import sleep

    def ports_def():

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(4, GPIO.IN) # button 1
    GPIO.setup(18, GPIO.IN) # button 5
    GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP) # button 1
    GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP) # button 5
    GPIO.add_event_detect(4, GPIO.FALLING, callback=my_callback1, bouncetime=300)
    GPIO.add_event_detect(18, GPIO.FALLING, callback=my_callback2, bouncetime=300)

    def my_callback1(channel):
    os.system(“pkill omxplayer”)
    os.system(‘omxplayer ../videos/50001_court.mp4′)

    def my_callback2(channel):
    os.system(“pkill omxplayer”)
    os.system(‘omxplayer ../videos/coin_court.mp4′)

    def balise1():
    while 1:

    print “la forge”

    sleep(0.02);

    ports_def()

    balise1()

    GPIO.cleanup()

    ————————————————————————————–
    the problem is the following:
    video process can sometimes exist in duplicate:

    root 1890 1 0 05:51 tty1 00:00:00 /bin/login —
    pi 1975 1890 0 05:53 tty1 00:00:01 -bash
    root 1989 1975 0 05:53 tty1 00:00:00 sudo ./test.py
    root 1990 1989 94 05:53 tty1 00:03:34 python2.7 ./test.py
    root 2141 1 15 05:57 tty1 00:00:00 /usr/bin/omxplayer.bin ../videos/coin_court.mp4
    root 2142 1990 0 05:57 tty1 00:00:00 sh -c omxplayer ../videos/coin_court.mp4
    root 2143 2142 0 05:57 tty1 00:00:00 /bin/bash /usr/bin/omxplayer ../videos/coin_court.mp4
    root 2144 2143 15 05:57 tty1 00:00:00 /usr/bin/omxplayer.bin ../videos/coin_court.mp4
    pi 2160 2003 0 05:57 tty2 00:00:00 grep –color=auto tty1

    and after a while it can happen that you press a button does not detect…

    Thanks for your help.

    • Maybe a brief sleep() between killing omxplayer and starting the next instance may help things?
      Have you tried updating to the lastest RPi.GPIO ? (simply “sudo apt-get update && sudo apt-get upgrade”)
      Have you tried experimenting with different bouncetimes?
      Putting in some debug prints (i.e. each time you enter one of the callback functions) might be useful ;)
      Presumably you’ve got the buttons wired to ground?

      • Thank you for your reply.
        The buttons are wired to ground.
        I’ll test your ideas and give you the results.

        This other script works very well, but does not allow interruptions:

        ———————————————————————————-
        #!/usr/bin/env python2.7

        import RPi.GPIO as GPIO
        import os
        from time import sleep

        # Ce programme est la propriete de l association La Forge

        def ports_def():

        GPIO.setmode(GPIO.BCM)
        GPIO.setup(4, GPIO.IN) # button 1
        GPIO.setup(18, GPIO.IN) # button 5

        def balise1():

        os.system(‘setterm -blank force’)

        while True:

        if ( GPIO.input(4) == False ):

        os.system(‘omxplayer ../videos/50001_court.mp4′)

        if ( GPIO.input(18) == False ):

        os.system(‘omxplayer ../videos/coin_court.mp4′)

        sleep(0.4);

        ports_def()

        balise1()

        GPIO.cleanup()
        ———————————————————————————————

        :-)

        • It’s almost certainly a debounce issue then (or a bug if you have an earlier RPi.GPIO). Trying Andrew’s suggestions is the best way forward :)

      • I made several tests:
        I inserted a brief sleep() between killing omxplayer and starting the next instance, it was a little better, processes were not always two copies.
        I experienced different bouncetimes, without success.
        I would like to update the unit RPi.GPIO as you both recommend. The problem is that the Raspberry PI I use is the Model A, without ethernet jack for access to the Internet. So i can’t type “sudo apt-get update…”

  14. thank you for your help, I’ll try as soon as possible. Cordially. CM

  15. I’ve connected the INTA pin of a MCP23017 to the GPIO 23 via a 3.33V voltage divider to be able to use the interrupts generated by the MCP23017. However, the software debounce parameter doesn’t seem to work for me. When i include bouncetime=300 in the GPIO.add_event_detect the callback fires just once and then never again. any ideas why? Also, is there a workaround? Right now I do a sleep(0.3) in the callback, but I’d really like to go without any sleeps in my code…

    thanks,
    Tom

  16. hello,
    I could load the package on a linux ubuntu pc and tranfer it to the sd card PI, the version is: RPi.GPIO > v 0.5.3a.
    The installation “dpkg -i package-name” was easy on PI.

    After many tests, no changes to the 0.5.2a version of … there are the same problems.

    I saw that there was a dedicated package in real time: http://pythonhosted.org/RPIO/index.html
    i’ll try and inform you of the results.

  17. Here is the latest version of the program based on RPIO. After much testing, rather it seems to work. 8 GPIO inputs are each connected to a push button. At rest, the polarity is +3.3 v PI through resistance 10Kohms. When pushing the pin is grounded.

    #!/usr/bin/env python2.7

    # Association la forge Saint Martin La Plaine
    # station video basee sur PI Raspberry type A
    # Choix video sur pupitre equipe de 8 boutons poussoir

    import RPIO
    import os
    from time import sleep

    def ports_def():

    RPIO.cleanup()
    sleep(0.1);
    RPIO.setmode(RPIO.BCM)
    RPIO.setup(4, RPIO.IN)
    RPIO.setup(17, RPIO.IN)
    RPIO.setup(7, RPIO.IN)
    RPIO.setup(22, RPIO.IN)
    RPIO.setup(18, RPIO.IN)
    RPIO.setup(23, RPIO.IN)
    RPIO.setup(24, RPIO.IN)
    RPIO.setup(25, RPIO.IN)
    sleep(0.1);
    RPIO.add_interrupt_callback(4, my_callback1, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(17, my_callback2, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(7, my_callback3, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(22, my_callback4, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(18, my_callback5, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(23, my_callback6, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(24, my_callback7, edge=’falling’, debounce_timeout_ms=800)
    RPIO.add_interrupt_callback(25, my_callback8, edge=’falling’, debounce_timeout_ms=800)
    sleep(0.1);
    RPIO.wait_for_interrupts(threaded=True)

    def my_callback1(gpio_id, val):
    # fonction appel film1 bouton 1
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film1.mp4′)

    def my_callback2(gpio_id, val):
    # fonction appel film2 bouton 2
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film2.mp4′)

    def my_callback3(gpio_id, val):
    # fonction appel film3
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film3.mp4′)

    def my_callback4(gpio_id, val):
    # fonction appel film4
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film4.mp4′)

    def my_callback5(gpio_id, val):
    # fonction appel film5
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film5.mp4′)

    def my_callback6(gpio_id, val):
    # fonction appel film6
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film6.mp4′)

    def my_callback7(gpio_id, val):
    # fonction appel film7
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film7.mp4′)

    def my_callback8(gpio_id, val):
    # fonction appel film8
    ports_def()
    sleep(0.1);
    os.system(“pkill omxplayer”)
    sleep(0.1);
    os.system(‘omxplayer ../videos/film8.mp4′)

    def main():

    while 1:

    # print “la forge”
    sleep(3);
    processname =’omxplayer’
    tmp = os.popen(“ps -Af”).read()
    proccount = tmp.count(processname)

    if proccount == 0:
    os.system(‘omxplayer ../videos/boucle.mp4′)
    sleep(0.2);
    ports_def()
    sleep(0.1);

    ports_def()
    sleep(0.1);

    main()

    RPIO.cleanup()

    Thanks to Alex and AndrewS…

  18. thank you for the example

    I have a question, is this is a SW or a HW interrupt ? I mean is the code checking for the button to be pushed ?

  19. Dear Alex
    Many thanks for your excellent tutorials. It is generous of you to devote so much time to helping others.
    I have been following your interrupt tutorials to help me detect rainfall using a tilting bucket rain gauge, and can print a message to screen saying “rain event detected”. I am however, unable, to return a value to accumulate total rainfall. I am sure that this is a very naive question, as I have yet to really get to grips with Python, but any help would be much appreciated.
    Best wishes
    Harry

    • How are you measuring total rainfall? Have you got some kind of sensor?

      • Hi Alex

        I am using a spare Maplin rain gauge, which is a tilting bucket device that see-saws whenever 0.3 mm rain has fallen, when a small attached magnet briefly operates a reed switch. I have connected to the switch GPIO7, which is normally pulled high but earths briefly whenever the bucket tilts.

        This is all part of a weather-data-logger-RPi setup, still in bread-board phase. So, at the same time as rain, I am recording various temperatures and insolation. The program reads the sensors every 60 sec and determines means over a period defined by the raw-input value named ; I generally choose 15 min.

        Attached is a bit of the (probably badly written) program, with much irrelevant stuff deleted but its position indicated by comments.

        At the moment the function works and prints a message, so all is OK there. But, what I really want to do is add a command like:

        raintotal=raintotal+0.3

        whenever the bucket tilts

        and print to to file after every averaging period of length .

        Any help you can give will be greatly appreciated.

        Best wishes, Harry

        +++++++++++++++++++++++
        Excerpt from logging program:
        +++++++++++++++++++++++

        #!/usr/bin/python
        # reading T & RH from DHT22 sensor, plus T frpm TMP36, thermistors and DS18B20; also, light from NORPS
        # version testing rain-guge (still indoors)
        # modified after A Eames & Adafruit tutorials

        import os
        import glob
        import subprocess
        import datetime
        import gspread
        import re
        import sys
        import time
        import datetime
        import time
        import RPi.GPIO as GPIO
        from math import exp,log
        from time import strftime
        GPIO.setmode(GPIO.BCM)

        os.system(‘modprobe w1-gpio’)
        os.system(‘modprobe w1-therm’)

        print ‘\nHi Harry, \nI need to know something … \n’
        interval=raw_input(“Input time between means in minutes: “)
        interval=int(interval)

        # …………………………………….
        # commands setting up GPIO channels for temp etc go in here
        # ditto setting accumulators to 0.0
        #…………………………………..

        raintotal=0.0

        # prepare o/p file …………………..

        timestamp=time.strftime(‘%Y-%m-%d-%H-%M’)
        opfile=””.join(["dhtadop",timestamp,".txt"])
        fo=open(opfile,’w+’)
        fo.write(“Date, Time, Drift, Temp1, Temp2, Temp3gmin, Temp4soil, Light mV, Thstor1mV, Thstor2mV, Tmp_DHT22, RH_DHT22, T_f8soil, T_0cgmin,rain,\n”)
        fo.close()

        # —————————————————————————-
        # now set up thread for detecting rain
        def raindetected(channel):
        print “rain bucket tilted”

        GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(7, GPIO.FALLING, callback=raindetected, bouncetime=300) # raingauge on pin 7

        # end of rain detection

        # ========================Data Logging Starts ===============================
        # measures light & temp every minute, and calculates means over interval input
        # ===================================================================

        try:
        while True:
        time1=time.time()
        time2=time1+float(interval)
        print ‘start of big loop’
        # time2 sets no of secs between each cycle of readings i.e. +60 gives 1 min

        #…………………………………………
        #
        # commands reading light & temp in here
        #
        # ………………………………………..

        datstr=(obstime+’, ‘+driftm+’, ‘+Tm[1]+’, ‘+Tm[2]+’, ‘+Tm[3]+’, ‘+VTh4m+’, ‘+VLm+’, ‘+VTh1m+’, ‘+VTh2m +’, ‘+temp +’, ‘+humidity+’, ‘+T_f8m +’, ‘+T_0cm+’, ‘+T_14m+’, ‘+raintotal + ‘\n’)
        print “means”
        print datstr
        print “****************************”
        fo=open(opfile,’a’)
        fo.write(datstr);
        fo.close()
        # end of writing to file

        # reset accumulators etc

        # ……………….
        # irrelevant lines deleted
        # ………………..

        raintotal=0.0

        #print ‘go to start of while true loop’

        except KeyboardInterrupt:
        GPIO.cleanup()
        GPIO.cleanup()
        exit

        • You can put the

          raintotal=raintotal+0.3
          in your callback function as long as you make raintotal a global variable in your function it will be remembered.

          global raintotal
          raintotal=raintotal+0.3

          As for writing it to a file. Have a look at the code for the battery logging script. It’s on here somewhere.

          I’m deliberately not spoon-feeding you here. Tough love and all that :)

          • Thank you very much. I have tried it and it works! I’ve also managed to write to file and to a Google sheet. Now to fix a long cable to the gauge and put it outside and wait for rain.
            I am very grateful for your kind help.
            Best wishes
            Harry

          • Excellent :)

            For extra credit, have a google about “scope of variables in Python functions” and you’ll hopefully find something to show you what you’ve just done with the “global” part. (Although I don’t know, maybe you know other languages already?)

            A lot of people frown on the use of global, but it has its uses.

  20. Do u know how to install RPIO?
    import RPIO

  21. Hi There

    I can seem to get sudo python myCode to see the add_event_detect module. when I run sudo python3 myCode I can.

    I have python3-rpi.gpio version 0.5.3 and python-rpi.gpio 0.5.3 installed.

    Does python-rpi.gpio not have the interrupt methods?

    Cheers

    Tony

    • What is myCode? If you’re talking about any of the scripts I’ve written, they’re all for python 2.7

      • Hi Alex, when I say my code I mean my script and in it has the line add_event_detect module(). The script works fine when I run it using python 3 but not when I use python 2.7

        I get:

        GPIO.add_event_detect(4, GPIO.BOTH, callback=my_callback_A, bouncetime=0)
        AttributeError: ‘module’ object has no attribute ‘add_event_detect’

        When I query the RPi.GPIO module contents I get:

        >>> import RPi.GPIO as GPIO
        >>> dir(GPIO)
        ['ALT0', 'BCM', 'BOARD', 'HIGH', 'IN', 'InvalidChannelException', 'InvalidDirectionException', 'InvalidModeException', 'InvalidPullException', 'LOW', 'ModeNotSetException', 'OUT', 'PUD_DOWN', 'PUD_OFF', 'PUD_UP', 'RPI_REVISION', 'SetupException', 'VERSION', 'WrongDirectionException', '__doc__', '__file__', '__name__', '__package__', 'cleanup', 'event_detected', 'gpio_function', 'input', 'output', 'set_falling_event', 'set_high_event', 'set_low_event', 'set_rising_event', 'setmode', 'setup', 'setwarnings']
        >>>

        The add_event_detect module part is not there but it is there when I run the same using python 3. Not sure how to fix this.

        Rgds

        Tony

        • Ok, I see the RPI.GPIO version is 0.4.1 when I call GPIO.VERSION. I have the newer one installed though. How do I make python2.7 use the RPi.GPIO version I want?

          Cheers

          Tony

          • Looks like you may have installed RPi.GPIO manually in the past. There is a way to get rid of it, but I’m not sure how.

            But if your Raspbian distro is that old, why not just back up your work and flash a new card and have everything bang up-to-date?

          • I guess I could do that but it’s a kind of shitty solution…..I may have to resort to it though!

            Thanks

          • I agree, in principle, but the improvements in Raspbian have been so great, it is worth updating frequently.

            There’s a thread in the Python section of the Raspberry Pi forums where your exact issue with old versions has been discussed before and Ben Croston showed how to get rid of it. You could try looking there. I don’t know exactly what the thread is called, but I saw it the other day. Have a look and see if you can find it, if you don’t want to update your OS. :)

            Found it here. Pasting command here for future reference…
            sudo rm -rf /usr/local/lib/python2.7/dist-packages/RPi.GPIO-0.1.0*
            obviously need to change it for the appropriate version number :)

          • I sorted this issue by deleting the following:

            sudo rm -r /usr/local/lib/python2.7/dist-packages/RPi.GPIO-0.4.1a-py2.7-linux-armv6l.egg

            python 2.7 now sees the correct version of RPi.GPIO………next problem…..

            GPIO.add_event_detect(17, GPIO.BOTH, callback=my_callback_B, bouncetime=0)
            ValueError: The channel sent is invalid on a Raspberry Pi

            DAMN U!!

  22. If the event callbacks are essentially the same function (differing only in data values) as in the example, you can use a higher-order function to define the callback (let’s see if the blog preserves indentation with <code> tags): (Ed. it doesn’t, but I edited it to use the same plugin I used above)

    def make_callback(port, msg=''):
        def callback(channel):
            print("falling edge detected on {0}; {1}".format(port, msg))
        return callback
    
    GPIO.add_event_detect(17, GPIO.FALLING, callback=make_callback(17, "it's crackers"), bouncetime=300) 
    GPIO.add_event_detect(23, GPIO.FALLING, callback=make_callback(23, "slip a rozzer"), bouncetime=300)
    
  23. I’ve been following your tutorials….but I’ve some doubts….can I give the dtmf coded bits to the raspberry pi directly…..could you please give an explanation on this as well as the code…please code it as soon as possible

  24. Hello I require some assistance :D

    The tutorial was great and I learned a lot, but my code does not want to work.

    Here is the code:

    ######
    import RPi.GPIO as gpio

    pulse = 0

    def turnEvent():
    pulse = pulse + 1
    print(str(pulse))

    def main():
    gpio.setmode(gpio.BCM)
    gpio.setup(23, gpio.IN)

    gpio.add_event_detect(23, gpio.BOTH)
    gpio.add_event_callback(23, turnEvent, 100)
    ######

    The error is:
    gpio.add_event_detect(23, gpio.BOTH)
    RuntimeError: Edge detection already enabled for this GPIO channel.

    All I want it to do is display a count for how many turns the rotary encoder goes through. I’m just keeping track of the distance of my motors.

    I will appreciate any help!!

    • I forgot to mention that I am using a rotary encoder to do this. The only change in edge I’m concerned about is with Pin A as it does not matter to me by which direction the encoder is turning.

Leave a Reply