Leggere un pulsante su GPIO con antirimbalzo software

Da raspibo.
Versione del 15 mar 2013 alle 14:20 di Eineki (discussione | contributi) (Creati i link i link)
Jump to navigation Jump to search

Come si collega un pulsante

Un circuito per collegare un pulsante e' il seguente:

Pulsante.png

Quando il pulsante e' aperto, il circuito e' chiuso verso massa attraverso le sue resistenze. Quando il pulsante e' chiuso la corrente fluisce attraverso la resitenza da 10Kohm e porta l'estremo collegato alla resistenza da 1Kohm al potenziale di 3.3V. La resistenza da 1K serve solo per limitare la corrente trasperita al pin di input (non e' necessaria ma e' una piccola protezione da incidenti di collegamento).

Purtroppo i contatti fisici non sono mai perfetti e spesso ogni pressione del pulsante viene ricevuta dalla CPU come un treno di impulsi prima che lo stato diventi stabile.

in Python

In Raspian c'e' il pacchetto python-rpi.gpio che consente di accedere a GPIO in modo semplice dal linguaggio Python. Potete installare RPI.gpio con in seguente comando:

$ sudo apt-get install python-rpi.gpio python3-rpi.gpio

A partire dalla versione 0.5 supporta anche la gestione degli interrupt.

Iniziamo con ordine. Per leggere semplicemente l'input potremmo scrivere un programma simile: (Il pulsante e' collegato a GPIO23, pin 16).

#!/usr/bin/env python

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

GPIO.setup(23, GPIO.IN)

value = GPIO.input(23)

print value

GPIO.cleanup()

Cosi' si puo' vedere se il pulsante e' premuto o rilasciato.

Di solito pero' si vuole compiere un'azione quando il pulsante viene premuto. E' in teoria possibile fare un ciclo continuo e continuare a leggere il valore del pulsante. Quando si rileva che il valore e' cambiato si compie l'azione corrispondente. Questo approccio e' molto inefficiente perche' la CPU continua a lavorare per non fare nulla, per attendere che avvenga qualcosa (si chiama busy wait).

Il programma che segue e' un esempio di pulsante passo-passo realizzato via software. Per fare l'esperimento collegate il pulsante al GPIO23/pin16 e un led con la resistenza di limitazione a GPIO22/pin15.

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import threading
import time

sem = threading.Semaphore(0)

time23 = 0

def cb23():
        global time23
        time23 = time.time()
        sem.release()

GPIO.setmode(GPIO.BCM)

GPIO.setup(23, GPIO.IN)
GPIO.setup(22, GPIO.OUT, initial=GPIO.LOW)

GPIO.add_event_detect(23, GPIO.BOTH, callback=cb23)

value23 = 0
timeout = None
value22 = 0
while True:
        try:
                new=sem.acquire(timeout=timeout)
        except:
                break

        now = time.time()
        if now - time23 > 0.05:
                tmpvalue23 = GPIO.input(23)
                if tmpvalue23 != value23:
                        value23 = tmpvalue23
                        if value23 == 1:
                                value22 = 1 - value22
                                if value22 == 1:
                                        GPIO.output(22, GPIO.HIGH)
                                else:
                                        GPIO.output(22, GPIO.LOW)

        if new:
                timeout = 0.05
        else:
                timeout = None

print("cleanup")
GPIO.cleanup()

E' un programma un po' piu' complesso, quindi lo spiego. La funzione cb23 verra' richiamata ogni volta che il sistema rivela un fronte di salita (da 0 a 1) o di discesa (da 1 a 0) nel segnale del pulsante. Questa funzione semplicemente annota il tempo dell'evento in time23 e risveglia il programma principale con la chiamata sem.release().

Il programma principale definisce GPIO23 come input e GPIO22 come output poi definisce cb23 come funzione da richiamare ogni qual volta avviene una variazione nel valore di GPIO23.

Nel ciclo principale si attende un evento (acquire) o lo scadere di un timeout, inizialmente infinito.

Quando la acquire esce new e' vero se e' arrivato un evento, falso se e' uscito per timeout.

Se lo stato del gpio23 e' stabile da piu' di 50ms si analizza se ha assunto un nuovo valore e si agisce di conseguenza (in questo caso ogni volta che diventa 1 si inverte il valire di value22).

Se il loop degli eventi e' stato attivato da un evento si pone il timeout a 50ms, altrimenti se era un timeout, il timeout viene posto a infinito (None).

Poniamo che il valore del pulsante sia 0 e che io prema il pulsante. Al primo impulso viene risvegliato il loop principale ma il valore attuale non viene considerato, pero' viene messo il timeout a 50ms. Per effetto dei contatti fisici imperfetti la funzione cb puo' essere chiamata molte volte (mettete una stampa di controllo se non ci credete!). Tutte le volte verra' risvegliato il loop del programma principale e spostato il timeout a 50ms dall'ultimo impulso. Solo quando per 50ms non succedera' nulla la funzione acquire uscira' per timeout e quindi si puo' andare a vedere quale e' il valore stabile di gpio23.

Questo programma potrebbe anche controllare piu' pulsanti in parallelo. Occorre una funzione cb per ogni pulsante che aggiorni una variabile time associata.

Tutto il blocco "if now - time23 > 0.05:" deve essere replicato per ogni pulsante associando l'azione corrispondente alla pressione.


Ora un esempio differente senza semafori utilizzabile come modulo:

#!/usr/bin/python

import RPi.GPIO as GPIO
import time

class killcontactbounce(object):
    """
    Define the management of gpio events and try to manage contact
    bounce.
    Contact bounce (also called chatter) is a common problem
    with mechanical switches and relays. Switch and relay contacts are
    usually made of springy metals. When the contacts strike together,
    their momentum and elasticity act together to cause them to bounce
    apart one or more times before making steady contact. The result
    is a rapidly pulsed electric current instead of a clean transition
    from zero to full current. The effect is usually unimportant in
    power circuits, but causes problems in some analogue and logic
    circuits that respond fast enough to misinterpret the on-off pulses as a data stream.
    """

    def __init__(self,pin,myfunction=None,eventstatus=None,timeout=0.5):
        """
        pin : pin to manage
        myfunction : function to call on events cleaned by contact bounce
        eventstatus : event to manage (1/0/None) if None both events will be take in account
        timeout : timeout period for clean contact bounces
        """
        self.pin=pin
        self.eventstatus=eventstatus
        self.dt=time.time()
        self.myfunction=myfunction
        self.timeout=timeout

        # set up GPIO input with pull-up control
        #   (pull_up_down be PUD_OFF, PUD_UP or PUD_DOWN, default PUD_OFF)
        if self.eventstatus == 0:
            GPIO.setup(self.pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        elif self.eventstatus == 1:
            GPIO.setup(self.pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        else:
            GPIO.setup(self.pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)

        self.oldpinstatus=GPIO.input(self.pin)
        GPIO.add_event_detect(self.pin, GPIO.BOTH, callback=self.manageevent)


    def manageevent(self):
        newpinstatus=GPIO.input(self.pin)
        if newpinstatus != self.oldpinstatus:
            #print "status changed",newpinstatus
            self.oldpinstatus=newpinstatus

            now=time.time()
            if now- self.dt > self.timeout :
                self.dt=now
                if newpinstatus == self.eventstatus or self.eventstatus is None:
                    #print "My Event happen! ",self.eventstatus
                    if self.myfunction is not None:
                        self.myfunction(self.pin,self.eventstatus)


    def delete(self):

        GPIO.remove_event_detect(self.pin)
        GPIO.setup(self.pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)


def main():

    def myfunction(pin,pinstatus):
        print "I get my new pin status : ",pin,pinstatus

    GPIO.cleanup()

    # to use Raspberry BCM pin numbers
    GPIO.setmode(GPIO.BCM)

    kcb18=killcontactbounce(pin=18,myfunction=myfunction,eventstatus=0)
    kcb23=killcontactbounce(pin=23,myfunction=myfunction,eventstatus=1,timeout=0.1)

    while True:
        try:
            print "I am sleeping ..."
            time.sleep(3)
        except:
            print
            print "Exit"
            kcb18.delete()
            kcb23.delete()
            break

    # to reset every channel that has been set up by this program to INPUT 
    # with no pullup/pulldown and no event detection.
    GPIO.cleanup()

if __name__ == '__main__':
    main()  # (this code was run as script)

Questo esempio monitora due pin, il 18 e 23 contemporaneamente; per il pin 18 la funzione myfunction viene chiamata quando il pin va basso mentre per il 23 quando il pin va alto. Sempre la funzione myfunction viene chiamata con le informazioni di pin e stato. In questo esempio vengono attivate automaticamente le resistenze di pull up/down della gpio alla condizione opposta di quella su cui si attiva l'interrupt di gestione di cambio di stato del pin; il circuito con le due resistenze di cui sopra si rende quindi inutile. Il tempo di eliminazione dei rimbalzi è parametrizzato. Di fatto usando questo modulo il tutto si riduce a: kcb18=killcontactbounce(pin=18,myfunction=myfunction,eventstatus=0) ossia la funzione myfunction verrà chiamata senza rimbalzi quando il pin 18 passa a stato basso.

Bello questo programma ? Non saprei ma il problema è che non funziona quando vogliamo gestire piu' di un pin ... ossia è solo il pin 18 che viene correttamente gestito, il pin 23 viene allegramente ignorato ... Peccato, apriamo la caccia al bug!

E il bug è già stato trovato e risolto: http://code.google.com/p/raspberry-gpio-python/issues/detail?id=28

Qualche debianista potrebbe produrre il pacchetto per rasbian con la patch applicata? Il pacchetto rpm per Fedora remix è già disponibile, scaricalo da www.comodino.org/file/index.php?page=viewdir&filepath=%2Fpubblico%2Fraspberry%2F

in C

Si puo' partire ragionando su questo codice che alla base del modulo python: da questo link