Home > BeagleBone > BeagleBone, GPIO, & IRQ

BeagleBone, GPIO, & IRQ

My last BeagleBone post looked at interfacing the the bone with an incremental rotary encoder. I wasn’t satisfied with the polling loop I used to read inputs from the encoder so I began investigating alternate methods. While reviewing the sysfs GPIO documentation I noticed that …

Inputs can often be used as IRQ signals, often edge triggered but sometimes level triggered. Such IRQs may be configurable as system wakeup events, to wake the system from a low power state.

Later in the documentation it states …

/sys/class/gpio/gpioN/ …

“value” … reads as either 0 (low) or 1 (high). … If the pin can be configured as interrupt-generating interrupt and if it has been configured to generate interrupts (see the description of “edge”), you can poll(2) on that file and poll(2) will return whenever the interrupt was triggered. If you use poll(2), set the events POLLPRI and POLLERR. If you use select(2), set the file descriptor in exceptfds. After poll(2) returns, either lseek(2) to the beginning of the sysfs file and read the new value or close the file and re-open it to read the value.

“edge” … reads as either “none”, “rising”, “falling”, or “both”. Write these strings to select the signal edge(s) that will make poll(2) on the “value” file return.

This file exists only if the pin can be configured as an interrupt generating input pin.

If all this is truly working on the bone then it’s a better method than polling in user space. It should eliminate a bunch of context switching between kernel and user space  in my current code. I had to try this out.

Using poll(2), with a properly configured GPIO, my code could now react to changes generated by the encoder rather than continually polling it on my own. For simplicity I configured the GPIO’s using some shell commands.

echo 70 > /sys/class/gpio/export
echo 71 > /sys/class/gpio/export

echo in > /sys/class/gpio/gpio70/direction
echo in > /sys/class/gpio/gpio71/direction

echo both > /sys/class/gpio/gpio70/edge
echo both > /sys/class/gpio/gpio71/edge

Using a the following C program I monitored a incremental rotary encoder wired to a BeagleBone.

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>

#define A 0
#define B 1

void dump_value(int fd) {
        char buffer[1024];
        lseek(fd, 0, 0);

        int size = read(fd, buffer, sizeof(buffer));
        buffer[size] = NULL;

        printf("\t\t size: %d  buffer: %s\n", size, buffer);
}

void dump_event(int fd, struct pollfd *pfd) {
        short revents = pfd->revents;

        printf("revents: 0x%04X\n", revents);
        if (revents & POLLERR) {
                printf("\t POLLERR  errno: %d\n", errno);

                if (errno == EAGAIN) {
                        printf("\t\t EAGAIN\n");
                }
                if (errno == EINTR) {
                        printf("\t\t EINTR\n");
                }
                if (errno == EINVAL) {
                        printf("\t\t EINVAL\n");
                }
        }

        if (revents & POLLHUP) {
                printf("\t POLLHUP\n");
        }

        if (revents & POLLNVAL) {
                printf("\t POLLINVAL\n");
        }

        if (revents & POLLIN) {
                printf("\t POLLIN\n");

                dump_value(fd);
        }
        if (revents & POLLPRI) {
                printf("\t POLLPRI\n");

                dump_value(fd);
        }
        if (revents & POLLOUT) {
                printf("\t POLLOUT\n");
        }
        if (revents & POLLRDNORM) {
                printf("\t POLLNORM\n");
        }
        if (revents & POLLRDBAND) {
                printf("\t POLLRDBAND\n");
        }
        if (revents & POLLWRNORM) {
                printf("\t POLLWRNORM\n");
        }
        if (revents & POLLWRBAND) {
                printf("\t POLLWRBAND\n");
        }
}

int get_lead(int fd) {
        int value;
        lseek(fd, 0, 0);

        char buffer[1024];
        int size = read(fd, buffer, sizeof(buffer));
        if (size != -1) {
                buffer[size] = NULL;
                value = atoi(buffer);
        }
        else {
                value = -1;
        }

        return value;
}

void main() {
        int fd[2];

        fd[A] = open("/sys/class/gpio/gpio70/value", O_RDONLY);
        fd[B] = open("/sys/class/gpio/gpio71/value", O_RDONLY);

        struct pollfd pfd[2];

        pfd[A].fd = fd[A];
        pfd[A].events = POLLPRI;
        pfd[A].revents = 0;

        pfd[B].fd = fd[B];
        pfd[B].events = POLLPRI;
        pfd[B].revents = 0;

        int lead[2];
        while (1) {
                int ready = poll(pfd, 2, -1);
                printf("ready: %d\n", ready);

                if (pfd[A].revents != 0) {
                        printf("\t Lead A\n");
                        //dump_event(fd[A], &pfd[A]);
                        lead[A] = get_lead(fd[A]);
                }
                if (pfd[B].revents != 0) {
                        printf("\t Lead B\n");
                        //dump_event(fd[B], &pfd[B]);
                        lead[B] = get_lead(fd[B]);
                }

                printf("\t\t A: %d  B: %d\n", lead[A], lead[B]);
        }
}

Using a timeout of -1 the poll will wait indefinitely. The code is simply waiting for an edge event to occur. That would be one of the encoder leads transitioning from low to high or high to low. This produced the following output when I turned the encoder knob.

ready: 2
         Lead A
         Lead B
                 A: 1  B: 1
ready: 1
         Lead A
                 A: 0  B: 1
ready: 1
         Lead B
                 A: 0  B: 0
ready: 1
         Lead A
                 A: 1  B: 0
ready: 1
         Lead B
                 A: 1  B: 1

In the above example, both leads start out on a detent. As I rotate the knob clockwise lead A goes low and then lead B does the same. At this point the knob is half way between detents. As rotation continues A goes high and then B follows. The knob has settled on the next detent.

I noticed two interesting artifacts.

  1. The first poll will always returns immediately with values for all the GPIO’s being polled.
  2. When I turned the knob quickly I did see some polls return changes on both GPIO’s. This means an intermediate state was lost. This isn’t a fatal problem. It just has to be accounted for when monitoring the encoder’s phases and calculating its position.
  3. Once the GPIO value is read it can’t be read again until another edge event occurs. If you try the read function will fail.

I’d like to see how well this new code can keep up with a fast turning encoder. Since nothing is being buffered by the sysfs GPIO kernel driver there’s a very good chance that the user space code still can’t service the edge events quickly enough.

At some point soon I’ll refactor the rotary code in my BeagleBone library to reflect this method.

Advertisements
  1. jkridner
    April 16, 2012 at 12:58 am

    I don’t remember the value right off hand, but you can write an IOCTL to read back the current status without needing to wait on an event. There are quite a few useful articles out there already.

  2. jkridner
    April 16, 2012 at 1:15 am

    You can read GPIO status without having an event by using an IOCTL. You might take a look at this library for some examples: http://wiki.gumstix.org/index.php?title=User_GPIO_Driver

    • April 21, 2012 at 9:27 am

      Yup, ioctl is an option. If I remember correctly ioctl only handles a single file descriptor. In this example I needed to monitor two file descriptors. poll() does all this quite nicely. I don’t think I could have written it any better using ioctl.

  3. June 4, 2012 at 11:13 am

    hi all,
    is there a generic C library taht set all GPIO using a configfile?
    I understand that before starting any dev I had to set my GPIO… 😦

  4. July 24, 2012 at 1:22 pm

    I’m trying to reproduce your code in my BB, but my poll returns immediately always with POLLERR into the revents field. I’m also using the pin 12, but with no success. Any guess?

  5. Austin Schuh
    April 30, 2013 at 11:42 pm

    Did you ever get time to figure out how fast it can read without missing counts?

    • May 1, 2013 at 1:00 am

      No. I’ve come across some documentation related to bouncing and that’s a related issue. I suspect that reading IO pins from an application isn’t going to cut it. Instead it will require some kernel level code that can capture all the changes and has some debounce capabilities.

      • September 3, 2013 at 8:59 pm

        An easy interrupt/debounce interface is provided through the gpio-keys driver, which is made for buttons. 🙂

  6. DataHero
    May 10, 2014 at 12:59 am

    I have had better success with epoll. Less lossy and you can use it on sysfs pins.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: