Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Manufacturing Technology >> Herstellungsprozess

Labyrinth-Löser-Roboter mit künstlicher Intelligenz

Komponenten und Verbrauchsmaterialien

Arduino Nano R3
× 1
SparkFun RedBot Sensor - Linienfolger
× 1
ZX03 (basierend auf TCRT5000) Reflektierende Infrarotsensoren (Analogausgang)
× 2
Android-Gerät
× 1
RobotGeek Kontinuierlicher Rotationsservo
× 2
4xAA Batteriehalter
× 2

Apps und Onlinedienste

Arduino-IDE
MIT App Inventor 2

Über dieses Projekt

Einführung

Dieses Tutorial wurde nach meinem letzten Projekt entwickelt:Line Follower Robot - PID Control - Android Setup. Sobald Sie einen Roboter mit Linienverfolgungsfähigkeiten haben, besteht der nächste natürliche Schritt darin, ihm ein gewisses Maß an Intelligenz zu verleihen. Also, unser lieber "Rex, der Roboter" wird jetzt versuchen, auf dem kürzesten und schnellsten Weg aus einem "Labyrinth" zu entkommen (übrigens hasst er den Minotaurus.

Was ist der Unterschied zwischen Labyrinth und Labyrinth ? Laut http://www.labyrinthos.net wird in der englischsprachigen Welt oft angenommen, dass ein Design, um als Labyrinth bezeichnet zu werden, Entscheidungen auf dem Weg haben muss. Dazu gehören natürlich viele der modernen Installationen in Vergnügungsparks und Touristenattraktionen, einschließlich unseres 2D-Labyrinths hier. Ein allgemeiner Konsens weist auch darauf hin, dass Labyrinthe einen Weg haben, der unaufhaltsam vom Eingang zum Ziel führt, wenn auch oft über die komplexesten und kurvenreichsten Wege.

Die meisten Labyrinthe, so komplex ihr Design auch erscheinen mag, wurden im Wesentlichen aus einer durchgehenden Wand mit vielen Verbindungen und Verzweigungen gebildet. Wenn die Mauer, die das Ziel eines Labyrinths umgibt, mit dem Rand des Labyrinths am Eingang verbunden ist, kann das Labyrinth immer gelöst werden, indem man eine Hand an der Wand hält, auch wenn es viele Umwege gibt. Diese „einfachen“ Labyrinthe werden korrekterweise als „einfach verbunden . bezeichnet " oder "perfektes Labyrinth " oder mit anderen Worten, die keine Schleifen enthalten .

Zurück zu unserem Projekt, es wird in zwei Teile geteilt (oder "Durchgänge ." "):

  • (Erster Pass) :Der Roboter findet seinen Weg aus einem "unbekannten perfekten Labyrinth ". Egal wo Sie es im Labyrinth platzieren, es findet eine "Lösung ".
  • (Zweiter Pass) :Sobald der Roboter eine mögliche Labyrinthlösung gefunden hat, sollte er seine Lösung optimieren, indem er den "kürzesten Weg vom Anfang bis zum Ende . findet ".

Das Video unten zeigt ein Beispiel dafür, wie Rex seinen Weg nach draußen findet. Wenn der Roboter das erste Mal das Labyrinth erkundet, wird er natürlich viel Zeit mit "Denken" verschwenden " darüber, was an einer Kreuzung zu tun ist. Beim Testen der zahlreichen Möglichkeiten wird es mehrere falsche Pfade und Sackgassen geben, die ihn zwingen, längere Wege zu laufen und unnötige "U-Turns . auszuführen ". Während dieses "1. Durchgangs" , der Roboter sammelt Erfahrungen, "Notizen machen " über die verschiedenen Kreuzungen und die Beseitigung der schlechten Verzweigungen. In seinem "2nd Pass ", geht der Roboter ohne Fehler oder Zweifel direkt und schnell zum Ende. In diesem Tutorial werden wir detailliert untersuchen, wie es geht:

Schritt 1:Stückliste

Die Materialliste ist im Grunde die gleiche wie beim Linienfolger-Roboter, außer dass ich 2 zusätzliche Sensoren für eine bessere Genauigkeit bei der Erkennung der Kreuzungen LINKS und RECHTS eingebaut habe:

Der endgültige Roboter ist immer noch sehr günstig (ca. 85,00 $):

Körper (Sie können es an Ihre Bedürfnisse oder verfügbare Materialien anpassen):

  • 2 X Holzquadrate (80X80mm)
  • 3 X Binder-Clips
  • 2 X Holzräder (Durchmesser:50 mm)
  • 1 X Kugelrolle
  • 9 X Gummibänder
  • 3M-Befehlsrahmenleiste
  • Kunststoffgelenke für Sensorfix
  • BreadBoard und Verkabelung
  • 2 X Sätze von 4X Ni-Metallhydrid-Batterien (5 V pro Satz)
  • 2 X SM-S4303R Kontinuierlicher 360-Grad-Kunststoffservo
  • Arduino Nano
  • HC-06 Bluetooth-Modul
  • 5 X-Liniensensoren (TCRT5000 4CH Infrarot-Linien-Track-Follower-Sensormodul + 1 unabhängiger Track-Sensor)
  • 2 X ZX03 (basierend auf TCRT5000) Reflektierende Infrarotsensoren (Analogausgang)
  • 1 LED
  • 1 Schaltfläche

Hinweis :Ich habe den obigen Punkt 7 mit analogem Ausgang verwendet, da ich keine Sensoren mit digitalem Ausgang wie die unter Punkt 6 zur Verfügung hatte. Ideal ist es, wenn alle Sensoren gleich sind, wenn möglich. Außerdem habe ich das Projekt getestet, wobei nur die ursprünglichen 5 Sensoren beibehalten wurden. Es wird funktionieren, erfordert jedoch sensiblere Anpassungen beim Erkennen von Kreuzungen.

Schritt 2:Veränderungen im Körper

Entfernen Sie den ursprünglichen Satz von 5 Zeilenfolgesensoren und reparieren Sie das neue "Far LEFT" und "Ganz RECHTS " Reflexionssensoren an jedem Ende der Kunststoffhalterung. Es wird empfohlen, die 7 Sensoren so gut wie möglich auszukleiden.

Schritt 3:Installieren und Testen der neuen Sensoren

Das neue Array von jetzt 7 Sensoren , ist so montiert, dass die 5 Originalen ausschließlich für die PID-Regelung (und die Erkennung der "vollen Linie", später erklärt) verwendet werden und die 2 neuen, links ausschließlich für die LINKE und RECHTE Kreuzungserkennung verwendet werden.

Erinnern wir uns als kurzer Rückblick an die Funktionsweise der 5 originalen "digitalen" Sensoren:

Wenn ein Sensor in Bezug auf die schwarze Linie zentriert ist, erzeugt nur dieser spezielle Sensor ein HIGH. Auf der anderen Seite sollte der Abstand zwischen den Sensoren so berechnet werden, dass 2 Sensoren gleichzeitig die volle Breite der schwarzen Linie abdecken können und auch ein HIGH-Signal an beiden Sensoren erzeugen.

So funktionieren die 2 neuen "analogen" Sensoren:

Wenn einer der Sensoren in Bezug auf die schwarze Linie zentriert ist, ist die Ausgabe ein analoger Wert, der normalerweise eine Ausgabe am Arduino ADC unter "100" erzeugt (denken Sie daran, dass der ADC eine Ausgabe von 0 bis 1023) erzeugt. Bei helleren Oberflächen ist der Ausgabewert höher (ich habe zum Beispiel 500 bis 600 auf weißem Papier getestet). Dieser Wert muss an verschiedenen Licht- und Oberflächenmaterialien getestet werden, um die richtige THRESHOLD-Konstante für Ihren Fall zu definieren (siehe Bild hier).

Wenn man sich den Arduino-Code ansieht, wird jeder der Sensoren mit einem bestimmten Namen definiert (beachten Sie, dass der ursprüngliche Line Follow-Sensor weiter links mit einem Label "0 ." zugewiesen werden muss "):

const int lineFollowSensor0 =12; // Mit Digital inputconst int lineFollowSensor1 =18; // Verwenden des analogen Pins A4 als digitaler Eingangconst int lineFollowSensor2 =17; // Verwenden des analogen Pins A3 als digitaler Eingangconst int lineFollowSensor3 =16; // Verwenden des analogen Pins A2 als digitaler Eingangconst int lineFollowSensor4 =19; // Verwenden des analogen Pins A5 als digitaler Eingangconst Int farRightSensorPin =0; // Analoger Pin A0const int farLeftSensorPin =1; //Analoger Pin A1 

Zur Erinnerung:Die möglichen 5 Original-Sensor-Array-Ausgaben beim Folgen einer Zeile sind:

1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 0 1 0 0 0 0 

Mit der Hinzufügung der 2 neuen sind ihre möglichen Ausgaben:

  • Sensor ganz LINKS:Analogausgang größer oder kleiner als ein THRESHOLD
  • Sensor ganz rechts:Analogausgang größer oder niedriger als ein SCHWELLE

Um die Werte jedes Sensors zu speichern, wird eine Array-Variable für die ursprünglichen 5 digitalen Sensoren erstellt:

int LFSensor[5]={0, 0, 0, 0, 0}; 

Und zwei ganzzahlige Variablen für die 2 neuen analogen Sensoren:

int farRightSensor =0;int farLeftSensor =0; 

Jede Position des Arrays und der Variablen wird ständig mit der Ausgabe jedes einzelnen der Sensoren aktualisiert:

LFSensor[0] =digitalRead(lineFollowSensor0);LFSensor[1] =digitalRead(lineFollowSensor1);LFSensor[2] =digitalRead(lineFollowSensor2);LFSensor[3] =digitalRead(lineFollowSensor3);LFSensor[4] =digitalRead(lineFollowSensor4);farRightSensor =analogRead(farRightSensorPin);farLeftSensor =analogRead(farLeftSensorPin); 

Mit 5 Sensoren, wie im Projekt Follower Line Robot gesehen, kann eine "Fehlervariable" generiert werden, die hilft, die Position des Roboters über der Linie zu kontrollieren. Außerdem wird eine Variable namens "mode" zur Definition verwendet, wenn der Roboter einer Linie folgt , über eine durchgehende Linie , eine Kreuzung oder keine Linie überhaupt.

Diese Variable "Modus " wird auch mit "Ganz LINKS/RECHTS . verwendet " Sensoren. Betrachten wir zur Darstellung die Sensoren ganz links und rechts mit 3 möglichen Zuständen:

  • H (höher als THRESHOLD),
  • L (kleiner als THRESHOLD) und
  • X (irrelevant).

Für die digitalen Ausgänge werden die üblichen 0, 1 und wir führen auch das X ein:

  • H 0 X X X X L ==> Modus =RIGHT_TURN; Fehler =0; (siehe Beispiel im Bild oben)
  • L X X X X 0 H ==> Modus =LEFT_TURN; Fehler =0;
  • X 0 0 0 0 0 X ==> Modus =NO_LINE; Fehler =0;
  • H 0 0 0 0 1 H ==> Modus =FOLLOWING_LINE; Fehler =4;
  • H 0 0 0 1 1 H ==> Modus =FOLLOWING_LINE; Fehler =3;
  • H 0 0 0 1 0 H ==> Modus =FOLLOWING_LINE; Fehler =2;
  • H 0 0 1 1 0 H ==> Modus =FOLLOWING_LINE; Fehler =1;
  • H 0 0 1 0 0 H ==> Modus =FOLLOWING_LINE; Fehler =0;
  • H 0 1 1 0 0 H ==> Modus =FOLLOWING_LINE; Fehler =-1;
  • H 0 1 0 0 0 H ==> Modus =FOLLOWING_LINE; Fehler =-2
  • H 1 1 0 0 0 H ==> Modus =FOLLOWING_LINE; Fehler =-3;
  • H 1 0 0 0 0 H ==> Modus =FOLLOWING_LINE; Fehler =-4;
  • X 1 1 1 1 1 X ==> Modus =CONT_LINE; Fehler =0;

Implementieren Sie die obige Logik in die Funktion:

readLFSsensors() ungültig machen 

gibt die Variablen "mode . zurück " und "Fehler ", die bei der Programmlogik verwendet wird. Es ist wichtig, die Logik der Sensoren zu testen, bevor Sie mit dem Projekt fortfahren. Die Balgfunktion ist im Code enthalten und kann zu Testzwecken verwendet werden:

void testSensorLogic (void) { Serial.print (farLeftSensor); Serial.print (" <==LINKS RECHTS==> "); Seriendruck (farRightSensor); Serial.print (" Modus:"); Seriendruck (Modus); Serial.print ("Fehler:"); Serial.println (Fehler);} 

Schritt 4:Das Labyrinth lösen - Die linke Handregel

Wie in der Einführung besprochen, wurden die meisten Labyrinthe, wie komplex ihr Design auch erscheinen mag, im Wesentlichen aus einer durchgehenden Wand mit vielen Verbindungen und Verzweigungen gebildet. Wenn die Mauer, die das Ziel eines Labyrinths umgibt, mit dem Rand des Labyrinths am Eingang verbunden ist, kann das Labyrinth immer gelöst werden, indem man eine Hand an der Wand hält, auch wenn es viele Umwege gibt. Diese „einfachen“ Labyrinthe werden korrekterweise als „Einfach verbunden . bezeichnet ."

Wenn wir bei Wikipedia suchen, erfahren wir Folgendes:

Kurz gesagt, die Linke-Hand-Regel kann wie folgt beschrieben werden:

Halten Sie an jeder Kreuzung und im gesamten Labyrinth Ihre linke Hand die Wand zu Ihrer Linken.

  • Legen Sie Ihre linke Hand an die Wand.
  • Beginne, vorwärts zu gehen
  • Irgendwann erreichst du das Ende des Labyrinths. Du wirst wahrscheinlich nicht den kürzesten und direktesten Weg gehen, aber du wirst ihn erreichen.

Der Schlüssel hier besteht also darin, die Kreuzungen zu identifizieren , den Kurs auf der Grundlage der oben genannten Regeln zu definieren. Speziell in unserer Art von 2D-Labyrinth finden wir 8 verschiedene Arten von Kreuzungen (siehe das erste Bild oben):

Wenn wir uns das Bild ansehen, können wir erkennen, dass die möglichen Aktionen an Kreuzungen sind:

1. Bei einem "Kreuz ":

  • Nach links gehen oder
  • Nach rechts gehen, oder
  • Gerade geradeaus

2. Bei einem "T ":

  • Nach links gehen oder
  • Nach rechts gehen

3. Bei einem "Nur rechts ":

  • Nach rechts gehen

4. Bei einem "Nur links ":

  • Nach links gehen

5. Bei "Gerade oder Links ":

  • Nach links gehen oder
  • Gerade geradeaus

6. Bei "Gerade oder Rechts ":

  • Nach rechts gehen, oder
  • Gerade geradeaus

7. In einer "Sackgasse ":

  • Zurück ("U-Wende")

8. Am "Ende des Labyrinths ":

  • Stopp

Bei Anwendung der „Linke-Hand-Regel“ werden die Aktionen jedoch auf jeweils eine Option reduziert:

  • An einem "Kreuz":Gehen Sie nach links
  • An einem "T":Gehe nach links
  • Bei "Nur rechts":Gehen Sie nach rechts
  • Bei "Nur links":Gehe nach links
  • An einer "Geraden oder Links":Gehen Sie nach links
  • An einer "Geraden oder Rechts":Geradeaus
  • In einer "Sackgasse":Geh zurück ("U-Turn")
  • Am "Ende des Labyrinths":Stopp

Wir sind fast da! "Sei ruhig!"

Wenn der Roboter eine "Sackgasse" oder das "Ende eines Labyrinths" erreicht, ist es leicht, diese zu identifizieren, da keine mehrdeutigen Situationen existieren (wir haben diese Aktionen bereits beim Line Follower-Roboter implementiert, erinnern Sie sich?). Das Problem ist, wenn der Roboter beispielsweise eine "LINIE" findet, denn eine Linie kann ein "Kreuz" (1) oder ein "T" (2) sein. Auch wenn es eine "LINKE oder RECHTE KURSE" erreicht, können dies eine einfache Abbiegung (Optionen 3 oder 4) oder Optionen sein, geradeaus zu gehen (5 oder 6). Um genau herauszufinden, auf welcher Art von Kreuzung sich der Roboter befindet, muss ein zusätzlicher Schritt unternommen werden:Der Roboter muss einen "Extra-Inch" fahren und sehen, was als nächstes kommt (siehe das zweite Bild oben als Beispiel).

In Bezug auf den Fluss können die möglichen Aktionen nun wie folgt beschrieben werden:

1. An einer "TASTEN":

  • Zurück ("U-Wende")

2. An einer "LINIE":Lauf einen zusätzlichen Zoll

  • Wenn es eine Linie gibt:Es ist ein "Kreuz" ==> Gehe nach LINKS
  • Wenn es keine Linie gibt:es ist ein "T" ==> Gehe nach LINKS
  • Wenn es eine andere Zeile gibt:es ist das "Ende des Labyrinths" ==> STOP

3. An einer "RECHTEN ABbiegung":Laufen Sie einen zusätzlichen Zoll

  • wenn es eine Linie gibt:Es ist eine Gerade oder Rechts ==> Gehe GERADE
  • Wenn es keine Linie gibt:es ist nur rechts ==> Gehe zu RECHTS

4. Bei einer "LINKEN KURVE":Laufen Sie einen zusätzlichen Zoll

  • wenn es eine Linie gibt:Es ist eine Gerade oder LINKS ==> Gehe nach LINKS
  • Wenn keine Linie vorhanden ist:Nur LINKS ==> Gehe zu LINKS

Beachten Sie, dass Sie im Falle einer "LINKS Abbiegung" den Test überspringen können, da Sie sowieso LINKS nehmen. Ich habe die Erklärung nur aus Gründen der Klarheit allgemeiner gelassen. Beim echten Code überspringe ich diesen Test. Das obige 3. Bild zeigt ein sehr einfaches Labyrinth auf meinem Laborboden, das zu Testzwecken verwendet wurde.

Schritt 5:Implementieren des Algorithmus "Linke Hand an der Wand" im Arduino-Code

Sobald wir die readLFSsensors() haben Funktion geändert, um die zusätzlichen 2 Sensoren aufzunehmen, können wir die Schleifenfunktion neu schreiben, um den Algorithmus wie im letzten Schritt beschrieben auszuführen:

void loop () { readLFSsensors (); Schalter (Modus) { Fall NO_LINE:motorStop(); goAndTurn (LINKS, 180); brechen; case CONT_LINE:runExtraInch(); readLFSsensoren(); if (mode ==CONT_LINE) mazeEnd(); sonst goAndTurn (LINKS, 90); // oder es ist ein "T" oder "Kreuz"). In beiden Fällen geht nach LINKS Pause; case RIGHT_TURN:runExtraInch(); readLFSsensoren(); if (mode ==NO_LINE) goAndTurn (RIGHT, 90); brechen; Fall LEFT_TURN:goAndTurn (LEFT, 90); brechen; case FOLLOWING_LINE:followingLine(); brechen; }} 

Hier erscheinen einige neue Funktionen:

  • followingLine() ist das gleiche wie beim Follow Line Robot, wo, wenn er nur einer Linie folgt, er calculatePID() . muss ; und steuern die Motoren in Abhängigkeit von PID-Werten:motorPIDcontrol();
  • runExtraInch(): schiebt den Roboter nur ein wenig nach vorne. Wie lange der Roboter laufen wird, hängt von der Zeit ab, die Sie in der Verzögerungsfunktion verwenden, bevor Sie dem Motor den Befehl zum Stoppen geben.
void runExtraInch(void){ motorPIDcontrol(); Verzögerung (zusätzliche Zoll); motorStop();} 
  • goAndTurn (Richtung, Winkel): Diese spezielle Funktion ist wichtig, da Sie den Roboter nicht wenden können, sobald Sie erkennen, welche Art von Kreuzung Sie sind. Denken Sie daran, dass wir einen Differentialroboter projiziert haben, der sich beim Kurvenfahren "um seine Achse dreht". Sobald sich die Sensorreihe vor seiner Achse befindet, müssen Sie den Roboter vorwärts fahren, um sie auszurichten. Die Zeitkonstante adjGoAndTurn muss je nach Abstand zwischen Axt und Sensorlinie angepasst werden ("d "), Geschwindigkeit und Größe der Räder (siehe obiges Bild zur Veranschaulichung).
void goAndTurn (int Richtung, int Grad) { motorPIDcontrol (); Verzögerung (adjGoAndTurn); motorTurn(Richtung, Grad);} 

An dieser Stelle "löst der Roboter ein Labyrinth"! Sie beenden einfach den "Ersten Pass". Egal, wo Sie in einem Labyrinth beginnen, Sie werden immer das Ende erreichen.

Unten, ein Test dieser Phase des Projekts:

Schritt 6:Pfad speichern

Betrachten wir das Beispiel wie auf dem obigen Foto gezeigt. Am gewählten Startpunkt findet der Roboter 15 Kreuzungen, bevor er das Ende des Labyrinths erreicht:

  • Links (L)
  • Zurück (B)
  • Links (L)
  • Links (L)
  • Links (L)
  • Zurück (B)
  • Gerade (S)
  • Zurück (B)
  • Links (L)
  • Links (L)
  • Zurück (B)
  • Gerade (S)
  • Links (L)
  • Links (L)
  • Ende

Was an jeder dieser Kreuzungen getan werden muss, ist, jede Aktion, die genau in derselben Reihenfolge ausgeführt wird, in der sie stattfindet, zu speichern. Erstellen wir dazu eine neue Variable (Array), die den Weg speichert, den der Roboter genommen hat:

char path[100] =" "; 

Wir müssen auch 2 Indexvariablen erstellen, die zusammen mit dem Array verwendet werden:

unsigned char pathLength =0; // die Länge des pathint pathIndex =0; // wird verwendet, um ein bestimmtes Array-Element zu erreichen. 

Wenn wir also das im Bild gezeigte Beispiel ausführen, enden wir mit:

path =[LBLLLBSBLLBSLL]und pathLengh =14 

Schritt 7:Vereinfachung (Optimierung) des Pfads

Kehren wir zu unserem Beispiel zurück. Als wir die erste Kreuzungsgruppe betrachteten, stellten wir fest, dass die erste linke Abzweigung tatsächlich eine "Sackgasse" ist und wenn der Roboter statt einer "Links-Zurück-Links" nur an dieser ersten Kreuzung geradeaus passierte, viel Energie und Zeit würde gespart! Mit anderen Worten, eine Sequenz "LBL" wäre tatsächlich dieselbe wie "S". Genau so kann der gesamte Pfad optimiert werden. Wenn Sie alle Möglichkeiten analysieren, bei denen ein "U-Turn" verwendet wird, kann die Menge von 3 Kreuzungen, an denen dieser "U-Turn" ("B") auftritt ("xBx"), auf nur eine reduziert werden.

Das obige ist nur ein Beispiel, unten finden Sie die vollständige Liste der Möglichkeiten (versuchen Sie es):

  • LBR =B
  • LBS =R
  • RBL =B
  • SBL =R
  • SBS =B
  • LBL =S

Wenn wir den vollständigen Weg oder unser Beispiel nehmen, können wir ihn reduzieren:

path =[LBLLLBSBLLBSLL] ==> LBL =Spath =[SLLBSBLLBSLL] ==> LBS =Rpath =[SLRBLLBSLL] ==> RBL =Bpath =[SLBLBSLL] ==> LBL =Spath =[SSBSLL ] ==> SBS =Bpath =[SBLL] ==> SBL =Rpath =[RL] 

Tolle! Wenn man sich das Beispiel ansieht, ist es sehr klar, dass der Roboter, wenn er an der ersten Kreuzung RECHTS und danach LINKS nimmt, das Ende des Labyrinths auf dem kürzesten Weg erreicht!

Der gesamte Code des First Path of Maze Solver wird in der Funktion mazeSolve() konsolidiert . Diese Funktion ist tatsächlich die zuvor verwendete loop()-Funktion, die jedoch all diese Schritte des Speicherns und der Pfadoptimierung umfasst. Wenn der erste Pfad endete, hat das path[]-Array den optimierten Pfad. Eine neue Variable wird eingeführt:

unsigned int status =0; // lösen =0; Labyrinth-Ende erreichen =1 

Unterhalb der First Path-Funktion:

void mazeSolve(void){ while (!status) { readLFSsensors(); Schalter (Modus) { Fall NO_LINE:motorStop(); goAndTurn (LINKS, 180); recIntersection('B'); brechen; case CONT_LINE:runExtraInch(); readLFSsensoren(); if (mode !=CONT_LINE) {goAndTurn (LINKS, 90); recIntersection('L');} // oder es ist ein "T" oder "Cross"). In beiden Fällen geht nach LINKS sonst mazeEnd(); brechen; case RIGHT_TURN:runExtraInch(); readLFSsensoren(); if (mode ==NO_LINE) {goAndTurn (RIGHT, 90); recIntersection('R');} else recIntersection('S'); brechen; Fall LEFT_TURN:goAndTurn (LEFT, 90); recIntersection('L'); brechen; case FOLLOWING_LINE:followingLine(); brechen; } }}  

Hier wurde eine neue Funktion eingeführt:recIntersection (Richtung). Diese Funktion wird zum Speichern der Schnittmenge verwendet und auch zum Aufrufen einer anderen Funktion simplifyPath() , das wird die Gruppe von 3 Kreuzungen reduzieren, die eine "U-Turn" beinhalten, wie wir zuvor gesehen haben.

void recIntersection(char direction){ path[pathLength] =direction; // Speichere den Schnittpunkt in der Pfadvariablen. Pfadlänge ++; vereinfachenPfad(); // Vereinfachen Sie den gelernten Pfad.} 

Das CREDIT für den simplifyPath( ) Funktion geht an Patrick McCabe für den Pfad Solving Code (für Details besuchen Sie bitte https://patrickmccabemakes.com)! Die Strategie der Pfadvereinfachung besteht darin, dass wir jedes Mal, wenn wir auf eine Sequenz xBx stoßen, diese vereinfachen können, indem wir die Sackgasse ausschneiden. Zum Beispiel LBL ==> S wie wir am Beispiel gesehen haben.

void SimplifyPath(){ // Vereinfache den Pfad nur, wenn die vorletzte Abbiegung ein 'B' war if(pathLength <3 || path[pathLength-2] !='B') return; int totalWinkel =0; int ich; for(i=1;i<=3;i++) { switch(path[pathLength-i]) { case 'R':totalAngle +=90; brechen; Fall 'L':totalAngle +=270; brechen; Fall 'B':totalAngle +=180; brechen; } } // Holen Sie sich den Winkel als Zahl zwischen 0 und 360 Grad. totalAngle =totalAngle % 360; // Ersetze alle diese Züge durch einen einzigen. switch(totalAngle) { case 0:path[pathLength - 3] ='S'; brechen; Fall 90:Pfad[PfadLänge - 3] ='R'; brechen; Fall 180:Pfad[PfadLänge - 3] ='B'; brechen; Fall 270:path[pathLength – 3] ='L'; brechen; } // Der Pfad ist jetzt zwei Schritte kürzer. Pfadlänge -=2; }  

Schritt 8:Zweiter Durchgang:Lösen Sie das Labyrinth so schnell wie möglich!

Das Hauptprogramm:loop() ist einfach so:

void loop() { ledBlink(1); readLFSsensoren(); mazeSolve(); // Erster Durchgang zum Lösen des Labyrinths ledBlink(2); while (digitalRead(buttonPin) {} pathIndex =0; status =0; mazeOptimization(); // Zweiter Durchgang:Lass das Labyrinth so schnell wie möglich laufen ledBlink(3); while (digitalRead(buttonPin) {} mode =STOPPED; status =0; // 1. Durchlauf pathIndex =0; pathLength =0;} 

Wenn der erste Durchgang endet, müssen wir den Roboter nur mit dem optimierten Pfadarray füttern. Es wird gestartet und wenn eine Kreuzung gefunden wird, definiert es jetzt basierend auf dem, was unter path[] . gespeichert ist, was zu tun ist .

void mazeOptimization (void){ while (!status) { readLFSsensors(); Schalter (Modus) { case FOLLOWING_LINE:followingLine(); brechen; case CONT_LINE:if (pathIndex>=pathLength) mazeEnd (); else {mazeTurn (Pfad[PfadIndex]); pathIndex++;} break; case LEFT_TURN:if (pathIndex>=pathLength) mazeEnd (); else {mazeTurn (Pfad[PfadIndex]); pathIndex++;} break; case RIGHT_TURN:if (pathIndex>=pathLength) mazeEnd (); else {mazeTurn (Pfad[PfadIndex]); pathIndex++;} break; } } } 

Um zu befehlen, was zu tun ist, eine neue Funktion mazeTurn(path[]) erstellt wurde. Die Funktion mazeTurn (Pfad[]) wird sein:

void mazeTurn (char dir) { switch (dir) { case 'L':// Links abbiegen goAndTurn (LEFT, 90); brechen; case 'R':// Rechts abbiegen goAndTurn (RIGHT, 90); brechen; Fall 'B':// Zurück goAndTurn (RIGHT, 800); brechen; case 'S':// Gehe geradeaus runExtraInch(); brechen; }} 

Der zweite Durchgang ist geschafft! Das folgende Video zeigt das komplette hier gearbeitete Beispiel, erster und zweiter Durchgang. Unterhalb des Arduino-Codes, der in diesem Tutorial verwendet wird:

FV6XNJWINJ45XWM.ino F2FXS8MINJ45XX6.h FX5MHFMINJ45XX7.ino FT2S1WXINJ45XXA.ino F9IC3HQINJ45XXB.ino FU2HRXJINJ45XXV.ino

Schritt 9:Verwenden des Android-Geräts zum Abstimmen

Hier kann auch die für das Follow-Line-Projekt entwickelte Android-App verwendet werden (bei Bedarf finden Sie die Android-App und deren Code unter:Line Follower Robot - PID Control - Android Setup. Der im letzten Schritt vorgestellte Arduino-Code beinhaltet bereits die Kommunikation mit Wenn Sie die Android-App nicht verwenden möchten, kein Problem, denn der Code ist "transparent ".

Ich habe das Android während des Projekts viel verwendet, um Testdaten vom Roboter an das Gerät mit der "Message Received . zu senden " Feld. Mehrere Variablen müssen gut definiert sein, um sicherzustellen, dass der Roboter den richtigen Winkel einnimmt. Die wichtigsten sind unten (die fett markierten habe ich mehrmals geändert):

const int adj =0; float adjTurn =8;int adjGoAndTurn =800;THRESHOLD =150const int power =250; const int iniMotorPower =250; int extraInch =200;  

Schritt 10:Fazit

Dies ist der zweite und letzte Teil eines komplexen Projekts, das das Potenzial eines Linienfolger-Roboters untersucht, bei dem Künstliche Intelligenz (KI) einfache Konzepte wurden verwendet, um ein Labyrinth zu lösen.

Ich bin keine KI Experte und basierend auf einigen Informationen, die ich aus dem Internet bekam, verstand ich, dass das, was unser kleiner Rex, der Roboter, tat, um das Labyrinth zu lösen, als eine Anwendung von KI angesehen werden konnte. Werfen wir einen Blick auf die beiden folgenden Quellen:

Aus Wikipedia:

Oder aus diesem Universitätspapier:"Maze Solving Robot Using Freeduino and LSRB Algorithm International Journal of Modern Engineering Research (IJMER)"

Die aktualisierten Dateien für dieses Projekt finden Sie unter GITHUB. Ich hoffe, ich kann anderen helfen, mehr über Elektronik, Roboter, Arduino usw. zu erfahren. Weitere Tutorials finden Sie in meinem Blog:MJRoBot.org

Saludos aus dem Süden der Welt!

Danke

Marcelo

Code

  • Code-Snippet Nr. 1
  • Code-Snippet Nr. 4
  • Code-Snippet Nr. 5
  • Code-Snippet #6
  • Code-Snippet #7
  • Code-Snippet Nr. 8
  • Code-Snippet #12
  • Code-Snippet #13
  • Code-Snippet #14
  • Code-Snippet #15
  • Code-Snippet #16
  • Code-Snippet #17
Code-Snippet Nr. 1Nur-Text
const int lineFollowSensor0 =12; // Mit Digital inputconst int lineFollowSensor1 =18; // Verwenden des analogen Pins A4 als digitaler Eingangconst int lineFollowSensor2 =17; // Verwenden des analogen Pins A3 als digitaler Eingangconst int lineFollowSensor3 =16; // Verwenden des analogen Pins A2 als digitaler Eingangconst int lineFollowSensor4 =19; // Verwenden des analogen Pins A5 als digitaler Eingangconst Int farRightSensorPin =0; // Analoger Pin A0const int farLeftSensorPin =1; //Analoger Pin A1
Code-Snippet #4Nur-Text
LFSensor[0] =digitalRead(lineFollowSensor0);LFSensor[1] =digitalRead(lineFollowSensor1);LFSensor[2] =digitalRead(lineFollowSensor2);LFSensor[3] =digitalRead(lineFollowSensor3);LFSensor[4] =digitalRead( lineFollowSensor4);farRightSensor =analogRead(farRightSensorPin);farLeftSensor =analogRead(farLeftSensorPin);
Code-Snippet #5Klartext
void testSensorLogic(void) { Serial.print (farLeftSensor); Serial.print (" <==LEFT RIGH==> "); Serial.print (farRightSensor); Serial.print (" mode:"); Serial.print (mode); Serial.print (" error:"); Serial.println (error);}
Code snippet #6Plain text
void loop(){ readLFSsensors(); switch (mode) { case NO_LINE:motorStop(); goAndTurn (LEFT, 180); brechen; case CONT_LINE:runExtraInch(); readLFSsensors(); if (mode ==CONT_LINE) mazeEnd(); else goAndTurn (LEFT, 90); // or it is a "T" or "Cross"). In both cases, goes to LEFT break; case RIGHT_TURN:runExtraInch(); readLFSsensors(); if (mode ==NO_LINE) goAndTurn (RIGHT, 90); brechen; case LEFT_TURN:goAndTurn (LEFT, 90); brechen; case FOLLOWING_LINE:followingLine(); brechen; }}
Code snippet #7Plain text
void runExtraInch(void){ motorPIDcontrol(); delay(extraInch); motorStop();}
Code snippet #8Plain text
void goAndTurn(int direction, int degrees){ motorPIDcontrol(); delay(adjGoAndTurn); motorTurn(direction, degrees);}
Code snippet #12Plain text
void mazeSolve(void){ while (!status) { readLFSsensors(); switch (mode) { case NO_LINE:motorStop(); goAndTurn (LEFT, 180); recIntersection('B'); brechen; case CONT_LINE:runExtraInch(); readLFSsensors(); if (mode !=CONT_LINE) {goAndTurn (LEFT, 90); recIntersection('L');} // or it is a "T" or "Cross"). In both cases, goes to LEFT else mazeEnd(); brechen; case RIGHT_TURN:runExtraInch(); readLFSsensors(); if (mode ==NO_LINE) {goAndTurn (RIGHT, 90); recIntersection('R');} else recIntersection('S'); brechen; case LEFT_TURN:goAndTurn (LEFT, 90); recIntersection('L'); brechen; case FOLLOWING_LINE:followingLine(); brechen; } }}
Code snippet #13Plain text
void recIntersection(char direction){ path[pathLength] =direction; // Store the intersection in the path variable. pathLength ++; simplifyPath(); // Simplify the learned path.}
Code snippet #14Plain text
void simplifyPath(){ // only simplify the path if the second-to-last turn was a 'B' if(pathLength <3 || path[pathLength-2] !='B') return; int totalAngle =0; int i; for(i=1;i<=3;i++) { switch(path[pathLength-i]) { case 'R':totalAngle +=90; brechen; case 'L':totalAngle +=270; brechen; case 'B':totalAngle +=180; brechen; } } // Get the angle as a number between 0 and 360 degrees. totalAngle =totalAngle % 360; // Replace all of those turns with a single one. switch(totalAngle) { case 0:path[pathLength - 3] ='S'; brechen; case 90:path[pathLength - 3] ='R'; brechen; case 180:path[pathLength - 3] ='B'; brechen; case 270:path[pathLength - 3] ='L'; brechen; } // The path is now two steps shorter. pathLength -=2; } 
Code snippet #15Plain text
void loop() { ledBlink(1); readLFSsensors(); mazeSolve(); // First pass to solve the maze ledBlink(2); while (digitalRead(buttonPin) { } pathIndex =0; status =0; mazeOptimization(); // Second Pass:run the maze as fast as possible ledBlink(3); while (digitalRead(buttonPin) { } mode =STOPPED; status =0; // 1st pass pathIndex =0; pathLength =0;}
Code snippet #16Plain text
void mazeOptimization (void){ while (!status) { readLFSsensors(); switch (mode) { case FOLLOWING_LINE:followingLine(); brechen; case CONT_LINE:if (pathIndex>=pathLength) mazeEnd (); else {mazeTurn (path[pathIndex]); pathIndex++;} break; case LEFT_TURN:if (pathIndex>=pathLength) mazeEnd (); else {mazeTurn (path[pathIndex]); pathIndex++;} break; case RIGHT_TURN:if (pathIndex>=pathLength) mazeEnd (); else {mazeTurn (path[pathIndex]); pathIndex++;} break; } } }
Code snippet #17Plain text
void mazeTurn (char dir) { switch(dir) { case 'L':// Turn Left goAndTurn (LEFT, 90); brechen; case 'R':// Turn Right goAndTurn (RIGHT, 90); brechen; case 'B':// Turn Back goAndTurn (RIGHT, 800); brechen; case 'S':// Go Straight runExtraInch(); brechen; }}
Github
https://github.com/Mjrovai/MJRoBot-Maze-Solverhttps://github.com/Mjrovai/MJRoBot-Maze-Solver

Schaltpläne

z7IdLkxL1J66qOtphxqC.fzz

Herstellungsprozess

  1. Roboter mit Raspberry Pi und Bridge Shield
  2. Gestengesteuerter Roboter mit Raspberry Pi
  3. WLAN-gesteuerter Roboter mit Raspberry Pi
  4. SONBI ROBOTER MENSCHLICHE ERKENNUNG MIT KINECT UND HIMBEE PI
  5. Bosch fügt Industrie 4.0 künstliche Intelligenz hinzu
  6. Ist künstliche Intelligenz Fiktion oder Modeerscheinung?
  7. Künstliche Intelligenz erhält enormen Kubernetes-Boost
  8. Künstliche Intelligenz hilft Robotern, Objekte durch Berührung zu erkennen
  9. Mit künstlicher Intelligenz die Entwaldung verfolgen
  10. Roboter mit künstlicher Intelligenz