Handling GPIO-connected switches robustly in C on the Raspberry Pi

pushbutton Educational applications for the Raspberry Pi often involve connecting switches or pushbuttons to the GPIO pins, and using the switch state to trigger some action -- perhaps controlling some other electrical device, or just performing some operation in software.

The 'sysfs' interface in the Linux kernal -- despite its deprecated status -- makes this kind of thing very easy, at least in principle. It's just a matter of reading the GPIO pin status from a pseudo-file in the /sys/class/gpio/ directory. This will either return "1" for a high logic level, or "0" for a low one.

In an educational setting, having a program loop endlessly, checking the state of the GPIO, and then doing something when it changes, might well be satisfactory. In a practical application, this won't work very well, for various reasons.

First, unless the program checks the status very frequently -- which involves a lot of CPU activity -- it won't be very responsive to button presses. Second, it doesn't cope very well with the scourge of switch contact bounce. Again, this might not matter in all applications -- it might not hurt to switch something on that is already on, for example. But if you're using push-buttons to operate a menu selection, for example, it's very unhelpful if every press of the button moves the menu selection by three of four steps. For counting applications, this lack of determinacy could be a critical failure.

What we need is a strategy that (a) uses little or no CPU for the routine processes of checking the GPIO pin status, (b) responds instantly (well, within milliseconds), and (c) makes some effort to avoid the effects of contact bounce.

This article describes an approach for handling these issues in a C application, working around some odd quirks in the way that the Raspberry Pi kernel handles GPIO interrupts. I don't suppose that this is the only way to solve the problem, and I can't be certain that future Raspberry Pi models, or even newer kernels, would behave in exactly the same way.

Polling for interrupts

The least CPU-intensive way to determine when a GPIO pin has changed status is to use the poll() operation to check for 'priority' events. The way this should work for the sysfs interface is described in the kernel documentation.

We need to open the value pseudo-file for each pin, and the call poll() on the array of file descriptors. In outline, the code looks something like this:

struct pollfd fdset [MAX_PINS];
int gpio_fd_1 = open ("/sys/class/gpio/gpio22/value", O_RDONLY|O_NONBLOCK);
fdset[0].fd = gpio_fd_1;
fdset[0].events = POLLPRI;
int gpio_fd_2 = open ("/sys/class/gpio/gpio27/value", O_RDONLY|O_NONBLOCK);
fdset[1].fd = gpio_fd_2;
fdset[1].events = POLLPRI;
...

poll (fdset, MAX_PINS, -1);

The code creates an array of pollfd objects, and then sets each one to be triggered by a priority POLLPRI event. The poll() call returns whenever such an event is detected.

A priority event could be a low-to-high level change, or a high-to-low level change, or both, according to the value written to the edge pseudo-file for the relevant GPIO pin. In practice, though, a switch contact will generate both high-low and low-high transitions, whether it opens or closes, so we can't assume that contact bounce won't generate multiple events, even if the specific edge value is set.

Note that the poll operation can monitor any number of pins in the same call, and it does not seem to make any difference to the CPU usage -- it's effectively zero whilst waiting for a transition, however many pins are monitored.

Of course, if you monitor multiple pins, you need a way to work out which pin triggered poll() to exit.

for (i = 0; i < MAX_PINS; i++)
  {
  if (fdset[i].revents & POLLPRI)
    {
    // Pin definition i was triggered
    }
  }

If we are looking for level changes in both directions (switch on and switch off), the polling code does not give any indication of which direction the level changed. To figure this out we need to read the file descriptor for the value pseudo-file again. Unfortunately, this file won't contain valid data at this point. According to the kernel documentation:

"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."

In fact, neither of these approaches seems to work very well on the Pi, if there are very rapid level transitions -- which there will be if the switch contacts bounce.

What I've found to work reasonably well is the following:

1. Read the file descriptor to exhaustion (probably only two bytes) to clear the interrupt status
2. Wait a short time (see below)
3. Open the value file again, and get a new file descriptor
4. Read two bytes -- the first byte will be the desired "1" or "0"
5. Close the new file descriptor

The length of a 'short time' needs a little explanation. We aren't waiting for any change in the contact status here -- we're just waiting for the value stored by the kernel to settle. In my tests, a delay of a millisecond or so is sufficient. I have to admit that I've arrived at this procedure by trial-and-error, because the documented method does not seem to work reliably. I can't be certain that it will work in all Pi versions and set-ups, although I've been using it myself for about five years, and it's held up so far.

Characterstics of contact bounce

So we have a way to detect pin level changes without putting any strain on the CPU. But that, in itself, doesn't provide a way to handle contact bounce.

It isn't obvious to most designers, but switch contacts rarely make or break cleanly. The GPIO input level detection trips at a particular voltage but, if the voltage is changing slowly -- and 'slowly' here means over the course of a few milliseconds -- mutliple level changes can be registered over a short period. In addition, switch contacts frequently do literally bounce; that is, the contact surfaces bounce off one another before they settle into position. Typically a plot of pin state over time looks something like this:

switch bounce

In this plot, the 'bounce time' is the time over which we have to assume that level changes are not genuine, and must be ignored. The chaotic nature of switch contact behaviour, combined with the behaviour of the GPIO input circuit, means that we can't really predict how long the bounce time is -- all we can do is to set a value and hope, or insist, that the switch is not operated more frequently than that limit allows.

Handling contact bounce

So far as I know, the only robust way to handle contact bounce in a C application (or any other language routinely used on the Raspberry Pi) is to lock out transition detection for the bounce time, after the first level transition is detected. That's easy enough if we're only monitoring one pin -- we can just have the program sleep for a couple hundred milliseconds, or whatever turns out to be appropriate, then flush any outstanding interrupt events. However, if we are monitoring multiple pins, although we might want to lock out the same pin from triggering rapidly, we probably want other pins to trigger within the bounce times of their neighbours.

One way to handle this is to maintain an array of time values for each pin, recording when the first transition was detected for each pin. Subsequent transition for the same pin are ignored until the current time exceeds the stored time by the selected bounce time.

The complication here is that most programming languages and libraries do not provide a way to determine the time elapsed since a fixed point in time (e.g., the program start). This is actually surprisingly difficult to do, unless you have specialist hardware. We can use the general system clock to determine the elapsed time by calling, for gettimeofday() but this is problematic.

Why should this be? The problem is that that Linux system clock is prone to be disturbed. In a system like the Pi, which has no built-in real-time clock, it's particularly problematic. That's because we usually have to synchronize the clock from a time server using, e.g., NTP -- or just accept that the date is sometime around January 1970 all the time.

What typically happens when a Pi boots is that the system clock starts in January 1970 and then, when an Internet connection is established, suddenly jumps forward forty or so years. Looking for timing differences in switch contact changes of the order of milliseconds, when your timing baseline can suddenly shift by forty years, is a little tricky.

If you can't delay your program start-up until the system time has settled -- and in embedded applications you probably can't -- then one approach to a solution is just to reset the program's timing baseline when two events seem to be further apart in time than it reasonably plausible -- a year, perhaps. This does seem to work for me, but it's a method that might have to be adapted to suit the needs of the application.

Example

Complete sample code that demonstrates the techniques described in this article can be found in my pi-button-pipe application on GitHub.

Conclusion

It's surprisingly difficult to handle GPIO-connected switches in a robust, production-ready way on the Raspberry Pi. Partly that's because it's a genuinely difficult problem, and partly because the Pi kernel is a little quirky in this area. If practicable, hardware switch debouncing, or a combination of hardware and software, might be more appropriate for critical applications.