81
Anleitung zur Programmierung des NXT in NXC Czech, Walter 14. Mai 2014

Anleitung zur Programmierung des NXT in NXCs507800777.online.de/blog/wp-content/uploads/2014/06/NXC.pdf · Anleitung zur Programmierung des NXT in NXC Czech, Walter 14. Mai 2014

Embed Size (px)

Citation preview

Anleitung zur Programmierung des NXT inNXC

Czech, Walter

14. Mai 2014

Inhaltsverzeichnis

1 Erste Einfuhrung 51.1 Roboter? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2 Tribot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.3 Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.4 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.5 Programmieren zu Hause . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.6 Hinweis zum Speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2 Dauerschleifen und voids 92.1 Wiederholung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.2 Mehr Ubersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3 Dauerschleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3 Variable und Konstanten 133.1 Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.2 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.3 Konstante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

4 Schleifen 184.1 Wiederholung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184.2 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

4.2.1 Ausgabe auf dem NXT-Bildschirm . . . . . . . . . . . . . . . . . . 194.2.2 while-Schleife mit Bedingung . . . . . . . . . . . . . . . . . . . . . 204.2.3 for-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

4.3 Aufgaben: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

5 Motorsteuerung 255.1 Befehle fur einen Motor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

5.1.1 Einen Motor bewegen . . . . . . . . . . . . . . . . . . . . . . . . . 255.1.2 Aufgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275.1.3 Winkelmesser abfragen . . . . . . . . . . . . . . . . . . . . . . . . . 275.1.4 Um bestimmten Winkel drehen . . . . . . . . . . . . . . . . . . . . 28

5.2 Befehle fur zwei Motore . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.2.1 Bewegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.2.2 Zwei Motore synchronisieren . . . . . . . . . . . . . . . . . . . . . 295.2.3 Aufgabe zum Nachdenken . . . . . . . . . . . . . . . . . . . . . . . 31

2

Inhaltsverzeichnis Inhaltsverzeichnis

6 Sensoren Teil 1 326.1 Sensoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326.2 Taster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326.3 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346.4 if: Reaktion auf den Sensor . . . . . . . . . . . . . . . . . . . . . . . . . . 356.5 Vergleichsbefehle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376.7 if - else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386.8 Auf Sensor warten: until . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396.9 Zwei Sensoren abfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396.10 Losung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

7 Sensoren Teil 2 417.1 Ultraschallsensor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417.2 Lichtsensor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

7.2.1 LineFollower . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467.2.2 PID-Steuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

7.3 Mikrofon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

8 Parallel laufende Prozesse 518.1 Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

8.1.1 Ein unabhangiger Prozess . . . . . . . . . . . . . . . . . . . . . . . 518.1.2 Zwei Zahlprozesse . . . . . . . . . . . . . . . . . . . . . . . . . . . 528.1.3 Fahren und Sensor abfragen . . . . . . . . . . . . . . . . . . . . . . 54

8.2 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

9 Kommunikation zwischen NXTs 589.1 Mit Kabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589.2 Mit Bluetooth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

9.2.1 Kontakt herstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . 619.2.2 Versenden einer Zahl . . . . . . . . . . . . . . . . . . . . . . . . . . 629.2.3 Master sendet String . . . . . . . . . . . . . . . . . . . . . . . . . . 649.2.4 Kommunikation mit Bestatigung . . . . . . . . . . . . . . . . . . . 679.2.5 Beispiel: Ubertragung mehrere Werte . . . . . . . . . . . . . . . . 69

10 Abfrage der NXT-Tasten 7210.1 Erstes Beispiel: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7210.2 Zweites Beispiel: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7310.3 Drittes Beispiel: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

11 Speichern von Daten 7611.1 Daten in Arrays speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

11.1.1 Erstes Beispiel: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7611.1.2 Zweites Beispiel: . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

3

Inhaltsverzeichnis Inhaltsverzeichnis

11.2 Daten in Files speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

4

1 Erste Einfuhrung

1.1 Roboter?

Wir bauen in diesem Kurs einen Roboter, d.h. eine Maschine, die autonom eine bestimm-te Aufgabe erledigen kann. Dazu muss der Roboter in der Regel Bewegungen ausfuhrensowie auf die Umwelt reagieren konnen. Fur die Bewegungen gibt es Motore, fur dieReaktion auf die Umgebung gibt es verschiedene Sensoren.

Im ersten Teil bauen wir ein kleines Fahrzeug, den Tribot, den wir zum Fahren bringenwollen:

Abbildung 1.1: Tribot

1.2 Tribot

Baue nach LEGO-Anleitung einen Tribot: Zwei Antriebsmotore, ein frei beweglichesdrittes Rad.

Achte darauf, dass die Motore an den Anschlussen A, B oder C angeschlossen wer-den. Die Anschlusse 1 .. 4 dienen dem Anschluss von Sensoren. Verwechselt man dieAnschlusse, gibt es zwar keinen Schaden, aber in der Regel funktioniert der Roboternicht.

1.3 Programm

Damit sich der Tribot bewegen kann, benotigt er ein kleines Programm, welches dieMotore startet. Das Programm schreiben wir am Computer und ubertragen es dann -wenn es fehlerfrei ist - auf den NXT.

5

1.3. PROGRAMM KAPITEL 1. ERSTE EINFUHRUNG

Schließe den NXT mit dem USB-Kabel an den Rechner an, schalte den NXT ein.

Starte das Programm BricxCC, es versucht eine Verbindung zum NXT aufzubau-en. Sollte die Verbindung nicht automatisch klappen oder verloren gehen, kannst Duversuchen, im BricxCC-Menu Tools → Find Brick die Verbindung neu aufzubauen.

Lege eine neue Programmdatei an: File → New. Speichere sie unter einem beliebigenNamen (nicht unter Untitled!!!). Achte darauf, dass die Endung des Programms ’.NXC’lautet.

Schreibe Dein erstes Programm (Zeilennummern von mir zugefugt):

1 task main ( )2 {3 OnFwd(OUT A, 7 5 ) ; //Motor A vorwaerts Le i s tung 754 OnFwd(OUT C, 7 5 ) ; //Motor C vorwaerts Le i s tung 755 Wait ( 2 0 0 0 ) ; //2 Sekunden fahren6 Off (OUT AC) ; // be ide Motore anhalten7 OnRev(OUT AC, 50) ; // be ide Motore zurueck Le i s tung 508 Wait ( 1 0 0 0 ) ; //1 Sekunde fahren9 Off (OUT AC) ; // be ide Motore aus

10 }

Kompiliere das Programm: Menu Compile → Compile oder F5 drucken.Der Compiler pruft zunachst, ob das Programm ohne Tippfehler geschrieben wurde

und ubersetzt es dann in Code fur den NXT. Der Compiler kann nicht prufen, ob dasProgramm genau das tut, was der Programmierer wollte.

Ist das Programm fehlerfrei und besteht eine Verbindung, ubertrage es an den NXT:Compile → Download. Starte das Programm auf dem NXT. Alternativ geht auch: Com-pile → Download and Run. Dann startet das Programm sofort.

Enthalt das Programm Fehler, rufst Du nicht nach dem Lehrer, sondern: lies dieFehlermeldungen genau durch und korrigiere entsprechend.

Erklarungen:

1. Unser Programm besteht aus der Task task main(), welche in jedem Programmvorhanden sein muss . Jedes Programm muss genau eine task main() ent-halten.

6

1.4. AUFGABEN KAPITEL 1. ERSTE EINFUHRUNG

2. OnFwd(OUT A, 75); Bedeutet: Schalte den Motor auf Anschluss A in Vorwartsrichtungein, die Motorleistung soll 75 betragen. Am Ende des Befehls steht ein ’;’. DiesesZeichen schließt einen Befehl ab.

3. Wait(4000); bedeutet: Warte 4000 ms = 4 s. D.h. die beiden Motore laufen 4Sekunden lang.

4. Off(OUT AC); bedeutet: Beide Motore werden gleichzeitig gestoppt.

5. Zum Bremsen gibt es anstelle von Off() den Befehl Coast(OUT AC); die Motorelaufen hier langsam aus und stoppen nicht schlagartig. Ausprobieren!

6. OnRev(OUT AC); bedeutet: Beide Motore laufen gleichzeitig ruckwarts.

7. Die Einruckungen im Programm dienen zu Deiner personlichen Ubersicht, alsoschreibe auch Deine Programme so!

8. // ist das Zeichen fur einen einzeiligen Kommentar, der Compiler ignoriert alleswas nach diesem Zeichen steht.

9. Mehrzeilige Kommentare schreibt man so:

/* Hier steht

der

Kommentar

*/

1.4 Aufgaben

Probiere jede dieser Aufgaben praktisch aus:

1. Was passiert, wenn man den Wait-Befehl weglasst?

2. Andere die Motorleistung, experimentiere mit großeren und kleineren Zahlen.

3. Will man zwei Motore z.B. auf A und C gleichzeitig laufen lassen, gibt es denBefehlOnFwd(OUT AC,30);

4. Anstelle von Anschluss C kann man auch den Anschluss B verwenden. Andere dasPRogramm entsprechend.

5. Welche Werte (minimal/maximal) darf Power annehmen bzw. was passiert, wenndie Grenzen uberschritten werden?

Lerne die Befehle!!!

7

1.5. PROGRAMMIEREN ZU HAUSE KAPITEL 1. ERSTE EINFUHRUNG

1.5 Programmieren zu Hause

Wer zu Hause programmieren mochte, braucht das Programm bricxcc.exe. Dies gibt es(nur fur Windows) frei im Internet unter:http://bricxcc.sourceforge.net/ , dort auf ’latest version’

1.6 Hinweis zum Speichern

Speichere aus Sicherheitsgrunden jedes wichtige Programm mindestens zwei mal: Bei-spielsweise auf einem USB-Stick und z.b. auf Dropbox oder GMX-Mediacenter oder ...

Es reicht keineswegs aus, ein Programm auf einem USB-Stick als alleiniges Speicher-medium zu sichern, denn er konnte kapuut gehen.

8

2 Dauerschleifen und voids

2.1 Wiederholung

1. Was bedeutet das ’;’ am Ende eines Befehls?

2. An welchen Anschlussen werden die Motore angeschlossen?

3. Wie lauten die Parameter (das was in der Klammer steht) im MotorsteuerbefehlOnFwd(....)?

4. Wie fahrt man ruckwarts? (2 Antworten)

5. Welche Zeit wird bei Wait(100) gewartet?

2.2 Mehr Ubersicht

Wir schreiben unser erstes Programm noch einmal, aber ubersichtlicher. Dazu werdendie Fahrbefehle in ein ’void’ ausgelagert:

1 void t e s t f a h r t ( )2 {3 OnFwd(OUT A, 7 5 ) ; //Motor A vorwaerts Le i s tung 754 OnFwd(OUT C, 7 5 ) ; //Motor C vorwaerts Le i s tung 755 Wait ( 2 0 0 0 ) ; //2 Sekunden fahren6 Off (OUT AC) ; // be ide Motore anhalten7 OnRev(OUT AC, 50) ; // be ide Motore zurueck mit Le i s tung 508 Wait ( 1 0 0 0 ) ; //1 Sekunde fahren9 Off (OUT AC) ; // be ide Motore aus

10 }1112 task main ( )13 {14 t e s t f a h r t ( ) ; / / void t e s t f a h r t au f ru f en15 }

Geandert hat sich:

1. Alle Befehle des ersten Programmes sind aus der task main verschwunden.

9

2.2. MEHR UBERSICHT KAPITEL 2. DAUERSCHLEIFEN UND VOIDS

2. Diese Befehle tauchen oberhalb wieder auf innerhalb einer void testfahrt()’void’ bedeutet: Es handelt sich um eine ausgelagerte Aufgabe.’testfahrt()’ ist der Name dieser ausgelagerten Aufgabe, unter dem sie aufgrufenwird. Der Name wird vom Programmierer frei festgelegt. Bedingung: Er darf keineSonderzeichen enthalten. D.h. erlaubt ist void schraeg(), verboten dagegen voidschrag()

3. Um dem Compiler mitzuteilen, dass es sich um eine ausgelagerte Aufgabe handelt,schreibt man in der ersten Zeile das Wort void, dann den Namen der void unddanach das Klammernpaar ’()’

4. In der task main() wird die Aufgabe testfahrt() aufgerufen. D.h. sie wird nur mitihrem Namen, ohne das Wort ’void’, aufgerufen.

Vorteil der neuen Programmstruktur:Das Programm wird ubersichtlicher, eine einzelne Aufgabe ist leichter zu testen.

Will man eine Linksskurve und dann eine Rechtskurve fahren, musste das Programmfolgendermaßen aussehen:

1 void recht skurve ( )2 {3 h i e r f e h l t de in e i g e n e r Programmcode ! ! !4 }56 void l i n k sku rve ( )7 {8 OnFwd(OUT A, 4 0 ) ;9 OnFwd(OUT B, 8 0 ) ;

10 Wait ( 1 00 ) ; // Ze i t muss angepasst werden !11 }1213 task main ( )14 {15 l i nk sku rve ( ) ; / / r u f t l i nk sku rve ( ) auf16 Off (OUT AB) ;17 recht skurve ( ) ; / / r u f t r echt skurve ( ) auf18 Off (OUT AB) ;19 }

Aufgaben:

1. Bringe das Programm zum Laufen, erganze den fehlenden Programmcode.

2. Andere das Programm so ab, dass es mit einer Rechtskurve beginnt.

3. Andere das Programm so ab, dass nach einer Linkskurve ein Stuck geradeausgefahren wird, dann wieder eine Linkskurve kommt usw. Schreibe dazu eine voidgeradeaus() und rufe sie aus der task main() heraus auf.

10

2.3. DAUERSCHLEIFE KAPITEL 2. DAUERSCHLEIFEN UND VOIDS

4. In der task main() werden nach jedem Aufruf einer Kurvenfahrt die Motore abge-schaltet. Verlege diesen Befehl in die voids.

2.3 Dauerschleife

Will man erzwingen, dass ein Programm endlos einen Befehl wiederholt, geht dies mitfolgender Dauerschleife:

while(TRUE)

{

//Befehle, die dauernd ausgefuehrt werden sollen

}

Beispielsweise konnte man in einem Programm erzwingen, dass der Tribot andauerndeine Kurve fahrt:

1 void l i n k sku rve ( )2 {3 OnFwd(OUT A, 4 0 ) ;4 OnFwd(OUT B, 8 0 ) ;5 Wait ( 1 0 0 ) ;6 }78 task main ( )9 {

10 whi l e (TRUE) // D a u e r s c h l e i f e beg innt11 {12 l i nk sku rve ( ) ;13 } // D a u e r s c h l e i f e von vorn14 }

Achtung:Aus einer Dauerschleife kommt das Programm nicht mehr heraus!!!

Will man standig eine Linkskurve, dann eine Rechtskurve fahren, musste das Pro-gramm folgendermaßen aussehen:

1 void recht skurve ( )2 {3 // h i e r f e h l t de in e i g e n e r Programmcode ! ! !4 }56 void l i n k sku rve ( )7 {8 OnFwd(OUT A, 4 0 ) ;9 OnFwd(OUT B, 8 0 ) ;

10 Wait ( 3 0 0 ) ;

11

2.3. DAUERSCHLEIFE KAPITEL 2. DAUERSCHLEIFEN UND VOIDS

11 }1213 task main ( )14 {15 whi l e (TRUE)// D a u e r s c h l e i f e beg innt16 {17 l i nk sku rve ( ) ; / / r u f t l i nk sku rve ( ) auf18 recht skurve ( ) ; / / r u f t r echt skurve ( ) auf19 } //Ende D a u e r s c h l e i f e20 }

Aufgaben:

1. Andere das Programm so ab, dass standig zweimal eine Linkskurve, dann ein Stuckgerade aus (z.B. void gerade() schreiben )und dann zweimal eine Rechtskurve ge-fahren wird.

2. Bringe das Programm dazu, einen bestimmten Buchstaben zu fahren.

12

3 Variable und Konstanten

3.1 Variable

In der Mathematik vertritt eine Variable alle moglichen Zahlen. In der Informatik ist eineVariable eine Stelle im Speicher, an der irgend etwas, z.B. eine Zahl oder ein Buchstabe,abgelegt ist. Das Programm kennt die Speicherstelle unter dem Namen, den ihr derProgrammierer gegeben hat.Vorteile:

1. Werte konnen viel leichter geandert werden, wenn sie einmal als Variable definiertwurden.

2. Taucht die selbe Variable mehrfach im Programm auf´, muss man sie nur einmalandern.

Variablen mussen immer mit ihrem Typ definiert werden, d.h. man muss dem Pro-gramm mitteilen, um welche Art von Variable es sich handelt: Ganze Zahl, Dezimalzahl,Buchstabe, ganzes Wort, logische Variable (TRUE / FALSE), Arrayvariable (Tabellevon Variablen), ...

Einer Variablen kann man sofort einen Wert zuweisen. Macht man dies nicht, weistdas Programm Zahlenvariablen den Wert 0 zu.

Wir kennen diese Befehle:

1 void t e s t f a h r t ( )2 {3 OnFwd(OUT A, 7 5 ) ; //Motor A vorwaerts Le i s tung 754 OnFwd(OUT C, 7 5 ) ; //Motor C vorwaerts Le i s tung 755 }

Wir wollen folgende Anderung: Der Powerwert fur den Motor soll nicht mehr mit einerZahl geschrieben werden, sondern mit einer Variablen. Da diese immer ganzzahlig seinmuss, definieren wir sie als Ganzzahl ’int’ (steht fur Integer). Als Name wahlen wir z.B.’schnell’ und ’langsam’.

Dann sieht unser Programm folgendermaßen aus:

1 //Schema der V a r i a b l e n d e f i n i t i o n : Typ Name=Anfangswert2 i n t s c h n e l l =75;3 i n t langsam =40;45 void t e s t f a h r t ( )

13

3.1. VARIABLE KAPITEL 3. VARIABLE UND KONSTANTEN

6 {7 OnFwd(OUT A, s c h n e l l ) ; //Motor A vorwaerts mit power s c h n e l l8 OnFwd(OUT C, s c h n e l l ) ; //Motor C vorwaerts mit power s c h n e l l9 Wait ( 2 0 0 0 ) ; //2 Sekunden fahren

10 Off (OUT AC) ; // be ide Motore anhalten11 OnRev(OUT AC, langsam ) ; / / be ide Motore langsam zurueck12 Wait ( 1 0 0 0 ) ; //1 Sekunde fahren13 Off (OUT AC) ; // be ide Motore aus14 }1516 task main ( )17 {18 t e s t f a h r t ( ) ;19 }

1. Die Definition einer Variablen erfolgt immer nach dem Schema: Typ Name, eventu-ell Anfangswert. In unserem Programm: ’int’ bedeutet Ganzzahlvariable, ’powerA’ist der Name der Variablen (ohne Umlaute), ’=75’ bedeutet, dass der Variablender Wert 75 zugewiesen wird.

2. Die Anweisung ’int schnell;’ wurde ebenfalls die Variable powerA definieren, ihraber den Anfangswert 0 zuweisen.

3. Der Name einer Variablen ist frei wahlbar, darf aber keine Sonderzeichen enthalten.Der gleiche Name darf nie doppelt vergeben werden, z.B. fur eine Variable und fureine Aufgabe.

4. Im Beispielprogramm werden die Variablen im Programmkopf definiert. Dies be-deutet, dass die Variablen im gesamten Programm gultig sind. Dies nennt man’globale Variablen’.

Hinweis:Dem Computer sind die Namen der Variablen gleichgultig, nur der Programmierer solltesich ihre Bedeutung merken konnen. Daher wahlt man die Namen der Variablen amBesten in einem Zusammenhang mit ihrer Bedeutung, dies nennt man sprechende Va-riablen.

Schlechtes Beispiel: int a=40;Dasselbe besser: int langsam=40;

14

3.2. AUFGABEN KAPITEL 3. VARIABLE UND KONSTANTEN

3.2 Aufgaben

1. Ersetze im oberen Programm auch die Wait-Zeit durch eine entsprechende Varia-ble.Hinweis:Der Name einer Variablen darf nur einmal definiert werden, d.h. fur die Wait-Zeitmuss man sich einen neuen Namen ausdenken.

2. Schreibe das Programm mit der Links-/Rechtskurve mit den Variablen ’schnell’und ’langsam’.

3. Schreibe die Variablendefinition in die void, im Programmkopf sollen sie nicht mehrauftauchen. Schreibe dann den Befehl langsam=40; in die task main(). Was pas-siert?Erklarung:Die Variable gilt nur in dem Bereich, in dem sie definiert wurde, aber nicht aus-serhalb davon. Dies nennt man ’lokale Variablen’.

15

3.3. KONSTANTE KAPITEL 3. VARIABLE UND KONSTANTEN

3.3 Konstante

Eine Konstante ist ahnlich wie eine Variable, aber ihr Wert darf nach der Definitionnicht mehr geandert werden.

Beispiel mit Variablen:

1 i n t w a r t e z e i t =100;2 i n t s c h n e l l =75;3 i n t langsam =40;45 task main ( )6 {7 whi l e (TRUE)8 {9 OnFwd(OUT A, s c h n e l l ) ; //Motor A vorwaerts Le i s tung 75

10 OnFwd(OUT C, langsam ) ; //Motor C vorwaerts Le i s tung 4011 Wait ( w a r t e z e i t ) ; //100 Mi l l i s ekunden fahren12 }13 }

Das selbe noch mal mit Konstanten.Beachte die Definition einer Konstanten:

#define NamederKonstanten Wert (KEIN ’;’)

1 #d e f i n e w a r t e z e i t 1002 #d e f i n e s c h n e l l 753 #d e f i n e langsam 4045 task main ( )6 {7 whi l e (TRUE)8 {9 OnFwd(OUT A, s c h n e l l ) ;

10 OnFwd(OUT C, langsam ) ;11 Wait ( w a r t e z e i t ) ;12 }13 }

Was bringts?Die Konstanten sind geschutzt gegen versehentliches Uberschreiben. D.h. folgendes

Beispiel wurde nicht funktionieren:

12 #d e f i n e w a r t e z e i t 1003 #d e f i n e s c h n e l l 75

16

3.3. KONSTANTE KAPITEL 3. VARIABLE UND KONSTANTEN

4 #d e f i n e langsam 4056 task main ( )7 {8 whi l e (TRUE)9 {

10 s c h n e l l =100;// e rzeugt Fehlermeldung ! ! !11 OnFwd(OUT A, s c h n e l l ) ;12 OnFwd(OUT C, langsam ) ;13 Wait ( w a r t e z e i t ) ;14 }15 }

Der Programmierer sieht schneller, dass diese Werte nicht geandert werden sollen.

Anwendung bei Motosteuerung

Im folgenden Beispiel werden die Motorausgange als Konstanten definiert. Dies hat denVorteil, dass der PRogrammierer sich nicht die Anschlusse merken muss (welcher Motorist auf welchem Anschluss...?)

1 #d e f i n e w a r t e z e i t 1002 #d e f i n e s c h n e l l 753 #d e f i n e langsam 404 #d e f i n e Motor Links OUT A5 #d e f i n e Motor Rechts OUT B6 #d e f i n e Motore OUT AB78 task main ( )9 {

10 whi l e (TRUE)11 {12 OnFwd( Motor Links , s c h n e l l ) ;13 OnFwd( Motor Rechts , langsam ) ;14 Wait ( w a r t e z e i t ) ;15 Off ( Motore ) ;16 }17 }

Der Vorteil hierbei ist, dass die Angabe ’Motor Links’ sehr viel aussagekraftiger ist alsdie Angabe ’OUT A’. Damit sieht man viel schneller, welche Kurve hier gefahren wird.

17

4 Schleifen

4.1 Wiederholung

1. Was ist eine void?

2. Wie werden voids aufgerufen?

3. Wo mussen voids stehen?

4. Woher kommt der Name einer void?

5. Was bringen voids?

6. Was macht eine Dauerschleife? Was ist ihr Nachteil?

7. Was bringen Variable?

8. Warum muss man bei einer Variablen immer ihren Typ angeben?

9. Was ist der Unterschied zwischen lokaler und globaler Variable?

4.2 Schleifen

Im vorigen Programm sind die Aufgaben endlos oft ausgefuhrt worden. Jetzt wollen wireine bestimmte Aufgabe nur dann ausfuhren, wenn eine bestimmte Bedingung erfullt ist.Beispielsweise soll das Programm 10 mal eine Rechts-/Linkskurve ausfuhren und dannanhalten.

Dazu muss das Programm:

1. Eine Ablaufbedingung prufen: Welcher Durchlauf?

2. Anhalten, wenn die Bedingung erreicht ist: Alle Durchlaufe abgearbeitet?

3. Eventuell die Nummer des Durchlaufes am Bildschirm anzeigen

Wir lernen zuerst, wie man am Bildschirm Zahlen oder Buchstaben ausgeben kann:

18

4.2. SCHLEIFEN KAPITEL 4. SCHLEIFEN

4.2.1 Ausgabe auf dem NXT-Bildschirm

Der NXT-Bildschirm kann ausgeben:- Zahlen- Buchstaben- Grafik (wird nicht behandelt)

Um Zahlen auszugeben, verwenden wir den Befehl NumOut(x-Position, y-Position,Zahl),um Text auszugeben, verwenden wir den Befehl TextOut(x-Position, y-Position,Text).

Die x-Position zahlt immer von links nach rechts, der NXT-Bildschirm ist 100 Pixelbreit.

Die y-Position zahlt entweder von oben nach unten, dann verwenden wir die Angabeder Zeilennummer oder von unten nach oben, dann verwenden wir die Angabe des y-Pixels, bei dem die Ausgabe beginnen soll. Der Bildschirm ist 64 Pixel hoch:

Abbildung 4.1: Positioniermoglichkeiten auf dem NXT-Bildschirm

Beispiele:

• Man mochte die Zahlvariable a ausgeben, a hat z.B. den Wert 20:NumOut(1,LCD LINE1,a);Ausgegeben wird in der ersten Zeile ganz links die Zahl 20. Die x-Position wirdhier in Bildschirmpixel, die y-Position in Zeilennummern von oben angegeben.

• Man mochte das Wort”otto“ ausgeben: TextOut(10,30,“otto“);

die x-Position ist 10 Pixel vom linken Rand aus nach rechts, die y-Position ist 30Pixel vom unteren Rand nach oben (Achtung: umgekehrt wie die Zeilennummern!)

19

4.2. SCHLEIFEN KAPITEL 4. SCHLEIFEN

Man kann die Ausgabe auch in die NXT-Bildschirmzeilen schreiben:TextOut(10,LCD LINE2,“otto“);

• Will man den NXT-Bildschirm vor der Ausgabe loschen, schreibt man:NumOut(1, LCD LINE1, a, true);Dies bedeutet: Zuerst wird der gesamte NXT-Bildschirm geloscht, dann wird dieAusgabe von NumOut geschrieben.

• Will man nur den Bildschirm loschen, kann man den Befehl ClearScreen() verwen-den.

• Moglicherweise zeigt das Programm uberhaupt nichts an. Dies liegt daran, dassdie Anzeige sofort wieder geloscht wird, wenn das Programm beendet ist.

Abhilfe: Schreibe ans Ende des Programms eine while(true)- Schleife.

1 void ausgabe ( )2 {3 TextOut (1 , LCD LINE1 , ”Otto mag Frieda ” , t rue ) ;4 TextOut (1 , LCD LINE2 , ” e i g e n t l i c h n i cht ” ) ;5 whi l e ( t rue ) ;6 }

• Kleine Aufgabe:Lass am NXT-Bildschirm ausgeben:

”Otto ist 20 Jahre alt und hat 5 Kinder“

Hinweis: Eine gemischte Ausgabe von Zahlen und Text in einem gemeinsamenBefehl ist nicht ohne Weiteres moglich.

4.2.2 while-Schleife mit Bedingung

In unseren bisherigen while-Schleifen stand in der Klammer nach while immer ’TRUE’.Dadurch wurde die while-Schleife zur Dauerschleife.

Jetzt schreiben wir in die Klammer eine Bedingung, die sich andern kann:

while(Bedingung)

{ //Beginn Schleifenkorper

//Befehle

//Befehle

} //Ende Schleifenkorper

//nachster Befehl

Wenn die Bedingung wahr ist, wird der nachfolgende Schleifenkorper abgearbeitet. Dannwird die Bedingung wieder gepruft: Ist sie wahr, wird der Schleifenkorper wieder ab-gearbeitet, ist sie falsch, bricht die Schleife ab. Das Programm macht dann mit dennachfolgenden Befehlen weiter.

Wir machen dazu folgendes Beispiel:

20

4.2. SCHLEIFEN KAPITEL 4. SCHLEIFEN

1. Wir definieren einen ’Schleifenzahler’, d.h. eine Zahl i, die nach jedem Durchlaufum 1 erhoht wird.

2. Das Programm pruft bei jedem Durchlauf, ob diese Zahl schon den vorgesehenenWert, z.B. 10, erreicht hat, indem es testet, ob die Aussage ’i < 10’ wahr oderfalsch ist.Ist die Aussage wahr, werden die Befehle im nachfolgenden Schleifenkorper aus-gefuhrt, ist die Bedingung falsch, wird der Schleifenkorper ubersprungen.

Abbildung 4.2: while-Schleife

1 i n t i =0; // S c h l e i f e n z a e h l e r d e f i n i e r e n und auf 0 s e t z en2 i n t anzahl =10;3 i n t power=75;45 void bewegen ( )6 {7 whi l e ( i<anzahl ) // Bedingung abfragen8 { // S c h l e i f e n k o r p e r beg innt9 TextOut (1 ,LCD LINE1 , ” Durchlauf : ” ) ;

10 NumOut(60 ,LCD LINE1 , i ) ; //Durchlaufnummer wird angeze i g t11 OnFwd(OUT AC, power ) ;12 Wait ( 5 0 0 ) ; // h i e r noch Var iab le e r s e t z e n !

21

4.2. SCHLEIFEN KAPITEL 4. SCHLEIFEN

13 Off (OUT AC) ;14 OnRev(OUT C, power ) ;15 Wait ( 5 0 0 ) ;16 Off (OUT C) ;17 i=i +1; // S c h l e i f e n z a e h l e r erhoehen , n i cht ve rge s s en !18 } //Ende S c h l e i f e n k o r p e r19 Off (OUT AC) ; // n a chs te r Be feh l nach S c h l e i f e20 }2122 task main ( )23 {24 bewegen ( ) ;25 }

Erklarungen:

1. in Zeile 1 wird ein Schleifenzahler definiert: int bedeutet ganze Zahl, i ist der Namedes Zahlers, 0 ist sein Anfangswert.

2. In Zeile 7 wird der Schleifenzahler mit dem Wert 10 verglichen.Liefert der Vergleich eine wahre Aussage, wird der nachfolgende Block von Zeile 8bis Zeile 18 ausgefuhrt.

3. In Zeile 17 wird der Schleifenzahler um 1 erhohtHinweis:Eine solche ’Gleichung’ liest man von rechts nach links, es ist keine Mathematik!

4. Ist der Block abgearbeitet, beginnt das Programm wieder mit dem Vergleich inZeile 4 und wiederholt das Ganze.

5. Irgendwann ist der Schleifenzahler i=10. Der Vergleich i < 10 ist dann eine falscheAussage, der Block wird nicht mehr abgearbeitetVergisst man den Schleifenzahler zu erhohen (Zeile 17), bleibt die Bedingung i < 10immer wahr, das Programm bricht niemals ab.

6. Das Programm springt an das Ende des Blocks in Zeile 17 und schaltet die Motoreab.

Aufgaben:

1. Andere die Zahl der Durchlaufe.

2. Fur den Befehl i=i+1; schreiben Programmierer haufig die Kurzform i++;, aus-probieren!

3. Lass die Schleife z.B. in Zweierschritten laufen.

4. Schreibe ein Programm mit ruckwarts laufender Schleife.

22

4.2. SCHLEIFEN KAPITEL 4. SCHLEIFEN

5. Schreibe eines der Programme vom letzten Kapitel so um, dass der Tribot bei-spielsweise 5 mal Links-/Rechtskurve fahrt.

6. Andere das Programm so ab, dass die Bedingung sofort falsch ist, die Schleife alsoniemals abgeabeitet wird.

7. Warum steuert man mit while(TRUE) eine Dauerschleife?

23

4.3. AUFGABEN: KAPITEL 4. SCHLEIFEN

4.2.3 for-Schleife

Bei einer for-Schleife wird im Schleifenkopf ein Zahler definiert, das Ende der Schleifeund die Erhohung oder Verringerung des Zahlers angegeben. Sie wird folgendermaßendefiniert, diesmal mit dem Schleifenzahler k:

1 void fahren ( )2 {3 f o r ( i n t k=0;k<10;k++) //k i s t der S c h l e i f e n z a h l e r !4 {5 l i nk sku rve ( ) ;6 r echt skurve ( ) ;7 }8 }

In diesem Beispiel wird 10 mal eine Linkskurve/Rechtskurve ausgefuhrt.’k++’ bedeutet: Erhohe k nach jedem Durchlauf. Es bedeutet dasselbe wie k=k+1.’k < 10’ bedeutet: Fuhre die Schleife so lange aus, wie k < 10 wahr ist.

4.3 Aufgaben:

1. Erganze das Programm, so dass im Display der Wert des Schleifenzahlers k ange-zeigt wird.

2. Wie wurde man nur einen einzigen Schleifendurchlauf (z.B. fur Testzwecke) erzeu-gen?

3. Schreibe foglendes Programm fur einen sanften Anfahrvorgang:In einer beliebigen Schleife (for, while) soll ein Schleifenzahler von 0 bis z.B. 125hochgezahlt werden. Dieser Schleifenzahler wird gleichzeitig als Powerwert an diebeiden Motore geschickt. D.h. das Fahrzeug rollt langsam an und erreicht erstallmahlich seine Endgeschwindigkeit.

4. In einer anschließenden zweiten Schleife soll das Fahrzeug wieder sanft abgebremstwerden. D.h. schreibe eine ruckwarts laufende Schleife, die bei 125 beginnt und bei0 stoppt.

24

5 Motorsteuerung

5.1 Befehle fur einen Motor

In diesem Kapitel werden die wichtigsten Befehle zur Ansteuerung von Motoren zusam-mengefasst.

5.1.1 Einen Motor bewegen

1 i n t powerA=50;2 void fahren ( )3 {4 OnFwd(OUT A, powerA ) ;5 Wait ( 5 0 0 ) ;6 }78 task main ( )9 {

10 fahren ( ) ;11 }

Erklare:Warum halt der Motor an, ohne dass ein Bremsbefehl vorhanden ist?Wichtig fur die spatere Programmierung:Der Programmierer muss die Kontrolle behalten, wann ein Motor anhalten soll!

Wir wollen den Motor nach einer Viertelsekunde anhalten. Dafur gibt es die Befehlecoast und off :

1 i n t powerA=100;2 void fahren ( )3 {4 OnFwd(OUT A, powerA ) ; / / Ruckwarts mit OnRev ( . . )5 Wait ( 2 5 0 ) ;6 Off (OUT A) ;7 }

25

5.1. BEFEHLE FUR EINEN MOTOR KAPITEL 5. MOTORSTEUERUNG

bzw. mit Coast

1 void fahren ( )2 {3 OnFwd(OUT A, powerA ) ;4 Wait ( 2 5 0 ) ;5 Coast (OUT A) ; / / Ro l l t nach 250 ms auch ohne Coast−Befeh l aus6 }

Ergebnis:Der Off -Befehl schaltet den Motor schlagartig ab, der Coast-Befehl lasst ihn ausrollen.Der Unterschied besteht darin, dass der coast-Befehl den Motorstrom abstellt, wahrendder Off -Befehl den Motor mit einem Haltestrom fixiert.

Probiere das folgende Programm:

1 i n t powerA=100;2 void fahren ( )3 {4 OnFwd(OUT A, powerA ) ;5 Wait ( 2 5 0 ) ;6 Off (OUT A) ;7 Wait ( 5 0 0 0 ) ;8 }9 task main ( )

10 {11 fahren ( ) ;12 }

Nach dem Anhalten des Motors spurt man 5 Sekunden lang einen deutlichen Widerstand,wenn man den Motor von Hand zu drehen versucht (Vorsicht, nicht beschadigen!).

Hinweis:Wahrend der Wait-Zeit kann das Programm nichts anderes machen als eine interneZahlschleife abzuarbeiten. Beispielsweise wenn der Tribot gegen eine Wand fahrt, reagierter moglicherweise zu spat auf einen Sensor (folgende Kapitel).

26

5.1. BEFEHLE FUR EINEN MOTOR KAPITEL 5. MOTORSTEUERUNG

5.1.2 Aufgabe

Probiere das folgende Programm aus:

1 i n t powerA=50;2 void fahren ( )3 {4 OnFwd(OUT A, powerA ) ;5 Wait ( 5 0 0 ) ;6 }78 task main ( )9 {

10 fahren ( ) ;11 whi l e (TRUE) ;12 }

Der Motor dreht sich weiter, obwohl die void fahren() langst abgearbeitet ist. Warum?

5.1.3 Winkelmesser abfragen

In jedem Motor ist ein Winkelmesser eingebaut, den man abfragen kann:MotorTachoCount(output) oderMotorRotationCount(output)

Der Winkelmesser wird beim Start des Programms auf 0◦ gesetzt.Drehe am Motor und beobachte die Anzeige des folgendes Programms:

1 i n t grad ;2 void winkel ( )3 {4 whi l e (TRUE)5 {6 grad=MotorTachoCount (OUT A) ;7 NumOut(1 ,LCD LINE1 , grad , t rue ) ;8 Wait ( 1 0 0 ) ;9 }

10 }

27

5.1. BEFEHLE FUR EINEN MOTOR KAPITEL 5. MOTORSTEUERUNG

1112 task main ( )13 {14 winkel ( ) ;15 }

Aufgabe:Schreibe ein Programm, welches einen Motor laufen lasst und standig den Winkelmes-

ser desselben Motors ausliest und anzeigt. Probiere beide Befehle (MotorTachoCountund MotorRotationCount) aus.

5.1.4 Um bestimmten Winkel drehen

Im nachsten Programm soll der Motor programmgesteuert um einen bestimmten Winkelgedreht werden. Dazu verwendet man den Befehl RotateMotor(outputs, pwr, angle):

1 void drehen ( )2 {3 RotateMotor (OUT A,50 , 360 ) ; //360 ◦−Drehung4 }56 task main ( )7 {8 repeat (10)9 {

10 drehen ( ) ;11 Wait ( 1 00 ) ; // macht kurze Unterbrechung12 }13 }

Fur diesen Befehl braucht man keine Wait-Anweisung. Das Programm dreht den Motorsolange, bis der Winkelmesser auf der Motorachse den gewunschten Winkel meldet undstellt dann den Motor, vermutlich mit einem Off-Befehl, ab.

Dies bedeutet aber, dass das Programm erst dann zum nachsten Befehl weitergeht,wenn der gewunschte Winkel tatsachlich erreicht ist. Wird der Motor aus irgend einemGrund blockiert, bleibt das Programm hangen!

Hinweis:Dieser Befehl steuert den Motor moglicherweise nicht sehr genau. Ist man bei einem Pro-jekt darauf angewiesen, um einen exakten Winkel zu drehen, sollte man besser der Be-fehl RotateMotorPID(Parameterliste) verwenden. Beispiele findet man im Benedetteli-Tutorial.

28

5.2. BEFEHLE FUR ZWEI MOTORE KAPITEL 5. MOTORSTEUERUNG

5.2 Befehle fur zwei Motore

5.2.1 Bewegen

Zwei Motore mit gleicher Leistung: OnFwd(OUT AB, 100);Zwei Motore mit unterschiedlicher Leistung:

1 i n t powerA=50;2 i n t powerB=100;3 void fahren ( )4 {5 OnFwd(OUT A, powerA ) ;6 OnFwd(OUT B, powerB ) ;7 Wait ( 1 0 0 0 ) ;8 }

Das gleiche Programm mit Konstanten zur besseren Ubersicht:

1 #d e f i n e Motor Links OUT A2 #d e f i n e Motor Rechts OUT B3 #d e f i n e w a r t e z e i t 10004 i n t powerA=50;5 i n t powerB=100;6 void fahren ( )7 {8 OnFwd( Motor Links , powerA ) ;9 OnFwd( Motor Links , powerB ) ;

10 Wait ( w a r t e z e i t ) ;11 }

5.2.2 Zwei Motore synchronisieren

Will man ein Fahrzeug, das zwei getrennt angetriebene Rader hat, geradeaus fahrenlassen, reicht es in der Regel nicht, beide Motore mit der gleichen Leistung zu steu-ern. Kleine Unterschiede, z.B. durch Reibung, lassen das Fahrzeug meistens eine Kurvefahren.

Daher kann man einen Befehl verwenden, der beide Motore synchronisiert: OnFwd-Reg(outputs, pwr, regmode) bzw. zum Ruckwartsfahren: OnRevReg(outputs, pwr, regmo-de); Fur den Parameter regmode gibt es die WerteOUT REGMODE IDLE, (tut nichts)OUT REGMODE SPEED oderOUT REGMODE SYNC.Probiere das folgende Benedetteli-Programm aus. Bremse in jedem Programmabschnitteinen der beiden Motore von Hand und beobachte, wie das Programm reagiert:

29

5.2. BEFEHLE FUR ZWEI MOTORE KAPITEL 5. MOTORSTEUERUNG

1 #d e f i n e motors OUT AC2 i n t power=50;3 void synchron ( )4 {5 OnFwdReg( motors , power ,OUT REGMODE SPEED) ;6 Wait ( 5 0 0 0 ) ;7 Off ( motors ) ;8 PlayTone ( 4 00 0 , 5 0 ) ;9 Wait ( 1 0 0 0 ) ;

10 OnFwdReg( motors , power ,OUT REGMODE SYNC) ;11 Wait ( 5 0 0 0 ) ;12 Off ( motors ) ;13 }1415 task main ( )16 {17 synchron ( ) ;18 }

Hinweise:

• OUT REGMODE SPEED versucht, die eingestellte Geschwindigkeit konstant zuhalten. Wird ein Rad gebremst, erhoht die Motorsteuerung die Stromstarke, wasaber nur bis zu einer gewissen Grenze moglich ist.

• OUT REGMODE SYNC versucht, die Bewegung der beiden Rader synchron zuhalten: Wird ein Rad gebremst, wird das andere ebenfalls langsamer.

• Die Synchronisation sorgt nur dafur, dass sich die beiden Rader gleichschnell dre-hen. Sind aber die beiden Rader unterschiedlich groß, fahrt das Fahrzeug trotzSynchronisation nicht gerade aus.

• Probiere aus, ob beides geht, namlich Synchronisation und Geschwindigkeitsrege-lung: OnFwdReg(OUT AC,power,OUT REGMODE SPEED+OUT REGMODE SYNC)

30

5.2. BEFEHLE FUR ZWEI MOTORE KAPITEL 5. MOTORSTEUERUNG

5.2.3 Aufgabe zum Nachdenken

Es soll ein Programm geschrieben werden, welches folgendes leistet: Der Tribot soll1 Sekunde geradeaus fahren, anschließend eine Sekunde lang eine Linkskurve machen.Folgende Losung wird angeboten:

1 void fahren ( )2 {3 OnFwd(OUT AB, 5 0 ) ;4 Wait ( 1 0 0 0 ) ;5 OnFwd(OUT A, 5 0 ) ;6 Wait ( 1 0 0 0 ) ;7 }8 task main ( )9 {

10 fahren ( ) ;11 }

Das Fahrzeug macht keine Kurve! Warum?

31

6 Sensoren Teil 1

6.1 Sensoren

Durch Sensoren kann der Tribot aus Signale seiner Umgebung reagieren.Fur alle Sensoren gilt:

• Jeder Sensor muss im Programm angemeldet werden.

• Bei jedem Sensor muss man den zugehorigen Abfragebefehl verwenden.

6.2 Taster

Der Tribot soll auf einen Taster reagieren. Der Taster ist ein Schalter, der vom Programmabgefragt werden kann, ob er offen/nicht gedruckt oder geschlossen/gedruckt ist.

Abbildung 6.1: Taster

Hinweis:In der Legoanleitung findet man eine Anleitung, wie man den Taster an den Tribotanbauen konnte.Wir schließen den Touchsensor auf einen der Eingange 1 bis 4, im Beispiel auf Eingang1

• Angemeldet wird der Touchsensor durch SetSensorTouch(IN 1); //Anmeldung aufEingang 1 Vergisst man die Anmeldung, wird der Sensor nicht erfasst, aber derCompiler meldet keinen Fehler.

• Um den Sensor abzufragen, kann man beispielsweise seinen Wert in der Ganzzahl-variable sensorwert speichern:sensorwert=Sensor(IN 1);

32

6.2. TASTER KAPITEL 6. SENSOREN TEIL 1

Das folgende Programm soll andauernd den Taster auslesen und seinen Wert (0 oder1) am Bildschirm ausgeben:

1 i n t sensorwert ;2 void s en so r ab f r ag en ( )3 {4 // Abfragen :5 sensorwert=Sensor ( IN 1 ) ; / / Sensorzustand spe i che rn6 NumOut(1 ,LCD LINE1 , sensorwert ) ; // Zustand ausgeben7 Wait ( 5 0 ) ;8 }9

10 task main ( )11 {12 //Anmelden :13 SetSensorTouch ( IN 1 ) ; / / Auf Eingang 1 l i e g t e in Taster14 whi l e (TRUE)15 {16 s en so r ab f r ag en ( ) ;17 }18 }

Erklarungen:

• In Zeile 11 wird der Taster im Programm angemeldet. Vergisst man die Anmeldung,wird er spater nicht gelesen.

• In der void wird der Zustand des Sensors gelesen und mit NumOut(...) - da es sichum eine Zahl handelt - ausgegeben.

• Anstelle von Sensor(IN 1) kann man auch schreiben: SENSOR 1

Dasselbe Programm, etwas ubersichtlicher durch Verwendung einer Konstanten:

1 #d e f i n e sensorvorne Sensor ( IN 1 )23 void s en so r ab f r ag en ( )4 {5 NumOut(1 , LCD LINE1 , sensorvorne ) ;6 Wait ( 5 0 ) ;7 }89 task main ( )

10 {11 //Anmelden12 SetSensorTouch ( IN 1 ) ; / / Auf Eingang 1 l i e g t e in Taster

33

6.3. AUFGABEN KAPITEL 6. SENSOREN TEIL 1

13 whi l e (TRUE)14 {15 s en so r ab f r ag en ( ) ;16 }17 }

6.3 Aufgaben

1. Merken: Was gibt der Taster aus, wenn er gedruckt/nicht gedruckt wird?

2. Schließe einen zweiten Touchsensor auf Anschluss 2 an und lass beide Taster-zustande ausgeben (Bildschirm: Zeilen 1 und 2).

34

6.4. IF: REAKTION AUF DEN SENSOR KAPITEL 6. SENSOREN TEIL 1

6.4 if: Reaktion auf den Sensor

Das Programm soll nun unterschiedlich reagieren, je nachdem, ob der Taster gedrucktist oder nicht: Ist er nicht gedruckt, soll das Fahrzeug vorwarts fahren, ist er gedruckt,soll es ruckwarts fahren.

Dazu verwenden wir die if-Abfrage. Der Block nach der if-Abfrage wird nur dann ab-gearbeitet, wenn die Bedingung in der Klammer WAHR ist. Ansonsten wird der gesamteBlock ubersprungen.

Man den Zustand des Tasters folgendermaßen ab:Ist er nicht gedruckt?

i f ( Sensor ( IN 1)==0) // n i cht gedrueckt{

OnFwd(OUT AB, power ) ; // vorwaerts fahrenWait ( 5 0 ) ;

}

Ist er gedruckt?

i f ( Sensor ( IN 1)==1) // gedrueckt{

Off (OUT AB) ;OnRev(OUT AB, power ) ; // rueckwaerts fahrenWait ( 3 0 0 ) ;

}

Dies braucht man beispielsweise, um einem Hindernis auszuweichen.

1 i n t power=50;2 void s en so r ab f r ag en ( )3 {4 NumOut(1 ,LCD LINE1 , Sensor ( IN 1 ) ) ; // Zustand ausgeben5 i f ( Sensor ( IN 1)==0) // n i cht gedrueckt6 {7 OnFwd(OUT AB, power ) ; // vorwaerts fahren8 Wait ( 5 0 ) ;9 }

10 i f ( Sensor ( IN 1)==1) // gedrueckt11 {12 Off (OUT AB) ;13 OnRev(OUT AB, power ) ; // rueckwaerts fahren14 Wait ( 3 0 0 ) ;15 }16 }17

35

6.5. VERGLEICHSBEFEHLE KAPITEL 6. SENSOREN TEIL 1

18 task main ( )19 {20 SetSensorTouch ( IN 1 ) ; / / Auf Eingang 1 l i e g t e in Taster21 whi l e (TRUE)22 {23 s en so r ab f r ag en ( ) ;24 }25 }

Erklarung:

• Ist der Sensor nicht gedruckt, fahrt der Motor vorwarts.

• Ist der Sensor gedruckt, fahrt der Motor ruckwarts, konnte also der Wand auswei-chen.

• Ein Vergleich if (wert==0) wird immer mit einem doppelten Gleichheitszeichengeschrieben.

• Nach der if-Abfrage steht KEIN ’;’Warum?Was passiert, wenn doch ein ’;’ geschrieben wird? Ausprobieren!

6.5 Vergleichsbefehle

Die Vergleichsbefehle lauten:

Operator Bedeutung

== equal to

< smaller than

<= smaller than or equal to

> larger than

>= larger than or equal to

!= not equal to

Achtung: Haufiger Anfangerfehler:

if (a=b)

{

//weitere Befehle

}

Was ist falsch????(Losung steht am Ende des Kapitels)

36

6.6. AUFGABEN KAPITEL 6. SENSOREN TEIL 1

6.6 Aufgaben

1. Nach dem Fahrbefehl steht ein Wait(50); Was passiert, wenn man diese Zeit we-sentlich erhoht? Lass den Tribot dazu gegen eine Wand fahren.

2. Unser Programm konnte dazu benutzt werden, dass der Tribot einem Hinder-nis ausweicht. Dazu muss er aber, wenn er vor eine Wand fahrt, nicht einfachzuruckfahren (Warum? Evtl. Ausprobieren).Statt dessen sollte er zuruckfahren, eine Kurve machen und dann wieder weiter-fahren.Aufgabe: Erzeuge ein passendes Flussdiagramm zu dieser Aufgabe, verwende dasProgramm PapDesigner.

3. Aufgabe: Der Tribot soll in der Aula an der Wand entlangfahren. Dazu wird an derrechten Seite (alternativ an der linken) ein Tastsensor angebaut. Beruhrt dieser dieWand, soll der Tribot eine kleine Korrektur von der Wand weg mit einer Linkskurvefahren:

Abbildung 6.2: An der Wand entlang fahren

37

6.7. IF - ELSE KAPITEL 6. SENSOREN TEIL 1

6.7 if - else

Soll das Programm zwischen zwei Blocken entscheiden, welcher von beiden ausgefuhrtwerden soll, verwendet man die Befehle if und else.

Beispiel:

1 i n t power=50;2 void s en so r ab f r ag en ( )3 {4 NumOut(1 ,LCD LINE1 , Sensor ( IN 1 ) ) ; // Zustand ausgeben5 i f ( Sensor ( IN 1)==0) // n i cht gedrueckt6 {7 Off (OUT AB) ;8 OnFwd(OUT AB, power ) ; // vorwaerts fahren9 Wait ( 5 0 ) ;

10 }11 e l s e // sons t12 {13 Off (OUT AB) ;14 OnRev(OUT AB, power ) ; // rueckwaerts fahren15 Wait ( 5 0 ) ;16 }17 }1819 task main ( )20 {21 SetSensorTouch ( IN 1 ) ; / / Auf Eingang 1 l i e g t e in Taster22 whi l e (TRUE)23 {24 s en so r ab f r ag en ( ) ;25 }26 }

38

6.8. AUF SENSOR WARTEN: UNTIL KAPITEL 6. SENSOREN TEIL 1

6.8 Auf Sensor warten: until

Die Abfrage, ob ein Sensor einen bestimmten Zustand besitzt, hat folgenden Nachteil:Wenn der Sensor in dem Augenblick, wenn das Programm ihn mit ’if’ abfragt, geradenoch nicht gedruckt ist, reagiert es eventuell nicht, obwohl er vielleicht einen kleinenAugenblick spater bereits gedruckt wurde. Jetzt aber reagiert das Programm bereitsnicht mehr darauf.

Statt dessen wollen wir das Programm so lange anhalten, bis der Sensor gedruckt wird.Probiere einmal folgendes kurze Programm aus

OnFwd(OUT AC, 7 5 ) ;u n t i l (SENSOR 1 == 1 ) ; / / Warten b i s Sensor gedr ucktOff (OUT AC) ;

Erganze es zu einem lauffahigen Programm: Der Motor lauft so lange, bis man den Tasterdruckt, erst dann wird der Ausschaltebefehl ausgefuhrt.

Aufgabe:Nach dem Start des Programms soll ein Motor erst dann loslaufen, wenn ein Tastergedruckt wird.

6.9 Zwei Sensoren abfragen

Schreibe ein Programm fur einen Tribot mit 2 Touchsensoren: Der eine ist fur Vorwarts/Ruckwartszustandig, der zweite soll den Powerwert andern, z.B. 40 oder 80. So konnte man schnelloder langsam, vorwarts oder ruckwarts fahren.

Dazu muss man gleichzeitig alle Sensorzustande im Programm abfragen:if ((Bedingung1) &&(Bedingung2)) ...Also:

if ((Sensor(IN_1)==0) && (Sensor(IN_2)==1))

{

OnFwd(OUT_AB,80);

}

if ((Sensor(IN_1)==1) && (Sensor(IN_2)==0))

{

OnRev(OUT_AB,40);

}

usw.

Wieviel Zustande konnen zwei Touchsensoren haben? Erganze das Programm, so dassalle Sensorzustande erfasst werden. Aufgaben:

1. Zum Nachdenken: Warum reicht es nicht aus, die beiden Sensoren getrennt abzu-fragen?

39

6.10. LOSUNG KAPITEL 6. SENSOREN TEIL 1

2. Die Touchsensoren sind links und rechts am Tribot angebaut: Beruhrt der Tribotmit dem linken Sensor die Wand, soll er zuruckfahren, eine kleine Kurve machenum von der Wand wegzukommen und dann weiterfahren. Entsprechendes gilt furden anderen Sensor. Damit konnte man erreichen, dass der Tribot durch eine Gassefahren kann ohne hangen zu bleiben.

3. Schreibe das Ausweichprogramm fur zwei Wande und zwei Sensoren noch einmal.Jetzt muss das Programm reagieren, wenn mindestens einer der beiden Sensorengedruckt wird. Wir brauchen also eine ODER-Verknupfung.ODER-Verknupfungen schreibt man:

until ((Sensor(IN_1)==1) || (Sensor(IN_2)==1) );

Off(OUT_AB);//erst mal anhalten

//jetzt abfragen, welcher Sensor ausgelost hat

6.10 Losung

a=b ist kein Vergleich, sondern eine Zuweisung: Der wert von b wird in a gespeichert.Ein Vergleich sieht so aus: if (a == b)

40

7 Sensoren Teil 2

7.1 Ultraschallsensor

Der Ultraschallsensor schickt einen kurzen Schallimpuls los und bestimmt aus der Zeit,wie lange der Puls unterwegs ist bis zu seiner Ruckkehr die Entfernung zum Hindernis.

Abbildung 7.1: Ultraschallsensor

Der Ultraschallsensor arbeitet nicht immer zuverlassig. Die Genauigkeit des Ultra-schallsensors hangt sehr vom Ladezustand des Akkus ab.

• Anmelden (z.B. Anschluss 1): SetSensorLowspeed (IN 1);

• Abfragen: abstand = SensorUS(IN 1);

Beispielprogramm:Der Ultraschallsensor wird dauernd ausgelesen und der gemessene Abstand angezeigt:

1 i n t abstand ;23 task main ( )4 {5 SetSensorLowspeed ( IN 1 ) ; / / U l t r a s c h a l l s e n s o r auf 1 anmelden6 whi l e (TRUE)7 {8 NumOut(1 ,LCD LINE1 , SensorUS ( IN 1 ) , t rue ) ; / / Direkt Ausgeben9 Wait ( 5 0 ) ;

10 }11 }

41

7.1. ULTRASCHALLSENSOR KAPITEL 7. SENSOREN TEIL 2

Hier ein Beispielprogramm (Benedetteli), bei dem der Tribot fahrt, solange der Ab-stand zu einem Hindernis großer als 15 cm ist:

1 i n t abst =15;2 i n t powerF=50;3 i n t powerR=100;45 void fahren ( )6 {7 whi l e ( t rue )8 {9 OnFwd(OUT AC, powerF ) ;

10 // fahren so lange Abstand g r o e s s e r a l s abst :11 whi l e ( SensorUS ( IN 4)>abst ) ;12 Off (OUT AC) ; / / Ausschalten , wenn Abstand zu k l e i n13 OnRev(OUT C, powerR ) ;14 Wait ( 8 0 0 ) ;15 }16 }1718 task main ( )19 {20 SetSensorLowspeed ( IN 4 ) ;21 fahren ( ) ;22 }

Beachte die Verwendung der while-Schleife in Zeile 11 ohne Schleifenkorper!Aufgaben:

1. Verwende im vorigen Programm anstelle von while den Befehl until

2. Finde die Zuordnung zwischen tatsachlicher Entfernung zum Hindernis und demangezeigten Sensorwert.

3. Schreibe ein Programm, welches anstelle vom while-Befehl den until -Befehl ver-wendet.

4. Schreibe ein Programm, z.B. mit einem while-Befehl, bei dem der Tribot fahrt undimmer wieder einen Ton erzeugt, wenn der Abstand zu einer Wand großer als einbestimmter Wert, z.B. 20 cm, ist. Ist der Abstand kleiner, soll irgend etwas anderespassieren.

5. Schreibe ein Programm, bei dem der Tribot einem Hindernis ausweichen kann:Nahert er sich einer Wand, soll er beispielsweise versuchen, dieser mit einer Links-oder Rechtskurve auszuweichen.

42

7.1. ULTRASCHALLSENSOR KAPITEL 7. SENSOREN TEIL 2

6. Erweitere dein Programm: Der Tribot soll einen Ton abspielen. Je naher der Tribotan einem Hindernis ist, desto hoher soll der Ton sein.

Abbildung 7.2: Hindernis ausweichen mit Ultraschallsensor

7. Schreibe ein Programm, bei dem der Tribot durch ein Labyrint fahrt: Er sollselbstandig erkennen, wenn eine Wand vor ihm ist und dann herausfinden, woder Weg weitergeht, ob nach links oder nach rechts.

8. Sehr fortgeschrittenes Labyrintprogramm: Der Ultraschallsensor wird auf einenMotor montiert. Trifft der Tribot auf ein Hindernis, kann der Motor den Sensornach rechts oder links drehen und damit nach einem freien Weg suchen.

9. Noch besser: Am Tribot werden links und rechts zusatzlich Touchsensoren ange-bracht, so dass dann, wenn der Tribot die Wand beruhrt, darauf reagiert werdenkann.

43

7.2. LICHTSENSOR KAPITEL 7. SENSOREN TEIL 2

7.2 Lichtsensor

Der Lichtsensor misst die Lichtmenge, die auf ihn einstrahlt, er benutzt eine eigene,abschaltbare Lichtquelle. D.h. er misst dann die reflektierte Lichtmenge. Die Werte liegenzwischen 0 und 100.

Abbildung 7.3: Lichtsensor

• Anmelden (hier auf Anschluss 1): SetSensorLight(IN 1);

• Abfragen: wert=Sensor(IN 1);

Der gemessene Wert der reflektierten Strahlung ist abhangig von:

• Farbe

• Form

• Oberflache

• Entfernung

des Gegenstandes. Ein weit entfernter heller Gegenstand kann den gleichen Helligkeits-wert aufweisesn wie ein naher dunkler. Ausserdem konnen Programme, die bei Regenwet-ter funktionieren, bei schonem Wetter moglicherweise nicht mehr richtig funktionieren.

Beispiel:

1 i n t wert ;23 void s en so r ab f r ag en ( )4 {5 wert=Sensor ( IN 1 ) ; // Zustand abfragen6 NumOut(1 ,LCD LINE1 , wert ) ; // Zustand ausgeben7 Wait ( 5 0 ) ;8 }9

44

7.2. LICHTSENSOR KAPITEL 7. SENSOREN TEIL 2

10 task main ( )11 {12 SetSensorL ight ( IN 1 ) ; / / Auf Eingang 1 l i e g t e in L i ch t s en so r13 whi l e (TRUE)14 {15 s en so r ab f r ag en ( ) ;16 }17 }

Hinweis:In diesem Programm wird der Sensorwert in einer Variablen gespeichert und diese dannausgegeben. Dafur werden einige Millisekunden verbraucht. In zeitkritischen Program-men konnte es durchaus erforderlich sein, darauf zu verzichten.

Aufgaben:

1. Untersuche die Ausgabewerte des Lichtsensors, wenn er gegen eine reflektierendeFlache gehalten wird. Untersuche auch unterschiedliche Flachen: helle, dunkle,rauhe, glatte Flachen, Papier, Holz, ... .

2. Schreibe ein Programm, welches die Motore entweder vorwarts oder ruckwartslaufen lasst, je nachdem, ob der Wert des Lichtsensors oberhalb oder unterhalbeiner bestimmten Schwelle liegt.

Hinweis:Im Programm sollte man in der if-Abfrage auf keinen Fall auf Gleichheit prufen(warum?), sondern auf kleiner oder großer.

• Schlecht: if (Sensor(IN 1)==wert ...

• Besser: if (Sensor(IN 1) < wert ...

die Zahlenwerte fur ’wert’ muss man ausprobieren.

3. Montiere den Lichtsensor mit Blick nach oben. Wird er beleuchtet, soll das Autoschneller fahren.Andere Moglichkeit:Der Lichtsensor blickt nach unten. Je heller der Boden, desto schneller soll derTribot fahren.

4. Schreibe ein Programm, bei dem der Tribot gegen eine Wand fahrt und dabeidauernd den Lichtsensor abfragt. Der Tribot soll einen bestimmten Abstand zurWand nicht unterschreiten, stattdessen soll er eine Ruckwartskurve fahren unddann geradeaus weiter fahren.Problem: Dieses Programm ist stark von der Umgebungshelligkeit abhangig.

5. Baue den Lichtsensor mit Blick nach unten an den Tribot. Schreibe ein Programm,welches verhindert, dass der Tribot vom Tisch fallt.Vorsicht:

45

7.2. LICHTSENSOR KAPITEL 7. SENSOREN TEIL 2

Fallt der Tribot vom Tisch, ist der NXT vermutlich hin. Daher mit einem Brettauf dem Boden ausprobieren.

6. Falls zwei Lichtsensoren verfugbar: Baue an den Tribot einen zweiten Lichtsensor,beide mit Blick nach oben. Leuchtet man den linken Sensor an, soll er eine Links-kurve, beim rechten Sensor eine Rechtskurve fahren.Fur diese Aufgabe kann man die im Lichtsensor eingebaute LED abschalten:

// S e n s o r l i c h t abscha l t enSetSensorType ( IN 1 , IN TYPE LIGHT INACTIVE ) ;// Sensor max . 100\% :SetSensorMode ( IN 1 ,IN MODE PCTFULLSCALE) ;// e r f o r d e r l i c h , wenn der Senso r s ta tu s ge andert wurde :ResetSensor ( IN 1 ) ;

Bei dieser Aufgabe muss man unbedingt die Umgebungshelligkeit mit einbziehen.Beispielsweise konnte man, bevor das eigentliche Programm ablafut, eine void vor-schalten, die zunachst die Grundhelligkeit misst und in einer Variablen speichert.Leuchtet man mit einer Taschenlampe auf die Sensoren, so muss das Signal bei-spielsweise 10 Einheiten hoher liegen, damit es erkannt wird.

Ist es im Raum sehr hell, kann moglicherweise eine Taschenlampe nicht mehr er-kannt werden.

7.2.1 LineFollower

Wir schreiben ein Programm, welches den Tribot an einer Schwarz-Weiss-Kante entlangfahren lasst, der Lichtsensor muss mit Blick nach unten montiert sein. Dazu verwendenwir die Technik des PID-Regelung.Hinweis:Die folgenden Informationen sind aushttp://www.inpharmix.com/jps/PID Controller For Lego Mindstorms Robots.htmloder unterhttp://lehrer.ikg-le.de/czech/NWT10/Anleitungen/PID/

P-Regler

Der Tribot soll entlang der Schwarz-Weiss-Kante fahren. Das Programm misst zuerstden Helligkeitswert fur weiss, dann den Helligkeitswert fur schwarz. Aus diesen beidenbildet es dann den Mittelwert, welcher fur die Fahrt unseren Sollwert der Kante darstellt.Beispiel: hellwert=50, dunkelwert = 40, mitte=45 (=Kante).

Aufgabe

Schreibe eine void mitteBestimmen(), welche die jeweiligen Werte misst und den Mittel-wert berechnet und in der Variablen mitte speichert. Der Lichtsensor wird in der task

46

7.2. LICHTSENSOR KAPITEL 7. SENSOREN TEIL 2

main() angemeldet, die void vor dem eigentlichen Programm aufgerufen. Baue eine Pau-se und ein akustisches Signal zwischen den Messungen ein, damit man den Tribot vomhellen auf den dunklen Untergrund stellen kann.

Als nachstes berechnen wir unsere Powerwerte fur die Motorsteuerung. Je weiter sichder Tribot von der Kante entfernt, desto starker soll korrigiert werden. Bei einem P-Regler ist die Fehlerkorrektur proportional zur Abweichung vom Sollwert.

Beispiel:Sei e der Fehler zwischen Soll- und Istwert des Sensors. Er wird gemessen durch: e=Sensor(IN 1)-mitte;

Dann werden die Motore folgendenmaßen angesteuert:

e=Sensor ( IN 1)−mitte ;powerA=power−P∗e ;powerC=power+P∗e ;OnFwd(OUT A, powerA ) ;OnFwd(OUT C, powerC ) ;

P ist eine Proportionalitatskonstante, der Wert muss ausprobiert werden. Die Variablepower ist die konstante Motorleistung, z.B. power=40;

Beispiel:Der Sollwert der Helligkeit sei 45. Der Messwert des Sensors sei 50. Die Abweichung vomSollwert ist dann e = 50-45=5. Ist die Proportionaliatskonstante P=2, dann wird dereine Motor mit 40+2*5=50, der andere mit 40-2*5=30 angesteuert. Der Tribot machtalso eine Kurve, die umso starker ist, je weiter er von der Kante entfernt ist.

Aufgabe

Schreibe eine void fahren(), welche in einer Dauerschleife den Tribot steuert wie abge-druckt. Die Feinabstimmung der Werte fur power, Wait-Time und P muss man auspro-bieren. Insbesondere muss man sicherstellen, dass der korrigierte Powerwert nicht uberdie zulassigen Grenzen hinaus geht.

47

7.2. LICHTSENSOR KAPITEL 7. SENSOREN TEIL 2

1 #d e f i n e motor l i nk s OUT A2 #d e f i n e motor rechts OUT C3 i n t power=40;4 i n t powerA , powerC ;5 i n t e ;6 f l o a t P=2.0;7 i n t mitte ;89

10 void mitte best immen ( )11 {12 /∗ h i e r f e h l t Code um13 mitte zu bestimmen14 ∗/15 }16 void fahren ( )17 {18 whi l e (TRUE)19 {20 e=Sensor ( IN 1)−mitte ; // Erro rS igna l21 powerA=power−P∗e ;22 powerC=power+P∗e ;23 OnFwd( motor l inks , powerA ) ;24 OnFwd( motor rechts , powerC ) ;25 Wait ( 5 ) ;26 }27 }2829 task main ( )30 {31 mitte bestimmen ( ) ;32 fahren ( ) ;33 }

Aufgaben

• Probiere unterschiedliche Werte fur P aus, solange, bis der Tribot sicher an derKante entlang fahrt.

• Eventuell braucht man Dezimalwerte, wenn z.B. P=1 zu klein und P=2 zu großist. Definiere den Wert P beispielsweise durch die Dezimalvariable float P=1.5;

48

7.2. LICHTSENSOR KAPITEL 7. SENSOREN TEIL 2

Aufgabe fur fortgeschrittene Bastler

Der Tribot soll beiden Kanten einer schwarzen Linie folgen, d.h. man braucht zwei Licht-sensoren und muss so programmieren, dass beispielsweise immer derjenige Sensor dieSteuerparameter liefert, der in den schwarzen Bereich eintaucht.

7.2.2 PID-Steuerung

Moglicherweise ist das Ergebnis noch nicht sehr zufrieden stellend, beipielsweise pendeltder Tribot stark um die Kante. Dieses Verhalten lasst sich verbessern, wenn man einevollstandige PID-Steuerung programmiert. Lies daher den oben angegebenen Artikeluber die PID-Steuerung uber den I- und den D-Teil der Steuerung und baue die weiterenKorrekturen fur Dein Programm ein. Damit sollte sich eine perfekte Kantenverfolgungerreichen lassen.

49

7.3. MIKROFON KAPITEL 7. SENSOREN TEIL 2

7.3 Mikrofon

Das Mikrofon ist ein reiner Lautstarkemesser, es ist nicht moglich, die empfangenenSchwingungen in irgend einer Weise zu untersuchen. Laut LEGO gibt das Mikrofon dieLautstarke in Dezibel zuruck, die Werte liegen zwischen 0 und 100.

Abbildung 7.4: Mikrofon

• Anmelden (hier auf Eingang 2): SetSensorSound(IN 2);.

• Abfragen wert= SENSOR 2 oder wert=Sensor(IN 2)

Beispielprogramm (Benedetteli):

1 i n t s chwe l l e =60;2 task main ( )3 {4 SetSensorSound ( IN 2 ) ;5 whi l e ( t rue )6 {7 u n t i l ( Sensor ( IN 2)> s chwe l l e ) ;8 OnFwd(OUT AC, 7 5 ) ;9 Wait ( 3 0 0 ) ;

10 u n t i l ( Sensor ( IN 2)> s chwe l l e ) ;11 Off (OUT AC) ;12 Wait ( 3 0 0 ) ;13 }14 }

Aufgaben:

1. Schreibe ein Programm, so dass das Fahrzeug bei zunehmender Lautstarke schnel-ler wird.

2. Schreibe ein Programm, so dass das Fahrzeug bei zunehmender Lautstarke eineLinkskurve, bei geringerer Lautstarke eine Rechtskurve fahrt.

3. Schreibe das Programm so um, dass fur das Mikrofon eine Konstante definiertwird.

50

8 Parallel laufende Prozesse

8.1 Programm

In diesem Teil lernen wir die Parallelverarbeitung von Aufgaben kennen, d.h. das Pro-gramm soll mehrere Aufgaben gleichzeitig erledigen.

8.1.1 Ein unabhangiger Prozess

Aufgabe:Der Roboter soll wahrend seiner Fahrt standig den Winkelmesser auf Ausgang B ausle-sen, wahrend in einer void der Winkelmesserwert irgendwie verarbeitet wird.

1 /∗Programm s t a r t e t e in e unabhangig l au f ende2 Task : wm, welche s t and ig den Winkelmesser a u s l i e s t3 d i e void tuwas ( ) wird durch e ine4 D a u e r s c h l e i f e ange s t eue r t und g r e i f t auf d i e5 Winkelmessung zu6 ∗/78 i n t i ;9 i n t w;

10 task wm( )11 {12 whi l e (TRUE)13 {14 w= MotorTachoCount (OUT B) ;15 NumOut(1 ,LCD LINE1 ,w) ;16 }17 }1819 void tuwas ( )20 {21 i ++;22 NumOut(1 ,LCD LINE3 , i ) ;23 i f (w>100) TextOut (1 ,LCD LINE2 , ” oberhalb ” ) ;24 i f (w<100) TextOut (1 ,LCD LINE2 , ” unterha lb ” ) ;25 Wait ( 5 0 0 ) ;26 }27

51

8.1. PROGRAMM KAPITEL 8. PARALLEL LAUFENDE PROZESSE

28 task main ( )29 {30 StartTask (wm) ; // l a u f t unabhangig31 whi l e (TRUE)32 {33 tuwas ( ) ;34 }35 }

8.1.2 Zwei Zahlprozesse

Wir schreiben ein Programm, wo zwei verschiedene Variablen in unterschiedlicher Ge-schwindigkeit hochgezahlt werden sollen. Die beiden Teilprogramme stellen jeweils eine’task’ dar, welche beide gleichzeitig abgearbeitet werden sollen. In beiden tasks steckteine while(TRUE)-Schleife, damit die task nicht sofort aufhort zu arbeiten.

Hier das Beispielprogramm:

1 i n t i , j ;2 task count1 ( )3 {4 whi l e (TRUE)5 {6 NumOut(1 ,LCD LINE1 , i ) ;7 i ++;8 Wait ( 2 0 0 ) ;9 }

10 }1112 task count2 ( )13 {14 whi l e (TRUE)15 {16 NumOut(1 ,LCD LINE2 , j ) ;17 j ++;18 Wait ( 5 0 0 ) ;19 }20 }2122 task main ( )23 {24 StartTask ( count1 ) ;25 StartTask ( count2 ) ;26 }

52

8.1. PROGRAMM KAPITEL 8. PARALLEL LAUFENDE PROZESSE

Aufgabe:Andere das Programm so ab, dass gleichzeitig zwei verschiedene Motore mit unter-schiedlicher Geschwindigkeit fahren.

Etwas schwieriger ist es, eine laufende task anzuhalten. Hier ein Beispiel:

1 i n t i , j ;23 task count1 ( )4 {5 whi l e (TRUE)6 {7 NumOut(1 ,LCD LINE1 , i ) ;8 i ++;9 Wait ( 2 0 0 ) ;

10 }11 }1213 task count2 ( )14 {15 whi l e (TRUE)16 {17 NumOut(1 ,LCD LINE2 , j ) ;18 j ++;19 Wait ( 5 0 0 ) ;20 }21 }2223 task c o n t r o l ( )24 {25 Wait ( 1 0 0 0 ) ;26 StopTask ( count1 ) ;27 TextOut (1 ,LCD LINE1 , ” Ende task count1 ” ) ;28 }2930 task main ( )31 {32 StartTask ( count1 ) ;33 StartTask ( count2 ) ;34 StartTask ( c o n t r o l ) ;35 // Precedes ( count1 , count2 , c o n t r o l ) ; // da s s e l b e wie StartTask . . .36 }

Hinweis:Falls die Funktion StopTask(task) nicht funktionieren sollte, kann man einen firmware-Update versuchen, ohne Garantie.

53

8.1. PROGRAMM KAPITEL 8. PARALLEL LAUFENDE PROZESSE

Wesentlich schwieriger ist es, einen Zahlprozess anzuhalten, wenn ein bestimmterZahlerstand erreicht wurde. Uberlege eine Losung!

Im folgenden Beispiel wird eine task aufgerufen. Wenn diese fertig ist, ruft sie diezweite task auf:

1 i n t i , j ;23 task count2 ( )4 {5 whi l e (TRUE)6 {7 NumOut(1 ,LCD LINE2 , j ) ;8 j++;9 Wait ( 5 0 0 ) ;

10 }11 }1213 task count1 ( )14 {15 f o r ( i n t i =0; i <10; i++ )16 {17 NumOut(1 ,LCD LINE1 , i ) ;18 Wait ( 2 0 0 ) ;19 }20 ExitTo ( count2 ) ; / / muss we i t e r oben im Programm stehen21 }222324 task main ( )25 {26 StartTask ( count1 ) ;27 }

8.1.3 Fahren und Sensor abfragen

Ein Auto soll im Viereck fahren. Stoßt es auf ein Hindernis (d.h. der Touchsensor istgedruckt), soll das Auto ein Ausweichmanover fahren. Dazu soll das Programm zweiAufgaben gleichzeitig erledigen:

• Im Viereck fahren.

• Touchsensor abfragen, ob ein Hindernis im Weg steht und dann entsprechend rea-gieren.

Aber:Beide tasks wollen dieselben Motore ansteuern!

54

8.1. PROGRAMM KAPITEL 8. PARALLEL LAUFENDE PROZESSE

1 /∗ Idee nach B e n e d e t t e l i p40 : P a r a l l e l v e r a r b e i t u n g von Aufgaben2 e i n i g e s ver andert3 Sensor f r e i : s o l l Quadrat fahren ,4 Sensor gedr uckt : zuruck , drehen5 ∗/67 mutex moveMutex ;8 i n t power =50;9

10 //Methode zum Tonerzeugen11 void hupen ( i n t f r equenz )12 {13 PlayTone ( frequenz , 1 00 ) ;14 Wait ( 1 0 0 ) ;15 }1617 //Task bewegt Auto im Viereck18 task move square ( )19 {20 whi l e ( t rue ) // Sensor f r e i21 {22 Acquire (moveMutex ) ; //Macht an s i c h r e i ß e n23 OnFwdReg(OUT AC, power ,OUT REGMODE SYNC) ; //Motoren synchron24 Wait ( 8 5 0 ) ; // Ze i t ausprob ie ren25 Coast (OUT AC) ; // be ide bremsen26 OnRev(OUT C, power ) ; // e i n e r r uckwarts27 Wait ( 8 5 0 ) ; // Ze i t ausprob ie ren28 Coast (OUT C) ; // bremsen29 Release (moveMutex ) ; // f r e i g e b e n30 } //Ende whi l e31 } //Ende task3233 //Task pr u f t Sensorzustand und r e a g i e r t34 task c h e c k s e n s o r s ( )35 {36 whi l e ( t rue )37 {38 i f (SENSOR 1 == 1) // Taster gedr uckt : Hindern i s39 {40 Acquire (moveMutex ) ; //Macht an s i c h r e i ß e n41 hupen ( 2 5 0 ) ; // S igna l ton zur Kontro l l e42 OnRevReg(OUT AC, power ,OUT REGMODE SYNC) ;43 Wait ( 8 5 0 ) ; // ZEit ausprob ie ren

55

8.1. PROGRAMM KAPITEL 8. PARALLEL LAUFENDE PROZESSE

44 Coast (OUT AC) ; // bremsen45 OnFwd(OUT A, power ) ; //A vor46 Wait ( 8 5 0 ) ; // Ze i t ausprob ie ren47 Coast (OUT A) ; // bremsen48 hupen ( 4 5 0 ) ; // S igna l ton 450 Hz zur Kontro l l e49 Release (moveMutex ) ; // f r e i g e b e n50 } //Ende i f51 } //Ende whi l e52 } //Ende task5354 task main ( )55 {56 SetSensorTouch ( IN 1 ) ; // Touchsensor anmelden57 Precedes ( check senso r s , move square ) ; / / be ide Aufgaben p a r a l l e l58 }

Hinweis:Probiere die beiden Tasks getrennt aus, d.h. rufe jede Task einzeln mit dem BefehlPrecedes(task) auf, um zu testen, daß sie einzeln fuktionieren.

Erklarungen:

1. Aus einer task heraus wird die Methode void hupen(int frequenz) aufgerufen. Da-mit kann man in unterschiedlichen Tonhohen hupen, muss aber nur eine Methodeschreiben.

2. Der Aufruf der tasks erfolgt in der task main mit dem Befehl Precedes(task1, task2,....). Die beiden Tasks konnen (theoretisch) gleichzeitig abgearbeitet werden. Mitvoids geht dies nicht.

3. Man kann auch ein Programm schreiben, bei dem mit Precedes nur eine taskaufgerufen wird. Diese task kann dann ihrerseits eine zweite task aufrufen mitExitTo(weitere task)

4. Da beide Tasks die Motore ansteuern, ware es fatal, wenn eine Task den Befehlzum Vorwartsfahren, die andere zum Ruckwartsfahren geben wurde.

Um dies zu verhindern, soll jede task fur sich die Hoheit uber bestimmte gemeinsa-me Resourcen wie Motore oder Display erhalten konnen. Dazu definiert man eineVariable vom Typ mutex (mutual exclusion). Bei unserem Programm heisst siemoveMutex, wie immer ist der Name beliebig.

Acquire(motorMutex); // make sure we have exclusive access

// Hier die exclusiven Befehle

Release(motorMutex);

5. Am Beginn jeder Task steht der Befehl Acquire(moveMutex);. Dies bedeutet, dassdie andere Task keinen Zutritt mehr zu den nachfolgenden Befehlen hat. In unseremFall sind dies die Befehle zur Ansteuerung der Motore.

56

8.2. AUFGABEN KAPITEL 8. PARALLEL LAUFENDE PROZESSE

6. Am Ende jeder Task steht der Freigabe-Befehl Release(moveMutex); Gibt man dieBefehle versehentlich nicht frei, hat eine andere Task keinen Zugriff darauf.

8.2 Aufgaben

1. Das obige Programm hat den Nachteil, dass das Auto auf ein Hindernis auffahrenmuss, um den Touchsensor auszulosen. Schreibe daher ein verbessertes Programmfur ein verbessertes Auto, welches den Ultraschallsensor verwendet. Registriert die-ser eine Wand, soll das Auto zuruckfahren, eine 90◦-Kehre fahren und weiterfahren.Die Aufgabe sollen wieder in zwei getrennten Tasks erledigt werden.

2. Verbessere das Programm folgendermaßen: Das Auto soll durch einen Parcoursfahren. Registriert es eine Wand, soll es selbstandig die Richtung herausfinden, inwelche es weiterkommt, also eine Kurve links oder nach rechts fahren.

3. Beim vorigen Programm kann es passieren, dass das Auto schrag in den Parcoursfahrt und irgend wann an der Wand hangen bleibt.

Montiere daher links und rechts die beiden Touchsensoren mit einem geeignetenGelenk und einem Hebel. Fahrt das Auto schrag gegen die Wand, soll es einekleine Richtungskorrektur durchfuhren, je nachdem, auf welcher Seite es die Wandberuhrt.

57

9 Kommunikation zwischen NXTs

Wir lernen die Kommunikation zwischen zwei NXTs kennen. Dies ist z.B. nutzlich beiFernsteuerungen. Moglich ist die Kommunikation per Kabel oder per Bluetooth.

9.1 Mit Kabel

Man kann zwei NXTs uber Kabel verbinden, dazu verwendet man an beiden den Sensor-Eingang Nr. 4. Hier zwei Kommunikationsprogramme, eines fur den Master, das anderefur den Slave. Sie stammen von J. Hansen und wurden von mir leicht abgeandert. DasMasterprogramm schickt einen String, der aus einem Wort und einer Zahl besteht, welchehochgezahlt wird, an den Slave.

Master:

1 i n t i =0;2 byte mlen ;3 s t r i n g msg ;45 void send ( s t r i n g msg) // Botscha f t uebergeben an void6 {7 mlen = ArrayLen (msg ) ;8 // This method w r i t e s count bytes o f data to the9 //High Speed output

10 // b u f f e r at the s p e c i f i e d o f f s e t :11 SetHSOutputBuffer (0 , mlen , msg ) ;12 SetHSOutputBufferOutPtr ( 0 ) ;13 SetHSOutputBufferInPtr ( mlen ) ;14 SetHSState (HS SEND DATA) ;15 SetHSFlags (HS UPDATE) ; // send i t16 }1718 void WaitForMessageToBeSent ( )19 {20 whi l e ( HSOutputBufferOutPtr ( ) < HSOutputBufferInPtr ( ) )21 Wait ( 1 ) ;22 }2324 task main ( )25 {

58

9.1. MIT KABEL KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

26 // Anschluss 4 zur Kommunikation i n i t i a l i s i e r e n :27 SetSensorType ( IN 4 , SENSOR TYPE HIGHSPEED) ;28 SetHSState ( HS INITIALISE ) ;29 SetHSFlags (HS UPDATE) ;30 Wait ( 1 0 ) ;31 whi l e ( t rue )32 {33 msg = ” goofy ” ;34 msg += NumToStr( i ) ;35 TextOut (1 ,LCD LINE1 , msg ) ;36 send (msg ) ;37 WaitForMessageToBeSent ( ) ;38 i ++;39 Wait ( 5 0 ) ;40 }41 }

Das Empfangerprogramm am Slave:

1 byte mlen ;2 s t r i n g b u f f e r ;34 task main ( )5 {6 // Anschluss 4 zur Kommunikation i n i t i a l i s i e r e n :7 SetSensorType ( IN 4 , SENSOR TYPE HIGHSPEED) ;8 SetHSState ( HS INITIALISE ) ;9 SetHSFlags (HS UPDATE) ;

10 // s t a r t with empty input b u f f e r11 SetHSInputBufferInPtr ( 0 ) ;12 SetHSInputBufferOutPtr ( 0 ) ;1314 Wait ( 1 0 ) ;15 whi l e ( t rue )16 {17 // wait f o r a message to a r r i v e .18 whi l e ( mlen == 0)19 //wenn n i cht mehr 0 , d , h , Nachricht ,20 //dann wird Buf f e rPo in t e r g e l e s e n21 // en th a l t d i e Lange der Nachricht22 mlen = HSInputBufferInPtr ( ) ;23 // This method reads mlen bytes o f data from the24 //High Speed input25 // b u f f e r and w r i t e s i t26 // to the b u f f e r provided :

59

9.1. MIT KABEL KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

27 GetHSInputBuffer (0 , mlen , b u f f e r ) ;28 // c l e a r the incoming b u f f e r29 SetHSInputBufferInPtr ( 0 ) ;30 // d i s p l ay message31 TextOut (0 , LCD LINE1 , b u f f e r ) ;32 }33 }

Diese beiden Programme kann man als Vorlage fur eigene Kommunikationsexperimen-te verwenden.

60

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

9.2 Mit Bluetooth

Abbildung 9.1: NXT-Bluetooth-Schema

Bei Bluetooth brauchen wir einen Master und bis maximal 3 Slaves. Die Slaves habendie Kommunikationsnummern 1, 2, 3. Der Master hat immer die Kommunikationsnum-mer 0. Die maximale Lange einer Nachricht betragt 58 Zeichen.

Die Kommunikation wird immer vom Master angestoßen, die Slaves konnen nur ant-worten und konnen untereinander nicht kommunizieren.

9.2.1 Kontakt herstellen

Den Bluetooth-Kontakt muss man selbst (ohne Programm) folgendermaßen herstellen:

1. Master und Slave einschalten, an beiden Bluetooth einschalten

2. Am Master (keinesfalls am Slave) auf Bluetooth → Search gehen. Nach einigerZeit werden alle Bluetoothgerate aufgelistet

3. Den gewunschten Slave auswahlen, Kontakt aufnehmen (in der Regel auf Kanal1).Eventuell wird am Master und am Slave ein Passwort verlangt, verwende immer’1234’ auf beiden Seiten.Ist der Kontakt hergestellt, erscheint oben links im Display des NXT eine geschlos-sene Raute: <>

61

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

9.2.2 Versenden einer Zahl

Der Master sendet nur jeweils eine einzige Zahl, dies kann beispielsweise eine Positions-angabe fur einen Winkelmesser oder ein Powerwert fur einen Motor sein, der vom Slaveverarbeitet werden soll. Die OUTBOX des Senders muss mit der INBOX des Empfangersubereinstimmen. Insgesamt kann jeder NXT bis zu 10 Mailboxen verwalten.

1 //MASTER2 #d e f i n e BT CONN 1 //Komm−Nummer 1 . S lave3 #d e f i n e OUTBOX 5 //OUTBOX f u r Senden4 i n t i ;56 void BTCheck( i n t conn )7 {8 i f ( ! BluetoothStatus ( conn)==NO ERR)9 {

10 TextOut (5 ,LCD LINE2 , ” Error ” ) ;11 Wait ( 1 0 0 0 ) ;12 Stop ( t rue ) ;13 }14 }1516 task main ( )17 {18 BTCheck(BT CONN) ; // check s l a v e connect ion , Ch119 whi l e ( t rue )20 {21 TextOut (10 ,LCD LINE1 , ” Master Test ” , t rue ) ; / / l o k a l ausgeben22 TextOut (0 ,LCD LINE4 , ”OUT: ” ) ;23 SendRemoteNumber (BT CONN,OUTBOX, i ) ; / / i senden24 NumOut(10 ,LCD LINE5 , i ) ; / / i l o k a l anze igen25 Wait ( 1 0 0 ) ;26 i ++; // zah l wird i n c r e m e n t i e r t27 }28 }

62

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

1 //SLAVE2 #d e f i n e INBOX 53 i n t in ;45 void BTCheck( i n t conn )6 {7 i f ( ! BluetoothStatus ( conn)==NO ERR)8 {9 TextOut (5 ,LCD LINE2 , ” Error ” ) ;

10 Wait ( 1 0 0 0 ) ;11 Stop ( t rue ) ;12 }13 }1415 task main ( )16 {17 BTCheck ( 0 ) ; // Verbindung zum Master auf 018 whi l e ( t rue )19 {20 TextOut (10 ,LCD LINE1 , ” Slave Test ” , t rue ) ;21 TextOut (0 ,LCD LINE2 , ” IN : ” ) ;22 ReceiveRemoteNumber (INBOX, true , in ) ; / / Empfang e i n l e s e n23 NumOut(10 ,LCD LINE3 , in ) ;24 Wait ( 1 0 0 ) ;25 }26 }

63

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

9.2.3 Master sendet String

Ein ’String’ ist eine Zeichenkette, Beispiele sind: ’“Hans-Georg“,”KarlderErste“,“Fridolin23“.

Auch die Zeichenkette”124“ konnte als Folge der Zeichen 1, 2, 4 gelesen werden, die kei-

nen Zahlenwert darstellt.Hier ein Beispiel fur eine Kommunikation: Der Master sendet M0, M1, M2, M3, ...

Der Slave empfangt diesen String und stellt ihn dar. Das Programm ist eine vereinfachteFassung aus dem Benedetteli-Tutorial.

1 //MASTER2 // Sendet nur3 #d e f i n e BT CONN 14 #d e f i n e OUTBOX 5 //OUTBOX f u r Senden5 s t r i n g out , i S t r ;6 i n t i ;78 void BTCheck( i n t conn )9 {

10 i f ( ! BluetoothStatus ( conn)==NO ERR)11 {12 TextOut (5 ,LCD LINE2 , ” Error ” ) ;13 Wait ( 1 0 0 0 ) ;14 Stop ( t rue ) ;15 }16 }1718 task main ( )19 {20 BTCheck(BT CONN) ; // check s l a v e connect ion , Ch121 whi l e ( t rue )22 {23 i S t r = NumToStr( i ) ; / / Zahl in St r ing verwandeln24 // out = StrCat (”M” , i S t r ) ; / / S t r i n g s ve rke t t en25 out=”’M”’+ i S t r ; // S t r i n g s ve rke t t en26 TextOut (10 ,LCD LINE1 , ” Master Test ” , t rue ) ; / / l o k a l ausgeben27 TextOut (0 ,LCD LINE4 , ”OUT: ” ) ;28 SendRemoteString (BT CONN,OUTBOX, out ) ; / / St r ing Senden29 TextOut (10 ,LCD LINE5 , out ) ;30 Wait ( 1 0 0 ) ;31 i ++;32 }33 }

64

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

1 //SLAVE2 #d e f i n e INBOX 53 s t r i n g in ;45 void BTCheck( i n t conn )6 {7 i f ( ! BluetoothStatus ( conn)==NO ERR)8 {9 TextOut (5 ,LCD LINE2 , ” Error ” ) ;

10 Wait ( 1 0 0 0 ) ;11 Stop ( t rue ) ;12 }13 }1415 task main ( )16 {17 BTCheck ( 0 ) ; // Verbindung zum Master auf 018 whi l e ( t rue )19 {20 TextOut (10 ,LCD LINE1 , ” Slave Test ” , t rue ) ;21 TextOut (0 ,LCD LINE2 , ” IN : ” ) ;22 ReceiveRemoteString (INBOX, true , in ) ; / / Ver s ch i ck t e s e i n l e s e n23 TextOut (10 ,LCD LINE3 , in ) ;24 Wait ( 1 0 0 ) ;25 }26 }

Zusatz:Haufig hat man das Problem, dass die Bluetooth-Verbindung zwar steht, aber eine Kom-munikation trotzdem nicht zustande kommt. Manchmal liegt es an der falschen oder nichtvorhandenen Kanalnummer der NXTs. Mit folgendem kleinen Programm kann man aufdem Master die funktionierende Kanalnummer testen:

1 void verbindung ( )2 {3 f o r ( i n t i =0; i <4; i ++)// d i e Kanalnummern4 {5 i f ( ! BluetoothStatus ( i )==NO ERR)6 {7 TextOut (5 ,LCD LINE2 , ” Es f u n k t i o n i e r t n i cht auf Kanal ” ) ;8 NumOut(1 ,LCD LINE2 , i ) ;9 Wait ( 1 0 0 0 ) ;

10 }11 e l s e

65

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

12 {13 TextOut (5 ,LCD LINE2 , ” Es f u n k t i o n i e r t gut auf Kanal ” ) ;14 NumOut(1 ,LCD LINE2 , i ) ;15 Wait ( 1 0 0 0 ) ;16 }17 }18 }1920 task main ( )21 {22 verbindung ( ) ;23 }

Mit geringen Anderungen konnte man das Programm sogar so ausbauen, dass die Verbin-dungsnummer, mit welcher die Kommunikation klappt, gespeichert wird und im ubrigenProgramm verwendet wird

Zusatzlicher Hinweis:Es gibt einige wenige Befehle, mit denen man vom Master aus Aktionen auf dem Slavestartet kann: Ton abspielen Programme starten, ... . Siehe Hansen-Guide.

66

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

9.2.4 Kommunikation mit Bestatigung

Der Nachteil dieser Programme: Der Sender (Master) bekommt nicht mit, ob der Empfangerdie Zahlen uberhaupt entgegennimmt. Im Benedettelitutorial findet man zwei Beispiele,bei denen Kommunikation in beide Richtungen stattfindet: Der Master schickt etwas,der Slave antwortet darauf.

Der Master schickt einen String oder eine Zahl, der Slave fuhrt den entsprechendenBefehl aus und soll anschließend eine Bestatigung an den Master zurucksenden. Dazuschickt der Slave beispielsweise die Zahl 255.

Hier die Programme aus dem Benedetteli-Skript (leicht verandert):

1 //MASTER2 #d e f i n e BT CONN 13 #d e f i n e OUTBOX 54 #d e f i n e INBOX 156 void BTCheck( i n t conn )7 {8 i f ( ! BluetoothStatus ( conn)==NO ERR)9 {

10 TextOut (5 ,LCD LINE2 , ” Error ” ) ;11 Wait ( 1 0 0 0 ) ;12 Stop ( t rue ) ;13 }14 }1516 task main ( )17 {18 i n t ack ;19 i n t i ;20 BTCheck(BT CONN) ;21 TextOut (10 ,LCD LINE1 , ” Master sending ” ) ;22 whi l e ( t rue )23 {24 i ++;25 NumOut(5 ,LCD LINE3 , i ) ;26 ack = 0 ;27 SendRemoteNumber (BT CONN,OUTBOX, i ) ;28 //Warten b i s vom Slave 255 g e s c h i c k t wird :29 u n t i l ( ack==255)30 {31 u n t i l ( ReceiveRemoteNumber (INBOX, true , ack ) == NO ERR) ;32 }33 Wait ( 2 5 0 ) ;34 }

67

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

35 }

Das zugehorige Slave-Programm:

1 //SLAVE2 #d e f i n e BT CONN 13 #d e f i n e OUT MBOX 14 #d e f i n e IN MBOX 556 void BTCheck( i n t conn )7 {8 i f ( ! BluetoothStatus ( conn)==NO ERR)9 {

10 TextOut (5 ,LCD LINE2 , ” Error ” ) ;11 Wait ( 1 0 0 0 ) ;12 Stop ( t rue ) ;13 }14 }1516 task main ( )17 {18 i n t in ;19 BTCheck ( 0 ) ;20 TextOut (5 ,LCD LINE1 , ” Slave r e c e i v i n g ” ) ;21 SendResponseNumber (OUT MBOX, 2 5 5 ) ; // unblock master22 whi l e ( t rue )23 {24 i f ( ReceiveRemoteNumber (IN MBOX, true , in )!=STAT MSG EMPTY MAILBOX)25 {26 NumOut(1 ,LCD LINE3 , in ) ;27 SendResponseNumber (OUT MBOX, 0xFF ) ;28 }29 Wait ( 1 0 ) ; // take breath ( o p t i o na l )30 }31 }

Um das Programm zu testen, kann man beispielsweise nur das Masterprogramm starten.Da keine Antwort vom Slave kommen kann, sollte der Master nicht weiter zahlen.

68

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

9.2.5 Beispiel: Ubertragung mehrere Werte

Man hat eine Fernsteuerung gebaut und mochte beispielsweise Gas und Lenkwinkelubertragen. Auf keinen Fall kann man die beiden Werte hintereinander im Wechselubertragen und hoffen, dass das Empfangsprogramm sie richtig zuordnet. Gerat derEmpfanger ausser Takt, geraten die Zurodnung der Werte durcheinander.

Weg 1:

Man schickt die Werte gleichzeitig, trennt sie aber durch z.B. ’:’.Beispielweise liest das Programm die folgenden Werte:gas=30, winkel=-25;Versendet wird 30:-25

Das Empfangerprogramm muss dann nur am ’:’ die beiden Werte zerlegen.Aber: Zahlenvariablen kennen kein ’:’. Daher muss man die Zahlen in Strings (

”Zeichen-

ketten“) umwandeln.Hier ein Beispiel einer void, welche den Winkelmesser auf A und C liest, die Zahlen

in Strings umwandelt und einen Trenner einfugt:

1 i n t winkel , gas ;2 s t r i n g winkelS , gasS , ausgabeS ;34 void motor l e sen ( )5 {6 winkel= MotorTachoCount (OUT A) ;7 winkelS=NumToStr( winkel ) ; / / in St r ing verwandeln8 gas= MotorTachoCount (OUT C) ;9 gasS= NumToStr( gas ) ; / / in St r ing verwandeln

10 // j e t z t S t r i n g s ve rke t t en mit +, Trenner e in fuegen11 ausgabeS=winkelS+”:”+gasS ;12 SendRemoteString (BT CONN,OUTBOX, ausgabeS ) ;13 // e v e n t u e l l Best a t igung vom Slave einbauen14 NumOut(1 ,LCD LINE1 , winkel ,TRUE) ;15 NumOut(1 ,LCD LINE2 , gas ) ;16 TextOut (1 ,LCD LINE3 , winkelS ) ;17 TextOut (1 ,LCD LINE4 , gasS ) ;18 TextOut (1 ,LCD LINE5 , ausgabeS ) ;19 Wait ( 1 0 0 ) ;20 }

Versenden konnte man nun die Variable ausgabeS, die vom Slave wieder zuruckverwandeltwerden muss: Zerlegen am Trenner ’:’ und die beiden Teilstrings in Zahlen umwandeln.

69

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

Weg 2:

Man verschickt die beiden Werte an verschiedene Outboxen: Nummer 5 und 6. Der Slaveliest die beiden Werte aus den beiden entsprechenden Inboxen 5 und 6.

1 //MASTER sendet auf 2 OutBoxen #5 und #62 // Sendet Zahlen d i e hoch (OUTBOX 5)3 //bzw . runter (OUTBOX1 6) l au f en45 #d e f i n e BT CONN 16 #d e f i n e OUTBOX 57 #d e f i n e OUTBOX1 68 s t r i n g in , out , iS t r , j S t r ;9 i n t i , j ;

1011 void BTCheck( i n t conn )12 {13 i f ( ! BluetoothStatus ( conn)==NO ERR)14 {15 TextOut (5 ,LCD LINE2 , ” Error ” ) ;16 Wait ( 1 0 0 0 ) ;17 Stop ( t rue ) ;18 }19 }2021 task main ( )22 {23 BTCheck(BT CONN) ; // check s l a v e connect ion24 whi l e ( t rue )25 {26 i S t r = NumToStr( i ) ;27 j S t r = NumToStr( j ) ;28 out = StrCat (”M” , i S t r ) ;29 TextOut (10 ,LCD LINE3 , out ) ; // Kontro l lausgabe30 SendRemoteString (BT CONN,OUTBOX, out ) ;31 Wait ( 1 0 0 ) ;32 out = StrCat (”M” , j S t r ) ;33 SendRemoteString (BT CONN,OUTBOX1, out ) ;34 TextOut (10 ,LCD LINE5 , out ) ;35 Wait ( 1 0 0 ) ;36 i ++;37 j−−;38 }39 }

70

9.2. MIT BLUETOOTH KAPITEL 9. KOMMUNIKATION ZWISCHEN NXTS

Das zugehorige Slave-Programm:

1 /∗Slave−Programm , horcht auf 2 Inboxen :2 auf #5 und #63 ∗/4 #d e f i n e BT CONN 15 #d e f i n e INBOX 56 #d e f i n e INBOX1 67 s t r i n g in , in1 , out , i S t r ;89 void BTCheck( i n t conn )

10 {11 i f ( ! BluetoothStatus ( conn)==NO ERR)12 {13 TextOut (5 ,LCD LINE2 , ” Error ” ) ;14 Wait ( 1 0 0 0 ) ;15 Stop ( t rue ) ;16 }17 }1819 task main ( )20 {21 BTCheck ( 0 ) ; // check master connect ion22 whi l e ( t rue )23 {24 ReceiveRemoteString (INBOX, true , in ) ;25 ReceiveRemoteString (INBOX1, true , in1 ) ;26 TextOut (10 ,LCD LINE3 , in ) ;27 TextOut (10 ,LCD LINE6 , in1 ) ;28 Wait ( 1 0 0 ) ;29 }30 }

71

10 Abfrage der NXT-Tasten

Bei manchen Anwendungen mochte man eventuell die Tasten am NXT verwenden, bei-spielsweise fur eine Fernsteuerung oder wenn die Sensoranschlusse nicht ausreichen. VonInteresse sind fur uns die beiden Tasten fur Links und Rechts sowie die orange Enter-Taste. Da die untere Taste gleich das Programm abbricht, ist sie fur uns ohne Interesse.

10.1 Erstes Beispiel:

Das folgende Programm fragt die Links-/Rechtstasten ab und zahlt eine Variable (speed)rauf oder runter. Druckt man die Entertaste, ist die Tastaturabfrage abgeschlossen. Die-ses Programm konnte man beispielsweise verwenden, wenn vor dem eigentlichen Pro-gramm eine Festlegung gemacht werden soll.

1 /∗ Programm f r a g t ab , ob Links / Rechts tas te gedrueckt .2 Var iab le speed wird hoch/ runter g e za e h l t3 S c h l i e s s t mit Enter−Taste ab4 ∗/5 i n t speed ;67 void ta s t en ( )8 {9 whi l e ( ButtonPressed (BTNCENTER, FALSE) == FALSE )

10 {11 TextOut (18 ,LCD LINE7 , ” Speed = ” ) ;12 NumOut (65 ,LCD LINE7 , speed ) ;1314 i f ( ButtonPressed (BTNLEFT, FALSE) == TRUE )// Links15 {16 whi l e ( ButtonPressed (BTNLEFT, FALSE) == TRUE ) ;17 speed−−;18 }1920 i f ( ButtonPressed (BTNRIGHT, FALSE)==TRUE )// Rechts21 {22 whi l e ( ButtonPressed (BTNRIGHT, FALSE)==TRUE ) ;23 speed++;24 }25 }

72

10.2. ZWEITES BEISPIEL: KAPITEL 10. ABFRAGE DER NXT-TASTEN

26 whi l e ( ButtonPressed (BTNCENTER,FALSE)==TRUE) ; / / Enter gedrueckt ?27 }2829 task main ( )30 {31 ta s t en ( ) ;32 TextOut (1 ,LCD LINE2 , ” Erg : ” ) ;33 NumOut(40 ,LCD LINE2 , speed ) ;34 whi l e (TRUE) ; / / Porgramm wartet h i e r35 }

10.2 Zweites Beispiel:

Dasselbe wie im ersten Beispiel, fragt aber die Links-/Rechtstasten dauernd ab, ohneEntertaste. Dieses Programm konnte man beispielsweise fur Fernsteuerungen verwenden.

1 /∗ Programm f r a g t dauernd ab , ob Links / Rechts tas te gedrueckt .2 Var iab le wird rau f / runter g e za e h l t .3 S c h l i e s s t ohne Enter ab4 ∗/5 i n t speed ;67 void ta s t en ( )8 {9 whi l e ( ButtonPressed (BTNCENTER, FALSE)==FALSE )

10 {11 TextOut (18 ,LCD LINE7 , ” Speed = ” ) ;12 NumOut (65 ,LCD LINE7 , speed ) ;1314 i f ( ButtonPressed (BTNLEFT, FALSE)== TRUE )15 {16 whi l e ( ButtonPressed (BTNLEFT, FALSE) == TRUE ) ;17 speed−−;18 }1920 i f ( ButtonPressed (BTNRIGHT, FALSE)== TRUE)21 {22 whi l e ( ButtonPressed (BTNRIGHT, FALSE)==TRUE ) ;23 speed++;24 }25 }26 }27

73

10.2. ZWEITES BEISPIEL: KAPITEL 10. ABFRAGE DER NXT-TASTEN

28 task main ( )29 {30 whi l e (TRUE)31 {32 ta s t en ( ) ;33 TextOut (1 ,LCD LINE2 , ” Erg : ” ) ;34 NumOut(40 ,LCD LINE2 , speed ) ;35 Wait ( 1 0 0 ) ;36 }37 }

74

10.3. DRITTES BEISPIEL: KAPITEL 10. ABFRAGE DER NXT-TASTEN

10.3 Drittes Beispiel:

Das folgende Beispiel fragt die Links-/Rechtstasten ab und gibt eine entsprechende Rich-tungsinformation aus. Dieses Programm konnte man z.B. fur Fernsteuerungen verwen-den:

1 /∗ Programm f r a g t ab , ob Links / Rechts tas te gedrueckt . Ze ig t das2 Ergebnis 100 ms an3 F a l l s ke ine Taste gedrueckt , wird ’ keine ’ angeze i g t4 ∗/5 s t r i n g r i =””;67 void ta s t en ( )8 {9 i f ( ButtonPressed (BTNLEFT, FALSE) == TRUE)

10 {11 r i =”LINKS” ;12 whi l e ( ButtonPressed (BTNLEFT, FALSE)==TRUE ) ;13 }14 i f ( ButtonPressed (BTNRIGHT, FALSE)==TRUE )15 {16 r i =”RECHTS” ;17 whi l e ( ButtonPressed (BTNRIGHT, FALSE)==TRUE ) ;18 }19 }2021 task main ( )22 {23 whi l e (TRUE)24 {25 ta s t en ( ) ;26 TextOut (1 ,LCD LINE1 , r i , t rue ) ;27 Wait ( 2 0 0 ) ; // f u e r Anzeige28 r i =”ke ine ” ;29 }30 }

75

11 Speichern von Daten

Bei manchen Anwendungen mochte man eine großere Menge von Daten speichern, bei-spielsweise irgendwelche Messwerte, die erst spater verarbeitet werden sollen..

11.1 Daten in Arrays speichern

Arrays sind im Wesentlichen Variable, die einen Index haben: x[0], x[1], x[2], ... Arrayswerden in einem fluchtigen Speicher gespeichert,. d.h. schaltet man den NXT aus, sinddie Werte verloren.

Uber diesen Index kann man auf die Variable zugreifen:

• Wert speichern: x[0]= wert;x[1]=wert; ...

• Wert holen: wert=x[0], wert=x[1]; ...

11.1.1 Erstes Beispiel:

Wir speichern die ersten 100 Quadratzahlen in einem Array und geben sie anschließendauf dem Bildschirm des NXT aus. Eine Schleife wird verwendet, die Quadratzahlen zuberechnen, der Schleifenzahler ist gleichzeitig der Index, d.h. die Nummer des Arrayspei-cherplatzes.

1 i n t quadrate [ ] ; / / Array−Var iab le anlegen2 i n t anzahl =100;3 task main ( )4 {5 ArrayIn i t ( quadrate , 0 , anzahl ) ; //100 Elementen anlegen6 // Spe ichern7 f o r ( i n t i =0; i<anzahl ; i++)8 {9 quadrate [ i ]= i ∗ i ; // Werte spe i che rn

10 }11 // Lesen12 f o r ( i n t i =0; i<anzahl ; i++)13 {14 NumOut(1 ,LCD LINE1 , quadrate [ i ] , t rue ) ; / / werte holen15 NumOut(1 ,LCD LINE2 , i ) ; / /Nummer des Arrayelements ausgeben16 Wait ( 5 0 ) ;17 }

76

11.1. DATEN IN ARRAYS SPEICHERN KAPITEL 11. SPEICHERN VON DATEN

18 whi l e (TRUE) ;19 }

Erklarung:

• int quadrate[]; Teilt dem Programm mit, dass quadrate eine Array-Variable ist, dieganze Zahlen speichern soll.

• ArrayInit(quadrate,0,anzahl); anzahl Elemente, d.h. Speicherplatze bereitgestel-len. Diese werden mit 0 vorbelegt.

• for(int i=0;i<anzahl;i++) Beachte: Arrayspeicherplatze werden ab 0 gezahlt!

• quadrate[i]=i*i; Der Schleifenzahler wird quadriert und dessen Wert in Array-Speicherplatz Nummer i geschrieben.

• NumOut(1,LCD LINE1,quadrate[i],true); Gibt den Inhalt von Speicherplatz Num-mer i auf dem Bildschirm aus.

• NumOut(1,LCD LINE2,i); Gibt die Nummer des Speicherplatzes aus.

11.1.2 Zweites Beispiel:

Im Array sollen Dezimalzahlen gespeichert werden.

1 // Array aus Dezimalzahlen23 f l o a t zahlen [ ] ; / / Array f u r Dezimalzahlen anlegen4 i n t anzahl =100;56 task main ( )7 {8 ArrayIn i t ( zahlen , 0 , anzahl ) ; / Anzahl Elemente anlegen9 // Spe ichern

10 f o r ( i n t i =0; i<anzahl ; i++)11 {12 zahlen [ i ]= i ∗0 . 1 ; / / Dezimalzahlen erzeugen13 }14 // Lesen15 f o r ( i n t i =0; i<anzahl ; i++)16 {17 NumOut(1 ,LCD LINE1 , zahlen [ i ] , t rue ) ;18 NumOut(1 ,LCD LINE2 , i ) ;19 Wait ( 5 0 ) ;20 }21 whi l e (TRUE) ;22 }

77

11.2. DATEN IN FILES SPEICHERN KAPITEL 11. SPEICHERN VON DATEN

Problem:Der Speicherplatz ist relativ begrenzt, man kann nicht sehr viele Arrayplatze belegen.

11.2 Daten in Files speichern

Im ersten Beispiel sollen die Zahlen 0, 1, 2, .... im Speicher abgelegt werden. Dazu wirdein File namens ’werte.txt’ angelegt, der Wert des Schleifenzahlers i mit NumToStr(i) ineinen String verwandelt und in den File geschrieben. Sollte der File bereits im Speicherexistieren, wird er nicht automatisch uberschrieben. Daher wird der eventuell vorhandeneFile vorher geloscht.

1 byte daten ;// Var iab le f u r D a t e i z u g r i f f2 i n t s i z e =5120;// S p e i c h e r p l a t z der Datei3 s t r i n g wr i t e ;4 shor t bytesWritten ;56 task main ( )7 {8 D e l e t e F i l e (” werte . txt ” ) ; // e v t l . e x i s t i e r e n d e n F i l e l o s chen9 Crea teF i l e (” werte . txt ” , s i z e , daten ) ; / Datei anlegen

10 f o r ( i n t i =0; i<s i z e ; i++)11 {12 wr i t e=NumToStr( i ) ; / / i in S t r ing umwandeln13 WriteLnString ( daten , write , bytesWritten ) ; / / in Datei14 }15 C l o s e F i l e ( daten ) ; / / Datei s c h l i e ß e n16 }

Verwendet man den NXT-Explorer, findet man auf dem NXT die Datei werte.txt, dieman mit der View-Funktion von Bricxcc (bei File) anschauen kann.Alternativ kann manmit dem nachsten Programm die Werte auf dem NXT-Bildschirm anzeigen lassen.

Allerdings kann man die Werte in der Datei nur so hintereinander auslesen, wie sie inder Datei gespeichert sind. Es ist nicht moglich, beispielsweise direkt auf das ElementNummer 73 zuzugreifen, dies geht nur bei Arrays.

Werte aus einer existierenden Datei wieder auslesen:

1 i n t s i z e ; // Dateigr oße , wird automatisch e r m i t t e l t2 byte daten ;// Var iab le f u r D a t e i z u g r i f f3 s t r i n g buf ; // Variable , welche d i e ge l e s enen Daten aufnimmt4 bool e o f=f a l s e ; // Variable , welche das Ende des F i l e s anze i g t56 task main ( )7 {8 i f ( OpenFileRead (” werte . txt ” , s i z e , daten)==NO ERR)9 {

78

11.2. DATEN IN FILES SPEICHERN KAPITEL 11. SPEICHERN VON DATEN

10 u n t i l ( e o f==true )11 {12 i f ( ReadLnString ( daten , buf )!=NO ERR) eo f=true ;13 TextOut (1 ,LCD LINE1 , buf ) ;14 Wait ( 5 ) ;15 }16 }17 C l o s e F i l e ( daten ) ;18 }

Als vorletztes Beispiel sei folgende Aufgabe dargestellt: Am Ende eines Spieles soll derScore des Spielers gespeichert werden, er betragt im Beispiel i=100.

Das Programm speichert den Score als einzigen Wert in der Datei werte.txt auf demNXT und liest anschließend die Datei wieder aus. Verwendet man den NXT-Explorer,findet man auf dem NXT die Date werte.txt, die als einzigen WErt den String 100enthalt.

1 byte daten ; // Handle auf d i e Datei2 i n t s i z e =10; // v o r e i n g e s t e l l t e Date igr oße in Byte3 s t r i n g iS ; // Score a l s S t r ing4 shor t bytesWritten ;5 bool e o f=f a l s e ; // Flag , wenn Ende der Datei e r r e i c h t6 i n t i =100; // Score des S p i e l e r s7 s t r i n g buf ; // Var iab le zur Aufnahme des D a t e i i n h a l t e s89 void spe i che rn ( )

10 {11 eo f=f a l s e ;12 iS=NumToStr( i ) ;13 TextOut (2 , LCD LINE2 , iS ) ;14 D e l e t e F i l e (” werte . txt ” ) ; / / a l t e n F i l e l o s chen15 Crea teF i l e (” werte . txt ” , s i z e , daten ) ; / / F i l e wieder erzeugen16 WriteLnString ( daten , iS , bytesWritten ) ; / / in F i l e s ch r e iben17 C l o s e F i l e ( daten ) ; / / F i l e s c h l i e s s e n18 }1920 void l e s e n ( )21 {22 // F i l e kann g e o f f n e t werden?23 i f ( OpenFileRead (” werte . txt ” , s i z e , daten)==NO ERR)24 {25 u n t i l ( e o f==true )// LEsen b i s Ende F i l e e r r e i c h t26 {27 i f ( ReadLnString ( daten , buf )!=NO ERR) eo f=true ;28 TextOut (1 , LCD LINE2 , buf ) ;

79

11.2. DATEN IN FILES SPEICHERN KAPITEL 11. SPEICHERN VON DATEN

29 whi l e (TRUE) ; // D a u e r s c h l e i f e ! ! !30 }31 }32 }3334 task main ( )35 {36 spe i che rn ( ) ; / /Am Spie l ende37 l e s e n ( ) ; / /Am Spie l an fang38 }

Als letztes Beispiels soll der score-Wert von zwei Spielern in einer Datei gespeichertwerden und beim Lesen dieser Datei in zwei Arrayvariablen gespeichert werden. Dereine Spieler soll den score-Wert 150, der andere 200 haben. Die beiden Werte werdenhintereinander in die Datei werte.txt geschrieben (void speichern()). Die vodi lesen()offnet die Datei, liest die beiden WErte und schreibt sie in die Arrayvariablen score[0]und score[1]:

1 byte daten ; // Handle auf d i e Datei2 i n t s i z e =10; // v o r e i n g s t e l l t e Date igr oße3 s t r i n g iS1 , iS2 ; // Score a l s S t r ing4 shor t bytesWritten ;5 bool e o f=f a l s e ; // Flag , wenn Ende der Datei e r r e i c h t6 i n t i 1 =150 , i 2 =200; // Scores der S p i e l e r7 s t r i n g buf ; // Var iab le zur Aufnahme des D a t e i i n h a l t e s8 i n t s co r e [ ] ; / / Array zur Aufnahme des Scorewerte9 i n t k ;// Zaeh le r

1011 void spe i che rn ( )12 {13 eo f=f a l s e ;14 iS1=NumToStr( i 1 ) ;15 iS2=NumToStr( i 2 ) ;16 D e l e t e F i l e (” werte . txt ” ) ;17 Crea teF i l e (” werte . txt ” , s i z e , daten ) ;18 WriteLnString ( daten , iS1 , bytesWritten ) ;19 WriteLnString ( daten , iS2 , bytesWritten ) ;20 C l o s e F i l e ( daten ) ;21 }2223 void l e s e n ( )24 {25 i f ( OpenFileRead (” werte . txt ” , s i z e , daten)==NO ERR)26 {27 u n t i l ( e o f==true )

80

11.2. DATEN IN FILES SPEICHERN KAPITEL 11. SPEICHERN VON DATEN

28 {29 i f ( ReadLnString ( daten , buf )!=NO ERR) eo f=true ;30 s co r e [ k]=StrToNum( buf ) ;31 k++;32 }3334 }35 C l o s e F i l e ( daten ) ;36 NumOut(1 , LCD LINE1 , s co r e [ 0 ] ) ; / / S p i e l e r 137 NumOut(1 , LCD LINE2 , s co r e [ 1 ] ) ; / / S p i e l e r 238 whi l e (TRUE) ; // D a u e r s c h l e i f e ! ! !39 }4041 task main ( )42 {43 ArrayIn i t ( score , 0 , 2 ) ;44 spe i che rn ( ) ;45 l e s e n ( ) ;46 }

81