16 novembre 2017

Creo il mio ROBOT con Raspberry Pi #002 - Distanze ed Ostacoli!


Benvenuti nel secondo episodio della serie #Ro-Pi: creo il mio ROBOT con Raspberry Pi!

Continua la serie di appuntamenti con il fai-da-te smanettoso in compagnia dei single board computer della fondazione inglese raspberry pi. In particolare, partendo da quanto costruito finora, oggi vedremo come ampliare il nostro robottino (in grado di muoversi sul piano) con LED di feedback e sensori di supporto.

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 di questa prima estensione al progetto:


Il secondo step di questa avventura, perciò, è quello di aggiungere un punto centralizzato di feedback per l'utilizzatore nonché tentare di calcolare quanto spazio rimane tra #Ro-Pi ed eventuali ostacoli, così da fermare automaticamente la corsa del robot stesso se si supera una determinata distanza di sicurezza ed evitare che la macchinina vada ad urtare qualcosa.


Componenti hardware necessarie


Una volta realizzata la base vista nella prima puntata, per ampliare il nostro robot occorreranno:
  • una breadboard che entri fisicamente sulla superficie del robottino (se non fosse già stata inserita in precedenza)
  • un LED RGB e 3 resistori (1 da almeno 100 ohm e 2 da 150 ohm, io comunque ne ho utilizzati 3 da 220 ohm)
  • uno o più sensori di distanza, e per ognuno di essi 2 resistori (1 da 1000 ohm e 1 da 2000 ohm, io ne ho utilizzati vari posti in parallelo così da raggiungere gli ohm previsti)


La breadboard è uno strumento ottimo per creare prototipi di circuiti elettrici poiché non richiede saldature ed è completamente riutilizzabile. Nel nostro caso sarà indispensabile perché ci permetterà di collegare sensori e LED al raspberry pi, dato che essi -come vedremo tra poco- non possono essere connessi direttamente ai PIN del GPIO.
È importante capire come funziona una breadboard per evitare di sovraccaricare il raspberry pi o ciò che ad essa viene collegato.


I fori presenti sulla superficie sono collegati tra loro come nella figura qui sopra, il che significa che la corrente fluirà in essi seguendo le linee o le colonne. Le aree sono separate tra loro: quelle esterne sono solitamente utilizzate per fornire corrente (+) e chiudere i circuiti (-), mentre le colonne poste nelle aree interne sono a disposizione per collegare sensori e quant'altro così da rendere possibile lo scambio di informazioni tra pi e componenti esterne.



Capito il funzionamento basilare di una breadboard, possiamo sfruttarla per i nostri scopi. Il LED RGB può fungere da feedback per l'utente, cioè è possibile sfruttare i colori che è in grado di emettere per segnalare eventuali operazioni in corso o problemi a chi sta utilizzando il robottino.

Un suo utilizzo iniziale, poi, può essere quello di feedback di avvio, cioè avvisare che il raspberry pi sia effettivamente pronto all'uso e a disposizione per farci accedere e giocare con #Ro-Pi.
Visto che il pi, quando viene avviato, attiva automaticamente alcuni PIN del GPIO (li imposta ad accesi), alcune delle componenti ad esso collegate (es: il bridge, o il LED stesso) riceveranno il segnale di attivazione. Nel caso del bridge può essere un problema poiché una od entrambe le ruote potranno essere attivate senza il nostro controllo. Per evitare (o comunque limitare questo problema) si può sfruttare il LED RGB: si può fare in modo che, una volta che il sistema sia avviato, il LED venga spento automaticamente così da avvisarci che #Ro-Pi è pronto. E a quel punto noi potremo manualmente accendere l'interruttore che dà corrente al bridge, certi che le ruote non gireranno all'impazzata: è chiaramente una soluzione di ripiego, ma neanche così assurda... Aldilà di questo, comunque, il LED servirà da indicatore per tante altre operazioni, soprattutto quelle legate ai sensori di distanza.

Il primo passo è quello di creare lo script di reset di tutti i PIN ed inserirlo nell'avvio automatico del sistema operativo nel file /etc/rc.local con i seguenti passi:
  • sul terminale digitare: sudo nano /etc/rc.local
  • raggiungere la riga precedente ad exit 0
  • aggiungere il comando python "[percorso/fino/al/file/python/creato]"
    • es: python "/home/pi/ropi/reset_pins.py"
  • ctrl + x poi y e poi invio per salvare il file e tornare sul terminale

Quindi inserire il seguente codice nel file reset_pins.py:

# import
import RPi.GPIO as GPIO

# set mode BOARD and disable warnings
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

# set used pins
used_pins = [7, 11, 13, 15, 16, 18, 22, 29, 31, 36, 37]

# reset used PINs
for pin in used_pins :
    GPIO.setup(pin, GPIO.OUT) # set as output
    GPIO.output(pin, GPIO.LOW) # deactivate

# clean and exit
GPIO.cleanup()

Esso si occuperà di resettare i PIN da noi utilizzati sul raspberry pi e sarà richiamato dal sistema operativo qualche secondo dopo l'effettivo startup del raspberry pi.




Ma come si fa a controllare un LED RGB? Il LED RGB ha 4 pin che permettono di attivare il colore rosso, quello verde, quello blu, ed ovviamente uno di essi è dedicato al ground (quello più lungo). Non è possibile collegare direttamente il LED al raspberry pi (nessun LED può esserlo) poiché il pi stesso può erogare soltanto poca corrente (circa 60 mA) mentre il LED ne vorrà molta di più, causando possibili problemi al GPIO (se non peggio) oppure rischiando di bruciarsi. Occorre sfruttare dei resistori per assicurarci che soltanto una piccola quantità di tensione fluisca.
Lo schema è semplicissimo (e riportato nelle due figure in alto): il LED va inserito nella breadboard (in un punto qualsiasi di una delle due aree interne in modo che ogni PIN occupi una colonna differente) ed i resistori vanno inseriti su righe differenti in modo che da un lato coincidano con la colonna in cui è presente il PIN del colore rosso, blu o verde, e dall'altro quella in cui è collegato un cavo che proviene dal raspberry pi. Su quest'ultimo si possono utilizzare i PIN 29, 31 e 36 per i tre colori ed il 34 per il ground.

Per testare il funzionamento del LED RGB si può utilizzare un programmino come il seguente che non farà altro che accendere o spegnere i PIN a comando così da far brillare il LED del colore desiderato: rosso, verde, blu, magenta, ciano, giallo o bianco. Chiaramente si può considerare anche il LED spento come ottavo possibile stato di colore.

# import libraries
import time
import RPi.GPIO as GPIO

# set mode BOARD
GPIO.setmode(GPIO.BOARD)

# define raspberry pi LED PINs and set them as output
redPin = 6
GPIO.setup(redPin, GPIO.OUT) # output, blink red
greenPin = 29
GPIO.setup(greenPin, GPIO.OUT) # output, blink green
bluePin = 31
GPIO.setup(bluePin, GPIO.OUT) # output, blink blue

# activate pin
def activate(pin) :
    GPIO.output(pin, GPIO.HIGH)

# deactivate pin
def deactivate(pin) :
    GPIO.output(pin, GPIO.LOW)

# red on and off
def redOn(seconds) :
    print "turn red on for ", seconds, " seconds"
    activate(redPin)
    time.sleep(seconds)
def redOff() :
    print "turn red off"
    deactivate(redPin)

# green on and off
def greenOn(seconds) :
    print "turn green on for ", seconds, " seconds"
    activate(greenPin)
    time.sleep(seconds)
def greenOff() :
    print "turn green off"
    deactivate(greenPin)

# blue on and off
def blueOn(seconds) :
    print "turn blue on for ", seconds, " seconds"
    activate(bluePin)
    time.sleep(seconds)
def blueOff() :
    print "turn blue off"
    deactivate(bluePin)

# yellow on and off
def yellowOn(seconds) :
    print "turn yellow on for ", seconds, " seconds"
    activate(redPin)
    activate(greenPin)
    time.sleep(seconds)
def yellowOff() :
    print "turn yellow off"
    deactivate(redPin)
    deactivate(greenPin)

# cyan on and off
def cyanOn(seconds) :
    print "turn cyan on for ", seconds, " seconds"
    activate(greenPin)
    activate(bluePin)
    time.sleep(seconds)
def cyanOff() :
    print "turn cyan off"
    deactivate(greenPin)
    deactivate(bluePin)

# magenta on and off
def magentaOn(seconds) :
    print "turn magenta on for ", seconds, " seconds"
    activate(redPin)
    activate(bluePin)
    time.sleep(seconds)
def magentaOff() :
    print "turn magenta off"
    deactivate(redPin)
    deactivate(bluePin)

# white on and off
def whiteOn(seconds) :
    print "turn white on for ", seconds, " seconds"
    activate(redPin)
    activate(greenPin)
    activate(bluePin)
    time.sleep(seconds)
def whiteOff() :
    print "turn white off"
    deactivate(redPin)
    deactivate(greenPin)
    deactivate(bluePin)

# test colors for 0.5 seconds each
def testLED() :
    print "test all LED colors..."
    redOn(.5)
    redOff()
    greenOn(.5)
    greenOff()
    blueOn(.5)
    blueOff()
    yellowOn(.5)
    yellowOff()
    cyanOn(.5)
    cyanOff()
    magentaOn(.5)
    magentaOff()
    whiteOn(.5)
    whiteOff()

# test!
testLED()

# clean and exit
print "Done!"
GPIO.cleanup()



Passiamo ora ai sensori di distanza che permettono di rilevare la presenza o meno di un ostacolo davanti al robottino. L'idea, infatti, è quella di verificare la presenza di ciò che c'è davanti a #Ro-Pi ed eventualmente fermare la corsa di quest'ultimo se si supera una certa distanza di sicurezza.

Come è fatto un sensore di distanza? Ogni sensore ha 4 PIN che sono: un PIN per il ground, uno per la tensione, uno per risvegliare il sensore stesso (TRIG) ed uno per inviare indietro l'avviso di effettivo rilevamento (ECHO). In pratica quello che fa questo chip non è altro che inviare un segnale sonoro ed attivare il rilevatore per avvisare quando lo stesso segnale sarà tornato indietro poiché ha rimbalzato da qualche parte. E fra poco vedremo come sfruttare questa peculiarità per verificare la presenza o meno di un ostacolo e la sua distanza dal robottino.
Anche in questo caso non è possibile collegare il sensore direttamente al raspberry pi poiché esso per mandare il segnale di avvenuta cattura userà una tensione di 5V che i PIN del GPIO del raspberry pi non possono tollerare. Quindi anche in questo caso è necessario separare la tensione con dei resistori.


La formula appena riportata spiega che cosa occorre fare per raggiungere la Vout desiderata (3.3V) partendo dalla Vin (5V) combinata con uno o più resistori Rx. L'idea è quella di inserire un resistore da 1000 ohm (R1) tra il cavo collegato al PIN del sensore ed il rispettivo collegato al PIN del raspberry pi, ed uno da 2000 ohm (R2) tra quest'ultimo ed il ground che chiude il circuito. Ciò permette effettivamente di scendere da 5V (Vin) a circa 3.3V (Vout), tollerati dai PIN GPIO.

Nota: se non avete a disposizione resistori da 1000 e 2000 ohm, come è stato il mio caso, è possibile collegarne uno o più in serie (se hanno ohm inferiori) o in parallelo (se hanno ohm maggiori) così da arrivare agli ohm necessari. Oppure utilizzare altre combinazioni di resistori.


Il PIN ground del sensore ed un PIN ground del pi (i cavi neri della figura) vanno inseriti su una delle due aree esterne della breadboard, inseriti ovviamente in una riga "-", così come i PIN della tensione (i cavi rossi della figura) andranno inseriti in una di quelle con il segno "+" (si possono utilizzare i PIN 6 o 14 del raspberry pi per il ground ed i PIN 2 o 4 per la tensione). Continuando il giro, il PIN che risveglia il sensore (TRIG) potrebbe essere collegato direttamente al raspberry pi ma, per comodità, è bene sempre passare per la breadboard inserendo su di essa in una delle colonne delle aree interne i cavi (quelli gialli nella figura) che partono dal sensore di distanza e da uno dei PIN (es: il 16 o il 37) del pi.
Infine va collegato il PIN che conferma l'avvenuta ricezione del segnale (ECHO): esso è quello che va inserito dopo i due resistori (i cavi blu della figura) come descritto poc'anzi, e a sua volta connesso ad un PIN del pi (es: il 18 o il 22) che sfrutteremo per leggere lo stato del sensore.

Per testare il funzionamento del sensore di distanza si può utilizzare un programmino (come quello presente poco più avanti) che non farà altro che risvegliare il sensore (con un piccolo impulso della durata di circa 10 microsecondi) e restare in attesa della risposta catturata. Una volta ottenuta ci sono una serie di passi da compiere per effettivamente calcolare la distanza dall'ostacolo rilevato. Nella pratica ciò che accade è che il sensore invia un segnale sonoro e rimane attivo finché non lo riceve indietro. Questo significa che il PIN che segnala la risposta (ECHO) rimane fisso sul valore 1 fintanto che esso non riceve indietro il segnale inviato. Memorizzando gli istanti di avvio del processo e quello di termine potremo conoscere l'effettiva durata di tutto l'impulso, cioè il tempo che il suono ha impiegato per tornare indietro.


Sfruttando la semplice formula "velocità = distanza / tempo" si può calcolare il dato che a noi interessa: cioè la distanza. Il tempo sarò proprio quello rilevato con il sensore; quello che manca è la velocità. Fortunatamente, seppur la velocità del suono sia variabile poiché dipende dal materiale che attraversa e dalla sua temperatura, si può approssimare che la velocità del suono in aria al livello del mare sia di circa 343 m/s, cioè 34300 cm/s.

Nota: a noi interessa sapere la distanza tra il robottino e l'eventuale ostacolo; il tempo catturato con il sensore sarà quello intercorso tra l'invio del suono ed il suo ritorno, cioè esattamente il doppio rispetto a quello impiegato per effettivamente raggiungere l'ostacolo.


Quindi la formula finale sarà: "34300 = distanza / tempo / 2", cioè "17150 = distanza / tempo", cioè "distanza = 17150 * tempo calcolato". Abbiamo quindi tutte le informazioni necessarie per sapere la distanza in centimetri tra il sensore e l'eventuale ostacolo.

Nota: la distanza calcolata sarà una approssimazione di quella effettiva sia perché abbiamo approssimato la velocità del suono e sia perché sensori economici a volte restituiscono valori non al 100% precisi...


Il codice seguente amplia quello per il test del LED RGB e le parti comuni non sono riportate.

[...] # import

# define constants
SAFETY_DISTANCE = 5 # cm
SPEED_OF_SOUND = 17150 # 34300/2
TRIG_SPAN = .00001 # sec
SENSORS_OFF_SPAN = 1 # sec

[...] # led defines

# define raspberry pi PINs, set TRIG as output and ECHO as input
trigPin = 37
GPIO.setup(trigPin, GPIO.OUT) # output: send signal
echoPin = 22
GPIO.setup(echoPin, GPIO.IN) # input: receive result

[...] # led functions

# trig on and off
def trigOn() :
    print "turn trig on"
    activate(trigPin)
    time.sleep(TRIG_SPAN)
def trigOff() :
    print "turn trig off"
    deactivate(trigPin)

# initialize trig
def initialize_trig() :
    print "initialize trig"
    redOn(0)
    trigOn()
    trigOff()
    redOff()

# wait for pulse start
def wait_for_signal(pulse_start) :
    print "wait for pulse start"
    blueOn(0)
    while GPIO.input(echoPin) == 0 :
       pulse_start = time.time()
    blueOff()
    return pulse_start

# wait for pulse end
def wait_for_distance(pulse_end) :
    print "wait for pulse stop"
    greenOn(0)
    while GPIO.input(echoPin) == 1 :
       pulse_end = time.time()
    greenOff()
    return pulse_end

# calculate distance
def calculate_distance(pulse_start, pulse_end) :
   pulse_duration = pulse_end - pulse_start
   distance = pulse_duration * 17150
   print "Distance: ", distance
   return int(distance)

# measure distance until obstacle is less than 5 cm away!
def measure() :
    print "Measure..."
    trigOff()

    # continue untill obstacle is too close
    while 1 :
        time.sleep(SENSORS_OFF_SPAN)
        initialize_trig()
        if (calculate_distance(wait_for_signal(time.time()),
             wait_for_distance(time.time())) < SAFETY_DISTANCE) :
            break

#testLED()
measure()

[...] # exit


Sfruttare le nuove componenti con #Ro-Pi


A questo punto perché non unire tutto quello sviluppato nei due step in un unico test generale? L'idea è quella di fare in modo che il robottino si muova ad esempio in una direzione in modo automatico e, incontrato un ostacolo, si fermi autonomamente senza il nostro intervento. In più, con l'ausilio del LED indicare all'utilizzatore che cosa sta succedendo grazie ai vari colori a disposizione.

Nota: in questa prima fase sperimentale possiamo limitarci a eseguire ripetutamente una sequenza di: 1) controllo presenza ostacoli, 2) accelerazione per tot secondi, 3) nuovo controllo e così via, finché non si incontra un ostacolo.

Bisogna impostare una distanza di sicurezza entro la quale interrompere l'avanzamento e definire per quanto tempo premere sull'acceleratore tra un controllo e l'altro. E ci sono alcune considerazioni da fare:
  • l'esecuzione del codice sarà sufficientemente veloce che, seppur i motori vengano attivati quindi fermati poi riattivati e così via, non ci sarà mai una sensazione di un movimento a scatti (e questo è chiaramente positivo). Ma...
  • ...occorrerà ponderare con cura i valori da indicare per distanza e tempo: provando a diminuire il tempo tra un controllo e l'altro (quindi riducendo il tempo di accelerazione e scendendo sotto il secondo e ½), il sensore può iniziare a generare errori di calcolo rischiando di far collidere il robot. Quindi va trovata una giusta proporzione:
  • allungando i tempi di accelerazione si dovrà aumentare la distanza di sicurezza poiché il robottino si avvicinerà all'eventuale ostacolo molto più in fretta. Al contrario accorciando i tempi di accelerazione si potrà diminuire la distanza di sicurezza poiché verranno effettuati controlli più rapidi e quindi il robottino si troverà più difficilmente "in pericolo"!

Nota: purtroppo, come detto, scendere sotto al secondo e ½ rende le misurazioni meno affidabili ed è per questo motivo che in futuro proveremo a migliorare l'algoritmo sfruttando più sensori di distanza...


Il codice seguente amplia quelli precedenti e le parti comuni non sono riportate.

[...] # import and costants


SAFETY_DISTANCE = 25 # cm, replace old test value
ACCELERATION_SPAN = 1.5 # sec

[...] # led and distance defines

# define raspberry pi 
PINs, set LEFT and RIGHT motors as output
leftPinBck = 7
GPIO.setup(leftPinBck, GPIO.OUT) # output, left back
leftPinFwd = 11
GPIO.setup(leftPinFwd, GPIO.OUT) # output, left forward
rightPinFwd = 13
GPIO.setup(rightPinFwd, GPIO.OUT) # output, right back
rightPinBck = 15
GPIO.setup(rightPinBck, GPIO.OUT) # output, right forward

[...] # led and distance functions


# turn all motors off
def motorsOff() :
    print "all motors off"
    deactivate(leftPinBck)
    deactivate(leftPinFwd)
    deactivate(rightPinBck)
    deactivate(rightPinFwd)

# go forward
def forward(seconds) :
    print "go forward"
    redOn(0)
    activate(leftPinFwd)
    activate(rightPinFwd)
    time.sleep(seconds)
    motorsOff()
    redOff()

# go untill SAFETY_DISTANCE is too close
def go() :
    print "Let's go!"
    trigOff()
    time.sleep(SENSORS_OFF_SPAN)

    # continue untill obstacle is too close
    while 1 :
        initialize_trig()
        if (calculate_distance(wait_for_signal(time.time()),
             wait_for_distance(time.time())) < SAFETY_DISTANCE) :
            break
        forward(ACCELERATION_SPAN)

# go
#testLED()
#measure()
go()

[...] # exit



Conclusioni


Bene, termina qui il secondo episodio della serie!

Nelle prossime puntate proveremo a rendere ancora più smart il robottino, donandogli ad esempio il dono della vista...


A presto :-)

Nessun commento:

Posta un commento