How to implement an interrupt driven GPIO input in Linux
With Linux, some of the things that seem like they should be easy are not — at least at first glance. For example, how do you read an interrupt driven GPIO input in a Linux application? With simpler microcontroller systems, this is straightforward, but with a system like Linux, you have to navigate through several layers of software (and for very good reasons). You can’t handle interrupts directly in a Linux application, so this means you need a kernel component involved. This operation of reading a GPIO resembles a key press, so the Linux input subsystem might be a good place to start looking. Once we take that route, we discover the gpio_keys driver.
Configuring the gpio_keys driver
The gpio_keys driver is configured with a few lines of code in your Linux board configuration file. An example is below:
static struct gpio_keys_button svs_button_table[] = {
{ .code = KEY_RECORD,
.gpio = PP_GPIO_MIC_EN,
.active_low = 1,
.desc = "MIC_EN",
.type = EV_KEY,
.wakeup = 0,
},
};
In this application, we are reading button presses on a cell phone style headset. Once the gpio_keys driver is configured, a new entry will show up in /dev/input/eventX. An application can then do a blocking read on this device.
Reading the GPIO in an application
To read the GPIO, we simply do a blocking read on the new /dev/input/eventX device. The read will block until there is a change in GPIO state. An example is show below:
#define MIC_INPUT_DEV "/dev/input/event0"
static gboolean mic_button_callback(GIOChannel *source, GIOCondition condition, gpointer data)
{
struct input_event ev;
int bytes_read;
g_io_channel_read_chars(source, (gchar *)&ev, sizeof(ev), &bytes_read, NULL);
if (bytes_read > 0) {
if (bytes_read != sizeof(ev)) {
s_debug(1, "warning, only read %i bytes from mic input");
return TRUE;
}
} else {
return TRUE;
}
if (ev.type != EV_SYN && ev.value == 1) {
/* button pressed, do something ... */
}
return TRUE;
}
void mic_button_init()
{
GIOChannel * micbutton = g_io_channel_new_file(MIC_INPUT_DEV, "r", NULL);
if (micbutton == NULL) {
s_debug(TRUE, "Error initializing mic button");
return;
}
g_io_channel_set_encoding(micbutton, NULL, NULL);
guint id = g_io_add_watch(micbutton, G_IO_IN, mic_button_callback, NULL);
}
The above example also shows how to incorporate the GPIO read into a GLib mainloop so that you don’t need to create a separate thread. ( As a side, GLib mainloop programming is worth learning!) Using this method, reading a GPIO interrupt is easy and requires very few lines of code. This is typical of complex systems like Linux — if you know how to do something, it is relatively easy, but getting started down the right path is sometimes the challenge.
Yonathana said,
I try this code by capturing /dev/input/event1 with the below mentioned code by for some reason i havent been able to figure out i end up getting “Error initializing mic button” . Any clues why this might be happening ?
#include
#include
#include
#include
#include
#include
#include
#include
#define MIC_INPUT_DEV “/dev/input/event1″
static gboolean mic_button_callback(GIOChannel *source, GIOCondition condition, gpointer data)
{
struct input_event ev;
int bytes_read;
g_io_channel_read_chars(source, (gchar *)&ev, sizeof(ev), &bytes_read, NULL);
if (bytes_read > 0) {
if (bytes_read != sizeof(ev)) {
// s_debug(1, “warning, only read %i bytes from mic input”);
printf(”warning, only read %i bytes from mic input”);
return TRUE;
}
} else {
return TRUE;
}
if (ev.type != EV_SYN && ev.value == 1) {
/* button pressed, do something … */
printf(”Power Key pressed YIPPY YIPPY”);
}
return TRUE;
}
void mic_button_init()
//void main()
{
GIOChannel * micbutton = g_io_channel_new_file(MIC_INPUT_DEV, “r”, NULL);
if (micbutton == NULL) {
// s_debug(TRUE, “Error initializing mic button”);
printf(”Error initializing mic button”);
return;
}
g_io_channel_set_encoding(micbutton, NULL, NULL);
guint id = g_io_add_watch(micbutton, G_IO_IN, mic_button_callback, NULL);
}
void main()
{
mic_button_init();
}
Cliff Brake said,
So the GIOChannel * micbutton = g_io_channel_new_file(MIC_INPUT_DEV, “r”, NULL); call must be failing. You might want to verify that the /dev/input/event1 device does exist, and perhaps test it first using one of the tools described in the following article: http://bec-systems.com/site/209/linux-input-testing-and-debugging.
Yonathana said,
Had to do a sudo inorder for the application to have sufficient permissions to access the event node. Thanks Cliff !
Add A Comment