28 gennaio 2019

Costruisco un GAMEPAD con Raspberry Pi #01 - Bottoni e LED!


Benvenuti nel primo episodio della serie #PiPAD: costruisco un GAMEPAD con Raspberry Pi!

Dopo aver creato un piccolo robottino (#Ro-Pi) in grado di compiere diverse operazioni in autonomia oppure dietro diretti comandi da parte dell'utente, ora proviamo a costruire (sempre previo utilizzo di un single board computer della fondazione inglese raspberry pi) un PAD che lo possa controllare (oppure che possa essere usato per altri compiti). In particolare, in questa prima puntata vedremo come organizzare la struttura, e come collegare e sfruttare due bottoni ed un piccolo LED RGB che fa da feedback real-time alle azioni svolte. Il tutto gestito via codice Python su un Raspberry Pi Zero (wireless).

Prima di entrare nel merito, vi riporto il video abbinato a questo articolo che ho pubblicato sul mio canale YouTube nel quale viene mostrato il risultato in esecuzione e spiegata a grandi linee l'idea alla base del progetto:


Il primo step di questa nuova avventura è quello di avviare la costruzione di un controller custom che abbia vari sensori (bottoni, stick, e via discorrendo) i quali possano comandare da remoto altri progetti (come #Ro-Pi).


Componenti hardware necessarie


Per costruire il nostro GAMEPAD occorreranno:
  • 2 bottoni minimo per avere comandi diretti
  • 1 LED RGB (con eventualmente resistori) per aiutare con i feedback in tempo reale
  • 1 breadboard a cui collegare tutti i sensori
  • cavi a sufficienza per tutte le connessioni da sensori a breadboard e da breadboard al Pi
  • (opzionale) 1 level shifter per gestire in futuro le eventuali differenze di voltaggio tra i PIN del GPIO e quelli dei sensori

Tutte le componenti utilizzate in questo progettino sono estremamente economiche, a tal punto che solitamente vengono vendute in kit che comprendono oltre ad esse anche tanti altri piccoli sensori. Conviene puntare direttamente ad uno di questi kit economici in vendita online: in essi, infatti, è possibile trovare bottoni, LED, cavi, breadboard e via discorrendo. Occorre solamente fare attenzione che nello specifico kit di interesse siano effettivamente presenti tutte le componenti che utilizzeremo.



Un bottone (o push switch) è un semplice pulsantino che permette di aprire o chiudere il circuito quando premuto o rilasciato. Il bottone va incastonato nella breadboard e vanno considerati soltanto 2 PIN (anche nel caso si utilizzi uno switch con 4 PIN, poiché connessi tra loro all'interno del componente stesso): un PIN deve essere collegato al GND e l'altro ad un PIN del Pi (nel codice demo sono stati scelti i PIN 8 e 10 per comunicare con i due bottoni presenti).


Un LED RBG consente di visualizzare fino a 7+1 colori differenti (rosso, verde, blu, giallo, ciano, viola, bianco/tutti i colori accesi, e nero/tutti i colori spenti) e quindi può essere un ottimo strumento di feedback su che cosa sta accadendo (soprattutto visto che il GAMEPAD andrà utilizzato scollegato da un monitor esterno sul quale invece si possono mostrare messaggi di stato, sopratutto in fase di sviluppo).
Nota: l'uso di LED "sfusi" richiede la presenza di resistori (R1, R2 e R3, una per colore) per evitare di danneggiare il componente stesso. I LED montati direttamente su di un chip, invece, solitamente contengono già tutto il circuito necessario e quindi non sarà necessario aggiungere resistenze di alcuna natura.
Un LED RGB ha 4 PIN: uno per ogni colore primario (R, G, B) che va collegato ad un rispettivo PIN sulla testata GPIO del Pi (nel codice demo sono stati usati i PIN 29, 31 e 33), ed uno per il GND.


Un level shifter (opzionale al momento) consente di far dialogare senza problemi i PIN del Raspberry Pi che lavorano a 3.3V con PIN di sensori esterni che lavorano invece a 5V. Al momento non si collegherà alcun componente al GAMEPAD che ne richieda la presenza, ma può essere utile iniziare a pensare fin da subito ad una disposizione "comoda" sulla breadboard anche per questo chip: in futuro potrebbe diventare indispensabile averne uno! Ad ogni modo, in questa fase, non si effettueranno collegamenti tra level shifter e Pi/sensori.


Nota: in generale l'idea è quella di organizzare la breadboard in modo che sia comoda per essere impugnata con le mani, e che i cavi ed i sensori presenti non infastidiscano la presa. Perciò conviene far passare tutti i fili in una sola direzione (opposta a quella della prevista impugnatura) e posizionare il LED ed il level shifter in disparte, mentre i bottoni vicini all'area di presa.
Nota 2: altra cosa da tenere in considerazione è la possibilità di sfruttare le corsie laterali della breadboad per unificare il GND e le fonti di corrente 3.3V e 5V. In questo modo ci saranno due soli fili tra breadboard (-) e PIN GND sul Pi ed altrettanti singoli fili tra un PIN 3.3V ed un PIN 5V sul Pi e le due corsie (+) sulla board. Così ogni sensore che necessita alimentazione e/o che deve chiudere il circuito potrà collegarsi alle due corsie e non non avrà necessità di un PIN dedicato sulla limitata testata GPIO.
Nota 3: al momento la questione alimentazione per il Raspberry Pi non viene presa in considerazione e sarà argomento di future puntate. Per queste fasi iniziali sarà possibile tenere il SBC in carica collegandolo ad un powerbank o direttamente ad una presa di corrente.
Similmente, il controller dovrà controllare da remoto altri device, quindi esso avrà necessità di connettività: se si utilizza un Pi Zero W non ci sono altre considerazioni da fare (se non assicurarsi di configurare correttamente la connessione wireless); se si utilizza un Pi Zero, invece, allora sarà necessario installare un dongle WiFi, il più piccolo possibile per evitare di rendere il GAMEPAD scomodo da utilizzare. Per queste fasi iniziali, comunque, il progetto può essere collegato al router anche tramite cavo di rete (ovviamente previo utilizzo di un adattatore micro USB - Ethernet).


Il software


Per quel che riguarda il sistema operativo da impiegare, Raspbian OS Lite è sicuramente la miglior scelta possibile poiché è l'OS per Raspberry Pi Zero più ottimizzato e perché contiene tutte le librerie necessarie per lo scopo. L'unica operazione da fare una volta inserito il sistema su microSD e avviato il Pi è installare la libreria Python per il controllo della testata GPIO. Sarà sufficiente digitare il comando sudo apt-get install python-rpi.gpio python3-rpi.gpio e il sistema di occuperà di configurare tutto il necessario.

Come funzionano bottoni e LED RGB? Come possono essere controllati? L'idea è quella di sfruttare la stessa logica che si è impiegata per la creazione di #Ro-Pi: una classe per la gestione di ogni componente, più un file generico di configurazione ed una applicazione principale che sfrutti i sensori tramite le suddette classi dedicate. Per maggiori dettagli sulla struttura rimando sia al codice di #Ro-Pi che ho postato sul mio account github, sia al video che trovate riportato ad inizio articolo.

In ogni caso, la seguente classe consente di controllare i due bottoni e può sia essere richiamata da applicazioni esterne (come quella principale), sia essere richiamata direttamente per test:

# import librerie GPIO, utilities, multiprocessing, configurazione
import RPi.GPIO as _g
import time as _t
import multiprocessing as _mp
from config import button_conf as _conf

# classe PIPAD BUTTON
class PiPadButton:

     # costruttore che prende in input un gestore LED
    def __init__(self, led) :

         # impostazione dei PIN
        if _conf['DEVEL_LOG'] :
            print("Initializing Button...")
            print("> button 1", _conf['btn1_pin'])
            print("> button 2", _conf['btn2_pin'])
        _g.setmode(_g.BOARD)
        _g.setup(_conf['btn1_pin'], _g.IN, pull_up_down=_g.PUD_UP)
        _g.setup(_conf['btn2_pin'], _g.IN, pull_up_down=_g.PUD_UP)

         # variabili interne per LED e per i bottoni
        self.led = led
        self.btn1 = _mp.Value('i', 1)
        self.btn2 = _mp.Value('i', 1)

         # processi per la gestione dei bottoni
        if _conf['DEVEL_LOG'] : print("Initializing processes...")
        self.process1 = _mp.Process(target=self.B1)
        self.process2 = _mp.Process(target=self.B2)
        self.process1.start()
        self.process2.start()

         if _conf['DEVEL_LOG'] : print("...init done!")

     # pulizia in fase di terminazione
    def terminate(self) :
        if _conf['DEVEL_LOG'] : print("Button termination...")
        self.process1.terminate()
        self.process2.terminate()

     # stato dei bottoni
    def isButton1Pressed(self) :
        return self.btn1.value == False
    def isButton2Pressed(self) :
        return self.btn2.value == False

     # processi controllo dei bottoni
    def B1(self) :
        self.worker(_conf['btn1_pin'], self.btn1, 0)
    def B2(self) :
        self.worker(_conf['btn2_pin'], self.btn2, 1)

     # metodo di controllo (pin del bottone, stato, colore LED)
    def worker(self, pin, btn, color) :
        while True :
            btn.value = _g.input(pin)
            if not self.led == False :
                if btn.value == False :
                    if color == 0 :
                        self.led.redOn()
                    else :
                        self.led.blueOn()
                else :
                    if color == 0 :
                        self.led.redOff()
                    else :
                        self.led.blueOff()
            _t.sleep(_conf['btn_wait_time'])

 # Codice per DEBUG
if __name__ == "__main__":
    print ("Welcome! Testing Buttons:")
    button = PiPadButton(False)
    try:
        while True:
            if (button.isButton1Pressed() == True) :
                print("BTN1 pressed!")
            if (button.isButton2Pressed() == True) :
                print("BTN2 pressed!")
            _t.sleep(_conf['btn_wait_time'])
    except KeyboardInterrupt:
        pass
    print ("Goodbye!")
    button.terminate()
    _g.cleanup()

Nota: il codice di test resterà in attesa della pressione di uno dei due pulsanti (o di tutti e due) mostrando a video un messaggio relativo al bottone interessato dall'azione dell'utente. Come è facilmente intuibile dal codice, poi, è necessario utilizzare un processo per ogni bottone (cosicché sia possibile catturarne lo stato in maniera indipendente), ed in più (se viene passato il gestore LED come parametro in fase di instaziamento della classe) il LED verrà colorato di colore rosso alla pressione del primo bottone e di blu alla pressione del secondo (premendoli assieme il LED diverrà viola).

Per quanto riguarda il LED RGB, invece, la seguente classe ne consente la gestione completa (e anche in questo caso il codice è ambivalente, cioè può essere importato in applicazioni esterne oppure avviato direttamente per test):

# import librerie GPIO, utilities e configurazione
import RPi.GPIO as _g
import time as _t
from config import led_conf as _conf

# classe PIPAD LED
class PiPadLED:

     # costruittore
    def __init__(self) :

         # impostazione dei PIN
        if _conf['DEVEL_LOG'] :
            print("Initializing LEDs...")
            print("> RED:", _conf['red_pin'])
            print("> GREEN:", _conf['green_pin'])
            print("> BLUE:", _conf['blue_pin'])
        _g.setmode(_g.BOARD)
        _g.setup(_conf['red_pin'], _g.OUT) # output, blink red
        _g.setup(_conf['green_pin'], _g.OUT) # output, blink green
        _g.setup(_conf['blue_pin'], _g.OUT) # output, blink blue

         if _conf['DEVEL_LOG'] : print("...init done!")

     # attivazione/disattivazione del PIN
    def activate(self, p) :
        _g.output(p, _g.HIGH)
    def deactivate(self, p) :
        _g.output(p, _g.LOW)

     # azioni possibili
    def allOff(self) :
        self.redOff()
        self.greenOff()
        self.blueOff()
    def redOn(self) :
        self.activate(_conf['red_pin'])
    def redOff(self) :
        self.deactivate(_conf['red_pin'])
    def greenOn(self) :
        self.activate(_conf['green_pin'])
    def greenOff(self) :
        self.deactivate(_conf['green_pin'])
    def blueOn(self) :
        self.activate(_conf['blue_pin'])
    def blueOff(self) :
        self.deactivate(_conf['blue_pin'])
    def yellowOn(self) :
        self.redOn()
        self.greenOn()
    def yellowOff(self) :
        self.redOff()
        self.greenOff()
    def cyanOn(self) :
        self.greenOn()
        self.blueOn()
    def cyanOff(self) :
        self.greenOff()
        self.blueOff()
    def magentaOn(self) :
        self.redOn()
        self.blueOn()
    def magentaOff(self) :
        self.redOff()
        self.blueOff()
    def whiteOn(self) :
        self.redOn()
        self.greenOn()
        self.blueOn()
    def whiteOff(self) :
        self.redOff()
        self.greenOff()
        self.blueOff()

# Codice per DEBUG
if __name__ == "__main__":
    print ("Welcome! Testing LED:")
    time = 1
    led = PiPadLED()
    print ("White...")
    led.whiteOn()
    _t.sleep(time)
    led.whiteOff()
    print ("Magenta...")
    led.magentaOn()
    _t.sleep(time)
    led.magentaOff()
    print ("Cyan...")
    led.cyanOn()
    _t.sleep(time)
    led.cyanOff()
    print ("Yellow...")
    led.yellowOn()
    _t.sleep(time)
    led.yellowOff()
    print ("Blue...")
    led.blueOn()
    _t.sleep(time)
    led.blueOff()
    print ("Green...")
    led.greenOn()
    _t.sleep(time)
    led.greenOff()
    print ("Red...")
    led.redOn()
    _t.sleep(time)
    led.redOff()
    print ("Goodbye!")
    led.allOff()
    _g.cleanup()

Nota: in questo caso il codice di test non farà altro che provare uno alla volta i 7 colori ottenibili dalla combinazione di rosso, verde e blu.

All'interno del codice delle due classi si fa riferimento alla configurazione, essa è inserita all'interno di un apposito file esterno e contiene i parametri generali come i numeri dei PIN a cui i sensori sono connessi e via discorrendo:

configuration = {
    'DEVEL_LOG' : False,
}

 led_conf = {
    'DEVEL_LOG' : False,
    'red_pin' : 29,
    'green_pin' : 31,
    'blue_pin' : 33
}

 button_conf = {
    'DEVEL_LOG' : False,
    'btn_wait_time' : 0.1,
    'btn1_pin' : 10,
    'btn2_pin' : 8
}

Infine, l'applicazione principale non farà altro che integrare le due classi appena descritte per usarle all'interno di un ciclo infinito in cui si rimane in attesa dell'input da parte dell'utente:

# import GPIO, configurazione e classi dei sensori
import RPi.GPIO as _g
import time as _t
from config import configuration as _conf
from PiPadButton import PiPadButton
from PiPadLED import PiPadLED

# inizializzazione componenti
if _conf['DEVEL_LOG'] : print("Starting...")
led = PiPadLED()
button = PiPadButton(led)
if _conf['DEVEL_LOG'] : print("Started!")

# ciclo infinito in attesa dell'input dell'utente
try:
    if _conf['DEVEL_LOG'] : print("Waiting for input!")
    while True:
        if (button.isButton1Pressed() == True) :
            if _conf['DEVEL_LOG'] : print("BTN1 pressed!")

         if (button.isButton2Pressed() == True) :
            if _conf['DEVEL_LOG'] : print("BTN2 pressed!")

         _t.sleep(0.2)

# cattura CTRL+C per uscita pulita
except KeyboardInterrupt:
    pass
finally:
    if _conf['DEVEL_LOG'] : print("Exiting!")
    button.terminate()
_g.cleanup()

Nota: al momento il codice non fa molto altro se non verificare la pressione dei bottoni da parte dell'utilizzatore (con tanto di messaggio a video e accensione del LED RGB come feedback).


Conclusioni


Bene, termina qui il primo episodio della serie!

Nelle prossime puntate aggiungeremo uno stick e poi altri sensori. In più inizieremo a vedere come utilizzare da remoto il nostro GAMEPAD!


A presto :-)

Nessun commento:

Posta un commento