RoboSoccer

Aus HSHL Mechatronik
Zur Navigation springen Zur Suche springen

Autoren:

  • Anika Leinhäuser
  • Andre Merkel

In dieser Anleitung wird ein Verbindungsaufbau zweier NXT-Bricks mit einem Host-Rechner via Bluetooth aufgezeigt. Dabei werden keine Kenntnisse über die Funktionsweise in Bluetooth oder der Netzwerktechnik benötigt. Allerdings erfolgt die Verbindung über MatLab, sodass Grundkenntnisse in MatLab vorausgesetzt sind.

Weiterhin wird ein Demo-Sourcecode in NXC (eine C-ähnliche Sprache) kurz beschrieben, welcher in der IDE BricxCC geschrieben wird. Dieser Sourcecode wird auf den Brick geladen und dient zum Steuern und zum empfangen von Daten (z.B. Koordinaten), welche via Bluetooth über Matlab gesendet werden.

1. Schritt: Vorbereitung

Im ersten Schritt werden alle Bereiche angesprochen, die als Basis für eine Bluetoothverbindung zwischen den Bricks und dem Host-Rechner dienen:

Als erstes sollte immer überprüft werden, ob der Host-Rechner über eine Bluetooth-Schnittstelle verfügt und ob diese tatsächlich auch eingeschaltet ist. Auch das Bluetooth vom NXT-Brick sollte eingeschaltet sein (Tipp: Der Schlafmodus des Bricks (Sleep) sollte am besten ausgeschaltet sein, damit bei Inaktivität der Brick sich nicht ausschaltet und somit seine Bluetooth-Verbindung beendet). Weiterhin ist es wichtig, den NXT-Brick nicht zu weit vom Host-Rechner zu stellen. Der Grund hierfür liegt in der Sendeleistung, welche die Reichweite bestimmt (abhängig von Interferenzen/Störquellen). Es sollte jedoch von einer Reichweite von max. 10m ausgegangen werden (Sendeleistung von 1mW). Weiterhin muss für die Ausführung die RWTH - Mindstorms NXT Toolbox von RWTH-Aachen auf dem Host-Rechner vorhanden sein. Falls dies nicht der Fall ist, so kann dies bspw. unter folgendem Link heruntergeladen werden: [1]. Hinweis: Wichtig ist, dass der Ordner, in dem sich die ebengenannte Toolbox befindet, zum MatLab-Pfad hinzugefügt ist (Add to Path).


2. Schritt: Informationssgewinnung

In diesem Abschnitt wird die Informationsgewinnung für eine Bluetoothverbidung zwischen den Bricks und dem Host-Rechner beschrieben:

Für die Verbindung zwischen den Bricks und dem Host-Rechner wird ein sogenanntes "handle" benötigt. Dieses handle braucht jedoch einige Informationen für eine Bluetoothverbindung und wird im Workspace von MatLab als Variable hinterlegt. Anhand des folgenden Beispiels wird aufgezeigt wie die benötigten Informationen aufgerufen werden.

Zunächst wird die Variable BT angelegt, in welcher die instrhwinfo hinterlegt wird:

>> BT=instrhwinfo('Bluetooth')

BT =

       RemoteNames: {4x1 cell}
         RemoteIDs: {4x1 cell}
   BluecoveVersion: 'BlueCove-2.1.1-SNAPSHOT'
    JarFileVersion: 'Version 3.4'

Wie aus dem oberen Quelltextausschnitt zu entnehmen ist, liegen in der Variable BT "RemoteNames" vor. Diese "RemoteNames" sind "Gerätenamen", welche über Bluetooth ermittelt wurden bzw. noch im Speicher liegen. Diese Gerätenamen werden an dieser Stelle benötigt. Um sie anzuzeigen, wird folgendes eingegeben:

>> BT.RemoteNames

ans =

   'EV3'
   
   'NXT17'
   'NXT'

Im oberen Codeausschnitt müssen diejenigen Namen auftauchen, welche auch auf dem Brick-Display zu sehen sind. An dieser Stelle wird der Brick mit dem Namen "NXT17" zum weiteren Vorgehen ausgewählt:

>> BT=instrhwinfo('Bluetooth', 'NXT17')

BT =

              RemoteName: 'NXT17'
                RemoteID: 'btspp://0016530EC53C'
   ObjectConstructorName: {'Bluetooth('NXT17', 1);'}
                Channels: {'1'}

>>

Aus den oberen Informationen werden der Name sowie der Kanal (Channel) für das weitere Vorgehen unabdingbar.

3. Schritt: Erstellung einer Konfigurationdatei

In diesem Schritt erfolgt eine Erläuterung, wie eine Konfigurationsdatei erstellt und geladen wird:

Für die Bluetoothverbindung ist die Konfigurationsdatei entscheidend. Allerdings muss diese noch erstellt und mit den obigen Informationen gefüllt werden. Zunächst muss die Funktion "COM_MakeBTConfigFile" aufgerufen werden. Dabei öffnet sich ein Dialog, in welchem ein Ordner für die Konfigurationsdatei ausgewählt werden muss. (Tipp: Auch hier sollte man nicht vergessen, dass der Ordner in welchem die Konfigurationsdatei abgespeichert wird, dem MatLab-Pfad hinzugefügt werden muss.) Ist die Wahl des Ordners abgeschlossen, taucht ein zweiter Dialog auf.

Dialog zum Erstellen einer Konfiguration

Der zweite Dialog, welcher hier rechts abgebildet ist, benötigt den Namen des Bricks und den Kanal. Da der ausgewählte Brick den Namen "NXT17" und den Kanal "1" besitzt, werden diese in die entsprechenden Textfelder eingetragen. (Tipp: Nach Abschluss der Eingabe sollte diese auf Korrektheit überprüft werden. Sollte dennoch ein Fehler auftreten, so kann diese Konfiguration einfach neu erstellt oder die bereits erstellte Konfiguration geändert/verbessert werden.) Weiterhin ist die Vergabe eines Namens (Filename) der Konfigurationsdatei wichtig. Der Grund hierfür liegt darin, dass pro Brick eine Konfiguration benötigt wird. Wenn also zwei Bricks benutzt werden, so müssen auch zwei Konfigurationsdateien erstellt werden (mit entsprechenden Informationen). Alle anderen Einstellungen bzw. Textfelder können i.d.R., so wie in dem rechts stehenden Bild, übernommen werden.

Wie bereits im 2. Schritt erwähnt, wird ein "handle" für die Bluetoothverbindung benötigt. Da die Konfigurationsdatei nun erstellt ist, kann nun ein "handle" erstellt werden:

handle = COM_OpenNXT('bluetooth.ini')

Wie in dem oberen Codeausschnitt zu entnehmen ist, stellt das "handle" eine Variable dar und kann somit frei gewählt werden. (Anmerkung: Gibt man den Befehl, wie oben dargestellt, ein, so kann eine Warnung in roter Schrift auftauchen. Davon sollte man sich nicht verunsichern. Entscheidend ist, dass das "handle" alle nötigen Informationen enthält. In der Regel kann die Warnung ignoriert werden.)

Beim Eingeben des oberen Befehls sollte ebenfalls darauf geachtet werden, dass der Name der erstellten Konfigurationsdatei, in diesem Fall "bluetooth.ini", korrekt ist. Findet der Einsatz bspw. zweier Bricks statt, so müssen ebenfalls zwei Konfigurationsdateien mit den entsprechenden Informationen aus dem 2. Schritt erstellt werden. Dabei ist weiterhin sicherzustellen, dass diese Konfigurationsdateien nicht den gleichen Namen besitzen. Auch die "handle"-Variable gilt immer nur für einen Brick. Setzt man zwei Bricks ein, so müssen auch zwei "handle"-Variablen angelegt werden (Im Praktikum sollten daher die Namen "handle_1" für Brick 1 und "handle_2" für Brick 2 verwendet werden).










4. Schritt: Prüfung der Verbindung

Nach erfolgreichem Abschluss des 3. Schrittes sollte jetzt eine Verbindung zwsichen dem Host-Rechner und dem Brick bestehen. Die Prüfung, ob die Verbindung tatsächlich hergestellt wurde, findet in diesem Schritt statt:

Als erstes muss die Variable "handle" im Workspace von MatLab auftauchen. Außerderm sollte diese von "class: struct" sein und ein "value: 1x1 struct" besitzen. Auf dem Display des Bricks erscheint ebenfalls bei erfolgreicher Verbindung neben dem Bluetooth-Symbol eine Raute. Sind die eben genannten Punkte vorhanden, muss nun die Verbindung überprüft werden, indem ein Signal vom Host-Rechner an den entsprechenden Brick gesendet wird. Hierzu wird bspw. folgendes in MatLab eingegeben:

NXT_PlayTone(800, 100, handle)

Dieser Befehl bewirkt, dass am Brick ein Ton abgespielt wird. (Hinweis: Um den Ton zu hören, sollte man sicherstellen, dass die Lautstärke am Brick laut genug ist.) Ertönt nun ein Ton am Brick, so ist die Verbindung zum Brick via Bluetoth gelungen.

5. Schritt: Koordinaten senden

In diesem Schritt erfolgt eine Beschreibung, wie Daten an den NXT-Brick gesendet werden können.

Wurde eine Verbindung zu einem (ggf. mehreren) Brick(s) hergestellt, so ist man in der Lage an diesen auch Daten, sogenannte "Messages", via Matlab zu senden. Dies sieht wie folgt aus:

NXT_MessageWrite('meineMessage', 0, handle);

Dadurch wird der String 'meineMessage' (Hinweis: Hier sollte man auf die Matlab-Syntax achten) zu dem Brick, welcher über das "handle" kommuniziert, gesendet. Diese Message wird dann auf ein Stack (dt. Stapel) im Brick hinterlegt. Nun kann die Message von dem Stack im eigenen Code (in nxc) abgeholt und verarbeitet werden (siehe dazu auch Demo-Sourcecode).

Die Message selbst kann dabei frei gewählt werden. So kann man bspw. 'BallX200Y200' oder auch 'BallX200' als eine einzige Message senden. Entscheidend ist dabei wie die Nachricht definiert wird, denn diese muss ebenfalls in dem nxc-Code wieder ausgewertet werden. Generell benötigt man die absoluten Werte (Koordinaten) vom Ball, eigenem Robotor usw., welche über die Tracking Skripte geliefert werden. Diese müssen dann nur noch entsprechend "verpackt" und gesendet werden. Zum Testen können auch Pseudokoordinaten gesendet werden. Weiterhin ist es wichtig zu beachten, dass die Koordinaten immer in einer Schleife gesendet werden. Dabei darf diese Schleife nicht schnellst möglich ablaufen sondern verzögert. Dies liegt daran, dass der Roboter eine gewisse Zeit braucht, bis er eine bestimmte Koordinate erreicht hat (abhängig von der Motorleistung und der Entfernung).

Demo-Sourcecode

An dieser Stelle wird aufgezeigt, wie ein Brick die gesendeten Daten (via Bluetooth) empfängt und bearbeitet.

Zunächst wird der Sourcecode betrachtet:

task main()
{
  string in;    
  
  int x = 200; 
  
  while(true)  
{
   if (ReceiveMessage (0, true, in) == NO_ERR) 
   {
      x = atoi(in);
   }
   
   if(x < 100) 
   {
      OnFwd ( OUT_A,75 );   
      OnFwd( OUT_C  , 75);  
      Wait (  4000);     
      
      Off(OUT_AC); 
   }
}
 
}

Als erstes wird die "task main()" betrachtet. Die "task main()" ist der Haupteinsprungspunkt jedes Programms welches mit nxc geschrieben wird. Das beudetet, dass ab "task main()" das Programm ausgeführt wird. Vor dem Haupteinsprungspunkt kann zwar ebenfalls Code stehen, dieser darf jedoch nur zu Deklaration, Definition und/oder Initialisierung genutzt werden. Man bezeichnet (je nach Programmiersprache/Skriptsprache) "alles was vor dem Haupteinsprungspunkt" steht als Präambel.

Nach "task main()" kommen geschweifte Klammern "{}". Diese sagen aus, wo das "eigentliche" Programm (also die Logik) beginnt und endet. Fehlen die Klammern bzw. gibt es zu viele Klammer, so weigert sich der Compiler den Code zu kompilieren. Deswegen sollte immer darauf geachtet werden, dass die Klammern auf- und zugehen.

Als nächstes sieht man die Deklaration und Initialisierung zweier Variablen. Die Variablen "string in;" und "int x = 200;" sind in diesem Kontext lediglich als Beispielvariablen zu betrachten, diese können natürlich anders heißen und auch andere Werte bekommen. Generell, wenn man Variablen anlegen möchte, schreibt man (bei nxc) zuerst den Typ der Varaible hin und dann den Namen der Variable. Beispielsweise wurde im oberen Code "string" geschrieben. Dieser Typ sagt dem "Computer", dass der Variablen Name "in" nur für Zeichenketten geeignet ist. Mit einem Semikolon wird die "Anweisung" beendet. Das ist sehr wichtig, denn dadurch weiß der Compiler das die Variable "in" von Typ "string" ist, aber noch keinen Inhalt besitzt, also leer ist. Die Variable "x" besitzt im Vergleich zur vorherigen Variable einen anderen Typen (int, also Ganzzahlenwert) und besitzt den Wert 200. In diesem Beispiel hat die Zahl keine besondere Bedeutung.

In der nächsten Zeile steht die "while"-Schleife. Diese hat in dem Demobeispiel im Kopf (in den Klammern) "true" stehen, welches dafür sorgt, dass die Schleife nie beendet wird und immer wieder durchlaufen wird. Solche Schleifen nennt man auch Endlosschleifen und sollten immer mit großer Vorsicht eingesetzt werden. Generell ist es so, dass die "task main()" von oben nach unten ablaufen würde und sich schließlich irgendwann beenden würde. Das ist ungünstig, weil man ja das Programm für eine längere Zeit in Betrieb haben möchte. An dieser Stelle greift eben diese Endlosschleife. Da diese Schleife nicht verlassen wird, kann auch das Programm nicht mehr von sich aus beendet werden.

Innerhalb der "while"-Schleife steht folgendes:

if (ReceiveMessage (0, true, in) == NO_ERR) 
   {
      x = atoi(in);  
   }

Diese "if"-Abfrage bezieht eine Message, also Daten welche von dem Hostrechner mittels Matlab und Bluetooth an den Brick gesendet wurden. Diese Daten werden als String gesendet und in diesem Beispiel in der Variable "in" gespeichert. Allerdings muss dieser String noch zum Typ "int" konvertiert werden. Dies geschieht mit der funktion "atoi". Das Ergebnis wird in der Variable "x" gespeichert. In diesem Fall könnte man annehmen, dass es sich hierbei um eine x-Koordinate handelt.

Die letzte "if"-Abfrage kontrolliert, ob der "x"-Wert kleiner 100 ist. Trifft dieser Fall ein, so werden die Motoren gestartet:

OnFwd(OUT_A,75);
OnFwd(OUT_C,75);

Der obere Ausschnitt sorgt dafür, dass die Motoren A und C Vorwärts fahren sollen mit einer Leistung von 75% (max 100% ist mögl.). Mit "Wait(4000);" wird das Programm für vier Sekunden pausiert mit der Besonderheit, dass die Motoren weiterhin fahren. Mit "Off(OUT_AC);" werden die Motoren wieder ausgeschaltet. Allerdings läuft das Programm so schnell ab, dass wenn in diesem Beispiel "x" immer kleiner 100 ist, die Motoren nie sichtbar zum stehen kommen.

Weiteres Tutorial zum Nachschlagen http://www.hsg-kl.de/faecher/inf/msr/lego/nxc/NXC-Tutorial_DE.pdf. Die Dokumentationen für nxc sind auf der folgenden url hinter legt: http://bricxcc.sourceforge.net/nbc/nxcdoc/nxcapi/index.html, http://bricxcc.sourceforge.net/nbc/nxcdoc/nxcapi/group___hi_technic_a_p_i_ga6acad43b9093e56fd45d2a76d21a6782.html, http://www.debacher.de/wiki/Sensoren#Der_Kompass-Sensor

Erweiterter Demo-Sourcecode

In dem vorangegangenen Kapitel wurde ein sehr kurzer Einblick in die nxc-Sprache gegeben. An dieser Stelle wird das oben erworbene Wissen über ncx an gewissen Stellen erweitert. Hinzu kommt ebenfalls ein Demo-Code von Matlab, um das Senden und Empfangen von Nachrichten besser zu greifen. Die Beschreibung des vorliegenden Beispiels setzt das erfolgreiche durchführen der Schritte 1 bis 5.

Zunächst wird der Matlab-Code betrachtet. Das Senden einer Message könnte wie folgt aussehen:

%Matlab Demo-Script 

while true

StrA = 'A';

Koordinate_x = Funktion_x;
Koordinaten_y = Funktion_y;

KoordinatenA = strcat(StrA, 'X' , int2str(Koordinate_x), 'Y', int2str(Koordinaten_y));

NXT_MessageWrite(PunktA, 0, handle);

end

Als erstes wird eine Bezeichnung erstellt. In dem obren Matlab-Code ist es "StrA = 'A';". Das 'A' steht stellvertretend für z.B. das Tor oder den Ball. Als nächstes wird über "Funktion_x" und "Funktion_y" die Koordinaten für das entsprechende Objekt abgeholt.

In diesem Beispiel soll die Koordinate eines Objektes als "Ganzes" gesendet werden. Hierfür werden die Werte in String umgewandelt mit "int2str()" und zu einem String mithilfe von "strcat()" zusammengefasst. Im nächsten Schritt kann mittels "NXT_MessageWrite(PunktA, 0, handle);" die Messages schon gesendet werden. Alle reine Zahlen Koordierung wie z.B. für Tor1 die Zahl z.B. 1 ist durchaus machbar. Allerdings sollte man dann bei der Dekodierung besonders aufpassen, um die einzelnen Bezeichnung nicht durcheinander zu bringen und diese ebenfalls nicht Koordinaten zu verwechseln. Die While-Schleife erlaubt als Endlosschleife die Koordinaten zyklisch zu senden.

Als nächstes wird der nxc-Code betrachtet:

//Sourcecode für die Bricksteuerung


         //TextOut( 0, LCD_LINE1, "Ausgabetext", true);  //zeigt einen String auf dem Brick-Display (ist ggf. bei der Fehlersuche nützlich)

#define Anfangsindex  0 //Makro
#define Anfangslaenge 1

//Globale Variablen
int X, Y = 0; //X-Koordinate und Y-Koordinate

/*Die Message, welche empfangen wird ist wie folgt aufgebaut:

"AXzahlenwertYzahlenwert"

Das A entspricht der Position eines Objektes (z.B. Ball), also das was man in Matlab für 
"StrA" geschrieben hat.

Das "X" gibt an, dass alle folgenden Zahlen die X-Koordinate sind bis zum "Y" (nicht vergessen: es handelt sich hier um ein String, 
welcher in Matlab von einem selber erstellt wurde!).

Das "Y" gibt an, dass alle Zahlen nach dem "Y" die Y-Koordinate sind bis zur letzten Stelle.

Allerdings liegt die empfangene Message als String vor und muss noch mit der Funktion atoi() in ein int-Typ konvertiert werden. Dabei
dienen die Zeichenketten wie "Ball" nur zur erkennung, dass es sich hierbei um einen die Koordinaten des Balles handelt. Die angehängten
Zahlen müssen noch "gefiltert" werden!*/

sub bezieheMessage()
{
   string MsgEingang;

   if (ReceiveMessage (0, true, MsgEingang) == NO_ERR)  //Hier wird die empfangene Message in den String MsgEingang hinterlegt
   {
     int Strlaenge = strlen(MsgEingang); //Gesamtlänge des Strings
     
     int i = 2; //Die "2" gibt die Position nach dem "X" in dem MsgEingang an, also die Stelle, an welcher die X-Koordinaten anfängt
     
     if("A" == SubStr(MsgEingang, Anfangsindex, Anfangslaenge)) //Hier findet die Auswertung des String statt, sofern der String mit "A"
                                                                //anfängt. Die Anfangslaenge sagt aus inweit der String betrachtet 
                                                                //werden soll, in diesem Fall nur ein Element weit (es wird also das
                                                                //erste Zeichen untersucht, ob es "A" ist oder nicht!).
     {
       if("X" == SubStr(MsgEingang, 1, Anfangslaenge)) //Hier wird das Zeichen nach "X" untersucht, ob es ein "X" ist
       {
       
         while("Y" == SubStr(MsgEingang, 2, i)) //In der Schleife wird solange inkrementiert bis bei MsgEingang "Y" erreicht wurde
         {
           i++;  
         }
         
         X = atoi(SubStr(MsgEingang, 2, i-1)); //X-Koordinate wird als Zahlenwert gespeichert
                                               //Die "2" gibt an, ab welcher Stelle der String gelesen wird und
                                               //"i-1" bis zur welcher Stelle der String gelesen wird.
                                               //Da "Y" bei der Stelle i sich befindet, so stellt "i-1" den letzt X-Zahlenwert dar!
       }
       
       if("Y" == SubStr(MsgEingang, i, Anfangslaenge))     //i sagt aus, dass die Position von Y betrachtet wird
                                                           //Wiederholung: i stellt die Position von dem String "Y" dar(siehe oben)!
       {
         Y = atoi(SubStr(MsgEingang, i+1, Strlaenge)); //Y-Koordinate wird als Zahlenwert gespeichert
       }
       
     }
     
     
     else if("B" == MsgEingang)
     {

     }
     
     else if("C" == MsgEingang)
     {

     }
     
     else if("D" == MsgEingang)
     {

     }
     
     else if("E" == MsgEingang)
     {

     }
   }
}

sub Zeit_In_X_Richtung(int X_Ziel, int X_Roboter) //Berechnung erfolgt bei konstanter Leistung, Strecke wird in cm angegeben
{
  //int Strecke = abs(X_Ziel - X_Roboter);
    //Rechnung: int Zeit_X = Strecke/50;  //50 sind 50% Leistung des Motors
}

sub Zeit_In_Y_Richtung(int Y_Ziel, int Y_Roboter) //Berechnung erfolgt bei konstanter Leistung, Strecke wird in cm angegeben
{
  //int Strecke = abs(Y_Ziel - Y_Roboter);
    //Rechnung: int Zeit_Y = Strecke/50;
}

sub Wenden()
{
  //Roboter kann hier gewendet werden
}

/*sub MotorenSteuerung()
{

     OnFwd(OUT_AC, 50);

     Wait(ZEIT);
     Off(OUT_AC);

      

} */


task main()
{

  while(true) //Programm kann nur manual vom Benutzer beendet werden
  {
   bezieheMessage();
   
   //Zeit_In_X_Richtung(int X_Ziel, int X_Roboter)
   //Zeit_In_Y_Richtung(int Y_Ziel, int Y_Roboter)
   
   /*if(muss gewendet werden?)
     {
       Wenden()
     }*/

     /*if(Motoren steueren?)
      {
        MotorenSteuerung();
       }
      */
   }

}



Theoretische Betrachtung

In diesem Kapitel findet eine Betrachtung statt in der man seinen Code Prüfen kann ohne ein Tracking Skript.

Sobald alle Schritte erfolgreich verlaufen sind und der ncx-Sourcecode vorliegt, kann dieser privat getestet werden. Hierzu sollte man mit bspw. einem Klebeband ein Feld auf den Boden kleben und die Mitte des Feldes als Koordinate (0|0) festlegen. Als nächstes misst man einen zufälligen Punkt (Hinweis: Positive X-Koordinatenwerte liegen im rechten Teil und positive Y-Koordinaten liegen im oberen Teil des kartesischen Koordinatensystem etc.). Dieser Punkt kann nun manuell über Matlab mit dem Befehl aus dem 5. Schritt gesendet werden (Angabe des Punktes in cm). Der Roboter sollte dabei ungefähr den Punkt anfahren können. Entscheidend dabei ist die Richtung des Roboters. Das bedeutet der Roboter muss in der Lage sein, seine Blickrichtung zu erkennen und zu verändern.