NXC Minimal Tutorial Christian Scheuer Helmut-Schmidt-Universität /Universität der Bundeswehr Hamburg Professur für Technische Informatik [email protected], [email protected] 30. Mai 2012 Dieses Dokument beschreibt einige grundlegende Ideen und Spracheigenschaften von NXC.

1

Was ist NXC?

NXC steht für Not Exactly C und ist eine von John Hanson entwickelte, Cähnliche Programmiersprache für den Lego Mindstorm. Bevor NXC entwickelt wurde, programmierte man den Lego Mindstorm mit NBC (NeXT Byte Code), einer eher assemblerähnlichen Sprache für den NXT-Baustein, für die Lego Compiler/Interpreter zur Verfügung stellt. NXC wird dementsprechend auch immer noch auf NBC herunterkompiliert und das Ergebnis den hauseigenen NBC-Compilern übergeben.

2

Die Programmierumgebung BricxCC

Das Bricx Command Center ist eine kostenlose Programmierumgebung, das die Programmierung des NXT in NXC vereinfacht. Es bietet zahlreiche Features von Syntax-Highlighting über Fehleranalyse bis zur Compilation und Übertragung des Programmes auf den NXT.

3

Ein kleines NXC-Programm start.nxt:

1

4

7

10

# define MY_SPEED 25 task fahren () { while ( true ) { TextOut (1 ,1 , " 2 " ); OnFwd ( OUT_AC , MY_SPEED ); Wait (500); }

1

13

16

19

} task laerm () { while ( true ) { TextOut (1 ,1 , " 1 " ); PlayTone (440 , 500); Wait (500); } }

25

task main () { ClearScreen (); TextOut (1 ,1 , " Hallo , ␣ Welt ! " );

28

}

22

Precedes ( fahren , laerm );

Das Programm start.nxt unterteilt sich in 3 Segmente, die Tasks fahren, laerm und main. Ein Task besteht aus Statements (auch Commands genannt), die sequentiell abgearbeitet werden. Statements sind Variablenzuweisungen, Kontrollstrukturen, Tasks, Subtasks oder Funktionen. Bei Programmstart wird der Task mit dem Namen main gestartet, alle weiteren Tasks werden nur gestartet, wenn sie mittels der Funktion precedes() aufgerufen werden. Im Gegensatz zur bekannten C-Programmierung werden hierbei standardmässig mehrere Tasks gleichzeitig gestartet, im Beispielprogramm fahren und laerm. Insgesamt kann ein Programm aus maximal 255 Tasks bestehen, die theoretisch alle parallel laufen können. Wie man im Beispielprogramm sehen kann, führt diese Möglichkeit zu einer neuen Art von Konflikt: Beide Tasks geben an der selben Stelle des Displays in einer Endlosschleife unterschiedliche Zeichen aus. Welches Zeichen man letztlich auf dem Bildschirm erkennen können wird, ist aus dem Programmtext nicht zu schließen.

3.1

Subtasks

Funktionen wurden in C hauptsächlich verwendet, um Codesegmente, die man an verschiedenen Stellen benötigt, an einer zentralen Stelle zusammenzufassen. Diese Aufgabe übernehmen in NXC die Subtasks, wie das folgende Beispielprogramm verdeutlicht: drehung.nxt: # define MY_SPEED 25 3

6

9

sub drehung ( int power ) { OnRev ( OUT_C , power ); Wait (500); OnFwd ( OUT_AC , power ); } task main () {

2

drehung (75); Wait (1000); drehung (75); Wait (1000);

12

15

}

4

Kontrollstrukturen, Kommentare und Präprozessorbefehle

Die meisten Kontrollstrukturen sind bereits aus C bekannt, darunter while, if, und do...while. Darüberhinaus gibt es eine repeat-Struktur: repeat(int n){Statements} arbeitet die Statements exakt n-mal ab. Kommentare und Präprozessorbefehle funktionieren exakt wie in C. drehung.nxt:

3

6

9

# define MY_STD_SPEED 210 /* Dieses Programm tut nichts ! */ task main () { while ( true ) // Endlosschleife { repeat (100){ Wait (100);} // tut nichts ! } }

5

Sensoren und Aktuatoren

Die Sensoren werden über die IN-Ports des NXT angeschlossen (IN_1,IN_2, IN_3 und IN_4) und mittels zahlreicher Funktionen initalisiert und ausgelesen. Gleiches gilt für die Aktuatoren, die an den OUT-Ports (OUT_A, OUT_B sowie OUT_C) angeschlossen sind. Die Aktuatoren lassen sich zusätzlich synchron ansteuern, indem OUT_AB, OUT_AC oder OUT_ABC verwendet werden. sensoren.nxt:

3

6

9

12

task fahren () { while ( true ) { if ( SENSOR_1 ==1) { OnFwd ( OUT_AC ,50); // Vorwaertsfahren PlayTone (440 , 500); // Geraeusch (440 Hz , halbe Sekunde ) Wait (1000); } OnFwD ( OUT_A , 50); // Drehung Wait ( Random (500)); // zufaellige Laenge Float ( OUT_A ); OnRev ( OUT_AC ,50) Wait ( Random (500));

3

Float ( OUT_AC );

15

} } 18

21

task main () { SetSensor ( IN_1 , SENSOR_TOUCH ); // Beruehrungssensor an IN_1 Precedes ( fahren ); }

Das Programm sensoren initialisiert einen Berührungssensor, der im Falle eines Kontaktes eine Vorwärtsfahrt auslöst (hierbei wird davon ausgegangen, dass der Sensor hinten am Fahrzeug angebracht ist). Der Sensor wird hierbei durch die hinter dem Makro SENSOR_1 versteckte Funktion ausgelesen.

6

Kommunikation zwischen NXT-Bausteinen

Die Kommunikation zwischen NXT-Bausteinen verlauft über ein Master-SlavePrinzip. Insgesamt kann Kommunikation zwischen insgesamt 4 Bausteinen ohne Hilfskonstrukte aktiviert werden, dabei übernimmt einer der NXTs die Rolle des Master, die übrigen die Rolle der Slaves. Kommunikation findet ausschließlich zwischen Master und Slave statt, Slaves nehmen sich gegenseitig nicht wahr. Hinzu kommt ein Postfach-Prinzip: Jeder NXT hat 10 Posteingänge (Inbox) und -ausgänge (Outbox). Gesendete Nachrichten aus der Outbox n werden beim Empfänger entsprechend in die Inbox n gelegt. Dies ermöglichst es beispielsweise, konfliktfrei mehr als einen Slave pro Kanal zu verwenden. bluetoothMASTER.nxt:

3

6

9

12

15

18

21

24

// MASTER # include " NXCDefs . h " # define BT_CONN 1 # define OUTBOX 5 # define INBOX 1 sub BTCheck ( int conn ){ if (! BluetoothStatus ( conn )== NO_ERR ){ TextOut (5 , LCD_LINE2 , " Error " ); Wait (1000); Stop ( true ); } } task main (){ int ack , i ; BTCheck ( BT_CONN ); TextOut (10 , LCD_LINE1 , " Master ␣ sending " , false ); while ( true ){ i = Random (512); TextOut (0 , LCD_LINE3 , " ␣ " ); NumOut (5 , LCD_LINE3 , i ); ack = 0; SendRemoteNumber ( BT_CONN , OUTBOX , i ); until ( ack ==0 xFF ) { until ( ReceiveRemoteNumber ( INBOX , true , ack ) == NO_ERR );

4

27

} Wait (250); } }

bluetoothSLAVE.nxt: 2

5

8

11

14

17

20

23

26

// SLAVE # include " NXCDefs . h " # define BT_CONN 1 # define OUT_MBOX 1 # define IN_MBOX 5 sub BTCheck ( int conn ){ if (! BluetoothStatus ( conn )== NO_ERR ){ TextOut (5 , LCD_LINE2 , " Error " ); Wait (1000); Stop ( true ); } } task main (){ int in ; BTCheck (0); TextOut (5 , LCD_LINE1 , " Slave ␣ receiving " ); SendResponseNumber ( OUT_MBOX ,0 xFF ); // unblock master while ( true ){ if ( ReceiveRemoteNumber ( IN_MBOX , true , in ) != ST AT_ MS G_E MP TY_ MA ILB OX ) { TextOut (0 , LCD_LINE3 , " ␣ " ); NumOut (5 , LCD_LINE3 , in ); SendResponseNumber ( OUT_MBOX ,0 xFF ); } Wait (10); // take breath ( optional ) } }

Im Beispielprogramm (Master) wird zunächst mittels Subtask BTCheck überprüft, ob eine Verbindung zu Slave 1 besteht. Diese muss vor Start des NXC-Programmes eingerichtet über das Betriebssystem des NXT eingerichtet worden sein. Nach Erfolg wird eine Zufallszahl ausgegeben und über die Outbox 5 an den Slave gesendet. Der Master wartet dann in einer until-Schleife auf das Acknowledgement des Slaves. Danach beginnt der Sendeprozess mit neuer Zufallszahl von neuem. Das Slave-Programm überprüft ebenfalls die Verbindung zum Master (Kanal 0), sendet ein einzelnes Acknowledgement und startet eine Endlosschleife. In dieser wartet der Slave auf eine gesendete Zahl, gibt sie aus und sendet ein weiteres Acknowledgement.

7

Funktionsübersicht NXC

Die folgende, unvollständige Liste gibt eine Übersicht über Funktionen, die dem verwedeten NXC-Compiler bereits bekannt sind, es müssen daher keinerlei Bibliotheken eingebunden werden. Alle weiteren Funktionen findet man unter anderem im „Not eXactly C (NXC) Programmer’s Guide“ von John Hansen. 5

void TextOut(int row, string text) Gibt den String in der Zeile row aus. string StrCat(string a, string b, ...) Verbindet Strings, gibt Gesamtstring zurück. int SensorUS(port p) Gibt die am Ultraschallsensor p anliegenden Werte zurück. string NumToStr(int number) Gibt den numerischen Wert number als String zurück unsigned int CurrentTick() Gibt den momentanen Zeitwert des Systems (Auflösung in Millisekunden) zurück. int SensorRaw(sensor s) Gibt Rohdaten des Sensors s zurück. int MotorRotationCount(port p) Gibt Positionscounter des Motors an Port p zurück. OnFwd(port out, int pwr) Bewegt die Motoren out mit prozentualer Leistung pwr (0-100) vorwärts. OnFwdSync(port out, int pwr,int turnpct) Wie OnFwdReg() im Sync-Modus, zusätzlich kann man die prozentuale Drehung der Reifen angeben (-100 bis 100). OnRev(port out, int pwr) Setzt die Ausgänge out auf Rückwärtsbewegung mit Power pwr (0-100). RotateMotor(port p, speed s, degrees d) dreht den Motor an p mit Geschwindigkeit s um d Grad. SetSensorLowspeed(port p) Sensor an Port p wird als UltraschallLowspeed-Sensor deklariert. SetSensorSound(port p) Sensor an Port p wird als Geräuschsensor deklariert. SetSensorLight(port p) Sensor an Port p wird als Helligkeitssensor deklariert. SetSensorTouch(port p) Write(handle h, int value) Schreibt einen Numerischen Wert in die Datei hinter h. NumOut(column, line, value) Schreibt einen numerischen Wert an die entsprechende Displayposition. WriteLn(byte handle, string text) Wie Write() mit anschließendem Zeilenumbruch. CreateFile(string Filename, int maxlengthinbyte, byte handle) Erstellt eine Datei mit Namen Filename. Die maximale Länge darf zur Laufzeit nicht überschritten werden. über das handle lässt sich die Datei referenzieren. 6

DeleteFile(string Filename) Entfernt die Datei mit Namen Filename. ClearScreen() Löscht das LCD. Schreibt den numerischen Wert value an die Position (x,y). PlayTone(int f,int t) Spielt den Ton der Frequenz f für t Millisekunden. (Beispiel PlayTone(440, 500) spielt a für eine halbe Sekunde. OnFwdReg(outputs,pwr, regmode), OnRevReg(outputs, pwr, regmode) Die angesprochenen outputs werden im regmode mit Stärke pwr angesteuert. Mögliche regmodes sind OUT_REGMODE_SPEED (Geschwindigkeitsregulierung, Motoren versuchen Geschwindigkeit auch gg. Last zu halten) OUT_REGMODE_SYNC (Synchronisationsregelung für mehr als einen Motor. Motoren warten aufeinander, falls Blockierung oder Lastungleichheit vorliegt) OUT_REGMODE_IDLE (keine Regulierung, OnFwdReg() wird zu OnFwd() ) Off(port p) Schaltet die mit p verbundene Komponente aus. Float(output) Setzt den output auf float-Modus. Bei Motoren bedeutet dies, die Motorleistung zu deaktiveren ohne die Bremse zu betätigen (ausrollen lassen). SetSensorMode(port p, const mode) Setzt den Modus des Sensors an Port p (mögliche Eingaben S1,S2,S3,S4). Ein möglicher Modus ist SENSOR_MODE_RAW (Rohdaten 0-1023) void SetSensorType(port p, const type) Setzt den Typen des Sensors an Port p (mögliche Eingaben S1,S2,S3,S4). Mögliche Typen sind SENSOR_TYPE_LIGHT_INACTIVE (Lichtsensor ohne Eigenlicht) unsigned int result Random(unsigned int Max) Generiert vorzeichenlose 16-bit Zufallszahl zwischen 0 und Max-1. void Wait(integer time) Task wartet time Millisekunden. Precedes(task t1, task t2, ...) Startet parallel alle angegebenen Tasks. StartTask(task t) Startet den Task t. void StopAllTasks(void) Beendet sämtliche Tasks inklusive main(). int BluetoothStatus(int con) Gibt den Status der Bluetoothverbindung auf Kanal con zurück. Ist die Verbindung aktiv, wird die Konstante NO_ERR zurückgegeben. ReceiveRemoteNumber(int queue, bool remove, int value) Ein Master-Befehl. Der Wert value wird aus der Inbox queue empfangen und abhängig vom Boolean remove entfernt. bool ButtonPressed(int button) überprüft, ob der Knopf button auf dem NXT gerade gedrückt wird, und gibt einen booleschen Wert zurück. für button sind folgende Konstanten definiert: BTN1, BTNEXIT = 0, BTN2, BTNRIGHT = 1, BTN3, BTNLEFT = 2, BTN4, BTNCENTER = 3, NO_OF_BTNS = 4. 7

SendResponseNumber(int queue, int value) Sendet als Antwort auf eine empfangene Nachricht den Wert value über die outbox queue an den letzten Sender zurück. SendRemoteNumber(int con, int queue, int value) Sendet die Zahl value an das Gerät hinter Verbindung con. Die Zahl wird dabei über die outbox queue versandt.

8