pigpio: Initial falling edge not detected due to glitch filter

My issue seems related to #359

However I can very easily AND reliably reproduce the issue using the method described below. First a description of my environment:

Hardware:

  • Raspberry Pi Zero WH (BCM 2835 SOC)
  • /boot/config.txt -> dtoverlay=pwm-2chan
  • GPIO pin 18 is used in my code below (but issue can be reproduced using any GPIO pin)
  • Generated pulses either manually or using an old rotary dial

Environment:

  • uname -a -> Linux raspberrypi 5.4.51+ #1333 Mon Aug 10 16:38:02 BST 2020 armv6l GNU/Linux
  • python --version -> Python 2.7.16
  • python3 --version -> Python 3.7.3
  • pigpiod -v -> 77

My testing code:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# test.py

# Low pulse lasts approximately 36ms
# High pulse lasts approximately 60ms
# Last low pulse lasts approximately 15ms
# Period is 96ms
# Frequency ~ 10.41hz
# Rise time is approximately 7-15µS
# Fall time is approximately 4-10µS

def detectEdgeCB(gpio, level, tick):
    print("detectEdgeCB " + str(level) + " " + str(tick))


if __name__ == "__main__":

    import time
    import pigpio

    PWM_GPIO = 18

    pi = pigpio.pi()

    if not pi.connected:
        print("Not connected to PIGPIO Daemon")
        exit()

    # GPIO as input
    pi.set_mode(PWM_GPIO, pigpio.INPUT)

    # Rise time ~20 µS and fall time ~15 µS - so 100 µS should be sufficient for software debouncing
    pi.set_glitch_filter(PWM_GPIO, 100)

    # GPIO pull-up
    pi.set_pull_up_down(PWM_GPIO, pigpio.PUD_UP)

    # Detect initial rotary dial turn
    detectEdge = pi.callback(
        PWM_GPIO, pigpio.EITHER_EDGE, detectEdgeCB)

    print("Waiting for rotary dial input (press CTRL-C to exit)...")

    try:
        while True:
            print("Direct " + str(pi.read(PWM_GPIO)) +
                  " " + str(pi.get_current_tick()))
            time.sleep(1)
    except KeyboardInterrupt:
        pass

    pi.stop()

Output 1st time (i.e. after reboot or power on):

Waiting for rotary dial input (press CTRL-C to exit)...
Direct 1 1287979995
Direct 1 1288985335
Direct 1 1289989956
Direct 0 1290994696
Direct 0 1291999280
detectEdgeCB 1 1293000384
Direct 1 1293004731
detectEdgeCB 0 1293058644
detectEdgeCB 1 1293094359
detectEdgeCB 0 1293152389
detectEdgeCB 1 1293188055
detectEdgeCB 0 1293246115
detectEdgeCB 1 1293281870
detectEdgeCB 0 1293340370
detectEdgeCB 1 1293376310
detectEdgeCB 0 1293434575
detectEdgeCB 1 1293470740
detectEdgeCB 0 1293528895
detectEdgeCB 1 1293564930
detectEdgeCB 0 1293623106
detectEdgeCB 1 1293659411
detectEdgeCB 0 1293717871
detectEdgeCB 1 1293753926
detectEdgeCB 0 1293812566
detectEdgeCB 1 1293848481
detectEdgeCB 0 1293907691
detectEdgeCB 1 1293922766
Direct 1 1294010372
Direct 1 1295014399
Direct 1 1296018785

oscope

As you can see the first falling edge is not detected. (4th line in the output when GPIO goes from ‘1’ to ‘0’)

Output every subsequent time (success):

Waiting for rotary dial input (press CTRL-C to exit)...
Direct 1 1497855392
Direct 1 1498860295
detectEdgeCB 0 1499649992
Direct 0 1499864555
Direct 0 1500869033
detectEdgeCB 1 1501437677
detectEdgeCB 0 1501496587
detectEdgeCB 1 1501532962
detectEdgeCB 0 1501591842
detectEdgeCB 1 1501627607
detectEdgeCB 0 1501685797
detectEdgeCB 1 1501721812
detectEdgeCB 0 1501780392
detectEdgeCB 1 1501816392
Direct 1 1501871616
detectEdgeCB 0 1501875148
detectEdgeCB 1 1501911108
detectEdgeCB 0 1501970128
detectEdgeCB 1 1502006298
detectEdgeCB 0 1502065343
detectEdgeCB 1 1502101573
detectEdgeCB 0 1502160568
detectEdgeCB 1 1502196918
detectEdgeCB 0 1502255719
detectEdgeCB 1 1502291789
detectEdgeCB 0 1502351079
detectEdgeCB 1 1502366119
Direct 1 1502876439
Direct 1 1503880759
Direct 1 1504885353

Observation: When executing the test code for the first time (i.e. after a reboot/power on) - the first falling edge is never detected. I can also get the same behavior without having to reboot by executing pigs pud 18 u; pigs pud 18 d; pigs pud 18 u; pigs pud 18 d. After running this command the first run of my code will not detect the 1st falling edge and every subsequent run will always detect the 1st falling edge.

I managed to find a workaround which might make sense to the author of the pigpio daemon. If I include the code below and manually trigger a short pulse - the test code will always (correctly) detect the 1st falling edge. Even on the first run.

    if not pi.connected:
        print("Not connected to PIGPIO Daemon")
        exit()

    # ...
    # ^--- existing code 

    # GPIO as output
    pi.set_mode(PWM_GPIO, pigpio.OUTPUT)

    # Pulse for debugging
    pi.gpio_trigger(PWM_GPIO, 100, 1)
    time.sleep(1)

    # existing code --->
    # ...

    # GPIO as input
    pi.set_mode(PWM_GPIO, pigpio.INPUT)

Recap

Using the Python library to interface with pigpiod on my Raspberry Pi I cannot detect the 1st falling edge on my GPIO pin when my script is first run. On every subsequent script run the 1st falling edge gets detected correctly and everything works fine. I’ve been able to reliably and consistently reproduce this behavior.

After a reboot or when manually pulsing the GPIO pin on the command line (pigs pud 18 u; pigs pud 18 d; pigs pud 18 u; pigs pud 18 d) the first execution of my script will miss the 1st falling edge again. And all subsequent script executions pick up the 1st falling edge without a problem.

Hope someone is able to reproduce this as well and maybe provide a fix (or a reason why my work around works?) If anyone needs any additional information I will be happy to provide it.

Thanks!

Edit: When using the workaround - the first script execution will always immediately trigger the callback without a falling edge present. This is also not what I would expect but this only happens on the first execution :

pi@raspberrypi:~/wonderphone/src $ python ./test.py
Waiting for rotary dial input (press CTRL-C to exit)...
detectEdgeCB 1 3509128308
Direct 1 3509134771
Direct 1 3510142484
detectEdgeCB 0 3511094933
Direct 0 3511147458
Direct 0 3512151744

About this issue

Most upvoted comments

First, appreciate your effort in building and maintaining this lib. It’s awesome.

Now relating to this issue: I’ve been having the same issue, and could reproduce it reliably. HOWEVER, as I put together this post, I managed to learn more about what’s going on. Basically:

  1. Before utilizing pigpio in script, if a pin’s value was at 0, either because that’s the default value since boot up (exp. BCM 17), or because it has been manually set to 0 (exp. set to OUTPUT mode), and …
  2. In script, during setup, call set_glitch_filter after set_pull_up_down;

THEN, when one starts listening for edge callbacks, the 1st fall-edge event will be missing.

Example

Say I wire up BCM 17 to GND, with a button in between (a simple open/close switch):

 __/  __      <-- Simple Button
|       |
17    GND

Power up Pi, ssh in, and run python test.py with the following code:

import pigpio

pi = pigpio.pi()

# read initial level from gpio 17
print(f'initial: level = {pi.read(17)}')

# set up gpio 17
pi.set_mode(17, mode=pigpio.INPUT)
pi.set_pull_up_down(17, pud=pigpio.PUD_UP)
pi.set_glitch_filter(17, steady=1000)

# read post-setup level from 17
print(f'postset: level = {pi.read(17)}')

# set up callback
def on_edge(pin, level, tick):
    print(f'on_edge: level = {level}')
pi.callback(17, edge=pigpio.EITHER_EDGE, func=on_edge)

# pause until exit
try:
    import signal
    signal.pause()
except KeyboardInterrupt:
    exit()

Then if I do a couple of clicks on the button (press down, and then release up), I’ll get this:

initial: level = 0
postset: level = 1

on_edge: level = 1        < - - - I click once (down and up), and only get a SINGLE callback, missing the first "level = 0"

on_edge: level = 0        < - - - I click again (down and up) -- from now on, all clicks are working with 2 callbacks
on_edge: level = 1

on_edge: level = 0        < - - - and so on...
on_edge: level = 1

Key Facts

  • If I stop the script, and then run again, the callbacks would continue to work properly.
  • If I set the pin level back to 0 (exp. in command line with pigz or gpio), and run the script again, the 1st fall-edge will be missing again.
  • If I use a pin with initial value at 1 (exp. BCM 7) before running the script, there won’t be an issue.

The “Workaround”

I’m guessing the issue has something to do with the “glitch filter”.

If I put set_glitch_filter before set_pull_up_down, the issue will be gone – although if I start listening for callback right after setup (as in example code above), I’d immediately get a level=1 edge event, before I press any button – I’d assume that’s from pulling up the pin from 0 to 1.

I hope the above makes sense, and would appreciate some feedback from dev on wether my guess was correct.

1st UPDATE - 3 HRs after this post

After more tests, it turns out, running set_glitch_filter before set_pull_up_down isn’t really a fix.

In fact, it seems that: as long as the pin was in OUTPUT mode with level = 0, and you start the script that utilizes pigpio, the first fall-edge event would always be missing – even if I don’t use set_glitch_filter at all.

LASTEST UPDATE - 48 HRs after this post

I have actually found a reliable workaround, but was too busy to post here till now.

Basically, for all my code utilizing pigpio from that point on, I have change the sequence of how a button gets initialized into

  1. set_glitch_filter() first to avoid glitches
  2. callback() next, before setting mode, to avoid missing the 1st edge event
  3. set_mode(mode=pigpio.INPUT)
  4. set_pull_up_down(pud=pigpio.PUD_UP)

This has worked reliably across different pins – no events would be missing whatsoever – although it does seem a bit odd that callback has to be set up before changing pin to input mode.

Still, I’m quite curious what could have caused such phenomenon. @guymcswain