Module freeze when IO pin is activated repeatedly at small interval
The IO Pi Plus is a 32 channel MCP23017 GPIO expander for the Raspberry Pi
21/09/2020
Posted by:
gthendean
I am using IO Pi Zero for 16 channels.
Following the tutorial4.py example but modified it slightly for 16 channels sand only connecting pin1 and pin9.
The code is shown below.
The modules works nicely when the button (1 or 9) is pushed at larger interval.
In this case, until after the biz logic is completed and the ready print statement
is shown on the console. [print("ready and waiting for next button push...")]
The issue occurred when the button was pushed at small interval continuously.
It did not take very long before the module stopped responding to button pushed.
I used the same resistor (1K) for the INT-A voltage divider.
Measuring the voltage on the board on normal operation:
When the button is not pushed about 2.5V.
When pushed it's 0.0V as expected.
When the module was locked up, the voltage did not recover to 2.5V after releasing
the button. It stayed at 0.0V.
Any suggestions will be appreciated.
Thanks,
Gabe
def init_hw():
global bus
bus = IOPi(0x20)
bus.set_port_direction(0, 0xFF)
bus.set_port_direction(1, 0xFF)
bus.set_port_pullups(0, 0xFF)
bus.set_port_pullups(1, 0xFF)
bus.invert_port(0, 0xFE)
bus.invert_port(1, 0xFE)
bus.set_interrupt_polarity(0)
bus.mirror_interrupts(1)
bus.set_interrupt_defaults(0, 0x00)
bus.set_interrupt_defaults(1, 0x00)
bus.set_interrupt_type(0, 0xFF)
bus.set_interrupt_type(1, 0xFF)
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
subscribe_interrupt_event()
def subscribe_interrupt_event():
bus.set_interrupt_defaults(0, INTERRUPT_DEFAULT)
bus.set_interrupt_defaults(1, INTERRUPT_DEFAULT)
bus.set_interrupt_on_port(0, 0x01)
bus.set_interrupt_on_port(1, 0x01)
bus.reset_interrupts()
GPIO.add_event_detect(12, GPIO.FALLING, callback=button_pressed)
def unsubscribe_interrupt_event():
bus.set_interrupt_on_port(0, 0x00)
bus.set_interrupt_on_port(1, 0x00)
bus.reset_interrupts()
# temporary unsubscribe to event
GPIO.remove_event_detect(PIN_INTERRUPT)
def button_pressed(pin_interrupt):
global bus
intval_p0 = bus.read_interrupt_capture(0)
intval_p1 = bus.read_interrupt_capture(1)
intval_status_p0 = bus.read_interrupt_status(0)
intval_status_p1 = bus.read_interrupt_status(1)
p0_read = bus.read_port(0)
p1_read = bus.read_port(1)
while (intval_p0 == p0_read and intval_p1 == p1_read):
print("sleep to wait for changes in port value...")
time.sleep(0.2)
p0_read = bus.read_port(0)
p1_read = bus.read_port(1)
# unsubscribe to event
unsubscribe_interrupt_event()
# do biz logic here then sleep
time.sleep(1)
subscribe_interrupt_event()
bus.reset_interrupts()
print("ready and waiting for next button push...")
21/09/2020
Posted by:
andrew
After looking through your code I think I know what may be going wrong.
In the button_pressed function, you are reading the interrupt capture and status at the start which has the effect of resetting the interrupts. This means that if the interrupt is triggered again before the button_pressed function finishes running a second instance of button_pressed is started which tries to talk to the IO Pi at the same time as the first instance, causing an I2C bus conflict.
You will need to add a way of stopping more than one instance of button_pressed from running. Possibly some sort of is_waiting variable or function which checks to see if the previous instance of the button_pressed function has finished before starting a new one.
22/09/2020
Posted by:
gthendean
Thank you very much for your help. Just want to share my findings.
First, just want to mentioned that I use interrupt mirror to detect both interrupts IA & IB with one GPIO pin. I don't think it's related to the issue.
Per your guidance, I rearanged the code a bit and implemented the inProcess flag in the button_pressed to stop more than one instance of button_pressed from running; or more specifically to prevent accessing the IO Pi at the same time as the first instance,
causing an I2C bus conflict.
That solved the problem partially but it still occurred when I pressed the button at a very high frequency. From trial and error, I found that the problem was not reproducable when I set the sleep time in the button_pressed function for "simulating biz logic" to 3 secs or larger. Or looping for 10 secs to simulate work load. Without the looping and sleep of 1 sec, the locking still occurred.
I am wondering if I should try python lock.
I should be able to live with the current approach since my biz logic will be long running and greater than 5 secs.
Thanks,
Gabe
def button_pressed(pin_interrupt):
global bus, inProcess
if inProcess:
return
else:
inProcess = True
unsubscribe_interrupt_event()
intval_p0 = bus.read_interrupt_capture(0)
intval_p1 = bus.read_interrupt_capture(1)
intval_status_p0 = bus.read_interrupt_status(0)
intval_status_p1 = bus.read_interrupt_status(1)
p0_read = bus.read_port(0)
p1_read = bus.read_port(1)
while (intval_p0 == p0_read and intval_p1 == p1_read):
print("sleep to wait for changes in port value...")
time.sleep(0.2)
p0_read = bus.read_port(0)
p1_read = bus.read_port(1)
# do biz logic by simulating with sleep & loop
i = 1
while i<5000:
time.sleep(0.002)
i = i+1
time.sleep(5)
subscribe_interrupt_event()
inProcess = False
print("ready and waiting for next button push...")
def unsubscribe_interrupt_event():
GPIO.remove_event_detect(PIN_INTERRUPT)
bus.set_interrupt_on_port(0, 0x00)
bus.set_interrupt_on_port(1, 0x00)
bus.reset_interrupts()
def subscribe_interrupt_event():
bus.set_interrupt_defaults(0, INTERRUPT_DEFAULT)
bus.set_interrupt_defaults(1, INTERRUPT_DEFAULT)
bus.set_interrupt_on_port(0, 0x01)
bus.set_interrupt_on_port(1, 0x01)
bus.reset_interrupts()
GPIO.add_event_detect(12, GPIO.FALLING, callback=button_pressed)
22/09/2020
Posted by:
andrew
Unless your interrupt defaults are changing you should be able to move the bus interrupt setup into a startup function which is run when the program initialises. The interrupts on the IO Pi should only fire once they have been reset so you don't need to turn them off and on each time.
One thing you could try is to put the p0_read and p1_read variables onto a stack and move the biz logic into a separate thread. That way the biz logic thread can monitor the stack and process data as it is pushed onto the stack. If more interrupt events occur while the biz logic thread is running they will be pushed onto the stack ready for when the biz logic is finished.
23/09/2020
Posted by:
gthendean
I resorted to Python lock and rearranged the code a bit based on your suggestions. Much simpler code. I believe it works reliably now such that I can press the button rapidly in successions without locking up.
I did not have to create stack because for my use case, I do not need to process the interrupts that happen while already processing one. I only need to process one interrupt at a time.
The working code is shown below for the benefit of the community. We will be using IO Pi zero in many of our projects.
Thank you so much for your help.
Gabe
from threading import Lock
lock = Lock()
inProcess = False
def init_hw():
global bus
bus = IOPi(0x20)
lock.acquire()
bus.set_port_direction(0, 0xFF)
bus.set_port_direction(1, 0xFF)
bus.set_port_pullups(0, 0xFF)
bus.set_port_pullups(1, 0xFF)
bus.invert_port(0, 0xFE)
bus.invert_port(1, 0xFE)
bus.set_interrupt_polarity(0)
bus.mirror_interrupts(1)
bus.set_interrupt_defaults(0, 0x00)
bus.set_interrupt_defaults(1, 0x00)
bus.set_interrupt_type(0, 0xFF)
bus.set_interrupt_type(1, 0xFF)
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
GPIO.add_event_detect(PIN_INTERRUPT, GPIO.FALLING, callback=button_pressed)
bus.set_interrupt_on_port(0, 0x01)
bus.set_interrupt_on_port(1, 0x01)
bus.reset_interrupts()
lock.release()
def button_pressed(pin_interrupt):
global bus, inProcess
if inProcess:
lock.acquire()
bus.reset_interrupts()
lock.release()
return
else:
inProcess = True
lock.acquire()
intval_p0 = bus.read_interrupt_capture(0)
intval_p1 = bus.read_interrupt_capture(1)
intval_status_p0 = bus.read_interrupt_status(0)
intval_status_p1 = bus.read_interrupt_status(1)
p0_read = bus.read_port(0)
p1_read = bus.read_port(1)
lock.release()
# while loop to wait for the button to be released;
# determine which button was pushed
# see tutorial4.py
# [...]
# do biz logic here then sleep
time.sleep(1)
inProcess = False
print("ready and waiting for next button push...")
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.