Article Index

An Interrupt Function

Note: This is advanced and something you can skip unless you need to use it. 

The poll function gets you as close to an interrupt handler as you can get in user mode but there is one thing missing - the thread is suspended while waiting for the interrupt. This is generally not what you want to happen. 

You can create a better approximation to a true interrupt by running the poll function on another thread. That is if the main program wants to work with an interrupt you have to define an interrupt handling function that will be called when the interrupt occurs. Next you have to create a new thread that sets everything up and then calls poll on the file descriptor. This causes your new thread to be suspended but you don't' care because its only purpose in life is to wait for the interrupt - your programs main thread continues to run and do useful work. When the interrupt occurs the new thread is restarted and it checks that the interrupt was correct and then calls your interrupt handler. When the interrupt handler completes the thread cleans up and calls poll again to wait for another interrupt.

This is all very straightforward conceptually but it does mean using thread and this is an advanced technique that tends to make programs more difficult to debug and prone to esoteric errors. This is in the nature of using interrupts however. 

In the following program the basic skeleton of an interrupt handling framework is developed - without error checking or error recovery. If you are going to use this sort of approach in the real world you would have to add code that handles what happens when something goes wrong - this code works when everything goes right.

First we need to include the pthreads library and this isn't just a matter of adding an include.

#include <pthread.h>

You also have to specify the name of the library that you want the linker to add to your program. To do this right click on the project and select properties. Select Build,Linker in the dialog box that appears, click the three dots in the Libraries section, click Add Library and specify pthread. Don't make the mistake of trying to add a Library File. 

To make the idea work we need two new functions. One to create a new thread and run the second which sets up the interrupt and waits using poll. 

The first is called attachGPIO because it attaches a specified GPIO line, edge event and interrupt handler. 

int attachGPIO(int gpio, char *edge, eventHandler func) {
    openGPIO(gpio, 0);
    setEdgeGPIO(gpio, edge);
    readGPIO(gpio);
    intData.fd = fd[gpio];
    intData.gpio=gpio;
    intData.func = func;
    pthread_t intThread;
    if (pthread_create(&intThread, NULL, waitInterrupt, (void*) &intData)) {
        fprintf(stderr, "Error creating thread\n");
        return 1;
    }
    return 0;
}

The first part of the function sets up the specified GPIO as an input and sets the edge event you want to respond to. I then does a read to clear any interrupts. Then it creates a new thread using the second function waitInterrupt - which waits for the interrupt and calls the interrupt function passed as the third parameter as a function pointer. To make this work we need to define the eventHander type:

typedef void (*eventHandler)();

Which simply defines the function used for the even handler as having no result and no input parameters.

We also need to pass some data to the waitInterrupt function. The pthread_create function lets you do this but only by passing a single pointer to void - i.e. any data type. We need to pass the file descriptor and the interrupt function to call to waitInterrupt so we have to pack them into a struct. What is more this struct has to be available after the attachGPIO function has terminated - the new thread keeps waitInterrupt running long after attachGPIO has completed. The correct solution is to get the function to create the struct on the heap but a simpler and workable solution is to create it as a global variable which lives for the entire life of the program:

typedef struct {
    int fd;
    int gpio;
    eventHandler func;
} intVec;

intVec intData;

It is this structure that is passed to waitInterrupt with the file descriptor and function pointer.

Next we have to write waitInterrupt:

void *waitInterrupt(void *arg) {

    intVec *intData = (intVec*) arg;
    int gpio=intData->gpio;
    struct pollfd fdset[1];
    fdset[0].fd = intData->fd;
    fdset[0].events = POLLPRI;
    fdset[0].revents = 0;
    for (;;) {
        int rc = poll(fdset, 1, -1);
        if (fdset[0].revents & POLLPRI) {
            intData->func();
            lseek(fdset[0].fd, 0, SEEK_SET);
            readGPIO(gpio);
        }
    }
    pthread_exit(0);
}

 

This unpacks the data passed to it using the arg pointer into a pollfd struct as before. Then it repeatedly calls poll which suspends the thread until the interrupt occurs. Then it wakes up and checks that it was the correct interrupt and if so it calls the interrupt routine and when this has finished resets the interrupt. 

To try this out we need a main program and an interrupt function. The interrupt function simply counts the number of times it has been called:

static int count;
void myIntHandler() {  
    count++;
};

The main program to test this is something like:

int main(int argc, char** argv) {
    attachGPIO(4, "both", myIntHandler);
   for (;;) {
     printf("Interrupt %d\n\r",count);
     fflush(stdout);       
    };
    return 0;
}

It simply attaches the handler to the GPIO line and then prints the count variable in an infinite loop. If you run the program you will see count increment every time there is an interrupt. 

Notice the count is incremented while the main program repeatedly prints the count - the use of a second thread really does let the main program get on with other work.

It is often argued that this approach to interrupts is second class but if you think about how this threaded used of poll works then you have to conclude that it provides all of the features of an interrupt. The interrupt routine is idle and not consuming resources until the interrupt happens when it is activated and starts running. This is how a traditional interrupt routine behaves. There might even be advantages in a a multicore system as the interrupt thread could be scheduled on a different core from the main program and hence run concurrently. This also might be a disadvantage if you are not happy about making sure that the result is a well behaved system.

There are some disadvantages of this approach. The main one is that the interrupt routine is run on a different thread and this can cause problems with code that isn't thread safe - UI components for example. It also more difficult to organize interrupts on multiple GPIO lines. It is generally said that you need one thread per GPIO line but in practice a single thread can wait on any number of file descriptors and hence GPIO lines. A full general implementation as part of the bcm2816  library say would need functions to add and remove GPIO lines and interrupt handlers as well as the routine that just adds an interrupt handler.

Finally the big problem with this approach to interrupts is speed. 

Let's find out how much overhead is inherent in using this approach to interrupts by repeating the pulse width measurement. This time we can't simply print the results as this would stop the interrupt handling. As a compromise we save 20 readings in an array and then print them. It is also important to keep the interrupt handling routines short as how long they take to complete. If the interrupt handling routine takes longer then the pulse width that can be measured is longer. 

 

uint64_t t[20];
static volatile int count = 0;
void myIntHandler() {
        t[count++]=bcm2835_st_read();
 };

int main(int argc, char** argv) {
    if (!bcm2835_init())
        return 1;
    attachGPIO(4, "both", myIntHandler);
    for (;;) {
        if (count >= 20)break;
    };
    int i;
    for (i = 1; i < 19; i++) {
        printf("%llo\n\r", (t[i + 1] - t[i])/1000);
    }
    fflush(stdout);
    return 0;
}

     

Notice we have interrupt handler called when there is a rising edge and a falling edge. 

This records reasonably accurate times for pulses longer than 100 milliseconds on both the Pi Zero and Pi 2. This makes this approach to interrupts suitable for infrequent non-urgent events and not fast protocols or high priority tasks. The very slow timing is most probably due to the time to stop and restart the thread coupled with a slow processing of the hardware interrupt. Even so 100 milliseconds is very slow and there might be a scheduling or some other tweek that would make it faster. Using FIFO scheduling doesn't seem to help - see chapter 6.

At the moment polling - real polling is faster. In fact it would be faster to use the second thread to poll the GPIO state and run the interrupt handler directly without the help of SYSFS and its interrupt facilities. For a multicore Pi this would be very similar to an interrupt handler. 

Where Next?

Now that we have explored many of the ideas in using the GPIO lines for output and input the next question is can we do better by access the hardware directly. 

 

 

 

 

Now On Sale!

You can now buy a print or ebook edition of Raspberry Pi IoT in C from Amazon.

 

For Errata and Listings Visit: IO Press

 

 

This our ebook on using the Raspberry Pi to implement IoT devices using the C programming language. The full contents can be seen below. Notice this is a first draft and a work in progress. 

Chapter List

  1. Introducing Pi (paper book only)

  2. Getting Started With NetBeans In this chapter we look at why C is a good language to work in when you are creating programs for the IoT and how to get started using NetBeans. Of course this is where Hello C World makes an appearance.

  3. First Steps With The GPIO
    The bcm2835C library is the easiest way to get in touch with the Pi's GPIO lines. In this chapter we take a look at the basic operations involved in using the GPIO lines with an emphasis on output. How fast can you change a GPIO line, how do you generate pulses of a given duration and how can you change multiple lines in sync with each other? 

  4. GPIO The SYSFS Way
    There is a Linux-based approach to working with GPIO lines and serial buses that is worth knowing about because it provides an alternative to using the bcm2835 library. Sometimes you need this because you are working in a language for which direct access to memory isn't available. It is also the only way to make interrupts available in a C program.

  5. Input and Interrupts
    There is no doubt that input is more difficult than output. When you need to drive a line high or low you are in command of when it happens but input is in the hands of the outside world. If your program isn't ready to read the input or if it reads it at the wrong time then things just don't work. What is worse is that you have no idea what your program was doing relative to the event you are trying to capture - welcome to the world of input.

  6. Memory Mapped I/O
    The bcm2835 library uses direct memory access to the GPIO and other peripherals. In this chapter we look at how this works. You don't need to know this but if you need to modify the library or access features that the library doesn't expose this is the way to go. 

  7. Near Realtime Linux
    You can write real time programs using standard Linux as long as you know how to control scheduling. In fact it turns out to be relatively easy and it enables the Raspberry Pi to do things you might not think it capable of. There are also some surprising differences between the one and quad core Pis that make you think again about real time Linux programming.

  8. PWM
    One way around the problem of getting a fast response from a microcontroller is to move the problem away from the processor. In the case of the Pi's processor there are some builtin devices that can use GPIO lines to implement protocols without the CPU being involved. In this chapter we take a close look at pulse width modulation PWM including, sound, driving LEDs and servos.

  9. I2C Temperature Measurement
    The I2C bus is one of the most useful ways of connecting moderately sophisticated sensors and peripherals to the any processor. The only problem is that it can seem like a nightmare confusion of hardware, low level interaction and high level software. There are few general introductions to the subject because at first sight every I2C device is different, but here we present one.

  10. A Custom Protocol - The DHT11/22
    In this chapter we make use of all of the ideas introduced in earlier chapters to create a raw interface with the low cost DHT11/22 temperature and humidity sensor. It is an exercise in implementing a custom protocol directly in C. 

  11. One Wire Bus Basics
    The Raspberry Pi is fast enough to be used to directly interface to 1-Wire bus without the need for drivers. The advantages of programming our own 1-wire bus protocol is that it doesn't depend on the uncertainties of a Linux driver.

  12. iButtons
    If you haven't discovered iButtons then you are going to find of lots of uses for them. At its simples an iButton is an electronic key providing a unique coce stored in its ROM which can be used to unlock or simply record the presence of a particular button. What is good news is that they are easy to interface to a Pi. 

  13. The DS18B20
    Using the software developed in previous chapters we show how to connect and use the very popular DS18B20 temperature sensor without the need for external drivers. 

  14. The Multidrop 1-wire bus
    Some times it it just easier from the point of view of hardware to connect a set of 1-wire devices to the same GPIO line but this makes the software more complex. Find out how to discover what devices are present on a multi-drop bus and how to select the one you want to work with.

  15. SPI Bus
    The SPI bus can be something of a problem because it doesn't have a well defined standard that every device conforms to. Even so if you only want to work with one specific device it is usually easy to find a configuration that works - as long as you understand what the possibilities are. 

  16. SPI MCP3008/4 AtoD  (paper book only)

  17. Serial (paper book only)

  18. Getting On The Web - After All It Is The IoT (paper book only)

  19. WiFi (paper book only)

 

 

comments powered by Disqus