Blocking read on GPIO from Python: How to block using epoll() and select.EPOLLET

1.8k views Asked by At

I'm experimenting with GPIO access from Python on an embedded system (ARM core), which is running linux built with Buildroot (kernel 4.1.15).

I want my code to BLOCK waiting for a pin change on GPIO2 (i.e. I don't want to poll the pin by calling "read" repeatedly). I'm trying to do this using "epoll" in edge-triggered mode:

See Python docs for epoll. select.EPOLLET flag is used to get edge triggering for epoll. See also Linux docs for epoll.

For simplicity, I've already set up my GPIO pin from the console, using sysfs:

# cat /sys/class/gpio/gpio2/direction 
in
# cat /sys/class/gpio/gpio2/edge
rising

Here's my Python code:

#!/usr/bin/env python
# coding=utf8

from time import sleep
import select
import sys

if __name__ == '__main__':

    try:
        pinIn = open("/sys/class/gpio/gpio2/value", "r")
    except IOError:
        print("Error setting up GPIO pin")
        sys.exit()

    myPoll = select.epoll()
    myPoll.register(pinIn.fileno(), select.EPOLLPRI | select.EPOLLET)

    while(1):

        events = myPoll.poll(4)
        print("EPoll result: %s" % (str(events),))

        for fd, event_type in events:
            print("FD: %d; Events: %d" % (fd, event_type))
            if event_type & select.EPOLLIN:
                print("-EPOLLIN!")
            if event_type & select.EPOLLPRI:
                print("-EPOLLPRI!")
            if event_type & select.EPOLLERR:
                print("-EPOLLERR!")

        value = pinIn.read(1)
        pinIn.seek(0)
        print("--> %s" % (str(value),))
        sleep(1)

For testing, I am feeding the input pin with a square wave from a signal generator at about 2 seconds per cycle, so I can see when the pin changes.

When I run this on my embedded system, I get this:

# python3 /usr/sbin/test-gpio-python.py 
EPoll result: [(3, 10)]
FD: 3; Events: 10
-EPOLLPRI!
-EPOLLERR!
--> 0

The code sleeps on the 1 seconds sleep, then on the next iteration, the poll() immediately returns and doesn't block. It should block since my input is only operating at one rising edge per 2 seconds.

Why isn't "poll()" blocking?

==== EDIT: ====

Originally, the code caused a weird error when I tried to use "select.EPOLLET":

OverflowError: can't convert negative value to unsigned int

However, I discovered that I had accidentally used myPoll = select.poll() instead of epoll(). Code now fixed.

1

There are 1 answers

1
Jeremy On

I decided to check the interrupt information by looking at /proc/interrupts.

Here it is, only a couple of seconds after setting edge to "rising" for the GPIO pin:

# cat /proc/interrupts 
...
 33:        421  gpio-mxc   2 Edge      gpiolib
...

Hmm, 421 interrupts have occurred already!

Two seconds later:

# cat /proc/interrupts 
...
 33:        852  gpio-mxc   2 Edge      gpiolib
...

That could explain it. Interrupts are piling up at ~400 per second, definitely faster than I am processing them in Python.

Further investigation with a scope showed that the signal generator was only putting out about 1.6 V, and it looks like noise was triggering the input circuit on the device.

When I switched to the correct signal generator output and got a clean signal on the GPIO pin, I started getting the expected number of interrupts, and the python code worked correctly (i.e. poll() blocked correctly between rising edges on the input).