Timer und Interrupts im ATARI ST: Wem die Stunde schlägt

Unter der Nummer 31 des XBIOS verbirgt sich die Funktion XBTI-MER(). Sie dient dazu, die Timer im MFP in Gang zu setzen; Versuche, sie auszuprobieren, scheitern meistens am schier unerklärlichen Phänomen des Rechnerabsturzes.. Über die Ursachen hierfür, die korrekte Handhabung und den Zweck der Timer erfährt man so gut wie nichts. ST COMPUTER klärt auf…

Das neudeutsche Wort 'Timer' dürfte wohl jedem Computerbesitzer vom Hörensagen bekannt sein: es bezeichnet ein elektronisches Bauteil, das nach Ablauf einer (hard- oder softwaremäßig) vorgegebenen Zeitdauer einen elektrischen Impuls aussendet. Wenn man so will, ist ein Timer also nichts anderes als ein „beleuchtungsfreies Blinklicht“.

Wozu Timer?

Die Anwendungsmöglichkeiten reichen von der Meß- und Regelungstechnik bis hin zur elektronischen Tonerzeugung im Synthesizer. Was aber sucht dieses ominöse Teil im ST? Um diese Frage zu beantworten, müssen wir uns in den Alltag eines Betriebssystems (im folgenden kurz BS genannt) versetzen. Dieser beginnt gewöhnlich mit dem Einschalten des Rechners, woraufhin das BS naturgemäß seine Gemächer bezieht, alle für den geordneten Betrieb notwendigen Systemvariablen und Peripheriegeräte initialisiert und anschließend auf irgendeine Art und Weise dem Benutzer mitteilt, er könne jetzt mit seiner Arbeit beginnen. Startet dieser ein Programm, so gibt das BS die Kontrolle an selbiges weiter und wartet darauf, daß es wieder aktiviert wird und den nächsten Befehl ausführen darf... In der Steinzeit der „Computer“ (vor etwa 20 Jahren), als die Bits noch in die Röhre schauten, da war das tatsächlich so. Aber heute würde jedes BS, das etwas auf sich hält, bei dem Vorwurf, bloß die Schnittstelle zwischen Mensch und Maschine zu sein, vor lauter Wut sämtliche Directories kurz- und kleinschlagen. Bietet es dem geplagten Software-Engineer (früher hieß das mal Programmierer) doch eine ganze Menge Erleichterungen: Wer kümmert sich denn noch um die Programmierung der Controller für die Peripherie? Wer. um die Speicherplatzverwaltung? ’Fensterln’ und ’Mausen’ (rein grafisch natürlich!) Sie gerne? Kein Problem dank GEM & Co. Und schließlich die Krönung: Wie aus gut unterrichteten Kreisen verlautbarte, soll es sogar Rechner geben, an denen mehrere Personen und sogar jeweils mehrere Programme gleichzeitig arbeiten können! Und wer glauben Sie, organisiert dieses Chaos? Richtig: Das BS. Und das geht eben nur, wenn das System ständig die Kontrolle über den Rechner, sich selbst und natürlich über die Anwenderprogramme hat. Wenn die Maschine aber leider nur eine Ceh-Peh-Uh hat, und die Kontrolle nun leichtfertig an ein Benutzerprogramm abtritt: Wie soll ein BS dann in der Lage sein, die Wacht am Rhein zu halten? Dazu

braucht man Timer! Die nämlich machen den Prozessor in kurzen Abständen höflich, aber bestimmt darauf aufmerksam, doch bitte wieder das BS anzustoßen, damit Ordnung herrsche im Lande der Busse und Leiterbahnen. Doch nicht nur hier werden diese 'Wecker’ gebraucht; im Prinzip lassen sie sich überall dort gewinnbringend einsetzen, wo bestimmte Aufgaben periodisch ausgeführt werden müssen: Vom Blinken des Cursors über die Abfrage der Mausposition bis hin zum Überprüfen von Tastatur und Floppy.

Exceptions, Traps und Interrupts

Nach diesem nicht allzu ernst gemeinten Trip in die Welt der Betriebs-Software nun zum Trap, denn wir wollen ja wissen, wie diese Vorgänge realisiert werden. Eine der größten Stärken der MC68000, so steht es geschrieben, sei ihre Fähigkeit, Interrupts zu verwalten. In der Tat stehen dem System 256 Vektoren zur Verfügung, die in Ausnahmefällen dem Rechner und dem BS das 'Leben' retten können! Im allgemeinen laufen Anwenderprogramme im sog. User-(U-) Mode. Hier sind einige der Maschinenbefehle nicht erlaubt und auch innerhalb des Speichers gibt es 'Sperrbezirke’, die für Programme tabu sind. Diese Maßnahmen sollen dazu dienen, das BS gegen unbefugten Zugriff und damit gegen 'Absturz' zu schützen. Im MOTOROLA-Jargon ist alles, was die CPU in den Supervisor-Modus (S-Mode) versetzt, eine Ausnahme (engl.: exception). Das soll nicht etwa darauf hinweisen, daß so etwas selten vorkäme, sondern darauf, daß irgendjemand irgendetwas vom BS erwartet, oder daß irgendwo irgendetwas irgendwie schief gegangen ist. Genaugenommen muß man also, zwischen zwei verschiedenen Exceptions unterscheiden: Den Traps, die prinzipiell von der Software explizit durch Befehle oder aber durch Laufzeitfehler (Division durch Null etc.) ausgelöst werden, und den Interrupts, die ausschließlich von der Hardware erzeugt werden. Im ST ist das bekanntermaßen so gelöst, daß der Anwender mittels TRAP-Befehl die Funktionen des TOS anfordern kann, während er normalerweise mit Interrupts (IR) überhaupt nichts zu tun hat. Diese Arbeit übernimmt glücklicherweise allein das BS.

Bild 1: Nach Auftreten eines Interrupts (IR) legt die MC68k Statusregister (SR) und Programmzähler (PC) auf dem Supervisorstack, geht in den S-Mode und springt anschließend in die Interrupt-Routine (IRR).

Von IPL.

Was kann denn nun alles einen Interrupt erzeugen? Z.B.: Ein Monitorwechsel, volle Sende- und Empfangspuffer der diversen Schnittstellen, Floppy- und Harddisk-Controller und: Die Timer! Was sich beim Auftreten einer Exception abspielt, wird in Bild 1 dargestellt. Zwei Ausnahmen bei den Ausnahmen: Beim Auftreten eines Busoder Adreßfehlers legt das Mikroprogramm der CPU noch ein paar Bytes mehr auf den Supervisorstack, um eine effektivere Post-Mortem-Analyse (Fehleranalyse nach BS-Absturz) zu ermöglichen. Aber das nur nebenbei. Stellen wir uns nun vor, die RS 232-Schnitt-stelle hätte beim Empfangen 'den Kanal voll’ und könnte keine weiteren Daten mehr empfangen. Bevor sie im Strom der Bits versackt, verursacht sie einen IR, um damit die CPU davon zu überzeugen, daß ein Abholen der Daten wünschenswert sei. In diesem Moment trifft das Ereignis eines Zeilenrücklaufes ein, das ebenfalls der CPU gemeldet wird. Aus einer Laune heraus schiebt der Benutzer die Maus ein paar Zentimeter über den Tisch und der Drucker bekommt - nachdem er gerade ein Listing verdaut hat - wieder Hunger auf Daten. Wenn dann auch noch der Floppy-Controller die erfolgreiche Abarbeitung eines zuvor abgesetzten Kommandos bekannt gibt, ist das Chaos perfekt: Die serielle Schnittstelle wurde zwischenzeitlich ein Opfer der DFÜ, der Elektronenstrahl ist bereits wiederholt zurückgelaufen, ohne daß es irgendjemanden interessiert hätte, die Maus steht immer noch auf dem Papierkorb statt auf Disk A, der Drucker ist verhungert, und der Controller schmollt - in Anbetracht solcher Ignoranz! - neben dem Sound-Chip vor sich hin. Klarer Fall: So geht’s nicht! Zwei Möglichkeiten bieten sich an: Entweder die CPU fragt im Ringelreihen (Round Robin) alle erdenklichen IR-Quellen ab (Polling) und entscheidet dann von Fall zu Fall, ob eine Verarbeitung erforderlich ist, oder man gibt jeder IR-Quelle eine Zahl mit auf den Weg, die angibt, wie wichtig eine Wortmeldung, also eine IR-Anforderung (IRQ, Interrupt Request) ist; man nennt das Prioritätensteuerung. Im ST sind beide Formen realisiert; die MC68000 kennt aber nur das Prioritätenprinzip. Hierzu verfügt sie über drei Eingänge, die mit IPL 0, IPL 1 und IPL 2 bezeichnet sind. Die Abkürzung steht für Interrupt Priority Level, also Unterbrechungsprioritätsebene (bleiben wir doch lieber bei IPL..). Tritt ein IR-Ereignis ein, so liegt die jeweilige Priorität an diesen Pins an. Die Bits 8 bis 10 im Statusregister des Prozessors bilden nun die 'Interrupt Mask’. Mit ihnen hat es folgende Bewandtnis: Steht die Maske z.B. auf 4, können nur IRs erkannt werden, deren Priorität größer oder gleich 4 ist. Setzt man diese Überlegung gewissenhaft fort, folgt hieraus, daß bei einer Maske von 0 alle IRs erlaubt sind, während eine 7 alle anderen IRs außer denen der Stufe 7 sperrt. Da man IRs auf diesem Level (7) nicht ausmaskieren kann, spricht man hier auch vom Non Maskable IR (NMI). Wohl aus Kostengründen wurde im ST die Leitung IPL 0 permanent auf logisch ’0’ gesetzt, sodaß nur noch die Level 2, 4 und 6 zur Verfügung stehen.

Nr.: Signal:
0 Busy (Centronics)
1 DCD (RS232)
2 CTS (RS232)
3 n.b.
4 Timer D (fuer Baudrate)
5 Timer C (200Hz, System)
6 ACIAs (Keyboard/MIDI)
7 FDC und DMA
8 Timer B (Zeiienruecklauf)
9 Sendefehler (RS232)
10 Sendepuffer leer (RS232)
11 Empfangsfehler (RS232)
12 Empfangs puff er voll (RS232)
13 TIMER A (frei verfuegbar)
14 RI (RS232)
15 Monochrome Monitor

Tab. 1: Die 16 Interruptkanäle des MFP 68901: Kanal 0 besitzt geringste, Kanal 15 höchste Priorität

Auf Stufe 2 erscheint der HBL- (Horizontal Blank-) IR. Wie oben bereits ermähnt, ist er so unwichtig, daß man sich fragen darf, weshalb er der CPU überhaupt 'Guten Tag’ sagen darf. Er entsteht bei jedem Zeilenrücklauf (bei 70Hz Bildfrequenz also alle 35 Mikrosekunden!) und hat sinnvollerweise die einzige Aufgabe, die IR-Maske auf 3 zu setzen, um weitere HBL-IRs zu unterbinden. Vergessen wir ihn also schnell wieder.

Auf Stufe 4 meldet sich der VBL- (Ver-tical Blank-) IR zur Stelle, und zwar bei jedem Bildrücklauf (l/70Hz). Im ST ist dann etliches zu tun: Test auf Monitorwechsel, evtl. Verändern der Auflösung und der Farbpalette, Blinken des Cursors, evtl. Andern der Bildschirm-Anfangsadresse, Test auf Diskettenwechsel usw. Zwar kann man hier ohne größeren Aufwand noch eigene Routinen einflicken, aber schon rein aufgrund der zeitl. Begrenzung steht fest, daß man hier nur kleinere Arbeiten ausführen lassen kann.

Auf Stufe 6 schließlich residiert der MFP, und der ist so interessant, daß man ihm ruhig ein paar Zeilen widmen sollte...

…über den MFP...

Der MFP 68901 ist ein Mehrfunktions-Prozessor, der im ST als IR-Controller zum Einsatz kommt. Er kann 16 IRs erzeugen, die alle gegeneinander priorisiert sind, teilweise auch durch Polling abgefragt werden. Tab. 1 zeigt die Auflistung dieser IR-Quellen. Für uns ist genau eine dieser Quellen von besonderer Bedeutung: Der Timer A. Von den insgesamt 4 im MFP enthaltenen Timern ist er der einzige, den man den Programmierern noch für eigene Zwecke gelassen hat; alle anderen dienen systeminternen Zwecken und müssen unangetastet bleiben, so man den Griff zum Reset-Knopf vermeiden möchte...

…zum Timer A

Wie eingangs schon erwähnt, erzeugt ein Timer periodisch Signale, die natürlich auch als IR dienen können. Nun wäre das aber nur halb so interessant, wenn die Zeit zwischen zwei solchen Signalen nicht einstellbar wäre.

Wir benötigen also schon einmal ein Datenregister. Einfacherweise wird der Wert in diesem Register kontinuierlich dekrementiert, bis er bei 0 angelangt ist. Just in diesem Moment soll ein Interrupt erfolgen und der Zähler wieder rückgesetzt werden. Abschalten muß man den Timer natürlich auch können; allein mit dem Datenregister ist das nicht möglich, denn selbst bei Eingabe einer 0 wird solange dekrementiert, bis wieder die 0 kommt. Und das ist bei einem 8bit-Register nicht sehr lange. Man benötigt also noch ein Kontrollregister. Wir definieren (der MFP tut das im übrigen genauso!) einen Timer als ausgeschaltet, wenn in seinem Kontrollregister eine 0 steht. Ein von 0 verschiedener Wert aktiviert also den Timer; er erfüllt im MFP sogar noch einen weiteren Zweck: je nachdem, welcher Wert gesetzt ist, müssen entsprechend viele Taktsignale am MFP ankommen, bis der Wert im Datenregister dekrementiert wird (Vorteilung!), oder die Betriebsart des Timers wird geändert (Count And Jump, -also so wie oben -, oder Zählen externer Impulse bzw. deren Länge). Die letzten beiden Modi sind für uns uninteressant, weil sie vorab den Gebrauch eines Lötkolbens voraussetzen; für den ersten Modus steht in Tab. 2 der Vorteilungsfaktor in Abhängigkeit vom Inhalt des Kontrollregisters. Jetzt können wir also den Timer ein- und ausschalten. Damit der MFP aber überhaupt weiß, daß das, was da aus Richtung Timer ankommt, auch ein IR ist, müssen wir ihm das explizit bei-bringen, und zwar, indem wir in einem weiteren Register (IR-Enable) ein Bit setzen. Nein, nicht irgendeins: Die Nummer 5 muß es sein, denn nur sie schaltet den Timer A zum MFP durch!

Wert: Vorteilung:
0 (Timer aus)
1 4
2 10
3 16
4 50
5 64
6 100
7 200

Tab. 2: Der Wert des Kontrollregisters bestimmt die Vorteilung von Timer A

Die XBTLMERQ-Funktion setzt dieses Bit automatisch; programmiert man von Hand zu Fuß, gilt: vergißt man dieses Detail, empfehlen sich Baldriantropfen zur Beruhigung...

Eine weitere Besonderheit, die es zu beachten gilt: Wird im MFP ein IR erzeugt (egal durch wen), wird das zum Verursacher gehörende Bit im IR-Service-Register gesetzt. Dadurch werden alle IRs niedriger Priorität ausmaskiert, also nicht mehr erkannt. Aus Gründen des Fairplay gebietet es sich, das besagte Bit nach Beenden der IR-Routine (IRR) wieder auf 0 zu legen! Für den Timer A ist wieder das Bit Nr. 5 zuständig. Zwar könnte man jetzt noch alle sonstigen Möglichkeiten und Eigenschaften des MFP erklären, aber das würde zu weit führen. Nur noch eins: Die Vektoren, über die beim Auftreten eines IRs des MFP gesprungen wird, stehen ab Adresse $100. Man erhält die Adresse des Vektors eines IRs durch Addition des Vierfachen der IR-Nummer zu diesem Offset. In Tab. 3 sind alle für die Programmierung des Timers A notwendigen Register und deren Adressen angegeben.

Zur besseren Demonstration ist im Li-sting 1 ein Programm abgedruckt, welches den Timer A programmiert und diesen dazu ausnutzt, durch entsprechend häufige IRQs soviel Rechenzeit von der CPU ’abzugraben’, daß für die Ausführung der eigentlichen Benutzerprogramme schlimmstenfalls nichts mehr übrig bleibt: Der ST steht still! Da die Bedienung aus dem Programmkopf hervorgeht, widmen wir uns nun den heiklen Seiten der IR-Programmierung.

Rien ne va plus...

Name: Adresse: Funktion:
TACR $FFFA19 Kontrollregister, bestimmt Timerfunktion
TADR SFFFA1F Datenregister, Dekrement-Offset
IREA $FFFA07 IR-Freigabe (Bit 5]
IRISA $FFFA0F Zeigt aktiven IR an (Bit 5)
IRPA $FFFA0B Bit 5 ist bei Timer A-IR gesetzt
IRMA $FFFA13 IR-Maske (Bit 5)

Tab. 3: Überblick über die für den Timer A wichtigen Register. TREA und IRMA werden von XBTIMER() automatisch gesetzt. IRPA (Bit 5) zeigt einen 'hängenden’ Timer-Interrupt an; ein Rücksetzen ist nicht unbedingt notwendig.

Nicht besonders witzig ist folgende Situation: Keine Bomben, die Maus reagiert wie gewohnt, das Desktop erstrahlt in vollem Glanz... Aber beim Auswählen der Drop-Down-Menüs in der Menü-Zeile tut sich nichts mehr, und die Tastatur hat in GEM-Pro-grammen sowieso nicht viel zu melden. Diagnose: Das AES ist abgeschossen worden, jener Teil des GEM, der sich so fürsorglich um Menüs, Dialoge und Fenster etc. kümmert. Da hilft nur ein Reset (und manchmal nicht einmal der). Trat dieser Faux-pas in Zusammenhang mit der Timer-Programmierung auf, war unter Garantie ein Blockieren der IRs anderer IR-Quellen der Sündenbock. Man muß sich eines vor Augen halten: Der MFP erscheint mit seinen IRs auf der für die CPU höchsten Prioritätsstufe. Damit wird durch Eintreten eines Interrupts an diesem Baustein automatisch der VBL-IR blockiert, weil dieser auf Level 4 arbeitet und der Prozessor die IR-Maske auf das aktuelle IR-Level setzt; will heißen: Wenn der MFP einen IR auf den Bus legt, setzt die MC68000 die Maske sofort auf 6! Zum zweiten ist der Timer A auch innerhalb des MFP auf sehr hohe Priorität gesetzt: Vor ihm rangieren nur noch zwei weitere IR-Quellen, RI (Ring Indicator), der nur bei angeschlossenem Modem von Bedeutung ist, und Monochrome Detect. Wer nicht permanent an seinem Monitorstecker herumspielt, hat hier auch nichts zu befürchten. Ergo: Wenn Timer A in Aktion tritt (einen IR auslöst), sind alle anderen IRs ausmaskiert! Die oberste Pflicht einer eigenen Timer-Routine muß also sein, gleich am Anfang alle IRs wieder freizugeben; schließlich wollte man ja nur die Programmkontrolle haben. Es muß desweiteren darauf geachtet werden, daß die VBL-IRR unter keinen Umständen abgebrochen wird, weil hier vorwiegend sehr kritische Dinge passieren, die keinen Aufschub dulden. Ob man diese Routine unterbrochen hat, kann man an Bit 10 des Statusregisters ablesen, das ja der Prozessor auf dem Stack abgelegt hat. Ist dieses Bit (IPL 2) gesetzt, d. h. die Maske steht mindestens auf 4, empfiehlt sich ein beschleunigtes Verlassen der IRR! Sleep überprüft dies mit dem Befehl btst #2,8(a7); der merkwürdige Offset kommt durch das Retten der Register dO und dl am Anfang der Routine zustande. Ebenfalls zu beachten: Da man vielleicht nicht weiß, wie lange man in der IRR bleiben wird, empfiehlt sich das sofortige Ausschalten des Timers, weil sonst ein erneuter IR auftreten könnte, und man mit dem alten noch gar nicht fertig ist... Am Ende der Routine muß man den Timer dann wieder neu programmieren. Der nächste wichtige Punkt: Die Floppy bzw. Harddisk. TOS setzt an der Adresse $43E beim Betreten einer Floppy-Routine einen Semaphoren und will damit allen sagen, es möchte jetzt nicht gestört werden. Also müssen wir auch in diesem Fall unsere Zelte vorzeitig abbrechen. Nur wenn diese Speicherzelle 0 enthält, dürfen wir loslegen, sonst liefert die Floppy Daten, die das BS noch gar nicht verarbeiten kann (Wirkung: Daten auf Disk A: defekt? Bitte überprüfen Sie...)! Schließlich noch ein wichtiger Punkt: Man darf in der Timer-Routine fast alles anstellen, aber eines ist unter Absturzgefahr verboten: das Aufrufen des BSs über den TRAP-Befehl! Der Grund hierfür liegt darin, daß man u. U. eine BS-Funktion unterbrochen hat, und bei einem zweiten Aufruf aus der IRR würde man die geretteten Register etc. des unterbrochenen Programmes überschreiben (die Trap-Handler sind nicht reentrant)!!! Es gibt hier nur zwei Möglichkeiten: Entweder man ruft die gewünschte Funktion direkt auf (Parameter auf den Stack legen, dann JSR $FCxxxx), oder man schreibt sie sich selbst...

Ihr Einsatz, bitte!

Wer der Maschinensprache der MC68000 nicht mächtig ist, möge diesen Abschnitt überlesen und sich gleich ans Eintippen von Sleep machen; zumindest können Sie in Zukunft mitreden, wenn es mal wieder um IPL, IRs und MFP geht.

Wie? Sie lesen immer noch? Na gut, ich habe Sie jedenfalls gewarnt... So schön ein sich in Super-Zeitlupe aufbauendes Fenster auch sein mag: Sicherlich gibt es wesentlich sinnvollere Anwendungen für einen Timer. Zwei davon möchte ich Ihnen kurz als Anregung vorstellen. Da wäre zunächst einmal die Implementation eines Spoolers (Simultaneous Peripheral Operation On Line, was zu deutsch soviel bedeutet wie: Parallele Ausführung von Ein- und Ausgaben), der es erlaubt, alle Druckausgaben im Hintergrund ablaufen zu lassen, also ohne Wartezeit für den Benutzer. Der Einfachheit halber benutzen wir den Druckertreiber 1st Patch und erweitern ihn folgendermaßen: Im BSS legen wir uns einen Puffer (z. B. der Länge 20k) an, der als Zwischenspeicher für die zu druckenden Daten dient. Zwei anzulegende Pointer verweisen auf die aktuelle Schreib- und Leseposition innerhalb dieses Puffers. Anstatt die Daten von Ist Patch aus direkt an den Drucker auszugeben, werden sie in den Puffer geschrieben und der Schreib-Pointer entsprechend erhöht. Ist der Puffer voll, so wird wieder beim ersten Byte des Puffers weitergeschrieben, wobei man beachten muß, daß die aktuelle Leseposition nicht überschritten wird (Turn-around). Über den Timer A steuern wir nun eine IRR an, die überprüft, ob im Puffer noch auszugebende Daten stehen (Schreibposition > Leseposition, Überlauf beachten!). Ist dies der Fall, so wird pro IR 1 Byte mit einer eigenen Routine an den Drucker geschickt (oder über TOS, s. o.) und der Lesezeiger inkrementiert. Fertig!

So einfach wie der Spooler ist die zweite Anregung nicht mehr: Multitasking heißt die Devise. Ein wenig seltsam ist die MC68000 ja schon: Da stellt sie einerseits dem Systemprogrammierer einen mächtigen Semaphoren-Befehl (TAS adr) zur Verfügung, der im Multitasking-Betrieb sicherstellen kann, daß immer nur ein Prozeß auf einen kritischen Bereich zugreifen darf, und ist andererseits nicht dazu in der Lage, einen doppelten Busfehler zu beheben:

Geben Sie mal im S-Mode den Befehl: move.b d0,-(a7). Wenn sich dann noch irgend etwas rührt, empfehle ich das Aufschrauben des ST-Gehäuses zwecks Überprüfung, ob da auch wirklich eine MOTOROLA MC68000 ihren Dienst versieht... Trotzdem läßt sich bei sauberer Programmierung das quasiparallele Ausführen von Prozessen erreichen. Es würde Bände füllen, auf alle Besonderheiten und Eventualitäten bei der Implementation einzugehen, deshalb hier nur ein paar allgemeine Hinweise: Jeder der parallelen Prozesse benötigt eigene Stacks (für U-und S-Mode). Der Dispatcher, also der Prozeß-Umschalter, muß bei einem Jobwechsel die Register des aktuellen Prozesses retten und die des nächsten laden. Alle Trap-Handler müssen mit einem Semaphoren ausgestattet werden, der von einem Prozeß vor Eintritt ins BS abgeprüft wird (TAS-Befehl!). War er Null, so wird er auf -1 gesetzt, und der Prozeß darf die Routine aufrufen, sonst muß gewartet werden, bis der Job, der sich gerade im BS befindet, den Semaphor wieder auf Null gesetzt hat (muß genau vor RTE, also dem Rücksprung ins Programm, erfolgen). Was sonst noch getan werden muß, hängt ganz davon ab, welche Forderungen man an das System stellt; hier ist also Ihre Phantasie und Intuition gefragt! Wie man sieht, kann man mit Timern und IRs eine ganze Menge anstellen; das zumindest sollte man nach dem Lesen dieses Artikels gelernt haben.

M. Schumacher

; #######################################
; #                S L E E P      05/87 #
; #######################################
; # gedrückte Taste:    | Wirkung:      #
; #---------------------+---------------#
; # - (10er-Block)      | CPU langsamer #
; # + (10er-Block)      | CPU schneller #
; # Shift rechts        | CPU 'stoppen' #
; # Shift links         | ...fortsetzen #
; # Backspace           | full speed on #
; #######################################

text

TASR	equ	$FFFA0F	: Timer	A Service Register
TACR	equ	$FFFA19	; Timer	A Control Register
TADR	equ	$FFFA1F	; Timer	A Data Register
FLOPLK	equ	$43E	; Flag,	ob Floppy aktiv ist
KEY		equ $E39	; enthält Code der derzeit gedrückten Taste
SKEY	equ $E1B	; enthält Status der Sondertasten

LOAD:
	pea		IRR			; Adresse der Timer A-Interrupt-Routine
	move.w	#64,-(a7)	; DATA	= 64
	move.w	#7,-(a7)	; CRTL	= 7 (Vorteiler auf 200)
	clr.w	-(a7)		; Timer A (=0)
	move.w	#31,-(a7)	; mit XBTIMER()...
	trap 	#14			;...des XBIOS aktivieren...
	add.l	#12,a7		; ...und Stapel korrigieren,
	clr.w	-(a7)		; kein Fehler aufgetreten
	move.l	#600,-(a7)	; 600 Bytes reservieren...
	move.w	#$31,-(a7)	; ...KEEP PROCESS...
	trap	#1			; ...und weggetreten!
;=========================================================
IRR:					; Hier geht’s lang beim Timer A-IR!
	movem.l	d0-d1,-(a7)	; verwendete Register retten
	move.b	#0,TACR		; erstmal Timer A ausschalten
	bclr	#5,TASR		; und die MFP-IRs freigeben,
	andi.w	#$F3FF,SR	; außerdem alle IRs ab IPL 4
	tst.w	FLOPLK		; Floppyzugriff???
	bne		ENDE		; ja, dann fertig
	btst	#2,8(a7)	; VBL-IRR unterbrochen???
	bne		ENDE		; dann aber hurtig beenden!

PLUS:					; Verzögerung bei erhöhen
	move.b	KEY,d0 		; Tastaturcode der gedrückten Taste
	cmpi.b	#$4A,d0		;	=	auf	10er-Block?
	bne.s	MINUS		;	nein

DEC:					; Vorteilungsfaktor dekrementieren
	cmpi.b	#1,CONTROL	; Schon kleinste Vorteilerstufe?
	beq.s	DELAY_TEST	; ja, dann Verzögerung erhöhen
	subi.b	#1,CONTROL	; sonst kleinere Vorteilung wählen
	bra.s	WAIT		; und warten

DELAY_TEST:			; Verzögerungsfaktor erhöhen
	cmpi.l	#100000,DELAY	; obere Grenze schon erreicht?
	beq.s	WAIT		; ja, dann nur warten
	addi.1	#2,DELAY	; sonst erst Verzögerung	erhöhen
	bra.s	WAIT		; und dann warten

MINUS:				; Verzögerung bei verkleinern
	cmpi.b	#$4E,d0		; = ' + '	auf 10er-Block?
	bne.s	BS		; nein,	dann vielleicht Backspace?
	tst.l	DELAY		; if Verzögerung=0
	beq.s	INC			; then CONTROL++
	subi.l	#2,DELAY	; 	else DELAY-=2 und
	bra.s	WAIT		; 		warten

INC:				; Vorteilungsfaktor inkrementieren
	cmpi.b	#7,CONTROL	; Vorteilung schon maximal?
	beq.s	KEYPRESS	; ja
	addi.b	#1,CONTROL	; sonst erhöhen
	bra.s	KEYPRESS

BS:
	cmpi.b	#14,d0		; Backspace-Taste gedrückt?
	bne.s	WAIT		; nein, nur warten
	move.l	#0,DELAY	; sonst CPU auf volle Lotte und
	move.b	#7,CONTROL	; Vorteilung auf 200 einstellen
	bra.s	ENDE		; fertig

WAIT:					; Warteschleife
	move.l	DELAY,d0	; d0=Verzögerungszähler
	beq.s	KEYPRESS	; falls 0, dann nicht warten
\wait_lpl:				; sonst	warten...
	move.l	DELAY, d1	; ...je nach Verzögerung...
\wait_lp2:
	dbra	d1,\wait_lp2	; ...aber immer...
	dbra	d0,\wait_lpl	; ... im Quadrat!

KEYPRESS:
	btst	#0,SKEY		; rechte Shift-Taste gedrückt?
	beq.s	ENDE		; nein, dann fertig
\key_release:
	btst	#1,SKEY		; sonst warten, bis linke Shift-
	beq.s	\key_release	; Taste gedrückt wird

ENDE:					; alles hat ein Ende, nur der Timer	nicht!
	move.b	#64,TADR	; Timer-Datenregister neu laden. . .
	move.b	CONTROL,TACR	; ...und aktivieren...
	movem.l (a7)+,d0-d1	; ...schließlich Register restaurieren...
	rte					; ...und Ende der Veranstaltung!

	data

DELAY:		dc.l 0	; enthalt Wert für Verzögerung
CONTROL:	dc.b 7	; enthält Wert für Timer A-Control-Register

end
Open "O"#1,"SLEEP.PRG"
Cls
Fertig$="Ende"
Do
	Read DatS 
	Inc Cnt%
	Exit If Instr(DatS,Fertig$,1)>0 
	DatS=RightS(DatS,Len(DatS)-1)
	If (Cnt% Mod 12)=1 
		Lin%=Val(Dat$)
		Chk%=0
		Print At(38,25);Lin%;
	Else
		Dat$=”&"+Dat$ 
		If (Cnt% Mod 12)=0 
			If Chk%<>Val(Dat$)
				Print " => Fehler!"
				Chk%=0
			Endif
		Else
			Add Chk%,Val(Dat$)
			Out #1,Val(Dat$)
		Endif
	Endif
Loop
Read Dat$
If Chk%<>Val("&"+RightS(Dat$,Len(Dat$)-1))
	Print " => Fehler!"
Endif
'
' #####################################################
Dat_lines:
Data 00001, 60, 1A, 00, 00, 01, 10, 00, 00, 00, 06, 091
Data 00002, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 000
Data 00003, 00, 00, 00, 00, 00, 00, 00, 00, 48, 79, 0C1
Data 00004, 00, 00, 00, 2A, 3F, 3C, 00, 40, 3F, 3C, 160
Data 00005, 00, 07, 42, 67, 3F, 3C, 00, 1F, 4E, 4E, 1E6
Data 00006, DF, FC, 00, 00, 00, 0C, 42, 67, 2F, 3C, 2FB
Data 00007, 00, 00, 02, 58, 3F, 3C, 00, 31, 4E, 41, 195
Data 00008, 48, E7, C0, 00, 13, FC, 00, 00, 00, FF, 3FD
Data 00009, FA, 19, 08, B9, 00, 05, 00, FF, FA, 0F, 3E1
Data 00010, 02, 7C, F3, FF, 4A, 78, 04, 3E, 66, 00, 3DA
Data 00011, 00, B0, 08, 2F, 00, 02, 00, 08, 66, 00, 157
Data 00012, 00, A6, 10, 38, 0E, 39, 0C, 00, 00, 4A, 18B
Data 00013, 66, 2C, 0C, 39, 00, 01, 00, 00, 01, 14, OED
Data 00014, 67, 0A, 04, 39, 00, 01, 00, 00, 01, 14, 0C4
Data 00015, 60, 60, 0C, B9, 00, 01, 86, A0, 00, 00, 2AC
Data 00016, 01, 10, 67, 54, 06, B9, 00, 00, 00, 02, 18D
Data 00017, 00, 00, 01, 10, 60, 48, 0C, 00, 00, 4E, 113
Data 00018, 66, 28, 4A, B9, 00, 00, 01, 10, 67, 0C, 215
Data 00019, 04, B9, 00, 00, 00, 02, 00, 00, 01, 10, 0D0
Data 00020, 60, 2E, 0C, 39, 00, 07, 00, 00, 01, 14, 0EF
Data 00021, 67, 3A, 06, 39, 00, 01, 00, 00, 01, 14, 0F6
Data 00022, 60, 30, 0C, 00, 00, 0E, 66, 14, 23, FC, 243
Data 00023, 00, 00, 00, 00, 00, 00, 01, 10, 13, FC, 120
Data 00024, 00, 07, 00, 00, 01, 14, 60, 26, 20, 39, 0FB
Data 00025, 00, 00, 01, 10, 67, 0E, 22, 39, 00, 00, 0E1
Data 00026, 01, 10, 51, C9, FF, FE, 51, C8, FF, F4, 634
Data 00027, 08, 38, 00, 00, 0E, 1B, 67, 08, 08, 38, 118
Data 00028, 00, 01, 0E, 1B, 67, F8, 13, FC, 00, 40, 2D8
Data 00029, 00, FF, FA, 1F, 13, F9, 00, 00, 01, 14, 339
Data 00030, 00, FF, FA, 19, 4C, DF, 00, 03, 4E, 73, 401
Data 00031, 00, 00, 00, 00, 07, 00, 00, 00, 00, 02, 009
Data 00032, 60, 0A, 0C, 0C, 0E, 0C, 0A, 0A, 12, 08, 0CA
Data 00033, 08, 08, 26, 00, Ende, 036

Listing 2: GfA-BASIC-Lader für Sleep



Aus: ST-Computer 12 / 1987, Seite 89

Links

Copyright-Bestimmungen: siehe Über diese Seite