Einführung in Forth Teil 6

Die Serie begann mit der Beschreibung einer Ampelsteuerung. In der letzten Folge der Serie soll diese Ampeisteue-rung in die Praxis umgesetzt werden, zumal es ein Bereich ist, in dem der Haupteinsatzbereich von FORTH liegt, der industriellen Geräte- und Prozeßsteuerung bzw. -Überwachung. Dabei lernen Sie auch die typische Vorgehensweise zur Entwicklung eines FORTH Programms kennen.

Um die Brauchbarkeit einer Programmiersprache bewerten zu können, empfiehlt es sich, die Hauptanwendungsgebiete der Sprache zu untersuchen. FORTH wurde ursprünglich zur Prozeßsteuerung konzipiert. Die Steuerung und Überwachung (industrieller) Anlagen und Geräte ist auch heute noch der Hauptanwendungsbereich von FORTH, obwohl FORTH andererseits keine typische Prozeßsteuerungssprache ist, denn es lassen sich genauso gut Textverarbeitungsprogramme oder Expertensysteme in FORTH implementieren. In dieser Folge soll es darum gehen, die Stärken von FORTH im Bereich der Gerätesteuerung zu demonstrieren. Zwar stehen als „Gerät“ lediglich ein paar Leuchtdioden zur Verfügung, dennoch lassen sich bereits an einer simplen Ampelsteuerung die spezifischen Vorteile von FORTH erkennen. Vor allem soll deutlich werden, warum FORTH Sprachen wie BASIC, C oder ASSEMBLER in bestimmten Punkten überlegen ist. Nicht zuletzt lernen Sie an diesem anschaulichen Beispiel die einzelnen Phasen bei der Entwicklung eines FORTH Programms kennen. Eingefleischte ST COMPUTER Leser werden sich vielleicht noch daran erinnern, wie diese Einführungsserie begann. In der ersten Folge wurde der typische Aufbau eines FORTH Programms am Beispiel einer Ampelsteuerung veranschaulicht.

Es wäre sicher wenig sinnvoll gewesen, Ihnen bereits damals ein komplettes Listing für eine Ampelsteuerung in FORTH zu präsentieren. Mittlererweile, nachdem fünf Folgen vergangen sind, und Sie hoffentlich einiges über FORTH hinzugelernt haben, ist ein preiswerter Userport für den ST erhältlich, und einer praktischen Umsetzung des Programms steht eigentlich nichts mehr im Wege.

Screen #1
0	Loadscreen	*	20/08/87	PM
1
2	2	7 THRU
3
4
5	\ dies sieht zwar nach Platzverschwendung aus hält
6
7	\ aber die Möglichkeit späterer Erweiterungen offen
8 
9
10 
11 
12
13
14
15

Screen #2

0 Ampelsteuerung - Konstantendefinitian *	20/08/87 PM
1
2	$FF8604 CONSTANT DMA_DATA
3	$FFS606 CONSTANT DMA_MODE
4	$43E	CONSTANT FLOCK
5
6	1 CONSTANT ROTE_LAMPE
7	2 CONSTANT GELBE_LAMPE
8	4 CONSTANT GRÜNE_LAMPE
9
10	VARIABLE USER_PORT
11	VARIABLE GELB_FLA6
12
13
14
15

Screen #3

0	Ampelsteuerung - Initi.alis. u. Warteschi. .* 20/08/87 PM
1
2	: DMA_INIT ( --- )
3		$FF FLOCK W!	\ Floppyzugriff sperren
4		$188 DMA_M0DE W! \ DMA Port konfigurieren
5	;
6
7	: TU_NIX ( --- )
8	;	\	der Name sagt's bereits
9
10	: WARTE ( n ---- )
11		DO TU_NIX LOOP
12	;
13
14
15

Screen #4

0	Ampelsteuerung - Userport Ansteuerung * 20/08/87
1
2	: EIN ( c ---- )
3		USER_PORT	C@	\	Zustand des Userports
4		OR				\	mit Muster verknüpfen
5		DUP				\	Wert kopieren
6		USER_PORT	C!	\	neuen Zustand speichern
7		DMA_DATA W! ;	\	auf Userport ausgeben
8
9 : AUS ( c ---)
10		USER_PORT C@	\	Zustand des Userports
11		255 SWAP - AND	\	mit Muster verknüpfen
12		DUP				\	Wert kopieren
13		USER_PORT C!	\	neuen Wert speichern
14		DMA_DATA W! ;	\	auf Userport ausgeben

Abbildung 1: Das Ampelsteuerungs Listing

Screen #5

0 Ampel Steuerung -- Ampelphasen *	20/08/87
1
2	: GRÜN_PHASE ( --- )
3		GRÜNE_LAMPE EIN
4		20000 WARTEN
5		GRÜNE_LAMPE AUS	;
6
7	: GELB_PHASE ( ---- )
8		GELBE_LAMPE EIN
9		10000 WARTEN
10		GELBE_LAMPE AUS ;
11
12	: ROT_PHASE ( ---- )
13		ROTE_LAMPE EIN
14		20000 WARTEN
15		ROTE_LAMPE AUS ;

Screen #6

0 Ampelsteuerung - Ampelphasen *	20/08/87
1
2	: ROT/GELB_F'HASE
3		GELBE_LAMPE EIN
4		ROTE_LAMPE EIN
5		1OOOO WARTEN
6		GELBE LAMPE AUS
7		ROTE_LAMPE AUS ;
8	: GELB_BL. INKEN (----->
9		GELB_FLAG @ IF			\ Gelbe Lampe an
10				0 GELB_FLAG	!	\ Ja, dann Flag rücksetzen
11				GELBE_LAMPE	AUS	\ und gelbe Lampe aus
12			ELSE				\	ansonsten
13				1 GELB_FLAG	!	\ Nein, dann Flag setzen
14				GELBE_LAMPE	EIN	\ und	gelbe Lampe ein
15		THEN	;

Screen #7

0 Ampelsteuerung - Hauptwort * 20/08/87 PM
1
2	: AMPEL ( ---- >
3	DMA_INIT ALLES_AUS 1 GELB_FLAG !
4	BEGIN				\ Beginn der Endlosschleife
5		ROT_PHASE
6		ROT/GELB_PHASE
7		GRüN_PHASE
8		GELB_PHASE
9		KEY IF	\ Prüfen aut Tastendruck
10			BEGIN		\ Ja, dann Gelbblinken	
11			GELB_BLINKEN
12			UNTIL		\ Aufhören, wenn Taste gedrückt
13			THEN
14		AGAIN			\ Ende der Endlosschleife
15

Listing 1

TOP DOWN Strategie

Wenn man am Anfang der Lösung eines größeren Problems steht, empfiehlt es sich, sich zunächst ein paar Gedanken über die grundsätzliche Vorgehensweise zu machen. Dies gilt für Situationen aus dem täglichen Leben, wie etwa das Vorbereiten auf eine Prüfung, insbesondere aber für das Erstellen eines Anwenderprogramms. Über die mögliche Vorgehensweise bei der Programmentwicklung sind bereits ganze Bücherschränke an Literatur verfügbar, in denen Dutzende verschiedener Strategien in allen Einzelheiten diskutiert werden. Als Quintessenz läßt sich zusammenfassen, daß es eine universell anwendbare Lösungsstrategie nicht gibt. Dies ist auch nicht verwunderlich, denn dazu sind die Probleme, die gelöst werden müssen, die Werkzeuge, mit denen diese Probleme gelöst werden sollen (in erster Linie die Programmiersprachen) und nicht zuletzt auch diejenigen Personen, die die Probleme lösen sollen, einfach zu verschieden.

Es gibt dennoch eine Vorgehensweise, deren Anwendung sich in den meisten Fällen als vorteilhaft erweist. Nach dieser Strategie wird zunächst einmal das Problem grob umrissen und die einzelnen Schritte des Programmablaufs werden namentlich festgelegt. Zweckmäßigerweise wird das gesamte Programm in einzelne Module aufgeteilt, wobei jeder der ermittelten Schritte mit einem Modul identisch ist. Die Aufteilung in Module bringt u. a. folgende Vorteile :

In der Planungsphase werden die Module noch als „Schwarze Kästen“ behandelt, bei denen lediglich die Funktion und unter Umständen noch die Ein-Ausgabeparameter interessieren. Im nächsten Schritt wird der Programmablaufplan weiter verfeinert, indem jedes Modul in weitere, in der Regel kleinere, Module unterteilt wird. Dieser „iterative Zyklus“ wiederholt sich solange, bis eine weitere Verfeinerung nicht mehr möglich ist bzw. nicht mehr sinnvoll erscheint. Nun, und nicht eher, sollte mit der Codierung d.h. dem Umsetzen des aufgestellten Programmablaus in die Befehle und Operationen einer beliebigen Programmiersprache begonnen werden.

Die eben beschriebene Entwurfsstrategie wird als TOP-DOWN Entwurf bezeichnet, womit die Vorgehensweise, nämlich die wiederholte Verfeinerung eines allgemeinen Lösungsansatzes, recht treffend beschrieben wird. Dabei ist der TOP-DO WN Entwurf kein einmaliger Vorgang. Er wiederholt sich quasi auf jeder Ebene erneut, d. h. „Untermodule“ werden nach dem gleichen Muster in weitere Untermodule zerlegt usw.. Der Planunsphase schließt sich die Codierungsphase an, in der das eigentliche Programm geschrieben wird. Allgemein gilt: daß je besser und strukturierter der Programmablauf entworfen wurde, desto leichter die Umsetzung in ein konkretes Programm.

Um die ganzen Betrachtungen nicht allzu theoretisch erscheinen zu lassen, soll gleich mit dem eingangs angekündigten Beispiel begonnen werden. Stellen Sie sich vor, daß Sie als Leiter einer Softwareentwicklungsabteilung für einen Kunden eine Steuerung für eine komplette Ampelanlage einer typischen Verkehrskreuzung entwerfen sollen. Sie beginnen gemäß dem vorhin beschriebenen TOP-DOWN Entwurf zunächst mit einer allgemeinen Beschreibung des Problems (Festlegen der benötigten Ampeln und deren Phasen) und gelangen nach mehreren Entwurfszyklen zu dem Problem, das Verhalten einer einzelnen Ampel beschreiben zu müssen. Dies könnte am einfachsten nach folgendem Schema erfoldgen:

	: AMPEL 
	 WIEDERHOLE
	  GRÜN_PHASE
	  GELB_PHASE
	  ROT_PHASE
	  ROT/GELB_PHASE
	ENDE-WIED ERHOLE
	;

Damit wäre der Ablauf beschrieben und der Entwurf auf der obersten Ebene komplett. Gleichzeitig haben wir aber schon ein, wenigsten im Prinzip, ablauffähiges FORTH Programm voregen. Daraus läßt sich eine wichtige Erkenntnis ableiten: FORTH verkürzt die Planungsphase eines Programms erheblich, da sich Planungsphase und Codierungsphase nicht mehr ausschließen, sondern sich teilweise überlappen.

Nun wiederholt sich der Entwurfszyklus, allerdings eine Ebene tiefer. Konkret heißt das, daß wir nun alle Komponentenworte des Wortes AMPEL rrfeinern müssen. Konkret könnte bis folgendermaßen aussehen:

	: GRÜN__PHASE
		GRÜNE_LAMPE EIN
		WARTEN
		GRÜNE_LAMPE AUS
	;

Auch hier wurde ein Ablauf in einer Art und Weise entworfen, die auf der einen Seite der menschlichen Sprache sehr ähnlich ist und auf der anderen Seite direkt in FORTH codiert werben kann. Dadurch wird die Entwicklungsphase eines FORTH Programms erheblich beschleunigt. Hinzu kommt, daß mit FORTH als interaktive Entwicklungsumgebung nicht der klassierte und zeitaufwendige „Edit-Compiler-Link-Go-Zyklus“ wie bei Sprachen wie C oder MODULA-2 durchlaufen werden muß.

Ähnlich wie das Wort GRÜN_PHASE werden auch die übrigen Worte GELB_ PHASE, ROT_PHASE und ROT/GELB_PHASE definiert. Als kleinen Nebeneffekt sollten Sie folgende Regel beherzigen, die zwar auf den ersten Blick als ein wenig trivial erscheinen mag, die aber die Lesbarkeit und das Verständnis von FORTH Code langfristig enorm steigern kann: Verwenden Sie Wortnamen mit Bedacht und nutzen Sie den Umstand, daß Wortnamen bis zu 31 Zeichen enthalten können aus. Schreiben Sie ruhig z. B. „GRÜN PHASE_EINSCHALTEN“ (22 Zeichen) anstelle von z. B. „GP_EIN“.

Die nächste Phase der Verfeinerung wird bereits in irgendeiner Form die Hardware, d. h. in unserem Beispiel den ST USERPORT ansprechen. Spätestens hier beginnt die Phase der Codierung, d. h. ein allgemeiner Entwurf ist in diesem Stadium nicht mehr sinnvoll. Anders als der Entwurf verläuft die Implementierung in FORTH von unten nach oben. Der Name für diese Strategie ist BOTTOM-UP und soll den Umstand beschreiben, daß mit der Implementation des elementarsten Wortes der Applikation begonnen wird. Daraufhin folgen die Worte, die auf diesen elementaren Worten aufbauen, usw. Die Implementierung endet in FORTH mit einem einzigen Wort, welches bei seinem Aufruf das gesamte Anwenderprogramm zur Ausführung bringt.

Das komplette Listing finden Sie in Abbildung 1. Zunächst noch einmal eine kurze Beschreibung des DMA Ports des ATARI ST, für den Fall, daß Sie das April Heft nicht vorliegen haben. Der DMA Controller des ATARI ST belegt den Adreßbereich von $FF8600 bis $FF860D. Für die Programmierung des Userports sind nur die beiden Register $FF8604 und $FF8606 interessant. Bei ersterem handelt es sich um das Datenregister, dessen Inhalt den Zustand der einzelnen Pins des Userports festlegt; bei dem letzterem um das DMA Kontrollregister, welches u. a. festlegt, ob eine Eingabe oder eine Ausgabe erfolgt. Um nun ein bestimmtes Bitmuster auf dem Userport ausgeben zu können, muß über das Kontrollregister der DMA Port auf Ausgabe geschaltet und das entsprechende Bitmuster in das Register $FF8604 geschrieben werden. Um Komplikationen zu vermeiden, sollte ferner die Systemvariable FLOCK unter der Adresse $43E für die Dauer des Zugriffes mit $FF belegt werden. Damit wären die wichtigsten Voraussetzungen geklärt und der Umsetzung des Programms steht eigentlich nichts mehr im Wege.

Das Wort DMA_INIT muß am Anfang aufgerufen werden und nimmt die notwendige Initialisierung vor. Dieses Wort sowie die Worte EIN und AUS sind die eigentlichen hardwareabhängigen Worte. Sollten sich aus irgendeinem Grund die Hardwarespezifikationen ändern, so müssen lediglich diese drei Worte entsprechend angepaßt werden, unabhängig davon, ob Sie lediglich eine einzelne Ampel steuern oder eine ganze Kreuzungsanlage. Zu dem Wort EIN muß nicht viel gesagt werden, es testet zunächst anhand eines Flags. Ausgehend von einer bestimmten Anordnung des Userports (siehe Abb. 2) wird ein entsprechendes Bitmuster auf dem Userport erzeugt. Entsprechend arbeitet das Wort AUS. Diese Bitmuster ergeben sich aus den Codes für die einzelnen Phasen. Daher wurden diese Codes als Konstanten definiert, da man nun schreiben kann:

	GRÜNE_LAMPE EIN OK

um z. B. die grüne Lampe einzuschalten. Nicht nur, daß diese Schreibweise eigentlich sehr logisch ist und auch von jemandem, der von FORTH keine Ahnung hat, angewendet werden kann, ist sie auch universell erweiterbar. Sollen nämlich irgendwann einmal mehrere Ampeln gleichzeitig gesteuert werden, so könnte die dazugehörige Syntax z. B. folgendermaßen aussehen:

	ERSTE GRÜNE_LAMPE EIN
	ZWEITE GRÜNE LAMPE EIN 

usw.

Bei ERSTE und ZWEITE handelt es sich um Konstanten mit den Werten eins und zwei. Das Wort GRÜNE_LAMPE hat zwar noch den gleichen Namen aber eine vollkommen neue Struktur. Es wertet den konstanten Wert, der durch ERSTE, ZWEITE usw. übergeben wird aus und verknüpft ihn so, daß ein entsprechendes Bitmuster erzeugt wird. Das Wort EIN ist nach wie vor unverändert. Ihm ist es gleichgültig, wie das Bitmuster zur Ansteuerung des Userports erzeugt wurde. Dieses „Verstecken von Information“ (das Wort EIN „weiß“ nicht, wie das Wort GRÜNE_LAMPE das Bitmuster erzeugt) und die extreme Modulasierung findet man auch in modernen allerseits hochgelobten Sprachen, wie z. B. MODULA-2. Bereits an diesem einfachen Beispiel wird die Mächtigkeit von FORTH deutlich, die unterschiedlichsten Datenstrukturen miteinander kombinieren zu können So konnte das Wort GRÜNE_LAMPE, im ersten Fall noch eine Konstante, ohne weiteres auch als Befehl geschrieben werden, ohne daß dadurch der Aufbau des Programms geändert werden mußte. Diese Flexibilität bei der Implementation eines Programmentwurfs bzw. diese Anpassungsfähigkeit des Programmcodes an neue Situationen ermöglicht keine andere Sprache in diesem Umfang.

Loadscreens steuern den Programmfluß

Doch zurück auf den Boden der Realität bzw. der Praxis. Geben Sie das Li-sting mit Hilfe des Editors ein. Noch eine Anmerkung zu Screen 1: Hierbei handelt es sich um einen sog. „Loadscreen“, der lediglich eine Reihe von LOAD Befehlen (in unserem Beispiel nur einen einzigen, der alle sechs Screens auf einmal lädt) enthält. Beim Laden des Loadscreens durch ’1 LOAD’ werden die einzelnen Screens in der Reihenfolge des Auftretens der korrespondierenden LOAD Befehle geladen. Der große Vorteil eines Loadscreens, besonders in umfangreicheren Applikationen, ist, daß man bei der Eingabe des Quelltextes nicht an eine starre Reihenfolge gebunden ist. Vielmehr kann der Quelltext in einer beliebigen Anordnung eingegeben werden. Die Reihenfolge der Kompilation wird lediglich durch den Loadscreen festgelegt. Zusätzlich können einzelne Screens einfach dadurch vom Kompilieren ausgeschlossen werden, daß ihr entsprechender LOAD Befehl aus dem Loadscreen entfernt wird.

Kein Kommentar ?

Die einzelnen Screens wurden weitgehend mit Kommentaren versehen. Auch dies ist etwas, was man sich als FORTH Programmierer nicht früh genug angewöhnen kann. Geizen Sie nicht mit Kommentaren, aus denen der Sinn einzelner Worte hervorgeht. FORTH bietet seinem Benutzer eine sehr große Freiheit. Doch darin liegt auch die Gefahr, sehr schnell unleserlichen Code entstehen zu lassen. Zeile 0 eines Screens sollte generell als Kommentarzeile benutzt werden, aus der der Inhalt des jeweiligen Screens hervorgeht.

Starten des Programms

Wenn Sie nun das Programm starten, so durchläuft die Ampel, die aus drei an den Userport angeschlossenen Leuchtdioden (oder auch Lampen mit entsprechenden Transistortreibern mit oder ohne Optokopplern, zur Trennung des Starkstromkreises voran) besteht, alle Phasen. Bei Betätigen einer Taste geht unsere Ampel in eine Gelbblinkphase über. Das Programm wurde der Einfachheit halber als eine Endlosschleife konzipiert, die Sie nur durch einen Reset verlassen können. Eventuell kann es erforderlich sein, die Warteschleifen der einzelnen Phasen zu verändern.

Eine neue Sprache entsteht

Durch die Worte aus den Screens 2 bis 7 wurde aus der Vielzwecksprache FORTH eine „Spezialsprache“ zur Steuerung von Verkehrsampeln. Dieses Konzept geht weit über das Bibliothekskonzept anderer Sprachen hinaus, da die neuen Worte genauso Bestandteil der Sprache sind, wie die Kernworte und von diesen nicht unterscheidbar sind. Dem Anwender steht es nun frei, den erweiterten Sprach-kern als eine sog. „Turnkey“ Applikation zu speichern, die dafür sorgt, daß beim erneuten booten des Systems das Anwenderprogramm gestartet wird. Professionelle Anwender können mit Hilfe eines Metacompilers dedizierte Systeme kompilieren, die nur die Worte (dabei wird von diesen Worten der Wortkopf entfernt) enthalten, die für die Applikation benötigt werden. Als Ergebnis eines solchen Compilationsprozesses kann auch reiner Maschinencode entstehen. Durch Verwendung zusätzlicher „Targets“ kann es sich hierbei um lauffähigen Maschinencode für im Grunde jeden beliebigen Prozessor, sei es für einen 8031 oder eine 68020, handeln.

Ende und Ausblick

Damit wäre die FORTH Einführungsserie vorläufig beendet. Ich möchte mich vor allem bei den Lesern bedanken, die bis zum Ende durchgehalten haben. Ich hoffe, es hat Ihnen Spaß gemacht, und Sie haben ein Gefühl dafür bekommen, was FORTH kann und was nicht. Sicher ist FORTH nicht „die“ ideale Sprache. Doch, obwohl es immer Anwendungen geben wird, die man besser in Assembler, BASIC, C, MODULA-2, PEARL oder LISP programmiert, kommt FORTH meiner Meinung nach einer solchen fiktiven Idealsprache sehr nahe. Im Grunde enthält es Komponenten aller aufgezählten Sprachen, bzw. kann jederzeit um diese Komponenten erweitert werden. Ich hoffe, Ihnen auch in Zukunft FORTH Programmiertechniken in Form praktischer Anwendungen präsentieren zu können. Wenn Sie Informationen über FORTH, Public Domain Software oder weiterführende Literatur benötigen, so wenden Sie sich an die

FORTH Gesellschaft e.V.
Friedensallee 92
2000 Hamburg 50

Schließen soll diese Einführungsserie mit einem Zitat, das mir persönlich sehr gut gefällt. Es stammt von Michael Ham, ehemaliger Kolumnist für Dr. Dobbs Journal, FORTH Enthusiast und Gewinner eines Wettbewerbs zur Beschreibung von FORTH in fünfundzwanzig oder weniger Worten. Ich habe es dem sehr empfehlenswerten Buch „In FORTH denken“ von Leo Brody (erschienen im Hanser Verlag, 1986) entnommen:

„FORTH ist wie das Tao: ein Weg, den man erkennt, wenn man ihm folgt. Seine Fragilität ist seine Stärke, seine Einfachheit ist seine Botschaft.“

(PM)

Abb.2 Belegung des Userports. (R1,R2,R3 = 478 Ohm)


Aus: ST-Computer 11 / 1987, Seite 44

Links

Copyright-Bestimmungen: siehe Über diese Seite