Article Index

Measuring Pulses With Events

Now we have all of the functions we need to implement a pulse measurement program using events. In this case we can measure the width of any pulse as the distance between a rising and a falling edge or a falling and a rising edge. 

To do this we need to set the rising and falling edge detection for the pin:

    bcm2835_gpio_fsel(RPI_GPIO_P1_07, BCM2835_GPIO_FSEL_INPT);    
    bcm2835_gpio_fen(RPI_GPIO_P1_07);
    bcm2835_gpio_ren(RPI_GPIO_P1_07);

Now Pin 7 will trigger an edge event if it goes from low to high or high to low. So now all we have to do is test the status register for the event and clear it after it has been detected

   volatile int i;
    while (1) {
        bcm2835_gpio_set_eds(RPI_GPIO_P1_07);
        while (0 ==bcm2835_gpio_eds(RPI_GPIO_P1_07));
        bcm2835_gpio_set_eds(RPI_GPIO_P1_07);
        for (i = 0; i < 5000; i++) {
            if (1 == bcm2835_gpio_eds(RPI_GPIO_P1_07)) break;
        }
        printf("%d\n\r", i);
        fflush(stdout);
    }

Notice that we clear the edge event and then wait for another and use the number of loops as a measure of the pulse time. 

This program produces very similar results as the previous program that simply read the inputs on the GPIO line. 

In this case there is no real advantage in using the events approach to polling. However if you had multiple GPIO lines and perhaps multiple conditions to test you could set all the events you were interested in and then check to see if any of them had happened with a single bcm2835_gpio_eds_multi function call. Of course you would then probably have to work out what had triggered the event but it does have a potential advantage to implement things this way. 

Interrupts

Linux does provide a way for system interrupts to communicate with user mode programs but, if you know how interrupts work you might not think it a good implementation. However if you agree with the basic premise of the situations in which you want to use and interrupt you have to agree that it isn't a bad way of doing things. 

The basic idea is that a user mode program can wait for a file operation to complete. Linux will suspend the thread concerned and restart it only when the file operation is complete. This is a completely general mechanism and you can use it to wait for any file based operation to complete including file operations in SYSFS. 

Any GPIO line that is capable of generating an interrupt has an edge directory in SYSFS. This can be set to none, rising, falling or both to set the corresponding edge interrupt. So the first thing we need is an extension to the SYSFS functions introduced in the previous chapter. 

int setEdgeGPIO(int gpio, char *edge) {
    char buf[BUFFER_MAX];
    int len = snprintf(buf, BUFFER_MAX, "/sys/class/gpio/gpio%d/edge", gpio);
    int fd = open(buf, O_WRONLY);
    write(fd, edge, strlen(edge) + 1);
    close(fd);
    return 0;
}

This simply writes whatever edge is to the edge directory. Notice that for simplicity no checks are included for the GPIO pin being exported and set to input.

So now we can set the interrupt we want to use but how do we wait for it to happen?

The solution is the standard Linux function poll

#include <poll.h>
int poll(fdset[],n,timeout)

where fdset is a struct that specifies the file descriptor and the event you want to wait for, n is the size of the array and timeout is the timeout in milliseconds. If you call poll then it will wait for the specified event on the file descriptors until the timeout is up when it returns. In fact what actually happens is a little more complicated. The thread that called poll is suspended until the event occurs and then the system restarts it. This is not really and interrupt as in a true interrupt the thread would continue to execute until the interrupt occurred when it would run the ISR specified. This may not be an interrupt proper but with a little more work it can provide more or less the same behavior. 

First let's construct the simplest possible example using poll to wait for a raising edge on GPIO 4 (pin 7).

First we need to open GPIO 4 and set edge to rising:

  openGPIO(4, 0);
  setEdgeGPIO(4, "rising");

Note that you don't have to use the SYSFS functions given you can do the job any way that works but you must use SYSFS because you need a file descriptor to pass to the poll function in the fdset struct:

  struct pollfd fdset[1];
    for (;;) {
​        fdset[0].fd = fd[4];
        fdset[0].events = POLLPRI;
        fdset[0].revents = 0;

In this case we are only polling on a single file descriptor and this is stored in the fd field - recall that the openGPIO function stores the file descriptor for the value folder in fd[gpip number]. The events field is a bit mask set in this case  to POLLPRI which is defined as "there is urgent data to read". There are other event types documented. The revents field is a bit mask with the same format as events filled in by the poll function when it returns to indicate the event that occurred. 

Now everything is in place to call poll to wait for the event - which might already have happened of course:

        int rc = poll(fdset, 1, 5000);

The time out is set to five seconds. You can specify a negative time out if you want to wait indefinitely or a zero time out if you don't want to wait at all.

The rc return value is either 0 for a time out, a positive value giving the file descriptor that fired the interrupt or a negative error value. 

        if (rc < 0) {
            printf("\npoll() failed!\n");
            return -1;
        }
        if (rc == 0) {
            printf(".");
        }
        if (fdset[0].revents & POLLPRI) {
            lseek(fd[4], 0, SEEK_SET);
            int val=readGPIO(4);
            printf("\npoll() GPIO 4 interrupt occurred %d\n\r",val);

        }
        fflush(stdout);
    }

If the event has occurred on the file descriptor we set then to clear the interrupt we seek to the start of the file. You can also read the current value of the GPIO line. 

If you try this out you will discover that when pin 7 is toggled from low to high you get an interrupt  

 

#include <stdio.h>
#include <string.h>
#include <bcm2835.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>

#define BUFFER_MAX 50
int fd[32] = {0};

int openGPIO(int pin, int direction);
int writeGPIO(int gpio, int value);
int readGPIO(int gpio);
int setEdgeGPIO(int gpio, char *edge);


int main(int argc, char** argv) {
    if (!bcm2835_init())
        return 1;

    openGPIO(4, 0);
    setEdgeGPIO(4, "rising");
    struct pollfd fdset[1];
    for (;;) {

        fdset[0].fd = fd[4];
        fdset[0].events = POLLPRI;
        fdset[0].revents = 0;

        int rc = poll(fdset, 1, 5000);
        if (rc < 0) {
            printf("\npoll() failed!\n");
            return -1;
        }
        if (rc == 0) {
            printf(".");
        }
        if (fdset[0].revents & POLLPRI) {
              lseek(fd[4], 0, SEEK_SET);
              int val=readGPIO(4);
            printf("\npoll() GPIO 4 interrupt occurred %d\n\r",val);

        }
        fflush(stdout);
    }
    return 0;
}

int openGPIO(int gpio, int direction) {
    if (gpio < 0 || gpio > 31) return -1;
    if (direction < 0 || direction > 1)return -2;
    int len;
    char buf[BUFFER_MAX];
    if (fd[gpio] != 0) {
        close(fd[gpio]);
        fd[gpio] = open("/sys/class/gpio/unexport", O_WRONLY);
        len = snprintf(buf, BUFFER_MAX, "%d", gpio);
        write(fd[gpio], buf, len);
        close(fd[gpio]);
        fd[gpio] = 0;
    }

    fd[gpio] = open("/sys/class/gpio/export", O_WRONLY);
    len = snprintf(buf, BUFFER_MAX, "%d", gpio);
    write(fd[gpio], buf, len);
    close(fd[gpio]);

    len = snprintf(buf, BUFFER_MAX, "/sys/class/gpio/gpio%d/direction", gpio);
    fd[gpio] = open(buf, O_WRONLY);
    if (direction == 1) {
        write(fd[gpio], "out", 4);
        close(fd[gpio]);
        len = snprintf(buf, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", gpio);
        fd[gpio] = open(buf, O_WRONLY);

    } else {
        write(fd[gpio], "in", 3);
        close(fd[gpio]);
        len = snprintf(buf, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", gpio);
        fd[gpio] = open(buf, O_RDONLY);
    }
    return 0;
}

int writeGPIO(int gpio, int b) {
    if (b == 0) {
        write(fd[gpio], "0", 1);
    } else {
        write(fd[gpio], "1", 1);
    }

    lseek(fd[gpio], 0, SEEK_SET);
    return 0;
}

int readGPIO(int gpio) {
    char value_str[3];
    int c = read(fd[gpio], value_str, 3);
    lseek(fd[gpio], 0, SEEK_SET);

    if (value_str[0] == '0') {
        return 0;
    } else {
        return 1;
    }

}

int setEdgeGPIO(int gpio, char *edge) {
    char buf[BUFFER_MAX];
    int len = snprintf(buf, BUFFER_MAX, "/sys/class/gpio/gpio%d/edge", gpio);
    int fd = open(buf, O_WRONLY);
    write(fd, edge, strlen(edge) + 1);
    close(fd);
    return 0;
}