How to implement an interrupt driven GPIO input in Linux

Posted by Cliff Brake on 2009-01-10 | 8 Comments to Read

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 !

  • Venki said,

    Can you please tell me how to cross compile that application. What are the header files and where can i find them. I am using openembedded to build kernel, on ubuntu. Thank you

  • Cliff Brake said,

    You can set up a script similar to: http://cgit.bec-systems.com/cgit.cgi/oe-build/tree/scripts/setup-env-armv7-cross to use the OE cross compiler outside of OE.

  • uk_raj said,

    cliff thanks for nice post.
    Can you please tell me, if device is present in sys-fs instead of /dev/input then also callbacks are valid.
    I mean
    #define MIC_INPUT_DEV “/sys/devices/platfrom/pio0.4″

    Thanks,

  • Simon said,

    Hi,

    I’m using this technique to trap a change on a GPIO line. The event is working and I do receive it when a change happens on the gpio. The thing is, I would like to be able to read the value of that GPIO when my program start. If it’s in a certain state, I would like to react right away. When I try to export the pin with echo > “xxx” /sys/class/gpio/export it isn’t created. Is it a limitation where I can’t use a gpio that is being used as an interrupt source at the same time as a gpio that I can poll?

    Thanks!

  • Beaglebot – A BeagleBoard based robot | Yet Another Hacker's Blog said,

    [...] sampling than polling). The interrupt is passed through to userspace on the BeagleBoard via the gpio_keys driver. Using expansion header pins as interrupts also requires changing the mux settings. [...]