## Inhaltsverzeichnis 1. Allgemein 1.1 Hardware 1.2 Bibliotheken 2. Teilprojekte 2.1 Manuelles Fahren 2.2 Anpassbare Beschleunigung 2.3 Autonomes Fahren 3. Probleme 3.1 Verweigern beim Hochladen auf dem Arduino Nano 3.2 Interrupts bei UltraSonic Sensor 3.3 Leere Batterien 4. Fazit ## 1. Allgemein ![](https://hackmd.io/_uploads/SydX1Zu_h.png) ### 1.1 Hardware #### 1.1.1 PIN Aufteilung Auto |PIN | Zweck | |----- | --------------------------------------- | |A1 | Roter LED-PIN | |A2 | Grüner LED-PIN | |A3 | Blauer LED-PIN | |A4 | Trigger PIN für Ultrasonic Sensor | |A5 | Echo PIN für Ultrasonic Sensor | |7 |Motorrichtung von Motor A | |6 |Motorgewschwindigkeit von Motor A | |5 |Motorgewschwindigkeit von Motor B | |4 |Motorrichtung von Motor B | |3 |Ultrasonic Servo PIN | |2 |Servo PIN für die Lenkung vom Auto | #### 1.1.2 PIN Aufteilung Controller | PIN | Zweck | | ----| --------| |1|Richtung Y vom Stick U1 (nach vorne bzw. hinten fahren)| |2|Richtung X vom Stick U2 (lenken)| |3|Richtung Y vom Stick U2| |4| R1 am Controller| |5| R6 am Controller (Einstellung der Beschleunigung)| |**Tasten A-D**|| |2|D2 am Controller| |3|D3 am Controller (Umschalten in manuelle Steuerung)| |4|D4 am Controller (Umschalten in Selbstfahrenmodus)| |5|D5 am Controller| |**LED-PIN**|| |6|Anzeige des Status des NRF24L01| |7|Falls Auto in manueller Steuerung| |8|Falls Auto in Sebstfahrenmodus| ### 1.2 Bibliotheken |Name |Verwendung | |--------------|---------------------------------------------------| |SPI.h |Ein serielles Protokoll speziell mit Microcontroller | |RF24.h |Zur Vebindung des Autos und dem Controller | |Servo.h |Ermöglicht Arduino-Boards, eine Vielzahl von Servomotoren zu steuern. | |NewPing.h |Zur Berrechnung der Distanz des Ultrasonic Sensors | |Filter.h |Zur Filterung des Eingangssignals | ## 2. Teilprojekte ## 2.1 Manuelles Fahren ### 2.1.1 Soll Zustand Das Auto soll mithilfe eines Controllers gesteuert werden. Die Daten werden regelmäßig vom Controller geholt und auf die erreichbaren Positionen gemappt. ### 2.1.2 Implementation Zuerst werden die Daten vom Controller empfangen. Anschließend werden die einzelnen Werte so gemappt, dass die Servos passend gesteuert werden können. Die Fahrtrichtung wird noch erkannt und entsprechend gesetzt. ### 2.1.3 Codebeispiel ``` C++ int dirServoDegree = map(data[0], 0, 1023, 55 ,125); int ultrasonicServoDegree = map(data[0], 0, 1023, 30, 110); int motorSpd = data[1] - 512 + (data[6] - 512) / 10; bool motorDir = motorSpd > 0 ? HIGH : LOW; // Motorrichtung wird bestimmt motorSpd = abs(constrain(motorSpd, -512, 512)); motorSpd = map(motorSpd, 0, 512, 0, 255); ```` Hier werden die Werte passend für die erreichbaren Positionen der Servos gemappt. Des Weiteren wird die Motorgeschwindigkeit berechnet und die Motorrichtung bestimmt. Der absolute Wert wird anschließend genommen und als Geschwindigkeit gesetzt. ## 2.2 Anpassbare Beschleunigen ### 2.2.1 Soll Zustand Das Auto soll, wenn es manuell gesteuert wird, über eine einstellbare Geschwindigkeit verfügen. Diese kann über einen Servo am Controller eingestellt werden. Je nach Einstellung benötigt das Auto eine gewisse Zeit, um auf die übertragene Geschwindigkeit zu beschleunigen. ### 2.2.2 Implementation Der eingestellte Wert des Servos am Controller wird übertragen und entsprechend gemappt. Anschließend wird die übergebene Geschwindigkeit durch den gemappten Wert dividiert. In einem Intervall von 150 Millisekunden wird ein Zähler hochgezählt. Anhand des Zählers und des dividierten Werts wird die aktuelle Geschwindigkeit berechnet und eingestellt. ### 2.2.3 Codebeispiel ``` C++ if ((millis()- timeStamp) >= intervall && zaehler < speedLevel){ timeStamp = millis(); zaehler = zaehler + 1; } // Falls die berechnete Geschwindigkeit unter dem Wert, ab dem Losgefahren wird liegt byte spd = (add * zaehler) <= addToDrive && speedLevel >= 18 ? addToDrive : (add * zaehler); // Falls der maximal berechnete Wert erreicht, wird Geschwindigkeit auf erreichbarea Maximum gesetzt spd = ((add * zaehler) >= add * speedLevel) ? (add * zaehler) + modulo: (add * zaehler); ``` Der Beispielcode erhöht einen Zähler in einem Intervall, das für die Beschleunigung zuständig ist. Dabei wird auch überprüft, ob die Geschwindigkeit hoch genug ist, damit das Auto losfahren kann, da die Beschleunigung sonst zu lange dauern würde. Zum Abschluss wird überprüft, ob die übergebene Geschwindigkeit erreicht wurde, da keine Kommazahlen berücksichtigt werden. Falls dies nicht der Fall ist, wird die Geschwindigkeit auf die maximale übertragene Geschwindigkeit gesetzt. ## 2.3 Autonomes Fahren ### 2.3.1 Soll Zustand Das Auto soll sich mithilfe von Daten, die vom Ultraschallsensor gesammelt werden, fortbewegen, ohne in ein Hindernis zu fahren. Sollte der Sensor ein Hindernis entdecken, soll die Fahrt sofort gestoppt werden. Anschließend soll der Sensor ermitteln, in welche Richtung das Auto ausweichen kann. Diese Richtung wird genutzt, um den Kurs der Fahrt rückwärts zu ändern. Nach der Kursänderung wird die Fahrt fortgesetzt. ### 2.3.2 Implementation Im Array data[], das vom Controller gesendet wird, wird ein Byte übergeben, um den automatischen Modus des Autos zu aktivieren. Wenn der automatische Modus aktiviert ist, wird die Geschwindigkeit auf einen mittleren Wert eingestellt und überprüft, ob sich vor dem Auto eine Wand befindet. Falls die Distanz zum nächstgelegenen Objekt kleiner als 20 cm ist, wird eine Wand erkannt und der boolsche Wert "wand" auf true gesetzt. Dieser Wert wird dann an die Methode Autocontrol übergeben. Wenn Autocontrol den Wert false erhält, lässt es das Auto einfach nur vorwärts fahren. Wenn jedoch der Wert true übergeben wird, stoppt es das Auto sofort und ruft die Methode GetDirection auf. In dieser Methode wird das Auto in eine sichere Richtung gelenkt, die durch den Ultraschallsensor ermittelt wird. Anschließend macht das Auto eine 90°-Drehung rückwärts, bevor es wieder geradeaus weiterfährt. ### 2.3.3 Codebeispiele #### 2.3.3.1 getDistance() ``` C++ float getDistance() { delay(50); int distance = sonar.ping_cm(); // Distanz ermitteln distance = ma2.filter(distance); // Filtern des ermittelten Wertes return distance; } ``` Für die Ermittlung der aktuellen Distanz wird die Bibliothek NewPing.h verwendet. Die Methode "sonar.ping_cm()" liefert die Distanz direkt in Zentimetern zurück, sodass das Parsen von Millisekundenwerten vermieden wird. Der ermittelte Wert wird durch die Methode "ma2.filter(int Wert)" der bereitgestellten Filter.h-Bibliothek gefiltert, um Störsignale des Sensors zu entfernen. #### 2.3.3.2 autoControl() ``` C++ void autoControl(byte motorSpd, bool wand){ if(wand){ analogWrite(pwmAPin, 0); analogWrite(pwmBPin, 0); getDirection(); } dirServo.write(90); digitalWrite(dirAPin, HIGH); digitalWrite(dirBPin, HIGH); analogWrite(pwmAPin, motorSpd); analogWrite(pwmBPin, motorSpd); } ``` Wenn der Methode als Übergabeparameter true übergeben wird, wird der Code im "if(wand)"-Block ausgeführt. In diesem Block werden die Motoren mithilfe von "AnalogWrite" gestoppt. Anschließend wird die Methode "getDirection()" aufgerufen. Sobald die Methode "getDirection()" beendet ist, werden die MotorSpeeds beider Motoren mithilfe von "AnalogWrite()" wieder auf den übergebenen Wert gesetzt. Durch die beiden "digitalWrite()"-Anweisungen wird die Richtung der Motoren auf "hoch" bzw. "nach vorne" gesetzt. #### 2.3.3.3 Herzstück von getDirection() ``` C++ float distances[6] = {0,0,0,0,0,0}; int j = 0; for(int i = 20; i < 140; i+=20){ delay(100); distances[j] = getDistance(); j++; ultrasonicServo.write(i + 15); } int minIndex = 100; float minValue = 600; for(int i = 0; i < 6; i++){ if(distances[i] < minValue && distances[i] != 0){ minValue = distances[i]; minIndex = i; } } ``` Das float-Array "distances[]" wird deklariert, um die ermittelten Distanzwerte abzuspeichern. Die Variable "j" wird als Zählervariable deklariert. Die Variable "i" wird als Zählervariable in der for-Schleife verwendet und ursprünglich auf den Wert 20 gesetzt. Bei jedem Schleifendurchgang wird der Wert von "i" um 20 erhöht. Die Schleife wird abgebrochen, sobald der Wert von "i" 140 erreicht hat. Innerhalb der Schleife wird zuerst eine Verzögerung von 100 Millisekunden durchgeführt. Diese Verzögerung ist erforderlich, um die Signale des Ultraschallsensors zu staffeln und Überlappungen zu vermeiden. Bei jedem Durchlauf der Schleife wird mithilfe der Methode "getDistance()" ein Distanzwert ermittelt und im Array abgespeichert. Der Speicherort wird durch die Variable "j" kontrolliert, die bei jedem Schleifendurchgang um 1 erhöht wird. Schließlich wird innerhalb der Schleife der Servo des Ultraschallsensors auf den Wert "i + 15" gesetzt. Hier repräsentiert "i" die Gradzahl, um die sich der Servo dreht. "+15" wird als minimaler Wert benötigt, da der Servo sich nicht unter 15° drehen kann. Nach der ersten Schleife wird in einer weiteren for-Schleife, die von "i = 0" bis "i = 5" in Einerschritten läuft, das Minimum der ermittelten Werte ermittelt und zusammen mit dem Index des Speicherorts im Array des Minimums in Variablen abgespeichert. ## 3. Probleme ### 3.1 Verweigern beim Hochladen auf dem Arduino Nano Beim Hochladen von neuem Code auf den Arduino Nano des Controllers trat das Problem auf, dass der Mikrocontroller nicht erkannt wurde und dadurch das Hochladen von neuem Code auf den Nano nicht möglich war. Das Problem wurde gelöst, indem ein Treiber für den Nano installiert wurde und in der Arduino IDE die Option "old Bootloader" unter "Prozessor" ausgewählt wurde. ### 3.2 Interrupts bei UltraSonic Sensor Beim Arbeiten mit dem Ultraschallsensor wurde frühzeitig festgestellt, dass der Sensor nur den Wert "0" zurückgibt. Nach intensiven Tests wurde festgestellt, dass die Pins 'D0' und 'D1' aufgrund ihrer Interrupts Probleme verursachten. Als Lösung wurden der Echo- und Triggerpin des Sensors auf die analogen Pins 'A5' und 'A4' umgesteckt. Die RGB-Pins, die zuvor diese Pins belegt hatten, wurden auf 'A3', 'A4' und 'A5' umgelegt. ### 3.3 Leere Batterien Ein weiteres Problem, dem wir im Verlauf des Projekts gegenüberstanden, war die geringe Kapazität der Lithium-Ionen-Batterien. Mehrmals entluden sich sowohl die Batterien des Autos als auch die des Controllers, was zu vorübergehenden Arbeitsunterbrechungen führte, da keine Ersatzbatterien verfügbar waren. ## 4. Fazit Das Arduino-Projekt, das wir umgesetzt haben, hat uns eine spannende und lehrreiche Reise in die Welt der Robotik und Automatisierung ermöglicht. Mit Hilfe des Arduino-Mikrocontrollers konnten wir eine Plattform entwickeln, die uns sowohl manuelle Steuerung als auch autonomes Fahren ermöglichte. Dieses Projekt hat nicht nur unsere technischen Fähigkeiten gestärkt, sondern auch unsere Kreativität angeregt. Wir konnten unser eigenes Roboterfahrzeug gestalten und dabei unsere Vorstellungskraft und Innovationskraft einbringen. Alles in allem war das Arduino-Projekt eine inspirierende und erfüllende Erfahrung. Es hat uns gezeigt, wie vielfältig und spannend die Welt der Robotik und Automatisierung sein kann und uns motiviert, weiterhin an solchen Projekten zu arbeiten und unsere Fähigkeiten weiterzuentwickeln. ## 5. Quellen |Name |Verwendung | Link | |--------------|---------------------------------------------------|------------------------------------------------------------| |Filter.h |Zur Filterung des Eingangssignals | https://github.com/zacsketches/Filter/blob/master/Filter.h | |NewPing.h |Zur Berrechnung der Distanz des Ultrasonic Sensors | https://github.com/eliteio/Arduino_New_Ping | |HackMD |zum Schreiben der Dokumentation | https://hackmd.io/ | ## 6. Quellcode ### 6.1 Auto ``` C++ #include <SPI.h> #include "RF24.h" #include <Servo.h> #include <NewPing.h> #include <Filter.h> RF24 radio(9, 10); byte addresses[5] = "00007"; int data[9]={512, 512, 0, 0, 1, 1, 512, 512, 512}; Moving_average ma2(3, 90); Servo dirServo; int dirServoPin = 2; float dirServoOffset = 0; Servo ultrasonicServo; int ultrasonicPin = 3; float ultrasonicServoOffset = 15; int trigPin = A5; // define Trig pin for ultrasonic ranging module int echoPin = A4; // define Echo pin for ultrasonic ranging module NewPing sonar(trigPin, echoPin, 600); const int dirAPin = 7; // define pin used to control rotational direction of motor A const int pwmAPin = 6; // define pin for PWM used to control rotational speed of motor A const int dirBPin = 4; // define pin used to control rotational direction of motor B const int pwmBPin = 5; // define pin for PWM used to control rotational speed of motor B // PINS der LEDs const int RPin = A1; const int GPin = A3; const int BPin = A2; int RGBVal = 0; int automatic = 0; float maxDistance = 200; // define the range(cm) for ultrasonic ranging module, Maximum sensor distance is rated at 400-500cm. float soundVelocity = 340; // Sound velocity = 340 m/s float rangingTimeOut = 2 * maxDistance / 100 / soundVelocity * 1000000; // define the timeout(ms) for ultrasonic ranging module byte tempSpd; bool tempDir; byte tempLevel; int intervall = 150; unsigned long timeStamp; int zaehler = 1; unsigned long timeStampAuto; void setup() { radio.begin(); // initialize RF24 radio.setRetries(0, 15); // set retries times radio.setPALevel(RF24_PA_LOW); // set power radio.openReadingPipe(1, addresses);// open delivery channel radio.startListening(); // start monitoring Serial.begin(9600); // initialize serial port dirServo.attach(dirServoPin); // attaches the servo on servoDirPin to the servo object ultrasonicServo.attach(ultrasonicPin); // attaches the servo on ultrasonicPin to the servo object pinMode(dirAPin, OUTPUT); // set dirAPin to output mode pinMode(pwmAPin, OUTPUT); // set pwmAPin to output mode pinMode(dirBPin, OUTPUT); // set dirBPin to output mode pinMode(pwmBPin, OUTPUT); // set pwmBPin to output mode pinMode(RPin, OUTPUT); // set RPin to output mode pinMode(GPin, OUTPUT); // set GPin to output mode pinMode(BPin, OUTPUT); // set BPin to output mode pinMode(trigPin, OUTPUT); // set trigPin to output mode pinMode(echoPin, INPUT); // set echoPin to input mode timeStamp = millis(); timeStampAuto = millis(); } int intervallAuto = 80; int angleServo = 20; void loop() { receiveData(); // Empfängt Daten vom Controller if (automatic == 0) { int dirServoDegree = map(data[0], 0, 1023, 55 ,125); int ultrasonicServoDegree = map(data[0], 0, 1023, 30, 110); int motorSpd = data[1] - 512 + (data[6] - 512) / 10; bool motorDir = motorSpd > 0 ? HIGH : LOW; // Motorrichtung wird bestimmt motorSpd = abs(constrain(motorSpd, -512, 512)); motorSpd = map(motorSpd, 0, 512, 0, 255); normalControl(dirServoDegree,ultrasonicServoDegree, motorDir, motorSpd); // Manuelle Steuerung wird ausgeführt } if (automatic == 1) { bool wand = false; int motorspeed = 100; ultrasonicServo.write(angleServo); if ((millis()- timeStampAuto) >= intervallAuto){ timeStampAuto = millis(); angleServo += 10; if (angleServo >= 140) { angleServo = 20; } } int distance = getDistance(); // Entfernung wird gemessen if(distance != 0 && distance < 20){ // Wenn Entfernung kleiner als 20 cm ist motorspeed = 0; // Geschwindigkeit des Motors wird auf 0 gesetzt wand = true; // Wand wird erkannt } autoControl(motorspeed, wand); // Automatische Steuerung wird ausgeführt } } void receiveData(){ if ( radio.available()) { // if receive the data while (radio.available()) { // read all the data radio.read( data, sizeof(data) ); // read data } if(!data[2]){ RGBVal++ ; if(RGBVal>4){ RGBVal=0; } } if(!data[3]){ automatic = 0; } if(!data[4]){ automatic = 1; } } } void autoControl(byte motorSpd, bool wand){ if(wand){ analogWrite(pwmAPin, 0); analogWrite(pwmBPin, 0); getDirection(); } dirServo.write(90); digitalWrite(dirAPin, HIGH); digitalWrite(dirBPin, HIGH); analogWrite(pwmAPin, motorSpd); analogWrite(pwmBPin, motorSpd); } float getDirection(){ float distances[6] = {0,0,0,0,0,0}; int j = 0; for(int i = 20; i < 140; i+=20){ delay(100); distances[j] = getDistance(); j++; ultrasonicServo.write(i + 15); } int minIndex = 100; float minValue = 600; for(int i = 0; i < 6; i++){ if(distances[i] < minValue && distances[i] != 0){ minValue = distances[i]; minIndex = i; } } float dir = map(minIndex, 0,5, 125,50) + dirServoOffset; if (dir >= 80 && dir <= 100) { dir = 125; } dirServo.write(dir); ultrasonicServo.write(70+15); digitalWrite(dirAPin, LOW); digitalWrite(dirBPin, LOW); analogWrite(pwmAPin, 100); analogWrite(pwmBPin, 100); for(int i = 0; i < 20; i++){ delay(100); receiveData(); if (automatic == 0) { break; } } analogWrite(pwmAPin, 0); analogWrite(pwmBPin, 0); } void normalControl(byte dirServoDegree,byte ultrasonicServoDegree, bool motorDir, byte motorSpd) { int speedLevel = map(data[6], 1023, 0, 1, 20); boolean standing; byte addToDrive = 50; if (motorSpd <= 7 || tempDir != motorDir || tempLevel != speedLevel) { zaehler = 1; } byte modulo = motorSpd % speedLevel; byte add = motorSpd / speedLevel; if(zaehler == speedLevel && abs(tempSpd - motorSpd) > add){ zaehler = 1; } tempLevel = speedLevel; tempSpd = motorSpd; tempDir = motorDir; if ((millis()- timeStamp) >= intervall && zaehler < speedLevel){ timeStamp = millis(); zaehler = zaehler + 1; } byte spd = (add * zaehler) <= addToDrive && speedLevel >= 18 ? addToDrive : (add * zaehler); spd = ((add * zaehler) >= add * speedLevel) ? (add * zaehler) + modulo: (add * zaehler); dirServo.write(dirServoDegree + dirServoOffset); ultrasonicServo.write(ultrasonicServoDegree + ultrasonicServoOffset); digitalWrite(dirAPin, motorDir); digitalWrite(dirBPin, motorDir); analogWrite(pwmAPin, spd); analogWrite(pwmBPin, spd); } float getDistance() { delay(50); int distance = sonar.ping_cm(); distance = ma2.filter(distance); return distance; } ``` ### 6.2 Controller ``` C++ #include <SPI.h> #include "RF24.h" RF24 radio(9, 10); // define the object to control NRF24L01 byte addresses[5] = "00007"; // define communication address which should correspond to remote control int data[9]; // define array used to save the communication data int mode = 0; const int pot6Pin = 5; // define R6 const int pot5Pin = 4; // define R1 const int led1Pin = 6; // define pin for LED1 which is close to NRF24L01 and used to indicate the state of NRF24L01 const int led2Pin = 7; // define pin for LED2 which is the mode is displayed in the car remote control mode const int led3Pin = 8; // define pin for LED3 which is the mode is displayed in the car auto mode // Tasten A-D const int APin = 2; // define pin for D2 const int BPin = 3; // define pin for D3 const int CPin = 4; // define pin for D4 const int DPin = 5; // define pin for D5 const int MotorPin = 2; // define pin for direction X of joystick U2 const int ServoPin = 1; // define pin for direction Y of joystick U1 const int ultrasonicPin = 3; // define pin for direction x of joystick U2 void setup() { radio.begin(); // initialize RF24 radio.setRetries(0, 15); // set retries times radio.setPALevel(RF24_PA_LOW); // set power radio.openWritingPipe(addresses); // open delivery channel radio.stopListening(); // stop monitoring pinMode(led1Pin, OUTPUT); // set led1Pin to output mode pinMode(led2Pin, OUTPUT); // set led2Pin to output mode pinMode(led3Pin, OUTPUT); // set led3Pin to output mode pinMode(APin, INPUT_PULLUP); // set APin to output mode pinMode(BPin, INPUT_PULLUP); // set BPin to output mode pinMode(CPin, INPUT_PULLUP); // set CPin to output mode pinMode(DPin, INPUT_PULLUP); // set DPin to output mode data[3] = 0; data[4] = 1; } void loop() { // put the values of rocker, switch and potentiometer into the array data[0] = analogRead(MotorPin); data[1] = analogRead(ServoPin); if(digitalRead(APin)==LOW){ delay(100); data[2] = digitalRead(APin); }else{ data[2] = HIGH; } if( digitalRead(BPin)==LOW){ mode = 0; data[3] = 0; data[4] = 1; } if(digitalRead(CPin)==LOW){ mode = 1; data[3] = 1; data[4] = 0; } data[5] = digitalRead(DPin); data[6] = analogRead(pot5Pin); data[7] = analogRead(pot6Pin); data[8] = analogRead(ultrasonicPin); // send array data. If the sending succeeds, open signal LED if (radio.write( data, sizeof(data) )) digitalWrite(led1Pin,HIGH); // delay for a period of time, then turn off the signal LED for next sending delay(2); digitalWrite(led1Pin,LOW); if(mode==0){ digitalWrite(led2Pin,HIGH); digitalWrite(led3Pin,LOW); } if(mode==1){ digitalWrite(led2Pin,LOW); digitalWrite(led3Pin,HIGH); } } ```