Reading 32 pins
The IO Pi Plus is a 32 channel MCP23017 GPIO expander for the Raspberry Pi
20/12/2019
Posted by:
maxread
I'm building a project that needs to read 32 buttons.
I've modified demo_interruptsthreading.py to create two busses and threads to scan them but it's not working.
Whichever bus I set as bus1 works as expected but bus2 doesn't trigger. Any help would be much appreciated.
Here's the code:
#!/usr/bin/env python
"""
================================================
ABElectronics IO Pi | - IO Interrupts Demo
Requires python smbus to be installed
For Python 2 install with: sudo apt-get install python-smbus
For Python 3 install with: sudo apt-get install python3-smbus
run with: python demo_iointerruptsthreading.py
================================================
This example shows how to use the interrupt methods with threading
on the IO port.
The interrupts will be enabled and set so that pin 1 will trigger INT A and B.
Internal pull-up resistors will be used so grounding
one of the pins will trigger the interrupt
using the read_interrupt_capture or reset_interrupts methods
will reset the interrupts.
"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
import time
import threading
try:
from IOPi import IOPi
except ImportError:
print("Failed to import IOPi from python system path")
print("Importing from parent folder instead")
try:
import sys
sys.path.append('..')
from IOPi import IOPi
except ImportError:
raise ImportError(
"Failed to import library from parent folder")
def callback_function(bus):
"""
Function we want to call from the background_thread function
This function will be called when an interrupt is triggered from
a state change on pin 1
"""
print("interrupt triggered")
if bus.read_pin(1) == 0:
print("pin 1 was set low")
else:
print("pin 1 was set high")
def background_thread1(bus):
"""
Function we want to run in parallel with the main program loop
"""
while 1:
# get the interrupt status for INTA
inta = bus.read_interrupt_status(0)
intb = bus.read_interrupt_status(1)
# reset the interrupts
bus.reset_interrupts()
# check the value of intA to see if an interrupt has occurred
if inta != 0 or intb != 0:
print("Bus1", bus.read_pin(1), bus.read_pin(2), bus.read_pin(3), bus.read_pin(4), bus.read_pin(5), bus.read_pin(6), bus.read_pin(7), bus.read_pin(8), bus.read_pin(9), bus.read_pin(10), bus.read_pin(11), bus.read_pin(12), bus.read_pin(13), bus.read_pin(14), bus.read_pin(15), bus.read_pin(16))
callback_function(bus)
# sleep this thread for 0.5 seconds
time.sleep(0.01)
def background_thread2(bus):
"""
Function we want to run in parallel with the main program loop
"""
while 1:
# get the interrupt status for INTA
inta = bus.read_interrupt_status(0)
intb = bus.read_interrupt_status(1)
# reset the interrupts
bus.reset_interrupts()
# check the value of intA to see if an interrupt has occurred
if inta != 0 or intb != 0:
print("Bus2",bus.read_pin(1), bus.read_pin(2), bus.read_pin(3), bus.read_pin(4), bus.read_pin(5), bus.read_pin(6), bus.read_pin(7), bus.read_pin(8), bus.read_pin(9), bus.read_pin(10), bus.read_pin(11), bus.read_pin(12), bus.read_pin(13), bus.read_pin(14), bus.read_pin(15), bus.read_pin(16))
callback_function(bus)
# sleep this thread for 0.5 seconds
time.sleep(0.01)
def create_bus(address):
# Create an instance of the IOPi class with an I2C address supplied
iobus = IOPi(address)
# Set all pins on the IO bus to be inputs with internal pull-ups enabled.
iobus.set_port_pullups(0, 0xFF)
iobus.set_port_pullups(1, 0xFF)
iobus.set_port_direction(0, 0xFF)
iobus.set_port_direction(1, 0xFF)
# invert the ports so pulling a pin to ground will show as 1 instead of 0
iobus.invert_port(0, 0xFF)
iobus.invert_port(1, 0xFF)
# Set the interrupt polarity to be active high and mirroring enabled, so
# pin 1 will trigger both INT A and INT B when a pin is grounded
iobus.set_interrupt_polarity(1)
iobus.mirror_interrupts(1)
# Set the interrupts default value to 0
iobus.set_interrupt_defaults(0, 0x00)
iobus.set_interrupt_defaults(1, 0x00)
# Set the interrupt type to be 1 for ports A and B so an interrupt is
# fired when a state change occurs
iobus.set_interrupt_type(0, 0x00)
iobus.set_interrupt_type(1, 0x00)
# Enable interrupts for pin 1
iobus.set_interrupt_on_port(0, 0xFF)
iobus.set_interrupt_on_port(1, 0xFF)
return(iobus)
def main():
'''
Main program function
'''
bus1 = create_bus(0x20)
bus2 = create_bus(0x21)
timer1 = threading.Thread(target=background_thread1(bus1))
timer1.daemon = True # set thread to daemon ('ok' won't be printed)
timer1.start()
timer2 = threading.Thread(target=background_thread2(bus2))
timer2.daemon = True # set thread to daemon ('ok' won't be printed)
timer2.start()
while 1:
"""
Do something in the main program loop while the interrupt checking
is carried out in the background
"""
# wait 1 seconds
time.sleep(1)
if __name__ == "__main__":
main()
20/12/2019
Posted by:
andrew
The problem you are having is probably caused by having two threads trying to access the I2C bus at the same time. Only one thread can access the I2C bus at any one time so once the first thread has opened the bus it will be blocking the second thread from accessing it. The best way to work around this issue is to have a single thread accessing both buses.
I have modified your code to use a single background thread which accesses bus1 and bus2.
#!/usr/bin/env python
"""
================================================
ABElectronics IO Pi | - IO Interrupts Demo
Requires python smbus to be installed
For Python 2 install with: sudo apt-get install python-smbus
For Python 3 install with: sudo apt-get install python3-smbus
run with: python demo_iointerruptsthreading.py
================================================
This example shows how to use the interrupt methods with threading
on the IO port.
The interrupts will be enabled and set so that pin 1 will trigger INT A and B.
Internal pull-up resistors will be used so grounding
one of the pins will trigger the interrupt
using the read_interrupt_capture or reset_interrupts methods
will reset the interrupts.
"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
import time
import threading
try:
from IOPi import IOPi
except ImportError:
print("Failed to import IOPi from python system path")
print("Importing from parent folder instead")
try:
import sys
sys.path.append('..')
from IOPi import IOPi
except ImportError:
raise ImportError(
"Failed to import library from parent folder")
def callback_function(bus):
"""
Function we want to call from the background_thread function
This function will be called when an interrupt is triggered from
a state change on pin 1
"""
print("interrupt triggered")
if bus.read_pin(1) == 0:
print("pin 1 was set low")
else:
print("pin 1 was set high")
def background_thread1(t_bus1, t_bus2):
"""
Function we want to run in parallel with the main program loop
"""
while 1:
# get the interrupt status for INTA on bus 1
int1a = t_bus1.read_interrupt_status(0)
int1b = t_bus1.read_interrupt_status(1)
# reset the interrupts
t_bus1.reset_interrupts()
# get the interrupt status for INTA on bus 1
int2a = t_bus2.read_interrupt_status(0)
int2b = t_bus2.read_interrupt_status(1)
# reset the interrupts
t_bus2.reset_interrupts()
# check the value of int1A to see if an interrupt has occurred
if int1a != 0 or int1b != 0:
print("Bus1", t_bus1.read_pin(1), t_bus1.read_pin(2), t_bus1.read_pin(3), t_bus1.read_pin(4), t_bus1.read_pin(5), t_bus1.read_pin(6), t_bus1.read_pin(7), t_bus1.read_pin(8), t_bus1.read_pin(9), t_bus1.read_pin(10), t_bus1.read_pin(11), t_bus1.read_pin(12), t_bus1.read_pin(13), t_bus1.read_pin(14), t_bus1.read_pin(15), t_bus1.read_pin(16))
# check the value of int1A to see if an interrupt has occurred
if int2a != 0 or int2b != 0:
print("Bus2", t_bus2.read_pin(1), t_bus2.read_pin(2), t_bus2.read_pin(3), t_bus2.read_pin(4), t_bus2.read_pin(5), t_bus2.read_pin(6), t_bus2.read_pin(7), t_bus2.read_pin(8), t_bus2.read_pin(9), t_bus2.read_pin(10), t_bus2.read_pin(11), t_bus2.read_pin(12), t_bus2.read_pin(13), t_bus2.read_pin(14), t_bus2.read_pin(15), t_bus2.read_pin(16))
# sleep this thread for 0.5 seconds
time.sleep(0.01)
def create_bus(address):
# Create an instance of the IOPi class with an I2C address supplied
iobus = IOPi(address)
# Set all pins on the IO bus to be inputs with internal pull-ups enabled.
iobus.set_port_pullups(0, 0xFF)
iobus.set_port_pullups(1, 0xFF)
iobus.set_port_direction(0, 0xFF)
iobus.set_port_direction(1, 0xFF)
# invert the ports so pulling a pin to ground will show as 1 instead of 0
iobus.invert_port(0, 0xFF)
iobus.invert_port(1, 0xFF)
# Set the interrupt polarity to be active high and mirroring enabled, so
# pin 1 will trigger both INT A and INT B when a pin is grounded
iobus.set_interrupt_polarity(1)
iobus.mirror_interrupts(1)
# Set the interrupts default value to 0
iobus.set_interrupt_defaults(0, 0x00)
iobus.set_interrupt_defaults(1, 0x00)
# Set the interrupt type to be 1 for ports A and B so an interrupt is
# fired when a state change occurs
iobus.set_interrupt_type(0, 0x00)
iobus.set_interrupt_type(1, 0x00)
# Enable interrupts for pin 1
iobus.set_interrupt_on_port(0, 0xFF)
iobus.set_interrupt_on_port(1, 0xFF)
return(iobus)
def main():
'''
Main program function
'''
bus1 = create_bus(0x20)
bus2 = create_bus(0x21)
timer1 = threading.Thread(target=background_thread1(bus1, bus2))
timer1.daemon = True # set thread to daemon ('ok' won't be printed)
timer1.start()
while 1:
"""
Do something in the main program loop while the interrupt checking
is carried out in the background
"""
# wait 1 seconds
time.sleep(1)
if __name__ == "__main__":
main()
You can also reduce the number of I2C reads and improve the performance of your code by using the read_port() method instead of read_pin.
# check the value of int1A to see if an interrupt has occurred
if int1a != 0 or int1b != 0:
print("Bus1 Port 0 ", t_bus1.read_port(0))
print("Bus1 Port 1 ", t_bus1.read_port(1))
# check the value of int1A to see if an interrupt has occurred
if int2a != 0 or int2b != 0:
print("Bus2 Port 0 ", t_bus2.read_port(0))
print("Bus2 Port 1 ", t_bus2.read_port(1))
The above code will return an 8-bit value between 0 and 255 for each port. Each bit within the value represents one of the pins so for example if pin 3 is high it will return 4 or if pin 7 is high it will return 64.
23/12/2019
Posted by:
maxread
Cheers, Max.
30/12/2019
Posted by:
maxread
I now have a couple of IOPis connected to 22 pushbuttons, each with 22 LEDs; one reads the buttons, the other illuminates them.
It's ALMOST working perfectly, but there's a slight problem: sometimes a button press is ignored.
I can be certain that it's not the buttons that are faulty, as sometimes the interrupt is triggered when the button is released, but not when it is pressed. Occasionally, neither the press nor the release trigger an interrupt.
It's strange, as sometimes it will read 20-30 presses without missing a single one and then will miss several presses in a row.
This happens using the code above with your enhancement.
There's nothing else running on the Pi.
Again, any help would be much appreciated. I have several other devices like this to build once this one is working OK.
Thanks, Max.
30/12/2019
Posted by:
andrew
Try changing the interrupt type so that it fires only when the input does not match the default value.
iobus.set_interrupt_type(0, 0xFF)
iobus.set_interrupt_type(1, 0xFF)
At the moment the code will fire an interrupt when the state changes so it will fire when the button is pressed and when it is released. By changing the interrupt type it should only fire when the button is pressed which may fix your problem.
You may also need to change the default value to 0xFF instead of 0x00 if you find that it is only firing on the release instead of the press.
iobus.set_interrupt_defaults(0, 0xFF)
iobus.set_interrupt_defaults(1, 0xFF)
31/12/2019
Posted by:
maxread
Thanks for the reply. That didn't help, sadly.
I've tried various things. In the end, I increased the i2c bus speed and that seems to have almost eliminated the missed presses.
In the /boot/config.txt I changed the line
dtparam=i2c_arm=on
to
dtparam=i2c_arm=on,i2c_arm_baudrate=400000
31/12/2019
Posted by:
andrew
You could try removing the time.sleep(0.01) from the background thread and see if that helps.
One possible cause for the problem could be the switch bouncing. If the switch bounces and makes the software think that it has been pressed several times instead of once that could possibly cause the interrupt to not reset properly which means the next button press could be missed.
How frequently will the buttons be pressed on the final project? If there is going to be more than a 100ms gap between button presses then you could add a short delay before resetting the interrupts which would add a form of software debouncing.
In the code below for the background thread function I have increased the time.sleep to 50ms and moved the interrupt reset to after the delay. This should add a software debounce and may fix the issue.
def background_thread1(t_bus1, t_bus2):
"""
Function we want to run in parallel with the main program loop
"""
while 1:
# get the interrupt status for INTA on bus 1
int1a = t_bus1.read_interrupt_status(0)
int1b = t_bus1.read_interrupt_status(1)
# get the interrupt status for INTA on bus 1
int2a = t_bus2.read_interrupt_status(0)
int2b = t_bus2.read_interrupt_status(1)
# check the value of int1A to see if an interrupt has occurred
if int1a != 0 or int1b != 0:
print("Bus1", t_bus1.read_pin(1), t_bus1.read_pin(2), t_bus1.read_pin(3), t_bus1.read_pin(4), t_bus1.read_pin(5), t_bus1.read_pin(6), t_bus1.read_pin(7), t_bus1.read_pin(8), t_bus1.read_pin(9), t_bus1.read_pin(10), t_bus1.read_pin(11), t_bus1.read_pin(12), t_bus1.read_pin(13), t_bus1.read_pin(14), t_bus1.read_pin(15), t_bus1.read_pin(16))
# check the value of int1A to see if an interrupt has occurred
if int2a != 0 or int2b != 0:
print("Bus2", t_bus2.read_pin(1), t_bus2.read_pin(2), t_bus2.read_pin(3), t_bus2.read_pin(4), t_bus2.read_pin(5), t_bus2.read_pin(6), t_bus2.read_pin(7), t_bus2.read_pin(8), t_bus2.read_pin(9), t_bus2.read_pin(10), t_bus2.read_pin(11), t_bus2.read_pin(12), t_bus2.read_pin(13), t_bus2.read_pin(14), t_bus2.read_pin(15), t_bus2.read_pin(16))
# sleep this thread for 50ms
time.sleep(0.05)
# reset the interrupts
t_bus1.reset_interrupts()
t_bus2.reset_interrupts()
Note: documents in Portable Document Format (PDF) require Adobe Acrobat Reader 5.0 or higher to view.
Download Adobe Acrobat Reader or other PDF reading software for your computer or mobile device.