How to debounce a GP Input in a python script using acmepins

How to debounce a GP Input in a python script using acmepins

In the classical article (in the Python subsection) there are several examples about how to read GPIO (General Purpose Input Output), both in polling and event mode, using acmepins.py module.

Why is debouncing needed ?

Really it is. Lets consider a possible, naively written, implementation that could be as follows:

from acmepins import GPIO
from time import sleep
from time import time

Button=GPIO('PC17','INPUT')

def event_handler():
    print ("Status: %d" % Button.digitalRead())

Button.set_edge("both",event_handler)

i=0
while True:
    print i
    i=i+1
    sleep(0.5)

Alas, testing the avove code, we can see, quite often, that simply pressing just once the button, we get more events than expected:

...
...
3
4
5
Status: 0
Status: 1
6
7
Status: 0
Status: 1
8
Status: 0
Status: 1
Status: 1
9
...
...

this is caused by the mechanical structure built-in into the physical switch: a small and very thin plate that is behaving as a small spring that is bouncing back and further. That, together with the extremely fast reading rate of CPU and its I/O circuit, make the simple software above to read all the bounces as regular events.

So, some smarter technique is in order to avoid that unconvenience.

Debounce class

Here a simple debouncing class is presented:

from acmepins import GPIO
from time import time


class PushButton(GPIO):
    OFF = 0
    PRESSED = 1


    def __init__(self, name):
        GPIO.__init__(self,name,'INPUT')
        self.status = PushButton.OFF
        self.x = 0.0
        self.set_edge("both",self.event_handler)
    
    
    def pressed (self):
        pass
        
    def released (self):
        pass
    
    def event_handler(self):
        
        if (self.status == PushButton.OFF):
            
            if self.digitalRead() == 0:
                self.status = PushButton.PRESSED
                self.x = time()
                self.pressed()
            
                
        elif (self.status == PushButton.PRESSED):
            
            if self.digitalRead() == 0:
                self.x = time()
            else:
                if (time() - self.x) > 0.005:
                    self.status = PushButton.OFF
                    self.released();
                
        else:
            printf ("Fatal error: status unknown: %d" % self.status)
            

The PushButton class is derived from GPIO in acmepins: that is quite straightforward as in our context a push button is, from the software perspective, a GPIO.

Some purist could argue here that a relation has-a captures better, in general, the concept of push button because, after all, there are even push buttons built upon other mechanism. Nonetheless, we use a simple subclassing.

Our constructor (init method), take care to built the parent object:

    def __init__(self, name):
        GPIO.__init__(self,name,'INPUT')
        ...

Later in the ctor, the event handler is registered in the parent class:

        self.set_edge("both",self.event_handler)

note that set_edge method is inherited from parent class GPIO and the

OFF and PRESSED are class static variables, in other words, are variables shared by all instances of the class.

pressed and release are two placeholder do-nothing methods, that are recalled when the respective events are detected (more on this later).

event_handler is the callback that implements the debouncing logic. A small state machine is defined here, with two states, OFF and PRESSED. When the push button is in its default position, not surprisingly the state is OFF. Once the user press the button, the handler is recalled (as the GPIO switches from 1 to 0 and we put "both" as first parameter in set_edge) and the first if branch is followed (status == OFF and digitalRead == 0): the state is then changed to PRESSED and the current time is noted in x variable. Lastly the pressed() method is called.

Now, even if, due to the mechanical behavior mentioned, the GPIO changes repeatedly from 0 to 1 and back to 0, as we are in PRESSED state, the pressed() method is ever called again.

Each time the GPIO bounces to 0, the timestamp in x is updated, whilst if it remains to 1 for more than 5 ms (showing that button is back to default unpressed status), the state is changed to off and virtual method released() is called.

How to derive a more specific worker class

So, in order to exploit that class one has to derive (again) a more specific class that is able to do a useful job.

First thing to do is to declare the new class, so it is derived from the parent one:

class MyButtonClass(PushButton):

next a proper contructor has to be defined, where the parent's constructor is explictly called:

    def __init__(self, name):
        PushButton.__init__(self, name)

in our case we passed the string identifying the GIOP pin, up to parent class.

Lastly, the two workers method are redefined accordingly to our purposes:

    def pressed (self):
        print ">>> Button pressed"
        
    def released (self):
        print ">>> Button released"

here we are just printing some informative message. Below you find a working example that, when run on Arietta, is displaying the actions done on the embedded white push button.

Note that in most cases, only the pressed() method is useful, as we need to react immediately to user input.

from gpio_classes import PushButton
from time import sleep


class MyButtonClass(PushButton):
    def __init__(self, name):
        PushButton.__init__(self, name)
        
    def pressed (self):
        print ">>> Button pressed"
        
    def released (self):
        print ">>> Button released"
        

button = MyButtonClass('PC17')

i=0
while True:
    print button.status, i
    i=i+1
    sleep(0.5)
    

Link

Really, on this specific topic, there is a quite large literature, for a good recap see, for example:

Andrea Montefusco
Currently employed as network architect, always Internet working man, real C/C++ programmer in the past, network and Unix system engineer as needed, HAM Radio enthusiast (IW0HDV), aeromodeller (a person who builds and flies model airplanes) since 1976 (ex FAI10655).
http://www.montefusco.com - https://github.com/amontefusco - andrew@montefusco.com

The TanzoLab Project

Il TanzoLab è una iniziativa senza fini di lucro, nata da un'idea di Sergio Tanzilli socio fondatore di Acme Systems srl nel Novembre 2015, per trasferire ad appassionati di elettronica e informatica, professionisti e aziende nel settore, le conoscenze necessarie per poter creare prodotti embedded adatti per la produzione industriale.

Le attività del TanzoLab si svolgono ogni mercoledi sera, salvo casi speciali, dalle ore 18:30 presso i locali della Acme Systems srl e consistono in:

  • Talk monotematici a cura di professionisti in vari settori tecnologici
  • Workshop pratici su elettronica embedded, produzione e informatica
  • Progettazione e realizzazione di nuovi prodotti embedded per l'IT

Le attività vengono coordinate tramite questo sito, in cui vengono pubblicati tutti i lavori svolti o in via di sviluppo, e tramite un gruppo Telegram con cui per interagire direttamente via chat con gli altri membri.