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:
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 (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())
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)
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.