Arc Doors/Schnelllader
<< zurück zu Arc Doors
Arc Doors/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Floppy-Schnelllader des Spiels Arc Doors dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion vor dem und während des Ladens gruppiert sind.
Floppy-seitige Schnelllade-Routine[Bearbeiten | Quelltext bearbeiten]
Die folgenden Routine P_AA liest den nachzuladenden Programmteil Sektor für Sektor in Puffer 4 (ab Adresse $0700) ein und folgt dabei den Verkettungszeigern in den ersten beiden Bytes jedes Sektors. Jeder Sektor wird dann vollständig an den C64 gesandt, da dieser anhand der ersten beiden Bytes sowohl den letzten Block als auch die Anzahl der gültigen Datenbytes im letzten Block erkennt.
Das Versenden findet parallel auf der CLOCK- und DATA-Leitung statt, wobei jedes Datenbyte in 4 Bitpaare aufgeteilt wird, die jeweils — beginnend mit den niederwertigsten beiden Bits — im Abstand von 8 Systemtakten (8 μs) auf die Leitungen gelegt werden. Die resultierende Datenrate von kurzzeitig 250 kBit/s wird nur durch die Verwendung des illegalen Opcode SAX erreicht, der während des Schreibzugriffs auf Port B von VIA1 alle nicht benötigten Bits im Akkumulator mit Hilfe einer Bitmaske im X-Register ausblendet.
Jeder Übertragung folgt dann noch ein Pegelwechsel auf der DATA-Leitung, der dem C64 eine Driftkompensation ermöglicht, bevor direkt zum nächsten Datenbyte übergegangen wird.
DEVICE EQU $08 ; Geräteadresse 8
SOURCE1:
P_AA: SEI ; Jobschleife deaktivieren
LDX #$08 ; DATA high, CLOCK low, ATN automatisch beantworten
STX $1800
LDA #$14 ; Rate des Jobschleifen-Interrupt erhöhen (alle 5 ms statt alle 14 ms)
STA $1C07
LDX *$18 ; Spur des zuletzt gelesenen Blocks
LDA *$19 ; Sektor des zuletzt gelesenen Blocks
AA00: STX *$0E ; Spur für Puffer 4 ($0700-$07FF) setzen
STA *$0F ; Sektor für Puffer 4 setzen
LDA #$80 ; Jobcode für "Sektor lesen"
STA *$04 ; als Job für Puffer 4 setzen
CLI ; Jobschleife aktivieren
AA01: BIT *$04 ; Warten bis Job erledigt (gewünschter Block gelesen)
BMI AA01
SEI ; Jobschleife wieder deaktivieren
AA02: LDA $1800 ; Warten auf DATA low
AND #$01
BNE AA02
TAY ; Y=%00000000
STY $1800 ; DATA und CLOCK high
LDX #$18 ; X=%00011000, 2 Systemtakte Verzögerung
CMP *$00,X ; +4 Systemtakte Verzögerung
STX $1800 ; DATA high, CLOCK low
CMP ($00),Y ; 6 Systemtakte Verzögerung
STY $1800 ; DATA und CLOCK high
CMP *$00,X ; 4 Systemtakte Verzögerung
STX $1800 ; DATA high, CLOCK low
NOP ; 2 Systemtakte Verzögerung
STY $1800 ; DATA und CLOCK high
AA03: LDA $0700,Y ; Byte aus Datenpuffer holen
ASL A ; Bit 0 und 1 nach Bitposition 3 und 4 verschieben
ROL A
ROL A
SAX $1800 ; Illegaler Opcode, speichert (A AND X)
ROR A ; Bit 2 und 3 nach Bitposition 3 und 4 verschieben
ROR A
SAX $1800 ; Illegaler Opcode, speichert (A AND X)
ROR A ; Bit 5 und 6 nach Bitposition 3 und 4 verschieben
LSR A
SAX $1800 ; Illegaler Opcode, speichert (A AND X)
LSR A ; Bit 7 und 8 nach Bitposition 3 und 4 verschieben
LSR A
SAX $1800 ; Illegaler Opcode, speichert (A AND X)
LDA #$08
NOP
STX $1800 ; DATA high, CLOCK low (zur Driftkorrektur)
STA $1800 ; DATA low, CLOCK high
INY ; Schreibzeiger auf nächstes Byte richten
BNE AA03 ; Rücksprung falls noch nicht alle Bytes übertragen
LDA $0701 ; Sektornummer des nächsten Blocks
LDX $0700 ; Spurnummer des nächsten Blocks
BNE AA00 ; Rücksprung falls nicht letzter Block
LDA #$01
STA *$1C ; Flag für "Diskette initialisieren" setzen
RTS
Startroutinen[Bearbeiten | Quelltext bearbeiten]
Die nachfolgende Routine P_AC überträgt die Floppy-seitigen Schnelllade-Routinen mittels des "M-W"-Befehls ("Memory-Write") in Abschnitten zu jeweils 32 Bytes in das RAM der Floppy ab Adresse TARGET1=$0500. Anschließend wird der Schnelllader dort mittels "UC" (User command 3) gestartet, bevor der C64 nahtlos in seine eigene Schnelllade-Routine übergeht.
; Befehlskanal für Floppy öffnen
P_AC: LDA #DEVICE ; Geräteadresse
JSR $FFB1 ; LISTEN senden
LDA #$6F ; Sekundäradresse 15 (Befehlskanal)
JMP $FF93 ; Sekundäradresse nach LISTEN senden
; Anzahl Headerbytes setzten und Ladeadresse umkopieren
P_AD: STA $09A2 ; Anzahl Headerbytes (4 Bytes im ersten Block, sonst 2 Bytes) am Anfang des Puffers setzen
LDA *$AE ; Low-Byte der Ladeadresse
STA *$AC ; merken
LDA *$AF ; High-Byte der Ladeadresse
STA *$AD ; merken
RTS
; Y als zweites Zeichen im Namen des nachzuladenden Programmteils setzen; Sprites ausschalten
P_AE: STY AB18+$01 ; Y als zweites Zeichen im Namen des nachzuladenden Programmteils setzen
LDA #$00
STA $D015 ; Alle Sprites ausschalten (vermeidet DMA-Zugriffe des VIC)
RTS
; Nachzuladende Programmdatei zum Lesen öffnen und Ladeadresse holen
P_AB: STX AB18+$00 ; X als erstes Zeichen im Namen des nachzuladenden Programmteils setzen
JSR $7E37 ; Y als zweites Zeichen im Namen des nachzuladenden Programmteils setzen, Sprites ausschalten
AB00: LDA #DEVICE ; Geräteadresse
STA *$BA ; merken
LDA #<AB18 ; Low-Byte der Anfangsadresse des Dateinamens
STA *$BB ; merken
LDA #>AB18 ; High-Byte der Anfangsadresse des Dateinamens
STA *$BC ; merken
LDA #$03 ; Länge des Dateinamens
STA *$B7 ; merken
LDX #$00
LDA #$60 ; Sekundäradresse
STA *$B9 ; merken
JSR $F3D5 ; Datei öffnen
LDA #DEVICE ; Geräteadresse
JSR $ED09 ; TALK senden
LDA #$60 ; Sekundäradresse
JSR $EDC7 ; Sekundäradresse nach TALK ausgeben
JSR $EE13 ; Low-Byte der Ladeadresse vom IEC-Bus holen (IECIN)
STA *$AE ; und merken
LDA *$90 ; Statusvariable ST holen
LSR A
LSR A ; Bit 6 ins CF
BCS AB00 ; Sprung zum erneuten Leseversuch falls kein Byte gelesen
JSR $EE13 ; High-Byte der Ladeadresse vom IEC-Bus holen (IECIN)
STA *$AF ; und merken
LDA #$04 ; Anzahl der Headerbytes im ersten Block
JSR P_AD ; setzen und Ladeadresse umkopieren
LDA #$FC ; Anzahl gültiger Datenbytes im ersten Block
STA AB13+$01 ; in Code einarbeiten (Selbstmodifikation)
; Floppy-seitige Schnelllade-Routine ins Floppy-RAM ab Adresse $0500 kopieren (kopiert unnötigerweise auch die ersten 18 Bytes von P_AB)
LDY #$00 ; Lese- und Schreibzeiger
AB01: STY AB19+$02 ; als Low-Byte der Zieladresse in "M-W"-Befehl einarbeiten
JSR P_AC ; Befehlskanal für Floppy öffnen
LDX #$05 ; "Memory-Write" ("M-W")-Befehl samt Argumenten besteht aus 5+1 Bytes
AB02: LDA AB19,X ; Befehl zeichenweise lesen
JSR $EDDD ; und an Floppy senden
DEX ; Lesezeiger erniedrigen
BPL AB02 ; Rücksprung wenn noch nicht alle Zeichen übertragen
AB03: LDA SOURCE1,Y ; Floppy-seitige Schnelllade-Routine lesen
JSR $EDDD ; und byteweise an "M-W"-Befehl anhängen
INY ; Lesezeiger erhöhen
TYA
AND #$1F ; Vielfaches von 32 Byte übertragen?
BNE AB03 ; Rücksprung falls noch nicht Vielfaches von 32 Bytes übertragen
JSR $EDFE ; UNLISTEN senden
CPY #$80 ; Schon 4*32=128 Bytes ins RAM der Floppy übertragen?
BNE AB01 ; Rücksprung falls nicht letzter 32 Byte-Block
; Floppy-seitige Schnelllade-Routine mit Befehl "UC" ab Adresse $0500 starten
JSR P_AC ; Befehlskanal der Floppy öffnen
LDA #$55 ; 'U'
JSR $EDDD ; auf IEC-Bus ausgeben (IECOUT)
LDA #$43 ; 'C'
JSR $EDDD ; auf IEC-Bus ausgeben (IECOUT)
JSR $EDFE ; UNLISTEN senden
C64-seitige Schnelllade-Routinen[Bearbeiten | Quelltext bearbeiten]
Für die hohen Datenraten des Schnellladers ist eine exakte Synchronisation wichtig. Diese wird vor der Übertragung jedes Datenblocks sichergestellt, indem die Floppy eine Folge von immer dichter aufeinanderfolgenden Pegelwechseln im Abstand von 10, 10, 8 und 6 Systemtakten auf der CLOCK-Leitung erzeugt, an denen sich der C64-seitige Teil der Schnelllade-Routine ausrichten kann. Anschließend überträgt die Floppy ein Datenbyte in Form von 4 Bitpaaren, die jeweils für 8 Systemtakte auf den DATA- und CLOCK-Leitungen liegen.
Da die Taktfrequenz des C64 um rund 1,5% niedriger ist als diejenige der Floppy, würden ohne weitere Maßnahmen die Sende- und Empfangsroutinen zeitlich auseinanderdriften. Auf die 4 Bitpaare folgt daher jeweils ein Pegelwechsel auf der DATA-Leitung, anhand dessen der C64 bei Bedarf die Empfangsroutine um 1 Systemtakt verzögern kann (bedingter Sprung zum Label AB12).
Nach jeder Übertragung eines vollständigen Sektors kopiert der C64 die Datenbytes (ab Offset 4 im ersten, ab Offset 2 in allen weiteren Sektoren) von einem Zwischenpuffer an Adresse $0400 an die endgültige Ladeadresse, während die Floppy den nächsten Sektor von Diskette einliest. Anschließend startet die Übertragung dieses nächsten Blocks mit einer erneuten Synchronisation. An einer Spurnummer von 0 im Verkettungszeiger (im ersten Byte des Sektors) erkennen sowohl Floppy als auch C64 das Dateiende.
SEI ; Interrupts verbieten
LDA #$23
STA $DD00 ; DATA low, CLOCK high
LDA #$0B
STA $D011 ; Bildschirm abschalten
AB04: LDA $D012 ; Warten, bis keine Badlines mehr erzeugt werden können
BNE AB04
AB05: BIT $DD00 ; Warten auf CLOCK low
BVS AB05
AB06: LDA #$00
STA $DD00 ; DATA und CLOCK high
LDY #$00
AB07: BIT $DD00 ; Warten auf CLOCK high
BVC AB07
AB08: BIT $DD00 ; Warten auf CLOCK high
BVC AB08
BIT *$93 ; 5 Systemtakte Verzögerung
NOP
BIT $DD00
BVS AB09 ; 3 Systemtakte Verzögerung bei CLOCK high, 2+3 Systemtakte bei CLOCK low
CMP *$00 ; 3 Systemtakte Verzögerung
AB09: BVS AB10 ; 3 Systemtakte Verzögerung bei CLOCK high, 2 Systemtakte bei CLOCK low
AB10: CMP ($00,X)
CMP ($00,X)
CMP ($00,X)
CMP *00,X ; 4 Systemtakte Verzögerung
AB11: LDA $DD00 ; Datenbit 1 und 0 an Bitposition 7 und 6
LSR A
LSR A ; Datenbit 1..0 nun an Bitposition 5..4
ORA $DD00 ; Datenbit 3 und 2 an Bitposition 7 und 6
LSR A
LSR A ; Datenbit 3..0 nun an Bitposition 5..2
ORA $DD00 ; Datenbit 5 und 4 an Bitposition 7 und 6
LSR A
LSR A ; Datenbit 5..0 nun an Bitposition 5..0
ORA $DD00 ; Datenbit 7 und 6 an Bitposition 7 und 6
EOR #$FF ; Empfangenes Byte invertieren
CMP *$00,X ; 4 Systemtakte Verzögerung
BIT $DD00 ; Resynchronisation
BPL AB12 ; 3 Systemtakte Verzögerung bei DATA low, 2 Systemtakte Verzögerung bei DATA high
AB12: CMP #$00 ; 2 Systemtakte Verzögerung
STA $0400,Y ; Empfangenes Byte in Puffer schreiben
CMP #$00 ; 2 Systemtakte Verzögerung
INY ; Schreibzeiger erhöhen
BNE AB11 ; Rücksprung falls noch nicht 256 Bytes übertragen
LDA #$20
STA $DD00 ; DATA low, CLOCK high
AB13: LDX #$FC ; Anzahl gültiger Datenbytes im ersten Block (wird später angepasst)
LDA $0400 ; Spurnummer des nächsten Blocks
BNE AB15 ; Sprung falls nicht letzter Block
LDX $0401 ; X=Anzahl gültiger Datenbytes im Block
LDA AB16+$01 ; Low-Byte der Adresse des ersten Datenbyte
CMP #$02 ; Nicht erster Block?
BEQ AB14 ; Sprung falls nicht erster Block
DEX ; Ladeadresse nicht umkopieren
DEX
AB14: DEX ; Anzahl gültiger Datenbytes im Sektor um Offset korrigieren
AB15: LDY #$00 ; Lese- und Schreibzeiger initialisieren
AB16: LDA $0404,Y ; Datenbyte aus Puffer holen (Adresse wird später angepasst)
STA ($AE),Y ; und an Zieladresse schreiben
INY ; Lese- und Schreibzeiger erhöhen
DEX ; Anzahl umzukopierender Datenbytes vermindern
BNE AB16 ; Rücksprung falls noch nicht alle Datenbytes umkopiert
LDX #$FE ; Anzahl gültiger Datenbytes ab dem zweiten Datenblock
STX AB13+$01 ; in Code einarbeiten (Selbstmodifikation)
LDX #$02 ; Anzahl Headerbytes ab dem zweiten Datenblock
STX AB16+$01 ; in Code einarbeiten (Selbstmodifikation)
TYA ; A=Anzahl umkopierter Datenbytes
CLC
ADC *$AE ; zum Low-Byte der Schreibadresse addieren
STA *$AE
BCC AB17 ; Sprung falls kein Additionsübertrag
INC *$AF ; sonst High-Byte der Schreibadresse erhöhen
LDX $0400 ; Spurnummer des nächsten Blocks
BEQ AB17 ; Sprung falls letzter Block
JMP AB06
; Abschluss
AB17: LDA #$07
STA $DD00 ; Port wieder auf Standardwert
LDA #$0B
STA $D011 ; Bildschirm bleibt abgeschaltet (unnötig)
CLI ; Interrupts wieder erlauben
RTS
AB18: DB "IN*"
AB19: DB $20,$05,$00,"W-M"