pigpio icon indicating copy to clipboard operation
pigpio copied to clipboard

Initial falling edge not detected due to glitch filter

Open QNimbus opened this issue 4 years ago • 16 comments

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

QNimbus avatar Sep 17 '20 07:09 QNimbus

Please generate the input signal from script as I don't have a rotary dial. Also, try removing the glitch filter, or enable it after you are sure the input signal has stablelized.

guymcswain avatar Sep 17 '20 12:09 guymcswain

@QNimbus , Can you confirm that pi.callback() catches the initial edge (after power-on or reboot) if the glitch filter is removed?

Hypothesis: Glitch filter initialization is consuming the first edge preventing the "initial" callback. This would explain why all scenarios work: Running script again (unless pi.set_glitch_filter(PWM_GPIO, 0)), toggling input by changing pullup/pulldown or by pi.gpio_trigger(PWM_GPIO, 100, 1).

guymcswain avatar Oct 12 '20 16:10 guymcswain

12Jan21 Same issue for me. I fell back to:

import RPi.GPIO as GPIO GPIO.add_event_detect(gpioinput, GPIO.BOTH, callback=interrupt_handler0, bouncetime=int(maxbouncetime/10))

and it worked fine. I'll try the workaround, as I prefer to use pigpio. thanks

lidodvr224 avatar Jan 12 '21 15:01 lidodvr224

Your feedback is appreciated. Please let me know what you mean by 'workaround'. My position is that you can't put a glitch filter - which requires two edges - in front of a piece of code that is expecting to catch the initial edge (after library is initialized).

guymcswain avatar Jan 12 '21 15:01 guymcswain

I tried removing the glitch filter and it made no difference. I also tried the 'workaround' and it did not work, still intermittently missing the first Falling Edge.

On Tue, Jan 12, 2021 at 10:57 AM Guy McSwain [email protected] wrote:

Your feedback is appreciated. Please let me know what you mean by 'workaround'. My position is that you can't put a glitch filter - which requires two edges - in front of a piece of code that is expecting to catch the initial edge (after library is initialized).

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/joan2937/pigpio/issues/385#issuecomment-758753736, or unsubscribe https://github.com/notifications/unsubscribe-auth/AODWKF4RBTUSNUGMBFSXZKLSZRWNVANCNFSM4RP77EQQ .

lidodvr224 avatar Jan 12 '21 16:01 lidodvr224

Please post a sample of your code.

guymcswain avatar Jan 12 '21 20:01 guymcswain

will do, pretty simple, as it's test code for something else. it's pretty much the same as the original developer who noticed the anomaly first, using BOTH EDGEs as trigger. I also tested using just FALLING_EDGE and got the same result. verified voltage levels using o-scope. RPi 3, newest s/w.

On Tue, Jan 12, 2021 at 3:47 PM Guy McSwain [email protected] wrote:

Please post a sample of your code.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/joan2937/pigpio/issues/385#issuecomment-758960805, or unsubscribe https://github.com/notifications/unsubscribe-auth/AODWKF2VDLARAK3RELZQRO3SZSYM3ANCNFSM4RP77EQQ .

lidodvr224 avatar Jan 12 '21 21:01 lidodvr224

##test_pig.py (this is NOT production code, for testing missing first FALLING_EDGE using pigpio!)

#/usr/bin/env python3

#test_pig.py (from startup_pig.py)

#torun: python test_pig.py #toggle switch between and 1K resistor to ground #gpioinput is PULL_UP #initial state: switch is in OFF position

#NOTE: PIGPIO misses FIRST FALLING_EDGE when switch is placed in ON position #ON = ground here

#USES PIG (callback parameters = 3, (channel, level, tick) #python test_pig.py

import time import signal import sys import datetime import subprocess, os

gpioflag = False use_pig = True

if use_pig: import pigpio pig = pigpio.pi()

if not use_pig: gpioflag = True import RPi.GPIO as GPIO GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) #use GPIO naming convention GPIO.cleanup()

maxbouncetime = 265   #
import RPi.GPIO as GPIO

else: maxbouncetime = 25000*8 #

gpioinput = 26 #monitor this input for ON or OFF gpiooutput = 19 #turn on LED if 26=ON(to ground)

ON_FLAG0 = False #global debounce = 0.5

##########################################################

def open0(onflag): #do this when interrupt occurs, ON state #falling_edge (3.3v to gnd)

    print("ON ON ON")
    onflag = True

    #turn on gpiooutput led
    if use_pig:
        pig.write(gpiooutput, True)
    else:
        GPIO.output(gpiooutput, True)

    return(onflag)  #for close0

def close0(onflag):
#rising_edge, gnd to 3.3v on gpio

    print("OFF OFF OFF")
                   
    #turn OFF gpiooutput led
    if use_pig:
        pig.write(gpiooutput, False)
    else:
        GPIO.output(gpiooutput, False)

    #reset for next time
    onflag = False
    return(onflag)

#uncomment if <use_pig=False> #def interrupt_handler0(gpio):

def interrupt_handler0(gpio,level,tick): #pig:default callback params are: #(channel,level,tick)

#level=0, falling edge (ON)
#level=1, rising edge  (Off)
#level=2, no change

global ON_FLAG0

if gpioflag:
    print("ih-0: <"+str(GPIO.input(gpio)))

    time.sleep(debounce)
    interrupt_state0 = GPIO.input(gpio)
    print("after sleep, got GPIO state --> "+str(interrupt_state0))
else: # use pig
    interrupt_state0 = pig.read(gpio)
    print(" ")
    print(" ***************************** ")
    print("IH: Interrupt-0: ->  "+str(interrupt_state0))
    print("IH: ON_FLAG0  -->  "+str(ON_FLAG0))
    print("IH: pig level -->  "+str(level))
    if level == 2:
        print("----------> GOT level=2???")
        print("----------> GOT level=2???")
        print("----------> GOT level=2???")
        print("----------> GOT level=2???")
        
if interrupt_state0:  #3.3v, turn OFF
    print("IH: off, ON_FLAG0 --> "+str(ON_FLAG0))
    if (ON_FLAG0):
        print("IH: interrupt: "+str(gpio)+" call close0")
        ON_FLAG0 = close0(ON_FLAG0)
    else:
        print("IH: interrupt: initially OFF, just return...")
    
else: #GND, turn ON
    if (not ON_FLAG0): #
        print("IH-on: interrupt: "+str(gpio)+" call open0")
        ON_FLAG0=open0(ON_FLAG0)

print(" ***************************** ")
#return(gpio,interrupt_state0)
return()

def handler(signum,frame): print("SIGINT handler: Here you go")

if not use_pig:
    GPIO.setmode(GPIO.BCM)   #use GPIO naming convention
    GPIO.cleanup()
else:
    print("CleanupPIG")
    pig.write(gpiooutput, False)
    pig.write(gpioinput, False)
    pig.stop()

print("SIGINT handler, now quit()")
print("quit now")
quit()

###############################################

print("in Mainroutine") #start program print(" ")

if use_pig: pig.set_mode(gpioinput, pigpio.INPUT) pig.set_pull_up_down(gpioinput, pigpio.PUD_UP)

pig.set_mode(gpiooutput, pigpio.OUTPUT)
pig.set_pull_up_down(gpiooutput, pigpio.PUD_UP)

else: # gpio goes here GPIO.setup(gpioinput, GPIO.IN, pull_up_down = GPIO.PUD_UP) GPIO.setup(gpiooutput, GPIO.OUT,initial=False)

print("Set GPIO events") #for interrupt if gpioflag: GPIO.add_event_detect(gpioinput, GPIO.BOTH, callback=interrupt_handler0, bouncetime=int(maxbouncetime/10))

else: #pig

#######################################
#pigpio workaround? missing FALLING_EDLGE
#, set GPIO as output first (from github)
pig.set_mode(gpioinput, pigpio.OUTPUT)

# Pulse for debugging
pig.gpio_trigger(gpioinput, 100, 1)
time.sleep(1)
#pigpio workaround 
#######################################

# GPIO as input
pig.set_mode(gpioinput, pigpio.INPUT)
pig.set_pull_up_down(gpioinput, pigpio.PUD_UP)
time.sleep(1) 
#microseconds here
pig.set_glitch_filter(gpioinput, maxbouncetime)

cb0 = pig.callback(gpioinput, pigpio.EITHER_EDGE, interrupt_handler0)
print("CB0 --> "+str(cb0))
print("cb0.callb.gpio: "+str(cb0.callb.gpio))
print("cb0.callb.edge: "+str(cb0.callb.edge))
print("cb0.callb.func: "+str(cb0.callb.func))
print("cb0.callb.bit:  "+str(cb0.callb.bit))
#level=0, falling edge (ON)
#level=1, rising edge  (Off)
#level=2, no change

#check initial state if gpioflag: interrupt_state0 = GPIO.input(gpioinput) else: interrupt_state0 = pig.read(gpioinput) #1=3.3v=switch OFF #0=gnd =switch ON

if interrupt_state0 < 1.0: interrupt_state = "ON" else: interrupt_state = "OFF"

print("INIT, interrupt_state0 --> "+str(interrupt_state0)) print("INIT, interrupt_state --> "+str(interrupt_state)) print("ON_FLAG0,...",ON_FLAG0)

if interrupt_state0: print("HW, initially Off")

else: #initially ON print("call interrupt_handler0") if use_pig: interrupt_handler0(gpioinput,0,0) #pin,FALLING_EDGE,tick else: interrupt_handler0(gpioinput) time.sleep(1) print("HW, initially ON")

time.sleep(1)

print("WaitingForInterrupt from gpio") print(" ")

try: while True: signal.signal(signal.SIGINT, handler) #wait on interrupt_state0, interrupt_state1

    time.sleep(1)

except KeyboardInterrupt: print("Got cntrl+c") pass

``

lidodvr224 avatar Jan 13 '21 13:01 lidodvr224

This code is confusing with multiple libraries and flags for what should be a simple test to determine if an input edge can be detected reliably. I am frankly too busy to want to sort through it.

Your problem symptoms don't match the original authors issue description: 'Initial falling edge not working.' If you read through the issue he describes in great detail that it is an initial condition problem but otherwise works reliably. You indicated that you miss the edge intermittently. I would recommend you tidy up your code and debug it further.

Please note that you cannot rely on reading an input from within the callback as the level may have changed. That is why a level argument is supplied to the callback. You should assess whether an edge occurs solely based on the level argument.

guymcswain avatar Jan 15 '21 06:01 guymcswain

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

FreezeFiver avatar Feb 02 '21 09:02 FreezeFiver

Thanks for providing the detail in your analysis. I've been busy with other matters but I will get back to you when I have more time to look at this again. A couple of comments for now:

  • You are correct that an edge will be produced upon setting the pull resistor if it is different than the power-on pull states - gpio 1-7 are pull-up and gpio >7 are pull-down after power-on reset.
  • The glitch filter, as written, requires two edges to ~produce an output~ initialize. This also seems to trip people up looking for a notification on the first edge of an input. It is possible for the glitch code to change but I've been reluctant to do so until I can establish the root cause of this issue.

guymcswain avatar Feb 13 '21 16:02 guymcswain

good info (gpio 1-7 are pull-up and gpio >7 are pull-down after power-on reset). I was setting gpio input pin #26 to Pull-Up, and missing the first 'on' state of my analog toggle switch. After reconfiguring to gpio input pin #4, the script works everytime.

On Sat, Feb 13, 2021 at 11:33 AM Guy McSwain [email protected] wrote:

Thanks for providing the detail in your analysis. I've been busy with other matters but I will get back to you when I have more time to look at this again. A couple of comments for now:

  • You are correct that an edge will be produced upon setting the pull resistor if it is different than the power-on pull states - gpio 1-7 are pull-up and gpio >7 are pull-down after power-on reset.
  • The glitch filter, as written, requires two edges to produce an output. This also seems to trip people up looking for a notification on the first edge of an input. It is possible for the glitch code to change but I've been reluctant to do so until I can establish the root cause of this issue.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/joan2937/pigpio/issues/385#issuecomment-778642011, or unsubscribe https://github.com/notifications/unsubscribe-auth/AODWKF5WO2R4S6UFNF53EY3S62SWBANCNFSM4RP77EQQ .

lidodvr224 avatar Feb 18 '21 14:02 lidodvr224

I've run in to this issue as well, and coding around it is quite annoying. I see no reason that a "glitch filter" as described in the docs would need to see 2 edges before edge detection starts working...

systemdarena avatar Apr 16 '21 17:04 systemdarena

It is possible for the glitch code to change but I've been reluctant to do so until I can establish the root cause of this issue.

It's annoying to me when people don't follow up on the issue they've posted. Your input is noted.

guymcswain avatar Apr 16 '21 20:04 guymcswain

Sorry, not sure I follow, would you have preferred me to open a separate issue? Or I can post more info here, but I am pretty sure my issue is exactly the same as the origin reporter's issue. If you want me to post any code examples from my tests let me know. I'd be happy to put in some more effort on my side. I appreciate the awesome library you have. It has been otherwise easy to use and worked well.

systemdarena avatar Apr 16 '21 20:04 systemdarena

No another issue is not necessary. I'll remove the documentation label and fix initializing the glitch filter when I get around to it. If you want something sooner you are welcome to submit a PR. (I was expressing some frustration on not hearing back from OP.)

guymcswain avatar Apr 16 '21 20:04 guymcswain