				  __________________________________________
         ______  |::.                                    .::|  ______
        //_____\ |::::  ASSEMBLERKURS - LEKTION 11		::::| /_____\\
       _\\ O o / |::::::::::::::::::::::::::::::::::::::::::| \ o O //_
       \_/\_-_/_  ------------------------------------------' _\_-_/\_/
        / __|.  \/ /                                      \ \/  .|__ \
       /____|. \__/                                        \__/ .|____\
    .---/   |.  \--------------------------------------------/  .|   \---.
   /   /____|____\      INTERRUPT, CIAA/CIAB, DOSLIB        /____|____\   \
  /                                                                        \
  `-------------------------------------------------------------------------'

Autor: Fabio Ciucci

(Verzeichnis Sorgenti7) - dann schreibe "V Assembler3:sorgenti7"

Jetzt, da Sie den Blitter gründlich kennen, können sie auch weil sie wissen  
wie man den 68000 und den Copper programmiert mit Sicherheit sagen, das sie  
die Amiga-Hardware kennen. Man hört jedoch nie auf zu lernen, und in dieser 
Lektion werden wir weitere Informationen über den 68000 sehen, wie Interrupts,
sowie Listings, die Details von Blitter und Copper zeigen und endlich die
Verwendung der CIAA- und CIAB-Chips, die wir bisher nur zum Testen der 
Maustaste verwendet haben.
Ich würde vorschlagen, wir beginnen mit neuen Informationen zum 68000, um einen 
besseren Startup-Code zu erstellen als den, den wir in Lektion 8 gemacht haben.
Die Informationen über Exception-Vektoren, Interrupts usw. die erklärt werden,
werden nicht alle nützlich und unverzichtbar für die Programmierung von Spielen
und Demos sein, nur ein Teil von ihnen wird uns dienen. 
Also lassen Sie sich nicht von der Menge der genannten Dinge einschüchtern.
In der Praxis werden wir nur sehr wenig davon verwenden!
Zunächst ist es notwendig, über die beiden Betriebsmodi des 680x0 zu sprechen,
nämlich den User-Mode und den Supervisor-Mode. Bereits in 68000-2.txt wurde
erwähnt, ohne zu erklären, dass es "privilegierte" Befehle gibt, die im 
Supervisor-Modus ausgeführt werden müssen, dh während einer Ausnahme oder eines
Interrupts.
Für den Moment haben wir unsere Routinen immer im USER-Modus laufen lassen, 
weil es nicht notwendig war privilegierte Befehle auszuführen und weil wir 
keine Prozessor-Interrupts benutzt haben.
Ich muss darauf hinweisen, dass sich die Ausführungsgeschwindigkeit zwischen
User- und Supervisor-Modus nicht ändert, sodass die Ausführung Ihres Programms
als Ausnahme oder Interrupt die Ausführung nicht stören wird.
Andererseits wird eine "Exception" so genannt, weil es sich um etwas handelt,
das nur in "außergewöhnlichen" Fällen passieren darf, zum Beispiel bei einem
Softwarefehler, bekannt als GURU. Beachten Sie, dass es Gurus gibt, die durch
das Amiga-Betriebssystem verursacht werden (wie Exec-Fehler) und andere, die
direkt von Motorola dem Hersteller des 680x0 programmiert sind. Zum Beispiel
die Fehler von BUS, DIVISION BY ZERO, usw., d.h. die Vektoren sind von dieser
Art.
Sie werden vielleicht bemerkt haben, dass das Betriebssystem die Position des
Mauszeigers auch dann aktualisieren kann, auch wenn wir unsere eigene Routine
ausführen, solange wir das Multitasking nicht mit einem Aufruf von Disable ()
deaktivieren.
Nun, wie ist der Fall, wenn der Prozessor unsere Schleife ausführt um in jedem
Frame andere Dinge zu aktualisieren? Sie werden sich daran erinnern, dass alles
einfriert, wenn Sie die Systeminterrupts deaktivieren. 
In der Tat werden Interrupts verwendet um die Ausführung unseres Programms in
jedem Frame zu "unterbrechen", um die eigene Routine auszuführen und es setzt
die Ausführung unseres Programms an der Stelle fort, wo es verlassen wurde ohne 
dass wir es merkten! Neben Interrupts gibt es noch andere Möglichkeiten,
Routinen in Supervisor auszuführen, z.B. TRAP-Befehle, oder Fehler in einem 
Programm. Zum Beispiel, wenn eine Division durch Null auftritt oder der 
Prozessor Daten findet, die keinem Befehl entsprechen, werden die Routinen der
Guru-Meditation und des Softwarefehlers in einem Supervisor-Modus ausgeführt.
Die Emulation anderer Prozessoren sorgt beispielsweise dafür, dass jeder
Binärwert eines Befehls des emulierten Prozessors, zum Beispiel eines 80286
oder des 6502 des Commodore 64, einer Routine entspricht, die die Operationen
ausführt, als wäre diese Anweisung für diesen Prozessor gemacht. (mehr oder 
weniger!). *1) Nun ist es nicht das Ziel dieses Kurses, das Programmieren von
Betriebssystemen oder Emulatoren zu lernen, da das Amiga-Betriebssystem bereits
das beste der Welt ist, und die Emulatoren auf dem Amiga unübertroffen sind,
so sehr, dass jeder, der einen Amiga hat, die Macintosh- und MSDOS-Programme
ausführen und die alten C64- und Spectrum-Spiele spielen kann. 
Aber selbst wenn Sie eine Demo oder ein Spiel programmieren wollen, oder ein 
Dienstprogramm wie protracker / octamed programmieren wollen, ist es SEHR 
NÜTZLICH, einige Interrupts zu handhaben. Viele ältere Demos und Spiele 
begannen mit der Überwachung des 68000, schrieben sofort in das SR und machten
seltsame Spiele mit dem Stack.
Nun, viele dieser Spiele funktionieren nicht auf Computern mit 68020, weil das
Statusregister in den moderneren Prozessoren zusätzliche Funktionen hat, was
manche Programmierer ignoriert haben und durch Setzen oder Zurücksetzen von 
Bits in großen Mengen haben Sie einen großen Fehler gemacht. 
Auch im Supervisor-Modus ist der Stack (SP) ein "privater" Stack des 
Supervisor-Modus, während auf den User-Stack über das USP-Register (User Stack
Pointer) zugegriffen wird. Kurz gesagt, ist es im Supervisor-Mode besser 
Vorsicht walten zu lassen, es sei denn, sie kennen sich perfekt mit allen neuen
Funktionen des 68020/30/40 und auch des 68060 aus!
Tatsächlich dient der Benutzermodus genau dazu, zu vermeiden das die Ausführung
von Anweisungen unterschiedliche Auswirkungen auf verschiedene Prozessoren haben 
können. 
Aber Programmierer haben es immer als schwieriger empfunden, die Register im
Supervisor-Mode umzudrehen, mit dem Ergebnis das sie den Ruf erhalten haben
"Programmierer, die zu inkompatiblem Code nicht fähig sind".
Doch bevor wir mit der Theorie fortfahren, wollen wir erst einmal einige 
Anweisungen im Supervisor-Mode ausführen. Von den vielen Möglichkeiten, eine 
Routine in einer Ausnahme auszuführen, ist die "sicherste" die Verwendung der 
speziellen Funktion des Betriebssystems "exec.library / Supervisor", die die
Eingabe der Adresse der auszuführenden Routine in A5 erfordert: 

	move.l	4.w,a6			; ExecBase in a6
	lea	SuperCode(PC),a5	; Routine zur Ausführung im Supervisor
	jsr	-$1e(a6)			; LvoSupervisor - Führen Sie die Routine aus
					; (es speichert nicht die Register! Achtung!)
	rts				; beenden, nachdem die "SuperCode" Routine
					; in supervisor ausgeführt wurde.
					
			
SuperCode:
	movem.l	d0-d7/a0-a6,-(SP)	; speichern der Register auf dem Stack
	...							; auszuführende Anweisungen
	...							; wie ein Unterprogramm....
	movem.l	(SP),d0-d7/a0-a6	; Register vom stack nehmen
	RTE	; Return From Exception: wie RTS, jedoch für Ausnahme.


Wie Sie sehen, gibt es nichts Einfacheres.
Sie können die auszuführende Routine als ein mit JSR oder BSR aufzurufendes
Unterprogramm betrachten, nur wird sie mit "JSR -$1e(a6)" aufgerufen, nachdem
die Adresse in A5 und natürlich die ExecBase in A6 eingegeben wurde. Am Ende
der kurzen "Supervisor-Subroutine" muss um zurückzukehren statt eines RTS, ein
RTE eingefügt werden, das speziell für die Rückkehr von Ausnahmen und 
Interrupts dient. An diesem Punkt kehrt der Prozessor zurück und führt die 
Anweisung unter "JSR -$1e(a6)" im Benutzermodus aus, als ob es ein "BSR" oder
"JSR" wäre. Die Supervisor-Funktion speichert die Register nicht und stellt sie
anschließend nicht mit movem wieder her, d.h. wenn sie während der Supervisor-
Routine geändert werden, bleiben diese Werte bei der Rückkehr aus dieser
Routine erhalten.
In diesem Zusammenhang empfehle ich Ihnen, die Register manuell am Anfang und 
am Ende der Supervisor-Routinezu zu speichern und wiederherzustellen.
Beachten Sie, dass der Zugriff auf den SP oder A7 im Supervisor-Modus im 
Supervisor-Stack (SSP) und nicht im User-Stack, der als USP aufgerufen werden 
kann, gespeichert wird. Dies ist kein Problem, da der Supervisor-Stack wie der
User-Stack speichert und wiederherstellt, aber im Falle einer gefährlichen 
Stack-Programmierung könnte es zu allgemeinen Implementierungsfehler kommen.

Im Listing11a.s. führen wir eine privilegierte Anweisungen aus.
Hier sind die privilegierten Befehle, die nur im Supervisor-Modus ausgeführt 
werden können:

	ANDI.W	#xxxx,SR
	ORI.W	#xxxx,SR
	EORI.W	#xxxx,SR
	MOVE.W	xxxxx,SR
	MOVE.W	SR,xxxxx
	MOVEC	register,register	; 68010+ - Spezialregister für
					; cache control, MMU, Vektoren wie:
					; CAAR,CACR,DFC,ISP,MSP,SFC,USP,VBR.
	RTE

Es gäbe auch MOVES, RESET, STOP, aber das interessiert uns nicht.
Die Möglichkeit, auf das Statusregister einzuwirken, ist von geringem 
Interesse, weil es sehr gefährlich ist (angesichts des Unterschieds der SR-Bits
zwischen 68000 und anderen 680x0), und es nicht notwendig ist, es zu stören.
Unter anderem, wenn Sie zu einer Exception springen, werden zusätzlich zum 
Programm Counter, auch das Statusregister auf dem Stack gespeichert, und zum 
Zeitpunkt des RTE wird der alte Wert des Programm Counters wiederhergestellt,
um unter dem "JSR -$1e(a6)" zurückzukehren, und das alte SR wird 
wiederhergestellt, so dass es keine Änderungen gibt, außer während der 
Ausführung der Ausnahme.
Stattdessen wird uns der MOVEC-Befehl viel mehr interessieren, denn um einen
Interrupt so zu verwenden, dass er auch auf 68020+ Prozessoren funktioniert,
ist es notwendig zu wissen, wo sich das VBR (Vector Base Register) befindet.
Was die Steuerung des CACHE betrifft, so halte ich es für besser, nicht 
einzugreifen, damit der Benutzer sie mit Dienstprogrammen aktivieren oder 
deaktivieren kann, bevor er das Demo oder dem Spiel aktivieren oder deaktivieren
kann, um die Unterschiede in der Einstellung zu bewerten. Wenn wir stattdessen 
entscheiden, welche Caches aktiviert und deaktiviert werden müssen, besteht
außerdem die Gefahr, dass Code erstellt wird, der auf dem 68060 und alle neuen
RISC-Prozessoren, die es emulieren könnten nicht funktioniert.
Der MOVEC-Befehl, der erst ab 68010 verfügbar ist, wird zum Kopieren des
Inhalts eines An oder Dx Registers in ein spezielles Registers oder umgekehrt
verwendet. Schauen wir uns die Spezialregister im 68020 ab:

	CAAR	- CAche Address Register
	CACR	- CAche Control Register
	VBR		- Vector Base Register

Es gibt auch DFC (Destination Function Code), SFC (Source Function Code),
ISP (Interrupt Stack Pointer), MSP (Master Stack Pointer), USP (User Stack
Pointer), aber wir sind nicht an ihnen interessiert, weil sie nur denen dienen, 
die Betriebssysteme, Emulatoren usw. programmieren.
Wir müssen jetzt den Wert des speziellen VBR-Registers kennen, wir werden 
später sehen warum. Um es zu erhalten, reicht zum Beispiel ein "MOVEC VBR, d0".
Aber was ist das Vector Base Register? Zunächst müssen wir erklären, was ein 
Vektor ist (nicht zu verwechseln mit Vektoren in der Mathematik oder 
3D-Vektoren!).
Zuerst sehen wir uns die Vektortabelle an, dann erklären wir sie:

VEKTOR NUM.   OFFSET	Zuordnung und Bedeutung

	0	$0	dient nur zum Zeitpunkt des Resets (SSP start)
	1	$4	dient nur dem Reset, tatsächlich gibt es ExecBase (PC start)
	2	$8	GURU/soft. Fehler: BUS-Fehler
	3	$c	GURU/soft. Fehler: Adressfehler
	4	$10	GURU/soft. Fehler: illegale Anweisung
	5	$14	GURU/soft. Fehler: Division durch Null
	6	$18	Ausnahme generiert durch Anweisung CHK,CHK2 (68020+)
	7	$1c	Ausnahme generiert durch Anweisung  TRAPV (68020+ TRAPCC)
	8	$20	GURU/soft. Fehler: Rechteverletzung
	9	$24	Trace (trace exception)
	$A	$28	GURU/soft. Fehler: Line emulator %1010 (LINE-A)
	$B	$2c	GURU/soft. Fehler: Line emulator %1111 (LINE-F)
	$C	$30	nicht verwendet
	$D	$34	Coprozessor-Protokollverletzung (68020+)
	$E	$38	Formatfehler (nur 68020, nach CALLM,RTM)
	$F	$3c	Nicht initialisierte Unterbrechung
	...	...

	$18	$60	Versehentliche Unterbrechung

; Hier sind die Vektoren der Interrupts: Dies sind die, die uns interessieren!

	$19	$64	INTERRUPT Level 1 (softint,dskblk,tbe)
	$1a	$68	INTERRUPT Level 2 (ports: I/O,ciaa,int2)
	$1b	$6c	INTERRUPT Level 3 (coper,vblanc,blit)
	$1c	$70	INTERRUPT Level 4 (Kanal audio aud0/aud1/aud2/aud3)
	$1d	$74	INTERRUPT Level 5 (rbf,dsksync)
	$1e	$78	INTERRUPT Level 6 (exter: ciab,int6 + inten)
	$1f	$7c	INTERRUPT Level 7 (external hardware cards: NMI)

	$20	$80	Vektor aufrufbar mit TRAP #0
	$21	$84	Vektor aufrufbar mit TRAP #1
	$22	$88	Vektor aufrufbar mit TRAP #2
	$23	$8c	Vektor aufrufbar mit TRAP #3
	$24	$90	Vektor aufrufbar mit TRAP #4
	$25	$84	TRAP Vektor #5, etc. , Ende TRAP #15
	...

	es folgen Vektoren für Fehler des möglichen mathematischen Coprozessors
    und der MMU, die uns nicht interessieren.


                             -|             |- 
         -|                  [-_-_-_-_-_-_-_-]                  |- 
         [-_-_-_-_-]          |             |          [-_-_-_-_-] 
          | o   o |           [  0   0   0  ]           | o   o | 
           |     |    -|       |           |       |-    |     | 
           |     |_-___-___-___-|         |-___-___-___-_|     | 
           |  o  ]              [    0    ]              [  o  | 
           |     ]   o   o   o  [ _______ ]  o   o   o   [     | ----__________
_____----- |     ]              [ ||||||| ]              [     | 
           |     ]              [ ||||||| ]              [     | 
       _-_-|_____]--------------[_|||||||_]--------------[_____|-_-_ 
      ( (__________------------_____________-------------_________) ) 


Stellen wir uns die Situation eines 68000-Prozessors vor, bei dem es nicht
nötig ist den Wert des VBR-Registers nachzuschlagen (es existiert nicht 
einmal!).
In diesem Fall ist der Offset genau der Speicherplatz! $0 = $00000000. An der
Adresse $4 finden wir die Execbase, während das erste Long (Adresse) bei $0  
normalerweise zurückgesetzt ist. Aber hier "innerhalb" der Stelle $8 finden
wir die Adresse der Routine, die im Falle eines BUS-FEHLERS die Meldung 
"GURU MEDITATION / SOFTWARE FAILURE" erscheinen lässt. In der Tat hat dieser
Guru, wie in 68000-2.TXT zu sehen, eine Identifikationsnummer #00000002, dh
seine Vektornummer. Der dritte Vektor enthält die Adresse der Routine, die den
ADDRESS ERROR Guru (#00000003) aufruft lässt und so weiter. 
In der Praxis springt der Prozessor, wenn er einen dieser Fehler findet, zum 
entsprechenden Vektor, der die Adresse der auszuführenden Routine enthält, die
im Supervisor-Modus auszuführen ist (in Anbetracht der Ernsthaftigkeit der 
Situation!). Zum Zeitpunkt des Resets werden alle Adressen in die Vektoren 
vom ersten zum letzten aus dem ROM geschrieben. Wenn Sie Programme haben, die
die Meldungen des Software-Fehler oder Gurus so ändern, dass sie die Vektoren
auf Ihre Routinen anstelle von normalen Systemroutinen "zeigen". Natürlich 
gibt es "legale" Möglichkeiten, die Vektoren zu ändern, dh vorbeizugehen an den
Betriebssystemstrukturen und -routinen. Das Schreiben der Adresse der
eigenen Routine in den Vektor kann ineffektiv oder inkompatibel sein.

Zum Beispiel:

	MOVE.L	#MiaDivisionePerZero,$14.w	; Ich ersetze den Guru-Vektor
										; für Division durch Null.
	rts

MiaDivisionePerZero:
	...
	RTE
	
TUN SIE ES NIEMALS AUS VIELEN GRÜNDEN WIE IN DIESEM BEISPIEL. DER ERSTE IST,
DASS BEIM 68020+ NICHT GESAGT WIRD, DASS DIESER VEKTOR VON DER ADRESSE $14 
AUFGERUFEN WIRD. DER ZWEITE GRUND IST, DASS ES EINE INKOMPATIBLE METHODE MIT
DEN STRUKTUREN VOM MMU UND DES AMIGA BETRIEBSSYSTEMS IST.
Theoretisch sollte dieses System jedoch funktionieren, und auf dem Amiga 500
funktioniert es fast immer, solange die Supervisor-Routine gut geschrieben ist.
Die Modifikation der Fehlervektoren / Guru interessiert uns jedoch nur minimal,
weil unser Programm keine zu korrigierenden Fehler enthalten sollte und wenn
zum Guru / Softwarefehler springt!
Auch die Vektoren der "TRAP #xx"-Befehle sind für uns von geringem Interesse.
Solche Vektoren wurden in der Vergangenheit verwendet, um als Ausnahme zu 
laufen, aber wir haben bereits einen sichereren Weg gesehen, nämlich über das 
Betriebssystem. 

Für Ihre Neugierde: Das war die "alte" Methode::

	move.l	$80.w,OldVector		; speichern Sie den alten TRAP #0 Vektor
	move.l	#SuperCode,$80.w	; Routine zur Ausführung im Supervisor Mode
								; in den TRAP# 0 Vektor gelegt
	TRAP	#0					; Führen Sie den Supercode als Ausnahme aus
	move.l	OldVector(PC),$80.w	; den alten TRAP #0 Vektor wiederherstellen
	rts							; zurück, nach Ausführung der "SuperCode"
								; Routine in supervisor mode.
OldVector:
	dc.l	0
			
SuperCode:
	movem.l	d0-d7/a0-a6,-(SP)	; Register auf dem stack speichern 
	...							; Anweisungen auszuführen, als wäre
	...							; es ein Unterprogramm....
	movem.l	(SP),d0-d7/a0-a6	; Register vom stack nehmen
	RTE	; Return From Exception: wie RTS, jedoch für eine Ausnahme.

Wie Sie sehen, können Sie die Funktion des TRAP-Befehls leicht verstehen: 
Wenn Sie eine "TRAP #0"-Routine ausführen, wird in der Praxis die Routine,
deren Adresse in der .l-Adresse bei $80 enthalten ist, als Ausnahme 
ausgeführt,  während bei "TRAP #1" dies in $84 geschieht, und so weiter.
Ebenso enthalten Interrupt-Vektoren die Adresse der Routine die im Falle
eines Interrupts ausgeführt werden soll. Die alte Art, einen Interrupt zu
setzen, war:

	move.l	$6c.w,OldInt6c		; Speichern Sie den alten int Level 3
	move.l	#MioInt6c,$6c.w		; Meine Routine für int Level 3

Am Ende des Programms wurde der alte Interrupt in $6c wiederhergestellt.
In diesem Interrupt steht normalerweise "BSR.w MT_MUSIC", da dieser Interrupt
(VERTB) einmal pro Frame ausgeführt wird.

	                       _----|         _ _ _ _ _ 
	                        ----|_----|   ]-I-I-I-[ 
	    _ _ _ _ _ _ _----|      | ----|   \ `  ' / 
	    ]-I-I-I-I-[  ----|      |     |    |. ` | 
	     \ `   '_/       |     / \    |    | /^\| 
	      []  `__|       ^    / ^ \   ^    | |*|| 
	      |__   ,|      / \  / ^ ^`\ / \   | ===| 
	   ___| ___ ,|__   / ^  /=_=_=_=\ ^ \  |, `_| 
	   I_I__I_I__I_I  (====(_________)_^___|____|____ 
	   \-\--|-|--/-/  |     I  [ ]__I I_I__|____I_I_| 
	    |[] `    '|_  |_   _|`__  ._[  _-\--|-|--/-/ 
	   / \  [] ` .| |-| |-| |_| |_| |_| | []   [] | 
	  <===>      .|-=-=-=-=-=-=-=-=-=-=-|        / \ 
	  ] []|` ` [] | .   _________   .   |-      <===> 
	  <===>  `  ' ||||  |       |  |||  |  []   <===> 
	   \_/     -- ||||  |       |  |||  | .  '   \_/ 
	  ./|' . . . .|||||/|_______|\|||| /|. . . . .|\_ 
	- --------------------------------------------------- 

*******************************************************************************
*		DAS VBR-REGISTER IM 68010 UND HÖHEREN PROZESSOREN					  *
*******************************************************************************

Sie fragen sich vielleicht, was VBR mit den Vektoren zu tun hat. Nun, das 
Vektor Base Register ist die Basisadresse, zu der die Offsets addiert werden 
müssen, um die Adresse der Vektoren zu finden. Wenn VBR = 0 ist, dann liegt der
der Interrupt Level 3 bei $6c, wie in 68000 und ebeneso wird TRAP #0 immer bei
$80 sein, und die oben gezeigten Beispiele würden funktionieren. Aber wenn VBR
bei $10000 wäre, würde der Level 3 Interrupt nicht mehr bei $6c, sondern bei
VBR + $6c sein, was $1006c entspricht! Dasselbe gilt für alle anderen Vektoren.

Also, im Prinzip:

	Adresse des Vekors = VBR + OFFSET

Auf dem 68000-Prozessor ist die Basis immer $0000, so dass weder das Register
VBR noch den privilegierten MOVEC-Befehl gibt. Aber auf 68010 und höher kann 
das VBR an andere Stellen verschoben werden, sogar ins FAST RAM. Nach einem 
Reset wird der VBR jedoch immer zurückgesetzt, sowohl auf dem A3000 als auch
auf dem A1200 oder A4000. Durch die Ausführung von SETPATCH oder anderen 
Dienstprogrammen wird der VBR verschoben.
Tatsächlich funktionieren viele Demos / Spiele als Dateien, wenn sie von selbst
von der Diskette gestartet werden ohne vorheriges Laden des Setpatch. Sie 
funktionieren nicht, wenn sie von der Workbench-Shell geladen werden. Einige 
funktionieren zwar, aber sie sind "stumm", weil sie ihren Interrupt, der nur 
die Musik abspielt, in $6c schreiben, obwohl der VBR auf mehr als $0000 zeigt.
Da wir das wissen, überprüfen wir einfach, ob der Prozessor ein 68000 oder ein
68010+ ist, und wenn es ein 68010 oder höher ist, nehmen wir den Wert des VBR
und addieren ihn zu dem Vektor, den Sie ändern möchten hinzu.
Hier steht, wie es in der Praxis gemacht wird:

	move.l	4.w,a6		; ExecBase in a6
	btst.b	#0,$129(a6)	; testen, ob wir auf einer 68010 oder höher sind
	beq.s	IntOK		; es ist ein 68000! Dann ist die Basis immer Null.
	lea	SuperCode(PC),a5 ; Routine zur Ausführung im Supervisor Mode
	jsr	-$1e(a6)		; LvoSupervisor - Führen Sie die Routine aus
						; (speichert nicht die Register! Achtung!)
	bra.s	IntOK		; wir haben den Wert des VBR, wir machen weiter ...

;**********************   SUPERVISOR CODE für 68010+   ************************
SuperCode:
	movem.l	a0-a1,-(SP)	; a0 und a1 auf dem stack speichern
	dc.l  	$4e7a9801	; movec VBR,A1 (Anweisung 68010+).
						; es ist hexadezimal, weil nicht alle Assembler
						; das movec assemblieren.
	lea	BaseVBR(PC),a0	; Label, wo der VBR-Wert gespeichert werden soll
	move.l	a1,(a0)		; den Wert speichern
	movem.l	(SP)+,a0-a1	; die alten Werte von a0 und a1 wiederherstellen
	RTE					; Rückkehr von der Ausnahme (exception)
;*****************************************************************************

BaseVBR:
	dc.l	0

IntOK:
	move.l	BaseVBR(PC),a0		; In a0 der Wert von VBR
	move.l	$64(a0),OldInt64	; Sys int lev 1 gespeichert (softint,dskblk)
	move.l	$68(a0),OldInt68	; Sys int lev 2 gespeichert (I/O,ciaa,int2)
	move.l	$6c(a0),OldInt6c	; Sys int lev 3 gespeichert (coper,vblanc,blit)
	move.l	$70(a0),OldInt70	; Sys int lev 4 gespeichert (audio)
	move.l	$74(a0),OldInt74	; Sys int lev 5 gespeichert (rbf,dsksync)
	move.l	$78(a0),OldInt78	; Sys int lev 6 gespeichert (exter,ciab,inten)

	movem.l	d0-d7/a0-a6,-(Sp)	; Register auf dem stack speichern
	bsr.s	START				; Hauptroutine ausführen
	movem.l	(sp)+,d0-d7/a0-a6	; Register vom stack wiederherstellen

	move.l	BaseVBR(PC),a0	     ; In a0 der Wert von VBR
	move.l	OldInt64(PC),$64(a0) ; Sys int lev1 gespeichert (softint,dskblk)
	move.l	OldInt68(PC),$68(a0) ; Sys int lev2 gespeichert (I/O,ciaa,int2)
	move.l	OldInt6c(PC),$6c(a0) ; Sys int lev3 gespeichert (coper,vblanc,blit)
	move.l	OldInt70(PC),$70(a0) ; Sys int lev4 gespeichert (audio)
	move.l	OldInt74(PC),$74(a0) ; Sys int lev5 gespeichert (rbf,dsksync)
	move.l	OldInt78(PC),$78(a0) ; Sys int lev6 gespeichert (exter,ciab,inten)
	rts

START:
	move.l	BaseVBR(PC),a0	     ; In a0 ist der Wert von VBR
	move.l	#MioInt6c,$6c(a0)	 ; Ich lege meine interrupt Level 3 Routine ab
	...
	das Programm ausführen
	...
	rts

Beachten Sie, dass Sie, selbst wenn Sie die TRAP-Anweisung verwenden wollten, 
das BaseVbr in a0 einsetzen und den Offset auf $80(a0) setzen. Das Gleiche gilt
für alle Vektoren. Ab dieser Lektion gibt es ein neues Startup, startup2.s, das
anstelle von startup1.s verwendet wird. Der einzige Unterschied besteht darin,
dass es die Anweisungen siehe oben enthält und das Label BaseVbr verfügbar ist,
um die Interrupts auf allen Mikroprozessoren richtig zu modifizieren. Das 
Speichern alter Interrupts und deren Wiederherstellung am Ende werden vom 
Startup durchgeführt, zusammen mit dem Speichern und Wiederherstellen der DMA 
und INTENA-Kanäle. Eine weitere Änderung ist die Hinzufügung einer Routine, die
Maus- und Tastatureingaben an das Betriebssystem blockiert, dass das Laden der 
Dateien dienen wird. 
Jetzt, wo wir wissen, wie wir einen System-Interrupt durch unseren eigenen
ersetzen können, müssen wir sehen, wie wir unseren Interrupt machen. Um die
folgenden Seiten vorwegzunehmen, spielen wir die Musik über Interrupt, wie im
Beispiel Listing11b.s. Sie werden besser verstehen, wie es funktioniert, wenn
Sie weiterlesen!

                                |||||
.__________________________.oOo_/o_O\_oOo.____________________________________.
*******************************************************************************
*		WIE MAN EINE INTERRUPT ROUTINE "BAUT"								  *
*******************************************************************************

Das Interrupt-System ermöglicht es einem externen Gerät oder den Custom Chips
die Ausführung des Programms durch den Prozessor zu unterbrechen, so dass er
im User mode zu der Routine springt, deren Adresse in einer der 
Interruptvektoradressen (z.B. $6c) zu finden ist. Diese Interrupts haben
unterschiedliche Prioritätsstufen, die von einer minimalen Stufe (1) bis zur
höchsten Stufe (7) reichen. Diese Prioritäten dienen dem Fall, dass während der
Ausführung eines Interrupts ein oder mehrere Interrupts auftreten. Wird zum
Beispiel das normale Programm im Usermodus durch einen Low-Level-Interrupt, 
z.B. der Stufe 2 unterbrochen, und kommt während der Ausführung dieser Routine
die Anforderung durch einen Interrupt einer höheren Stufe, z.B. 5, wird der
Interrupt 2 durch den Interrupt 5, mit höhere Priorität unterbrochen und sobald
die Abarbeitung erfolgt ist kehrt er zum Interrupt der Ebene 2 zurück, der 
danach zum normalen Programm im Benutzermodus zurückkehrt. Auf diese Weise 
können mehrere Interruptroutinen warten, bis die Ausführung abgeschlossen  
ist und je nach ihrem Level werden sie zuerst ausgeführt oder früher beendet.
Die Notwendigkeit der Einführung von Interrupts bei den ersten Mikroprozessoren
ist darauf zurückzuführen, dass die CPU-Leistung aufgrund sehr langer 
Warteschleifen häufig falsch genutzt wird. Wenn man zum Beispiel auf den Beginn
des vertikalen Leerzeichens warten wollte, müsste man eine Schleife ausführen,
die die erreichte Zeile prüft, und bis diese Zeile erreicht ist tut der 
Prozessor nichts anderes, als in dieser lächerlichen Schleife stecken zu 
bleiben. Wenn die Wartezeit auf ein bestimmtes Signal einige Sekunden betragen
würde, stellen Sie sich vor, wie viel Prozessorleistung verschwendet werden
würde! Dafür gibt es Interrupts, die es dem Prozessor ermöglichen Programme 
auszuführen, ohne sich um das Warten auf "Ereignisse" zu kümmern. Wenn Sie bei
jedem vertikal blank einen Interrupt der Stufe 3 erzeugen, können Sie den 
Prozessor ein Fraktal oder ein 3D-Bild berechnen lassen, und wenn der 
Interrupt auftritt, zum Zeitpunkt des vertikal blank wird die Berechnung des
3D-Volumenkörpers unterbrochen. Anschließend wird die Routine mit der 3D 
Berechnung, die vor dem Vblank ausgeführt wurde weiter ausgeführt. Wir kehren
zurück, um die 3D-Routine dort fortzusetzen, wo sie vorher aufgehört hat.
Dadurch wird Multitasking möglich, weil Sie drucken oder von der Diskette lesen
können während sie andere Dinge machen können. Auf dem MSDOS-PC kann der 
Prozessor eine Aufgabe ausführen, die von der Diskette oder der seriellen / 
parallelen Schnittstelle unterbrochen wird, und wird dann fortgesetzt, sobald
diese Interrupts ausgeführt wurden. Der Amiga weist 6 von 7 verfügbaren 
Interrupt-Leveln Interruptsignalen von custom chips (Blitter, copper, CIA) zu,
die in bestimmten Situationen erzeugt werden.
Die siebte Ebene wird von externen Karten wie dem Action Replay verwendet, weil
die IPL2-IPL0-Leitungen, die ihn erzeugen, zum Erweiterungsport geführt werden.
Die ersten 6 Interrupt-Stufen werden von den Custom-Chips erzeugt, zum Beispiel 
wenn ein Blitt oder ein vertikaler Video-Blank abgeschlossen wird.

                                 ||||
                            _A_  /oO\
.__________________________(iIi)_\\//_oOo.____________________________________.
*******************************************************************************
*			ANWENDUNG VON INTENA UND INTENAR								  *
*******************************************************************************

Über das INTENA-Register ($dff09a) ist es möglich, einige dieser Interrupts zu
maskieren, das heißt, zu verhindern, dass sie generiert werden.
Es gibt auch ein Register zum Anfordern von Interrupts, das INTREQ ($dff09c). 
Diese Register funktionieren wie das DMACON ($dff09a). Tatsächlich entscheidet 
Bit 15, ob die angegebenen Bits gesetzt oder zurückgesetzt werden.
Wie beim DMACON / DMACONR in Lektion 8 sehen wir uns nun die "Belegung" an. Das
INTENA-Register ($dff09a) ist nur zum Schreiben und INTENAR-Register
($dff01c) ist nur zum Lesen:

INTENA/INTENAR ($dff09a/$dff01c)

BIT	NAME	 LEVEL	BESCHREIBUNG

15	SET/CLR			Steuerbit "Set/Clear". Bestimmt, ob die Bits die 1 sind
					zurückgesetzt oder gesetzt werden, wie in DMACON.
					die Bits = 0 werden nicht gesetzt oder zurückgesetzt
14	INTEN			Master Interrupt (general enable interrupt)
13	EXTER	6 ($78)	Externer Interrupt, an die INT6-Leitung angeschlossen
12	DSKSYN	5 ($74)	Wird generiert, wenn das DSKSYNC-Register mit den Daten 
					von der Diskette im Laufwerk eingelesen wurden. 
					Wird verwendet für Hardwarelader.
11	RBF		5 ($74)	serieller Port UART-Puffer voll nach Daten empfangen.
10	AUD3	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 3 beendet.
09	AUD2	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 2 beendet.
08	AUD1	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 1 beendet.
07	AUD0	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 0 beendet.
06	BLIT	3 ($6c)	Wenn der Blitter einen Blitt beendet hat, wird es auf 
					1 gesetzt
05	VERTB	3 ($6c)	Wird jedes Mal generiert, wenn der Elektronenstrahl	zur
					Zeile 00 geht, d.h. bei jedem Beginn eines vertical blank
04	COPER	3 ($6c) es kann mit dem copper eingestellt werden, um es an einer
					bestimmten Videozeile zu erzeugen.
					Einfach nach einem bestimmten WAIT abfragen.
03	PORTS	2 ($68)	Input/Output Port und Timer, die an die INT2-Leitung 
					angeschlossen sind
02	SOFT	1 ($64)	Reserviert für durch Software ausgelöste Interrupts.
01	DSKBLK	1 ($64)	Ende der Übertragung eines Datenblocks von der Diskette.
00	TBE		1 ($64)	UART-Puffer der seriellen Schnittstelle zum Senden ist leer

Wie Sie sehen können, ist die Analogie zu DMACON / DMACONR offensichtlich:
-Bit 15 ist sehr wichtig: Wenn es eingeschaltet ist, werden beim Schreiben in 
 $dff09A die Bits, die auf 1 gesetzt die zugehörigen Interrupts aktivieren. 
 Wenn das Bit 15 auf 0 ist, dann werden die Bits mit 1 im Register zum 
 Deaktivieren verwendet, das heißt die damit verbundenen Interrupts werden 
 maskiert. Um einen oder mehrere Interrupts wie in DMACON zu aktivieren oder zu
 deaktivieren müssen die relativen Bits auf 1 gesetzt werden. Was bestimmt ob
 die Interrupts aktiviert oder deaktiviert werden ist Bit 15: wenn es 1 ist
 werden sie aktiviert, während wenn es 0 ist werden sie ausgeschaltet. (immer
 unabhängig von ihrem vorherigem Zustand). Angenommen, Sie wählen aus, welche
 Bits benötigt werden und entscheiden dann, ob Sie sie aktivieren (0) oder 
 deaktivieren (1) möchten basierend auf Bit 15. Die Bits mit 0 werden weder 
 gesetzt noch zurückgesetzt.

 Nehmen wir ein Beispiel:
			;5432109876543210
	move.w #%1000000111000000,$dff09A ; Bits 6,7 und 8 sind AKTIVIERT 
			;5432109876543210
	move.w #%0000000100100000,$dff09A ; Bits 5 und 8 sind DEAKTIVIERT

- Bit 14 dientt als allgemeiner Schalter (wie auch Bit 9 im DMACON).
  Es kann zum Beispiel zurückgesetzt werden, um vorübergehend alle Interruptlevel
  zu deaktivieren, ohne das das gesamte Register zurückgesetzt werden muss.

Sie werden sich an das alte Listing3a.s erinnern, das mit:

	MOVE.W	#$4000,$dff09a	; INTENA - Stop Interrupt

Alle Interrupts wurden blockiert, und mit:

	MOVE.W	#$C000,$dff09a	; INTENA - Interrupt erlauben

								    ;5432109876543210
Alle wurden aktiviert. Nun, $4000 = %0100000000000000, mit anderen Worten, das
MASTER Bit, das 14 wird auf Null zurückgesetzt. 
Stattdessen ist $c000 = %1100000000000000, dh das MASTER Bit aktiviert alle 
Interrupts. In Listing11b.s, um VERTB zu aktivieren:

	move.w	#$c020,$9a(a5)	; INTENA - aktiviert Level 3 "VERTB" interrupt
							; ($6c), welches einmal pro Frame (an der Zeile 00)
							; generiert wird

					; 5432109876543210
Tatsächlich, $c020 = %1100000000100000 - Bit 5, VERTB, zusammen mit dem MASTER.

Wie Sie vielleicht bemerkt haben, reichen die Interrupts von der Verwaltung der
seriellen Schnittstelle bis zur Synchronisation der Laufwerktracks zum Lesen, 
ohne weder Blitter noch CIA, noch copper zu speichern. Alle Interrupt-Ebenen neu 
zu definieren ist aus Sicht der Kompatibilität sehr gefährlich, und es ist auch 
sehr schwierig und spezifisch für diejenigen, die ihr eigenes Betriebssystem 
entwickeln wollen.
Demo- und Spielprogrammierer interessieren sich nur für den Interrupt $6c, dh.
den level 3 interrupt, welcher zB für die Erzeugung synchronisierter Interrupts
durch den Elektronenstrahl (VERTB - Bit 5) oder auf bestimmte Videozeilen mit
dem copper (COPER - Bit 4) erzeugt werden. Seltener kann es vorkommen, dass man
mit Interrupts für die Tastaturverwaltung oder anderes zu tun hat.
Insbesondere das Laden von einer Diskette mit Hardwareloader ist veraltet, weil
es notwendig ist Spiele und Demos zu erstellen, die auf der Festplatte 
installiert werden können. Aus dem Grund werden uns Diskettenlaufwerks-
Interrupts nicht interessieren.
Außerdem, wenn Sie ein Spiel entwickeln wollen, das die serielle Schnittstelle
nutzt, um es auf zwei 2 Computern zu spielen, die per Kabel oder Modem verbunden
sind, wäre es besser, die legalen Aufrufe des Betriebssystems des SERIAL.DEVICE
zu verwenden, anstatt Interrupts zu machen, die nicht sehr kompatibel mit
Multiserial-Karten oder neuer Hardware sind.

				 .
				      ·
				 :    :
				 ¦    ¦
				_|    l_
				\      /
				 \    /
				  \ _!_
				   \/¡

*******************************************************************************
*			WIE IST INTREQ UND INTREQR ANZUWENDEN?							  *
*******************************************************************************

In Listing11b.s haben wir auch INTREQ / INTREQR gesehen. Worum geht es dabei?
Möglicherweise haben Sie bemerkt, wie der $6c-Interrupt aufgebaut ist:

MioInt6c:	
	btst.b	#5,$dff01f			; INTREQR - Bit 5, ist VERTB zurückgesetzt?
	beq.s	NointVERTB			; Wenn ja, ist es kein "echter" VERTB Interrupt!
	movem.l	d0-d7/a0-a6,-(SP)	; Register speichern auf dem stack
	bsr.w	mt_music			; Musik spielen
	movem.l	(SP)+,d0-d7/a0-a6	; Register vom stack wiederherstellen
nointVERTB:	; 6543210
	move.w	#%1110000,$dff09c	; INTREQ - Löschen Flag BLIT,VERTB,COPER
								; da der 680x0 es nicht von selbst löscht!!!
	rte							; Ende vom Interrupt BLIT,VERTB,COPER


HINWEIS: INTREQR ist das Wort $dff01e/1f. In diesem Fall wirken wir auf sein 
	 Byte $dff01f anstatt auf $dff01e, aber es ist immer das Low-Byte von INTREQR.

Die Zuordnung von INTREQ / INTREQR ist die gleiche wie die von INTENA / INTENAR:

INTREQ/INTREQR ($dff09c/$dff01e)

BIT	NAME	 LEVEL	BESCHREIBUNG

15	SET/CLR			Steuerbit "Set/Clear". Bestimmt, ob die Bits die 1 sind
					zurückgesetzt oder gesetzt werden, wie in DMACON.
					die Bits = 0 werden nicht gesetzt oder zurückgesetzt
14	INTEN			Master Interrupt (general enable interrupt)
13	EXTER	6 ($78)	Externer Interrupt, an die INT6-Leitung angeschlossen
12	DSKSYN	5 ($74)	Wird generiert, wenn das DSKSYNC-Register mit den Daten 
					von der Diskette im Laufwerk eingelesen wurden. 
					Wird verwendet für Hardwarelader.
11	RBF		5 ($74)	serieller Port UART-Puffer voll nach Daten empfangen.
10	AUD3	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 3 beendet.
09	AUD2	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 2 beendet.
08	AUD1	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 1 beendet.
07	AUD0	4 ($70)	Einlesen eines Datenblocks aus Audiokanal 0 beendet.
06	BLIT	3 ($6c)	Wenn der Blitter einen Blitt beendet hat, wird es auf 
					1 gesetzt
05	VERTB	3 ($6c)	Wird jedes Mal generiert, wenn der Elektronenstrahl	zur
					Zeile 00 geht, d.h. bei jedem Beginn eines vertical blank
04	COPER	3 ($6c) es kann mit dem copper eingestellt werden, um es an einer
					bestimmten Videozeile zu erzeugen.
					Einfach nach einem bestimmten WAIT abfragen.
03	PORTS	2 ($68)	Input/Output Port und Timer, die an die INT2-Leitung 
					angeschlossen sind
02	SOFT	1 ($64)	Reserviert für durch Software ausgelöste Interrupts.
01	DSKBLK	1 ($64)	Ende der Übertragung eines Datenblocks von der Diskette.
00	TBE		1 ($64)	UART-Puffer der seriellen Schnittstelle zum Senden ist leer


Wozu dient ein Interrupt-request register?
Natürlich um Interrupts anzufordern. Und auch um die Interruptanforderung zu
löschen, nachdem der Interrupt ausgeführt wurde, wenn er automatisch (von den 
Custom Chips) oder manuell (aus unserem Programm) aufgerufen wurde, denn die 
Interruptanforderung wird nach der Interruptausführung nicht automatisch 
gelöscht.

Der INTREQ ($dff09c) wird vom 680x0 verwendet, um die Ausführung eines 
Interrupts zu erzwingen. In der Regel der Software-Interrupt oder vom COPPER,
um den COPER-Interrupt an einer bestimmten Videozeile auszuführen. Natürlich
sofern eine Interruptanfrage gestellt wurde, wenn dieser Interrupt in INTENA
nicht aktiviert ist, können Sie ein Leben lang warten.

Wenn ein in INTREQ gesetztes Bit gleichzeitig auch in INTENA gesetzt ist, tritt
der zu diesem Bit entsprechende Interrupt auf. Beachten Sie die Besonderheit, 
dass wenn Bit 14 von INTREQ gesetzt ist überprüft es einen der Level 6
Interrupt (solange das entsprechende Bit in INTENA, Master Enable, ebenfalls
gesetzt ist). 
Andererseits werden die Interrupt-Anforderungsbits genutzt um die Interrupt-
anfoderungen die bereits ausgeführt wurden zu löschen, da 
Interruptanforderungen nicht automatisch gelöscht werden.
Sie müssen vorsichtig sein, denn wenn Sie vergessen, das Anforderungsbit am
Ende eines ausgeführten Interrupts zu löschen, wird der Prozessor ihn erneut
ausführen!! Jetzt solltest sie den letzten Teil des Interrupts verstehen:

			 ;6543210
	move.w	#%1110000,$dff09c ; INTREQ - Löschen Flag BLIT,VERTB,COPER
							  ; da der 680x0 es nicht von selbst löscht!!!
	rte						  ; Ende vom Interrupt BLIT,VERTB,COPER


Das INTREQR ($dff01e) ist schreibgeschützt, im Gegensatz zu INTREQ das nur 
schreibbar ist. Es wird verwendet, um zu wissen, welcher Chip den Interrupt
angefordert hat. Wenn nämlich der Level 3 Interrupt ($6c) ausgeführt wird, kann
es ein "Fehler" des Blitters, des vertical blank oder des coppers sein. Durch 
Testen der INTREQR-Bits wissen wir, welche der 3 Möglichkeiten die Ursache ist
und wir bestimmen, welche Routine ausgeführt werden soll, oder ob nur die 
Routine ausgeführt werden soll, falls wir nur an einer dieser 3 Quellen 
interessiert sind. Bit 15 hat im INTREQR keine Bedeutung, da es sich um das
Set / Clr handelt. 
Lassen Sie uns nun die Verwendung in Listing11b.s überprüfen:

	btst.b	#5,$dff01f	; INTREQR - ist Bit 5, VERTB, zurückgesetzt?
	beq.s	NointVERTB	; Wenn ja, ist es kein "echter" int VERTB!

In diesem Fall wird, da das BTST auf einer Adresse nur .BYTE sein kann, das
$dff01f getestet, also das Low-Byte des Worts, anstelle von $dff01e. Falls Sie
zu Noint springen, ist klar, dass der Interrupt der generiert wurde vom copper 
oder Blitter erzeugt wurde, und die Bits 4 oder 6 gesetzt wurden. Aus diesem 
Grund müssen diese Interrupt-Anforderungen ebenfalls gelöscht werden, um zu 
verhindern das alle Mikrosekunden eine erneute Interruptausführung für nichts
auftritt.:

			 ;6543210
	move.w	#%1110000,$dff09c ; INTREQ - Löschen Flag BLIT,VERTB,COPER
							  ; da der 680x0 es nicht von selbst löscht!!!
	rte						  ; Ende vom Interrupt BLIT,VERTB,COPER

Es wird Ihnen seltsam erscheinen, dass, obwohl nur VERTB mit INTENA aktiviert
ist, es vorkommen kann, dass COPER- oder BLIT-Interrupts angefordert und
ausgeführt werden. In der Tat sollten sie weder angefordert noch ausgeführt 
werden ... *2) Aber aus Gründen, die wahrscheinlich mit der MMU oder der 
Prozessorgeschwindigkeit zusammenhängen, kann es auf schnelleren Computern als
dem Basis A1200, wie z.B. dem A4000, sehr wohl passieren, und dies verursacht 
in der Tat Problemen mit Demos, sogar mit einigen neueren für AGA. *3)
Auf einem A1200 kann es vorkommen, dass der Interrupt auch ohne BTST des 
VERTB-Bits funktioniert, aber bei einem beschleunigten A4000 oder A1200 ist 
dies jedoch nicht der Fall. Ich gebe zu, das es "theoretisch" funktionieren
sollte, aber Tatsache ist, dass viele Demos für den A1200, wenn sie auf dem
A4000 gespielt werden, die Musik zweimal pro Frame spielen. *4)
Testen Sie also immer kategorisch die intreq-Bits, bevor Sie den Interrupt 
ausführen, auch wenn auf Ihrem Computer alles funktioniert.

Zusammengefasst, sind folgende Dinge zu tun, um unseren Interrupt einzurichten:

- Holen Sie sich die VBR-Adresse, speichern Sie den alten Interrupt und setzen
  Sie ihn vor dem Beenden wieder her. Diese Aufgabe wird von startup2.s gut
  erledigt, es gibt kein Problem: Die VBR-Adresse befindet sich im 
  BaseVBR-Label.
- Setzen Sie alle Interrupts mit INTENA zurück. Diese Aufgabe wird auch von 
  startup2.s mit einem MOVE.W #$7fff,$9a(a5) ausgeführt.
- Setzen Sie die Adresse unseres Interrupts in den richtigen Eigenvektor ein.
- Aktivieren Sie nur den Interrupt oder die Interrupts, die wir benötigen

Und hier ist, was wir in unsere Interrupt-Routine einbauen müssen:

- Speichern Sie alle Register und stellen Sie sie mit einem schönen MOVEM
  wieder her, denn wenn Sie einige Register "verschmutzt" haben, stellen Sie
  sich vor, was am Ende des Interrupts passieren würde, wenn Sie ein 
  unterbrochenes Programm in wer weiß was für einer Situation und mit wer 
  weiß was für Werten in den Registern ausführen! 
- Testen Sie $dff01e/1f (INTREQR) sofort, um herauszufinden, wer oder was einen 
  Interrupt dieses Levels ausgelöst hat. Ein Level-3-Interrupt kann zum
  Beispiel von COPER, VERTB oder BLITTER ausgelöst werden und
  ein Level 4-Interrupt von AUD0, AUD1, AUD2 oder AUD3, usw. 
  Beachten Sie, auch wenn es manchmal auch ohne diesen Test zu funktionieren
  scheint, auf A4000 oder ähnlichen Geräten gerät es aus der Phase, als ob die
  CPU betrunken wäre (dies kann allerdings ein Spezialeffekt sein!) *4)
- Löschen Sie die Bits von INTREQ ($dff09c), die den Interrupt ausgelöst haben,
  da sie nicht automatisch gelöscht werden. Wenn Sie das vergessen, hat der 
  Prozessor die feste Interruptanforderung und wird dadurch kontinuierlich 
  ausgeführt.
- Beenden Sie den Interrupt mit einem RTE, so wie Sie ein Unterprogramm mit
  RTS beenden.

In Anbetracht dieser Überlegungen schlage ich den ersten Interrupt erneut vor:

	btst.b	#5,$dff01f			; INTREQR - Bit 5, ist VERTB zurückgesetzt?
	beq.s	NointVERTB			; Wenn ja, ist es kein "echter" VERTB Interrupt!
	movem.l	d0-d7/a0-a6,-(SP)	; Register speichern auf dem stack
	bsr.w	mt_music			; Musik spielen
	movem.l	(SP)+,d0-d7/a0-a6	; Register vom stack wiederherstellen
nointVERTB:	; 6543210
	move.w	#%1110000,$dff09c	; INTREQ - Löschen Flag BLIT,VERTB,COPER
								; da der 680x0 es nicht von selbst löscht!!!
	rte							; Ende vom Interrupt BLIT,VERTB,COPER


                                  ||||   
                              <---/oO\--®® 
._________________________________\--/________________________________________.
*******************************************************************************
*						INTERRUPT UND BETRIEBSSYSTEM						  *
*******************************************************************************

Der 680x0 hat nur 7 INTERRUPT-Ebenen, aber wie ist es dann möglich, dass es in
der Praxis 15 Interrupts sind? Nun, der Paula-Chip sorgt für die Aufteilung der
7 "echten" Interruptlevel in Pseudointerrupts. Zum Beispiel wirkt der Level 3 
Interrupt in drei Fällen: COPER, VERTB und BLIT, und der einzige Weg zu wissen, 
welche dieser drei Quellen den Interrupt ausgelöst hat, ist die Abfrage eines
Register, das mit dem Paula-Chip selbst verbunden ist, dh INTREQR!
Auf der anderen Seite, gibt es beim 680x0 nur 7 "echte" Interrupt-Level und es
ist durch Paula nicht möglich bei einem Interrupt der gleichen Ebene sich
gegenseitig zu unterbrechen. Während ein Level 5 Interrupt, wie DSKSYNC, die
Ausführung eines Level 3 Interrupts, wie COPER unterbrechen kann, ist es nicht
möglich, dass BLIT die Ausführung von COPER zu unterbrechen, auch wenn dieser
eine höhere "Paula Priorität" hat, da sich beide auf dem gleichen physischen 
Niveau wie der 680x0 befinden.
Aus diesem Grund, wenn die Anforderung während der Ausführung eines Interrupts
von einem Interrupt eines anderen Pseudo-Levels von Paula im selben Level 680x0 
auftritt, wie z.B. ein BLIT, während Sie einen COPER ausführen, wird am Ende
des int, sofort der Interrupt von Level 3 ausgeführt.
Diesmal wird die Routine für BLIT ausgeführt (entsprechend dem btst).
Auf dem INTREQR wird angegeben, welche Art von "int" ausgeführt werden soll.
Hier sind die Prioritäten der Interrupt-Ebenen im Betriebssystem, dh
in der Exec.library, die wie Sie sehen können, der Hardwarepriorität folgt:


	Level 1: ($64)		KLEINSTE PRIORITÄT

	1	empty transmission buffer	TBE
	2	disk block transferred		DSKBLK
	3	Software Interrupt 			SOFTINT

	Level 2: ($68)

	4	external ports				INT2 & CIAA	PORTS

	Level 3: ($6c)

	5	copper						COPER
	6	interval of vertical blanking	VERTB
	7	Blitt fertig				BLIT

	Level 4: ($70)

	8	Audiokanal 2				AUD2
	9	Audiokanal 0				AUD0
	10	Audiokanal 3				AUD3
	11	Audiokanal 1				AUD1

	Level 5: ($74)

	12	receive buffer full			RBF
	13	disk sync found				DSKSYNC

	Level 6: ($78)		Höchste PRIORITÄT

	14	external INT6 & CIAB		EXTER
	15	spezial (master enable)		INTEN

	Level 7: ($7c) (externe Karten wie die Action Replay)

	-	Nicht maskierbare Interrupt	NMI

Die Tatsache, dass das Betriebssystem seine eigenen Interruptroutinen 
verwaltet, macht es für uns gefährlicher, einige von ihnen zu ersetzen. Was die
Priorität 6 betrifft, so verwendet die graphics.library den CIAB Time Of Day 
(TOD) Timer Interrupt zur Steuerung des Bildschirms. 
In Priorität 5 wird DSKSYNC von TrackDisk und RBF vom serial.device verwendet.
In Level 4 gibt es Audiokanäle, die von audio.device verwendet werden. In 
Level 3, der BLIT-Interrupt, der auftritt, wenn der Blitter eine Operation
beendet hat. Während des Operation werden Routinen häufig zur Wiederverwendung
der Daten verwendet, die gerade vom Blitter geschrieben wurden, um 
Zeitverschwendung zu vermeiden. In Level 2 verwendet das Timer.device den 
den TimerA-Interrupt des CIAA-Chips für den Tastatur-Handshake, den TimerB für
den Mikrosekunden-Timer und der Interrupt TOD-Alarm bei 50/60 Hz. Außerdem gibt 
es auch den INT2 für alle externen Hardware-Karten.
In Level 1, der niedrigsten Stufe, wird der TBE-Interrupt von Serial.device 
und der DSKBLK-Interrupt vom TrackDisk.device verwendet. Der SOFTINT-
Interrupts, dh Software, kann über das Betriebssystem definiert werden, zum
Beispiel mit der Funktion Cause Function von Exec oder durch Erstellen eines 
Nachrichtenports vom Typ SOFT_INT.

*******************************************************************************
*		DIE INTERRUPTS COPER AUFGERUFEN VON DER COPPERLIST					 *
*******************************************************************************

Wenn Sie den Level 3 COPER Interrupt ($6c) an einer bestimmten Video-Zeile 
aufrufen möchten, schreiben Sie einfach $8010 in das Intreq ($dff09c), nach
einem wait an dieser Videozeile.
 
COPPERLIST:
	dc.w	$100,$200	; BPLCON0 - keine Bitebenen
	dc.w	$180,$00e	; color0 BLAU
	dc.w	$a007,$fffe	; WAIT - warte auf Zeile $a0
	dc.w	$9c,$8010	; INTREQ - fordern Sie einen COPER-Interrupt an,
						; wodurch color0 mit einem "MOVE.W" geändert wird.
	dc.w	$FFFF,$FFFE	; Ende der copperlist

Tatsächlich, der Wert $8010 = $8000 + %10000, dh Bit 4, COPER ist gesetzt.

Sehen wir uns ein praktisches Beispiel in Listing11c.s an.

Natürlich kann der Interrupt auch jedes Mal auf anderen Zeilen aufgerufen
werden und den "Effekt" ändern. Sehen wir uns das in Listing11d.s an.

Angesichts der besonderen Komplexität der Interrupts werden wir vorerst keine
Beispiele in Bezug auf Disk Interrupts, serielle Schnittstelle usw geben.
Für Anwendungen, die uns interessieren, nämlich DEMOs und GAMES, sind die zwei
Arten von Level 3 Interrupts ($6c), die wir gesehen haben, nämlich VERTB, der
bei jedem Frame ausgeführt wird und der COPER der vom Copper an jeder 
Videozeile aufgerufen werden kann, oft genug.
Die Anwendungen der anderen Interrupts werden so wie sie in Beispiellistings
zu finden sind kommentiert, da sie in jeden Bereich reichen!
Im Moment können wir die Verwendung aller Interrupt-Ebenen vorwegnehmen:
In den Listings Listing11e.s und Listing11f.s sind ALLE Interrupts neu 
definiert und ALLE Level werden neu aktiviert, aber es gibt natürlich nur
Routinen für Level 3. Dieses Beispiel kann als "Start" zur Definition von 
beliebigen Interrupt-Leveln nützlich sein: Sie können den Level der Sie 
interessiert herausschneiden und die Routinen darin "einfügen".

   ·          .   .                   .         .               .    .
        .              .             .       .           .               .
   .             .            .  .       .        .
       .           .    ,----|     .   _ _ _ _ _           .      .
            .         . `----|,----|   ]-I-I-I-[       .     .   _ _ _  _ _ _
     _ _ _ _ _ _ ,----|      |`----| . \_`_ '__/      .          ]-I-I--I-I-[
     ]-I-I-I-I-[ `----|  .   |     |    |. `  |.                  \_`__  '_/
      \ `   '_/       |     /^\  . |    | /¯\ |           .        |~_ [],|
       [¯] `__|       |    /  ^\   |    | |_| |     _ _ _  _ _ _  _|______|_
       |__   ,|      /^\  /  ^  \ /^\   | === |     I-I-I--I-I-I <=-=-==-=-=>
    ___| ___ ,|__   /-=-\/=_=_=_=\-=-\  |, `_ |     \ ` `  ' ' /  \__   _'_/
   (__I__I_I__I_ ) (====(_________)___)_| ___ |__    | çÅ$t£e |    |.   _ |
    \-\--|-|--/-/  |     I  [ ]  I  (  I|_I I |I )  _|____ ___|_   |   _  |
     |[] `    '|_  |_   _|`__  ._[  _\ \  | | / /  <=-=- øF -=-=>  |`    '|
    / \  [] ` .| |-| |-| |_| |_| |_| | []   [] |  /\\__ ___ ___/   | '    |
   <===>      .|-=-=-=-=-=-=-=-=-=-=-|  , ,   / \/  \|.    .  | /\ |[]    |
   |[ ]|` ` [] | .   _________   .   |-    , <=======|¦££u$¦øN|<==>|'   __|
   <===>  `  ' ||||  |       |  |||  |  []   <=======|        ||  || '  I-|
    \T/     -- ||||  | çOi!  |  |||  | .  '   \T/--T-T-T-T-T-T|T-T||__.   |
  __/|\   .   .||||| |       | ||||  |. . ¯¯. /|\__|_|_|_|_|_|||_|/ çO¦!`¶4\_
  ¯¯ : \       ||||! ! _o ,  |  ||!  |       / | \ ! ! ! | !.!|  /     ¦    ¯
     ¦  \      !||!   //\/   |  |!   |       \ | /       !   !| /      :
     :          `!   '/\     !  !    !        \!/             !      __¦__
___ _|_______________/ /_  /\________________________________________\  //_ ___
¯¯¯ ¯|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ¯¯\/  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\//¯¯ ¯¯¯
     :                                                                 ¦

*******************************************************************************
* ERWEITERTE INFORMATIONEN ZUM COPPER - NUR COLOR0 ($180) - KEINE BITPLANES   *
*******************************************************************************

Wie der Titel sagt, werden wir jetzt die möglichen Anwendungen sehen, die nur 
eine copperliste ohne Bitplanes verwenden. Das heißt, wir erstellen Grafiken
oder Animationen nur mit WAIT und MOVE. 
Wenn Sie Bitebenen hinzufügen, können Sie natürlich diese Effekte "kreuzen" und
"überlagern" in dem man color2 oder color3 zusätzlich zu color0 viele Male pro
copperliste ändert.
Zu Beginn ist es jedoch notwendig, einige Dinge zu erklären, die noch nicht 
behandelt wurden. Dies ist die "Zeit", die der copper benötigt, um seinen 
eigenen MOVE-Befehl auszuführen. Wir haben bereits gesehen, wie man die gesamte
Palette von 32 Farben an einer bestimmte Videozeile ändert oder einige Hundert
Farben auf dem Bildschirm erscheinen zu lassen, wobei nur "32" oder "16" Farben
offiziell in BPLCON0 eingestellt sind.
Nun, in einer Zeile konnten wir 32 Farben ändern, dh die Anzeige führt 32 MOVEs
des coppers aus:

	dc.w	$180,xxx	; 1 move in color0
	dc.w	$182,xxx	; 1 move in color1
	...					; etc.

Nun, wenn wir damit beginnen, die Farben von der horizontalen Position $07 oder
sogar $01 aus zu ändern, werden alle 32 Farben nur in Richtung der Mitte des
Bildschirms geändert, da jeder move 8 Pixel lowres benötigt um ausgeführt zu 
werden. Aus diesem Grund ist es immer ratsam, die Farben 1 Zeile, bevor die
Zeichnung wirklich beginnt zu ändern. Wenn wir ein Folge von fünfzig moves
hintereinader setzen, würden wir mit dem letzten in der Zeile darunter 
ankommen!
Andererseits wäre es physikalisch unmöglich, Dutzende von Züge in weniger 
als einem Fünfzigstel einer Sekunde auszuführen!
Wir können diese offensichtliche Einschränkung jedoch für unsere Zwecke 
verwenden, zum Beispiel, um die Farbe horizontal alle 8 Pixel zu ändern, ohne
WAITs zu verwenden, sondern einfach durch die Folge von fünfzig COLOR0s pro 
Zeile. Schauen wir uns ein praktisches Beispiel dafür in Listing11g1.s an.
Eine Verwendung dieses Listings könnte darin bestehen, die $182 zu ändern, und
nicht die $180, in einem 1-Bit-Bildschirm: auf diese Weise würde jeder "Text"
von links nach rechts, anstatt von oben nach unten eingeblendet, wie wir es
normalerweise mit der Copperliste tun.

In Listing11g2.s und Listing11g3.s gibt es farbenfrohe Versionen dieses
Effekts, sodass es eine Basis für "PLASMA"-Effekte sein könnte.

Übrigens, wenn wir die Farben einer solchen Zeile "rotieren" oder "zirkulieren"
würden, was hätten wir dann? Ein sehr bekannter Effekt, der von den Intros 
schon in den frühen Tagen des Amigas verwendet wurde: In Listing11g4.s sehen
wir den "Supercar"-Effekt.

Vielleicht begeistert es nicht, nur 2 Zeilen zu verwenden. Lassen sie uns einen
Zyklus mehr versuchen, vielleicht mit einem verknoteten Effekt, wie in 
Listing11g5.s.

Eine weitere "Fantasie", die das "Gedränge" von colorXX ausnutzt, ist im Beispiel 
Listing11g6.s zu sehen.

Sehen wir uns nun einen "einfachen" Weg an, etwas plasmaähnliches zu machen: 
Anstatt den Inhalt der vielen color0's zu ändern, setzen wir ein wait an den
Anfang der Zeile, von denen jede 52 color0 enthält: Wir können die Zeile 
einfach nach rechts und links verschieben und dabei die horizontale Position
der verschiedene waits ändern! In der Praxis ist dies in Listing11g7.s

* VERWENDUNG DES COPPER2 (COP2LC/COPJMP2):

Möglicherweise haben Sie bemerkt, dass es zusätzlich zu $dff080 und $dff088 die
zum Setzen und Starten von copper1 vorhanden sind es auch $dff084 und $dff08a 
zum Setzen und Starten von copper2 gibt. Aber wie funktioniert copper2? Und was
kann es für uns tun? Mit jedem Frame-Start startet der copper den copper1,
dessen Adresse von $dff080 gelesen wird.
Manchmal fangen wir "on the fly" an, ohne auf das Ende des Frames zu warten in
dem wir in COPJMP1 oder $dff088 schreiben.
Wenn wir eine copperliste in $dff084 (COP2LC) einfügen, können wir diese auch
Starten mit dem Schreiben in COPJMP2 ($dff08a). Aber am Ende des Frames würde 
es das copper1 neu starten.
Mit dieser Funktion können nun mehrere copperlisten erstellt werden und wir
zu denen gesprungen werden kann, wie wir es für 680x0-Anweisungen mit "JMP"
tun.
Wenn wir zum Beispiel copper1 bis zur Hälfte des Bildschirms ausführen möchten,
um dann die andere Hälfte von copper2 auszuführen, würde es genügen die Adresse
von copper 2 am Anfang zu setzen und es dann von der copperliste über copjmp2
zu starten um zu copper2 zu springen.

	move.l	#copper1,$dff080	; COP1LC
	move.l	#copper2,$dff084	; COP2LC
	...
	
	section	copperissime,data_C

copper1:
	...	; verschiedenen Anweisungen...
	dc.w	$a007,$fffe	; warte auf Zeile $a0
	dc.w	$8a,0		; COPJMP2 - start copper 2


copper2:
	...	; verschiedenen Anweisungen
	dc.w	$ffff,$fffe	; Ende copperlist, wir starten neu mit copper1!
					


Wenn der copper bei "dc.w $8a,0" angekommen ist, springt er (wie BRA oder JMP)
nach copper2, vorausgesetzt dass vorher in $dff08a geschrieben wurde. Beachten
Sie, dass der Sprung kein "bsr" ist, also nie unter das "dc.w $8a,0" von
copper1 zurückkehrt. Sehen wir uns nun ein paar praktische Anwendungen von 
copper2 an. Eine ist die sogenannte Copperdynamik, die aus 2 Copperlisten 
besteht, die bei jedem Frame ausgetauscht werden, wie eine "doppelte Pufferung"
der Bitplanes. Dadurch werden die Farben "weicher", denn wenn man bei jedem
Frame 2 Farben austauscht, hat man einen Effekt wie beim Interlace, wodurch die
Zwischenfarbe "sichtbar" wird.
Bereiten Sie einfach 2 copperlisten mit dem gleichen Farbton vor, die jedoch
ein wenig "phasenverschoben" sind, und tauschen Sie sie kontinuierlich aus.
Sehen wir uns in der Praxis eine dynamische Copperliste in Listing11h1.s an.

Haben Sie den Unterschied bemerkt? Sie können ihn als AGA-Farbton ausgeben!
Trotzdem wurde die Copperdynamik nur in wenigen Spielen verwendet obwohl es
nicht so schwer zu erstellen ist. Unter den Spielen, die dynamisches copper
haben, erinnere ich mich an AGONY und an das italienische Kampfspiel SHADOW 
FIGHTER von NAPS TEAM.

Sehen wir uns eine weitere Anwendung von copper2 an. Anstatt ein Copperlisten-
paar auszutauschen können wir ein paar Dutzend von ihnen durchlaufen werden.
Wir können also einen Coppereffekt "vorberechnen", indem man für jede "Phase"
des Effekts jeweils eine copperliste berechnet. Da der Effekt dann zyklisch 
ist, reicht es aus, jedes Mal auf die copperliste "danach" zu setzen.
Auf diese Weise erhalten wir den Coppereffekt, aber wir sparen VOLLSTÄNDIG 
die Zeit, was die 68000-Routinen verbraucht hätten! Wir können als sagen, dass
der betreffende Coppereffekt "KOSTENLOS" ist und wir können eine Routine 
ausführen, die den Rest der Zeit verbraucht.

In Listing11h2.s sehen wir eine "normale" Routine und die Version die "Copper-
Frames" vorberechnet in Listing11h3.s. Der einzige Nachteil der vorberechneten
Version ist das zusätzlicher Speicher verwendet wird wird um alle Copperlisten-
Frames zu speichern.

Da Sie die Prüfung der Copper Stufe 2 bestehen möchten, sollten Sie auch wissen
dass sie die Y-Koordinate der WAITs "maskieren" können. In der Praxis sieht ein
Wait mit einem maskierten Y wie folgt aus:

	dc.w	$0007,$80FE	; Wait bei Y "Maske"

Und das bedeutet: Überprüfen Sie nicht die Zeile Y, sondern warten Sie auf die 
X-Position $07 der aktuellen Zeile. Es handelt sich um ein "behindertes" WAIT, 
das die Y-Position nicht lesen kann.
In Wirklichkeit kann es die 7 niedrigen Bits der Y-Position nicht lesen, also
funktioniert es nach der Zeile $80. Aber wozu brauchen wir ein maskiertes Wait,
dass nicht die Y-Position vor der Y-Position $80 überprüft.

Wenn wir einen mythischen Balken, wie in Lektion 3, verschieben müssten, dann
müssten wir alle Waits ändern, aus denen er besteht. Wenn wir stattdessen ein
normales Wait unter allen maskiert waits an den Anfang setzen, genügt es, den
das erste wait zu ändern und die anderen "werden folgen". Die Einsparung von
680x0-Befehlen ist offensichtlich: Mit nur einem Add / Sub kann ein ganzer
Balken verschoben werden.

Sehen wir uns eine Implementierung in Listing11h4.s an. (die legendäre Bar aus
Lekion 3!) 

Beachten Sie, dass dies auch unterhalb der vertikalen Zeile $FF funktioniert, 
da die Nummerierung wieder bei $00 erneut beginnt. Jetzt wo sie es wissen, 
können Sie diesen Trick nutzen, wenn sie in diesem Bereich etwas bewegen.

Jetzt könnten wir über den SKIP-Befehl sprechen, aber da ich die Verwendung 
noch nie durch irgendjemanden benutzt gesehen habe und ich selbst auch nicht
weiß, was es tun kann (Sie können ziemlich alle Dinge mit copper2 für Sprünge 
benutzen ...) lasse ich ihn weg. Ich hoffe, dass Sie an die völlige 
Nutzlosigkeit dieses Befehls glauben. 
(Anmerkung: Der Skip-Befehl wird in Lektion 17 erklärt.)


Um das Thema "ONLY COPPER WITHOUT BITPLANES" abzuschließen, schlage ich 6
Listings vor, die die häufigsten Effekte dieses Typs zusammenfassen.

Listing11i1.s - ein Vollbild-Farbscroll

Listing11i2.s - eine Pseudoparallaxe mit 3 Ebenen von Balken. Es kann als 
Hintergrund für ein Plattformspiel dienen während eines "Aufstiegs" zum Beispiel.

Listing11i3.s - eine kleine COP-Fantasie...

Listing11i4.s - ein pseudozufälliger Gradient, der die Werte der horizontalen
Position vom Elektronenstrahl (in der Regel unterschiedliche Werte) mischt, um 
die Farben des Coppergradienten zu erstellen.

Listing11i5.s - eine copperliste, die die Farben in einer Art und Weise bewegt,
das es wie 3D aussieht.

		                 .......
		              .::::::::::.
		       _______j::'__ __:::,__
		     _/¯     /. .-^-.-^-.¯\ ¬\_
		    /        \_ | ® | © |_/    \
		   /      __  T `---'\--'!      \
		  /     __/`  |  ¯¬ _ \¬ \       \
		 /     _/     | _/\ ¬_/ _,\       \
		(      '\     | `  ¯¯ ¯Y   \       \
		 \       \    l____________/        \
		  \      7________________\_        /
		   \     l       ____      (       /
		    \_____\       ¬T       /______/
		     /    \        |       /    /
		    C _ _ ( __     |   __ ( _ _(
		     ¯ T ¯   ¬     |   ¯   T ¯ ¯
		      /¯           ¯\      ¯\ xCz
		  ___/_______________\_______\__
		 (________________)_____________)


*******************************************************************************
*	ERWEITERTE INFORMATIONEN ZUM COPPER - AUCH AKTIVIERTE BITPLANES	          *
*******************************************************************************

Haben sie gesehen, dass wir allein mit den Copper move und wait eine Menge
anfangen können? Aber was ist, wenn wir die copperliste komplizierter machen
durch aktivieren der Bitplanes?
Wir können den bplmod in jeder Zeile ändern, um die Bilder zu dehnen oder
den bplcon1 ($dff102) verwenden, um sie zu schwingen, oder sogar in jeder
Zeile die Zeiger auf die Bitplanes ändern!!!

In den folgenden Listings wird unter anderem ein bestimmtes System verwendet,
um diwstart / diwstop und ddfstart / ddfstop zu berechnen, dh durch einige
EQUATE , die dank der Verschiebeoperatoren "<<" und ">>" sowie "&" (und) und
die gebräuchlichen "*", "/", "+", "-" Operatoren zur Berechnung von Werten
verwendet werden.
Wenn Sie einen normalen Bildschirm in 320 * 256 machen möchten, tun Sie dies
zuerst um die normalen Werte zu setzen oder ändern Sie sie von Hand. Wenn Sie
stattdessen einen Bildschirm mit einer bestimmten Größe machen wollten,
z. B. 256 * 256, kann das Zeit sparen.

scr_bytes	= 40	; Anzahl der Bytes für jede horizontale Zeile.
			; Daraus berechnen wir die Bildschirmbreite,
			; multiplizieren der Bytes mit 8: normaler Bildschirm 320/8 = 40
			; zB für einen 336 Pixel breiten Bildschirm 336/8 = 42
			; Beispielbreiten:
			; 264 pixel = 33 / 272 pixel = 34 / 280 pixel = 35
			; 360 pixel = 45 / 368 pixel = 46 / 376 pixel = 47
			; ... 640 pixel = 80 / 648 pixel = 81 ...

scr_h		= 256	; Bildschirmhöhe in Zeilen
scr_x		= $81	; Startbildschirm, XX-Position (normal $xx81) (129)
scr_y		= $2c	; Startbildschirm, YY-Position (normal $2cxx) (44)
scr_res		= 1	; 2 = HighRes (640*xxx) / 1 = LowRes (320*xxx)
scr_lace	= 0	; 0 = non interlace (xxx*256) / 1 = interlace (xxx*512)
ham			= 0	; 0 = nicht ham / 1 = ham
scr_bpl		= 1	; Anzahl Bitplanes

; Parameter automatisch berechnet

scr_w		= scr_bytes*8		; Bildschirmbreite
scr_size	= scr_bytes*scr_h	; Größe des Bildschirms in Bytes 
BPLC0	= ((scr_res&2)<<14)+(scr_bpl<<12)+$200+(scr_lace<<2)+(ham<<11)
DIWS	= (scr_y<<8)+scr_x
DIWSt	= ((scr_y+scr_h/(scr_lace+1))&255)<<8+(scr_x+scr_w/scr_res)&255
DDFS	= (scr_x-(16/scr_res+1))/2
DDFSt	= DDFS+(8/scr_res)*(scr_bytes/2-scr_res)

dann, in copperlist einsetzen:

	dc.w	$8e,DIWS	; DiwStrt
	dc.w	$90,DIWSt	; DiwStop
	dc.w	$92,DDFS	; DdfStart
	dc.w	$94,DDFSt	; DdfStop
	dc.w	$100,BPLC0	; BplCon0

Es ist jedoch nicht "idiotensicher" und wenn Sie Bildschirme mit seltsamen 
Größen erstellen wollen, funktioniert es möglicherweise nicht und es wird
besser sein, es "von Hand" zu machen. Sie können es auch verwenden, um den Wert
zu berechnen, indem Sie ihn nach dem assemblieren mit "? DIWS" oder "? xxxx"
überprüfen und dann den Wert von Hand schreiben.

Hier sind die Listings zu diesem Abschnitt:

Listing11l1.s - ändert in jeder Zeile sowohl color0 als auch bplcon1 ($dff102).
wodurch eine Welligkeit der Bitebenen verursacht wird.

Listing11l2.s - in jeder Zeile werden 3 von 4 Farben geändert (2 Bitplanes).

Listing11l3a.s, Listing11l3b.s und Listing113c.s - sind 3 Schritte um den 
Welleneffekt des AMIGA ET-Logos der kleinen Demo auf Diskette 1 zu erreichen. 
Stück für Stück haben wir alles beschrieben! Die Logo schwankt dank der 
negativen Modulos, die sich mit den Null Modulos abwechseln.

Listing11l4.s - dies ist eine andere Art zu beeinflussen: die bplpointers 
werden in jeder Zeile neu definiert!

Listing11l5.s - wenn Sie ein kleines Bild mit einer Breite von 40 * 29 Pixel
hätten und sie wollten den ganzen Bildschirm ausfüllen was könnten sie tun?
Versuchen Sie einen 8fachen Zoom, um es in 320 * 232 zu verwandeln. Das macht
dieses Listing, indem es den Modulo für die horizontale Verlängerung verwendet,
durch eine Routine welche jedes Bit testet und es in ein Byte (8 Bits)
"transformiert".

Listing11l5b.s - ist eine optimierte Version des vorherigen Listings, die 
eine Tabelle mit den 256 möglichen Kombinationen eines 8-Byte "erweiterten"
Bytes verwendet. Die Ausführung dauert weniger als die Hälfte der Zeit! 
Lernen Sie selbst diese Art von Optimierungen zu machen: Routinen, die 
unmöglich zu beschleunigen scheinen, können auf diese Weise beschleunigt 
werden!

* WIE MAN EINEN INTERLACE BILDSCHIRM MACHT (Länge 512 Zeilen)

Im Interlaced-Modus können Sie doppelt so viele Videodaten anzeigen. Dies ist
möglich, indem die Anzahl der angezeigten Zeilen verdoppelt wird. Normalerweise
sind 256 vertikale Zeilen möglich, während mit dem Interlace-Modus 512 Zeilen
erreicht werden können, sowohl in lowres als auch in hires.
Es gibt jedoch einige Besonderheiten, denn es reicht ist nicht aus, die
Bitebenen und das Interlace-Bit (Bit 2 des bplcon0) zu setzen.
Was das RAW-Bild betrifft, so konvertieren Sie einfach eine normale Interlace-
Zeichnung mit dem iff-Konverter und speichern es, also als ein Bild in 
320x512 oder in 640x512.
Auch ein kleinerer Pinsel ist in Ordnung, aber denken Sie immer daran, dass
das Bild durch die doppelte Auflösung nicht vertikal "abgeflacht" werden darf. 
Bekanntlich "flackert" das Zeilensprungverfahren (interlace).
Das ist schlecht, aber auch gut. In der Tat auf normalen Fernsehern oder 
Monitoren wäre eine vertikale Auflösung von mehr als 256 Zeilen nicht möglich,
es wäre ein "VGA"-Monitor, dh Multisync oder Multiscan notwendig.
Der "Trick" besteht darin, dass nur die 256 ungeraden Zeilen auf einmal 
angezeigt werden und beim nächsten Mal die anderen 256 geraden Zeilen.
Der Wechsel findet mit jedem Frame statt, so dass das Auge getäuscht wird, 
abgesehen vom Flimmern, dass stark abnimmt, wenn die Farben gut gewählt sind.
Dieser Austausch ist jedoch nicht ganz "automatisch", es ist notwendig, etwas
"von Hand" zu tun.

  +-----------------------------------+----------------------------------+
  |    BILD 1 (ungerade Zeilen)       |        BILD 2 (gerade Zeilen)    |
  +-----------------------------------+----------------------------------+
  | ZEILE 1: ---> xxxxxxxxxxxx        |                                  |
  |                                   |       ZEILE 2: ---> xxxxxxxxxxxx |
  | ZEILE 3: ---> xxxxxxxxxxxx        |                                  |
  |                                   |       ZEILE 4: ---> xxxxxxxxxxxx |
  | ZEILE 5: ---> xxxxxxxxxxxx        |                                  |
  |                                   |       ZEILE 6: ---> xxxxxxxxxxxx |
  | ZEILE 7: ---> xxxxxxxxxxxx        |                                  |
  |                                   |       ZEILE 8: ---> xxxxxxxxxxxx |
  | ZEILE 9: ---> xxxxxxxxxxxx        |                                  |
  |                                   |       ZEILE 10: --> xxxxxxxxxxxx |
  |          [...]                    |                                  |
  |                                   |                 [...]            |
  | ZEILE 311: -> xxxxxxxxxxxx        |                                  |
  |                                   |       ZEILE 312: -> xxxxxxxxxxxx |
  | ZEILE 313: -> xxxxxxxxxxxx        |                                  |
  +-----------------------------------+----------------------------------+

Für den Interlaced-Modus muss das Modulo neu definiert werden, indem es auf 40
gesetzt wird, wenn wir in lowres sind oder 80, wenn wir in hires sind. In der
Praxis ist es notwendig, das Modulo auf die Bildlänge einer Zeile zu setzen,
um sie zu überspringen, da der Modulo ein Wert ist, der am Ende jeder 
Videozeile hinzugefügt wird, um die Länge einer ganzen Zeile zu überspringen.
Beim Überspringen passiert folgendes:
Die erste Zeile wird gelesen und angezeigt, am Ende wird die zweite Zeile
übersprungen und die dritte Zeile angezeigt. Am Ende wird die vierte Zeile
übersprungen und die fünfte Zeile angezeigt usw. In der Praxis haben wir dafür
gesorgt, dass nur die ungeraden Zeilen angezeigt werden.  
Die Anzeige kann aber aus Hardwaregründen nicht mehr als 256 Zeilen anzeigen.
Aber niemand schreibt uns vor, welche 256 und auch nicht ab wann wir anfangen 
sie anzuzeigen. Wenn wir einmal die geraden Zeilen überpsringen und beim 
nächsten Mal die ungeraden Zeilen, können wir einen Bildschirm mit 512 Zeilen
"alternierend" auf einem 256 Zeilen Bildschirm anzeigen!

Wir fassen zusammen: Wir haben ein Bild, zum Beispiel in Interlace 640x512, das
wir in RAW konvertiert haben und möchten es anzeigen. Wir zeigen auf das Bild 
und stellen das Modulo auf 80 richtig ein und setzen Sie das Interlace-Bit
(neben dem von hires) in bplcon0 ($dff100).
Was bekommen wir? DAS BILD, WIE ES IN LOWRES WAR! 256 ZEILEN HOCH, ALS OB DIE
AUFLÖSUNG HERABGESETZT IST!

So, jetzt ist es an der Zeit, diesen kleinen Teil "von Hand" zu machen, um das 
Interlacing zu ermöglichen. Es gibt ein spezielles Bit, das überprüft werden
muss, und das uns anzeigt ob für den Frame die ungeraden oder geraden Zeilen
angezeigt werden sollen. Dies ist das Bit 15 des VPOSR ($dff004), genannt LOF 
oder Long Frame, das uns anzeigt ob wir uns im "langen Frame" befinden oder
nicht. Hier ist ein Routinebeispiel:

LACEINT:
	MOVE.L	#BITPLANE,D0	; Adresse Bitplane
	btst.b	#15-8,$dff004	; VPOSR LOF bit?
	Beq.S	Faidispari		; wenn ja, zeigen Sie auf die ungerade Zeilen
	ADD.L	#80,D0			; Oder fügen Sie die Länge einer Zeile hinzu,
							; Starten der Ansicht von den den zweiten:
							; gerade Zeilen werden angezeigt!

FaiDispari:
	LEA	BPLPOINTERS,A1		; PLANE POINTERS IN COPLIST
	MOVE.W	D0,6(A1)		; Zeiger auf das Bild
	SWAP	D0
	MOVE.W	D0,2(A1)
	RTS

Wie Sie sehen, begint die Anzeige mit der ersten Zeile, wenn das LOF-Bit auf
Null gesetzt ist. Dadurch werden durch die Wirkung des Modulos die 
Zeilen 1,3,5,7 ... angezeigt etc. oder die ungeraden. Andernfalls überspringen
Sie eine Zeile und die Anzeige startet ab der zweiten, bei den Zeilen 2,4,6,8
... usw. .: GERADE!

Es gibt diejenigen, die zwei Copperlisten erstellen, eine, die auf die eine und
eine, die auf die andere zeigt und dann je nach LOF Bit wird auf den einen oder
anderen Frame gezeigt. Ich denke aber es ist "schlauer", nur auf die Bitplane 
zu setzen ... Sie können es jedoch tun, wie Sie möchten. Verstehe Sie einfach 
die Methode.

Listing11l6.s - ist ein Beispiel in 640x512 mit 1 Bitplane

Listing11l6b.s - ist ein Beispiel in 320x512 mit 4 Bitplanes

Um diesen Kurs in "copper", Stufe 2 zu beenden, halten wir nicht an, sondern
machen noch ein komplexeres Beispiel auch für Sprites.
Erinnern sie sich, wie wir sie in Lektion7 "wiederverwendet" haben, um die Sterne
zu machen? Was passiert, wenn wir alle 2 Zeilen Sprites wiederverwenden?

Listing11l7.s - hier sehen Sie eine Megaanwendung von Sprites (jeweils 128 Mal)

		        ____::::____
		      _/::__ ·· __  \_
		     ,|:·( o _, o )  |,
		     (|·    (_       |)
		     `l.   _        .j'
		       \   \____/   / Zs!
		  ______\   :l/    /____
		 /:::·· ¬\________/   ::\
		/::·                   ·:\


******************************************************************************
*		DIE 2 8520 CHIPS, GENANNT CIAA UND CIAB								 *
******************************************************************************

Wenn Sie den Amiga zerlegen, finden Sie zusätzlich zu Agnus, Paula, Denise, 
und dem 680x0 auch zwei 8520 Chips, genannt CIA. Diese Chips haben 16 Ein- /
Ausgangs-Pins, ein serielles Schieberegister, drei Timer, einen einzelnen 
Ausgangspin und einen einzelnen Eingangspin. Beide haben 16 Register auf die
Sie über die entsprechenden Adressen zugreifen können:

CIAA Übersicht der Adressen
---------------------------------------------------------------------------
 Byte    Register                  Data bits
Addresse   Name     7     6     5     4     3     2     1    0
---------------------------------------------------------------------------
$BFE001    pra     /FIR1 /FIR0  /RDY /TK0  /WPRO /CHNG /LED  OVL
$BFE101    prb     Parallel port
$BFE201    ddra    Datenrichtung für Port A (BFE001);1=output (normalerweise $03)
$BFE301    ddrb    Datenrichtung für Port B (BFE101);1=output (kann in/out sein)
$BFE401    talo    CIAA Timer A Byte niedrig (7.15909 MHz NTSC; 7.09379 MHz PAL)
$BFE501    tahi    CIAA Timer A Byte hoch
$BFE601    tblo    CIAA Timer B Byte niedrig (7.15909 MHz NTSC; 7.09379 MHz PAL)
$BFE701    tbhi    CIAA Timer B Byte hoch
$BFE801    todlo   Timer zu 50/60 Hz - Bits 7-0 (VSync or line tick)
$BFE901    todmid  Timer zu 50/60 Hz - Bits 15-8
$BFEA01    todhi   Timer zu 50/60 Hz - Bits 23-16
$BFEB01            Nicht verwendet
$BFEC01    sdr     CIAA serial data register (mit der Tastatur verbunden)
$BFED01    icr     CIAA interrupt control register
$BFEE01    cra     CIAA control register A
$BFEF01    crb     CIAA control register B

Hinweis: Die CIAA kann einen INT2-Interrupt generieren, d.h. Lev.2, $68.


CIAB Übersicht der Adressen
---------------------------------------------------------------------------
 Byte     Register                   Data bits
Addresse    Name     7     6     5     4     3     2     1     0
---------------------------------------------------------------------------
$BFD000    pra     /DTR  /RTS  /CD   /CTS  /DSR   SEL   POUT  BUSY
$BFD100    prb     /MTR  /SEL3 /SEL2 /SEL1 /SEL0 /SIDE  DIR  /STEP
$BFD200    ddra    Datenrichtung für Port A (BFD000);1 = output (normalerweise $FF)
$BFD300    ddrb    Datenrichtung für Port B (BFD100);1 = output (normalerweise $FF)
$BFD400    talo    CIAB Timer A Byte niedrig (7.15909 MHz NTSC; 7.09379 MHz PAL)
$BFD500    tahi    CIAB Timer A Byte hoch
$BFD600    tblo    CIAB Timer B Byte niedrig (7.15909 Mhz NTSC; 7.09379 Mhz PAL)
$BFD700    tbhi    CIAB Timer B Byte hoch
$BFD800    todlo   Timer für horizontale Synchronisation -  Bits 7-0
$BFD900    todmid  Timer für horizontale Synchronisation -  Bits 15-8
$BFDA00    todhi   Timer für horizontale Synchronisation -  Bits 23-16
$BFDB00            Nicht verwendet
$BFDC00    sdr     CIAB serial data register (nicht verwendet)
$BFDD00    icr     CIAB interrupt control register
$BFDE00    cra     CIAB Control register A
$BFDF00    crb     CIAB Control register B

Hinweis: CIAB kann ein INT6, d.h. Level 6, generieren: $78.

Anhand dieser "Karte" können Sie sehen, wie die Aufgaben der beiden CIAs vom
Lesen der Tastatur, zur Verwaltung der seriellen Schnittstelle, (für den
Datenaustausch zwischen 2 Computern oder zwischen Computer und Modem), bis hin
zur Verwaltung der parallelen Schnittstelle (z.B. für den Drucker), bis zur 
Überprüfung der Diskettenlaufwerksköpfe enthalten sind. Weiterhin sind Timer 
vorhanden mit den Sie die Mikrosekunden oder Stunden zählen können. 
In der Realität werden wir jedoch aus verschiedenen Gründen nicht an all diesen 
Merkmalen interessiert sein. Zunächst einmal kann der Teil der Hardware der 
sich mit den Diskettenlaufwerk beschäftigt, weggelassen werden, da jedes gute
Spiel / Demo respektwürdig sein muss, d.h. auf einer Festplatte (oder CD-ROM!)
wie Brian The Lion installiert werden kann.
Was die Verwaltung des parallelen und des seriellen Ports betrifft, könnte die 
Verwendung darin bestehen, einige Anweisungen für das Spiel oder einen Satz, 
den ein Charakter sagt zu drucken, oder für die serielle Schnittstelle die
Möglichkeit zu zweit mit den Computern im Netz zu spielen, die mit einem Kabel 
verbunden sind. Aber es muss gesagt werden, dass die Verwaltung des Druckers
durch das Betriebssystem erfolgen muss und zwar mit Hilfe des
"parallel.device". Gleiches gilt für die serielle Schnittstelle: Das
serial.device ist sicherlich sicherer als in Hardware geschriebene Routinen, 
insbesondere für zukünftige Amigas oder Multi-Serial-Karten.
Was die Timer angeht, so verwendet das Betriebssystem mehrere für seine
Aufgaben. Wir werden sehen, ob und welche zu verwenden sind. 

Aber sind wir dann fast ausschließlich daran interessiert, von der Tastatur zu
lesen? Ja, in der Tat, wenn Sie einen Blick in den Code eines Videospiels
werfen würden, würden sie feststellen, dass nur die Interrupts $6c (coper /
vertb / vblank) und $68 (int2 für Tastatur CIAA) neu definiert sind: 
Level 3 ($6c) wird verwendet, um die Musik oder andere Routinen die mit dem
Elektronenstrahl synchronisiert sind, während Level 2 ($68) zum Lesen der 
Tastatur verwendet wird. Natürlich wird auch beim Auslesen der linken 
Maustaste oder anderer Dinge auf CIA-Register zugegriffen, aber dies sind
einfache Überprüfungen oder Bit-Einstellungen, für die man keine lange 
Dissertationen über "btst.b #6,$bfe001" schreiben muss.

In den Demos ist es sogar einfach, keinen Interrupt neu zu definieren oder wenn
nur $6c verwendet wird, um die Musik einzuschalten, neu einzurichten. 

Beginnen wir also mit der Erklärung der CIAA für die Tastaturverwaltung die
folgende Register beinhaltet: $bfec01 (sdr), $bfed01 (icr), $bfee01 (cra) und 
den Level 2 Interrupt ($68). Zuerst sehen wir uns die 3 Register einzeln an und
dann machen wir Beispiele für ihre korrekte Verwendung. 

Wenn eine Taste gedrückt oder losgelassen wird, wird von der Tastatur ein 
8-Bit-Code über $bfec01 gesendet und ein Level 2 Interrupt ($68) wird
generiert, wodurch die Tastatur "mitteilet", dass der Code dieser Taste 
empfangen worden ist.
Beachten Sie, dass dieser Code NICHT der ASCII-Code des gedrückten Zeichens
ist, sondern ein Code mit der Position der gedrückten Taste der Tastatur.

*******************************************************************************
;* BFEC01    sdr   CIAA sdr (serial data register - verbunden mit der Tastatur)
*******************************************************************************

Es ist ein synchrones 8-Bit-Schieberegister, das mit der Tastatur verbunden
ist. Es kann auf zwei Arten arbeiten: EINGANG oder AUSGANG und die Auswahl 
zwischen diesen beiden Modi erfolgt durch Einwirkung auf Bit 6 von $bfee01
(cra).
In der Betriebsart INPUT werden von der Tastatur empfangene Daten bitweise in
das Register eingegeben. Ein Bit nach dem anderen und wenn alle 8 Bits des
gedrückten Zeichen, die es bilden, angekommen sind wird ein Interrupt INT2
($68) generiert. Um zu sehen welche Taste es ist, ist es es notwendig den Wert
in eine Variable zu schreiben. In diesem Fall wird das Byte, das dem 
Zeichencode entspricht gelesen:

	move.b $bfec01,d0

Im Modus OUTPUT wird stattdessen, in das Register geschrieben, 
z.B. "clr.b $bfec01".

****************************************************************************
;* BFED01    icr     CIAA interrupt control register
****************************************************************************

Dieses Register steuert die Interrupts, die von der CIAA generiert werden 
können. Die CIAs erzeugen nämlich bei verschiedenen Gelegenheiten Interrupts,
beispielsweise wenn ein Countdown-Timer abgelaufen ist oder wenn die serielle 
Schnittstelle eine Übertragung beendet hat.
Von besonderem Interesse ist der INT2-Interrupt, Level2, d.h. der Offsetvektor
$68, der erzeugt wird, wenn eine Taste gedrückt wird.
Die Funktionsweise des icr ($bfed01 für CIAA und $bfdd00 für CIAB) ist sehr
speziell, denn sie bestehen in der Tat aus einer "Maske" eines 
schreibgeschützten Datenregisters. Aber was heißt das? Zuallererst ist es sehr
einfach es falsch zu machen und die CIA-Interrupts können verrückte Sachen 
machen was nicht wünschenswert ist. Jeder Interrupt wird aktiviert, wenn das 
entsprechende Bit der Maske auf 1 gesetzt wird, und zwar bei jedem 
CIAA-Interrupt, wie dies der Fall bei INTREQ ($dff09c) ist, wo sein 
entsprechendes Anforderungsbit in diesem Register gesetzt ist.
Wenn dieser Interrupt aktiviert ist, wird an dieser Stelle Bit 7 (IR) gesetzt,
das eine Art set / clr-Bit, wie in dmacon, dh wenn dieses Bit gelöscht wird,
werden die anderen 6 Bits auf 0 zurückgesetzt, wenn das Bit 7 stattdessen
gesetzt ist, werden jedoch die anderen gesetzten Bits gesetzt, während die
die auf Null gesetzt sind, nicht geändert werden.
Das Verwirrende ist, dass beim Lesen des Registers dessen Inhalt zurückgesetzt
wird, unabhängig davon, ob Sie eine "tst.b $bfed01" oder eine Aktion Leseaktion
durchführen. Das Zurücksetzen des Registers beseitigt auch die 
Interruptanforderung, ähnlich wie das Zurücksetzen des INTREQ-Bits ($dff09c).
Jetztsind wir nur am Tastatur-Interrupt Funktion interessiert, also sehen wir
uns kurz seine Bits im Lesemodus an, mit Kommentaren nur dort, wo es
interessant ist:

CIAA ICR ($bfed01)

BIT	NAME	BESCHREIBUNG

07	IR	Bit das, falls gesetzt, anzeigt, dass ein Interrupt ausgeführt wird
06	0
05	0
04	FLG
03	SP	Wenn gesetzt, befinden wir uns in einem von der Tastatur generierten
	    Interrupt
02	ALRM
01	TB
00	TA

Denken Sie daran, dass das Register beim Lesen zurückgesetzt wird. Wenn Sie
also wissen wollen welche Bits gesetzt wurden, müssen Sie es in ein Dx-Register
kopieren und Prüfungen mit diesem Register ausführen: Durch Lesen von $bfed01
werden die Bits gelöscht.

****************************************************************************
;* BFEE01    cra     CIAA cra (control register A)
****************************************************************************

Dieses Register wird "Control" genannt, weil seine Bits die Funktion anderer
Register steuern. Hier ist eine "Karte", mit Kommentaren nur zu den Bits, die
uns zum Lesen von der Tastatur interessieren:

CIA Control Register A

  BIT  NAME		FUNKTION
  ---  ----		--------
   0  START		Timer A
   1  PBON		Timer A
   2  OUTMODE	Timer A
   3  RUNMODE	Timer A
   4  LOAD		Timer A
   5  INMODE	Timer A
   6  SPMODE	Wenn es 1 ist = Register ($bfec01) AUSGANG (zum Schreiben)
				Wenn es 0 ist = Register ($bfec01) EINGANG (zum Lesen)
   7  Nicht verwendet


Wie Sie sehen können, ist das einzige Bit, das uns interessiert, Bit 6, was 
über die Funktion von $bfec01 "entscheidet", dh ob seine Richtung "in Richtung
Tastatur" (Ausgabe) ist, so dass wir darauf schreiben können, oder "von der 
Tastatur zum Amiga" (Eingabe), so dass wir das Zeichen relativ zu der 
gedrückten Taste lesen können.
Um den Modus zu wechseln, gehen Sie einfach so vor:

	bset.b	#6,$bfee01		; CIAA cra - sp ($bfec01) Ausgabe
	....
	bclr.b	#6,$bfee01		; CIAA cra - sp (bfec01) Eingabe

Oder, wenn Sie es für eleganter halten, können Sie auch AND und OR dafür 
verwenden:

	or.b	#$40,$bfee01	; SP OUTPUT (%0100000, wir setzen Bit 6!)
	...
	and.b	#$bf,$bfee01"	; SP INPUT  (%10111111, wir löschen Bit 6!)

Sie können auch "0000" in ein Register verschieben, mit 5 multiplizieren,
durch 5 dividieren, 20 addieren, 10 subtrahieren, 1 addieren, 11 subtrahieren, 
Bit 6 setzen oder zurücksetzen und ein UND oder ein ODER mit $bfee01 machen. 
Der Assembler erlaubt unendlich viele Wege, um die gleiche Sache zu tun. Aber
das bset / clr ist genug!
Es muss jedoch beachtet werden, dass zwischen dem Eingabemodus und dem
Ausgabemodus etwa neunzig Mikrosekunden gewartet werden müssen, da die Hardware
des CIAA und der Tastaturchip sich im Eingabemodus nicht selbst takten können.
Die 8 Bits des Zeichens, das der gedrückten Taste entspricht, werden seriell ein 
Bit nach dem anderen vom Tastaturchip zum CIAA übertragen. Wenn alle 8 Bits
übertragen worden, MÜSSEN WIR DIE KDAT-LEITUNG MINDESTENS FÜR EINE ZEIT VON 90
MIKROSEKUNDEN (oder 3/4 Rasterzeilen) ZUR BESTÄTIGUNG DER TASTATUR DASS WIR DIE
DATEN ERHALTEN HABEN SENKEN. Der KDAT "Thread" wird durch das SP / SPMODE-Bit 
gesteuert und in der Praxis müssen wir dies tun: 

------------------------------------------------------------------------------
	move.b	$bfec01,d0	; CIAA sdr - Wir lesen das aktuelle Zeichen
	bset.b	#6,$bfee01	; CIAA cra - sp ($bfec01) Ausgabe, 
						; die KDAT-Leitung senken, um zu bestätigen, das
						; wir das Zeichen erhalten haben.

	st.b	$bfec01		; $FF in $bfec01 - Wow! Ich habe die Daten erhalten!

; Hier müssen wir eine Routine einbauen, die 90 Millisekunden wartet, weil
; die KDAT-Leitung genügend Zeit niedrig bleiben muss, um von allen Arten von 
; Tastaturen "verstanden" zu werden. Sie können beispielsweise 3 oder 4 
; Rasterzeilen warten.

	bclr.b	#6,$bfee01	; CIAA cra - sp (bfec01) erneut eingeben.
------------------------------------------------------------------------------

Wenn Sie die Tastatur über die Hardware auslesen, müssen Sie sehr vorsichtig 
mit dem Timing umgehen, das 90 Millisekunden wartet, aus zwei Gründen:
1) Die Timing-Routine muss auf allen Prozessoren von 68000 bis 68060 dieselbe Zeit 
   warten. Hierfür können Sie den Elektronenstrahl oder sogar einen CIA-Timer
   verwenden, aber machen Sie NIEMALS eine einfache Dbra-Schleife oder eine Reihe 
   von NOPs, denn aufgrund des Cache auf 68020+ wird dies in kürzester Zeit 
   ausgeführt.

2) Sobald unsere Routine auf allen 680x0 korrekt "wartet", müssen wir auch den
   Fakt berücksichtigen, dass nicht alle Tastaturen gleich sind!
   Beispielsweise können für eine Tastatur zwei Rasterzeilen ausreichen, 
   während für eine 4 Rasterzeilen benötigt werden! Tastaturen enthalten 
   nämlich einen Chip der sie steuert, und dieser kann in den verschiedenen 
   Amiga-Modellen unterschiedlich sein.
   Zum Beispiel ist die Tastatur im A1200 "billig", in der Tat unterscheidet
   sie sich von den normalen Amiga-Tastaturen (Mitsumi im Allgemeinen) durch
   die Tatsache, dass man nicht mehr als einen Tastendruck gleichzeitig 
   registrieren kann ...  Wenn Sie eine Taste gedrückt halten und gleichzeitig
   eine andere drücken, wird beim Loslassen der ersten Taste die zweite nicht
   angezeigt. 
   Die Warte-Routine für das Warten zwischen Ausgabe und Eingabe:
   "or.b #$40" oder "bset.b #6",$bfee01 und "and.b #$bf" oder 
   "bclr.b #6",$bfee01 bestimmt, ob Ihr Programm die Tastatur richtig liest oder
   abstürzt, wenn auf manchen Computern eine Taste gedrückt wird.

In diesem Zusammenhang sehen wir uns an, wie man richtig wartet, indem man das
vblank verwendet:

; Wenn Sie die Adressregister nicht "durcheinander bringen" möchten:

------------------------------------------------------------------------------
	moveq	#4-1,d0		; we wait 4 rasterlines (3+random...!)
waitlines:
	move.b	$DFF006,d1
stepline:
	cmp.b	$DFF006,d1
	beq.s	stepline
	dbra	d0,waitlines
------------------------------------------------------------------------------

; wenn Sie stattdessen auch ein Adressregister "dreckig" machen wollen:

------------------------------------------------------------------------------
	lea	$dff006,a0	; VHPOSR
	moveq	#4-1,d0	; Anzahl der zu wartenden Zeilen = 4 (in der Praxis 3 plus
					; der Bruchteil, in dem wir uns gerade befinden)
waitlines:
	move.b	(a0),d1		; $dff006 - aktuelle vertikale Zeile in d1
stepline:
	cmp.b	(a0),d1		; sind wir immer noch auf der gleichen Zeile?
	beq.s	stepline	; wenn ja, dann warten
	dbra	d0,waitlines	; zu "wartende" Zeile, warten auf d0-1 Zeilen
------------------------------------------------------------------------------

		                    _  ___
		         _æøæ,_  ¸æ¤³°¤¤4Øæ,
		       ,Ø° __¬¶_æ³       ¬VØø
		    __æV  Ø°4, Ø'  ___     0Ø
		  _ØØØØ#  #_,²J¹  æ°"°4,   IØ
		 ÁØ""ØØØþ_____ØL  #__,Ø¹   ØØ
		JØF  ØØ³°°0ØØØØØ_  ¬~~    JØ#
		ØØ1  ¶Ø_  ,Ø°¤ØØØØæ______øØØØ,
		#Ø1   °#ØØ#   ØØØØØØØØØØØØ¯¬ØQ
		¬ØN     `¢Ø&æØØØØØØØØØØØØ`  ØW
		 ¤Øb       °¢ØØØØØØØØØØ³   JØØ
		  `Øæ         ¬`°°°°"    _dØØ@
		   ¬¢Ø_               __øØØØØ
		     0Ø       ¸___,øøØØØØØØ³
		     VØL_   _øØØØØØØØØØØØ²  xCz
		     ¬ØØØØØØØØØØØØØØØ¤³°
		      ¬ØØØØØØØ°
		        °^°°¯

- MERKMALE DES ÜBERTRAGENDEN ZEICHENCODES IN $bfec01

Wir haben bereits gesagt, dass der übertragene Code kein ASCII-Code ist,
sondern eine Information über die gedrückte Taste ist. Das liegt auch daran,
das bei die verschiedenen Tastaturen, in Englisch, Italienisch oder anderen,
viele Tasten oben mit einem anderen Buchstaben bedruckt sind. Aber wenn ich
sage: die dritte Taste der zweiten Reihe, können Sie nichts falsch machen. Die
8 Bits (1 Byte), die wir aus $bfec01 entnehmen enthalten 7 Bits, die sich auf
die Tastenkennung beziehen, sowie ein Bit, das feststellt ob die Taste gedrückt
oder losgelassen wurde. Tatsächlich wird die Tastenkennung sowohl beim Drücken
als auch beim Loslassen der Taste gesendet, mit dem Unterschied, dass das 
höchste Bit, das achte, einmal gelöscht (losgelassen) oder gesetzt (gedrückt)
wird.
Darüber hinaus werden alle gesendeten Codes vor ihrer Übertragung um ein Bit
nach links gedreht. Die Reihenfolge der Übertragung ist daher 6-5-4-3-2-1-0-7.
Verwenden Sie jedoch nur die Anweisung "ROR.B #1,xxx", um die Reihenfolge 
7-6-5-4-3-2-1-0 zurück zu erhalten. Die Übertragung eines Bits dauert 
60 Mikrosekunden, daher wird das gesamte Byte, dass das Zeichen bildet, in 
480 Mikrosekunden übertragen, sodass 17000 Bits pro Sekunde übertragen werden
können. Aber was bedeutet das für uns? Nichts!
Schauen wir uns stattdessen an, wie man erkennt, ob die gedrückte Taste das A,
das B oder eine andere Taste ist. Im Hardware-Handbuch gibt es eine Liste mit
einem Code pro Taste, wobei die Besonderheit ist, dass die Taste "1" dem 
Code "01" entspricht. Um diese Codes zu erhalten, ist es notwendig, ein NICHT
des Bytes zu machen, und eine Rotation der Bits mit einem "ROR" durchzuführen,
um die Reihenfolge 76543210 zu erhalten.

In der Praxis müssen wir Folgendes tun:

	move.b	$bfec01,d0	; CIAA sdr (serial data register - verbunden
						; mit der Tastatur - enthält das vom Tastaturchip
						; gesendete Byte) WIR LESEN DAS ZEICHEN!
	NOT.B	D0			; Wir passen den Wert durch invertieren der Bits an
	ROR.B	#1,D0		; und geben die Sequenz 76543210 zurück.

Jetzt haben wir in d0 das Byte mit der Bitfolge 76543210 anstelle von 65432107,
und außerdem sind alle Bits invertiert, so dass die "Zählung" von der ersten
Taste oben links (nicht ESC, sondern die Taste neben 1) beginnt.
Hier ist die Reihenfolge der Codes mit den jeweiligen Zeichen (normal und
verschoben), aber bedenken Sie, dass die hier beschriebene Tastatur die 
US-Tastatur ist. (Tasten gedrückt)

	cod.	$00		 ;` - ~
	cod.	$01      ;1 - ! 
	cod.	$02      ;2 - @
	cod.	$03      ;3 - #
	cod.	$04      ;4 - $
	cod.	$05      ;5 - %
	cod.	$06      ;6 - ^
	cod.	$07      ;7 - &
	cod.	$08      ;8 - *
	cod.	$09      ;9 - (
	cod.	$0A      ;0 - )
	cod.	$0B      ;- - _
	cod.	$0C      ;= - +
	cod.	$0D      ;\ - |
	cod.	$0e		 ;  << (leer)
	cod.	$0F      ;0  Ziffernblock
	cod.	$10      ;q - Q
	cod.	$11      ;w - W
	cod.	$12      ;e - E
	cod.	$13      ;r - R
	cod.	$14      ;t - T
	cod.	$15      ;y - Y
	cod.	$16      ;u - U
	cod.	$17      ;i - I
	cod.	$18      ;o - O
	cod.	$19      ;p - P
	cod.	$1A      ;[ - {
	cod.	$1B      ;] - }
	cod.	$1c		 ; << nicht verwendet
	cod.	$1D      ;1  Ziffernblock
	cod.	$1E      ;2  Ziffernblock
	cod.	$1F      ;3  Ziffernblock
	cod.	$20      ;a - A
	cod.	$21      ;s - S
	cod.	$22      ;d - D
	cod.	$23      ;f - F
	cod.	$24      ;g - G
	cod.	$25      ;h - H
	cod.	$26      ;j - J
	cod.	$27      ;k - K
	cod.	$28      ;l - L
	cod.	$29      ;; - :
	cod.	$2A      ;' - "
	cod.	$2B      ;(nur internationale Tastaturen) - Nähe return
	cod.	$2c		 ; << nicht verwendet
	cod.	$2D      ;4  Ziffernblock
	cod.	$2E      ;5  Ziffernblock
	cod.	$2F      ;6  Ziffernblock
	cod.	$30      ;< (nur internationale Tastaturen)
	cod.	$31      ;z - Z
	cod.	$32      ;x - X
	cod.	$33      ;c - C
	cod.	$34      ;v - V
	cod.	$35      ;b - B
	cod.	$36      ;n - N
	cod.	$37      ;m - M
	cod.	$38      ;, - <
	cod.	$39      ;. - >
	cod.	$3A      ;/ - ?
	cod.	$3b		 ; << nicht verwendet
	cod.	$3C      ;.  Ziffernblock
	cod.	$3D      ;7  Ziffernblock
	cod.	$3E      ;8  Ziffernblock
	cod.	$3F      ;9  Ziffernblock
	cod.	$40      ;space
	cod.	$41      ;back space <-
	cod.	$42      ;tab ->|
	cod.	$43      ;return Ziffernblock (enter)
	cod.	$44      ;return <-'
	cod.	$45      ;esc
	cod.	$46      ;del
	cod.	$47		 ; << nicht verwendet
	cod.	$48		 ; << nicht verwendet
	cod.	$49		 ; << nicht verwendet
	cod.	$4A      ;-  Ziffernblock
	cod.	$4b		 ; <<
	cod.	$4C      ;cursor hoch  ^
	cod.	$4D      ;cursor runter v
	cod.	$4E      ;cursor rechts   »
	cod.	$4F      ;cursor links «
	cod.	$50      ;f1
	cod.	$51      ;f2
	cod.	$52      ;f3
	cod.	$53      ;f4
	cod.	$54      ;f5
	cod.	$55      ;f6
	cod.	$56      ;f7
	cod.	$57      ;f8
	cod.	$58      ;f9
	cod.	$59      ;f10
	cod.	$5A      ;(  Ziffernblock
	cod.	$5B      ;)  Ziffernblock
	cod.	$5C      ;/  Ziffernblock
	cod.	$5D      ;*  Ziffernblock
	cod.	$5E      ;+  Ziffernblock
	cod.	$5F      ;help
	cod.	$60      ;lshift (links)
	cod.	$61      ;rshift (rechts)
	cod.	$62		 ;caps lock
	cod.	$63      ;ctrl
	cod.	$64      ;lalt (links)
	cod.	$65      ;ralt (rechts)
	cod.	$66      ;lamiga (links)
	cod.	$67      ;ramiga (rechts)


Wie Sie sehen können, entspricht die Reihenfolge in etwa der Reihenfolge der
Tasten, beginnend mit der Reihe mit 1,2,3,4,5 ... dann die Reihe darunter mit
q, w, e, r, t, y ... usw. Diese Codes beziehen sich auf die gedrückten Tasten,
wobei Bit 7 bestimmt, ob eine Taste gedrückt oder losgelassen wurde, wenn es
auf Null steht. Tatsächlich haben wir ein NOT auf dem Byte angewendet, wodurch
auch Bit 7 invertiert wurde: wenn die Taste gedrückt ist, ist Bit 7 = 0, wenn
eine Taste losgelassen wird, ist Bit 7 = 1. Wenn die Taste losgelassen wird,
muss Bit 7 (das achte) als gesetzt betrachtet werden, sodass die obige Tabelle
so aussehen würde:

	cod.	$80		 ;` - ~
	cod.	$81      ;1 - ! 
	cod.	$82      ;2 - @
	...

Beachten Sie, dass der Amiga600 keinen Ziffernblock hat, d.h. wenn Sie also die 
Tasten auf solchen Computern verwenden, gibt es keine Möglichkeit, sie zu 
drücken! Ich empfehle Ihnen daher, diese Taster auf der Tastatur zu meiden.
In Anbetracht dieser Überlegungen können wir sehen, wie der Level 2 Interrupt 
($68) aussehen wird, der es uns ermöglicht, den Code des Tastenanschlags in der
Variablen "ActualKey" zu speichern:

		         ___________
		        /~~/~~|~~\~~\
		        \  \  |  /  /______
		       __\_________/__oOOo_Z________
		      |::888°_~_°888 o¯¯¯T::::Y~~~~~|
		 _    |:::\  °'°  /  __ ||::::|     |
		 \\/Z |::::\ `-' /¯]|··|T|::::|     |
		(\\  )|::::/\`='/\¯  ¯¯  |::::l_____j
		 \¯¯/ ~Z   \ ¯¯¯ /~~~~~~~/~~~~~~~~~~~
		 /¯¯\_/     \ _ /  _    /
		 \   /   /T  (Y)   |\__/
		  \_____/ |   ¯    |
		          |   :    |
		          |        |
		          |   .    | ppX

*****************************************************************************
*	INTERRUPT ROUTINE $68 (Level 2) - Tastatur-Verwaltung					*
*****************************************************************************

;03	PORTS	2 ($68)	Input/Output Port und Timer, verbunden mit Leitung INT2

MioInt68KeyB:	; $68
	movem.l d0/a0,-(sp)	; speichern der Register auf dem Stack
	lea	$dff000,a0		; custom Register offset

	MOVE.B	$BFED01,D0	; Ciaa icr - in d0 (durch Lesen des ICR, wird
						; auch seine Nullsetzung verursacht, so dass der int
						; "gelöscht" wird wie bei intreq).
	BTST.l	#7,D0		; bit IR, (cia interrupt autorisiert), zurückgesetzt?
	BEQ.s	NonKey		; wenn ja, beenden
	BTST.l	#3,D0		; bit SP, (Tastaturinterrupt), zurückgesetzt?
	BEQ.s	NonKey		; wenn ja, beenden

	MOVE.W	$1C(A0),D0	; INTENAR in d0
	BTST.l	#14,D0		; Enable Master Bit zurückgesetzt?
	BEQ.s	NonKey		; wenn ja, interrupt ist nicht aktiv!
	AND.W	$1E(A0),D0	; INREQR - in d1 bleiben nur die Bits gesetzt welche
						;  sowohl in INTENA als auch in INTREQ gesetzt sind
						; um sicher zu sein, dass wenn der Interrupt
						; auftritt, auch aktiviert ist.
	btst.l	#3,d0		; INTREQR - PORTS?
	beq.w	NonKey		; Wenn nicht, dann beenden!

; Wenn wir nach den Kontrollen hier sind, heißt das, dass wir das Zeichen
; übernehmen müssen!

	moveq	#0,d0
	move.b	$bfec01,d0	; CIAA sdr (serial data register - verbunden mit der
						; Tastatur - enthält das vom Tastaturchip
						; gesendete Byte) WIR LESEN DAS ZEICHEN!

; wir haben den char in d0, wir "arbeiten" daran...

	NOT.B	D0			; Wir passen den Wert durch Invertieren der Bits an
	ROR.B	#1,D0		; und Rückgabe der Sequenz zu 76543210.
	move.b	d0,ActualKey	; wir speichern das Zeichens

; Jetzt müssen wir der Tastatur mitteilen, dass wir die Daten übernommen haben!

	bset.b	#6,$bfee01	; CIAA cra - sp ($bfec01) ausgeben, um
						; die KDAT-Zeile zu senken, um zu bestätigen
						; das wir den Charakter erhalten haben.

	st.b	$bfec01		; $FF in $bfec01 - Ich habe die Daten erhalten!

; Hier müssen wir eine Routine einbauen, die 90 Mikrosekunden wartet, weil die
; KDAT-Leitung lange genug niedrig bleiben muss um genügend Zeit haben, um von 
; allen Tastaturen "verstanden" zu werden. Sie können beispielsweise 3 oder 4 
; Rasterzeilen warten.

	moveq	#4-1,d0	; Anzahl der zu wartenden Zeilen = 4 (in der Praxis 3 plus 
				; den Bruchteil, in dem wir uns zum Startpunkt gerade befinden)
waitlines:
	move.b	6(a0),d1	; $dff006 - aktuelle vertikale Zeile in d1
stepline:
	cmp.b	6(a0),d1	; sind wir immer noch auf der gleichen Zeile?
	beq.s	stepline	; wenn ja, warte
	dbra	d0,waitlines	; "erwartete" Zeile, warte d0-1 Zeilen

; Nachdem wir gewartet haben, können wir $bfec01 wieder in den Eingabemodus
; versetzen ...

	bclr.b	#6,$bfee01		; CIAA cra - sp (bfec01) erneut eingeben.

NonKey:						; 3210
	move.w	#%1000,$9c(a0)	; INTREQ Anfrage entfernen, int ausgeführt!
	movem.l (sp)+,d0/a0		; Register vom Stack wiederherstellen
	rte

-----------------------------------------------------------------------------

Sie hätten nichts Neues bemerkt haben, es ist nur eine "Zusammenfassung" der
Dinge, die wir bereits erklärt haben. Schließlich sind es nur ein paar Zeilen
und wir verwenden nur die Register d0 und a0, es handelt sich nicht um eine
KOMPLIZIERTE Routine! 
Das Einzige, was Sie sich merken müssen, ist, dass sie diesen Interrupt auf den
Vektor $68 + VBR setzen und ihn durch Setzen von Bit 3 von INTENA ($dff09a) 
aktivieren.
Wenn Sie zum Beispiel einen Level-3-Interrupt ($6c) zum Abspielen von Musik 
verwenden, der nur VERTB (Bit 5) verwendet, können Sie schreiben:

			 ; 5432109876543210
	move.w	 #%1100000000101000,$9a(a5)   ; INTENA - nur VERTB aktivieren
										  ; von Level 3 und Level 2

Oder anders ausgedrückt "move.w #$c028,$dff09a".

Wir können die korrekte Verwendung dieses Interrupts in Listing11m1.s sehen

In diesem Listing wird der Tastaturcode in Farbe 0 eingegeben, um die 
eigentliche Funktionsweise der Routine selbst zu sehen. Um die Routine zu 
verlassen, müssen sie natürlcih eine Taste drücken: die Leertaste.

Der Einfachheit halber ist in Listing11m2.s eine rudimentäre Routine für die
Konvertierung von ASCII-Tastaturcodes enthalten, die sie bei Bedarf verwenden
können, damit Sie drucken können, was mit der Tastatur geschrieben wurde, zum
Beispiel, wenn Sie sich selbst ein Hilfsprogramm erstellen oder einfach Ihren
Namen in den Highscore Ihres Spiels schreiben wollen.

		             ___________
		            (          ¬)
		             \_       _/
		              L       L
		         ____/___   ___\____
		        //¯¯¯¯¯¯\\_//¯¯¯¯¯¯\\
		       ((     ___¯¯¯___     ))
		        \\ _/\___\X/___/\_ //
		         \ T     \ /     T /
		           |      T      |
		         __|   - o|O -   |__
		     tS / ¬|      |      | ¬\ tS
		\      / / ¯\_____T_____/¯ \ \      /
		->----/ /\      /   \      /\ \----<-
		/     \   \    (_____)    /   /     \
		       \   \_/\_______/\_/   /
		        \_________A_________/

*****************************************************************************
;		TIMER CIAA UND CIAB
*****************************************************************************

Diese Timer werden in Spielen nur sehr wenig verwendet und in Demos fast nie.
Nur in bestimmten (komplizierten) Routinen, die Musik abspielen, die man in
jedem Fall einfach einbindet und die dann von alleine abspielen.
Diese Timer werden unter anderem auch vom Betriebssystem verwendet, sodass,
wenn wir sie verwenden, können wir riskieren, das unser Programm verrückt 
aussteigt.
Außerdem kann man mit dem $dff006 auf die Rasterzeilen warten, die Sie 
brauchen, ohne diese Eventualitäten zu riskieren.
Aus diesem Grund gibt es in dieser Lektion nur einige Listings, die die Timer,
als Beispiel verwenden. In weiterführenden Lektionen werden wir nach 
Anwendungen für diese Timer suchen, und wir werden sie von Fall zu Fall prüfen.

Listing11n1.s	- Verwendung von Timer A von CIAA oder CIAB

Listing11n1b.s	- Verwendung von Timer B von CIAA oder CIAB

Listing11n2.s	- Verwendung von TOD (Time of day)

Berücksichtigen Sie bei der Verwendung von CIA-Timern, dass das Betriebssystem
diese für folgende Zwecke verwendet: (besser die CIAB verwenden!)

   CIAA, timer A	benutzt als Tastaturschnittstelle

!  CIAA, timer B	wird von EXEC für den Taskaustausch verwendet etc.

   CIAA, TOD		50/60 Hz Timer benutzt von Timer.device 
  
   CIAB, timer A	Nicht verwendet, für Programme verfügbar

   CIAB, timer B	Nicht verwendet, für Programme verfügbar

   CIAB, TOD		Wird von der graphics.library verwendet, um der
					Positionen des Elektronenstrahls zu folgen

Wenn Sie Timer verwenden müssen, die auch dem Betriebssystem dienen, dann
machen Sie das nur, wenn Sie Multitasking und System-Interrupts deaktiviert 
haben, dh wenn sie die vollständige Kontrolle über das System haben. 
Niemals den CIAA, Timer B!

                                                              |||||
              |||||                                       _A_ /o O\
_   _ ___.oOo _o_O_ oOo. __ ____ ___ _ _ _____ _ _ _ _   (_^_)_\_/ _oOo. _  
*****************************************************************************
;		LADEN VON DATEIEN MIT DER DOS.LIBRARY
*****************************************************************************

Um diese Lektion voller Verfeinerungen und verschiedener Themen abzuschließen,
gibt es kein besseres Thema als das LADEN von Daten.
Wenn Sie etwas "Großes" programmieren wollen, aus der Sicht der Größe von
verschiedenen Daten wie Bilder und Musik, bei denen man nicht einfach alles mit 
incbin einbinden kann und eine Mega-ausführbare Datei mit "WO" speichern kann,
da die Datei zu groß werden würde, um in den Speicher geladen werden zu können.
Angenommen, Sie möchten eine Diashow erstellen, dh ein Programm, das eine Reihe 
von Bildern nacheinander anzeigt, z.B. mit 30 Bildern, mit jeweils 100 KB
kommen 3 MB an Daten zusammen. 
Wir sind nicht in der Lage, eine Serie von 30 INCBIN's zu machen, um eine 3MB
große Datei zu speichern und auszuführen. Wir müssen einen Weg finden, eines
nach dem anderen zu "laden".
Aber welchen Weg soll man benutzen? Es gibt hauptsächlich 2 davon:

1) AUTOBOOT-LADEN VON DEN TRACKS DER DISKETTE, das ist ein Nicht mit DOS
   kompatibler Modus. In der Tat werden Sie feststellen, dass viele Spiele-
   Disketten, wenn sie in das Laufwerk eingelegt werden, nach dem Laden der
   Workbench, nicht mit Befehlen wie "DIR" gelesen werden können und NOT DOS
   oder FALSCH sind ... kurz gesagt, sie scheinen wie schlechte Disketten zu
   sein!
   Beim Kopieren über Kopierer wie XCOPY oder DCOPY werden einige dieser Nicht-
   Dos-Spiele als "ROTE" Tracks angezeigt, das heißt sie sind nicht einmal vom
   Kopierer zu erkennen, während andere trotz das sie von DOS her nicht lesbar
   sind, als "GESUND" erscheinen, also mit grünen Tracks.
   Ich muss darauf hinweisen, dass die CRACKED-Spiele (ohne Schutz und von 
   Raubkopierern vertrieben) alle vom zweiten Typ sind, dh saubere Tracks
   haben. Tatsächlich besteht der Schutz oft darin, die nicht reparierbaren 
   Spuren in kopierbare Spuren umzuwandeln, die aber von DOS unlesbar bleiben.   
   Die TRACKMOs sind die Mehrheit der Demos, und sie haben "kopierbare" Spuren,
   aber sind nicht über DOS lesbar. 
   Eine Eigenschaft ist, dass wir Code für absolute Adressen schreiben müssen,
   der nicht relozierbar ist, wofür meist nur das erste Mega des CHIP-RAM, oder
   bei einem 1200er die ersten 2 MB verwendet werden, und eventuelle 
   Erweiterungen des FAST-RAM werden nicht verwendet, abgesehen von solchen,
   die COMPLEX LOADERS WITH RELOCATORS verwenden, die wie Mini-Betriebssysteme
   aussehen, die aber häufig Aussetzer auf 68040 aufgrund des übermäßigen 
   Programmier "Überschwangs" haben.
   Dieses System hat den "Vorteil", von Diskette etwas schneller zu sein, als 
   normales DOS, aber es hat den Nachteil, nicht in der Lage zu sein, das 
   Programm auf der Festplatte zu installieren, noch kann es für CD32 etc.
   konvertiert werden.

2) "LEGALES" LADEN UNTER VERWENDUNG DER DOS.LIBRARY, d.h. auf eine Weise, die
   derjenigen sehr ähnlich ist, die von jedem Programm, das das Betriebssystem
   benutzt und mit einer beliebigen Sprache kompiliert wurde wie C, AMOS und so
   weiter. In Wirklichkeit, wenn wir unsere Copperliste beibehalten und mit den
   Hardware-Registern arbeiten machen wir ein "hybrides" System, d.h. wir 
   benutzen die dos.library in einem "besonderen" Zustand mit unserer 
   Copperliste und unseren Interrupts. Ein Merkmal der Programme, die dieses
   System verwenden, ist das das Betriebssystem "intakt" bleiben muss und der
   Code muss vollständig verschiebbar sein (Zugriff auf FAST RAM). Dieses 
   System hat den Vorteil, dass es auf Festplatte, CD-ROM verwendet werden kann
   und allen vom System unterstützten Laufwerken verwendet werden kann, auch 
   von zukünftigen Peripheriegeräte.

Obwohl das erste System für jemanden, der auf Hardwareebene programmieren will,
attraktiver erscheinen mag, ist es in Wirklichkeit ein veralteter, oft 
INKOMPATIBLER und einschränkender Weg, da es unmöglich ist, das Programm (oder
die Demo) auf der Festplatte zu installieren. Solange wir über eine Demo oder
ein Spiel für den Amiga500 sprechen, das nur auf einer 1 Diskette ist, ist die
Trackloader-Option vielleicht akzeptabel, aber ab 2 Disketten aufwärts führt
das System nur noch zu Wut bei Festplattenbesitzer, die immer mehr werden.

Ein auf der HD installiertes Spiel wird immer schneller geladen sein als eines
von der Diskette mit dem schnellstmöglichen Turbolader.

Dann ist da noch das Problem des FAST RAM: um es mit einem Trackloader
verwenden zu können, müsste man ein Mini-Betriebssystem entwickeln, das den
Speicherplatz findet und den Code an die richtige Adresse verschiebt. Ich werde
Ihnen nicht das Listing eines dieser Loader + Relocators anbieten, um Sie nicht
auf einen falschen Weg zu führen.
Denken Sie an die Genugtuung, wenn sie Ihr Spiel für die CD32 konvertieren 
können, oder es auf dem 68060 und jeder HardDisk laufen zu sehen, statt der 
Enttäuschung zu sehen, dass der "Hand"-Relocator versagt, oder zu bemerken,
dass das Programm keinen Fast Ram verwendet ... habe ich Sie überzeugt?

Es gibt noch eine weitere Sache: Es wäre gut, den Befehl "assign" für unsere 
Produktionen zu verwenden, wenn sie Dateien hochladen müssen. Zum Beispiel,
wenn wir ein Spiel von einer Diskette spielen und der Diskette den Namen "Hund"
geben, können Sie die Dateien mit "Hund:Datei1", "Hund:Datei2", "Hund2 /
objects / ogg1" usw. laden
Wenn Sie auf der Festplatte installieren möchten, reicht es aus, ein Verzeichnis
zu erstellen, und dort kopieren Sie den Inhalt des Datenträgers rein und fügen
in der startup-sequence hinzu:

	assign	Hund: dh0:Spielhund	; zum Beispiel...

Wenn es sich um ein Spiel mit mehreren Disketten handelt, kopieren Sie einfach
alle Disketten in das Verzeichnis und weisen Sie jede Diskette zu:

	assign	Hund1: dh0:Spielhund
	assign	Hund2: dh0:Spielhund
	assign	Hund3: dh0:Spielhund

Um die notwendigen Zuweisungen "automatisch" in die Startup-Sequenz oder zum 
user-start, während der Installation des Spiels hinzuzufügen, können Sie die
Optionen des Commodore-Installers oder anderer Systeme nutzen, aber das ist
nicht Bestandteil des Kurses.

Nun, lassen Sie uns sehen, wie eine "Pfad:xx"-Datei in ein Ziel im Speicher 
geladen wird. Es gibt verschiedene Möglichkeiten. Die einfachste ist diese:

CaricaFile:
	move.l	#filename,d1	; String-Adresse "Dateiname + Pfad"
	MOVE.L	#$3ED,D2		; AccessMode: MODE_OLDFILE - Datei, die bereits
							; existiert, und die wir daher lesen können.
	MOVE.L	DosBase(PC),A6
	JSR	-$1E(A6)			; LVOOpen - "Öffnen" der Datei
	MOVE.L	D0,FileHandle	; Handle der Datei speichern 
	BEQ.S	ErrorOpen		; Wenn d0 = 0 ist, liegt ein Fehler vor!

	MOVE.L	D0,D1			; FileHandle in d1 zum Lesen
	MOVE.L	#buffer,D2		; Ziel-Adresse in d2
	MOVE.L	#42240,D3		; Dateilänge (Exakt!)
	MOVE.L	DosBase(PC),A6
	JSR	-$2A(A6)			; LVORead - Datei lesen und in den Puffer kopieren

	MOVE.L	FileHandle(pc),D1	; FileHandle in d1
	MOVE.L	DosBase(PC),A6
	JSR	-$24(A6)			; LVOClose - schließt die Datei.
ErrorOpen:
	rts


FileHandle:
	dc.l	0

; Textstring, der mit einer 0 abgeschlossen werden muss, auf den d1 zeigen 
; muss, bevor wir ÖFFNEN mit der dos.lib. Es ist ratsam, den gesamten Pfad
; anzugeben.

Filename:
	dc.b	"Assembler3:sorgenti7/amiet.raw",0	; Pfad und Dateiname
	even

Dies ist perfekt, wenn Sie die genaue Länge der zu ladenden Datei kennen.
Da es sich um unser Programm handelt, sollten wir wissen, wie lang unsere
Datendateien sind!

Schauen wir uns ein Beispiel in Listing11o1.s an. 

Wir sind jedoch mehr daran interessiert, eine Datei zu laden, während wir 
unsere copperliste anzusehen und vielleicht Musik in Interrupt abspielen. Wie
vereinbaren Sie ein "legales" Laden mit einem vollständig deaktivierten 
Betriebssystem? Inzwischen berücksichtigen wir die Tatsache, dass alle 
Systeminterrupts reaktiviert werden müssen, während die System-Copperliste
nicht benötigt wird, und wir unsere behalten können. Wie kann man also weiter 
Musik abspielen oder etwas anderes tun, während ein Ladevorgang stattfindet?

Die Systeme sind vielfältig. Wir könnten unsere Routinen "legal" zum System-
Interrupt hinzufügen, mit einem AddIntServer(). Oder wir könnten unseren 
Interrupt ausführen, der dann zur Ausführung des Systeminterrupts springt.
Ein etwas weniger respektvoller Weg, aber er funktioniert und ich bevorzuge ihn 
auch weil ich gesehen habe, dass es in CD32-Spielen verwendet wird.

In der Praxis müssen wir Folgendes tun: 
Wiederherstellung der alten Interrupts und des alten DMA / INTENA-Zustands,
Multitasking wieder aktivieren usw., wie wir es beim Beenden tun, aber wir
lassen unsere Copperliste und "fügen" unseren $6c-Interrupt zusätzlich zu dem
des Systems ein. Laden Sie dann die Datei und warten Sie ein paar Sekunden, um
sicher zu sein, dass das Laufwerk ODER FESTPLATTE ODER CD-ROM AUS IST, dann
schließen Sie alles und gehen zurück um gnadenlos auf das Metall einzuschlagen.
Kurz gesagt, vor und nach dem Laden ist es notwendig, das Betriebssystem wieder 
herzustellen und zu aktivieren, wobei unsere Kupferliste beibehalten wird.
Das einzige Detail ist der Interrupt: Wie führen wir unsere aus und springen
dann zur alten? Ich möchte Ihnen ein System für echte Schmuggler anbieten,
aber es funktioniert, solange Sie die Routine "ClearMyCache" aufrufen, die den
Anweisungscache des Prozessors (68020+) löscht.
Tatsächlich werden wir zum ersten (und letzten) Mal SELF-MODIFYING code
verwenden! Es sollte niemals verwendet werden, aber ich möchte, dass Sie einen
der wenigen Fälle sehen, in denen es funktioniert und es nützlich ist, nur zur
Information. 

Sie wissen, dass jede Anweisung, wenn sie assembliert wird, zu einer Reihe von
hexadezimal Werten wird? Zum Beispiel wird rts zu $4e75 und so weiter.

Wir müssen zum alten Interrupt springen, nachdem wir unseren ausgeführt haben.
So wird ein "JMP $12345" beispielsweise zu "$49f900012345" oder zu "$4ef9",
gefolgt von der Adresse, zu der gesprungen werden soll, das ein Langwort ist:.

	dc.w	$4ef9	; Hex-Wert von JMP
Crappyint:
	dc.l	0		; Adresse, zu der gesprungen werden soll, SELF-MODIFYING ...

Wenn wir nun die System-Interrupt-Adresse in CrappyInt eintragen mit:

	move.l	oldint6c(PC),crappyint	; per DOS LOAD - wir werden zu oldint springen

Wir würden den "JMP oldint6c" haben, den wir gesucht haben ... denn es ist der 
letzte Interrupt:


          :                                                     ||| |
          .  ||||                                            .  oO\ .
          : ([oO])                                          (^) \O/<:
          |__\--/__                                          |\__>  |
 -  - - --+------ - ---- ----- - ---------- ------ ------- - - -----+-  -   -
*****************************************************************************
; Interrupt Routine, die beim Laden ausgeführt werden soll. Die Routinen die
; in diesem Interrupt abgelegt wird, wird auch während des Ladens von einer 
; Diskette, einer Festplatte oder einer CD-ROM ausgeführt.
; BITTE BEACHTEN SIE, DASS WIR DEN COPER INTERRUPT VERWENDEN UND NICHT DEN
; VBLANK. DER GRUND IST, DAS WÄHREND DES LADENS VON DER DISKETTE, INSBESONDERE
; UNTER KICK 1.3, DER VERTB-INTERRUPT NICHT STABIL IST, so dass die Musik 
; "ruckeln" würde. Wenn wir stattdessen "$9c,$8010" in unsere copperliste  
; setzen, sind wir sicher, dass diese Routine nur einmal pro Frame ausgeführt
; wird.
*****************************************************************************
																				
myint6cLoad:
	btst.b	#4,$dff01f		; INTREQR - ist Bit 4, COPER zurückgesetzt?
	beq.s	nointL			; Wenn ja, ist es kein "echter" int COPER int!
	move.w	#%10000,$dff09c	; Wenn nicht, ist es die richtige Zeit, 
							; die Anforderung entfernen!
	movem.l	d0-d7/a0-a6,-(SP)
	bsr.w	mt_music		; Musik abspielen
	movem.l	(SP)+,d0-d7/a0-a6
nointL:
	dc.w	$4ef9			; Hex-Wert von JMP
Crappyint:
	dc.l	0	; Adresse, zu der gesprungen werden soll, zum AUTOMODIFY ...
				; ACHTUNG: Der selbstmodifizierende Code sollte generell nicht
				; verwendet werden. Wenn Sie jedoch ein ClearMyCache
				; vorher und nachher aufrufen funktioniert es!

Wie Sie sehen können, müssen Sie nur auf diesen Interrupt in $6c + VBR zeigen,
um mt_music auszuführen und den alten System Interrupt auszuführen, um die
Musik und das Laden gleichzeitig zu erhalten. 

Schauen wir uns ein Beispiel in Listing11o2.s an.

An dieser Stelle können Sie sich vorstellen, was die Routine, die die Eingabe
der Intuition blockiert: Wenn wir eine Datei laden, aktivieren wir Multitasking 
und Systeminterrupts wieder, selbst wenn unsere copperliste angezeigt wird.
Die Workbench funktioniert einwandfrei, so dass, wenn man während des Ladens
die Maus "blind" bewegen kann, man auch durch Klicken auf ein Menu oder Icon
oder auch Tastaturbefehle an das cli geben kann.
Stellen Sie sich einen Spieler vor, der die Gewohnheit hat, die Maus während 
des Ladens zu bewegen und zu drücken, um nicht nervös zu werden: Am Ende des
Spieles könnte er feststellen, dass er auf das Festplattensymbol geklickt hat
und zufällig die Option "Formatieren" aus dem WB-Menü ausgewählt hat, die er
nicht gesehen hat und vielleicht hat das versehentliche Drücken auf der 
Tastatur auch einen obszönen Namen ergegeben. Wenn einmal das Betriebssystem
deaktiviert ist, der Aufruf der InputOff-Routine ist nicht unbedingt notwendig, 
wenn sie Dateien laden oder andere Operationen ausführen werden, ist es gut,
dass kein Schaden angerichtet werden kann!

			-	-	-

Lassen Sie uns zum Abschluss der Lektion sehen, wie Sie eine Datei laden, deren
Länge wir im vorhinein nicht kennen und wir nutzen auch die Möglichkeit die
Routinen AllocMem und FreeMem zu erklären.
Um die Länge einer Datei zu ermitteln, führen Sie einfach eine spezielle
Funktion genannt Examine aus, solange die Datei gesperrt ist. Das ist nicht
sehr schwierig, man braucht nur ein paar JSRs.
Beachten Sie, dass Examine nichts anderes macht, als einen $104 Byte Puffer 
zu füllen. Hier ist ein Beispiel für die verschiedenen Dateidaten:


	cnop	0,4	; Achtung! Der FileInfoBlock muss ausgerichtet sein
; ein Langwort, es reicht nicht aus, dass es an einer geraden Adresse ist!

fib:
	dcb.b	$104,0	; Struktur FileInfoBlock: offsets.
			; 0 = fib_DiskKey
			; 4 = fib_DirEntyType (<0 = file, >0 = directory)
			; 8 = FileName (max 30 Zeichen, Ende mit 0)
			; $74 = fib_Protection, $78 = fib_EntryType
			; $7c = fib_Size, $80 = fib_NumBlocks
			; $84 = fib_Date (3 longs: Days, Minute, Tick)
			; $90 = comment (endet mit einer 0)

Wie Sie sehen können, finden wir die Länge beim Offset $7c. Die anderen Dinge,
die dort sind kümmern uns nicht ... was ist mit dem Datum oder dem Kommentar?
Da wir jedoch Speicher für die Datei reservieren müssen, werden wir sie auch für 
den FileInfoBlock reservieren, um uns diese "dcb.b $104.0" zu sparen.
Sobald wir die Länge der Datei kennen, müssen wir einen Puffer im Speicher
anlegen, von der Länge der Datei, um sie in den Speicher zu laden. Dies 
geschieht mit AllocMem, welcher die Anzahl der zuzuweisenden Bytes und die Art
des Speichers ob Chip oder nicht erfordert, in ähnlicher Weise wie die 
Sections mit "_C" oder nicht. Im Gegensatz zu den Sections müssen wir jedoch
am Ende des Programms alle zugewiesenen Blöcke manuell über die Funktion 
FreeMem freigeben.

 AllocMem
 --------
 
Diese Exec-Routine wird verwendet, um einen Speicherblock für unsere Zwecke 
anzufordern. Geben Sie einfach den benötigten Speichertyp an (in der Praxis, ob
es CHIP-RAM sein muss oder nicht), und die Länge dieses Blocks in Bytes.
Die Routine ALLOKIERT das freie Stück RAM für unsere exklusive Verwendung, den
wir in Besitz nehmen, das bedeutet, dass das Betriebssystem nicht mehr in 
dieses Stück Speicher schreibt, bis "wir es mit Freemem freigeben".
Tatsächlich arbeitet das Amiga-Multitasking-System mit diesem System: Jedes
Programm, fordert durch AllocMem so viel Speicher an, wie es benötigt. Das 
Betriebssystem reserviert dafür freien RAM Bereich, denn wird einem anderen
Programm, das im Multitasking lädt, andere Teile des freien RAMs zugeordnet.
Bisher haben wir den "SECTION BSS" für den von uns benötigten freien 
Speicherplatz verwendet, da wir seine Größe von Anfang an kannten. Und es ist
besser, BSSs für Bitplanes oder Puffer einer bestimmten Größe zu verwenden, aus
verschiedenen Gründen, z.B. um keine Routinen aufrufen zu müssen und um im 
Gegensatz zum zugewiesenen Speicher, auf den wir über Offsets auf den Anfang 
des Blocks zugreifen müssten.
In unserem Listing laden wir eine Datei in den Speicher, deren Länge wir nicht
kennen, daher ist es hier zwingend erforderlich, AllocMem zu verwenden, nachdem 
wir wissen, wie viel Speicherplatz die Datei einnehmen wird.
Sehen wir uns die Funktion im Detail an: 

	move.l	Grandezza(PC),d0 ; Größe des Blocks in bytes
	move.l	TypeOfMem(PC),d1 ; Typ des Speichers (chip,public...)
	move.l	4.w,a6			 ; Exec-Basis	
	jsr	-$c6(a6)			 ; Allocmem
	move.l	d0,FileBuffer	 ; die Startadresse des zugeordneten Speicher-Blocks.
	beq.s	FineMem			 ; d0=0? dann Fehler!
	...

Wenn es nicht notwendig ist, Chip-RAM zuzuweisen (d.h. wenn der zugewiesene
Puffer nicht für Grafik oder Sound ist), weisen sie immer "MEMF_PUBLIC" zu, was
bedeutet: "FAST RAM, wenn vorhanden, oder wenn es wirklich keinen gibt, dann 
Chip-RAM."
Ich erinnere Sie zum x-ten Mal, dass es gut ist, Chip-RAM zu sparen, und dass 
Fast RAM schneller als der Chip RAM ist.
Am Ausgang, befindet sich in d0 die Adresse des angeforderten Speicherblocks, 
der unter anderen auf Long-Wort ausgerichtet wird (d.h. 32-Bit ausgerichtet).
Wenn stattdessen d0 = 0 ist, war es nicht möglich, einen Block diesen Typs zu
reservieren! Testen Sie immer dieses d0, denn im Falle eines "kein Speicher
vorhanden", würden Sie alles nach $0 kopieren !!!

Wir können auch verlangen, dass der benötigte Speicher gelöscht wird, setzen 
sie einfach das Bit MEMF_CLEAR, das 16. (10000). Hier sind die nützlichsten 
Parameter, die man in d1 eingeben kann, um die verschiedenen Typen von Speicher
anzufordern:

MEMF_CHIP	=	2	; Anfrage Chip Ram
MEMF_FAST	=	4	; Anfrage Fast Ram (nicht verwenden)
MEMF_PUBLIC	=	1	; Anfrage Fast, aber wenn nicht vorhanden, ist es Chip!

Und natürlich, wenn Sie wollen, dass die Blöcke auf Null gesetzt werden:

CHIP		=	$10002
FAST		=	$10004	; nicht verwenden...
PUBLIC		=	$10001

Ich rate davon ab, MEMF_FAST anzufordern, denn das Fast-RAM ist nicht auf allen 
Maschinen vorhanden. Verwenden Sie immer MEMF_PUBLIC, außer wenn der Speicher 
für Bitplane, Copperliste oder Audio verwendet werden soll, also MEMF_CHIP ist.
Beachten Sie, dass die Länge des Blocks, den wir eingeben, durch das
Betriebssystem auf ein Vielfaches der Systemblöcke gerundet wird. Dies ist kein 
Problem für uns, denn wenn sie 39 angeben, wird wahrscheinlich 40 zugewiesen, 
aber die 39 angeforderten sind alle da, also ist es uns egal.
Wenn Sie das Programm beenden, denken Sie daran, den Speicherblock freizugeben!

 FreeMem
 -------

Dies ist die Routine, die aufgerufen wird, um den zugewiesenen Speicherblock
freizugeben. Die Adresse des Blocks wird in a1 benötigt und die Länge in Bytes
in d0.
VORSICHT: Wenn Sie versuchen, einen Block freizugeben, der nicht zugewiesen
wurde, werden sie ein verrücktes Durcheinander mit Guru Meditation / Soft 
Failure verursachen! So geben Sie den Speicherblock von vorhin frei:

	move.l	Grandezza(PC),d0  ; Größe des Blocks in Bytes
	move.l	FileBuffer(PC),a1 ; Adresse des Blocks im Speicher zugewiesen
	move.l	4.w,a6			  ; Exec-Basis	
	jsr	-$d2(a6)			  ; FreeMem

	                               /T /I
	                              / |/ | .-~/
	                          T\ Y  I  |/  /  _
	         /T               | \I  |  I  Y.-~/
	        I l   /I       T\ |  |  l  |  T  /
	     T\ |  \ Y l  /T   | \I  l   \ `  l Y
	 __  | \l   \l  \I l __l  l   \   `  _. |
	 \ ~-l  `\   `\  \  \\ ~\  \   `. .-~   |
	  \   ~-. "-.  `  \  ^._ ^. "-.  /  \   |
	.--~-._  ~-  `  _  ~-_.-"-." ._ /._ ." ./
	 >--.  ~-.   ._  ~>-"    "\\   7   7   ]
	^.___~"--._    ~-{  .-~ .  `\ Y . /    |
	 <__ ~"-.  ~       /_/   \   \I  Y   : |
	   ^-.__           ~(_/   \   >._:   | l______
	       ^--.,___.-~"  /_/   !  `-.~"--l_ /     ~"-.
	              (_/ .  ~(   /'     "~"--,Y   -=b-. _)
	               (_/ .  \  :           / l      c"~o \
	                \ /    `.    .     .^   \_.-~"~--.  )
	                 (_/ .   `  /     /       !       )/
	                  / / _.   '.   .':      /        '
	                  ~(_/ .   /    _  `  .-<_
	                    /_/ . ' .-~" `.  / \  \          ,z=.
	                     ~( /   '  :   | K   "-.~-.______//
	                       "-,.    l   I/ \_    __{--->._(==.
	                       //(     \  <    ~"~"     //
	                      /' /\     \  \     ,v=.  ((
	                    .^. / /\     "  }__ //===-  `
	                   / / ' '  "-.,__ {---(==-
	                 .^ '       :  T  ~"   ll
	                / .  .  . : | :!        \\
	               (_/  /   | | j-"          ~^ 
	                 ~-<_(_.^-~"

An dieser Stelle können wir auch das Programm sehen: Listing11o3.s


Anmerkungen:
*1) Eine Emulation eines anderen Prozessors über Illegal Instruction Exceptions
des 68000 durchzuführen ist kompletter Blödsinn. Erstens lösen die meisten
Bitmuster gar keine Exception aus und werden stattdessen vom 68k normal
ausgeführt, und zweitens sind die Instruktionsworte immer 16 Bit breit, was
bei 8-Bit Instruktionen des 6502 oder x86 schwierig wird.

*2) Die IRQ-Flags im INTREQ werden immer dann gesetzt, wenn der entsprechende
Interrupt auftritt. Dabei spielt es keine Rolle ob er in INTENA erlaubt ist
oder nicht. Die gesetzten COPER und BLIT Flags in INTREQ verwundern daher gar
nicht.

*3) Die IRQ-Flags für andere Interrupts haben damit gar nichts zu tun, und die
MMU natürlich auch nicht.

*4) Die Interrupt-Quelle sollte man natürlich in INTREQR prüfen, sobald mehr als
eine Quelle pro Interrupt-Level in INTENA aktiviert wurde. Aber es funktioniert 
dann nicht nur "manchmal" sondern immer.

Bei diesem Beispielcode braucht man sich auch nicht wundern, warum das auf
A4000 und schnelleren CPUs nicht funktioniert:

[...]
nointVERTB: ; 6543210
    move.w  #%1110000,$dff09c   ; INTREQ - Löschen Flag BLIT,VERTB,COPER
                                ; da der 680x0 es nicht von selbst löscht!!!
    rte                         ; Ende vom Interrupt BLIT,VERTB,COPER

68040 und 68060 sind im Fast-RAM so schnell daß das "rte" schon ausgeführt, und
somit die alte Interrupt-Maske im SR-Register bereits wieder hergestellt wurde,
während der Schreibzugriff auf $dff09c noch in der Pipeline auf den langsamen
Custom-Chip Bus wartet.

Resultat: Der Interrupt wird gleich noch einmal ausgeführt, weil Paula noch 
nicht bemerkt hat daß die IRQ-Flags in INTREQ zurückgesetzt sind.

Übliche Lösung ist das Schreiben von INTREQ doppelt auszuführen und/oder ein
"nop" vor dem "rte" einzufügen ("nop" bewirkt auf dem 68060 einen
Instruction Pipeline Flush, und schließt alle Operationen ab bevor die
nächste Instruktion ausgeführt wird).
