Arc Doors/Schnelllader

Aus C64-Wiki
Zur Navigation springenZur Suche springen

<< 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"