Aufgaben¶
LEDs ansteuern¶
Zu Beginn ist es am einfachsten die LEDs anzusteuern, um sich an die Funktionsweise des Autos zu gewöhnen.
Dafür können die Funktion setup() und loop() genutzt werden.
In der Funktion setup() muss jeder Pin, an den eine LED angeschlossen ist als Ausgang definiert werden. Dies geschieht mit der Funktion pinMode().
Die LED vorne links ist an Pin 13 angeschlossen.
Wie im Schaltplan im Abschnitt A1 zu sehen, ist der LED ein Vorwiderstand vorgeschaltet. Dieser ist notwendig, um die LED vor zu hohen Strömen zu schützen. Der notwendige Widerstand ist bei jeder LED unterschiedlich und kann mithilfe des Ohmschen Gesetzes berechnet werden.
void setup()
{
pinMode(13, OUTPUT); // Pin 13 als Ausgang definieren
}
Alternativ sind alle verwendeten Pins in der Datei config.h als Variablen definiert.
Diese Datei muss in der Datei main.cpp inkludiert werden. Dafür muss der relative Pfad zur Datei angegeben werden.
Diese Variablen können dann in der Funktion setup() genutzt werden. Die Variable PIN_LED_WHITE_LEFT entspricht dem Wert 13.
Dies kann überprüft werden, indem man mit dem Mauszeiger über die Variable fährt.
#include "../lib/config.h"
void setup()
{
pinMode(PIN_LED_WHITE_LEFT, OUTPUT); // PIN_LED_WHITE_LEFT entspricht dem Wert 13
}
So ist besser erkennbar, welcher Pin konfiguriert wurde.
Im Setup kann auch noch die initiale Helligkeit der LEDs eingestellt werden. Dies geschieht mit der Funktion analogWrite().
Der Wert kann zwischen 0 und 255 liegen. 0 entspricht ausgeschaltet und 255 entspricht voller Helligkeit.
void setup()
{
pinMode(PIN_LED_WHITE_LEFT, OUTPUT);
analogWrite(PIN_LED_WHITE_LEFT, 255);
}
Die LED vorne links leuchtet nun mit voller Helligkeit. Das kann für alle weiteren LEDs wiederholt werden.
Die LED kann auch mit der Funktion digitalWrite() auf die volle Helligkeit gesetzt oder ausgeschaltet werden. Diese Funktion nimmt den Pin sowie den Wert HIGH oder LOW entgegen.
In der Funktion loop() kann die LED zum blinken gebracht werden. Dafür wird zusätzlich die Funktion delay() benötigt.
Diese Funktion wartet die angegebene Zeit in Millisekunden.
void loop()
{
analogWrite(PIN_LED_WHITE_LEFT, 255); // LED auf volle Helligkeit setzen
delay(1000); // 1 Sekunde warten
analogWrite(PIN_LED_WHITE_LEFT, 127); // LED auf halbe Helligkeit setzten
delay(1000); // 1 Sekunde warten
analogWrite(PIN_LED_WHITE_LEFT, 0); // LED ausschalten
delay(1000); // 1 Sekunde warten
}
In den meisten Fällen wird kein wirklicher Analogwert ausgegeben, also keine Spannung zwischen 0 und 3,3V. Stattdessen wird ein PWM-Signal ausgegeben. Das bedeutet, dass die LED schnell ein- und ausgeschaltet wird. Das Verhältnis zwischen der Zeit, in der die LED an ist und der Zeit, in der sie aus ist, bestimmt die Helligkeit. Wenn die LED ein ist wird sie immer mit 3,3V betrieben.
Manche Mikrocontroller haben spezielle Ausgänge, die auch einen echten Analogwert ausgeben können. In diesem Projekt ist die Weiße rechte LED an einem solchen Ausgang angeschlossen.
Das dimmen über ein PWM Signal kann selbst getestet werden, indem man die LED mit voller Helligkeit schnell blinken lässt. Es kann die Funktion delayMicroseconds() verwendet werden, um die Zeit zu bestimmen, in der die LED an oder aus ist.
Wenn die LED langsam aufleuchten soll, kann die Helligkeit in einer For-Schleife schrittweise erhöht werden.
void loop()
{
for (int i = 0; i <= 255; i++) // Schleife wird 255 mal durchlaufen, der Wert von i wird jedes mal um 1 erhöht
{
analogWrite(PIN_LED_WHITE_LEFT, i); // Helligkeit erhöhen
delay(10); // 10ms warten
}
}
Der Wert von i wird in der Schleife von 0 bis 255 erhöht. Die LED leuchtet also langsam auf.
Die Schleife kann auch rückwärts laufen, um die LED langsam zu dimmen. Dafür wird der Wert von i von 255 auf 0 mittels i-- verringert.
void loop()
{
for (int i = 255; i >= 0; i--) // Schleife wird 255 mal durchlaufen, der Wert von i wird jedes mal um 1 verringert
{
analogWrite(PIN_LED_WHITE_LEFT, i); // Helligkeit verringern
delay(10); // 10ms warten
}
}
Die LED leuchtet nun langsam auf und wieder ab. Dies kann für alle weiteren LEDs wiederholt werden. Vollständiger Code für eine LED:
#include "../lib/config.h"
void setup()
{
pinMode(PIN_LED_WHITE_LEFT, OUTPUT);
}
void loop()
{
for (int i = 0; i <= 255; i++) // Schleife wird 255 mal durchlaufen, der Wert von i wird jedes mal um 1 erhöht
{
analogWrite(PIN_LED_WHITE_LEFT, i); // Helligkeit erhöhen
delay(10); // 10ms warten
}
for (int i = 255; i >= 0; i--) // Schleife wird 255 mal durchlaufen, der Wert von i wird jedes mal um 1 verringert
{
analogWrite(PIN_LED_WHITE_LEFT, i); // Helligkeit verringern
delay(10); // 10ms warten
}
}
Es können auch die beiden Buttons verwendet werden, um die LEDs zu steuern.
Diese müssen in der Funktion setup() als Eingang definiert werden.
void setup()
{
pinMode(PIN_BUTTON_1, INPUT);
}
In der Funktion loop() kann dann überprüft werden, ob der Button gedrückt wurde. Dafür wird die Funktion digitalRead() sowie eine If-Abfrage benötigt.
void loop()
{
if (digitalRead(PIN_BUTTON_1) == HIGH) // Button wurde gedrückt
{
analogWrite(PIN_LED_WHITE_LEFT, 255); // LED auf volle Helligkeit setzen
}
else
{
analogWrite(PIN_LED_WHITE_LEFT, 0); // LED ausschalten
}
}
Wie im Schaltplan in Abschnitt B4 zu sehen, befindet sich an dem Button ein Pull-Down Widerstand. Dieser ist notwendig, da die Eingänge des Mikrocontrollers einen sehr hohen Widerstand haben. Ohne den Pull-Down Widerstand würde der Pin nach einmaligen drücken des Buttons lange auf HIGH bleiben und mit der Zeit in einen undefinierten Zustand übergehen. Bei einem undefinierten Zustand befindet sich die Spannung an dem Pins zwischen den Spannung, die für ein eindeutiges HIGH oder LOW benötigt werden. Wenn der Button nicht gedrückt ist, liegt ein LOW Signal an dem Pin an, da dieser über den Widerstand mit GND verbunden ist. Wenn der Button gedrückt wird, wird der Widerstand zu 3,3V viel kleiner als der zu GND und der Pin wird auf HIGH gesetzt.
Auslagerung in Funktionen¶
Wenn alles funktioniert, kann eine Funktion für den Code erstellt werden. Diese Funktion kann dann in der Funktion loop() aufgerufen werden.
Unter dem Ordner lib/ sind eine Reihe an verschiedenen Header Dateien zu finden, welche die Funktionen der
einzelnen Komponenten bereitstellen sollen.
Funktionen:
setupLed()setFrontLed(uint8_t brightness)setBackLed(uint8_t brightness)setAllLed(uint8_t brightness)
Für die gewünschte Funktionalität werden folgende Arduino Funktionen benötigt:
pinMode()analogWrite()
#pragma once
#include "config.h"
void setupLed()
{
pinMode(PIN_LED_RED_LEFT, OUTPUT);
pinMode(PIN_LED_RED_RIGHT, OUTPUT);
pinMode(PIN_LED_WHITE_LEFT, OUTPUT);
pinMode(PIN_LED_WHITE_RIGHT, OUTPUT);
}
void setAllLed(uint8_t brightness)
{
analogWrite(PIN_LED_RED_LEFT, brightness);
analogWrite(PIN_LED_RED_RIGHT, brightness);
analogWrite(PIN_LED_WHITE_LEFT, brightness);
analogWrite(PIN_LED_WHITE_RIGHT, brightness);
}
Der in setAllLed() übergebene Wert brightness wird an alle LEDs übergeben.
#pragma once sorgt dafür, dass der Code der Header Datei nur einmal eingebunden wird. Selbst wenn die Datei mehrmals inkludiert wird.
In der Datei main.cpp können die Funktionen folgendermaßen aufgerufen werden:
#include "../lib/led.h"
void setup()
{
setupLed();
}
void loop()
{
setAllLed(0);
delay(1000);
setAllLed(255);
delay(1000);
}
Alle LEDs blinken. Um sich mit den Funktionen vertraut zu machen, können einzelne Funktionen erstellt werden, um nur die vorderen LEDs oder die hinteren LEDs anzusteuern.
Es können Beispielsweise auch die beiden Buttons verwendet werden, um die Helligkeit der LEDs zu steuern.
Dafür kann eine globale Variable mit beliebigem Namen und vorzugsweiße dem Datentyp uint8_t verwendet werden, um die Helligkeit zu speichern. Diese sollte mit dem Wert 0 initialisiert werden: uint8_t brightness = 0;
Der Wert der Variable kann mit brightness++ oder brightness-- erhöht oder verringert werden, je nachdem welcher Button gedrückt wurde.
Der Wert kann an die Funktion setAllLed() übergeben werden. Mithilfe eines delays kann die Geschwindigkeit der Helligkeitsänderung eingestellt werden.
Hier kann auch der „Überlauf“ einer Variable getestet werden. Wenn der Datentyp uint8_t verwendet wird, beginnt der Wert wieder bei 0, wenn der Wert 255 erhöht wird.
In die andere Richtung beginnt der Wert bei 255, wenn der Wert 0 verringert wird.
Um das zu verhindern, kann eine If-Abfrage verwendet werden, um den Wert nur zu erhöhen, wenn dieser kleiner als 255 ist und nur zu verringern, wenn er größer als 0 ist.
Für eine schnellere Helligkeitsänderung kann der Wert von brightness um 10 erhöht oder verringert werden.
Das kann mittels brightness = brightness + 10 oder der Kurzschreibweise brightness += 10 erreicht werden.
Auslesen der Batteriespannung¶
Die Batteriespannung kann mithilfe der Funktion analogReadMilliVolts() an dem Pin PIN_VOLTAGE_MEASURE ausgelesen werden. Da dieser Pin an einen Spannungsteiler aus zwei Widerständen angeschlossen ist muss der eingelesene Wert mit 3.7 Multipliziert werden. Die Spannung wird dabei auf die beiden Widerstände mit 100k und 270k aufgeteilt. Nur die Spannung, die an dem 100k Widerstand anliegt liegt auch an dem Pin des Mikrocontrollers an. Aufgrund des sehr hohen Eingangswiderstand kann der Spannungsteiler als Unbelastet angenommen werden. Ohne den Spannungsteiler wäre die Spannung an dem Pin zu hoch und könnte den Mikrocontroller beschädigen. Die Funktion zum Auslesen der Batteriespannung ist bereits implementiert. Der Spannungsteiler ist in Abschnitt D6 abgebildet.
Außerdem ist eine Funktion implementiert, die nach einmaligem Aufrufen automatisch die Batteriespannung alle 10 Sekunden im Hintergrund misst und bei Unterschreitung den ESP sowie Motorcontroller und Spannungsregler abschält. Bitte beachten Sie, dass danach trotzdem minimal Strom verbraucht wird und die Batterien tiefendentladen werden können wenn diese nicht über den Hauptschalter abgeschaltet oder aus dem Batteriefach entnommen werden.
Die Funktion startAutomaticBatteryProtection() kann zur Verwendung des automatischen Schutzes einmalig im setup() aufgerufen werden.
An der Batteriespannung ist auch ein Spannungsregler angeschlossen. Dieser befindet sich im Schaltplan im Abschnitt B8. Dieser wird genutzt, um aus der Versorgungsspannung der Batterie (6V bis 8,2V) fest 5V zu erzeugen. Diese 5V werden für den Mikrocontroller, Servo und den Ultraschallsensor benötigt. Die 3,3V erzeugt der Mikrocontroller intern aus den 5V. Die Batteriespannung wird direkt nur für die Motoren verwendet.
Lenkung ansteuern¶
Die Funktion der Lenkung wird durch einen Servo Motor gegeben welcher die Lenkachse verschieden ausrichten kann. Ziel ist es diesen Servo Motor präzise anzusteuern und verschiedene Ausgangspositionen fest in den Programmablauf zu implementieren.
Der Servo wird mit einem PWM-Signal angesteuert, ähnlich wie die LEDs. Der integrierte Motortreiber benötigt jedoch bestimmte Frequenzen und An / Aus Zeiten des Signals. Deshalb wird zur Ansteuerung die Bibliothek ESP32Servo.h verwendet. Diese wurde automatisch bei dem erstmaligen Öffnen des Projektes installiert. Diese Bibliothek enthält alle Funktionen, die benötigt werden, um den Servo Motor nutzerfreundlich anzusteuern.
Der Servo benötigt außerdem noch eine Spannungsversorgung. Diese ist mit 5V gegeben.
Zur Ansteuerung muss zuerst eine Instanz der Klasse Servo erstellt werden. Diese Instanz wird benötigt um die Funktionen der Klasse zu nutzen. Von nun an kann auf die Funktionen mithilfe von steer. zugegriffen werden. Die Initialisierung wurde bereits in der Funktion setupSteer() implementiert.
Die einzige Funktion, die im weiteren Verlauf für die Ansteuerung benötigt wird, ist steer.write(). Diese Funktion nimmt einen Wert zwischen 0 und 180 entgegen. Dieser Wert entspricht dem Winkel, in den der Servo Motor fahren soll. Der minimale für das Fahrzeug verwendbare Wert ist 65 und der maximale Wert ist 115. Der Wert 90 entspricht der Mittelstellung des Servos. Dann fährt das Auto geradeaus. Die min und max Werte sind in der Datei config.h als Variablen definiert.
Die korrekten Werte für min und max müssen in der Datei config.h angepasst werden. Diese müssen möglicherweise experimentell ermittelt werden, da die Servos bei 90 Grad nicht immer perfekt geradeaus fahren und sich dadurch auch die möglichen Werte für min und max ändern.
void setup()
{
setupSteer();
}
void loop()
{
steer.write(65); // Servo auf minimalen Winkel setzen
delay(1000); // 1 Sekunde warten
steer.write(90); // Servo auf mittleren Winkel setzen
delay(1000); // 1 Sekunde warten
steer.write(115); // Servo auf maximalen Winkel setzen
delay(1000); // 1 Sekunde warten
steer.write(90); // Servo auf mittleren Winkel setzen
delay(1000); // 1 Sekunde warten
}
Es kann sein, dass der Servo zu Beginn nicht perfekt auf Mittelstellung ist. Um das zu beheben wird ein Offset zu dem Wert hinzugefügt, mit dem der Servo gesteuert wird. Der Perfekte Steer-Wert für die Mittelstellung kann experimentell ermittelt werden. Dafür kann der Servo mit den beiden Tastern gesteuert werden und der aktuelle Wert auf dem Seriellen Monitor ausgegeben werden.
Es kann eine extra Funktion erstellt werden, die den Servo ansteuert, auf min und max Winkel überprüft und den Offset hinzugefügt.
Motoren ansteuern¶
Es gibt 2 Motoren welche über einen einzelnen Motortreiber angesteuert werden.
Alle für den Motor genutzten Pins müssen in der Setup Funktion als Ausgang definiert werden.
für die Motorsteuerung muss ein PWM Signal an den Pins des Mikrocontrollers erzeugt werden. Dafür werden ledc Funktionen verwendet.
Um den Einstieg leichter zu gestalten, sind die Funktionen in der Datei motor.h Datei bereits implementiert.
Der Motortreiber beinhaltet H-Brücken, die es ermöglichen, die Motoren in beide Richtungen zu drehen.
Die Funktion setupMotor() muss einmalig in der Setup Funktion aufgerufen werden.
Die Funktion moveMotor(int16_t speed) kann in der Loop Funktion aufgerufen werden, um die Geschwindigkeit beider Motoren zu ändern.
Der Wert speed kann zwischen -255 und 255 liegen. Ein positiver Wert bewirkt, dass das Auto vorwärts fährt, ein negativer Wert bewirkt, dass das Auto rückwärts fährt.
Wenn der Wert größer als 255 oder kleiner als -255 ist, wird der Wert auf 255 oder -255 gesetzt.
Es können die beiden Taster verwendet werden, um die Motoren zu starten und zu stoppen.
Die Motoren drehen zum mit dem vorgegebenen Code in die falsche Richtung. Die Funktion moveMotor() muss in der Datei motor.h angepasst werden, um die Motoren in die richtige Richtung drehen zu lassen.
Ultraschallsensor¶
Der Ultraschallsensor kann Distanzen zwischen dem Sensor und einem Objekt ausgeben. Wie zum Beispiel dem zwischen Auto und einer Wand.
Bei dem Ultraschallsensor werden zwei Pins benötigt. Ein Pin für den Trigger und ein Pin für den Echo. An dem Trigger Pin wird ein kurzes Signal gesendet. Mithilfe des Echo Pins wird die Zeit gemessen, die das Signal benötigt, um an einem Gegenstand zu Reflektieren zum Sensor zurückzukehren.
Dafür muss der Trigger Pin als Ausgang und der Echo Pin als Eingang definiert werden.
Ablauf, um die Distanz zu messen:
Trigger Pin auf LOW setzen
2µs warten
Trigger Pin auf HIGH setzen
10µs warten
Trigger Pin auf LOW setzen
Zeit messen, die der Echo Pin HIGH ist
Distanz berechnen
Distanz zurückgeben
Eine Zeit in Mikrosekunden kann mit der Funktion delayMicroseconds() gewartet werden. Die Zeit, die der Echo Pin High ist, kann mit der Funktion pulseIn() gemessen werden. Weitere Informationen sind im Abschnitt Zeit oder unter https://www.arduino.cc/reference/de/language/functions/advanced-io/pulsein/ zu finden. Über die Schallgeschwindigkeit kann die Distanz in Zentimetern berechnet werden. Die Schallgeschwindigkeit beträgt ca. 343 m/s.
Die Funktionen zum initialisieren und zum messen der Distanz sollen in der Datei sonic.h implementiert werden.
Die Funktion setupSonic() soll die Pins als Eingang und Ausgang definieren.
Die Funktion getDistance() soll die Distanz in Zentimetern zurückgeben.
Die berechnete Distanz kann auf dem Seriellen Monitor ausgegeben werden.
Wenn die Abstandsmessung zuverlässig funktioniert kann die Distanz genutzt werden, um das Auto anzuhalten, wenn ein Hindernis zu nah ist.
Bodensensor für Linienerkennung¶
Zur Erkennung von Linien befinden sich auf der Unterseite 8 Bodensensoren. Diese können analog ausgelesen werden.
Ein einzelner Sensor besteht aus einer LED und einem Fototransistor. Die LED sendet Licht aus, welches von der Linie reflektiert wird. Über den Fototransistor kann die Intensität des reflektierenden Lichts gemessen werden. Die Intensität des reflektierten Lichts hängt von der Farbe der Linie ab.
Die einfachste Methode ist nur zu überprüfen, ob der Sensor über der Linie ist oder nicht. Hierfür ist ein Schwellwert in der Datei config.h definiert. Dieser kann den eigenen Anforderungen angepasst werden.
Für einen einzelnen Sensor kann das folgendermaßen erreicht werden:
bool isLineDetected = analogRead(PIN_REFLECTION_SENSOR_1) > REFLECTION_SENSOR_THRESHOLD;
Dieses Beispiel ist für eine schwarze Linie auf weißem Untergrund. Wenn die Linie weiß ist und der Untergrund schwarz, muss das die Abfrage entsprechend geändert werden.
Mithilfe der Sensoren kann der Servomotor angesteuert werden, um dem Linienverlauf zu folgen.
Um die Sensoren zu testen kann der Wert in der loop() Funktion ausgelesen und auf dem Seriellen Monitor ausgegeben werden. Hier kann auch getestet werden, die ausgelesenen Werte in einem Array abzuspeichern.
Am einfachsten wird eine Funktion erstellt, die die Sensoren ausliest, den Lenkwinkel des Servos berechnet und diesen zurück gibt.
Alternativ kann eine Funktion nur für das Auslesen der Sensoren erstellt werden, die Werte in einem übergebenen Array speichern oder ein std::array zu verwenden.
Um die Verfolgung zu verbessern, oder für erfahrende Nutzer können direkt die analogen Werte verwendet werden. Dadurch kann die Linienerkennung minimal verbessert werden, ist jedoch viel aufwendiger. Dadurch kann überprüft werden, ob sich ein Sensor komplett über der Linie befindet oder am Rand.