Zum Inhalt springen

Ein autonomes Auto bauen

Ein autonom fahrendes Auto zu bauen, ist ein schwieriges und komplexes Unterfangen. Vielleicht verfolgst du die Nachrichten zu dem Thema oder interessierst dich generell dafür. Große und namhafte Hersteller versuchen sich seit Jahren daran und investieren viel Geld in Forschung und Entwicklung. Wir werden also kein Auto auf die Straßen bringen, sondern ein kleines Vehikel basteln, das zumindest nicht direkt auf eine Wand zufahren soll. Mit etwas mehr Forschung, schaffen wir auch mehr …

Die Grundlagen

Einen Plan machen

Jetzt geht es so richtig los! Anders als bei der Übung zu den LEDs wird es jetzt wirklich handwerklich. Ich hoffe, du freust dich darauf! Befolge die Schritte unten ganz genau, dann solltest nur wenige Hindernisse zu überwinden haben.

Werkzeug und Material: Statte dich mit dem unten aufgeführten Werkzeug und Material aus, bevor du loslegst.

  • Auto-Kit von Joy-IT (4 Motoren)
  • Schraubendreher (im Kit enthalten)
  • Kabelentmanteler (eine Art Zange, um den Plastikmantel von leitenden Drähten zu trennen)
  • L298N Motorsteuerung
  • 4 mal AA-Batterie bzw. -Akku
  • evtl. Lötmaterial (in der Schule löten wir die Teile derzeit nicht)
  • Raspberry Pi + Zubehör für Bedienung

Grundgerüst aufbauen

Wir statten uns aus mit einem Kit, das die wichtigsten Teile bereithält. Aktuell verwenden wir ein Produkt der Marke Joy-IT. Baue das Auto nach der folgenden Anleitung (des Herstellers) auf, bevor wir mit den Programmierübungen anfangen:

Tipps: Du solltest die Drähte zuerst mit den Motoren verbinden. In der Packung sind in der Regel pro Rad ein schwarzes und ein rotes Stück Draht. Lege mit dem Entminterer so viel Draht frei, so dass du es zusammenzwirbeln und um die Kontakte des Motors wickeln kannst.

Einen Motor steuern

Ja, richtig gelesen! Du wirst erst einmal nur einen Motor steuern, um die Grundlagen zu verstehen.

Verbinde die Bauteile nach dem Schema unten. Dafür benötigst du verschiedene Kabel. Kabel, die du einklemmst, nimmst du aus der Spule und verwendest dafür möglichst keine Jumper-Kabel, weil sie nur dünne Nadeln haben. Gehe wie folgt vor:

  1. Den Raspberry Pi noch nicht einschalten!
  2. Verbinde den Motor eines(!) Rades mit der Motorsteuerung. Die Motorsteuerung ist eine eigene kleine Platine, in unserem Fall ist es eine rote Platine des Modells L298N. In den folgenden Übungen wirst du diesen Motortreiber (motor driver) besser kennenlernen.
  3. Verbinde die Motorsteuerung mit dem Raspberry Pi. Nutze bitte ganz genau die Pins im Bild, der Code in den Übungen wird sich auf diese beziehen.
  4. Verbinde die Motorsteuerung mit dem Batteriehalter. 2 Batterien sollten zunächst ausreichen. Beachte: Der Motor wird von den Batterien angetrieben. Die Energie des Raspberry Pi reicht nicht dafür aus, sich selbst und Motoren zu steuern.
  5. Du nutzt das Auto oben, das heißt sobald du den Code ausführst, wird sich das Auto auch bei einem Rad bewegen. Damit nicht alles im Chaos endet, sollte das Rad in der Luft sein! Lege entweder etwas unter das Auto oder das Auto auf die Seite, auf der sich nichts bewegen wird.
  6. Verbinde den Raspberry Pi mit dem Monitor und danach mit der Stromversorgung.

Probelauf mit einem Rad

  1. Lege einen Ordner auf dem Raspberry Pi an, unter Documents. In diesen Ordner legst du alle Projektdateien ab. Achte darauf, dass die Dateinamen nicht zu umständlich sind. Wir werden später ein mal per Command Line auf den Pi zugreifen, also ohne Bildschirm. Da brauchen wir keine langen Namen…
  2. Erstelle die Datei motors.py mit diesem Inhalt:
# motors.py
import RPi.GPIO as gpio
import time

def initBoard():
    gpio.setmode(gpio.BOARD)
    # gpio.setwarnings(False)

def initMotors():
    initBoard()
    gpio.setup(11, gpio.OUT) #IN1
    gpio.setup(13, gpio.OUT) #IN2
    gpio.setup(12, gpio.OUT) #pwm0 (hardware) EN1: controls M1-speed with IN1 and IN2

def forward(tf,speed):
    print("start forward")
    initMotors()
    pwm1 = gpio.PWM(12,100) #arg1: pin, arg2: frequenz in Hz
    pwm1.start(speed) #min 20, max 100
    gpio.output(11, 0) #IN1
    gpio.output(13, 1) #IN2
    time.sleep(tf)
    gpio.cleanup()
    print("clean up")

forward(3,50)
  1. Führe das obige Python-Skript aus. Was beobachtest du?
  2. Nun betrachten wir diesen Block etwas genauer:
def forward(tf,speed):
    initMotors()
    pwm1 = gpio.PWM(12,100) #arg1: pin, arg2: frequenz in Hz
    pwm1.start(speed) #min 20, max 100
    gpio.output(11, 0) #IN1
    gpio.output(13, 1) #IN2

Was bewirkt dieser Block genau? Analysiere den Code Zeile für Zeile. Die zwei markierten Zeilen sind wichtig für die Motorsteuerung. Tausche die 0 mit der 1. Führe den Code erneut aus. Was stellst du fest?

4 Räder steuern

Oben hast du gelernt, wie man ein einziges Rad steuert. Mithilfe des Diagrams unten kannst du die übrigen Motoren anschließen. Beachte insbesondere die Notiz, die dich auf die Verkabelung überkreuz hinweist.

Für diesen Teil gebe ich dir nur minimale Hinweise, damit du alles, was du bisher gelernt hast, konzentrieren und das Problem selbst lösen kannst.

  • Erstelle Code, mit dem du alle Räder bewegen kannst. Mit diesem Setting ist es nicht möglich, jedes Rad einzeln anzusteuern. Du siehst bereits am Diagramm, welche Räder zusammengefasst werden!
  • Erstelle Code, mit dem du vor und zurück fahren kannst sowie das Auto nach rechts und links drehen kannst.
  • Tipp: Verwende Funktionen wie beispielsweise forward, Reverse, left, right.
  • Es gibt bestimmte PINs, mit denen sogenanntes PWM möglich ist. Was das genau ist, kannst du hier nachlesen. Wir nutzen das hier einfach. So wie in dem Code oben.

Einen Ultraschallsensor für Abstandsmessungen nutzen

Verkabelung

Im Internet findest du viele verschiedene Anleitungen. Die folgende funktioniert mit dem Code unten gut.

Code

Diesen Code solltest du auf deinen Stick und von dort auf den Raspberry kopieren. Beim Abschreiben können Fehler passieren, das empfehle ich nicht.

import RPi.GPIO as gpio
import time 

gpio.setmode(gpio.BOARD)
gpio.setwarnings(False)
gpio_trigger = 16
gpio_echo = 18

gpio.setup(gpio_trigger, gpio.OUT)
gpio.setup(gpio_echo, gpio.IN)

def distance():
    # set Trigger High
    gpio.output(gpio_trigger, True)
 
    # set Trigger after 0.1ms low
    time.sleep(0.00001)
    gpio.output(gpio_trigger, False)
 
    startTime = time.time()
    endTime = time.time()
 
    # store start time
    while gpio.input(gpio_echo) == 0:
        startTime = time.time()
 
    # store arrival
    while gpio.input(gpio_echo) == 1:
        endTime = time.time()
 
    # elapsed time
    TimeElapsed = endTime - startTime
    # multiply with speed of sound (34300 cm/s)
    # and division by two
    distance = (TimeElapsed * 34300) / 2
 
    return distance
 

while True:
    dist = distance()
    #print ("Entfernung = %.1f cm" % dist)
    if dist > 50:
        print("vor")
    else:
        print("crash")
    time.sleep(0.2)

Servomotor steuern

Schließe den Servmotor an:

  • gelbes Kabel: PIN Nummer 11 an,
#this code is for the use of servormotors
#tested with MG996R, circuit diagram is given

import RPi.GPIO as gpio
from time import sleep

gpio.setmode(gpio.BOARD)
gpio.setup(11, gpio.OUT)

pwm=gpio.PWM(11, 50) #select pin, 50Hz is standard
pwm.start(0)

def setAngle(angle):
    duty = angle / 18 + 3
    gpio.output(11, True)
    pwm.ChangeDutyCycle(duty)
    sleep(1)
    gpio.output(11, False)
    pwm.ChangeDutyCycle(duty)

setAngle(90)

pwm.stop()
gpio.cleanup()

Den Raspberry Pi für SSH einrichten

In der Regel sind nicht alle folgenden Schritte notwendig, um per SSH auf einen Raspberry Pi zuzugreifen. In der Anleitung unten schalten wir zunächst die Energiesparfunktion des Raspberry Pi aus, damit WLAN auch dann funktioniert, wenn die Energieversorgung knapp ist. Danach finden wir die IP-Adresse (Adresse im Netzwerk, IP steht für Internetprotokoll) des Raspberry Pi heraus, um von einem anderen Computer auf den Raspberry Pi zuzugreifen.

  1. Melde dich mit einem Admin-Account auf dem Raspberry Pi an, die Zugangsdaten erhältst du von mir.
  2. Öffne das Terminal und gib folgende Befehle nacheinander ein:
# Öffne die Datei /etc/rc.local mit Superuser Privilegien
sudo nano /etc/rc.local # bei Aufforderung PW eingeben

# Scrolle nach unten und schreibe VOR exit 0 das folgende
/sbin/iwconfig wlan0 power off

# Beende den Vorgang mit Ctrl+X und gib y ein, um zu bestätigen

Wir haben damit nun sichergestellt, dass du dich mit dem WLAN verbinden kannst. Bisher hat der RPi diese Funktion blockiert, wenn die Stromversorgung knapp war. Mit dem Code oben wurde die Stromsprafunktion abgestellt.

  1. Jetzt verbinde dich mit einem WLAN-Netz. In diesem Netzwerk kannst du dich von einem anderen Rechner aus mit dem Raspberry Pi verbinden. Das funktioniert mithilfe von SSH (Secure Shell). Dazu benötigst du die IP-Addresse des RPi, diese findest du mit dem Befehl ip addr heraus.
  2. Verbinde dich an dem anderen Rechner (=Host) mit dem gleichen WLAN. Öffne am Host die Konsole oder das Terminal. Fahre dort wie folgt vor:
ssh [email protected] # dabei ersetze user durch den Admin auf dem Raspberry Pi und die IP-Adresse durch die echte IP-Adresse

Sobald du das Passwort für user auf dem Zielrechner (Server) eingibst, bist du damit verbunden! Nun kannst du auf dem Server remote (also von der Ferne, hier per WLAN) arbeiten.

Code-Gerüst für das autonome Auto

Weiter unten stelle ich dir ein Code-Gerüst für dein autonom fahrendes Auto bereit. Bevor du aber sofort dahin wechselst, lies dir die kommenden Erklärungen durch, damit du auch weißt, was du machst.

Wir nutzen hier einen Ansatz, bei dem wir unseren Code in Module aufteilen. Das ist bei komplexeren Aufgaben und für die Pflege des Codes (Strukturierung und Fehlerbehebung) sehr wichtig. Durch die Modularisierung (Aufteilung) deines Codes kannst du dich zielgerichtet den Teilen deines Codes widmen, die gerade wichtig sind oder Fehler aufweisen.

Ziel: Das Auto soll eine vorgegebene Zeit lang autonom und ohne Unfälle fahren.

  • Das Auto fährt x Sekunden lang.
  • Das Auto fährt gerade aus, sofern kein Hindernis per Ultraschall erkannt wird (empfohlener Sicherheitsabstand: 50cm).
  • Das Auto dreht nach links, wenn ein Hindernis erkannt wird. Diese Regel kannst du im weiteren Verlauf des Projekts noch verbessern.

Vorbereitungen: Modularisierung (also den Code aufteilen und Tests durchführen)

Erstelle zunächst 3 Dateien:

  • main.py
  • motors.py
  • sonic.py
  • timer.py

In der main.py-Datei importierst du die Funktionen aus den anderen Dateien, die wir folgend Module nennen. Beachte folgende Dinge:

  • In den Modulen rufst du keine Funktionen auf, dort werden sie nur definiert.
  • In einem Modul führst du nur Variablen und Methoden (=Funktionen ) ein, die zu diesem Modul gehören. Beispielsweise solltest du in dem Modul motors.py nur das definieren, was die Motoren deines Autos betrifft. Warum? Taucht später ein Problem mit der Motorsteuerung auf, kannst du dich gezielt nur darauf konzentrieren.
  • Entscheide dich am Anfang für einen der beiden Import-Varianten unten. Das macht es für dich einfacher, deinen Code zu lesen und zu verstehen.

Den Einstieg dazu erleichtere ich dir, indem ich dir ein paar Vorgaben mache:

  1. Lege alle oben genannten Dateien an und schreibe in die erste Zeile den Namen der Datei als Kommentar. Bei timer.py sollte es so aussehen:
# timer.py

# importiere das eingebaute time-Modul
import time 

# diese Funktion erstellt ein Timer-Ende, also einen Zeitpunkt in der Zukunft
def timer(sekunden):
    now = time.time()
    return now+sekunden
  1. Du siehst in der ersten Zeile also sofort, in welcher Datei du dich befindest (und ja, ich weiß, viele IDEs zeigen dir auch den Namen der Datei an; das ist nur eine Orientierungshilfe). Du siehst sogleich auch, dass ich bereits eine Methode timer(sekunden) angelegt habe. Diese werde ich gleich nutzen.

    Nun geht es weiter. Öffne die Datei main.py und schreibe folgendes hinein:
# main.py

# Importiere das eigene Modul timer mit dem Alias (=Spitznamen) t
import timer as t
import time # importiere internes Modul

# Greife auf die Methode timer() im Modul t zu
ende = t.timer(20)

print(ende) # Der Wert ist ein Zeitstempel, der der Anzahl vergangener Sekunden seit dem 1. Januar 1970 entspricht. Rechne gerne nach, um es zu überprüfen...

# Teste diese Funktion
def uhr(dauer):
    ende = t.timer(dauer)
    while time.time() < ende:
        print("tik")
        time.sleep(0.5)
        print("tak")
        time.sleep(0.5)
        
uhr(10)

Führe den Code aus. Du wirst feststellen, dass du 10 Sekunden lang “tik, tak” lesen wirst. Doch was ist nun so besonders an all dem? Schau dir die markierten Zeilen mal ganz genau an. Was ist dort passiert?

Mit ende = t.timer(dauer) passiert schon sehr viel! t.timer(dauer) greift auf die Methode timer(sekunden) in dem Modul timer zu. Das Argument dauer übernimmt die Funktion dabei von der übergeordneten Funktion uhr. Schließlich wird der Wert in der Variablen ende gespeichert. Das ist ein Zeitstempel, also irgendeine Kommazahl, die Sekunden seit dem 01.01.1970 plus so viele Sekunden wie dauer angibt, speichert. Wir haben damit also einen Zeitstempel für die Zukunft in dauer Sekunden. All das passiert in dieser einen Zeile. Intern passiert noch mehr, denn die timer(sekunden)-Methode aus dem timer-Modul greift auf das interne Python-Modul time zu, das kannst du unter timer.py noch einmal nachvollziehen. Ist das nicht grandios? So viele Prozesse lassen sich mit dieser kurzen Zeile anstoßen.

In dem Schleifenkopf der while-Schleife wird nun für jeden Durchgang der Zeitstempel zu Beginn des Schleifendurchgangs (time.time()) mit dem Zeitstempel der Zukunft (ende) verglichen. Solange der Stempel in der Zukunft nicht erreicht wird, wird der Schleifenkörper – dieser ganze tik-tak-Kram – ausgeführt.

Nun stellt sich die Frage, ob du das Prinzip verstanden hast? Ja? Super! Nein? Lass uns darüber sprechen. Es ist sehr wichtig, dass du das verstehst, bevor du weitermachst.

Seitennotiz: Wie importieren?

import time # Importiere das ganze Modul
time.sleep(1) # Nutzung mit Bezug zum Modul

Empfohlene Variante: Einerseits hast du einen klaren Bezug zu einem Modul. Das macht den Ursprung der genutzten Methoden transparent. Anderseits schreibst du mit der Verwendung von Aliasen weniger Text. Wenn du Mal zwei Module importierst, die den gleichen Anfangsbuchstaben haben, dann nutze einfach mehr Buchstaben oder verzichte auf Aliase.

import time as t # Importiere das ganze Modul mit Alias
t.sleep(1) # Nutzung mit Bezug zum Modul

from time import sleep # konkrete imports

sleep(1) # direkte Nutzung

from time import * # importiere alle (*) Methoden von Modul time

sleep(1) # direkte Nutzung

Codegerüst

Dieses Code-Gerüst soll dir nur eine grobe Idee geben, wie dein Code aussehen kann. Beginne schrittweise: Da bedeutet, dass du zunächst einmal nur ein Modul importieren sollst, dieses testen und dann das nächste importieren sollst. All diese Tests mache bitte zunächst an deinem Platz. Erst wenn all das abgeschlossen ist, nutze ssh, um dein Auto aus der Ferne zu steuern.

# main.py

import RPi.GPIO as gpio
import timer as t
import time
import motors as m
import sonic as s

# GPIO-Einstellungen
# Inputs und Outputs einstellen

# Setze Dauer durch Endzeitpunkt
ende = t.timer(20)

# solange der Endzeitpunkt nicht erreicht ist, ist der Code gültig
while time.time() < ende:
    d = s.distance() # Nutze Distanz-Funktion aus sonic-Modul
    if d > 50:
        m.forward(0.5, 80) # fahre mit 80% Energie 0,5 Sekunden lang vorwärts
    else:
        m.turn_left(1, 70) # drehe mit 70% Energie 1 Sekunde lang links herum

# gpio.cleanup() am Ende nicht vergessen! Falls der Teil nicht bereits in deinen Motor-Methoden inkludiert ist.

Mit diesem Code-Gerüst solltest du hoffentlich in der Lage sein, weiterzuarbeiten und einen Code zu entwickeln, mit dem dein Auto den abgebildeten Parcours bewältigen kann.

In dem Flur werden mehrere Kisten in einer ähnlichen Formation wie rechts aufgestellt. Dein Auto muss vor der Startlinie losfahren und dann ohne dein physisches Zutun das Ziel erreichen. Die einzelnen Etappen entsprechen Noten. Je nach dem wie weit dein Auto kommt, erhältst du eine Note.

Mögliches Codegerüst für Auto mit 1 Sensor

zuletzt = ""

def strecken_checken:
    m.links(0.3)
    links = s.distance()
    m.rechts(0.3)
    m.rechts(0.3)
    rechts = s.distance()
    m.links(0.3)
    return links, rechts
    
    # später die Regelen in eine Funktion schreiben

while True: # timer!
    
    while d < 50:
        links, rechts = strecken_checken() # misst Abstand links und rechts, gibt links, rechts zurück
        
        if zuletzt != "":
            if zuletzt == "r" and links >= 100:
                print("zuletzt rechts, jetzt links")
                m.links(0.3)
                m.forward(2)
                # nach links fahren
            elif zuletzt == "l" and links >= 100:
                print("zuletzt links, jetzt links")
                m.links(0.3)
                m.forward(2)
                # nach links fahren
            elif zuletzt == "l" and rechts >= 100:
                print("zuletzt links, jetzt rechts")
                m.rechts(0.3)
                m.forward(2)
            else:
                print("panik")
        else: # bisher noch nicht gedreht!
            if links > rechts:
                m.links(0.3)
                m.forward(2) # für 20 cm
            else:
                m.rechts(0.3)
                m.forward(2) # für 20 cm
        
        # Regeln
        
    else:
        m.forward(1.4) # 20c m fahren

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert