|Exploring Edison - Mraa GPIO
|Written by Harry Fairhead
|Monday, 13 July 2015
Page 3 of 4
If you know another programming language then you can think of an interrupt as an event and an interrupt handler as an event handler. In this case however an interrupt is caused by an external event - like an input line changing its value. When the interrupt occurs your specified interrupt handler is called.
You can also set what actually constitutes an event - the input can be edge-triggered from low to high or from high to low or both. There is a predefined enumeration you can use:
To set an interrupt handler you use the function:
The first parameter determines the pin and the second determines what causes the interrupt. The final parameters determine the function called when the interrupt happened and a parameter to pass to the function.
You can also use
to remove the interrupt handler associated with a pin.
The simplest example of interrupt input is:
You can see that the main program doesn't actually do anything once it has setup the input pin and interrupts - it just loops forever. After initializing pin 31 and setting it to input, an interrupt triggered on the falling edge of the input is set. Notice that the final parameters, args, isn't used in this example for simplicity - it is set to a NULL pointer.
The interrupt handler just prints the fact that the switch has been pressed, i.e. a falling edge. If you run this program you should see "switch" printed in the Eclipse Console each time the switch is pressed.
When the switch is released it generates a rising edge and you can generate an interrupt on both edges using
We can use the arg parameter to pass in the details of the pin that caused the interrupt and allow the interrupt handler to read the state of the pin.
Notice that way that arg parameter is passed as a pointer to void - a C idiom for passing a pointer to any data type.
Also notice that we don't have to dereference pin because mraa_gpio_context is already a pointer to struct and the dereference is automatic.
When you run this program you will see the switch state printed each time it changes, i.e. when the switch is pressed and released.
In general it is preferable to use an interrupt to service changes in input state unless you have a very specific reason not to - and the most common reason is that you need to work with very fast changes in the input.
The Truth About Intel Edison Interrupts
If you know something about interrupts in other systems you will probably be very pleased that the Edison provides a per pin interrupt system. However things are not quite what they seem. The pin interrupts are not true interrupts but software simulated interrupts.
What happens when you set an interrupt on a pin in that mraa creates a new thread and starts makes a blocking call to the Linux system call poll. This only returns when there is an event in the SYSFS file system for the file corresponding to the pin you have attached the interrupt handler to.
There are a number of consequences of this implementation.
The first is that the response time isn't a fast as you might expect a hardware based interrupt to be. The polling loop is run on new thread and this is scheduled by Linux in the usual way meaning that it could take milliseconds in the worst case to respond to a change.
The second is that if the event occurs during the call to poll your interrupt handler is called on the thread and this naturally stops any additional interrupts occurring. That is you cannot have the same interrupt during the execution of an interrupt handler - the interrupt is automatically disabled.
The third is that because mraa will only spin up a single thread for each pin and because SYSFS doesn't tell mraa what edge transition occurred you can only associate one interrupt handler per pin.
Also when the interrupt handler is called it is running on a different thread to the main program. This means you cannot use mraa_gpio_isr_exit (pin) to remove an interrupt handler from within the interrupt handler - notice that this would be possible with a real interrupt. The reason is simply that the isr_exit function stops the thread that is running the interrupt and must be run on the main function's thread. The inability to change interrupt handler after an interrupt means you cannot set up a chain of handlers that deal with a rising edge, then a falling edge.
It also means that the parameter you pass to the interrupt handler has to be accessible from another thread and any variable created in the interrupt handler belong to the interrupt thread.
Pulse Width Measurement
Let's finish with an example of measuring a pulse width as this is a common task. In this case the pulse will be generated by a switch as in the last example, but in general the pulse could come from almost anything.
Assume that a falling edge triggers the start of the measurement and a rising edge ends the interval. If we were working with physical interrupts we could try something clever like setting one interrupt handler for the rising edge and another for the falling edge. As only one interrupt handler can be set per pin this isn't possible. Equally we can't set a falling edge interrupt which, when it is called sets a rising edge interrupt handler, because you can't change an interrupt handler from within an interrupt handler.
The only way to make this work is to set up a single interrupt handler that is to set up an interrupt handler for both edge transitions and read the pin to find out which one is occurring. On the falling edge you read the system clock and store the value. On the rising edge you read the system clock again and work out the difference between the two values.
This is quite simple. In the main program we have:
The interrupt handler is a little more complicated. First we get the time that the interrupt handler was entered:
Next we read the pin to discover what transition has occurred and either save the start time or compute the interval:
Notice that while clock_gettime returns a structure with second and nanoseconds the time isn't accurate to the nearest nanosecond.
If you run this program you will discover that it sort of works. Occasionally it will get a negative time because an rising edge occurs before a falling edge.
If you are using a mechanical switch to generate the pulses you will also discover that you get multiple pulses for each press of the switch. The reason for this is switch bounce. When you press a switch it doesn't make contact cleanly - the voltage goes up and down until it settles at the low. And when you release the switch the bounce generates multiple rising edges. The slowness of the interrupt means that you are unlikely to see many pulses due to switch bounce but in the real work you will have to debounce the switch either physically or in the software.
To physically debounce a switch you add a capacitor, but software debounce is very easy - simply add a delay before reading the pin's state to give it time to settle. The only problem is how long to wait? For the switch used here, and for most switches, a usleep(500), i.e. half a millisecond wait, should be enough. Note that in practice the wait is likely to be longer than 500 microseconds because of Linux task scheduling.
|Last Updated ( Thursday, 26 November 2015 )