NXC

Aus Debacher-Wiki
Wechseln zu: Navigation, Suche

NXC ist eine C-ähnliche Programmiersprache aber eben "Not eXactly C". Sie wird von John Hansen entwickelt und einer freien Lizenz verteilt, der Mozilla Public License (MPL). John Hansen hat neben der C-ähnlichen Sprache NXC noch eine maschinennahe Sprache namens NBC (Next Byte Codes) entwickelt, in die alle NXC-Programme vom Compiler übersetzt werden. Für die Arbeit mit dem NXC (und anderen Programmiersprachen) hat er auch eine integrierte Entwicklungsumgebung (IDE) namens Bricx Command Center (BricxCC) erstellt.

Mit BricxCC und NXC ist die Programmierung der Mindstorm-Roboter relativ einfach.

Die Oberfläche

Beim Start von BricxCC ergibt sich folgendes Bild:

Bricxcc1.png

Es öffnet sich das verkleinerte Hauptfenster und ein kleines Fenster mit der Überschrift Templates (1). Der größte Teil des Hauptfensters ist blau hinterlegt, weil noch keine Datei geöffnet wurde. Mit einem Klick auf das Menü File (2) und den Menüpunkt New, oder direkt auf das Icon New (2) kann man einen neuen Text öffnen, worauf der entsprechende Bereich weiß hinterlegt ist.

Vergrößert man nun das Hauptfenster einfach, so ist das Template-Fenster verdeckt. Sinnvoller ist es das Template-Fenster zuerst unter das Code-Explorer Fenster zu platzieren (3). Dazu zieht man das Template-Fenster einfach mit der Maus über den gewünschten Zielbereich und kann es dort ablegen. Mit einem Doppelklick auf die doppelte Linie am oberen Rand kann man das Fenster jederzeit wieder aus seiner neuen Position lösen. Sollte das Template-Fenster nach den Veränderungen leer bleiben, so kann man BricxCC einfach beenden und neu starten, danach stimmt die Darstellung wieder.

Es bietet sich nun folgendes Bild:

Bricxcc2.png

Das Programm verfügt über ein umfangreiches Menü und eine Reihe von Menüicons. Hiervon sind vor allem vier Icons für die praktische Arbeit wichtig:

  • Compile (oder F5), damit wird das Programm auf Fehler überprüft und dann übersetzt (1)
  • Download (oder F6), damit wird das Programm an den Roboter übertragen (2)
  • Run (oder F7) startet das Programm auf dem Roboter(3)
  • Stop (oder F8) beendet das laufende Programm (4)

Die letzten drei Icons sind nur dann aktiv, wenn eine Verbindung zum Roboter besteht, ansonsten sind sie grau dargestellt. Wichtig zu erwähnen ist, Run beinhaltet nicht den Download! Run startet nur das aktuelle Programm auf dem Roboter. Nach Veränderungen muss das Programm immer neu per Download übertragen werden, wobei der Download das Compilieren mit einschließt.

Das Menü Tools

Ein für das Arbeiten mit dem Roboter recht wichtiges Menü ist Tools (1):

Bricxcc3.png

Hier finden sich viele Funktionen zur Kommunikation mit dem Roboter. Über Brick Joystick (2) z.B. kann man den Roboter wie mit einer Fernbedienung steuern. Eine nützliche Funktion, um die Funktionsfähigkeit der Motoren zu testen.

Hat man die Verbindung verloren, so kann man sie über Find Brick (3) neu aufbauen.


Preferences

Die wichtigsten Konfigurationsmöglichkeiten für BricxCC finden sich im Menü Edit unter Preferences. Hier sind vor allem zwei Bereiche interessant. Im Reiter Compiler und dort unter NBC/NXC findet sich eine wichtige Einstellung, vor allem für die Nutzung von Bluetooth.

Bricxcc4.PNG

Hier sollte man unbedingt das Häkchen bei Use internal compiler setzen, das beschleunigt die Übertragung erheblich. Normalerweise benutzt BricxCC externe Programme, um die verschiedenen Programmiersprachen übersetzen zu können. Das ist langwierig, wenn man mit Bluetooth arbeitet, da immer nur ein Programm den Zugriff haben kann. Wenn sich BricxCC und externer Compiler abwechseln müssen, geht viel Zeit verloren, die man bei Nutzung des internen Compilers sparen kann.

Im Reiter Startup legt man fest, welche Einstellungen für den Verbindungsaufbau beim Programmstart gelten:

Bricxcc5.png

Diese Einstellungen sollte man auf die eigene Situation (USB oder Bluetooth) anpassen. Im Bereich Firmware sollte man normalerweise keine Änderungen vornehmen. Es gibt verschiedenen Programmierumgebungen, die eine eigene Firmware, das Betriebssystem des Roboters, mitliefern. BricxCC kann auch mit diesen Betriebssystemen umgehen, in der Regel sollte man aber das Standard-Betriebssystem von Lego auf dem Roboter belassen.


Einfache Programme in NXC

Das Erstellen von Programmen in NXC ist naturgemäß mit etwas Schreibaufwand verbunden. Diesen Aufwand verringern hilft das Fenster Templates, indem es die wichtigsten auf Mausklick in das Textfenster einfügt. Für die ersten Programme ist es aber sinnvoll, erst einmal auf diese Unterstützung zu verzichten.

Eine Warnung noch vorweg, C ist case sensitive, es kommt also sehr darauf an, ob man etwas groß oder klein schreibt.

Das erste Programm

Als erstes Beispiel soll ein Klang abgespielt werden, dazu kann das folgende einfache Programm dienen:

/* Programm zur Erzeugung eines einzelnen Tones
   geschrieben von Uwe Debacher 2008            */

task main()
{
 PlayTone(262, 400);
 Wait(500);
}

Hier kann man schon sehr viele Elemente von C bzw. NXC erkennen. Jedes Programm besteht aus einem Hauptprogramm (bzw. Task) namens main(). Alles, was zu main gehört, befindet sich innerhalb der geschweiften Klammern {..}.

Innerhalb des Hauptprogrammes stehen zwei Befehle. Der Befehl

PlayTone(Frequenz, Dauer)

verfügt über zwei Parameter, die Frequenz des Tones in Hz und die maximale Dauer in Millisekunden. Bei NXC ist es aber ähnlich, wie bei der grafischen Umgebung, der Roboter wartet nicht unbedingt auf das Ende eine Befehles. Damit man den Ton dann auch wirklich hört, muss man das Programm dazu bringen etwas zu warten, dafür gibt es auch hier einen Wartebefehl nämlich

Wait(Dauer)

Auch hier ist die Dauer wieder in Millisekunden angegeben.

Am Anfang des Programmes stehen noch zwei Zeilen, die vor allem bei größeren Projekten wichtig sind, nämlich Kommentarzeilen. Ein guter Programmierer kommentiert seinen Quelltext ausführlich, wobei der Kommentar leicht einmal umfangreicher werden kann, als das eigentliche Programm. In dem Beispiel ist ein mehrzeiliger Kommentar zu sehen, der mit /* beginnt und */ endet. Man kann auch einzeilige Kommentare benutzen, die müssen nicht beendet werden und beginnen mit der Zeichenkombination //.

Wie bei allen Programmierumgebungen sollte man sich auch bei BricxCC angewöhnen, seine Projekte regelmäßig zu speichern. Als Datei-Typ wählt man NXC-File aus, dann bekommt die Datei die Endung .nxc angehängt.

Das Programm kann man nun leicht erweitern:

/* Programm zu Ausgabe eines Liedes
   geschrieben von Uwe Debacher 2008  */
   
#define d6 1175
#define e6 1319
#define f6 1397
#define g6 1568
#define a6 1760
#define h6 1976

task main()
{
 PlayTone(d6, 400);  Wait(500);
 PlayTone(e6, 400);  Wait(500);
 PlayTone(f6, 400);  Wait(500);
 PlayTone(g6, 400);  Wait(500);
 PlayTone(a6, 800);  Wait(900);
 PlayTone(a6, 800);  Wait(900);
 PlayTone(h6, 400);  Wait(500);
 PlayTone(h6, 400);  Wait(500);
 PlayTone(h6, 400);  Wait(500);
 PlayTone(h6, 400);  Wait(500);
 PlayTone(a6, 800);  Wait(900);
}

Hier wird der erste Teil von Alle meinen Entchen angespielt. Zwei Dinge sind neu in dieser Erweiterung. Die Wait-Befehle stehen jetzt hinter den PlayTone Befehlen, man darf also mehrere Befehle in eine Zeile schreiben.

Am Anfang des Programmes werden die Notentöne definiert. Dazu werden Konstanten benutzt. Eine Konstanten-Definition fängt immer an mit

#define

danach folgt dann ein Name für die Konstante und zuletzt ihr Wert. Eine Konstante kann im Laufe des Programmes nicht verändert werden, ein wichtiger Unterschied zu Variablen.

Das zweite Programm

Auch NXC kann das Display des Roboters ansteuern. Ein einfaches Besipiel zeigt das folgende Listing.

// Programm zum Ausgabe auf das Display

task main()
{
 TextOut(0, LCD_LINE2, "Hallo Welt");
 Wait(5000);
}

In der ersten Zeile taucht zur Abwechslung ein einzeiliger Kommentar auf. Im Hauptprogramm ist dann die zentrale Zeile

TextOut(x, y, Nachricht, clear=false)

Die ersten beiden Angaben bezeichnen die Position, an der die Textausgabe beginnt. Da man oft mit mehreren Zeilen arbeiten möchte, gibt es für die Zeilenposition vordefinierte Konstanten LCD_LINE1 bis LCD_LINE8. Der dritte Parameter ist der eigentliche Text. Der vierte Parameter ist optional und legt fest, ob das Display vor der Ausgabe gelöscht werden soll, oder nicht. Wenn man den Parameter weglässt, dann wird nicht gelöscht.

Da aber die Ausgabe normalerweise durch das folgende Programmende wieder gelöscht würde, folgt noch ein Warteblock.

Im Gegensatz zu der grafischen Programmierumgebung gibt es hier auch die Möglichkeit Zahlen auf den Bildschirm auszugeben, wie folgende Erweiterung des Programmes zeigt.

// Programm zum Ausgabe auch von Zahlen auf das Display

task main()
{
 TextOut(0, LCD_LINE2, "Hallo Welt");
 NumOut(0, LCD_LINE8, 8);
 Wait(5000);
}

Die Funktion

NumOut(x, y, Wert, clear=false)

gibt eine Zahl direkt auf dem Bildschirm aus.

Es gibt auch eine Reihe von weiteren Befehlen, mit denen man recht aufwändige Zeichnungen erstellen kann. Es gibt sogar Spiele für den NXT, die in NXC geschrieben wurden.

ClearScreen() löscht den Bildschirminhalt
NumOut(x, y, Wert,clear=false) gibt eine Zahl aus
TextOut(x, y, Nachricht, clear=false) gibt einen Text aus
GraphicOut(x, y, filename, clear=false) gibt eine Bitmapdatei aus, die sich auf dem Roboter befindet
CircleOut(x, y, Radius, clear=false) zeichnet einen Kreis mit dem Mittelpunkt (x,y) und dem angegeben Radius
LineOut(x1, y1, x2, y2, clear=false) zeichnet eine Linie vom Punkt (x1,x2) zum Punkt (x2,y2)
PointOut(x, y, clear=false) zeichnet einen einzelnen Punkt
RectOut(x, y, Breite, Höhe, clear=false) zeichnet ein Rechteck mit der linken oberen Ecke in in (x,y) und den angegebenen Abmessungen
ResetScreen() setzt den Bildschirm zurück auf den Standard-Inhalt eines laufenden Programmes


Motoren an

Für die Ansteuerung der Motoren gibt es natürlich eine Vielzahl von Funktionen. Die Mindstorms verfügen bekanntlich über drei Motorausgänge mit den Bezeichnungen A, B und C. Für diese Ausgänge sind passende Konstanten definiert:

  • OUT_A
  • OUT_B
  • OUT_C

Da man häufiger mehrere Motoren gleichzeitig ansteuern möchte, sind auch alle Kombinationen vorgesehen:

  • OUT_AB
  • OUT_AC
  • OUT_BC
  • OUT_ABC

Ein einfaches Programm könnte also folgendermaßen aussehen:

// Programm Motoren an

task main()
{
 OnFwd(OUT_BC, 50);
 Wait(5000);
 Off(OUT_BC);  
}

Hier werden zuerst die Motoren B und C eingeschaltet und zwar auf 50% der Leistung

OnFwd(Ausgang, Leistung)

Dann muss wieder gewartet werden, damit der Roboter sich auch wirklich bewegen kann, bevor das Programm beendet ist und nach Ablauf der Wartezeit werden die Motoren abgeschaltet.

Off(Ausgang)

Wir kontrollieren die Motoren I

Eine wichtige Möglichkeit innerhalb von NXC ist es, die Motoren sehr exakt zu kontrollieren. Die dazu notwendigen Funktionen sind in der Software der Geräte vorhanden, werden aber von der grafischen Oberfläche anscheinend nicht genutzt. Für die Steuerung der Motoren gibt es sehr viele unterschiedliche Funktionen (für viele der Funktionen gibt es auch noch eine erweiterte Funktion, die durch ein Ex im Namen gekennzeichnet ist, also z.B. OnFwdEx):

Off(Ausgang) schaltet die angegebenen Ausgänge bzw. Motoren ab und bremst
Coast(Ausgang) schaltet die angegebenen Ausgänge ab, ohne zu bremsen
Float(Ausgang) Alias für Coast
OnFwd(Ausgang, Leistung) schaltet die angegebenen Motoren mit der angegebenen Leistung an
OnRev(Ausgang, Leistung) schaltet die angegebenen Motoren mit der angegebenen Leistung auf Rückwärtsgang
OnFwdSync(Ausgang, Leistung, Lenkung) wie OnFwd, aber mit Lenkung von -100 bis +100
OnRevSync(Ausgang, Leistung, Lenkung) wie OnRev, aber mit Lenkung von -100 bis +100

In den Beschreibungen ist für die Lenkung oft ein Bereich von -100 bis +100 angegeben, das scheint aber nicht zu stimmen. Geht man mit dem Wert von +100 aus weiter hoch, so gibt es bis +127 erst einmal keine nennenswerten Veränderungen. Bei +128 kehrt sich aber die Drehrichtung der Motoren um. Ein Roboter, der sich vorher rechts herum gedreht hat, dreht sich nun links herum. Bei einem weiteren Vergrößern der Werte verringert sich der Lenkeinschlag, bei +255 ist kein Unterschied zum Wert 0 zu bemerken.

Bei einem Wert von 0 für die Lenkung laufen beide Motoren parallel, bei +127 dann gegenläufig, so dass der Roboter auf der Stelle dreht. Bei Werten knapp unterhalb von +50 bewegt sich nur einer der beiden Motoren, der andere bleibt nahezu regungslos.

Bei negativen Werten ist das Verhalten ähnlich, nur dass die Richtungsänderung jetzt beim Wechsel von -128 zu -129 erfolgt,

Der nächste Befehl erlaubt es die Motoren zu synchronisieren, er bietet sich für normale gerade Fahrtstrecken an:

OnFwdReg(Ausgang, Leistung, RegMode) wie OnFwd, aber mit geregelter Synchronisation
 Es gelten folgenden Angaben für RegMode
   OUT_REGMODE_IDLE,  keine  Regulation
   OUT_REGMODE_SPEED, Regelt die Geschwindigkeit eines Motors
   OUT_REGMODE_SYNC,  Synchronisiert die Drehung der Motoren

Sehr interessant sind die Möglichkeiten der Funktion OnFwdReg, die das folgende Listing zeigen kann.

// Programm Motoren-Sync

task main()
{
 OnFwdReg(OUT_BC, 50, OUT_REGMODE_IDLE );
 Wait(5000);

 PlayTone(1976, 400);  Wait(500);
 OnFwdReg(OUT_BC, 50, OUT_REGMODE_SPEED );
 Wait(5000);

 PlayTone(1976, 400);  Wait(500);
 OnFwdReg(OUT_BC, 50, OUT_REGMODE_SYNC );
 Wait(5000);

 Off(OUT_BC);
 
}

Wenn man dieses Programm startet und eines der Räder am Roboter mit der Hand gut festhält, dann sind die unterschiedlichen Möglichkeiten sehr gut zu spüren.

  • Beim OUT_REGMODE_IDLE ist kein besonderer Effekt zu bemerken.
  • Beim OUT_REGMODE_SPEED stellt man fest, dass der Motor kräftiger gegen den Widerstand angeht.
  • Beim OUT_REGMODE_SYNC hält das andere Rad auch an, bis man wieder los lässt.

Wir kontrollieren die Motoren II

Die Motor-Befehle im ersten Teil setzen die Motoren nur in Gang, das Abschalten der Motoren erfolgt dann in Abhängigkeit von Sensor-Werten oder der Zeit. Bei den folgenden Motor-Befehlen wird ein Drehwinkel vorgegeben, die Motoren drehen sich also solange, bis sie einen bestimmten Drehwinkel erreicht haben. Dieses Verhalten ist z.B. dann nützlich, wenn man eine genau festgelegte Strecke zurücklegen möchte, oder der Roboter seine Richtung um einen bestimmten Winkel ändern soll.

RotateMotor(Ausgang, Leistung, Drehwinkel) wie OnFwd, aber mit Angabe des Drehwinkels für den Motor
RotateMotorEx(Ausgang, Leistung, Drehwinkel, Lenkung, bSync, bStop) wie RotateMotor, aber mit Lenkung und den logischen Schaltern (true/false)
                                                                    für Synchronisation (true synchronisiert) und Bremsen (true bremst).

Für recht genaue Rotationen des Roboters wird man in der Regel RotateMotorEx nutzen.

 RotateMotorEx(OUT_BC, 30, 180, 100, true, true)

bewirkt eine Rechtsdrehung des Roboters um 90°. Die Genauigkeit der Bewegung hängt aber auch von äußeren Faktoren, wie dem Untergrund und der exakten Montage der Räder ab, zuviel darf man hier also nicht erwarten.

Mit den folgenden Befehlen kann man prinzipiell die Bewegung der Motoren noch genauer regeln, leider ist ihre Funktionsweise nicht ganz einfach zu verstehen:

RotateMotorPID(Ausgang, Leistung, Drehwinkel, P, I, D) wie RotateMotor, aber mit exakter Kontrollmöglichkeit für das Erreichen des Zieles.
RotateMotorExPID(Ausgang, Leistung, Drehwinkel, Lenkung, bSync, bStopP, I, D) wie RotateMotorPID, aber mit den gleichen Erweiterungen wie bei RotateMotorEx

PID steht hier für Proportional Integrative Derivative, wobei die von Wikipedia angebotene Übersetzung Proportional-Integral-Differential auch nicht viel weiter hilft. Im Prinzip geht es hier um eine eingebaute Steuerung, die im Bereich Robotik weit verbreitet ist und sehr exakte Bewegungen ermöglicht.


Wir kontrollieren die Motoren III

Die im letzten Abschnitt beschriebenen Befehle halten den weiteren Ablauf des Programmes an, bis die Bewegung abgeschlossen ist. Gelegentlich möchte man hier, eventuell in Abhängigkeit von Sensor-Werten eingreifen können.

Für besondere Situationen kann man die Kontrolle der Motoren individuell übernehmen.

SetOutput(port, Feld1, Wert1, Feld2, Wert2, ...); 

Dieser Befehl erlaubt es, für einen oder mehrere Motoren die einzelnen Felder der Motoransteuerung individuell zu regeln. Dazu kann man eine beliebige Anzahl von Paaren aus Feld und Wert hinter dem Motorport angeben.

// spezielle Ansteuerung der Motoren

task main()
{
 SetSensorTouch(S1);
 TextOut(0, LCD_LINE1, "Stop durch Tastendruck");
 SetOutput(OUT_BC,                          // der Port
           Power, 75,                       // die Motorleistung
           TachoLimit, 3600,                // Drehwinkel, hier 10 Umdrehungen
           RegMode, OUT_REGMODE_SYNC,       // der Regulierungsmodus
           RunState, OUT_RUNSTATE_RUNNING,  // Motor-Power an
           OutputMode, 7,                   // Bitfeld für Steuerung (s. Handbuch)
           UpdateFlags, 7);                 // Bitfeld für Steuerung (s. Handbuch)

 // Warte auf Tastendruck oder 10 Umdrehungen
 while((MotorRunState(OUT_B) == OUT_RUNSTATE_RUNNING) && (SENSOR_1 < 1));
 Off(OUT_BC);
 NumOut(0, LCD_LINE2, MotorTachoCount(OUT_B));
 NumOut(0, LCD_LINE3, MotorRotationCount(OUT_C));
 Wait(10000);                               // damit der Bildschirminhalt zu sehen ist
}

Auf alle Optionen dieses Befehles kann hier nicht eingegangen werden. Es ist auf alle Fälle so, dass hier die Motoren beauftragt werden 10 volle Umdrehungen durchzuführen, aber unterbrochen werden können, wenn der Berührungssensor gedrückt wird.

Am Ende werden die Zähler für beide Motoren zur Kontrolle ausgegeben, wobei MotorTachoCount für den zweiten Motor schon nicht mehr funktioniert, der erste MotorTachoCount scheint den Wert zurück zu setzen, daher hier die Nutzung von MotorRotationCount.

Wer mehr wissen will, vor allem die Bedeutung der Bitfelder, der muss im NXC Programmer's Guide nachschlagen, ab Seite 49 sind dort die Informationen zu finden.

Immer wieder - Wiederholungen und Entscheidungen

Bei den bisherigen Beispielen wurden mehrere Befehle bzw. Funktionen einfach hintereinander ausgeführt. Für viele Probleme benötigt man etwas komplexere Abläufe.

Wir fahren ein Quadrat

Im nächsten Schritt soll der Roboter ein Quadrat abfahren und möglichst nah am Startpunkt wieder ankommen. Er muss also eine Strecke vorwärts fahren und dann die Lenkung einschlagen und ein Stück Kurve fahren. Das Programm könnte folgendermaßen aussehen:

// Programm Links Schwenk 1

task main()
{
 OnFwd(OUT_BC, 50);
 Wait(5000);
 OnRev(OUT_C, 50);
 Wait(500).
 Off(OUT_BC); 
}

Der Roboter wird hier also für 5 Sekunden mit beiden Motoren vorwärts fahren und dann für eine halbe Sekunde den Motor C rückwärts laufen lassen. Motor B fährt in dieser Zeit weiterhin vorwärts. Das bewirkt einen Schwenk um etwa 90°.

Die Zeit für den Schwenk hängt relativ stark von der konkreten Situation ab, man muss in der Regel etwas experimentieren. Da bietet es sich an die Zahlen, mit denen man experimentieren möchte, als Konstanten dem Programm voran zu stellen:

// Programm Links Schwenk 2

#define MOVE_TIME 5000
#define TURN_TIME 500 

task main()
{
 OnFwd(OUT_BC, 50);
 Wait(MOVE_TIME);
 OnRev(OUT_C, 50);
 Wait(TURN_TIME).
 Off(OUT_BC); 
}

Wenn man diese Bewegung nun viermal wiederholt, dann sollte der Roboter ein Quadrat fahren:

// Programm Quadrat 

#define MOVE_TIME 5000
#define TURN_TIME 500 

task main()
{
 repeat(4)
 {
   OnFwd(OUT_BC, 50);
   Wait(MOVE_TIME);
   OnRev(OUT_C, 50);
   Wait(TURN_TIME).
 }
 Off(OUT_BC); 
}

Repeat beschreibt eine sog. Schleife. In der runden Klammer wird angegeben, wie oft die Schleife durchlaufen werden soll, hier 4-mal, in der geschweiften Klammer wird da angegeben, was wiederholt werden soll.

Der Befehl zum Abschalten der Motoren steht außerhalb der Schleife, der er nur einmal am Ende des Programmes notwendig ist.

Man kann solche Schleifen auch innerhalb von anderen Schleifen ausführen lassen:

// Programm Quadrate 

#define MOVE_TIME 5000
#define TURN_TIME 500 

task main()
{
 repeat(10)
 {
   repeat(4)
   {
     OnFwd(OUT_BC, 50);
     Wait(MOVE_TIME);
     OnRev(OUT_C, 50);
     Wait(TURN_TIME).
   }
 }
 Off(OUT_BC); 
}

Hier fährt der Roboter jetzt zehnmal das Quadrat ab.

Hinweis: Die Einrückung der Zeilen innerhalb der Schleife sind für das Programm nicht notwendig, sie dienen der Lesbarkeit. Man sollte sich angewöhnen, das Listing nach einer öffnenden geschweiften Klammer immer um zwei Zeichen einzurücken und erst mit der schließenden Klammer diese Einrückung wieder zu beenden.

Natürlich kann man statt der einfachen Motorbefehle auch die etwas komplizierteren aber auch genaueren Versionen benutzen:

// Quadrat 2

#define MOVE_TIME 1000

task main()
{
  repeat(4)
  {
    Off(OUT_BC);
    OnFwdReg(OUT_BC, 50, OUT_REGMODE_SPEED);
    Wait(MOVE_TIME);
    RotateMotorEx(OUT_BC, 30, 210, 100, true, true);
  }
}

Hinweis: Ersetzt man in diesem Listing OUT_REGMODE_SPEED durch OUT_REGMODE_SYNC, so verhält sich der Roboter seltsam, er dreht sich vermehrt und fährt das Quadrat nicht mehr ab. Das muss an irgendwelchen Countern liegen, die zurück gesetzt werden müssten.

Wiederholungen allgemein

NXC kennt eine Vielzahl von Wiederholstrukturen, die sogenannten Schleifen:


while (Bedingung) Befehl

Die Übersetzung für das while wäre solange. Solange, wie die Bedingung erfüllt ist wird der Befehl ausgeführt. Beispiel:

x=0;
while (x<10)
{
  x = x + 1;
  NumOut(0,LCD_LINE1,x);
  Wait(500);
}


until (Bedingung) Befehl

Bei dieser Art von Wiederholung wird die Bedingung logisch umgedreht:

x=0;
until (x>9)
{
  x = x + 1;
  NumOut(0,LCD_LINE1,x);
  Wait(500);
}

Mit der Kurzform

until (Bedingung);

dieser Schleife kann man die Warteblocks vom NXT-G nachbilden:

until(Sensor(S3>10));

Im nächsten Abschnitt findet sich dazu ein Anwendungsbeispiel.

do Befehl while (Bedingung)

Ähnelt der vorherigen Struktur, mit dem Unterschied, dass hier der Befehl mindestens einmal ausgeführt wird, da die Bedingung erst hinterher überprüft wird.

x=0;
do
{
  x = x + 1;
  NumOut(0,LCD_LINE1,x);
  Wait(500);
}
while (x<10)


for(ausdruck1; bedingung; ausdruck2) Befehl

Eine sehr oft genutzte Art der Wiederholung, vor allem dann nützlich, wenn die Zahl der Wiederholungen bekannt ist.

 for(x=1; x<11; x=x+1)
 {
   OutNum(0,LCD_LINE1,x);
   Wait(500);
 }



repeat (Ausdruck) Befehl

Diese Art der Wiederholung haben wir bereits benutzt. Beim Auswerten des Ausdrucks legt das Programm fest, wie oft der Befehl ausgeführt wird.

 repeat(8/2)
 {
   PlayTone(262, 400);
   Wait(500);
 }

Dieses Programm spielt vier Töne

Wir weichen Hindernissen aus

Das nächste Programm benutzt auch die mitgelieferten Sensoren. Zum Roboter gehören insgesamt 5 Sensoren:

  • zwei Berührungssensoren
  • ein Ultraschallsensor zur Abstandmessung
  • ein Lichtsensor
  • ein Geräuschsensor

Für den Anschluss der Sensoren verfügt der Roboter über vier Anschlüsse, die von 1 bis 4 durchnummeriert sind. In NXC sind für die vier Eingänge die Konstanten S1 bis S4 bzw. IN_1 bis IN_4 vordefiniert (John Hansen empfiehlt die Nutzung von S1 bis S4 in NXC und die Nutzung von IN_1 bis IN_4 in NBC).

Recht interessant ist der Ultraschallsensor, der es dem Roboter erlaubt Hindernisse zu erkennen. Mit dem folgenden Programm soll sich der Roboter vorwärts bewegen, bis er ein Hindernis erkennt.

// Programm Hindernis 1

#define NAH 15

task main()
{
 SetSensorLowspeed(S4);
 OnFwd(OUT_BC, 50);
 until(SensorUS(S4)<NAH);
 Off(OUT_BC); 
}

In diesem Programm tauchen mehrere Dinge neu auf. Die vorhandenen Sensoren unterscheiden sich sehr stark in der Art der internen Anbindung. Das eigene Programm muss also dem NXT geben, was für eine Art von Sensor an welchem Port angeschlossen ist.

SetSensorLowspeed(Anschluss)

sagt dem NXT, dass es sich um einen digitalen Sensor mit bestimmten Eigenschaften handelt. Von den mitgelieferten Sensoren gehört nur der Ultraschall-Sensor in diese Gruppe. Es gibt aber eine Vielzahl von weiteren Sensoren, die mit dem gleichen Kommando bekannt gemacht werden müssen.

Der NXT muss nun noch wissen, wie die Werte vom jeweiligen Sensor zu interpretieren sind. Der Ultraschallsensor liefert irgendwelche Zahlenwerte, die der NXT in Entfernungsangaben umsetzen kann. Um Entfernungsangaben zu bekommen, die einfach zu interpretieren sind, fragt man den Sensor ab mit

SensorUS(Port)

Damit legt man fest, dass man den Ultraschall-Sensor abfragt, und gibt den zugehörigen Port an. Das US im Funktionsnamen steht also nicht für einen Staat, sondern für Ultraschall.

Weiter neu ist

until(Bedingung) Kommando;

Im Prinzip handelt es sich hier um eine Schleife, wie bei Repeat. Das Kommando kann bis auf das Semikolon entfallen, ist es im Beispiel auch. In der benutzten Kurzform

until(Bedingung);

haben wir eine Warteschleife, die drauf wartet, dass die angegebene Bedingung wahr wird, dazu wird ständig der Sensor abgefragt.

Das Programm schaltet die Motoren ein und wartet dann darauf, dass der Sensorwert unterhalb von NAH liegt, und stoppt dann. Im nächsten Schritt soll der Roboter, wenn er das Hindernis erkannt hat, in einer Kurve rückwärts fahren, bis er dem Hindernis ausgewichen ist. Den gesamten Ablauf wiederholen wir unendlich oft, dann bewegt sich der Roboter durch den Raum und weicht die ganze Zeit Hindernissen aus.

// Programm Hindernis 2

#define NAH 15

task main()
{
  SetSensorLowspeed(S4);
  until (false)
  {
    OnFwd(OUT_BC, 50);
    until(SensorUS(S4)<NAH);
    OnRev(OUT_C, 50);
    until(SensorUS(S4)>NAH);
  }
}

Zur Wiederholung taucht hier wieder die Warteschleife auf, diesmal mit einer Bedingung, die nie wahr werden kann, nämlich der Bedingung false bzw. Falsch. Da hinter der Bedingung nur ein einziges Kommando stehen darf, klammern wir unser kleines Programm in geschweifte Klammern ein, wodurch es als ein Kommando gilt.

Wir weichen Hindernissen aus - if

Für das eben beschriebene Problem gibt es auch eine Lösung, die statt mit zwei Warteschleifen mit einer Fallunterscheidung arbeitet. Zur Fallunterscheidung dient das if-Statement:

if(Bedingung)
  Kommando
else
  Kommando;

Es wird also eine Bedingung abgefragt und anschließend kann ein Kommando angegeben werden für den Fall, dass die Bedingung erfüllt ist und ein Kommando für den Fall, dass die Bedingung nicht erfüllt ist. Natürlich kann das einzelne Kommando wieder durch eine Gruppe von Kommandos ersetzt werden, sofern diese in geschweifte Klammern gesetzt wurde.

// Programm Hindernis 3

#define NAH 15

task main()
{
  SetSensorLowspeed(S4);
  until (false)
  {
    if(SensorUS(S4)<NAH)
      OnFwd(OUT_BC, 50);
    else
      OnRev(OUT_C, 50);
  }
}


Die Sensoren in NXC

Im Beispielprogramm Hindernis wird bereits ein Sensor eingesetzt. Im Prinzip arbeiten alle Sensoren ähnlich, sie liefern irgendwelche Zahlenwerte an einem der vier möglichen Ports. Die Zahlenwerte sind erst einmal Roh-Werte (RAW) und können in leichter interpretierbare Werte umgerechnet werden, wenn der Typ des Sensors bekannt ist. Die RAW-Werte des Ultraschall-Sensors lassen sich z.B. als Entfernungsangaben in Zentimetern oder Zoll auslesen.

Grundlagen

Bevor also ein Sensor sinnvoll abgefragt werden kann, muss erst einmal festgelegt werden, von welchem Typ er ist und an welchen Port er angeschlossen ist. Dazu gibt es einige spezifische Befehle:

SetSensorTouch(port)     Erklärt den Sensor am port zum Berührungssensor (Standard: S1)
SetSensorSound(port)     Erklärt den Sensor am port zum Soundsensor (Standard: S2)
SetSensorLight(port)     Erklärt den Sensor an port zum Lichtsensor (Standard: S3)
SetSensorLowspeed(port)  Erklärt den Sensor am port zum Ultraschallsensor (Standard: S4). Genaugenommen wird hier festgelegt, dass es sich um einen
                         digitalen I2C-Sensor handelt. Von den mitgelieferten Sensoren gehört aber nur der Ultraschallsensor in diese Gruppe.

Abgefragt wird der Sensor dann mittels

x = Sensor(S1);

oder, falls es sich um den Ultraschallsensor handelt mittels:

x = SensorUS(S4);

Der Berührungssensor

Der Berührungssensor ist relativ kompliziert in der Abfrage, eventuell, weil er so einfach ist. Wenn man nur den Zustand des Tsters abfragen will, so langt das folgende kleine Programm:

// Nutzung des Berührungs-Sensors

task main()
{
 SetSensorTouch(S1);
 until(false)
 {
   NumOut(0, LCD_LINE1, Sensor(S1), true);
   Wait(100);
 }
}

Als Anzeige bekommt man eine 1, wenn der Taster gedrückt ist, ansonsten eine 0.

Wenn man den Berührungs-Sensor dazu nutzen will, um Aktionen zu starten oder zu stoppen kann es sinnvoll sein darauf zu achten, dass der Wert einmal von 0 auf 1 wechselt und dann wieder von 1 auf 0. Dann ist der Sensor gedrückt und wieder losgelassen worden. Auch zum Zählen von Tastendrücken ist der doppelte Wechsel wichtig.

Für das Zählen der Tastendrücke gibt es noch zwei besondere Modi:

SetSensorMode(S1, SENSOR_MODE_EDGE); zählt alle Wechsel zwischen 0 und 1
SetSensorMode(S1, SENSOR_MODE_PULSE); zählt alle Wechsel zwischen 0 und 1 und zurück, also die klassischen Tastendrücke

Hierzu ein kleines Beispiel:

// Nutzung des Berührungs-Sensors als Zähler

task main()
{
 SetSensorTouch(S1);
 SetSensorMode(S1, SENSOR_MODE_PULSE);
 until(false)
 {
   NumOut(0, LCD_LINE1, Sensor(S1), true);
   Wait(100);
 }
}

Gerade beim Zählen der Tastendrücke kann es sinnvoll sein den Zähler auch wieder zurück setzen zu können:

ClearSensor(S1);

macht genau das.

Der Soundsensor

Der Soundsensor misst die Intensität von Geräuschen, wobei Werte auftreten, die in Dezibel (dB) skaliert sind. Dabei kann man sich gut in Erinnerung bringen, dass die Schmerzschwelle des Menschen bei einem Geräuschpegel von 130 dB liegt. Eine Erhöhung des Schalldruckpegels um 10dB wird subjektiv als Verdoppelung der Lautstärke empfunden. Eine Verdoppelung der Schallsignale, also zwei gleiche Quellen statt einer, bewirken eine Erhöhung um 3dB.

// Nutzung des Sound-Sensors

task main()
{
 SetSensorSound(S2);
 until(false)
 {
   NumOut(0, LCD_LINE1, Sensor(S2), true);
   Wait(100);
 }
}

Der Lichtsensor

Der Lichtsensor misst die Intensität des Lichtes, wobei Werte zwischen 0 und 100 auftreten können.

// Nutzung des Licht-Sensors

task main()
{
 SetSensorLight(S3);
 until(false)
 {
   NumOut(0, LCD_LINE1, Sensor(S3), true);
   Wait(100);
 }
}

Der Befehl SetSensorLight aktiviert dabei auch die Lichtquelle des Sensors, so dass in der Regel die Menge des reflektierten Lichtes gemessen wird. Soll die interne Lichtquelle nicht aktiviert sein, so muss man sie mittels

SetSensorType(S3, SENSOR_TYPE_LIGHT_INACTIVE);

ausgeschaltet werden.

// Nutzung des Licht-Sensors ohne eigene Lichtquelle

task main()
{
 SetSensorLight(S3);
 SetSensorType(S3, SENSOR_TYPE_LIGHT_INACTIVE);
 until(false)
 {
   NumOut(0, LCD_LINE1, Sensor(S3), true);
   Wait(100);
 }
}


Der Ultraschallsensor

Der Ultraschallsensor unterscheidet sich in seiner Ansteuerung von den anderen mitgelieferten Sensoren. Es handelt sich bei ihm um einen digitalen I2C-Sensor, der über spezielle Kommandos zur Ansteuerung verfügt. Die Messwerte des Sensors geben die Entfernung zu einem Hindernis in cm an.

// Nutzung des Ultraschall-Sensors

task main()
{
 SetSensorLowspeed(S4);
 until(false)
 {
   NumOut(0, LCD_LINE1, SensorUS(S4), true);
   Wait(100);
}


Die Sensoren der Motoren

In die Motoren der NXT-Roboter sind Drehsensoren eingebaut, über die die Position recht exakt kontrolliert werden kann. Diese Sensoren verfügen über keinen zusätzlichen Anschluss, werden also über die Motor-Ausgänge abgefragt.

// Nutzung des Rotations-Sensoren

task main()
{
 OnFwd(OUT_B, 50);
 until(false)
 {
   NumOut(0, LCD_LINE1, MotorRotationCount(OUT_B), true);
   Wait(100);
 }
}

Der Sensor registriert nicht nur die Veränderungen, die aktiv vom Motor erzeugt werden, man kann auch einfach an dem entsprechenden Rad drehen, worauf sich der Wert ebenfalls verändert.

Die Messwerte sind übrigens auf 1° genau, eine vollständige Umdrehung des Motors ergibt also 360°.

Den Sensor kann man mittels

 ResetRotationCount(OUT_B);

jederzeit auch zurück setzen.

Neben dem MotorRotationCount gibt es noch weitere Sensoren:

 MotorTachoCount(port);
 MotorBlockTachoCount(port);

Die Unterschiede zwischen diesen drei Zählern werden nirgends klar beschrieben. Es scheint so zu sein, dass sie von unterschiedlichen Steuerbefehlen zurück gesetzt werden. Mit dem obigen Beispiel ließe sich kein Unterschied zwischen den drei Countern feststellen.


Weitere Möglichkeiten

Von der Firma HiTechnic gibt es eine Reihe zusätzlicher Sensoren. Diese werden von NXC unterstützt, weitere Informationen dazu auf meiner Seite zu Sensoren. Hier finden sich auch weitere Möglichkeiten zur Abfrage der Standard-Sensoren.