joan2937 / pigpio

pigpio is a C library for the Raspberry which allows control of the General Purpose Input Outputs (GPIO).
The Unlicense
1.47k stars 410 forks source link

cannot set callback reliably #516

Closed AlfaBravoX closed 2 years ago

AlfaBravoX commented 2 years ago

Hi, I appreciate help, because my project is quite large and using this lib, however i noticed, that sometimes initial falling edge is not detected, after script is restarted. I know there were many discussions on that topic here, ie. #385, but I was assuming the issue is resolved if workaround to use gpio_trigger() is used. However in my case none of wokrarouns works.

I have RPI 3 with: pigpiod -v 79

and when using this code with different lib, callback works reliably 100% also for initial falling edge:

import RPi.GPIO as GPIO
import time
#Setup GPIO board
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)

_gpin_a = 27

def _callback(channel):
    global _gpin_a_state
    try:
        if _gpin_a_state == GPIO.input(_gpin_a):
            print(f"___####glitch,pin was {GPIO.input(_gpin_a)} returning")
            return
    except Exception as e:
        print(e)
    print(f"previous _gpin_a_state: {_gpin_a_state}")
    print("_callback_a called for gpin #{} current state is: {}".format(channel, GPIO.input(_gpin_a)))
    _gpin_a_state = GPIO.input(_gpin_a)

GPIO.setup(
    _gpin_a,
    GPIO.IN,
    pull_up_down=GPIO.PUD_UP
)  # input alarm Reset both SW/HW button

_gpin_a_state = GPIO.input(_gpin_a)

GPIO.add_event_detect(
    _gpin_a, GPIO.BOTH, callback=_callback, bouncetime=500
)

time.sleep(.9)

i=0
while True:
    try:
        print()
        print(f"___**** loop: {i}")
        GPIO.setup(_gpin_a, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        time.sleep(2)
        GPIO.setup(_gpin_a, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        time.sleep(2)
        i += 1

    except KeyboardInterrupt:
        GPIO.cleanup()
        exit()

    except Exception as e:
        print(e)

but when I set same with this lib:

import pigpio, time

_gpin_a = 27
pi_gpio = pigpio.pi()

def _callback(channel, level, tick):
    global _gpin_a_state
    try:
        if _gpin_a_state == pi_gpio.read(channel):
            print(f"___####glitch,pin was {pi_gpio.read(channel)} returning")
            return
    except Exception as e:
        print(e)
    print(f"previous _gpin_a_state: {_gpin_a_state}")
    print("_callback_a called for gpin #{} current state is: {}".format(channel, pi_gpio.read(channel)))
    _gpin_a_state = pi_gpio.read(channel)

pi_gpio.set_glitch_filter(_gpin_a, 5000)
pi_gpio.gpio_trigger(_gpin_a, 100, 1) <--this should be workaround, but it does not work
pi_gpio.callback(_gpin_a, pigpio.EITHER_EDGE, _callback)
pi_gpio.set_mode(_gpin_a, pigpio.INPUT)
pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_UP)
_gpin_a_state = pi_gpio.read(_gpin_a)

i=0
while True:
    try:
        print()
        print(f"___**** loop: {i}")
        pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_DOWN) <- if i break the script here by ctrl+c, then next time running the script, initial falling edge is missed, even PUD_UP was defined above
        time.sleep(2)
        pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_UP)
        time.sleep(2)
        i += 1

    except KeyboardInterrupt:
        pi_gpio.stop()
        exit()

    except Exception as e:
        print(e)

in second case i have absolutely unpredictable first falling edge results. Sometimes callback is triggered just fine, sometimes not.

Because this first callback reliability is critical for me , I am considering to revert to RPI.GPIO lib, but I want to try all options before taking decision.

thanks for any advises!

guymcswain commented 2 years ago

In the callback you should use the argument level to determine the pin's value. This is because the callbacks are generated from post processing GPIO samples collected by the DMA. The post processing occurs each millisecond.

AlfaBravoX commented 2 years ago

In the callback you should use the argument level to determine the pin's value. This is because the callbacks are generated from post processing GPIO samples collected by the DMA. The post processing occurs each millisecond.

Thanks, Basically the problem is following: When interrupting the script by kill or ctrl+c in the moment when PIN is LOW (pigpio.PUD_DOWN), and after running the script again, first falling edge is missed.

Regarding level I did some testing and level holds permanent value 0/1 that initiated the callback. So for example If I need to verify the PIN state later in callback function, (PIN level could changed in the meantime), I need to use read() function. Or? I am not sure, but using level to filter out electrical fluctuations in the code is impossible.

guymcswain commented 2 years ago

First, you should initialize the pin mode and state before the callback and filter:

pi_gpio.set_mode(_gpin_a, pigpio.INPUT)
pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_UP)
time.sleep(1) # give pin some time to settle
pi_gpio.set_glitch_filter(_gpin_a, 5000)
pi_gpio.gpio_trigger(_gpin_a, 100, 1) # workaround
pi_gpio.callback(_gpin_a, pigpio.EITHER_EDGE, _callback)

Then, your callback should look something like this:

def _callback(channel, level, tick):
    global _gpin_a_state
    try:
        if _gpin_a_state == level:
            print(f"___####glitch,pin was {level} returning")
            return
    except Exception as e:
        print(e)
    print(f"previous _gpin_a_state: {_gpin_a_state}")
    print("_callback_a called for gpin #{} current state is: {}".format(channel, level)
    _gpin_a_state = level

This may not be be exactly what you want but it should give you an idea of how to use the callback api.

AlfaBravoX commented 2 years ago

Thanks!

Again, this issue is about following problem: If you stop the script by ctrl+c or kill when PIN is LOW, then next time you start it, first falling edge is not reported to callback. Even if workaround is used, it does not help. I did a lot of playing last 24 hours and conclusion is following:

when setting PIN, you need to set pigpio.PUD_UP after callback and by sw filter out that callback, so it is not reported.

Example:

def _callback(channel, level, tick):
    if bool(starting):
        print("ignoring_callbacks_when_starting_the_script")
        return

    time.sleep(.3)
    if pi_gpio.read(channel) != level:
        print(f"___####false calbacks ignored, level was {level} and real pin state was {pi_gpio.read(channel)} -> returning")
        return
    print("_callback fully executed for gpin #{} current state is: {}".format(channel, bool(level)))

starting = True #important to filter callback when setting up pin

pi_gpio.set_glitch_filter(_gpin_a, 500)
pi_gpio.set_mode(_gpin_a, pigpio.INPUT)
time.sleep(0.3)
pi_gpio.callback(_gpin_a, pigpio.EITHER_EDGE, _callback)
pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_UP)
time.sleep(0.3) # give pin some time to settle

starting = False

This logic works as expected, you can kill the script anytime and none of edges will be missed. If there are other ideas, I am more then happy to see better way of handling this logic.

guymcswain commented 2 years ago

I am happy to help but I see that you have chosen to ignore my suggestions. I'll try once more.

Please read the documentation for the callback api and note that it says to NOT call read() in the callback. I tried to explain that the callbacks are looking into the past and therefore are timestamped (tick). Once you begin dealing with faster signal rates you will understand why this is a feature of pigpio.

Also, you again are generating a spurious edge during initialization:

starting = True #important to filter callback when setting up pin
pi_gpio.set_glitch_filter(_gpin_a, 500)
pi_gpio.set_mode(_gpin_a, pigpio.INPUT)
time.sleep(0.3)
pi_gpio.callback(_gpin_a, pigpio.EITHER_EDGE, _callback)

# This will cause an edge depending on the initial state of the pin
pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_UP)

...

I don't see a problem with the library here so I'm going to have to close this issue.l

AlfaBravoX commented 2 years ago

Thanks, appreciate your comments. I haven't chosen to ignore your suggestions. You can see that in my previous post, I shared revised the code and I removed read() everywhere with one exception of following line: if pi_gpio.read(channel) != level: The reason I left it there is because I am not sure how I detect current pin state if it changed right after callback was called. Why? Because my RPI is installed in 220V fuse board and there are frequent power glitches caused by 220V relays that are sometimes not captured by glitch filter. Noises are either too long or repetitive. So for this reason to exclude 100% spurious edges, I need to check real PIN state in callback function. This mechanism is nothing new and widely used in others project as well.

Also I see little misunderstanding here. My very first question was not about this level stuff (even I learned that here too). My initial question was, that killing the script in the moment, when PIN is LOW causing the callback misses initial falling edge after script is restarted when PUD_UP in INIT is set before defining callback as you are suggesting me.

Yes, my solution is not perfect, on one hand setting pi_gpio.set_pull_up_down(_gpin_a, pigpio.PUD_UP) after setting callback generating a spurious edge (i can detect it and filter that out on software level) , but initial falling edge is not missed.

The reason I opened this thread is that RPi.GPIO lib does not behave like this. If I set callback after PUD_UP as you are suggesting, initial falling edges are not missed:

_gpin_a = 27
GPIO.setup(
    _gpin_a,
    GPIO.IN,
    pull_up_down=GPIO.PUD_UP
)  # input alarm Reset both SW/HW button

_gpin_a_state = GPIO.input(_gpin_a)

GPIO.add_event_detect(
    _gpin_a, GPIO.BOTH, callback=_callback, bouncetime=500
)

So I wanted to know why.