| Projekte | |
Basic für das Robo Interface von fischertechnik
Vorwort
Diese Software entstand aus der Idee heraus, eine Scriptsprache für das Robo Interface zu entwickeln, die auch downloadfähig ist – d.h.: die auch autonom ohne PC im Interface funktioniert. Zwar existiert bereits eine downloadfähige Scriptsprache in Form von C, allerdings ist dies sehr fehleranfällig und für Anfänger gänzlich ungeeignet.
Es ist in Zukunft geplant, die Sourcen zu veröffentlichen – allerdings müssen die erst einmal bereinigt und lesbar gemacht werden ;-)
Ich kann leider nicht garantieren, dass die vorliegende Software völlig fehlerfrei ist. Eine Benutzung geschieht deshalb auf eigenes Risiko, eine Haftung bei Folgeschäden durch die Benutzung dieser Software schließe ich aus.
Momentan hat die Software Beta-Status.
Dies ist auch der Grund, weshalb ich momentan die Software nicht frei zum
Download anbiete, denn ich brauche Feedback, um die noch vorhandenen Fehler zu
finden. Eine kurze Mail an mich, und das Paket geht auf den Weg.
Die grafische Programmieroberfläche "RoboPro" von fischertechnik hat durchaus ihren Reiz. Auch als Anfänger kann man sehr schnell komplexe Programme erstellen, ohne sich lange mit der Syntax von Programmiersprachen herumzuärgern. Auch als langjähriger Script-Programmierer kann man sich relativ schnell auf diese Art der Programmierung umstellen.
Jedoch hat auch das beste System seine Grenzen, gerade im Bereich der Arithmetik (z.B. Gleitkommazahlen) und bei Feldern (Arrays) kommt man mit RoboPro nicht weit. Da ich genau diese Funktionen vermisste und fischertechnik auch die Möglichkeit bietet, das RoboInt mit C zu programmieren, bot sich dies an. Allerdings stößt man spätestens bei der Implementierung von Multitasking (= scheinbar gleichzeitige Ausführung mehrerer Programmteile) bei C an die Grenzen, denn dies wird vom Renesas-Compiler (= C-Compiler für das Robo Interface) nicht unterstützt und eigene Implementierungen sind schwierig und fehleranfällig. So langsam aber sicher reifte die Idee, einen Interpreter zu verwenden, um bequem ein Multitasking realisieren zu können. Bei einem Interpreter hat man als übergeordnete Instanz immer die Möglichkeit, einzugreifen und dadurch ist Multitasking eine relativ einfache Geschichte.
Jetzt wird der Einwand kommen, dass ein Interpreter doch sehr langsam ist. Im Prinzip stimmt dies, denn üblicherweise muss ein Interpreter den Quellcode während des Programmablaufs aktuell übersetzen und wird dadurch stark gebremst. Die Lösung für dieses Problem stellt die Übersetzung in einen Bytecode dar. So werden Schlüsselwörter wie "FOR", "GOTO", "MOTOR" bereits im PC in ein einzelnes Byte übersetzt, das dann wesentlich schneller abgearbeitet werden kann. Diese Umsetzung von Schlüsselwörtern in sogenannte "Tokens" wurde schon bei den frühen BASIC-Versionen von VC-20 und C-64 von Commodore verwendet. Leider ist das C-64 BASIC nicht gerade als schnell bekannt, dies hat aber andere Gründe.
1. Funktionsprinzip
Das Basic besteht aus zwei Komponenten:
Einem Compiler, der den Quelltext in einen Bytecode umsetzt
Einem Interpreter, der den Bytecode ausführt.
Bei der Planung des Basic habe ich viele verschiedene Möglichkeiten geprüft und – meines Erachtens – die bestmögliche verwirklicht. In der Anfangsphase sollte es ein reinrassiger Interpreter werden, dies hätte den Vorteil gehabt, dass man theoretisch ohne einen PC ausgekommen wäre (über die serielle Schnittstelle wäre der Anschluss eines Displays und einer Tastatur durchaus möglich). Allerdings hätte dies bedeutet, dass der Prozessor des Interface sehr stark mit den Übersetzungsaufgaben beschäftigt gewesen wäre. Eine Zwischenlösung ist die Umsetzung von Befehlen in Tokens, d.h. der Klartext der Befehle wird in Bytecode umgesetzt. Bei dieser Methode bleiben aber immer noch: Berechnung der Sprungadressen, Berechnung der Variablenadressen, Syntaxprüfung etc. Gerade die Syntaxprüfung ist ein Punkt, der für einen vom Interpreter getrennten Compiler sprach.
In dem nun vorliegenden Programm sind im Bytecode alle Adressen von Variablen, Funktionen und sonstigen Sprüngen vorberechnet und auch die Konstanten liegen nicht als Ascii, sondern im Binärformat vor, was die Ausführungszeiten positiv beeinflusst.
Theoretisch wäre es auf Grundlage des Bytecodes auch möglich, andere Sprachen (z.B. Pascal o.ä.) zu realisieren.
Beim Übersetzungsvorgang des Compilers wird ein .hex-File erzeugt, das mit dem von ft gelieferten „FtLoader“ in das Robo Interface übertragen werden kann. Das .hex-File beinhaltet den Interpreter und den Bytecode, ist also ohne weitere Downloads funktionstüchtig.
1.1 Features
In den letzten Monaten hat sich die Liste der Features von Woche zu Woche geändert – meistens wurde sie verlängert ;-). Mittlerweile bin ich der Überzeugung, dass man mit diesem Basic durchaus ein interessantes Werkzeug zur Verfügung hat. Bei der Realisierung bin ich hauptsächlich von meinen Bedürfnissen ausgegangen – falls jemand ein Feature vemisst, das realisierbar scheint, kann ich nochmal etwas nachlegen.
Eine kurze Liste der realisierten Features (ausführliche Information gibt es in der Befehlsreferenz)
Multitasking mit bis zu 10 Tasks
lokale Variablen innerhalb von Tasks und Funktionen
Funktionen mit Parametern (auch Rückgabe)
Funktionen rekursiv aufrufbar
Funktionen mehrfach aus verschiedenen Tasks aufrufbar
Ausgabe von Zahlen über die serielle Schnittstelle zum Debuggen
Arrays mit bis zu zwei Dimensionen (nur global möglich)
Datentypen Byte, Integer, Long, Float und Double
Trigonometrische Funktionen mit Double-Genauigkeit
Unterstützung der Hardware des Robo Interface mit entsprechenden Befehlen
1.2 Multitasking
Das mit Sicherheit interessanteste Feature ist das Multitasking. Nachdem ich es in C aufgegeben habe, weil es sehr viel Durcheinander auf dem Stack gegeben hat, war die Realisierung innerhalb eines Interpreters geradezu einfach. Die Tasks (bis zu 10 + Hauptprogramm) werden scheinbar gleichzeitig ausgeführt und auch Wartebefehle haben darauf keinen Einfluss.
Die einzige Einschränkung: Für einen Taskwechsel muss der mathematische Stack leer sein. Vor allem bei rekursivem Aufruf von Funktionen kommt es vor, dass ein Berechnungsergebnis auf dem mathematischen Stack verbleibt, dies hat zur Folge, dass die komplette Rekursion erst aufgelöst werden muss, bevor der nächste Task ausgeführt werden kann. Diese Eigenheit lässt sich ohne weiteres durch eine Erweiterung lösen, allerdings sehe ich es eher als Feature, weil man hierdurch eine Art Priorisierung erreichen kann.
1.3 Globale und lokale Variablen
Neben den globalen Variablen, die im gesamten Programm sichtbar sind, können für jede Funktion und jeden Task separat lokale Variablen definiert werden. Die lokalen Variablen sind nur innerhalb des Tasks / der Funktion sichtbar, in dem / der sie definiert wurden, nicht darüber hinaus.
1.4 Funktionen mit Parametern
Funktionen können mit oder ohne Parameter definiert werden. Werden Parameter definiert, sind diese automatisch als lokale Variable innerhalb der Funktion definiert. Ein Rückgabeparameter wird mit dem return Befehl übergeben.
Dadurch, dass lokale Variablen für jeden Task getrennt gespeichert werden, können Funktionen aus mehreren Task unabhängig voneinander und gleichzeitig aufgerufen werden. Die Aufrufe beeinflussen sich nicht gegenseitig. In diesem Zusammenhang ist aber Vorsicht bei globalen Variablen geboten.
Die restlichen Features sind im Kapitel 2 besprochen.
1.5 Bekannte Bugs
Wo Licht ist, ist auch Schatten – es gibt noch einige Bugs, die mir zwar bekannt sind, aber bei denen momentan einfach die Zeit fehlt.
Eine aktuelle Liste ist im Text-File „bekannte_bugs.txt“ abgelegt. Diese Liste sollte unbedingt gelesen werden, bevor man mit dem Programmieren anfängt. Ansonsten bin ich natürlich immer froh, wenn mir Bugs mitgeteilt werden.
2. Programmieren in BASIC
Ich gehe davon aus, dass die grundlegenden BASIC-Begriffe und Programmiertechniken bekannt sind.
Die wichtigsten Unterschiede zu einem Standardbasic bestehen in den Elementen für strukturierte Programmierung und darin, dass es keine Zeilennummern gibt. Ferner müssen – BASIC-untypisch – Variablen im Voraus definiert werden. Dies hat seine Begründung darin, dass der Compiler schlichtweg einfacher ist, wenn man Variablen deklariert.
Bei den meisten mir bekannten BASIC-Dialekten konnte man kaum unterschiedliche Datentypen verwenden, bestenfalls kann man zwischen Integer (= ganze Zahlen) und Float (= Gleitkommazahl) wählen. Beim hier vorliegenden BASIC hat man die Wahl zwischen Byte, Int, Long, Float und Double. Warum diese Vielfalt? Ich möchte eine möglichst optimale Ausnutzung von Speicher und Prozessor erreichen. Wenn z.B. ein Array mit 1000 Elementen definiert wird, braucht es als long 4000 Bytes, als byte nur 1000 Bytes.
2.1 Datentypen:
Byte:
Ganze Zahlen mit einem Wertebereich von -128 bis +127. Benötigt 1 Byte (8 Bit).
Int: (Integer)
Ganze Zahlen mit einem Wertebereich von -32768 bis +32767. Benötigt 2 Byte (16 Bit).
Long: (long Integer)
Ganze Zahlen mit einem Wertebereich von -2147483648 bis +2147483647. Benötigt 4 Byte (32 Bit).
Float: (Gleitkommazahl)
Gleitkommazahlen mit einem Wertebereich von (+/-)3,4*(+/-)10^38. Benötigt 4 Byte (32 Bit). Die Genauigkeit liegt bei max. 7 Ziffern.
Double: (Gleitkommazahl)
Gleitkommazahlen mit einem Wertebereich von (+/-)1,7*(+/-)10^308. Benötigt 8 Byte (64 Bit). Die Genauigkeit liegt bei max. 15 Ziffern.
Vor der Programmierung sollte man sich im Klaren sein, welche Variablen welchen Zahlenbereich annehmen können und dementsprechend einen Datentyp wählen. Die Benutzung von float und double sollte man so weit als möglich vermeiden, denn Gleitkommazahlen verbrauchen überdurchschnittlich viel Rechenleistung.
2.1 Rechenoperationen und Zahlen
Bei den Rechenoperationen ist (fast) alles erlaubt. Dieser Teil des Compilers hat mich einige graue Haare gekostet, aber scheint nun zu funktionieren. Es gilt: Punkt vor Strich, Klammern können auch gesetzt werden.
+ Addieren
- Subtrahieren
* Multiplizieren
/ Dividieren
^ Potenzieren
& Bitweise UND (nur für long, bei double ist das Ergebnis immer 0)
| Bitweise OR s.o.
! Bitweise XOR s.o.
Funktionen:
sin() sinus
cos() cosinus
tan() tangens
cot() cotangens
asin() Umkehrfunktion zu sin()
acos() Umkehrfunktion zu cos()
atan() Umkehrfunktion zu tan()
acot() Umkehrfunktion zu cot()
Es gibt momentan einen bekannten Fehler im Parser:
Ist innerhalb einer Konstanten ein nicht für Rechenoperationen reserviertes Zeichen (z.B. 4t5), wird kein Fehler ausgegeben. Die Zahl wird bis zum Vorkommen des Zeichens ausgewertet, der Rest wird verworfen. Im obigen Beispiel würde die Zahl also als 4 interpretiert.
Zu beachten ist: Bei den Rechenoperationen müssen die unterschiedlichen Zahlentypen "gecastet", also
angepasst werden, sonst kann man long (Ganzzahl) und double (Gleitkomma) nicht miteinander verknüpfen.
Dies macht der Interpreter automatisch. Dabei setzt sich immer das genauere (größere) Zahlenformat durch. Die Reihenfolge: Byte – Integer – Long – Float – Double.
Beispiele:
a = (b+c)*(d+e)
a = 5*(b+8)*(-c-d)
Der Compiler nimmt dem Interpreter bereits die arbeit ab, die Zahlen aus dem Klartext in ein dem Rechner verständliches Format umzuwandeln. Konstanten werden direkt im Bytecode abgespeichert, wobei ganze Zahlen 4 Bytes benötigen, Gleitkomma 8 Bytes. Ist in einer Konstanten ein "." (Dezimalpunkt) vorhanden, wird sie als double behandelt, sonst als long. Wenn man von vornherein weiß, dass eine Berechnung in double ausgeführt wird, kann man die Laufzeit optimieren, indem man den Compiler dazu zwingt, eine double abzuspeichern. Dann muss der Interpreter kein Typcasting mehr durchführen.
Es ist geplant, durch Bereichsprüfung bei den Konstanten auch die fehlenden Datentypen byte, integer und float zu implementieren.
1234 long - Konstante
1234.0 double
1234.0E4 double, entspricht 12340000
1234E4 long, das E wird nicht interpretiert, entspricht 1234
2.2 Reihenfolge der Definitionen
Der Compiler akzeptiert die verschiedenen Definitionen nur in einer bestimmten Reihenfolge.
Globale Variablen
Funktionen (globale Variablen)
Tasks (globale Variablen)
Hauptprogramm (main:)
Grundsätzlich muss eine Funktion definiert sein, bevor sie aufgerufen werden kann. Ein rekursiver Aufruf (die Funktion ruft sich selbst auf) ist deshalb möglich, da die Definition vor dem eigentlichen Aufruf innerhalb der Funktion steht.
Die Definitionen von Tasks und Funktionen können miteinander vertauscht werden, sollen Funktionen aus Tasks heraus aufgerufen werden, müssen die Funktionen wiederum vor dem Task definiert sein.
Globale Variablen müssen direkt nach der Kopfzeile der Funktion / des Tasks definiert werden. Hierzu gibt es im Anhang noch einige Beispiele.
3. Wie schreibe und compiliere ich Programme?
Im Paket ist der kostenlose Editor "Programmers Notepad" enthalten, der auch unter http://www.pnotepad.org heruntergeladen werden kann. Er bietet Syntax-Highlighting und Tools für den einfachen Aufruf der benötigten Programme.
An der Dateistruktur des Verzeichnisses "RoboInt_Basic_Compiler" sollte nichts verändert werden, da der Editor diese Struktur erwartet und nur dann richtig funktioniert.
3.1 Schreiben eines Programms
Starten des Editors mit Doppelklick auf "Programmers Notepad Editor.bat". Beim ersten Aufruf speichert der Editor seine Konfigurationsdaten in "C:\Dokumente und Einstellungen\(Username)\Anwendungsdaten\Echo Software". Hierbei werden auch die Tools automatisch definiert.
Nun kann das Programm eingegeben oder eines der Beispielprogramme im Ordner "Testprogramme" geladen werden. Wichtig: Das Label main: muss auf jeden Fall im Programm vorhanden sein, da der Compiler hierduch mitgeteilt bekommt, wo das Hauptprogramm beginnt.
3.2 Compilieren eines Programms
Mit Klick auf "Tools" und dann auf "[RoboInt] compile" wird das Programm compiliert und gelinkt. Beim Link-Vorgang wird der Interpreter automatisch zum generierten Bytecode hinzugefügt, so dass immer ein lauffähiges Programm entsteht. Dies ist auch der Grund, weshalb die Programme mehr als 32 KByte haben.
Im Output-Fenster am unteren Rand wird entweder die erfolgreiche Compilierung oder ein Fehler angezeigt. Bei der Ausgabe "Process Exit Code: 0" ist alles in Ordnung. Der Compiler kann auch mit der Taste "F5" aufgerufen werden. Zusätzliche Optionen (nur für "Fortgeschrittene")
- compile (Line Numbers) : Fügt Informationen der Zeilennummern hinzu. Bei Runtime-Fehlern wird dieseInformation benutzt, um die Zeilennummer auszugeben, in der der Fehler passierte.
- compile (Detailed) : Der Compiler gibt alle Zwischenschritte aus, die er beim übersetzen des Codes benötigt. Dient zum Debuggen des Compilers.
Beim "Process Exit Code: 1" ist ein Fehler beim Compilieren aufgetreten, üblicherweise sollte eine entsprechende Fehlermeldung angezeigt werden, die die Ursache des Fehlers zeigt.
3.3 Übertragen eines Programms zum Interface
Während des compilierens wird ein "hex"-File erzeugt, das den Interpreter und den Bytecode enthält. Das Hex-File hat den gleichen Namen, wie der Sourcetext. Durch Aufruf des Tools "FtLoader" wird das von der Fa. Knobloch zur Verfügung gestellte Download-Programm gestartet. Das Interface muss angewählt und das Hex-File geöffnet werden, dann kann das Programm in das Interface geladen und gestartet werden. Die Programme werden zur Zeit für das RAM des Interface compiliert, später ist geplant, dies auch für die beiden Flash-Speicher zu ermöglichen.
3.4 Ausführen des Programms
Im FtLoader den Button "Prg Ram Start" drücken.
3.5 Anzeige von Meldungen des Interface
Momentan missbrauche ich die Message-Funktion des Interface, um via serieller Schnittstelle eine Möglichkeit zum Debuggen zu haben. Leider kann diese Funktion keine beliebigen Texte aussenden, es werden zusätzliche Informationen mit übertragen, die bei einem normalen Terminal-Programm nur zu unleserlichen Ausgaben führen würden. Hierzu habe ich ein kleines Windows-Programm geschrieben (RoboIntSerialConnect.exe), das die uninteressanten Daten ausfiltert und die Daten, die mit dem "print" Befehl ausgegeben werden, vom Interface ausliest und in einem Fenster ausgibt. Auch Runtime-Fehler werden über die serielle Schnittstelle ausgegeben.
Beim normalen Programmende blinkt die rote Fehler-LED des Interface 1x. Sollte ein Runtime-Fehler auftauchen,schaltet die rote LED auf dauerblinken.
4. Befehlsreferenz
<Ausdruck>
Ein Ausdruck kann aus Variablen, Konstanten oder Kombinationen davon bestehen (z.B. eine komplette Berechnungsformel). Der jeweilige Befehl verwendet dann das Ergebnis der Berechnung als Parameter.
Gültige Ausdrücke:
| 10 | Konstante |
| a | Variable |
| a+5 | Formel |
| (a+5)*(b+2) |
nicht erlaubt:
10>5
<Vergleich>
Ein Vergleich stellt eine Beziehung zwischen zwei Ausdrücken her.
Gültige Vergleiche:
a = 0
b > 5
(a+b) = (c * d)
input (x) = 0
sin(0) > 1
<variable>
Ein Variablenname kann aus bis zu 15 Zeichen bestehen
4.1 Befehle / Funktionen in alphabetischer Reihenfolge
<Ergebnis> = analog(<Ausdruck>)
Fragt einen der analogen Eingänge des Robo Interface ab. <Ausdruck> gibt die Nummer des Analogeingangs an. Um die Programmierung zu vereinfachen, sind Konstanten vordefiniert. Eine Liste der Konstanten finden man im Anhang.
dim <variable> (<Konstante>[,<Konstante>]) <typ>
Definiert ein Array (Feld). Felder sind immer global und müssen an der entsprechenden Stelle im Programm (direkt am Anfang) definiert werden. Arrays können ein- oder zweidimensional sein.
Gerade bei Arrays ist der Speicherbedarf sehr groß, deshalb sollte der Typ des Arrays sorgfältig gewählt werden.
Beispiele:
dim positionen (100,4) int
dim werte (20) float
edge <Typ> <Eingang>
Wartet auf eine Flanke am angegeben Eingang. Als Typ kann any (irgendeine), up (steigend, also 0->1) oder down (fallend, also 1->0) angegeben werden.
Beispiele:
edge any 5 Wartet auf eine Änderung an Eingang 5
edge down 4 Wartet auf einen 1->0 Übergang (loslassen des Tasters) an Eingang 4
for <variable> = <Ausdruck 1> to <Ausdruck 2> [step <Ausdruck 3>]
<Anweisungsblock>
next
Führt einen Anweisungsblock ein oder mehrfach aus, bis die Endbedingung <variable> = <Ausdruck 2> erreicht ist. „step“ gibt die Schrittweite an, wird „step“ weggelassen, wird +1 angenommen.
Die Variable <variable> wird von <Ausdruck 1> bis <Ausdruck 2> mit der Schrittweite <Ausdruck 3> durchgezählt. Bei negativen <step> kann auch rückwärts gezählt werden. Die Angabe von <step> ist optional, wird sie weggelassen, wird automatisch 1 angenommen. Hinter next wird nicht, wie teilweise üblich, die Zählvariable angegeben - sie wird automatisch zugewiesen.
(Einschalten der Motoren 1 bis 4)
for
x=1 to 4
motor
x,8
next
(Zählen von 10 bis 0 rückwärts)
for
x=10 to 0 step -1
<Anweisungsblock>
next
(Verschachtelte Schleifen)
for
x=1 to 10 step 2
for
y=20 to 25
<Anweisungsblock>
next
next
(Schrittweiten kleiner als 1)
for
x = 1.0 to 1.5 step 0.1
<Anweisungsblock>
next
function <Funktionsname> [(<variable> <typ> , ...) [typ]]
Definiert eine Funktion mit dem entsprechenden Namen.
Eine Funktion kann keine oder bis zu vier Übergabeparameter haben und / oder bis zu einen Rückgabeparameter.
Beispiele:
function test Definiert eine Funktion ohne Parameter
function test1 (a long,b float) Definiert eine Funktion mit zwei Übergabeparametern
function test2 (a long) long Definiert eine Funktion mit einem Übergabe- und einem
Rückgabeparameter
Beispiel für eine Funktion mit Parametern:
function
summe (a long, b long) long
return
a+b
Der Aufruf dieser Funktion:
x = summe (10,20)
Beispiel für eine rekursive Funktion (gesamtes Programm)
var
x long
function
summe (parameter long) long
var
a long
var
b long
if
parameter <= 1
a
= 1
else
b
= summe (parameter – 1)
a
= parameter + b
end
return
a
main:
print
summe (10)
Tipp: Es fällt auf, dass im else – Teil des Unterprogramms unnötigerweise die Variable b benutzt wird. Kürzer könnte es heißen:
else
a
= parameter + summe(parameter – 1)
end
Dies hat allerdings den Nachteil, dass hierdurch ein Taskwechsel verhindert wird, weil die Variable parameter zur Berechnung auf den mathematischen Stack gelegt wird. Erst nach Rückkehr der Funktion (also nach Ausführung aller Rekursionen) ist der mathematische Stack bereinigt. Außerdem hat der mathematische Stack nur 10 Speicherplätze, eine Berechnung von summe (11) wäre nicht möglich. Durch das Speichern des Ergebnisses in der lokalen Variable b werden diese Effekte verhindert.
Diese Eigenart kommt bei normaler Nutzung kaum zu tragen, sollte es zu überdurchschnittlich vielen Problemen führen, kann sie durch eine Änderung am Interpreter eliminiert werden.
goto <label>
Ganz ohne goto gehts in basic leider nicht. Da es keine Zeilennummern gibt, müssen Labels definiert werden. Das Label main hat eine besondere Bedeutung, es teilt dem Compiler mit, wo das Hauptprogramm beginnt. Dieses Label muss auf jeden Fall in jedem Programm vorhanden sein.
Beispiele:
if
a=b
goto
label1
end
endloop:
goto
endloop
If <Vergleich>
<Anweisungsblock1>
[else
<Anweisungsblock2>]
end
„Falls ... dann ... sonst ...“
Falls
die im Vergleich angegebene Gleichung wahr ist, wird Anweisungsblock
1 ausgeführt, ansonsten (sofern vorhanden) Anweisungsblock 2.
Ein
Anweisungsblock kann aus einem oder mehreren Anweisungen bestehen. Es
dürfen beliebig viele if-else-end Anweisungen ineinander
verschachtelt werden.
Beispiel A:
if
a = 0
motor
1,0 // a ist gleich 0
else
motor
1,8 // a ist ungleich 0
end
Falls a = 0, wird der Motor abgeschaltet, bei allen anderen Werten von a wird er eingeschaltet.
Beispiel B:
if
a = 0
motor
1,0
end
Falls a = 0, wird der Motor abgeschaltet, bei allen anderen Werten von a passiert nichts.
<Ergebnis> = input (<Ausdruck>)
Fragt die digitalen Eingänge des Robo Interface ab. <Ausdruck> kann Werte zwischen 1 und 32 annehmen, das Ergebnis ist entweder 0 (Eingang nicht betätigt) oder 1 (Eingang betätigt).
Beispiele:
a
= input (8)
Fragt Eingang 8 ab und speichert das Ergebnis in
a
if
input (1) = 1
Schaltet den Motor 1 ein, wenn Eingang 1
betätigt wird.
motor
1,8
end
<Ergebnis> = ir
Fragt
den Infrarot-Eingang des Robo Interface ab. Ist keine Taste auf der
Fernbedienung gedrückt, ist das Ergebnis 0.
Um
Abfragen zu vereinfachen, sind Konstanten vordefiniert. Eine Liste
der Konstanten findet man im Anhang.
kill <taskname>
Beendet einen Task und initialisiert ihn neu. Bei einem weiteren start <taskname> startet der Task neu.
motor <Ausdruck 1>,<Ausdruck 2>
Steuert einen Motorausgang des Robo Interface an. <Ausdruck 1> gibt die Nummer des Motors an (1..16), <Ausdruck 2> die Drehrichtung und Geschwindigkeit. <Ausdruck 2> kann Werte zwischen -8 und 8 annehmen, bei negativen Werten dreht der Motor links herum, bei positiven rechts herum. Bei 0 wird der Motor abgeschaltet. Größere oder kleinere Werte bei den Parametern werden ignoriert.
Beispiele:
motor
1,8
Schaltet Motor 1 ein, rechtsdrehend, mit maximaler
Geschwindigkeit.
motor
a,b
Je nach Inhalt der Variablen a und b wird ein Motor ein- oder
ausgeschaltet.
output <Ausdruck 1>,<Ausdruck 2>
Steuert einen Ausgange des Robo Interface an. <Ausdruck 1> gibt die Nummer des Ausgangs an (1..32), <Ausdruck 2> die Geschwindigkeit. <Ausdruck 2> kann Werte zwischen 0 und 8 annehmen. Bei 0 wird der Ausgang abgeschaltet.
print <Ausdruck>
Zwar hat das Robo Interface keinen Bildschirm, aber eine serielle Schnittstelle. Mit diesem Befehl können Variablenwerte oder Konstanten (z.B. zur Fehlersuche) über die serielle Schnittstelle ausgegeben werden.
Beispiele:
print
a Gibt den Inhalt der Variablen a aus.
print
a+5 Addiert 5 zur Variablen a hinzu und gibt das Ergebnis aus.
print
10 Gibt die Konstante 10 aus.
repeat
<Anweisungsblock>
until <Vergleich>
Führt den Anweisungsblock so lange aus, bis der Vergleich wahr ist.
Beispiel:
a
= 1 Stoppt die Motoren 1 - 4
repeat
motor
a,0
a
= a + 1
until
a >= 4
Select <Ausdruck>
case <Konstante 1>
<Anweisungsblock 1>
case <Konstante 2>
<Anweisungsblock 2>
.
.
end
Mit der „select – case“ - Anweisung können Programmverzweigungen elegant gelöst werden. Beispiel: Die Abfrage der IR-Schnittstelle. Hierbei wird der Ausdruck mit den Konstanten verglichen und bei Gleichheit der entsprechende Anweisungsblock ausgeführt. Gleich danach springt die Programmausführung komplett an das Ende der Select-Anweisung, es werden also keine weiteren Vergleiche durchgeführt.
select
ir
Abfrage des IR - Eingangs
case#m1l1
ist die Taste „Motor 1 Links“ gedrückt ?
motor
1,-8 Motor 1 linksherum einschalten
while
ir <> 0 warten, bis die Taste wieder losgelassen wird
end
motor
1,0
Motor 1 abschalten
case
#m1r1 das Gleiche für rechtsherum
motor
1,8
while
ir <> 0
end
motor
1,0
case
#m2l1 weitere Abfragen können folgen
.
.
case
#m2r1
.
.
end
start
<taskname>
Startet einen Task.Wurde der Task vorher durch „stop“ unterbrochen, wird er an der gleichen Stelle ausgeführt, an der er unterbrochen wurde. Ein Task, dessen Ausführung beendet ist (keine Schleife), muss erst durch „kill“ neu initialisiert werden.
stop <taskname>
Stoppt einen Task. Wird der task mit start <taskname> gestartet, macht er dort weiter, wo er aufgehört hat. stop <taskname> ist so etwas wie eine "pause" - Taste.
task
<taskname>
Definiert einen Task. Man kann sich einen Task vorstellen, wie ein Unterprogramm, das zwischendurch immer wieder aufgerufen wird. Die Definition muss immer durch ein "end" beendet werden.
Siehe auch : start, stop, kill
Wird der Programmteil innerhalb des Tasks nicht durch eine Schleife wiederholt (z.B. durch ein goto), wird der Task nur ein einziges mal ausgeführt, kann aber durch „kill“ und anschließendes „start“ neu gestartet werden. Innerhalb eines Tasks können lokale Variablen definiert werden. Wichtig ist: Die Variablendefinition muss direkt hinter der Taskdefinition stehen.
Beispiele:
Task ohne lokale Variablen, nur einmal ausgeführt:
task
reset
motor
1,0
end
Sobald bei der Ausführung das „end“ erreicht wird, bleibt der Task an dieser Stelle stehen.
Task ohne lokale Variablen, dauernd ausgeführt:
task
blinken
blinkloop:
output
1,8
waitms
200
output
1,0
waitms
200
goto
blinkloop
end
Task mit lokalen Variablen:
task
test
var
a long
var
b long
a
= 1
b
= 0
loop:
motor
a,b
a
= a + 1
if
a > 4
a
= 1
end
goto
loop
end
var <variable> <typ>
Definiert (je nach Position innerhalb des Programms) eine globale oder lokale Variable.
Beispiele:
var
variable1 long
var
x int
var
y double
Variablennamen müssen immer mit einem Buchstaben beginnen und dürfen anschließend nur Buchstaben, Zahlen oder den Unterstrich _ beinhalten.
waitms <Ausdruck>
Wartet
die angegebene Zeit in ms. Währenddessen werden andere Tasks
weiter ausgeführt.
Beispiele:
waitms 1000
waitms a
while
<Vergleich>
<Anweisungsblock>
end
Führt den Anweisungsblock so lange aus, wie der Vergleich wahr ist.
5.
Beispielprogramme
Dieses
Programm schaltet Motor 1 und 2 links oder rechts abhängig vom
Zustand der Eingänge 1 und 2. Wird Eingang 8 betätigt, wird
das Programm
beendet.
-------------------
main:
if
input(1)=1
motor
1,8
else
motor
1,-8
end
if
input(2)=1
motor
1,8
else
motor
1,-8
end
if
input(8)=0
goto
main
end
-------------------
Das gleiche Programm, nur mit Multitasking mit einem Task für jeden Motor.
-------------------
task
motor1
t1loop:
if
input(1)=1
motor
1,8
else
motor
1,-8
end
goto
t1loop
end
task
motor2
t2loop:
if
input(2)=1
motor
2,8
else
motor
2,-8
end
end
main:
start
motor1
start
motor2
endloop:
if
input(8)=0
goto
endloop
end
------------------
Für dieses Programm sollte man 8 Lampen an die Ausgänge anschließen. Die Lampen blinken unabhängig voneinander durch die unterschiedlichen Wartezeiten. Dies ist der Funktionstest für die Multitasking-Funktion.
-------------------
task
motor1
motor1loop:
motor
1,8
waitms
500
motor
1,-8
waitms
500
goto
motor1loop
end
task
motor2
motor2loop:
motor
2,8
waitms
525
motor
2,-8
waitms
525
goto
motor2loop
end
task
motor3
motor3loop:
motor
3,8
waitms
550
motor
3,-8
waitms
550
goto
motor3loop
end
task
motor4
motor4loop:
motor
4,8
waitms
575
motor
4,-8
waitms
575
goto
motor4loop
end
main:
start
motor1
start
motor2
start
motor3
start
motor4
endloop:
goto
endloop
| Projekte | |