Roboter steuern mit ROBOprogy


zur homepage

Forth - Befehle

 

Inhaltsangabe

 

A) Befehlsübersicht des Compilers Forth -> P-Code

B) Besondere Befehle in Forth

Allgemeines zu Forth

Die Entstehungsgeschichte lässt sich auf vielen Internetseiten nachlesen und muss hier nicht wiederholt werden. Zentrale Idee sind mindestens zwei Stacks – eines für die Rücksprungadressen und eines für die Daten. Bei Bedarf können weitere Stacks eingerichtet werden, beispielsweise für längere Zahlen in Fließkommadarstellung. Dafür ist aber kein Platz im winzigen MEGA32. Wenn sich im Zeitscheibenbetrieb mehrere Programme den Rechner teilen müssen, bekommt jedes seinen privaten Satz an Stacks. Manche, vielleicht auch alle modernen Programmiersprachen arbeiten intern mit Stacks, verheimlichen diese aber vor dem Benutzer. In der Sprache C werden Daten und Rücksprungadressen auf einem einzigen Stack bunt gemixt.

Was sind Rücksprungadressen? Jedes große Programm wird in kleinere Einheiten aufgeteilt, die meist noch kleinere Einheiten aufrufen. Im abgebildeten Beispiel sei die Einheit W so universell verwendbar, dass sie von vier verschiedenen Programme aufgerufen wird, eine kleine Aufgabe erfüllt und dann aber an der richtigen Stelle weiter gemacht werden muss. Die Adresse der Stelle, an der die CPU nach Abschluss der Einheit W fortfahren muss, heisst Rücksprungadresse.

Was sind Stacks?

Ein Stack ist ein Stapel, auf dem die CPU Zahlen abgelegen und auch wieder wegnehmen kann, so eine Art Zwischenspeicher. Die zuletzt abgelegte Zahl wird als „Top of Stack“, kurz TOS bezeichnet. So weit ist ein Stack recht gut vergleichbar mit einem Aktenstapel in einer Behörde. Das Besondere ist nun, dass man diese übereinanderliegenden Daten verknüpfen kann und die Ergebnisse wieder auf das Stack legt. Zum Veranschaulichen diene im folgenden Beispiel eine Addition:

Die untersten drei Werte auf dem Stack (es können auch mehr oder weniger sein) werden nicht angetastet, der Additionsbefehl fasst die beiden obersten Bytes zu einem einzigen zusammen. Wenn vor der Addition weniger als zwei Elemente auf dem Stack sind, wird kein Fehler gemeldet, das Ergebnis ist aber unsinnig.

Trennzeichen zwischen zwei Anweisungen ist (mindestens) ein Leerzeichen (Space) oder Tabulator oder Zeilenfortschaltung (RETURN-Taste). Strukturmaßnahmen wie Einrücken oder Gliedern werden zwar dringend empfohlen, um die Les- und Verstehbarkeit der Funktionen zu erhöhen, wirken sich aber nicht auf das Übersetzungsergebnis aus. Verwenden Sie ausgiebig Kommentare, damit Sie auch nach Wochen noch verstehen, was die einzelnen Programmteile bewirken sollen. Es gibt zwei Arten, den Beginn eines Kommentars zu kennzeichnen:

Entweder ein \ , der links und rechts von jeweils mindestens einem Leerzeichen oder Tabulator eingerahmt werden muss, damit es der FORTH-Compiler als Steuerzeichen erkennt. Nach diesem \ wird der gesamte Text bis zum Zeilenende (RETURN-Taste) als Kommentar betrachtet und vom Compiler überlesen.

Oder eine ( für die die gleichen Abgrenzungen wie für \ eingehalten werden müssen. Nach dieser öffnenden Klammer wird der gesamte Text bis einschließlich ) als Kommentar überlesen. Dahinter kann das Programm in der gleichen Zeile fortgesetzt werden. Das Einschließen zwischen runde Klammern eignet sich eher für kurze Texte, wie sie beispielsweise gleich nach dem Namen bei einem Funktionsbeginn stehen sollten. Dort wird angegeben, ob und wie sich das DatenStack verändert.

Es wird nicht zwischen Groß- und Kleinschreibung unterschieden. Jede Anweisung muss vor ihrer Verwendung entweder

(in Maschinensprache) im unveränderlichen Speicher des ROBOprogy existieren (dann ist sie in der Befehlsübersicht auf der Startseite angegeben) oder

sie wurde vorher vom Roboterprogrammierer im PC-Editor geschrieben, in P-Code übersetzt und an den ROBOprogy übermittelt oder

sie ist eine Zahl.

In den beiden ersten Fällen ist der Name in einer der vielen Tabellen im PC zu finden. Andernfalls wird "Parser-Fehler" gemeldet und die Übertragung abgebrochen. Deshalb sind alle Programme in Forth strikt "bottom-up" aufgebaut - zuerst werden einfache Programmteile und elementare Funktionen geschrieben und sofort getestet. Diesen Bausteinen werden zu immer komplizierteren Einheiten zusammengesetzt und ebenfalls getestet. Solange, bis der Roboter die gewünschten Reaktionen zeigt.

Bei Forth und P-Code dreht sich alles um das Stack: Da werden Zwischenergebnisse abgelegt und wieder geholt, da werden Informationen von einem Programmteil zum anderen weiter gegeben. Meist wird das, was zuletzt dort abgelegt wurde, zuerst wieder geholt. Wenn die Reihenfolge nicht stimmt, muss mit SWAP und ROT umsortiert werden. Wenn man einen Wert mehrfach benötigt, kann man ihn mit DUP duplizieren oder vorübergehend auf das ReturnStack übertragen. Wenn das zu beschwerlich wird, kann man Ergebnisse in (globale) Variable auslagern. Erstaunlicherweise genügen auch für recht umfangreiche Programme zur Robotersteuerung kleine Stacks mit 10 bis 20 Plätzen.

Fast alle vorbereiteten Funktionen "essen" ihre Argumente auf. Sie nehmen sich also die Werte, die sie benötigen, von DatenStack. Die wenigen Ausnahmen sind die Umsortierbefehle wie SWAP oder ?DUP. Achten sie bei Ihren Programmen darauf, dass Funktionen keine Werte auf dem Stack „vergessen“ – der ROBOprogy hat nicht genügend Platz dafür. Es ist sehr sinnvoll, beim Testen einzelner Funktionen immer wieder mal mit dem Befehl .S den aktuellen Stand auf dem DatenStack zu überprüfen: Damit wird die Belegung des DatenStacks im ROBOprogy zum PC gemeldet und man kann diese mit den vorgesehenen Werten vergleichen. Der oberste Wert (Top-of-Stack) steht immer ganz rechts, zwischen den eckigen Klammern steht sicherheitshalber die Anzahl der Stack-Einträge. Probieren Sie auf diese Weise aus, was die Befehle DUP, 2DUP, ROT, >R, R@, etc... mit den Stacks machen.

Mit dem Befehl RAM kann man sich am PC die aktuelle Speicherbelegung im ROBOprogy ansehen.

Alle Programmiersprachen arbeiten mit Stacks - es geht nicht anders. Wohin sonst mit den Return-Adressen bei geschachtelten Unterprogrammen? Die wenigen Versuche aus grauer Vorzeit, Computer ohne Stack zu bauen, sind gescheitert - wie die PDP12 der Firma DEC, von Intersil als einer der ersten Mikroprozessoren überhaupt unter der Bezeichnung 6100 nachgebaut. Weil sie kein Stack hatten. Populäre Programmiersprachen wie C++ mixen Daten und Returnadressen auf einem Stack. Solange sich der Compiler noch zurechtfindet, mag das ja funktionieren. Forth sorgt dagegen für Sauberkeit: Daten auf das DatenStack, Adressen auf das ReturnStack. Damit kann man übersichtlich arbeiten und schnell ist es auch.

Wenn die Stacks wegen vergessener Daten oder das RAM nach vielen Programmänderungen voll sind oder der ROBOprogy seltsam reagiert, kann man ihn wahlweise durch kurzes Unterbrechen der Stromzufuhr oder durch den Befehl "mreset" wieder in den Normalzustand versetzen. Dann schickt man ein korrigiertes Programm aus dem PC zum ROBOprogy und testet weiter. Das kostet nur wenige Sekunden.

Zurück zur Übersicht

Einfache Befehle

Nun werden die Befehle etwa in der Reihenfolge vorgestellt, wie sie auf der Startseite des Programms aufgelistet sind.

Beginnen wir mit den Befehlen : und ;

Damit werden eigene Funktionsdefinitionen begonnen bzw. beendet. Üblicherweise steht der Doppelpunkt ":" am Zeilenanfang, damit werden sowohl der PC als auch der ROBOprogy vom "Immediate mode" in den "Compile mode" umgeschaltet. Anschließend muss - wie üblich durch mindestens einen Zwischenraum (space) begrenzt - der Name der damit begonnenen Funktionsdefinition folgen. Dann kommen mehr oder weniger sinnvolle Programmschritte und zuletzt das Ende-Signal dieser Funktionsdefinition, der Strichpunkt ";" . Damit werden sowohl PC als auch ROBOprogy wieder in den Immediate mode zurückgeschaltet.

Im "Immediate mode" werden alle Befehle sofort und nach kurzer Kontrolle zum ROBOprogy geschickt und dort ausgeführt, sofern sie in einer der PC-internen Tabellen verzeichnet und damit "bekannt" sind. In dieser Betriebsart sind aber nicht alle Befehle erlaubt, da beispielsweise ein übertragenes THEN allein keinen Sinn macht. Da hätte vorher ein IF übertragen werden müssen. Der Befehl IF darf aber erst dann ausgeführt werden, wenn das abschließende THEN bereits im Speicher steht. Wegen dieser Zwickmühle dürfen solche Programmsteuerbefehle nur komplett innerhalb von Funktionen übertragen werden.

Im "Compile mode" werden derartige Strukturen zuerst im PC kontrolliert und dann erst zum ROBOprogy geschickt. Ein Strukturfehler liegt vor, wenn beispielsweise zuerst eine Fallunterscheidung (mit IF) und dann eine Wiederholung (mit BEGIN) begonnen werden und dann zuerst die Fallunterscheidung mit THEN beendet wird. In Mathematik ist das so, als ob so [( begonnen und so ]) geendet wird. Auch andere grobe Fehler werden im PC zusätzlich kontrolliert, die später diskutiert werden. Wurde solch eine Funktionsdefinitionen fehlerfrei zum ROBOprogy übertragen, kann sie im Immediate mode sofort getestet und notfalls verbessert werden. Verbessern heißt, eine neue Variante vom PC zum ROBOprogy schicken. Die vorhergehende ist dann ungültig.

Konstruieren Sie zunächst nur kurze Funktionen, um den Überblick zu behalten. Zwischen : und ; sollten maximal 20 Befehle oder vorher definierte Funktionen stehen. Und strukturieren Sie durch Einrücken bzw. Tabulator! Der Verfasser hat zwar schon überlange Funktionen geschrieben, die dicht gedrängt mehr als 20 Zeilen benötigten, so etwas sollte aber die Ausnahme bleiben. Bei solchen Mammutfunktionen werden Änderungen und Ergänzungen problematisch. Sehen Sie sich im Beispiel Heulboje an, in wie viele Einzelfunktionen diese Aufgabe zergliedert wurde. Versuchen Sie, alles in eine einzige Funktion zu packen und prüfen Sie dann mit dem Befehl RAM, wie viele Speicherplätze Sie eingespart haben. Hat sich die Arbeit gelohnt?

Das folgende "k" in der Befehlsliste ist nur ein Platzhalter für das Kennzeichen, das jedem konstanten Wert vorangestellt wird, bevor er zum ROBOprogy geschickt wird. Dieser erkennt dann, dass kein Befehl, sondern ein Zahlenwert folgt, der im ROBOprogy einfach ganz oben auf das DatenStack gelegt wird. Der Programmierer braucht kein k einzugeben, das erledigt der PC automatisch.

Nun kommt der ".", der gewissermaßen das Gegenteil von k bewirkt und den man vor allem in den Testphasen benötigt (wann enden diese eigentlich?). Dieser Befehl heißt "print", nimmt den obersten Wert vom DatenStack und schickt ihn zum PC. Dort wird er am Bildschirm sowohl in dezimaler als auch in hexadezimaler (16-System) Basis angezeigt. Wenn das DatenStack vorher leer war, folgt stattdessen eine Fehlermeldung und der ROBOprogy startet neu.

Zurück zur Übersicht

 

Mathematische Befehle

Alle mathematischen Befehle liefern stets Ergebnisse im Bereich 0 bis 255 (in Hex ist das der Bereich $00 bis $FF). Mathematisch gesprochen gibt es nur vorzeichenfreie Werte modulo 256.

+          addiert die beiden obersten Werte des DatenStacks und legt das Ergebnis auch wieder dort ab.

-          subtrahiert entsprechend. Enthielt das Stack vorher 55 44 33, so wird es nachher 55 11 enthalten.

            Enthielt das DatenStack vorher 55 33 44, so enthält es nachher 245 (=$F5). Komisch? Rechnen Sie mal im HEX-System nach!

U*(MAX=255)       multipliziert, ohne Vorzeichen zu beachten, wobei als höchstes Ergebnis nur 255 vorkommen kann.

Beispiele: Enthält das Stack vorher 5 6 7, so wird es nachher 5 42 enthalten. Enthält das Stack vorher 5 26 37, so wird es nachher 5 194 enthalten. Nachrechnen ergibt 26*37=962 in dezimaler Schreibweise bzw. $3C2 in Hex. Nur die beiden niederwertigen Zeichen $C2 werden auf das Stack gelegt und die heißen in dezimaler Schreibweise 194.

In dieser Forth-Version gibt es eine Division, diese beansprucht aber sehr viel Zeit.

U*/      erwartet drei beliebige Zahlen auf dem Stack. Steht dort beispielsweise 55 30 20, so wird zuerst 55*30 berechnet und ergibt das Zwischenprodukt 1650. Dieses wird dann durch 20 dividiert und das (abgerundete) Ergebnis 82 auf das Stack gelegt.

64*/     erwartet nur zwei beliebige Zahlen auf dem Stack, die dritte (64) ist bereits im Befehl enthalten. Enthält das Stack beispielsweise 130 103, so wird zuerst das Zwischenprodukt 13390 berechnet und nach einer schnellen Binär-Verschiebung das Ergebnis 209 auf das Stack gelegt. Dieser Befehl ist etwa 30 mal schneller als 130 103 64 */

128*/   arbeitet fast genauso wie 64*/. Bei Robotersteuerungen lässt sich dieser Befehl vorteilhaft einsetzen, um kleine Korrekturen an Sensorwerten vorzunehmen. Soll beispielsweise die Richtung eines Lichtzieles durch Vergleich der Messwerte zweier nebeneinander montierter Fototransistoren A und B bestimmt werden und liefert A trotz gleicher Beleuchtung nur 86% (=128/149) des Messwertes von B, so lässt sich das leicht korrigieren, wenn jede A-Messung mit dem Befehl 149 128*/ "frisiert" wird.

256*/   gehört auch zu dieser Gruppe, lässt sich aber nicht durch 130 103 256 */ nachbilden, weil man 256 nicht mit 8 bit darstellen kann. Diesen Befehl kann man auch anders sehen: 130 103 * ergibt (in 16-Bit-Hex) das Zwischenprodukt $344E, davon wird nur das obere Byte $34 auf das Stack gelegt und das lautet dezimal geschrieben 52. Das untere Byte wird dabei ignoriert. Die beiden Befehle 256*/ und * ergänzen sich: mit dem einen kann man das high-byte erzeugen, mit dem anderen das low-byte. Will man wissen, ob der weiter oben erwähnte Multiplikationsbefehl * ein korrektes 8-bit-Ergebnis liefert, prüft man, ob der Befehl 256*/ das Ergebnis 0 liefert.

2*        schiebt den Byte-Inhalt des obersten Stackeintrages eine Stelle nach links. Mathematisch gesehen wird der Wert verdoppelt. Ab 127 ist die mathematische Interpretation des Resultats falsch, weil Zahlen über 256 nicht in 8 bits passen.

2/         schiebt den Byte-Inhalt eine Stelle nach rechts. Mathematisch gesehen wird der oberste Stackeintrag halbiert und dabei immer abgerundet.

1+        erhöht den obersten Stackeintrag um eins. Vorsicht: 255 1+ liefert den Wert 0 (overflow).

1-        vermindert den obersten Stackeintrag um eins. Vorsicht: 0 1- liefert den Wert 255.

MIN    lässt nur den kleineren der beiden obersten Stackeinträge stehen und entfernt den größeren.

MAX   lässt den größeren der beiden obersten Stackeinträge stehen.

Zurück zur Übersicht

 

Logische Verknüpfungen

Logik-Befehle werden meist vor IF-Entscheidungen oder als Ende-Kriterium bei Wiederholungen benötigt. Damit kann man aber auch Eingänge zusammenfassend abfragen, etwa so: Wenn Eingang A = 1 und Eingang B = 0 und Motor 2 langsamer als 30, dann....

AND   bildet die bitweise UND-Verknüpfung der beiden obersten Werte auf dem Stack.

Im Dezimalsystem mag die Ergebnisbildung rätselhaft erscheinen: 88 67 AND liefert 64, das klingt nicht logisch.

Im Hex-System ist sie leichter verständlich: $58 $43 AND liefert $40, das kann man sich schon leichter erklären.

Im Binärsystem ist dagegen alles sehr einfach, wenn man die Darstellungen untereinander schreibt:

01011000 = $58

01000011 = $43

-----------------  AND-Verknüpfung durchführen

01000000 = $40 ist das Ergebnis. Eine 1 gibt es nur, wenn in beiden Zeilen eine 1 steht.

 

OR      bildet die bitweise UND-Verknüpfung der beiden obersten Werte auf dem Stack. Obige Zahlen liefern:

01011000 = $58

01000011 = $43

-----------------  bedeutet OR-Verknüpfung

01011011 = $5B ist das Ergebnis. In jeder Spalte muss mindestens eine 1 stehen.

 

XOR    bildet immer dann eine 1, wenn die Bits der beiden obersten Werte auf dem Stack verschieden sind.

01011000 = $58

01000011 = $43

------------------  bedeutet XOR-Verknüpfung

00011011 = $1B ist das Ergebnis. Die Bits müssen verschieden sein, um eine 1 zu erzeugen.

Zurück zur Übersicht

 

Das DatenStack umsortieren

Diese Befehlsgruppe bildet den offensichtlichsten Unterschied zu anderen Programmiersprachen: Es ist Sache des Programmierers, der jeweils nächsten Funktion die benötigten Zahlenwerte „mundgerecht“ bereit zu legen. Als Folge kann FORTH besonders schnell arbeiten und übertrifft alle anderen Programmiersprachen außer Assembler. Auf diese Geschwindigkeit kommt es zwar bei dieser kleinen Robotersteuerungen nicht unbedingt an (bei einer größeren vielleicht schon), sondern vielmehr auf die eingesparten Befehle, wenn man dem Computer nicht beibringen muss, wie er diese Umsortierung „in real time“ vorzunehmen hat. Das macht das Programm so kompakt und kurz. Es gibt aber auch weitere Notwendigkeiten: Häufig werden Messwerte oder Zwischenergebnisse mehrfach benötigt, manchmal sind Werte im DatenStack überflüssig. Dazu dienen folgende Befehle:

Befehl

Stack vorher

Stack nachher

DUP

33 44 55

33 44 55 55

legt den obersten Wert noch mal drüber

?DUP

22 33

22 33 33

ungleich 0? -> duplizieren (Spezialbefehl)

?DUP

66 0

66 0

gleich 0?   -> nichts machen

2DUP

11 22 33

11 22 33 22 33

das oberste Daten-Paar duplizieren, oft vor Vergleichen

DROP

44 33 77

44 33

den obersten Wert entfernen

2DROP

44 33 77

44

das oberste Paar entfernen

SWAP

22 33 44

22 44 33

das oberste Paar vertauschen

OVER

66 77 88

66 77 88 77

den vorletzten Wert nochmals oben drauf

ROT

22 33 44

33 44 22

den dritten Wert ganz nach oben, die anderen rutschen nach (erinnert manche Leute an Rotieren)

Zurück zur Übersicht

 

Daten von einem Stack zum anderen bewegen

Wenn "tiefere" Stack-Einträge benötigt werden, genügen die eben genannten Befehle nicht mehr. Dann empfiehlt es sich, entweder diese Werte rechtzeitig in Variable auszulagern und bei Bedarf zu holen oder das Return-Stack zu missbrauchen. Variable kosten Platz und Bearbeitungszeit, können aber das Programm übersichtlicher machen. Das Return-Stack ist andrerseits "Privatangelegenheit" von P-Code bzw. Forth und man sollte da nichts ändern. Wenn man aber weiß, bei welchen Gelegenheiten der Prozessor auf das Return-Stack zugreift, darf man schon mal kurz Daten dort zwischenspeichern – Sorgfalt vorausgesetzt! Das geht ganz sicher schief, wenn eine Funktion oder eine DO ... LOOP begonnen oder beendet wird. Da erwartet das Maschinenprogramm, dass die Daten auf dem Return-Stack unverändert so vorhanden sind, wie es diese dort abgelegt hat. Das geht auch schief, wenn eine am Ende einer Funktion die Adresse vom ReturnStack geholt wird, an der das Programm fortfährt. Wenn da ein falsches Byte geholt wird, springt das Programm nicht an die Stelle, wo es herkam.

Befehl

DatenStack vorher

DatenStack nachher

ReturnStack vorher

ReturnStack nachher

>R

22 33

22

??

?? 33

R>

44 55

44 55 11

?? 11

??

DUP>R

11 22

11 22

??

?? 22

R>DROP

77 88

77 88

?? 55

??

R@

22 33

22 33 66

?? 66

?? 66

Zurück zur Übersicht

 

Vergleichsbefehle

In jeder Robotersteuerung findet man Vergleichsbefehle. Unproblematisch sind:

=

22 33 44

22 0

die obersten beiden Werte waren ungleich

=

44 77 77

44 255

die obersten Werte waren gleich

UNGLEICH

55 66 77

55 255

heißt "ungleich" und ist das Gegenteil von =

0=

55 66

55 0

der oberste Wert war nicht null

0=

22 0

22 255

Der oberste Wert war 0

Die 8-Bit breiten Datenwerte eignen sich hervorragend für alle Aufgaben der Robotersteuerung, sind aber zu klein, um damit vorzeichenbehaftete Mathematik zu betreiben. Die Vergleichsbefehle > und < werden nur verwendet, wenn mindestens 16-Bit-Daten verarbeitet werden. Da aber der Speicherbereich des Atmel Mega32 dafür zu klein ist, sind diese beiden Befehle nicht vorgesehen. Bei folgenden Befehlen erinnert das U daran, dass unsignierte, also vorzeichenlose 8-Bit-Werte verglichen werden.

U>

22 33 44

22 0

denken Sie sich das Vergleichszeichen dazwischen geschrieben: 33 > 44, dann lautet die Antwort "nein".

U>

22 44 11

22 255

44 > 11 ergibt die Antwort "ja".

U<

22 33 44

22 255

33 < 44, ergibt die Antwort "ja".

U<

22 44 33

22 0

44 < 33, ergibt die Antwort "nein".

 

Zurück zur Übersicht

 

Variable

Mitunter ist es zweckmäßig, Variable zu verwenden. Angesichts des kleinen RAM-Speichers von nur 2048 Bytes erschien es sinnvoll, maximal 255 Variable zu erlauben, das vereinfacht die Programmierung. Für jede Variable kann man einen beliebigen, aber eindeutigen Namen verwenden, dieser wird im PC gespeichert und bekommt eine Adresse im PC zugewiesen. Nur diese Adresse wird zum MEGA32 übertragen und verwendet. Vordefinierte Funktionsnamen oder Kombinationen wie $01, die als Zahlen interpretiert werden können, dürfen nicht als Variablennamen verwendet werden. Der Rechner soll nicht raten müssen, sondern einen Roboter steuern. Wie teilt man dem Rechner mit, welche Variable man benutzen will? Zulässig sind die Befehle

VAR tempo

Var Hoehe

„Var“ ist ein Schlüsselwort, das den Compiler im PC anweist, dem folgenden token (in diesem Fall dem Begriff "tempo") eine Adresse zugewiesen und dem Begriff "Hoehe" die nächste. Wenn später der Begriff tempo auftaucht, wird die entsprechende Adresse auf das DatenStack gelegt.

Wer unbedingt will, kann auch auf Namen verzichten und gleich mit numerischen Adressen, z.B. 47, arbeiten. Fraglich bleibt aber, ob dadurch das Programm lesbarer wird. Das vorangestellte C in folgenden Befehlen soll daran erinnern, dass nur 8-Bit-Werte verwendet werden.

 

C@

55 66

55 77

kopiert den Inhalt der Variablenadresse 66 auf das DatenStack.

C!

11 22 33

11

speichert den Wert 22 unter der Adresse 33

C+!

33 6 99

33

addiert 6 zum Inhalt der Adresse 99

C+!

22 254 3

22

subtrahiert 2 vom Inhalt der Adresse 3 (dezimal 254 = $FE = $-2)

Für erfahrenere Programmierer: Schreibt man die Befehle

99 tempo 1+ c!

so bekommt – mit der obigen Vereinbarung - die Variable "Hoehe" den Wert 99 zugewiesen. So etwas nennt man indizierte Adressierung. Die Erklärung ist recht einfach: Zuerst wird die Zahl 99 auf das DatenStack gelegt, darüber die Adresse der Variablen „tempo“. Der folgende Befehl 1+ wirkt nur auf den obersten Wert, deshalb wird die Adresse geändert. Zum Schluss nimmt der Befehl c! beide Werte und speichert 99 unter der Adresse tempo+1 ab.

Für die weiter unten diskutierten Vielfachentscheidungen wie "AMODEJUMP" werden die Spezialbefehle

AM! BM! CM! DM!

benötigt. Diesen ist gemeinsam, dass sie den obersten Wert vom DatenStack nehmen (der im Bereich 0...15 liegen muss) und an Prozessor-internen Stellen speichern. Der Inhalt dieser Speicherplätze kann mit den Befehlen AM@ .. DM@ zurückgelesen werden.

Zurück zur Übersicht

Digitale Ein-/Ausgabebefehle

Von digitalen Befehlen wird gesprochen, wenn dem Programm nur zwei Zustände mitgeteilt werden: Ja oder nein, ein oder aus. Der ROBOprogy enthält in der kleinen A-Version sechs digitale Ausgänge mit zwei selbsterklärenden Befehlen pro Ausgang:

P1ON und P1OFF für den Ausgang Nummer 1

...... bis

P6ON und P6OFF für den Ausgang Nummer 6

Dazu kommen sieben digitale Eingänge 1IN bis 7IN, an die üblicherweise Kontaktschalter angeschlossen werden. Liegt die Spannung an den entsprechenden Anschlüssen zwischen -0,3 V und +0,8 V, so legt der Befehl den Wert 0 auf das DatenStack, das bedeutet "nein" oder FALSE. Liegt die Spannung zwischen 2,5 V und 5,4 V, so legt der Befehl den Wert 255 auf das DatenStack, das bedeutet "ja" oder TRUE. Spannungen außerhalb dieser Grenzen sind zu vermeiden und können den Prozessor zerstören. Meist folgt im Programm eine Fallunterscheidung, also der Befehl IF.

Zurück zur Übersicht

Analoge Eingänge (ADC-Wandler)

Benötigt das Programm eine genauere Aussage über die Eingangsspannung im Bereich 0 V bis +5 V, weil beispielsweise Fototransistoren Helligkeitsunterschiede messen sollen, verwendet man die verfügbaren sechs ADC-Wandler. In der Grundeinstellung werden diese der Reihe nach jede Millisekunde umgeschaltet, also erhält man 167 Messwerte pro Sekunde, die in je einem Zwischenspeicher abgelegt werden. Beim Abfragen mit einem der Befehle

1ADC bis 6ADC

wird der aktuelle Wert im Bereich 0 (entspricht 0 V) bis 255 (entspricht +5 V) auf das DatenStack kopiert. Legt man den Fototransistor zwischen 0 V (Masse) und ADC-Eingang, so benötigt man noch einen Widerstand vom ADC-Eingang nach +5 V. Dessen Wert soll zwischen 1 kOhm und 100 kOhm liegen. Bei typischer Helligkeit sollte über den Fototransistor eine Spannung von etwa 2,5 V zu messen sein. Bei dieser Schaltung gilt: Je mehr Licht auf den Fototransistor fällt, desto kleiner werden die Messwerte. Wird der Fototransistor vollständig verdunkelt, muss man den Messwert 255 erhalten.

Manchmal reichen 167 Messungen pro Kanal nicht aus, dann kann man über den Befehl CONFIG! die Abtastrate des ADC im MEGA32 ändern. Hier geht es ausschließlich um das Bit 0, andere Bits haben andere Wirkungen. Gibt man den Befehl

1 config!

so wird bit0 in einem Spezialspeicher auf 1 gesetzt und 5ADC + 6ADC werden jede Millisekunde gewandelt. Das ergibt 1000 Messwerte pro Sekunde. Der ROBOprogy arbeitet schnell genug, um bei diesem Tempo problemlos auswerten zu können. Als Nebenwirkung werden 1ADC bis 4ADC jede Millisekunde der Reihe nach umgeschaltet, das ergibt 250 Messungen pro Sekunde und Kanal.

Was man mit den ADC-Eingängen noch machen kann, wird in den Kapiteln Frequenzselektive Amplitudenmessung und Grundeinstellungen erklärt, ebenso die Bedeutung der anderen Bits im Configurations-Speicher.

Zurück zur Übersicht

 

Steuerung der Antriebsmotore

Man kann fahrbare Roboter wie Autos bauen: Ein Antriebsmotor mit Diffentialgetriebe und gelenkten Rädern. Der mechanische Aufwand ist jedoch erheblich höher als bei der meist verwendeten Bauart: Zwei Motore treiben unmittelbar zwei gegenüber liegende Räder an, ein oder zwei Stützräder (oder "Warzen") sorgen dafür, dass der Roboter nicht kippt. Damit kann sich der Roboter auf der Stelle drehen, ist viel wendiger als die Imitation eines Autos und hat bei Vorwärts- und Rückwärtsfahrt das gleiche Fahrverhalten. Der ROBOprogy hat für diese meist verwendete Version vorbereitete Befehle. Stand der Technik ist, die Motorleistung durch häufiges Ein- und Ausschalten des Stromes (PWM) zu regulieren, beim ROBOprogy 1000 mal pro Sekunde. Dafür gibt es zunächst die einfachen Befehle ohne Geschwindigkeitssteuerung: 1MOT und 2MOT für linken und rechten Motor. Für Vorwärtsdrehung muss eine positive Zahl zwischen 1 (extrem langsam) und 127 (Vollgas) übergeben werden, bei entsprechenden negativen Werten dreht sich der Anker entgegengesetzt. Der Befehl

0 1mot         

schaltet den 1. Motor auf Leerlauf, der Befehl

128 1mot

bremst ihn (Der Motor wird dann kurzgeschlossen). Für den 2. Motor gilt entsprechendes. Recht praktisch sind die Befehle MMGO und SPIN für Geradeausfahren und Drehen um die eigene Achse. Der Befehl

60 mmgo

ist eine Abkürzung für

60 1mot 60 2mot

Der ROBOprogy sorgt mit einer Regelschaltung dafür, dass die Drehzahl jedes Motors weitgehend unabhängig von der Belastung bleibt. Dazu wird während kurzer Strompausen die Dynamospannung jedes Motors gemessen und mit dem Sollwert verglichen. Ist die Drehzahl zu gering, wird der mittlere Strom erhöht und umgekehrt. Der Roboter fährt tatsächlich Schrittgeschwindigkeit und behält diese auch bei kleinen Hindernissen bei, wenn man

15 1mot

programmiert. Versuchen Sie, den Motor langsam – nicht schlagartig! – stärker zu belasten und Sie werden deutlich spüren, dass er „Gas gibt“. Dabei wird intern mitgezählt, wie oft nacheinander (in 24 ms Abständen) der Motorstrom erhöht werden muss, um den Sollwert zu erreichen. Fünf mal ist normal; wird der Maximalwert 15 erreicht, ist der entsprechende Motor sehr wahrscheinlich blockiert. Wenn kurz nach Umkehr der Drehrichtung wieder dieser Maximalwert gemeldet wird, kann sich der Motor offenbar nicht drehen. Dann sollte die Stromzufuhr abgeschaltet werden, um Überhitzungsschäden an Motor und Elektronik zu vermeiden.

Die einfachste Überwachung beschränkt sich darauf, die beiden vom Befehl MLOAD gelieferten Werte zu addieren: Ist das Ergebnis 30, so laufen beide Motore seit mindestens 400 ms untertourig, wahrscheinlich hängt der Roboter an einem Hindernis fest. Das Programm "eingeklemmt?" sieht etwa so aus und kann in beliebigen Programmschleifen verwendet werden:

: eingeklemmt? mload + 28 >               \ beide Motore untertourig?

                        if          beep                \ jammern

                                   0 mmgo           \ beide Motore abschalten

                                   20 100ms        \ 2 Sekunden warten, nichts tun

                                   brumm             \ Pause ist beendet, auf zu neuen Taten

                        then ;                           \ hier geht es in jedem Fall weiter

Wenn dieses Signal MLOAD regelmäßig ausgewertet wird, kann man Schäden an Motor und Steuer-IC verhindern und die Berührungssensoren wirkungsvoll unterstützen oder sogar ersetzen.

Zurück zur Übersicht

 

Servosteuerung

Servos sind preiswert und lassen sich sehr vielseitig einsetzen. Üblicherweise reagieren sie auf kurze Impulse: Sind diese 1,5 ms breit, läuft das Servo in Mittelstellung und bleibt dort. Bei kürzeren bzw. längeren Impulsen drehen sie mit viel Kraft nach rechts bzw. links und bleiben dann wieder stehen. Damit lassen sich Spinnenbeine, Hebelarme und Lenkungen bauen. Aus diesem Grund wurden beim ROBOprogy Anschlussmöglichkeiten für neun Servos vorgesehen, die sich unabhängig voneinander mit den Befehlen

1SERVO bis 9SERVO

steuern lassen. Als kurzes Beispiel sei gezeigt, wie man ein Servo fünfmal "winken" lassen kann:

: 0li 127 0 do i 4SERVO 5 ms loop ;                        \ Reihe 0/1/2/3....126/127 durchlaufen, ganz nach links

: li0 127 0 do 127 i - 4SERVO 5 ms loop ;               \ Reihe 127/126/125...2/1/0 durchlaufen, zurück zur Mittelstellung

: 0re 127 0 do 0 i - 4SERVO 5 ms loop ;                 \ Reihe 0/-1/-2/...-126/-127 durchlaufen, ganz nach rechts

: re0 127 0 do i 127 - 4SERVO 5 ms loop ;             \ Reihe -127/-126/-125...-2/-1/0 durchlaufen, und zurück

: wink 0li li0 0re re0 ;                                       \ einmal winken

: winke 5 0 do wink loop ;                               \ fünfmal winken

Sicher kann man da einiges zusammenfassen, das sei jedem als Übungsmaterial überlassen. Der Befehl "i" ist neu und wird im Kapitel Wiederholungen besprochen; er legt eine Kopie der Zählervariablen auf das DatenStack.

Zurück zur Übersicht

 

Zeitgeber

Es gibt zwei Arten Zeitgeber: Die eine Sorte kann man starten und danach jederzeit abfragen, wie weit die Zeit fortgeschritten ist und entsprechend handeln. Zwischendurch lassen sich andere Aufgaben erledigen. Zu dieser Gruppe gehören die beiden Kurzzeituhren AT1! AT1@ BT1! BT1@ und die beiden Langzeituhren AT100! AT100@ BT100! BT100@. Das Ausrufezeichen ! bedeutet "speichern", es verlagert den obersten Wert vom DatenStack in eine bestimmte RAM-Speicherstelle und startet damit die Uhr. Ein internes Interrupt-Programm verringert entweder nach einer oder nach 100 ms den gespeicherten Wert, bis 0 erreicht wird. Dann passiert nichts mehr. Die Gesamtdauer lässt sich beliebig ändern, indem man einfach den aktuellen Wert überschreibt. Das @-Zeichen bedeutet, dass der Wert von der angegebenen RAM-Speicherstelle zum DatenStack kopiert wird. Die Uhr wird dadurch nicht beeinflusst.

Die andere Art Zeitgeber legt nach dem Start den ROBOprogy lahm, bis die Zeit abgelaufen ist. Dazu gehören MS und 0,1S, die jeweils einen Wert von 1 bis 255 auf dem DatenStack erwarten und dann die Programmausführung so lange stoppen, bis null erreicht ist.

 

Beispiel: Zeitabhängiges Fahren des Roboters

Der Roboter soll 5 Sekunden lang schnell vorwärts fahren, sich 1,4 Sekunden lang drehen und dann 3 Sekunden lang langsam rückwärts fahren. Während der gesamten Zeit soll auch die Aufgabe xxx laufen (diese soll entweder keine Wiederholung enthalten oder diese Wiederholung darf höchstens 100ms dauern). Hier ein mögliches Programm:

50 14 30 + +                                      \ Gesamtzeit in 100ms-Einheiten berechnen

at100!                                                 \ Zeitgeber A damit starten; dieser zählt rückwärts

begin at100@ 44 >                             \ Rücksprungadresse für UNTIL festlegen; aktuelle Zeit holen und vergleichen

            if 200 mmgo                            \ zunächst schnell Tempo vorwärts

            else at100@ 30 <                    \ neuer Vergleich: Zeit fast abgelaufen?

                        if 50 mmgo                  \ ja, langsam geradeaus fahren (Roboter hat bereits gedreht)

                        else -70 spin                \ nein, drehen

                        then

            then xxx                                   \ beliebige andere Aufgaben xxx erledigen

            at100@ 0=                             \ Zeit A abgelaufen?

Until     0 mmgo                                   \ Motore stoppen

yyy                     \ ja, Zeitgeber A ist inaktiv, andere Aufgaben yyy erledigen

Wenn die Aufgabe xxx beispielsweise die Überwachung von Berührungssensoren enthält, lässt sich das obige Programm sehr einfach "notabschalten": Bei Berührung wird der Zeitgeber auf 0 gesetzt und damit die Wiederholung sofort verlassen. Wenn der entsprechende Schließkontakt zwischen 0 Volt und Eingang 6 angeschlossen ist, kann das Programm xxx so aussehen:

: xxx 6in 0= if 0 at100! then ;   \ Zeitgeber auf kleinsten Wert stellen, um bei UNTIL die Schleife zu verlassen.

 

Beispiel Heulboje

Sollen Zeitspannen unter 1 ms erzeugt werden, bieten sich programmierte Verzögerungsschleifen an. Im folgenden Beispiel wird gezeigt, wie man den ROBOprogy in eine Heulboje verwandeln kann. Der Lärmerzeuger ist an den (fast normal verwendbaren) Ausgang P3 angeschlossen und erzeugt je nach Pausenlänge zwischen P3EIN und P3AUS unterschiedliche Frequenzen. Diese Pausenlänge kann mit der Funktion

: wart ( n -- ) 0 do loop ; \ Zählerendwert per DatenStack übergeben!

variiert werden: Diese Wiederholung macht nichts außer Zeit zu "verbrauchen": Ein Umlauf dauert 2,2 Mikrosekunden. Wegen der hohen Verarbeitungsgeschwindigkeit des ROBOprogys dauert die längstmögliche Verzögerung für

255 wart

nur 560 Mikrosekunden. Die tiefste so erzeugbare Schwingungsdauer beträgt also etwa 1,1 ms, entsprechend 900 Hz, wenn man nicht einige „Zeitverbraucher“ hintereinander schaltet. Die folgende Funktion mit dem Namen "TON" erzeugt 10 Schwingungen einstellbarer Periodendauer. Diese muss wieder per DatenStack übergeben werden:

: TON { n -- } 10 0                            \ Wartezeit auf dem Stack liegen lassen; 10 Schwingungen beginnen

            do        p3ein dup wart             \ Spannung am Beeper einschalten, Warteschleife laufen lassen

                        p3aus dup wart            \ Spannung ausschalten, Warteschleife laufen lassen

            loop drop ;                              \ Nach Schleifenende: Wartezeit vom DatenStack entfernen

Nun wird eine bei etwa 2500 Hz beginnende, auf 6700 Hz ansteigende Frequenzfolge erzeugt, indem der Funktion TON nacheinander die Periodendauern 90, 89, 88, 87...32, 31 übergeben werden:

: y1 60 0 do 90 i - ton loop ;

Die Funktion y2 macht das Gegenteil, die erzeugten Frequenzen sinken von 6700 Hz auf 2500 Hz.

: y2 60 0 do 30 i + ton loop ;

Zuletzt werden immer zwei solche Folgen y1 und y2 nacheinander ausgeführt und das Ganze vier mal wiederholt. Fertig ist die Sirene.

: SIRENE 4 0 do y1 y2 loop ;

Wenn sich jemand die Mühe macht und herausbekommt, mit welchen Schwingungsdauern man Musik machen kann, kann der ROBOprogy ganze Melodien spielen. Der Kammerton A hat 440 Hz, eine halbe Schwingung dauert also 1136 Mikrosekunden. Einige Mikrosekunden verbraucht die Ausführung der Befehle, diese Zeit muss man raten oder messen. Die Funktion TON wird geringfügig modifiziert, in TONa umbenannt und könnte so aussehen (wenn der ROBOprogy genau 8 MHz hat):

: TONa { n -- } 0                    \ Anzahl der Schwingungen per DatenStack übergeben

            do        p3ein 255 wart 255 wart

                        p3aus 255 wart 255 wart

            loop ;

Nun ja, klingt ein bisschen schräg, weil die Hintergrundprogramme immer wieder die Zeiten verlängern. Vielleicht gilt das aber als ultimativer Kick...

Zurück zur Übersicht

 

Ein- oder zweiseitige Fallunterscheidung

Die entweder-oder-Entscheidung existiert in jeder Programmiersprache: Zuerst wird durch Berechnung, logische Verknüpfung oder Abfragen eines Signales eine Entscheidungsgrundlage auf das DatenStack gelegt, dann wird entschieden: $00 heißt "nein", dann werden alle Befehle zwischen ELSE und THEN ausgeführt. Alle anderen Werte auf dem DatenStack werden als "ja" interpretiert und alle Befehle zwischen IF und ELSE werden ausgeführt. Der "nein"-Teil darf fehlen, dann gelten alle Befehle zwischen IF und THEN als "ja"-Teil.

Mit AND / OR-Befehlen entweder $00 oder $FF auf dem DatenStack erzeugen

Der IF-Befehl holt diesen Wert und entscheidet:

Bei $ff                           bei $00

ç                                         è

Alle Befehle des „Ja“-Teils nach IF erledigen, dann  è

Alle Befehle des „Nein“-Teils nach

ç  ELSE erledigen

THEN

...weitere Befehle

Leider lässt sich Forth nicht spalten-, sondern nur zeilenweise schreiben, deshalb müssen die Blöcke, die hier nebeneinander stehen, untereinander angeordnet werden:

 

3IN 5IN and               \ Mit AND / OR-Befehlen entweder $00 oder $FF auf dem DatenStack erzeugen

IF                                \ jetzt den obersten Wert im DatenStack begutachten

if1....                \ Entweder alle Befehle des „Ja“-Teils zwischen IF und ELSE erledigen

ELSE   if2....                \ oder alle Befehle des „Nein“-Teils nach ELSE bis THEN erledigen

THEN                         \ hier folgen weitere Befehle, die nichts mehr mit der Fallunterscheidung zu tun haben.

Es wird dringend empfohlen, sich selbst und anderen Mitbürgern die Programmstruktur durch Einrücken sichtbar zu machen, wie das in den vorhergehenden Beispielen versucht wurde. Das erleichtert die Fehlersuche. Forth prüft das aber nicht, sondern zählt nur mit, ob paarweise Bedingungen erfüllt werden.

Bei fast allen anderen Programmiersprachen beginnt die Fallunterscheidung mit IF, anschließend wird die Entscheidungsgrundlage ermittelt und dann muss irgend ein Zeichen eingefügt werden, um zwischen dem Entscheidungsresultat und dem "ja"-Teil zu trennen. In Forth ist kein Sonderzeichen notwendig, da dient das IF als Trennzeichen. Ist irgendwie logischer.....

Zurück zur Übersicht

 

Wiederholungen

In Forth kann man Programmteile auf drei unterschiedliche Arten mit unterschiedlichen Eigenschaften wiederholen:

a) Bei BEGIN .. (Befehle) .. (Bedingung) .. UNTIL dient BEGIN lediglich als Marke, wie weit zurückgesprungen werden muss, wenn UNTIL das "befiehlt". Der Rücksprung zu BEGIN wird dann ausgeführt, wenn UNTIL auf dem DatenStack ein "ja" vorfindet, also einen Wert ungleich Null. Ansonsten wird die Wiederholung nicht durchgeführt und Forth macht mit dem Befehl weiter, der nach UNTIL folgt.

Wichtig ist: Alle Befehle zwischen BEGIN und UNTIL werden mindestens einmal durchlaufen! Erst dann wird geprüft, ob wiederholt werden muss. Wenn dieses eine Mal zu viel sein kann, verwendet man die folgende Variante:

b) Bei BEGIN ..(Bedingung) .. WHILE ..(Befehle) .. REPEAT wird zuerst geprüft, ob die Befehle ausgeführt werden müssen. Findet WHILE auf dem DatenStack den Wert $00, so springt das Programm sofort hinter REPEAT und führt die dortigen Befehle aus. Andernfalls werden zuerst die Befehle zwischen WHILE und REPEAT ausgeführt, dann springt das Programm auf jeden Fall zu BEGIN zurück. Dort wird der oberste Wert von DatenStack geholt und geprüft, ob die Befehle erneut ausgeführt werden sollen.

Man kann die BEGIN-WHILE-REPEAT-Schleife ersetzen durch eine BEGIN-UNTIL-Schleife mit vorgeschalteter IF-THEN-Abfrage.

c) Die dritte Variante wird verwendet, wenn zu Beginn schon bekannt ist, wie oft die Befehle zwischen DO..(Befehle)..LOOP wiederholt werden müssen. Vor Urzeiten wurde für Forth vereinbart, dass zuerst der Endwert auf das Stack kommt, dann erst der Startwert der "Laufvariablen". Und, dass der Endwert nicht erreicht wird. Daran hält sich auch das Übersetzungsprogramm von Forth zu P-Code - mit einer praktischen Änderung: Die Wiederholung bei der "Normalversion" wird auch dann beendet, wenn die Laufvariable größer ist als der Endwert. Damit vermeidet man den lästigen Umweg über "overflow", wenn man sich verprogrammiert hat J

Einige Beispiele für diese dritte Variante wurden in den vorhergehenden Kapiteln behandelt, hier wird nur noch auf Besonderheiten eingegangen:

c1) Der aktuelle Wert der "Laufvariablen" kann an beliebiger Stelle zwischen den Befehlen DO und LOOP mit dem Befehl I auf das DatenStack kopiert werden. Die Vorgehensweise wurde im vorhergehenden Beispiel "Heulboje" gezeigt. Da mit dieser Version von Forth keine mathematischen Probleme bearbeitet werden, wurde auf die Möglichkeit verzichtet, die Laufvariable der übergeordneten DO..LOOP auf das Stack zu holen.

c2) Was passiert, wenn auf dem DatenStack Start- und Endwert vor DO vertauscht sind? Nicht viel, wie in folgendem Beispiel erläutert wird, in dem die Laufvariable am Bildschirm des PC angezeigt wird:

: test 7 50 DO i . LOOP ;

Die Schleife druckt den Startwert 50, dann erhöht LOOP die Laufvariable auf 51, stellt fest, dass der Endwert 7 überschritten ist und beendet die Schleife. Wie bei BEGIN..UNTIL werden die Befehle zwischen DO und LOOP mindestens einmal durchlaufen.

c3) Mitunter will man die DO..LOOP in Zweierschritten oder rückwärts durchlaufen. Dafür gibt es den Befehl +LOOP, der den "normalen" LOOP-Befehl ersetzt. Während aber im LOOP-Befehl die Schrittweite 1 fest eingebaut ist, erwartet +LOOP den Wert der Schrittweite auf dem DatenStack. Damit kann man kaum durchschaubare Programme konstruieren. Dabei ist aber Vorsicht geboten: Die Möglichkeit, die Schleife auch mit immer kleiner werdender Laufvariablen - also rückwärts - zu durchlaufen, erfordert, dass LOOP die Schleife dann beendet, wenn aktuelle Laufvariable und Endwert gleich sind. Das kann zu einer Endlosschleife führen, wenn beispielsweise die Schrittweite ungerade ist. Experimentieren Sie damit ruhig herum, Sie können den ROBOprogy ja jederzeit abschalten und neu starten ;-)

c4) Muss die DO..LOOP-Wiederholung vorzeitig verlassen werden, weil beispielsweise eine außergewöhnliche Sensormeldung vorliegt, kann man mit dem Befehl LEAVE dafür sorgen, dass es keine Wiederholung mehr gibt. Dieser Befehl funktioniert aber nur in Zusammenarbeit mit dem Befehl LOOP. Für Befehl +LOOP gibt es keinen Notausstieg.

c5) Laufvariable und Endwert einer DO..LOOP werden auf dem ReturnStack zwischengespeichert, das auch die Rücksprungadressen der Funktionen in der richtigen Reihenfolge aufnimmt. Da gibt es keinerlei Konflikte. Verwendet man das ReturnStack auch noch als Zwischenspeicher für Variable, die eigentlich auf das DatenStack gehören (mit Befehlen wie >R und R>), so kann man den Befehl LOOP zu falschen Reaktionen veranlassen. Deshalb darf man das ReturnStack nicht als "Transportmedium" für Werte weder über die Grenzen einer DO..LOOP noch einer Funktion verwenden.

Zurück zur Übersicht

 

Besondere Befehle in Forth.

Zustandsgesteuertes Verhalten

Während der Testphase hat sich gezeigt, dass oft schon einfache Robotersteuerungen recht unübersichtliche Schachtelungen von Fallunterscheidungen IF..ELSE..THEN erfordern. Deshalb wurde eine Befehlsstruktur eingeführt, die auch andere Sprachen als "indexed goto" oder "computed jump" oder "zustandsgesteuertes Verhalten" kennen. Wenn es gelingt - meist ist das der Fall - eine Vielfachentscheidung entsprechend dem Wert einer Variablen zu treffen, bieten Befehle wie AMODEJUMP enorme Vorteile. Ein konkretes Beispiel soll das erläutern:

* Wenn A=0 ist und ein bestimmter Sensor anspricht, wird A=1 und der Roboter fährt los. Das war der Startvorgang

* Wenn A=1, führe den Programmteil X1 aus. Dort wird - abhängig von der abgelaufenen Zeit oder von Sensorsignalen - entweder A=2 oder A=5.

* Wenn A=2, wird Programm Y2 erledigt, dieses sorgt für A=0 und schaltet die Motore aus. Der Roboter ist abgeschaltet.

* Wenn A=3, reagiert der Roboter gemäss Programm F3 und schaltet dann auf A=7.

* ..so geht es weiter....

Natürlich müssen die Einzelprogramme X1, Y2, F3, etc..., in denen spezifische Aufgaben wie Sensorabfragen und Motorsteuerung erledigt werden, zuerst im PC-Editor entworfen, in P-Code übersetzt und zum ROBOprogy gesendet worden sein, bevor sie die folgende Vielfachentscheidung akzeptiert. Es gibt vier derartige Vielfachentscheidung, deshalb sind vier entsprechende Variable AMODE bzw. B/C/DMODE im System fest vereinbart. Alle werden beim Start automatisch mit 0 initialisiert.

In jedem Teilprogramm kann die Variable AMODE als Folge von Sensorergebnissen oder Berechnungen so geändert werden, dass beim nächsten Aufruf von AMODEJUMP bzw. B/C/DMODEJUMP ein anderes Teilprogramm erledigt wird. Jedes Mal, wenn AMODEJUMP aufgerufen wird, wird GENAU EINES der n Programme bis JUMPEND ausgeführt. JUMPEND ist kein Programm, sondern dient nur als Endkennzeichen der Kette - ähnlich wie "then" nach "if".

Wenn dieses eine Teilprogramm X1 oder Y2 oder F3 erledigt ist, wird der Befehl ausgeführt, der nach JUMPEND kommt.

Ein typisches Forthprogramm sieht etwa so aus:

***********************************

0 Amode!        \ diese Zeile kann entfallen, wird beim Einschalten automatisch erledigt

.....                   \ beliebige Befehle, nun folgen die Einzelprogramme

: Ap0 ( -- ) ...(Roboter starten lassen)... 1 Amode! ;

: Ap1 ( -- ) ...(3 Sekunden lang ohne Berührung?)... if 3 else 2 then Amode! ;

: Ap2 ( -- ) ...(Batterie leer?)... 6 Amode! ;   \ Reihenfolge beliebig, aber keine

: Ap3 ( -- ) ...(Pfeifton?)... 2 Amode! ;            \ Kennziffer sollte ins Nirvana zeigen

......                  \ Nach den Einzelprogrammen können beliebige Befehle folgen, zum Schluss kommt das Hauptprogramm

: main ( -- )                  \ Das Hauptprogramm ist immer eine Endloswiederholung

    BEGIN ....   \ beliebige Befehle ...

    AMODEJUMP Ap0 Ap1 Ap2 Ap3 Ap4 Ap5 JUMPEND \ bei jedem Durchlauf wird genau ein Programm erledigt

    ...                            \ beliebige Befehle ...

    0 UNTIL ;   \ Rücksprung zu BEGIN

************************************

Es gibt ein Limit: Wenn den Variablen AMODE bzw. B/C/DMODE Werte größer als 15 zugewiesen werden, stoppt der ROBOprogy und der PC meldet den Fehler "Maximaler Wert von Modus überschritten". Man darf zwischen AMODEJUMP und JUMPEND also höchstens 16 Teilprogramme schreiben. Aber darüber hinaus wird die Geschichte sowieso unübersichtlich....

Man kann bis zu vier Blöcke AMODEJUMP bis DMODEJUMP in einem Programm verwenden und damit etwa ähnliches wie parallele Programmierung betreiben: Der ROBOprogy kann auf diese Weise quasi simultan mehrere Aufgaben nebeneinander erledigen. In Hintergrund macht er das ja auch: Dort laufen bis zu acht getrennte Aufgaben im Zeitscheibenverfahren nebeneinander her. Ohne zu ruckeln...

Zurück zur Übersicht

 

Grundeinstellungen

Mit dem Befehl CONF! lassen sich die Grundeinstellungen des MEGA32 ändern. Im Normalfall und nach Reset sind bei der kleinen A-Version alle Bits gelöscht. Bei der größeren B-Version ist nur das Bit7 eingeschaltet. Wird die A-Version durch ein Schieberegister erweitert, muss das dem ROBOprogy über geeignete Bits im Configurationsregister gemeldet werden, weil dann die Steuerung der Outputs umgelenkt wird. Im Einzelnen gilt:

bit0=1:             5ADC + 6ADC werden jede ms gewandelt -> 1000 Hz Abtastfrequenz

                        ADC1..ADC4 werden nacheinander gewandelt. Also einmal pro 4ms -> 250 Hz Abtastfrequenz

bit0=0:             ADC1..ADC6 werden nacheinander gewandelt (einmal pro 6ms) -> 167 Hz Abtastfrequenz

bit1=0:             keine Fourier-Berechnung

bit1=1:             Fourier-Summe für 5ADC + 6ADC (jeweils sin/cos) bilden

                        Nach jedem Durchgang wird Wurzel(sin^2+cos^2) berechnet -> Ergebnisse in ampl4 / ampl5 speichern. Diese Berechnungen kosten Zeit, deshalb werden andere Programme etwas langsamer laufen.

bit2=0:             Fouriersummen über je 3 Perioden mit je 16 Samples/Periode.

                        Das entspricht 62,5 Hz, wenn bit0=1 oder 10,4 Hz, wenn bit0=0

bit2=1:             Fouriersummen über je 6 Perioden mit je 8 Samples/Periode.

                        Das entspricht 125 Hz, wenn bit0=1 oder 20,8 Hz, wenn bit0=0

bit3                  Einschalten der I2C-Steuerung, dafür gehen die Eingänge 6IN und 7IN verloren.

bit4                  vorläufig frei, muss 0 sein

 

Zurück zur Übersicht

© Herbert Weidner 2006-08

zur homepage