Raspberry Pi IoT In C - Events & Interrupts
Written by Harry Fairhead   
Monday, 02 November 2020
Article Index
Raspberry Pi IoT In C - Events & Interrupts
Events And The BCM 2835 Library
Measuring Pulses With Events
An Edgy Button

The BCM 2835 library doesn't support interrupts and the reason is that Linux doesn't support user-mode interrupts. However, it does support events in connection with GPIO lines.

That is, a GPIO line can be configured to set a bit when the input changes in a specific way, and this setting is performed in hardware with no need for the software to get involved. The hardware can also be set to generate an interrupt when the flag bit is set but we cannot handle this interrupt in user mode so it has to be disabled. Later we will discover how to make use of the GPIO interrupt, but this involves using the GPIO character driver – see Chapter 7.

At the time of writing, there is a “bug” or “feature” in the latest Linux kernel, version 4.19.75, that causes the system to crash when you use edge interrupts. The solution is to edit the /boot/config.txt file and add the line:

dtoverlay=gpio-no-irq

This may become unnecessary in later versions of the kernel. The problem is that with GPIO interrupts turned on, an interrupt occurs when the event status flags are set and this is seen by the kernel, which doesn’t know what to do with it.

If you want to disable interrupts dynamically, i.e. without having to ask the user to edit a configuration file, you can use:

#define _DEFAULT_SOURCE
#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void checkIRQ() {
    FILE *fp = popen("sudo dtparam -l", "r");
    if (fp == NULL) {
        printf("Failed to run command\n\r");
        exit(1);
    }
    char output[1024];
    int txfound = 0;
    while (fgets(output, sizeof (output), fp) != NULL) {
        printf("%s\n\r", output);
        fflush(stdout);
        if (strstr(output, "gpio-no-irq") != NULL) {
            txfound = 1;
        }
    }
    pclose(fp);
    if (txfound == 0) {
        fp = popen("sudo dtoverlay gpio-no-irq","r");
        if (fp == NULL) {
            printf("Failed to run command\n\r");
            exit(1);
        }
        pclose(fp);
    }
}

If you use checkIRQ at the start of your program it checks to see if gpio-no-irq is loaded as an overlay. If it isn’t it loads it. Notice that dynamically unloading overlays isn’t a good idea and the gpio-no-irq remains in effect until the next reboot.

The BCM 2835 GPIO hardware has a very sophisticated and impressive list of conditions that you can set to trigger an event. These are all made available via the BCM 2835 library as a group of set and clear functions. While we cannot use these to implement an interrupt we can use them to make detecting changes in the state of a GPIO line much easier. All that happens is that the hardware sets a status bit when the specified event occurs and this happens without the intervention of the software. The setting of the flag can also generate an interrupt and, in an ideal world, we would be able to write an interrupt handler for this, but you can’t do this in user mode so we are restricted to polling for changes in the state of the flag.

There are four groups of functions that work with any of high, low, rising edge or falling edge detection:

  • High Detect

void bcm2835_gpio_hen(uint8_t pin)
void bcm2835_gpio_clr_hen(uint8_t pin) 
  • Low Detect 

void bcm2835_gpio_len(uint8_t pin)
void bcm2835_gpio_clr_len(uint8_t pin) 
  • Falling Edge Detect

void bcm2835_gpio_fen(uint8_t pin)
void bcm2835_gpio_clr_fen(uint8_t pin) 
  • Rising Edge Detect

void bcm2835_gpio_ren(uint8_t pin)
void bcm2835_gpio_clr_ren(uint8_t pin)

The first function of each pair enables the event and the second disables it. You can set both rising and falling edge detection so as to enable both edges. Notice that as these are hardware settings, they will persist between program runs.

It is obvious that the rising and falling events occur when the input changes from low to high and high to low respectively, but when do the high and low level events occur? The answer is that a high or low level event is triggered as soon as the line is high or low and the event is enabled. This means that you could trigger the event as soon as you enable it, if the line is already high or low. The event also stays set as long as the high or low status persists and trying to reset it has no effect. In practice, edge detection is usually more useful. 

There are also two groups that work with an asynchronous version of rising and falling edge detection. These are simply faster versions of the previous two:

Asynchronous Falling Edge Detect

void bcm2835_gpio_afen(uint8_t pin)
void bcm2835_gpio_clr_afen(uint8_t pin)

Asynchronous Rising Edge Detect

void bcm2835_gpio_aren(uint8_t pin)
void bcm2835_gpio_clr_aren(uint8_t pin)

It is worth explaining that the asynchronous versions may be faster but they are not "debounced". The difference between the two is that the first set of functions gates the inputs with the system clock so limiting the rate that the line can change. Only use the asynchronous versions if you really have to.

Each of these events, if set and if triggered, set bits in an event detection register. The setting of these status bits can also be set to cause an interrupt, but as already mentioned there is no way of directly handling such an interrupt in user mode and they have to be explicitly disabled.

The functions that let you test the status bits are:

Return or clear a specific bit

uint8_t bcm2835_gpio_eds(uint8_t pin)
void bcm2835_gpio_set_eds(uint8_t pin)

Return or clear a set of bits using a mask

uint32_t bcm2835_gpio_eds_multi(uint32_t mask)
void bcm2835_gpio_set_eds_multi(uint32_t mask)

The first function of the pair returns one if the bit is set and zero otherwise. Despite it having “set” in its name, the second clears the status bit.

Notice that after an event has been detected you have to clear the status bit for the event to be detected again. As already mentioned, clearing bits for high or low events only works if the level isn't currently high or low respectively. If you clear the high bit and the line is already high then it is immediately set again, and similarly for the low bit, and this can cause a system crash. Avoid such level events if you can.

You can set multiple events on different pins at the same time and you can test the all of the event bits using the multi version of the functions.

Putting all this together, the steps in using events are:

  1. Set the line to be an input e.g.

    bcm2835_gpio_fsel(RPI_BPLUS_GPIO_J8_07,
                            BCM2835_GPIO_FSEL_INPT);
  2. Select which type of event you want to enable e.g

    bcm2835_gpio_ren(RPI_BPLUS_GPIO_J8_07);

    enable rising edge event.

  3. Clear the event bit e.g.

    bcm2835_gpio_set_eds(RPI_BPLUS_GPIO_J8_07);
  4. After this you can do something else and eventually read the status bit to see if the event occurred e.g.

    bcm2835_gpio_eds(RPI_BPLUS_GPIO_J8_07);

    returns one if the event occurred.

  5. Reset the event bit to detect another event e.g.

    bcm2835_gpio_set_eds(RPI_BPLUS_GPIO_J8_07);


Last Updated ( Saturday, 07 November 2020 )