Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
yogotech
Posts: 15
Joined: Tue Feb 25, 2020 5:11 am

Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by yogotech » Tue Apr 28, 2020 3:35 am

Background: I'm writing code in MicroPython to process data from a sensor that interrupts pretty quickly. However, I'm getting strange behavior in the ISR. I'm running the code on an ESP32, which if I write similar code in C works fine. Also similarly written C code works pretty well on a bone stock Arduino Uno, so the HW is more than capable of handling the interrupt load. However, I want to write it in Python. :D

Code: Select all

from machine import Pin, disable_irq, enable_irq
import micropython
from utime import sleep

def isr(p):
    state = disable_irq()
    pin_state = p.value()
    if pin_state != p.value():  # XXX - I'm reading the value of the pin immediately, and a large percentage of the time the value is different
        print("<Boo>", end="")
    enable_irq(state)


def main():
    micropython.alloc_emergency_exception_buf(100)
    sensor = Pin(25, Pin.IN)
    sensor.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=isr)

    while True:
        sleep(1)
        print(".", end="")

if __name__ == "__main__":
    main()
Is there a way to read what the value of the pin was AT THE TIME the isr routine was called. Obviously, I tried disabling interrupts, but that didn't seem to make any difference at all, and I know that calling print in an ISR is very bad, but it was the only way I could easily demonstrate the issue. Also, it does seem to behave slightly better by NOT disabling interrupts, but it's still not good enough to actually get the value of the pin.

Am I asking too much to use MicroPython to measure how long a digital pin is held HIGH/LOW? A very simplified version of what I'm trying to do is below (error-handling and other biz-logic removed).

Code: Select all

import array
from machine import Pin
import micropython
from utime import sleep, ticks_us, ticks_diff

NUM_VALUES = 128

data_to_process = False
prev_time = ticks_us()
values = array.array('i', (0 for _ in range(NUM_VALUES)))
value_idx = 0

def isr(p):
    global data_to_process, prev_time, values, value_idx
    curr_time = ticks_us()

    # Only capture new data if we've processed the old data
    if not data_to_process
        len = ticks_diff(curr_time, prev_time)

        # Capture if the value was HIGH/LOW
        if p.value() == 0:
            len *= -1
        # There's a bit more biz-logic here, but it's pretty simple (comparisons)
        values[value_idx] = len
        value_idx += 1
        if value_idx == NUM_VALUES:
            data_to_process = True

    prev_time = curr_time

def process():
    global data_to_process, values, value_idx
    if not data_to_process:
        return

    # Will be smarter in the real code
    for i in range(len(values)):
        print("c[{}]={}".format(i, values[i]))
    print("")

    # Reset the data so we can capture more!
    value_idx = 0
    data_to_process = False

def main():
    micropython.alloc_emergency_exception_buf(100)
    sensor = Pin(25, Pin.IN)
    sensor.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=isr)

    while True:
        sleep(1)
        process()

if __name__ == "__main__":
    main()

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by pythoncoder » Tue Apr 28, 2020 7:28 am

Given that you interrupt on each edge the pin state will alternate with each call to the ISR. If you know the pin state before the first interrupt occurs, you can therefore deduce it:

Code: Select all

pin_state = 1  # State before 1st interrupt occurs
def isr(p):
    global pin_state
    pin_state ^= 1  # An interrupt has occurred so it must have changed
Peter Hinch
Index to my micropython libraries.

yogotech
Posts: 15
Joined: Tue Feb 25, 2020 5:11 am

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by yogotech » Tue Apr 28, 2020 3:45 pm

pythoncoder wrote:
Tue Apr 28, 2020 7:28 am
Given that you interrupt on each edge the pin state will alternate with each call to the ISR. If you know the pin state before the first interrupt occurs, you can therefore deduce it:

Code: Select all

pin_state = 1  # State before 1st interrupt occurs
def isr(p):
    global pin_state
    pin_state ^= 1  # An interrupt has occurred so it must have changed
That assumes that each state change runs the ISR to completion before the next state change, which given the fact that the pin value changes during he ISR, I'm not sure is a guarantee.

yogotech
Posts: 15
Joined: Tue Feb 25, 2020 5:11 am

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by yogotech » Tue Apr 28, 2020 3:53 pm

I tried the following, which seems to work. I sometimes get a couple of errors for the first couple of runs, but that's expected as I'm guessing the initial state, and I only have a 50% chance of guessing right. :)

Code: Select all

from machine import Pin
import micropython
from utime import sleep

# Guess, will be wrong at startup half of the time
pin_state = False

def rising_isr(p):
    isr(True)

def falling_isr(p):
    isr(False)

def isr(state):
    global pin_state

    if pin_state != state:
        print("<Boo>", end="")
    pin_state = state

def main():
    micropython.alloc_emergency_exception_buf(100)
    sensor = Pin(25, Pin.IN)
    sensor.irq(handler=rising_isr, trigger=Pin.IRQ_RISING)
    sensor.irq(handler=falling_isr, trigger=Pin.IRQ_FALLING)

    while True:
        sleep(1)
        print(".", end="")

if __name__ == "__main__":
    main()
By separating out the ISR's into two different routines that call a shared method seems to work.

yogotech
Posts: 15
Joined: Tue Feb 25, 2020 5:11 am

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by yogotech » Tue Apr 28, 2020 4:09 pm

yogotech wrote:
Tue Apr 28, 2020 3:53 pm
I tried the following, which seems to work. I sometimes get a couple of errors for the first couple of runs, but that's expected as I'm guessing the initial state, and I only have a 50% chance of guessing right. :)

Code: Select all

from machine import Pin
import micropython
from utime import sleep

# Guess, will be wrong at startup half of the time
pin_state = False

def rising_isr(p):
    isr(True)

def falling_isr(p):
    isr(False)

def isr(state):
    global pin_state

    if pin_state == state: # <---- My test was wrong, this should check for identical states
        print("<Boo>", end="").
    pin_state = state

def main():
    micropython.alloc_emergency_exception_buf(100)
    sensor = Pin(25, Pin.IN)
    sensor.irq(handler=rising_isr, trigger=Pin.IRQ_RISING)
    sensor.irq(handler=falling_isr, trigger=Pin.IRQ_FALLING)

    while True:
        sleep(1)
        print(".", end="")

if __name__ == "__main__":
    main()
By separating out the ISR's into two different routines that call a shared method seems to work.
My test was wrong, it was considering the state that was different as an error, and it should have tested if the state was the same (since I had received the same trigger twice in a row). Unfortunately, it appears that you can only register a single ISR to a pin, so only one is active at a time. :(

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by kevinkk525 » Tue Apr 28, 2020 8:01 pm

yogotech wrote:
Tue Apr 28, 2020 3:45 pm
pythoncoder wrote:
Tue Apr 28, 2020 7:28 am
Given that you interrupt on each edge the pin state will alternate with each call to the ISR. If you know the pin state before the first interrupt occurs, you can therefore deduce it:

Code: Select all

pin_state = 1  # State before 1st interrupt occurs
def isr(p):
    global pin_state
    pin_state ^= 1  # An interrupt has occurred so it must have changed
That assumes that each state change runs the ISR to completion before the next state change, which given the fact that the pin value changes during he ISR, I'm not sure is a guarantee.
If you are using some kind of a button, you can be very sure that your pin state will change a lot during the ISR because buttons bounce a lot, therefore you need to debounce. The workaround is to know that previous state and when an ISR occurs, the state changed. However, due to the bouncing of the button the state will keep changing so you actually end up ignoring any state changes within the next 20-50ms.
If you are measuring a human input, it is unlikely that someone is capable of pressing a button for a shorter time than 50ms.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

yogotech
Posts: 15
Joined: Tue Feb 25, 2020 5:11 am

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by yogotech » Tue Apr 28, 2020 8:17 pm

kevinkk525 wrote:
Tue Apr 28, 2020 8:01 pm
yogotech wrote:
Tue Apr 28, 2020 3:45 pm
pythoncoder wrote:
Tue Apr 28, 2020 7:28 am
Given that you interrupt on each edge the pin state will alternate with each call to the ISR. If you know the pin state before the first interrupt occurs, you can therefore deduce it:

Code: Select all

pin_state = 1  # State before 1st interrupt occurs
def isr(p):
    global pin_state
    pin_state ^= 1  # An interrupt has occurred so it must have changed
That assumes that each state change runs the ISR to completion before the next state change, which given the fact that the pin value changes during he ISR, I'm not sure is a guarantee.
If you are using some kind of a button, you can be very sure that your pin state will change a lot during the ISR because buttons bounce a lot, therefore you need to debounce.
In a normal ISR on an embedded system, the state would NOT change during the ISR, as another ISR would be fired if the state changed, as the ISR should complete fast enough before the state changes. (Again, this is my experience on MUCH slower hardware, and in my experience writing OS drivers > 30 years ago).
The workaround is to know that previous state and when an ISR occurs, the state changed.
It's impossible to know what the current state was when the state changes fairly quickly.
However, due to the bouncing of the button the state will keep changing so you actually end up ignoring any state changes within the next 20-50ms.
If you are measuring a human input, it is unlikely that someone is capable of pressing a button for a shorter time than 50ms.
In my case, it's not a button, but a 433Mhz sensor, which receives a sequence of garbage data + valid data, so I have to play games to find the good data. Finding the good data means looking for specific patterns of events, and the events are determined the sequence of pulses.

Regardless, even if it were a button, how do you 'debounce' the data when AFAICT, it's not possible to actually know what value triggered the press (the up or down event)? (Normally, I do the debouncing in the electronics, not software...). If this were a button, I'd change the interrupt to fire on either the button press (IRQ_RISING) for button press, or button release (IRQ_FALLING) and then filter out any events that happened recently, but I can't do that in this case, since ALL the events are relevant.

I do filter out certain garbage events which occur if the pulse width is < 80 uSecs, but in that case, I have to throw away ALL of the captured data and start over again.


Nate

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by kevinkk525 » Tue Apr 28, 2020 9:02 pm

yogotech wrote:
Tue Apr 28, 2020 8:17 pm
In my case, it's not a button, but a 433Mhz sensor, which receives a sequence of garbage data + valid data, so I have to play games to find the good data. Finding the good data means looking for specific patterns of events, and the events are determined the sequence of pulses.

Regardless, even if it were a button, how do you 'debounce' the data when AFAICT, it's not possible to actually know what value triggered the press (the up or down event)? (Normally, I do the debouncing in the electronics, not software...). If this were a button, I'd change the interrupt to fire on either the button press (IRQ_RISING) for button press, or button release (IRQ_FALLING) and then filter out any events that happened recently, but I can't do that in this case, since ALL the events are relevant.

Well in that case I can point you to some very good resources. 433MHz receiver: https://github.com/peterhinch/micropython_remote

About the button: You know the state the button is in when you start your program, e.g. low. So if an ISR is triggered, the state must now be HIGH. After the debounce time of e.g. 50ms you check again if the state is still HIGH and wait for the next ISR.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

yogotech
Posts: 15
Joined: Tue Feb 25, 2020 5:11 am

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by yogotech » Tue Apr 28, 2020 9:04 pm

kevinkk525 wrote:
Tue Apr 28, 2020 9:02 pm
yogotech wrote:
Tue Apr 28, 2020 8:17 pm
In my case, it's not a button, but a 433Mhz sensor, which receives a sequence of garbage data + valid data, so I have to play games to find the good data. Finding the good data means looking for specific patterns of events, and the events are determined the sequence of pulses.

Regardless, even if it were a button, how do you 'debounce' the data when AFAICT, it's not possible to actually know what value triggered the press (the up or down event)? (Normally, I do the debouncing in the electronics, not software...). If this were a button, I'd change the interrupt to fire on either the button press (IRQ_RISING) for button press, or button release (IRQ_FALLING) and then filter out any events that happened recently, but I can't do that in this case, since ALL the events are relevant.

Well in that case I can point you to some very good resources. 433MHz receiver: https://github.com/peterhinch/micropython_remote
That's doing something very different than what I need. It's capturing data so it can be later sent out, and I'm capturing remote sensor data (temperature, power-meter data, etc...) in real-time that changes over time.


Nate

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: Pin interrupts and getting the value of the PIN state WHEN the ISR occurred

Post by kevinkk525 » Tue Apr 28, 2020 9:07 pm

The way the signal is captured still applies. But I see that it isn't explained as well as I remember because I was messaging Peter a lot..
Basically you have to poll a 433MHz signal because interrupts are way too slow. at least on the ESPs.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

Post Reply