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.
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
-
Introducing Pi (paper book only)
-
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.
-
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? -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
SPI MCP3008/4 AtoD (paper book only)
-
Serial (paper book only)
-
Getting On The Web - After All It Is The IoT (paper book only)
-
WiFi (paper book only)
GPIO Input
GPIO input is be a much more difficult problem than output from the point of view of measurement and verification. At least for output you can see the change in the signal on a logic analyzer and know the exact time that it occurred. This makes if possible to track down timing problems and fine tune things with good accuracy.
Input on the other hand is "silent" and unobservable. When did you read in the status of the line? Usually the timing of the read is with respect to some other action that the device has taken. For example, read the input line 20 microseconds after setting the output line high.
The usual rule of thumb is to assume that it takes as long to read a GPIO line as it does to set it. This means we can use the delay mechanisms that we looked at with output in mind for input.
One common and very useful trick when you are trying to get the timing of input correct is to substitute and output command to a spare GPIO line and monitor it with a logic analyzer. Place the output instruction just before the input instruction and where you see the line change on the logic analyzer should be close to the time that the input would be read in the unmodified program. You can use this to debug and fine tune and then remove the output statement.
In some applications the times are long and/or unimportant but in some they are critical.
The Basic Input Functions
We have already met the function that sets a GPIO line to input or output:
void bcm2835_gpio_fsel(uint8_t pin,uint8_t mode)
To set the GPIO line of your choice to input simply use BCM2835_GPIO_FSEL_INPT for the mode.
Once set to input the GPIO line is high impedance, it wont take very much current no matter what you connect it to, and you can read its input state using:
uint8_t bcm2835_gpio_lev(uint8_t pin)
This is all there is to using GPIO line as an input - apart from the details of the electronics and the small matter of interrupts.
Basic Input Circuit - The Switch
One of the most common input circuits is the switch or button. If you want another external button you can use any GPIO line and the following circuit:
The 10K resistor isn't critical in value. It simply pulls the GPIO line high when the switch isn't pressed. When it is pressed a current of a little more than 0.3mA flows in the resistor. If this is too much increase the resistance to 100K or even more - but notice that the higher the resistor value the noisier the input to the GPIO and the more it is susceptible to RF interference.
If you want a switch that pulls the line high instead of low, to reverse the logic just swap the positions of the resistor and the switch in the diagram.
Although the switch is the simplest input device it is also very difficult to get right. When a user clicks a switch of any sort the action isn't clean - the switch bounces. What this means is that the logic level on the GPIO line goes high then low and high and bounces between the two until it settles down. There are electronic ways of debouncing switches but software does the job much better. All you have to do is but a delay of a millisecond or so after detecting a switch press and read the line again - if it is still low then record a switch press. Similarly when the switch is released read the state twice with a delay. You can vary the delay to modify the perceived characteristics of the switch.
A more sophisticated algorithm is based on the idea of integration to debounce a switch. All you have to do is read the state multiple times, every few milliseconds say. and keep a running sum of values. If say you read the switch 10 times then a running sum of 6 to 10 can be taken as an indication that the switch is high and less than this that the switch is low.
The Potential Divider
If you have an input that is outside of the range of 0 to 3.3V then you can reduce it using a simple potential divider.
V is the input from the external logic and Vout it the connection to the GPIO input line:
You can spend a lot of time on working out good values of R1 and R2. For loads that take a lot of current you need R1+R2 to be small and divided in the same ratio as the voltages.
For example for a 5V device R1=18K and R2=33K work well to drop the voltage to 3.3V.
The problem with a resistive divider is that it can round off fast pulses due to the small capacitive effects. This usually isn't a problem, but if it is then the solution is to use an FET buffer again.
Notice that this is an inverting buffer but you can usually ignore this and simply correct in software i.e. read a 1 as a low and a 0 as a high state. The role of R1 is to make sure the FET is off when the 5V signal is absent and R2 limits the current in the FET to about 0.3mA.
In most case you should try the simple voltage divider and only move to an active buffer if it doesn't work.
How Fast Can We Measure?
The simplest way to find out how quickly we can take a measurement is to perform a pulse width measurement using a busy wait. Apply in square wave to GPIO 4 i.e. pin 7 we can measure the time that the pulse is high using:
#include <stdio.h> #include <stdlib.h> #include <bcm2835.h> #include <sched.h> #include <sys/mman.h> int main(int argc, char** argv) { const struct sched_param priority = {1}; sched_setscheduler(0, SCHED_FIFO, &priority); mlockall(MCL_CURRENT | MCL_FUTURE); if (!bcm2835_init()) return 1; bcm2835_gpio_fsel(RPI_GPIO_P1_07, BCM2835_GPIO_FSEL_INPT); volatile int i; while (1) { while (1 == bcm2835_gpio_lev(RPI_GPIO_P1_07)); while (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07)); for (i = 0; i < 5000; i++) { if (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07)) break; } printf("%d\n\r", i); fflush(stdout); } return (EXIT_SUCCESS); }
This might look a little strange at first.
The inner while loops are responsible for getting us to the right point in the waveform. First we loop until the line goes low, then we loop until it goes high again and finally measure how long before it goes low. You might think that we simply have to wait for it to go high and then measure how long till it goes low but this misses the possibility that the signal might be part way though a high period when we first measure it.
If you run this program with different pulse widths the result are very regular:
(y axis loop count x axis pulse time in microseconds)
The equation relating time t and loop count n is:
t=0.12 n -1.28 microseconds
This works down to about 1 microsecond or slightly less.
If you want to record the time in microseconds rather than using a loop count then you could use:
uint64_t t; volatile int i; while (1) { while (1 == bcm2835_gpio_lev(RPI_GPIO_P1_07)); while (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07)); t= bcm2835_st_read(); for (i = 0; i < 5000; i++) { if (0 == bcm2835_gpio_lev(RPI_GPIO_P1_07)) break; } t= bcm2835_st_read()-t; printf("%d,%ld\n\r",i,t); fflush(stdout); }
This is accurate to around can measure down to around 1 microsecond with an accuracy of 0.5 microseconds e.g.
Pulse width | System Time |
1 | 5 |
2.5 | 18.2 |
5 | 40 |
10 | 84 |
25 | 214 |
50 | 440 |
Notice that in either case if you try measuring pulse widths much shorter than the lower limit that works you will get results that look like longer pulses are being applied. The reason is simply that the Pi will miss the first transition to zero but will detect a second or third or later transition. This is the digital equivalent to of the aliasing effect found in the Fourier Transform or general signal processing.
Interrupts Considered Harmful?
There is a general feeling that realtime programming and interrupts go together and if you are not using an interrupt you are probably doing something wrong. In fact the truth is that if you are using an interrupt you probably are doing something wrong. Some organizations agree with the general idea that interrupts are dangerous so much that they are banned from being used at all - and this includes realtime software.
Interrupts are only really useful when you have a low frequency condition that needs to be dealt with on a high priority basis. Their use can simplify the logic of your program but rarely does using an interrupt speed things up because the overhead involved in interrupt handling is usually quite high.
For example suppose you want to react to a doorbell push button. You could write a polling loop that simply checks the button status repeatedly and forever - or you could write an interrupt service routine ISR to respond to the doorbell. The processor would be free to get on with other things until the doorbell was pushed when it would then stop what it was doing and transfer its attention to the ISR.
How good a design this is depends on how much the doorbell press has to interact with the rest of the program and how many doorbell pushes you are expecting. It takes time to respond to the doorbell push and then the ISR has to run to completion - what is going to happen if another doorbell push happens while the first push is still being processed? Some processors have provision for forming a queue of interrupts but it doesn't help with the fact that the process can only handle on interrupt at a time. Of course the same is true of a polling loop but if you can't handle the throughput of events with a polling loop you can't handle it using an interrupt because interrupts add the time to transfer to the ISR and back again.
Finally before you dismiss the idea of having a processor do nothing but ask repeatedly "is the doorbell pressed" - what else has it to do?
If the answer is "not much" then a polling loop might well be your simplest option.
Also if the processor has multiple cores then the fastest way of dealing with any external event is to use one of the cores in a polling loop.
Despite their attraction interrupts are usually a poor choice.
Interrupts And The bcm2816 Library
The bcm2816 library doesn't support interrupts and for all the reasons given
The reason is that Linux doesn't support user mode interrupts. However it is possible to make interrupts work in user mode and this is something explained at the end of this chapter but it involves using SYSFS.
The bcm2816 GPIO hardware has a very sophisticated and impressive list of conditions that you can set to trigger and event. These are all made available via the bcm2816 library as a set of set and clear functions.
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)
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 the high or low level event is triggered as soon as the line is high or low - i.e. perhaps when you enable the event detection. The event also stays set as long as the high or low status persists - trying to reset it has no effect. In practice edge detection is usually the most useful.
There are also two groups that work with 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".
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 there is no way of directly handling such an interrupt in user mode. 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)
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.
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; }
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
-
Introducing Pi (paper book only)
-
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.
-
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? -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
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. -
SPI MCP3008/4 AtoD (paper book only)
-
Serial (paper book only)
-
Getting On The Web - After All It Is The IoT (paper book only)
-
WiFi (paper book only)