2 aprile 2019

Costruisco un GAMEPAD con Raspberry Pi #03 - Rotary Encoder e Wi-Fi!


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

Nelle prime due puntate abbiamo collegato diversi sensori (dapprima bottoni e LED e poi lo Stick analogico) ed oggi aggiungeremo un ulteriore sistema di controllo: un Rotary Encoder! Come sempre il tutto è gestito via codice Python su un Raspberry Pi Zero (wireless). E proprio perché questa guida si basa sul modello senza connettività Wi-Fi, finalmente collegheremo anche un dongle USB che consentirà il collegamento senza fili.

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 spiegato a grandi linee il funzionamento del nuovo sensore:


Il terzo step di questa nuova avventura è perciò quello di aggiungere il nuovo sistema di controllo e la connettività al Raspberry Pi Zero.


Componenti hardware necessarie


Per aggiungere Rotary Encoder e Wi-Fi al nostro GAMEPAD occorreranno:
  • 1 Rotary Encoder
  • 1 dongle USB Wi-Fi
  • 1 adattatore da USB type-A a type-B
  • 1 hub USB con porta Ethernet
  • cavi a sufficienza per tutte le connessioni dal sensore al Pi
Come sempre, le componenti utilizzate in questo terzo episodio sono reperibili con facilità online, e sono molto economiche.



Un Rotary Encoder è un componente che presenta una manopola in grado di ruotare in senso orario ed antiorario. La rotazione avviene a scatti, un click (o scatto) per volta, ed in più la manopola stessa può essere premuta trasformando di fatto tutto il sensore in un bottone.
Un Encoder di questo tipo funziona tramite due contatti interni che ad ogni scatto chiudono od aprono un rispettivo circuito interno. I contatti sono due poiché, essendo posizionati in modo difforme, rendono possibile aprire o chiudere i rispettivi circuiti o all'unisono oppure in modo alternato, e ciò consente di capire se la manopola sia stata ruotata in un senso oppure nell'altro.



Un sensore di questo tipo può tornare utile per controllare, ad esempio, interfacce grafiche con liste di elementi oppure, più semplicemente, per aumentare o diminuire un valore in base a quanto si ruoti la manopola. E poi la manopola stessa, come detto, può essere premuta quindi tutto il sensore può fungere da bottone e questo ci permette di aggiungere un quarto elemento di input (dopo i due bottoni e la pressione sullo Stick) al nostro controller.

Un Rotary Encoder ha 5 PIN: uno per il GND, uno per la corrente da 3V, uno per gestire la pressione e gli ultimi due per catturare i valori (Clock e DT) che servono per capire il verso della rotazione. GND e tensione vanno collegati sulla breadboard nell'apposito lato in cui si trova la 3V, gli altri 3 PIN invece possono anche essere connessi direttamente al Raspberry Pi. Nell'esempio essi sono stati collegati ai PIN 11, 12 e 13.



Un dongle Wi-Fi consente di aggiungere connettività wireless al Pi Zero che ne è sprovvisto. Il consiglio è di scegliere un componente che sia il più piccolo possibile così da non dare noia durante l’utilizzo di PiPAD. Purtroppo non tutti i dongle sono compatibili con Raspberry Pi, ed in più il Pi Zero ha la scomodità di avere solamente una porta e anche di tipo microUSB: ciò significa che non sarà possibile collegare direttamente il dongle (ma va utilizzare un piccolo adattatore da type-A a type-B) e soprattutto non sarà possibile collegare più periferiche contemporaneamente, a meno di sfruttare un hub USB (che sarà quindi inevitabile per la prima configurazione della rete Wi-Fi).
Nota: l'hub USB dotato di porta Ethernet consente di collegare il Pi Zero via cavo al router e allo stesso tempo di inserire anche il dongle Wi-Fi. Una volta entrati via SSH da remoto nel Pi Zero si potranno effettuare le configurazioni necessarie e verificare il corretto funzionamento del Wi-Fi. A quel punto si potrà rimuovere l’hub e ricollegare il solo dongle Wi-Fi USB per liberarsi definitivamente dalla necessità di utilizzare PiPAD con connessione via cavo e solamente nei pressi del router.

Per verificare che il dongle sia stato correttamente riconosciuto dal sistema operativo basterà eseguire il comando dmesg | more e scorrere tra tutti i messaggi a video cercando informazioni relative allo stesso. Se ci sono ed è tutto OK si può digitare sudo raspi-config ed entrare nella sezione Network Options, quindi Wi-Fi, impostate la regione ed infine inserire il SSID (nome) della propria rete con relativa password. Completato il processo, basterà riavviare il Pi e poi digitare ifconfig per verificare che oltre alla interfaccia eth0 anche quella wlan0 abbia un IP assegnato. Se è questo il caso allora la configurazione sarà stata ultimata con successo, in alternativa sarà necessario configurare tutto manualmente. Sul sito ufficiale della fondazione Raspberry Pi è presente una sezione che spiega in dettaglio tutte le strade percorribili.


Il software


Arrivati fin qui, le componenti saranno installate e pronte, e quindi si può passare al codice di controllo.

Come per tutti gli altri sensori, anche per catturare lo stato del Rotary Encoder sfrutteremo una classe specifica (PiPadRotary.py) ed inseriremo i valori di configurazione nell'apposito file config.py condiviso. La classe, al pari delle altre, potrà essere inclusa nel codice principale di gestione di PiPAD oppure utilizzata direttamente per test:

# GPIO
import RPi.GPIO as _g
# time utilities
import time as _t
# multiprocessing
import multiprocessing as _mp

# configuration
from config import rotary_conf as _conf

# PIPAD ROTARY ENCODER
class PiPadRotary:

    # initialize
    def __init__(self, led) :

        # set PINs on BOARD
        if _conf['DEVEL_LOG'] :
            print("Initializing Rotary Encoder...")
            print("> Button:", _conf['btn_pin'])
            print("> Clock:", _conf['clock_pin'])
            print("> DT:", _conf['dt_pin'])
        _g.setmode(_g.BCM)
        _g.setup(_conf['btn_pin'], _g.IN, pull_up_down=_g.PUD_UP)
        _g.setup(_conf['clock_pin'], _g.IN, pull_up_down=_g.PUD_DOWN)
        _g.setup(_conf['dt_pin'], _g.IN, pull_up_down=_g.PUD_DOWN)

        # initialize LED
        self.led = led

        # values
        self.btn = _mp.Value('i', 1)
        self.clock = _mp.Value('i', 0)
        self.dt = _mp.Value('i', 0)
        self.counter = _mp.Value('i', 0)

        # processes
        if _conf['DEVEL_LOG'] : print("Initializing processes...")
        self.process1 = _mp.Process(target=self.B)
        self.process2 = _mp.Process(target=self.worker)
        self.process1.start()
        self.process2.start()

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

    # terminate
    def terminate(self) :
        if _conf['DEVEL_LOG'] : print("Rotary Encoder termination...")
        self.process1.terminate()
        self.process2.terminate()

    # get counter
    def getCounter(self) :
        return self.counter.value

    # check button pressed
    def isButtonPressed(self) :
        return self.btn.value == False

    # control button
    def B(self) :
        while True :
            self.btn.value = _g.input(_conf['btn_pin'])
            if not self.led == False :
                if self.btn.value == False :
                    self.led.yellowOn()
                else :
                    self.led.yellowOff()
            _t.sleep(_conf['btn_wait_time'])

    # control rotary
    def worker(self) :
        self.clock.value = _g.input(_conf['clock_pin'])
        while True :
            clock = _g.input(_conf['clock_pin'])
            dt = _g.input(_conf['dt_pin'])
            if _conf['DEVEL_LOG'] : print ("Clock: ", self.clock.value)
            if _conf['DEVEL_LOG'] : print ("DT: ", self.dt.value)
            if clock != self.clock.value :
                if dt != clock :
                    if _conf['DEVEL_LOG'] : print ("!=")
                    self.counter.value += 1
                else :
                    if _conf['DEVEL_LOG'] : print ("==")
                    self.counter.value -= 1
            self.clock.value = clock
            self.dt.value = dt
            _t.sleep(_conf['clock_wait_time'])

# DEBUG
if __name__ == "__main__":
    print ("Welcome! Testing Rotary Encoder:")
    rotary = PiPadRotary(False)
    try :
        while True:
            print ("Counter: ", rotary.getCounter())
            if (rotary.isButtonPressed()) :
                print ("Rotary BTN pressed!")
            _t.sleep(_conf['btn_wait_time'])
    except KeyboardInterrupt:
        pass
    print ("Goodbye!")
    rotary.terminate()
    _g.cleanup()

Nota: il metodo lavoratore si occuperà di verificare lo stato della manopola. Ad ogni spostamento verranno controllati i valori di Clock e DT per verificarne uguaglianza o differenza. Se i valori coincidono allora la manopola sarà stata ruotata ad esempio in senso orario, se differiscono in senso antiorario. In generale si utilizzano due processi, uno per gestire la pressione della manopola e l'altro per il calcolo della rotazione. In più (se viene passato il gestore LED come parametro in fase di instaziamento della classe) il LED verrà colorato di colore giallo alla pressione.


Nel file di configurazione, invece, andranno inseriti i parametri generali:

[...] # altri valori nella configurazione

rotary_conf = {
    'DEVEL_LOG' : False,
    'btn_wait_time' : 0.1,
    'clock_wait_time' : 0.01,
    'btn_pin' : 27,
    'clock_pin' : 17,
    'dt_pin' : 18
}


Infine, nell'applicazione principale non resterà altro che integrare la classe appena descritta per usarla assieme alle altre già presenti in un ciclo infinito in cui si rimane in attesa dell'input da parte dell'utente:

# GPIO
import RPi.GPIO as _g
import time as _t

# configuration
from config import configuration as _conf
# buttons
from PiPadButton import PiPadButton
# leds
from PiPadLED import PiPadLED
# sticks
from PiPadStick import PiPadStick
# rotary
from PiPadRotary import PiPadRotary

if _conf['DEVEL_LOG'] : print("Starting...")
led = PiPadLED()
button = PiPadButton(led)
stick = PiPadStick(led)
rotary = PiPadRotary(led)
if _conf['DEVEL_LOG'] : print("Started!")

try:
    if _conf['DEVEL_LOG'] : print("Waiting for input!")
    counter = 0
    while True:
        if (button.isButton1Pressed() == True) :
            if _conf['DEVEL_LOG'] : print("BTN1 pressed!")

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

        if (stick.isButtonPressed() == True) :
            if _conf['DEVEL_LOG'] : print("BTN Stick pressed!")

        position = stick.position()
        if (position is not 0) :
            if _conf['DEVEL_LOG'] : print("Stick position: ", position)

        if (rotary.isButtonPressed() == True) :
            if _conf['DEVEL_LOG'] : print("BTN Rotary pressed!")

         if rotary.getCounter() != counter :
            counter = rotary.getCounter()
            print("Rotary counter: ", counter)

        _t.sleep(0.2)

# capture interruption
except KeyboardInterrupt:
    pass

# exit cycle
finally:
    if _conf['DEVEL_LOG'] : print("Exiting!")
    button.terminate()
    stick.terminate()
    rotary.terminate()

# clean
_g.cleanup()

Nota: al momento il codice non fa molto altro se non verificare la pressione del Rotary Encoder o la sua rotazione (in grassetto le aggiunte rispetto alle precedenti puntate), oltre che continuare a gestire bottoni, LED RGB di feedback e Stick.


Conclusioni


Bene, termina qui il terzo episodio della serie!

Dopo aver inserito diversi sensori di controllo, nella prossima puntata vedremo come sfruttarli per controllare da remoto altri dispositivi, come RoPi.


A presto :-)

Nessun commento:

Posta un commento