Joystick Rover

Beschreibung:

Unser Rover ist über eine H-Brücke mit einem Raspberry Pi verbunden (die beiden Motoren auf derselben Seite sind gleichgeschaltet) und mit einem Ultraschall Sensor, der den Rover vor einer Kollision mit Gegenständen abhält, sowie einer Pi Camera. An einen zweiten Raspberry Pi ist ein Joystick angeschlossen, der die Werte der x und y-Achse ausliest und diese über eine UDP Verbindung an den - mit dem Rover verbundenen Raspberry Pi - sendet. Aus dem Winkel zur x-Achse und der Länge des Vektors werden dort die Basisgeschwindigkeit der Räder sowie der Faktor mit dem diese, im Fall einer Drehung, auf der jeweiligen Seite multipliziert werden soll.

Verwendete Komponenten:

  • 2 x Raspberry Pi 3B+
  • H-Brücke
  • Rover
  • Ultraschallsensor
  • Analog Thumb Joystick
  • AD-Wandler
  • PiCamera

Projektaufbau:

PiCamera Konfiguration

Erstmal muss die Kamera in den Raspberry Pi Configurations aktiviert werden:

Command:

sudo raspi-config

Interface → Kamera → aktivieren

Den Livestream haben wir dann mit dem Code dieser Website erstellt:

https://randomnerdtutorials.com/video-streaming-with-raspberry-pi-camera/

UDP-Verbindung

UDP (User Datagram Protocol) haben wir benutzt um einen Raspberry Pi über das W-LAN miteinander zu verbinden. TCP wäre als Alternative infrage gekommen, was aber für unsere einfache Anwendung nicht notwendig war. Dabei speisen wir die Rohdaten des Joysticks an dem Client in ein Programm ein, das diese in unsere gewünschte Skala umwandelt und schließlich an den anderen Raspberry Pi (den Server) schickt.

Nachteil dieser Art von Übertragung ist, dass der Rover nur innerhalb der Grenzen des W-LANs funktioniert.

Beispiel dieses Clientprogramms:

import socket 
 
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
ip = ("192.168.178.200") 
nachricht= ("Hallo")
s.sendto(nachricht.encode(), (ip,50000))
s.close

Die Funktion Socket erzeugt eine Socket Instanz, worin wir zwei Parameter übergeben: Die IP-Adresse des Empfängers und die Nachricht selbst, in unserem Fall kontinuierlich die Koordinaten des Joysticks und im Beispielcode „Hallo“

Beispiel des Serverprogrammes:

import socket
try: 
   s.bind(("", 50000))
   while True:
      daten, addr = s.recvfrom(1024)
      print("[{}] {}".format(addr[0], daten.decode()))
finally:
   s.close()

Auch hier wird eine Socket-Instanz erstellt. Bind() übernimmt zwei Parameter: Die IP-Adresse des Senders als String und die Portnummer. Wir persönlich haben, wie im Beispiel, keine IP-Adresse angegeben, weil niemand uns sonst störende Nachrichten schicken wollte. recvfrom() wartet theoretisch endlos auf eine Nachricht, weshalb auch eine try/finally-Anweisung gebraucht wird damit wir mit einem KeyboardInterrupt mit close() die Verbindung schließen können. Die übergebene Nachricht ist als String vorhanden, deswegen ggf. darauf achten diese wieder in den ursprünglichen Datentyp umzuwandeln (ast.literal_eval())

Ultraschallsensor

Auf dem Raspberry Pi muss ein GPIO Pin als Ausgang gesetzt werden, der direkt mit Trigger verbunden werden kann, und ein Eingang, der mit einem Pulldown Widerstand mit dem Pin verbunden wird. Letzteres ist notwendig um „zufällige“ Werte zu verhindern.

Beispielcode:

import time 
import RPi.GPIO as gpio
 
# Initialisieren
 
gpio.setmode(gpio.BOARD) 
gpio.setup(12, gpio.OUT) 
gpio.setup(18, gpio.IN)
 
 
counter = 0 
new_reading = False 
 
# Pin 12 gibt fuer 0.00001s ein Signal aus, um den Ultraschall Signal zu 
# erzeugen.
gpio.output(12, 1)  
time.sleep(0.00001) 
gpio.output(12, 0)
 
# Pin 18 wartet solange bis ein Signal eingeht und nimmt dann die Zeit
while gpio.input(18) == 0: 
   pass 
start = time.time() 
# Pin 18 wartet solange bis kein Signal mehr da ist und nimmt dann die Zeit
while gpio.input(18) == 1: 
   counter += 1 
   if counter == 5000: 
      new_reading = True 
      break 
   pass 
stop = time.time() 
#  Berechnung des Abstandes 
dist = (stop - start) * 17000 
print(dist)

Steuerung

Um den Rover zu steuern, teilen wir das Koordinatensystem im 4 Quadranten auf. Für jeden Quadrant schreiben wir eine Funktion in der die allgemeine Richtung (vorwärts oder rückwärts) und die Wendungsrichtung (bzw. welche Seite der Räder sich schneller dreht) festgelegt sind.

Beispielcode Vorwärts-Links

# gpio Pins müssen vorher initialisiert werden
def left(speed, mult): 
   gpio.output(11, gpio.LOW)
   gpio.output(38, gpio.LOW)
 
   l = gpio.PWM(13, 150) 
   r = gpio.PWM(40, 150) 
 
   # Rechts läuft schneller als Links, d.h. Drehung nach links erfolgt
   r.start(speed * mult) 
   l.start(speed) 
   # Motoren drehen sich nur 0.006s und stoppen dann
   time.sleep(0.006) 
   r.stop() 
   l.stop() 
 

Alle vier Methoden nehmen zwei Parameter. Der erste („speed“) bestimmt die Basisgeschwindigkeit in PWD-Signalstärke. Sie wird bestimmt durch den Betrag unseres Vektor, d.h. je weiter man den Joystick auslenkt desto schneller ist die Basisgeschwindigkeit.

Der 2. Parameter ist der Faktor mit dem die schnellere Seite multipliziert wird. Er wird aus dem Winkel zur x-Achse berechnet, der auf einer Skala von 1 bis 5 liegt, wobei 90° = 1 ist und 0° = 5.

import math
 
# c[x, y] ist der Vektor den wir vom anderen Raspberry Pi gesendet bekommen
 
betrag = sqrt(sum(i**2 for i in c)) 
 
# Betrag in eine andere Skala - mit niedrigerem Maximum - umwandeln, weil PWD Signal nie über 100% gehen 
# darf
speed = ((((betrag - 0) * (25 - 0)) / (30 - 0)) + 0) + 2
# ZeroDivisionError vermeiden
if c[0] != 0: 
   angle = abs((atan(c[1]/c[0]) * (180 / pi))) 
   mult = ((angle - 0) / (90 - 0) ) * (5 - 1) + 1 
 

Bei der Winkelberechnung teilt man aber durch x, also musste der Fall „Joystick befindet sich auf der y-Achse“ vorher abgefangen und manuell definiert werden.

Auch wichtig ist, dass für die nähere Umgebung der Mitte (0|0) Nicht-Tätigkeit spezifiziert wird, da der Joystick dazu neigt unterschiedliche Werte anzuzeigen je nach dem in welcher Lage er liegt.