Leggere un pulsante su GPIO con antirimbalzo software
Come si collega un pulsante
Un circuito per collegare un pulsante e' il seguente:
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. (controllato funzionante 2013.12.10, renzo)
#!/usr/bin/env python3 import RPi.GPIO as GPIO import threading import time sem = threading.Semaphore(0) time23 = 0 def cb23(channel): 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.
un pulsante per lo spegnimento
#!/usr/bin/env python3 import subprocess import RPi.GPIO as GPIO channel=25 GPIO.setmode(GPIO.BCM) GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.wait_for_edge(channel, GPIO.FALLING) subprocess.call(['/sbin/halt'])
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.
Attenzione c'è un bug già risolto: http://code.google.com/p/raspberry-gpio-python/issues/detail?id=28
Quindi per far funzionare il tutto occorre la versione >= 0.5.1a che non so quando verrà distribuita in rasbian; eventualmente 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
ATTENZIONE
Le API di raspberry-gpio-python si modificano continuamente quindi gli esempi riportati non funzionano piu' con l'ultima versione del pacchetto ... La funzione di antirimbalzo software è stata implementata nel pacchetto quindi l'utente puo' semplicemente attivarla. Per seguire gli sviluppi delle API e per creare una comoda interfaccia DBUS si consiglia di seguire il progetto su SourceForge raspdbusgpiopy
in C
Si puo' partire ragionando su questo codice che alla base del modulo python: da questo link