IO Pi Tutorial 3 - Introducing Interrupts
Using Interrupts with the IO Pi Plus
Created: 20/02/2019 | Last Updated: 22/05/2021
In the IO Pi tutorial 2 we learned how to connect a button and an LED to the IO Pi Plus and check for a button press using the read_pin() method.
In this tutorial we are going to expand on Tutorial 2 by introducing interrupts.
What are interrupts?
In the IO Pi Plus an interrupt is a piece of logic inside the MCP23017 IO controller which continuously monitors the pins on each port. If the pin either changes state or does not match a preconfigured default it will trigger the interrupt.
Once triggered the interrupt logic will record which pin caused the interrupt event and holds that value until you read it or reset the interrupts.
As well as recording the interrupt status internally you can also configure one or both interrupt pins IA and IB to go high or low giving you an electrical indication that an interrupt event has occurred.
In tutorial 2 if a button was pressed and released in the time between the read_pin() method calls the event would go undetected. Interrupts have an advantage that they monitor the inputs continuously so a button press will be recorded by the interrupt logic, setting the status to show that an event occurred. The only time you will miss a button press is when it occurs before the interrupts are reset so you will still need to read the interrupt status at regular intervals.
In this tutorial we will monitor 8 buttons and show their status with 8 LEDs. When a button is pressed the corresponding LED will be turned on for 1 second. For this you will need your Raspberry Pi, an IO Pi Plus, 8 buttons, 8 LEDs and 8 300R resistors. The diagram to the right shows how the components will be connected, with the buttons on Bus 1 and the LEDs on Bus 2.
We will use the AB Electronics UK python library to talk to the IO Pi, to download the library visit our Python Library and Demos knowledge base article.
You will need to enable i2c on your Raspberry Pi, see our other tutorial on i2c: I2C, SMBus and Raspbian Linux
The IO Pi Plus is supplied with Bus 1 on I2C address 0x20 and Bus 2 on 0x21, if you have changed the I2C addresses you will need to update the code below to use the new I2C addresses.
The AB Electronics python library uses another library called python-smbus, you can install it using apt-get with the following commands.
sudo apt-get update
sudo apt-get install python-smbus
With the libraries installed and the Raspberry Pi is configured to use i2c we can get started.
Stage 1 – Configuring the inputs and outputs
The first step will be to configure port 0 for the MCP23017 I/O controller on Bus 1 as inputs with the internal pull-up resistors enabled. The buttons connect the inputs to ground so we will need to invert the inputs making them show as on or 1 when pressed and off or 0 when released.
We will start by creating a new python program file called tutorial3.py. You can use your favourite text editor for writing the program. You can find a complete example of tutorial3.py in the ABElectronics_Python_Libraries/ABElectronics_IOPi/demos folder.
At the top of your program you will need to import the IOPI library and time library.
from IOPi import IOPi
The IOPI library is used for all communication with your IO Pi. It gives you control over almost everything that can be done with the MCP23017 controller.
We will be using both MCP23017 controllers so we will create two instances of the IOPi class called busin and busout.
busin = IOPi(0x20)
busout = IOPi(0x21)
Next, we enable the internal pull-up resistors for port 0 on busin and set the port direction as inputs.
As with tutorial 2 we will invert port 0 so that a button press will register as a 1 instead of 0.
Port 0 on busout will be configured as output pins to drive the LEDs. Set the port direction to be 0x00 to configure all of the pins as outputs and write a value of 0x00 to the port to turn off all of the LEDs.
The inputs and outputs are now working so the next step is to configure the interrupts for the input pins.
Stage 2 – Configuring the Interrupts
Interrupts can be triggered in two ways, on stage change and when the pin does not match a default value.
On state change will trigger the interrupt every time the input changes state from low to high, or high to low. This is useful in many situations where you want to know that the input has changed, but when monitoring for a button press the interrupt would trigger both when the button is pressed and when it is released. For this tutorial we only want to update the LEDs when the button is pressed so we will configure the interrupts to use the other option of triggering when the input does not match a default state.
The default state can be high or low and can be configured for each pin individually. We do this by setting each bit in an 8 bit number to 1 or 0 in the same way as you would when writing the output to a port. The interrupt will need to trigger when the button is pressed and the input registers as going high, or to 1, so we will need to set the default value for the port as 0. We do this with the set_interrupt_defaults(port, value) method.
Next, we need to tell the interrupt logic that we want to use the default value instead of triggering on state change. This is done with the set_interrupt_type(port, value) method. For each pin:
0 = Trigger on state change
1 = Trigger when input does not match the default value
We want each pin on the port to trigger when the input does not match the default value so we will set the port to be 0xFF.
Next, we want to turn the interrupt monitoring on for each pin on port 0. This is done with the set_interrupt_on_port(port, value) method.
The final step before we can start monitoring the interrupts is to reset the interrupt status.
Stage 3 – Monitoring the interrupt status
The interrupts are now configured and monitoring the inputs. When a button is pressed the input will go high and the interrupt with trigger.
To check if an interrupt has triggered, we will need to regularly read the interrupt status. We can do this using a while loop.
The IOPi python library contains a method for checking the interrupt status read_interrupt_status(port). The method returns a value indicating the interrupt status for each pin on the selected port. When an interrupt is triggered the value for that pin will change from 0 to 1.
At this stage we only want to know that a button has been pressed so we can read the interrupt status and if the returned value is not 0 we know that an interrupt has occurred on one of the input pins.
Inside the while loop we will use an if statement to check that the interrupt status is not 0.
if (busin.read_interrupt_status(0) != 0):
When an interrupt occurs the value from the port is captured by the interrupt logic and can be read using the read_interrupt_capture(port) method.
Inside the if statement copy the captured value into a variable called value.
value = busin.read_interrupt_capture(0)
The captured value will contain the button that was pressed. To light an LED for the same pin number on the busout bus we can simply copy the value to the output port using the write_port(port, value) method.
Calling the read_interrupt_capture(port) or read_port(port) methods will reset the interrupts making them ready for the next button press.
The last thing you need to add is a time.sleep(200) outside of the if statement to pause the program for 200ms on each loop.
The sleep method will stop the program from using too much of the computers CPU time while still giving a reasonable response time for any button presses.
Stage 4: Save and run the program
With the program complete you can now save the file and run it with the following command.
If everything works correctly you should see an LED light each time you press a button. The LED should stay on until the next button is pressed.
(images created with Fritzing)