Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupts #15

Closed
alanchrt opened this issue Feb 1, 2013 · 11 comments
Closed

Interrupts #15

alanchrt opened this issue Feb 1, 2013 · 11 comments

Comments

@alanchrt
Copy link
Contributor

alanchrt commented Feb 1, 2013

The BeagleBone reference manual mentions the possibility of using a hardware interrupt to detect pin changes immediately when they happen. The documentation is sparse, and all I could find was this statement:

Any GPIO can be used as an interrupt and is limited to two interrupts per GPIO Bank for a maximum of eight pins as interrupts.

Bonescript uses an attachInterrupt method, similar to the Arduino to pull it off, although in my experience with it, it seemed spotty.

Have you considered moving away from the EventIO model and using interrupts? I'm not sure how to pull it off in Python (signals, maybe?), but it would be extremely helpful for pin changes that last only a few milliseconds.

I'm still not 100% sure I understand how the interrupts work, so maybe they're only doing something similar to your EventIO model anyway, but here's the method I'm talking about in Bonescript:

https://github.com/jadonk/bonescript/blob/master/node_modules/bonescript/index.js#L272

@bduhan
Copy link

bduhan commented Feb 1, 2013

Unless it has changed, EventIO from this project uses a blocking poll loop to check for pin status changes. One better way to do this is with epoll because it lets the kernel take care of notifying your program when a file descriptor - any of them, but it can be tied to a gpio - changes. In the Bone's case this is done in memory/hardware so it never actually touches a file.

Bonescript uses epoll, you can do it manually with the epoll library in C, directly from Python (http://scotdoyle.com/python-epoll-howto.html) or, personally I enjoy using Tornado (http://www.tornadoweb.org/) because it comes with the default Bone distribution, you can still use regular PyBBIO in the IOLoop, and it is very cpu efficient. The same single process Tornado IOLoop is able to handle real-time WebSocket clients, regular web traffic, inbound and outbound TCP sockets, scheduled events, and, of course, edge-triggered IO.

@alanchrt
Copy link
Contributor Author

alanchrt commented Feb 1, 2013

I think I understand that. I saw something else about the gpio driver being able to create /dev/input/event* devices to be used for edge-triggered interrupts, but it's way above my level.

Are you just using PyBBIO digitalRead() for edge-triggered IO, but inside the context of Tornado's IOLoop? Currently, that's my approach without Tornado. I have digitalReads in a loop running inside a thread, instead of using a separate process, since I want to be able to easily pass objects around without them getting pickled/copied. However, it's missing a lot of edge events. I think the reads in the loop aren't firing rapidly enough to catch the change. Will IOLoop change that, just by including my reads inside the loop, or are you using a different model to detect changes entirely (monitoring specific files, etc..)?

@bduhan
Copy link

bduhan commented Feb 1, 2013

Give it a shot! Install python-tornado through opkg, then configure a GPIO in your shell:
# connector P8 pin 27 is GPIO2_22 (aka, lcd_vsync)
echo 86 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio86/direction
echo falling > /sys/class/gpio/gpio86/edge

Then run a test program like this:

import logging, tornado.ioloop, tornado.options

class MyInterrupter (object):
    def __init__ (self, loop, gpio_val_path):
        self.fd = open (gpio_val_path, "r")
        loop.add_handler (self.fd.fileno (), self.int_handler, loop._EPOLLET)
        self.interrupt_count = 0

    def int_handler (self, fd, events):
        # instead of just counting you could read a data line or something else here with bbio
        self.interrupt_count += 1

    def log_count (self):
        # don't print on every count, the overhead could cause us to miss some interrupts if we are blocking on write
        logging.info ("interrupt_count is %d" % self.interrupt_count)

tornado.options.parse_command_line ()
loop = tornado.ioloop.IOLoop.instance ()

interrupter = MyInterrupter (loop, "/sys/class/gpio/gpio86/value")
printer = tornado.ioloop.PeriodicCallback (interrupter.log_count, 1000)
printer.start ()

loop.start ()

@alanchrt
Copy link
Contributor Author

alanchrt commented Feb 1, 2013

Nice! I've never worked with Tornado, but this looks really elegant. So it's essentially just abstracted away the epoll API a bit and added some really nice stuff for callback loops, non-blocking IO loops, etc? I'll give it a shot. That should work great for my app.

Quick question, and I'll dig through if you don't know off the top of your head, but is there something in PyBBIO's API that will let me write /sys/class/gpio/gpioXX/edge without manually doing it in the shell or writing it with Python file I/O?

@bduhan
Copy link

bduhan commented Feb 1, 2013

If this is on a custom Cape you can store pinmux settings in an EEPROM chip, add some code to the Bone board file in the kernel and rebuild the whole system and etc, etc.. eventually it can configure IO automatically. Otherwise (for dev) I either do it before starting the IOLoop in code or by running a shell script at boot with systemd. I think Bonescript has some utilities for these settings too, but I haven't looked at it much.

I would like to reiterate for others that PyBBIO is still very useful with Tornado due to Alexander's memory mapped IO approach.

Have fun!

@alanchrt
Copy link
Contributor Author

alanchrt commented Feb 4, 2013

So, I ended up implementing this directly with epoll (still super-interested in Tornado, just didn't delve into it yet). I haven't had the time to extract the bits and pieces out, but I have a pretty good idea of how to turn it into attachInterrupt() and detachInterrupt() methods for PyBBIO. Alexander, let me know if you want me to take a stab at writing them and adding them to the API, and I'll send you a pull request!

@alexanderhiam
Copy link
Member

Hey guys, sorry for the delay responding. Hardware interrupts have been on the list for PyBBIO, but I hadn't thought too much about it yet. The EventIO library was a quick first pass at an event loop of sorts, more or less as a proof of concept of what an event-driven API for PyBBIO could look like. I've actually been meaning to rewrite it using Tornado, and having built-in epoll bindings is yet another reason it would be a good fit; thanks for pointing that out bduhan.

alanctkc, it would be much appreciated if you wanted to contribute some interrupt code! Do you think it could be integrated into the core API and used without having to change the structure of a PyBBIO program; that is have a Tornado event loop running in a separate thread or process which only handles interrupts, and have the main loop execute normally?

@alanchrt
Copy link
Contributor Author

alanchrt commented Feb 9, 2013

Cool. I've got a busy weekend, so I'll probably get to it Monday-ish.

I think I could incorporate it into the core PyBBIO API pretty easily, with no dependencies. My plan is to add a basic thread, in daemon mode, in the run() method. I'd then just use epoll directly instead of using Tornado at all. If you ever wanted to incorporate Tornado later, it would be trivial to change it, but that way it keeps it simple and only uses core Python for now. The attachInterrupt() method would check to see if the epoll object had already been created and started running, and, if not, it would create it and add the event. So, all the epoll stuff would be invisible to the user.. they just set the pinMode for each GPIO they want to use as an input, then use attachInterrupt() to add a handler.

I'll let you know how that goes!

@alanchrt
Copy link
Contributor Author

alanchrt commented Feb 9, 2013

Err, maybe I'll leave the thread out of the run() method, just in case someone wants to use the API without using the PyBBIO loop.

@alexanderhiam
Copy link
Member

Yeah, the idea is to have the API fully support three methods of using PyBBIO: using the Arduino-style setup()->loop() scheme, just importing bbio and calling the bbio_init() and bbio_cleanup() methods explicitly, and importing it into the Python interactive interpreter in which case bbio_init() and bbio_cleanup() are called automatically. The run() function is only used in the first case.

I agree leaving Tornado out of the core API is a good idea, I just misread what you said before.

@alexanderhiam
Copy link
Member

Interrupt code pulled from Alan, see issue #16.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants