Pi IoT In Python Using GPIO Zero - Getting Input
Written by Harry Fairhead & Mike James   
Monday, 03 June 2024
Article Index
Pi IoT In Python Using GPIO Zero - Getting Input
Polling
Interrupts and events
Event Handling

Interrupts are often confused with events but they are very different. An interrupt is a hardware mechanism that stops the computer doing whatever it is currently doing and makes it transfer its attention to running an interrupt handler. You can think of an interrupt as an event flag that, when set, interrupts the current program to run the assigned interrupt handler.

Using interrupts means the outside world decides when the computer should pay attention to input and there is no need for a polling loop. Most hardware people think that interrupts are the solution to everything and polling is inelegant and only to be used when you can’t use an interrupt. This is far from the reality.

There is a general feeling that real-time 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 are probably doing something wrong. So much so that some organizations are convinced that interrupts are so dangerous that they are banned.

Interrupts are only really useful when you have a low-frequency condition that needs to be dealt with on a high-priority basis. Interrupts 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. If you have a polling loop that takes 100ms to poll all inputs and there is an input that demands attention in under 60ms then clearly the polling loop is not going to be good enough. Using an interrupt allows the high priority event to interrupt the polling loop and be processed in less than 100ms. However, if this happens very often the polling loop will cease to work as intended. Notice that an alternative is to simply make the polling loop check the input twice per loop.

For a more real-world 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 stop what it was doing and transfer its attention to the ISR. How good a design this is depends on how much the doorbell 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 one 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 either, 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", consider what else it has 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 fast polling loop. This can be considered to be a software emulation of a hardware interrupt – not to be confused with a software interrupt or trap, which is a hardware interrupt triggered by software. If you are going to use interrupts to service input then a good design is to use the interrupt handler to feed an event queue. This at least lowers the chance that input will be missed.

Despite their attraction, interrupts are usually a poor choice for anything other than low-frequency events that need to be dealt with quickly. 

Asynchronous Buttons

GPIO Zero implements an event-driven approach to asynchronous programming that is presented as if it was an interrupt. You can specify a function to be called when Button is pressed or released using the when_pressed and when_released properties. For example:

from gpiozero import Button
from signal import pause
button = Button(4,bounce_time=0.25)
def pressed():
    print(“Button pressed”)
def released():
    print(“Button released”)
button.when_pressed =  pressed
button.when_released = released
pause()

Notice that you need the pause at the end of the program to stop the program coming to an end. You could just put the program into an infinite loop and do other things while waiting for the button press/release. Also notice that when_pressed and when_released are assigned as references to the functions and the functions are not called until later.

It is important to realize that the pressed and released functions are called asynchronously – i.e. not synchronized with anything happening in the program. This may be true, but Python only allows one thing to happen at a time and there is no parallelism implied by this form of programming. What happens is that a new thread of execution is started and this simply waits until the event occurs. When the event occurs the event handler is run. When the event handler is run the rest of your program is halted. Python is constructed so as to only allow one part of your Python code to execute at any one time. This is the so-called Global Interpreter Lock or GIL and it means you cannot take advantage of multiple cores to run your Python programs in parallel. Instead the operating system chooses which thread to run at any given time. The thread that is waiting for the event is only started if the event it is waiting for has occurred and then it gets a chance to run the event handler. While the event handler is running you can be sure that no other part of the Python program is running.



Last Updated ( Saturday, 08 June 2024 )