dimecres, 11 de gener del 2017

Cotxe robot autònom amb Raspberry Pi

[ESTA ENTRADA ESTÀ EN CONSTRUCCIÓ]


Edat recomanada: 
Muntatge a partir de 6 anys acompanyats d'un adult. 
Programació amb Python a partir de 8 anys.
Programació amb Scratch a partir de 6 anys.
               Programació amb botons tipus "Bee-Bot" a partir de 3 anys.
               Vigileu amb les piles, pegaments, objectes menuts, punxeguts, calents, etc.

[Al llarg d'esta entrada apareixerà molt codi de programació. No us preocupeu si no ho enteneu i us sembla xinés, anirem explicant poc a poc que fa cada part. A més, no tenim perquè aprendre un llenguatge de programació,  el més important és entendre el passos que s'han de seguir per a construir i "ensenyar" al nostre robot, així com les estructures bàsiques de la programació en general.]


Anem a construir un cotxe robot autònom controlat amb una Raspberry Pi i programat mitjançant Python. També el podrem programar en Scratch i els més menuts podran programar-lo amb botons, de manera similar a com es fa amb el robot Bee-Bot (no obstant, farem un robot específic per als més menuts de tipus Bee-Bot més endavant).
Es tracta d'un projecte de certa entitat que dividirem en 5 grans fites que detallem a continuació:

1. Muntatge del cotxe robot bàsic. Una vegada assolida esta fita, tindrem un cotxe que es mourà si donem electricitat als motors, però no controlarem la seua trajectòria ni velocitat.

2. Connexió a la Raspberry Pi i programació per a controlar el moviment de les rodes. Una vegada aconseguida esta fita, podrem conduir el nostre cotxe per control remot mitjançant un teclat Bluetooth, o mitjançant connexió remota via WIFI.

3. Muntatge i prova del sensor d'ultrasons. Arribats ací, podrem detectar la distancia del nostre robot als objectes que tinga davant. A més, motoritzarem els sensor per a poder girar-lo i obtindre una observació més completa del seu voltant.

4. Programació dels algorismes necessaris per a la conducció autònoma del robot. El robot podrà detectar obstacles i girar per a evitar-los. També aprendrem els algorismes que puguen permetre eixir de qualsevol laberint al nostre robot. Així doncs, haurem aconseguit un robot autònom a les nostres ordres!.

5. Extres:
- Afegir botons de control tipus Bee-Bot per a que puga ser programat pels més menuts. Hem de tenir en compte que con usem motors DC, el recorregut del robot no serà 100% exacte. Per això, més endavant farem un robot específic tipus Bee-Bot amb motors pas a pas que ens donaran la precisió requerida.
- Afegir sensor d'infrarojos per a detectar la presència de moviment i actuar en conseqüència.
- Afegir fotoresistències per a detectar la presència de llum / foscor i actuar en conseqüència.
- Afegir visió al nostre robot per a reconèixer persones i objectes i actuar en conseqüència.
- ...




Material necessari

2 motors DC a 5V i 2 rodes per a ells.
1 roda boja.
Un pont H dual L298N
Una Raspberry Pi (amb connector de càmera).
Càmera per a RaspBerry Pi.
Sensor d'ultrasons HC-SR04.
Servomotor  per a girar el sensor HC-SR04.
Sensor d'infrarojos HC-SR501.
Bateria de 5V per a la Raspberry Pi.
Bateria de 5V per als motors.
Breadboard (placa de proves)
Cables variats per a les connexions.
Una xassís on muntar tot. Usarem un "tupper".
LEDs per als llums de davant i darrere (blancs), i els de frenada (rojos).


Nosaltres construirem el robot triant els diferents materials per a intentant estalviar alguns euros, però també existeixen al mercat kits molt barats amb els motors, rodes i xassís:


.



O bé, kits complets amb TOT el necessari, com el robot GOPIGO de dexterindustries:


https://www.dexterindustries.com/gopigo/



1. Muntatge del cotxe robot bàsic:

En primer lloc, acoblarem les dues rodes als motors i els unirem al xassís mitjançant els caragols amb volanderes i les femelles. Si ho preferiu, podeu usar cola termo fusible. Haurem de vigilar la posició de les rodes per a què no ens freguen amb el xassís. També haurem de posar a la part de darrere del xassís la roda boja, tenint en compte l'alçada a la que haurà de quedar per a que rode correctament respecte a les rodes davanteres.
Una vegada tenim els motors i les rodes ben muntades, és hora de connectar-los a la bateria amb els cables i provar el seu funcionament. Usarem una bateria de 5V, ja que els motors que hem triat necessiten una alimentació de 5V. No importa la polaritat que usem, però ha de ser la mateixa per als dos motors, sinó, una roda anirà en un sentit i l'altra al contrari.


Connexió de la bateria al motor.
Passem els cables per un forat al xassís.












Robot amb els motors i les rodes muntats.




Si tot a anat bé, quan connectem la bateria les rodes giraran. Ja ho hem aconseguit!.





2. Connexió a la Raspberry Pi i programació per a controlar el moviment de les rodes:

Anem a connectar ara els motors a la Raspberry Pi, però no directament, si ho férem, depenent de l'amperatge dels motors podríem trencar-la, ja que només pot oferir uns quants mili amperes, mentre que els motors normalment consumeixen molt més. A més, tan sols amb la Raspberry Pi no podríem fer que els motors funcionaren en els dos sentits. Per a solucionar estos problemes usarem un pont H L298N dual.
Esta placa ens permet controlar els motors tant en velocitat com en sentit de gir mitjançant la nostra Raspberry Pi.

L'esquema de connexió que usarem és el següent:

Esquema de com ens quedaran les connexions.

Connexions del pont H dual L298N.





Connexió dels motors a la Raspberry Pi.




- En 1, 2 conectarem el positiu i negatiu del motor 1, respectivament.
- En 2, 3 conectarem el positiu i negatiu del motor 2, respectivament.
- LLEVAREM el jumper 5, ja que nosaltres no anem a usar la capacitat de la placa de regular el voltatge.
- En 6 (VCC, o 12V segons la placa), connectarem el positiu de la bateria de 5V per a alimentar els motors.
- En 7 (GND) connectarem el negatiu de la bateria de 5V i TAMBÉ un negatiu (GND) de la Raspberry Pi.
- En 8 (5V) connectarem un pin de 5V de la Raspberry Pi (per a alimentar a la placa, al xip L298N).
- Deixarem el jumper 9 (ENA) tal i com està (connectat).
- En 10 (In1) connectarem al pin GPIO04 (7) de la Raspberry Pi per a activar el motor 1.
- En 11 (In2) connectarem al pin GPIO17 (11) de la Raspberry Pi per a activar el motor 1.
- En 12 (In3) connectarem al pin GPIO27 (13)de la Raspberry Pi per a activar el motor 2.
- En 13 (In4) connectarem al pin GPIO22 (15) de la Raspberry Pi per a activar el motor 2.
- Deixarem el jumper 14 (ENB) tal i com està (connectat).


ATENCIÓ:

- MAI alimenteu els motors per l'entrada 6 (VCC, o 12V segons la placa) amb la Raspberry Pi. S'han d'alimentar amb una bateria de 5V, tal i com hem dit abans.
- LLEVEU el jumper 5 tal i com hem dit abans. En la configuració que nosaltres usem, el connector 8 (5V) és d'entrada i el connectem a un pin 5V de la Raspberry Pi. Això alimenta la placa. Si deixem el jumper 5, el connector 8 (5V) serà d'eixida i donarà 5V, la qual cosa cremarà la Raspberry Pi.
- CONNECTEU al pin 7 (GND) tant el negatiu de la bateria que alimenta els motors, com un pin GND de la Raspberry Pi, tal i com hem dit abans.
- Els motors i la Raspberry Pi NO han de compartir la bateria per a alimentar-se. És a dir, useu dues bateries de 5V, una per als motors i una altra per a la Raspberry Pi.

Pins GPIO de la Raspberry Pi 3


La nostra feina amb el maquinari ha finalitzat (per ara), i ens toca passar a la programació del nostre robot. Usarem el llenguatge de programació Python, encara que més endavant, també usarem Scratch per a que els més menuts puguen fer servir i programar el robot amb més facilitat.
Hem d'implementar les funcions bàsiques de funcionament dels motors com ara: Endavant, Enrrere, GirEsquerre, GirDret, ... comencem doncs amb el programari Python que ens permet controlar el nostre robot mitjançant el teclat:

###### Inici Còpia-Engantxa

import RPi.GPIO as gpio
import time
import sys
import readchar

EnA = 13
EnB = 18
In1 = 11
In2 = 12
In3 = 15
In4 = 16


def init():
    gpio.setmode(gpio.BOARD)
    gpio.setup(EnA, gpio.OUT)
    gpio.setup(EnB, gpio.OUT)
    gpio.setup(In1, gpio.OUT)
    gpio.setup(In2, gpio.OUT)
    gpio.setup(In3, gpio.OUT)
    gpio.setup(In4, gpio.OUT)
  

def turn_left(tf):
    gpio.output(In1, True)  # Si In1 activat i In2 desactivat aleshores gira en un sentit.
    gpio.output(In2, False) # Si In1 desactivat i In2 activat aleshores gira en l'altre sentit.
    gpio.output(In3, True)  # Igual que abans per al motor 2.
    gpio.output(In4, False)
    time.sleep(tf)
    gpio.cleanup()
 
def turn_right(tf):
    gpio.output(In1, False)
    gpio.output(In2, True)
    gpio.output(In3, False)
    gpio.output(In4, True)
    time.sleep(tf)
    gpio.cleanup()

def forward(tf):
    gpio.output(In1, True)
    gpio.output(In2, False)
    gpio.output(In3, False)
    gpio.output(In4, True)
    time.sleep(tf)
    gpio.cleanup()
 
def reverse(tf):
    gpio.output(In1, False)
    gpio.output(In2, True)
    gpio.output(In3, True)
    gpio.output(In4, False)
    time.sleep(tf)
    gpio.cleanup()  

def stop(tf):
    gpio.output(In1, False)
    gpio.output(In2, False)
    gpio.output(In3, False)
    gpio.output(In4, False)
    time.sleep(tf)
    gpio.cleanup()

def key_input():
    init()
    c = readchar.readchar()
   if c == 'q':
forward(sleep_time)
       stop(0)
    elif c == 'a':
reverse(sleep_time)
      stop(0) 
    elif c == 'o':
turn_left(sleep_time) 
      stop(0)  
    elif c == 'p':
turn_right(sleep_time)
      stop(0)
    elif c == 's':
stop()
    elif c == 'x':
gpio.cleanup()
quit()


while True:
key_input()

###### Fi Còpia-Engantxa




3. Muntatge i prova del sensor d'ultrasons:

Ara  passarem a muntar el sensor d'ultrasons per a poder detectar obstacles. El primer de tot serà connectar el sensor a la Raspberry Pi seguint el següent esquema:





El sensor HC-SR04 té 4 pins de connexió. VCC i GND corresponen a 5V i 0V (+ i -) respectivament. Podem alimentar-lo directament des dels pins de voltatge 5V de la Raspberry Pi. El pin TRIG (TRIGGER, DISPARADOR) és pel què li diguem al sensor que s'ha d'activar. Per a finalitzar, el pin ECHO és el que ens retorna el temps que ha tardat el so en anar i tornar des de que es va activar. El problema és que el pin ECHO retorna el valor amb un voltatge de 5V, i els pins d'entrada/eixida de la Raspberry Pi treballen amb 3.3V. Per això, necessitem connectar un divisor de voltatge al pin ECHO, sinó, hi ha moltes possibilitats de cremar el microprocessador de la placa.

Un divisor de voltatge és simplement dues resistències, una amb el doble de valor que l'altra (o el més a prop possible), connectades amb el següent esquema:

En el nostre cas, Vin és el nostre pin ECHO, Vout és el pin de la Raspberry Pi (nosaltres el connectarem al pin 32). La resistència R2 serà el doble (o el més a prop possible) que la resistència R1.
Amb este muntatge aconseguim reduir els 5V que ens envia el pin ECHO als 3.3V que pot usar la Raspberry Pi.


Per tant, el sensor HC-SR04 ens quedarà connectat de la següent manera:

HC-SR04                                      Raspberry Pi
----------                                      --------------

 pin Vcc                                          pin 1 (5V)
 pin Trig                                          pin 31
 pin Echo                                        pin 32 (amb el divisor de voltatge)
 pin Gnd                                         pin 30 (GND)



Connexió provisional del sensor HC-SR04 al nostre robot.

El següent codi python ens mostra el valor en centímetres fins l'obstacle que està davant del sensor. Per a aconseguir un valor més exacte i fiable, fa 17 lectures de la distància i mostra la mediana de les 17 lectures:

# Inici codi

import RPi.GPIO as gpio
import time
import sys
import statistics
from array import array

TRIGGER_PIN = 31
ECHO_PIN = 32

def init():
    gpio.setwarnings(False)
    gpio.setmode(gpio.BOARD)
    gpio.setup(TRIGGER_PIN,gpio.OUT)            
    gpio.setup(ECHO_PIN,gpio.IN)
 
 
def distance():
    init()
    values = array('f', [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
 
    for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]:
      gpio.output(TRIGGER_PIN, False)        
      time.sleep(0.01)                    
      gpio.output(TRIGGER_PIN, True)          
      time.sleep(0.00001)                  
      gpio.output(TRIGGER_PIN, False)
   
      while gpio.input(ECHO_PIN)==0:            
        pulse_start = time.time()            
   
      while gpio.input(ECHO_PIN)==1:
        pulse_end = time.time()    
        pulse_duration = pulse_end - pulse_start
        d = pulse_duration * 17150
        d = round(d, 2)          
   
      values[i] = d
 
    return round(statistics.median(values), 2)

print (distance())

# Fi codi



4. Programació dels algorismes necessaris per a la conducció autònoma del robot.

Ara sí, anem a fer que el nostre menut robot es comporte com un autèntic cotxe autònom Tesla model X. La diferència de preu pot ser que siga substancial, però al cap i a la fi, els dos cotxes conduiran a soles sense por d'atropellar massa vianants.

L'algorisme és el següent:

#####################################################################
girar_motor_sensor(90º)                             # posem el sensor mirant cap a endavant (90º).

per sembre()                                              # llaç (bucle) infinit.

    si Sensor_Ultrasons() > 20cm                # no hi ha cap obstacle a menys de 20cm
        endavant(1)                                       # avancem 1 unitat.
    si no
        per a aux = 0 fins a 12                        # repetir 13 vegades per a recórrer els 180º en parts de 15º.
        girar_motor_sensor(aux  * 15)           # 0 * 15º = 0º fins a 12 * 15º = 180º.
        matriu[aux] = Sensor_Ultrasons()       # desem a la posició aux de la matriu el valor del sensor.

        posicio_via_lliure = posicio_maxim_valor_de_matriu(matriu[]) 
       cas posicio_via_lliure:                             # segons la posició que tinga el valor més alt (més via lliure té).
            0: esquerra(6)
            1: esquerra(5)
            2: esquerra(4)
            3: esquerra(3)
            4: esquerra(2)
            5: esquerra(1)
            6:
            7: dreta(6)
            8: dreta(5)
            9: dreta(4)
            10: dreta(3)
            11: dreta(2)
            12: dreta(1)
            13:
#####################################################################

Amb l'algorisme anterior el nostre cotxe conduirà a soles i no xocarà amb cap obstacle...
El codi en python  a falta de millores, és el següent:

# Inici codi

import RPi.GPIO as gpio
import time
import sys
import readchar
import statistics
from array import array


EnA = 13
EnB = 18
In1 = 11
In2 = 12
In3 = 15
In4 = 16
TRIGGER_PIN = 31
ECHO_PIN = 32
SERVOMOTOR_PIN = 22


def init():
    gpio.setwarnings(False)
    gpio.setmode(gpio.BOARD)

    gpio.setup(In1, gpio.OUT)
    gpio.setup(In2, gpio.OUT)
    gpio.setup(In3, gpio.OUT)
    gpio.setup(In4, gpio.OUT)
    gpio.setup(TRIGGER_PIN,gpio.OUT)        
    gpio.setup(ECHO_PIN,gpio.IN)
    gpio.setup(SERVOMOTOR_PIN, gpio.OUT)
    global ControlServoMotor
    ControlServoMotor = gpio.PWM(SERVOMOTOR_PIN, 50)
    ControlServoMotor.start(7.5)

 
def turn_right(tf):
    init()
    gpio.output(In1, True)
    gpio.output(In2, False)
    gpio.output(In3, True)
    gpio.output(In4, False)
    time.sleep(tf)
    gpio.cleanup()
 
def turn_left(tf):
    init()
    gpio.output(In1, False)
    gpio.output(In2, True)
    gpio.output(In3, False)
    gpio.output(In4, True)
    time.sleep(tf)
    gpio.cleanup()

def forward(tf):
    init()
    gpio.output(In1, True)
    gpio.output(In2, False)
    gpio.output(In3, False)
    gpio.output(In4, True)
    time.sleep(tf)
    gpio.cleanup()
 
def reverse(tf):
    init()
    gpio.output(In1, False)
    gpio.output(In2, True)
    gpio.output(In3, True)
    gpio.output(In4, False)
    time.sleep(tf)
    gpio.cleanup()  

def stop(tf):
    init()
    gpio.output(In1, False)
    gpio.output(In2, False)
    gpio.output(In3, False)
    gpio.output(In4, False)
    time.sleep(tf)
    gpio.cleanup()


def left_servo():
    init()
    ControlServoMotor = gpio.PWM(SERVOMOTOR_PIN, 50)
    ControlServoMotor.ChangeDutyCycle(2.5)
    time.sleep(0.3)        
    gpio.cleanup()
     
def right_servo():
    init()
    ControlServoMotor = gpio.PWM(SERVOMOTOR_PIN, 50)
    ControlServoMotor.ChangeDutyCycle(12.5)
    time.sleep(0.3)        
    gpio.cleanup()
     
def center_servo():
    init()
    ControlServoMotor = gpio.PWM(SERVOMOTOR_PIN, 50)
    ControlServoMotor.ChangeDutyCycle(7.5)
    time.sleep(0.3)        
    gpio.cleanup()

def step_left_servo(currentcycle):
    init()
    ControlServoMotor = gpio.PWM(SERVOMOTOR_PIN, 50)
    ControlServoMotor.ChangeDutyCycle(currentcycle - 1)
    gpio.cleanup()

def step_right_servo(currentcycle):
    init()
    ControlServoMotor = gpio.PWM(SERVOMOTOR_PIN, 50)
    ControlServoMotor.ChangeDutyCycle(currentcycle + 1)
    gpio.cleanup()

 
def distance():
    init()
    values = array('f', [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
 
    for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]:
      gpio.output(TRIGGER_PIN, False)        
      time.sleep(0.01)                    
      gpio.output(TRIGGER_PIN, True)          
      time.sleep(0.00001)                  
      gpio.output(TRIGGER_PIN, False)
   
      while gpio.input(ECHO_PIN)==0:            
        pulse_start = time.time()            
   
      while gpio.input(ECHO_PIN)==1:
        pulse_end = time.time()    
        pulse_duration = pulse_end - pulse_start
        d = pulse_duration * 17150
        d = round(d, 2)          
   
      values[i] = d
 
    return round(statistics.median(values), 2)



while True:
  init()
  center_servo()
  if (distance() > 20):
      forward(0.1)
  else:
      values = array('f', [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
      left_servo()
      for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]:
        step_right_servo(i)
        values[i] = distance()

      maxvalue = -1
      maxvalueindex = -1  
      for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]:
        if (maxvalue < values[i]):
         maxvalue = values[i]
            maxvalueindex = i

      print(maxvalueindex)
      print(maxvalue)

      if (maxvalueindex < 6):
        for i in range(0, 6 - maxvalueindex):
          turn_left(0.1)
        forward(0.1)
      elif (maxvalueindex > 6):
        for i in range(0, maxvalueindex - 6):
          turn_right(0.1)
        forward(0.1)
      else:
        forward(0.1)

# Fi codi


[CONTINUARÀ]



Cap comentari:

Publica un comentari a l'entrada