Novaload/Quellcode1
<< zurück zu Novaload
Novaload/Quellcode1: Die folgenden Abschnitte stellen die erste Interruptroutine des Kassetten-Schnellladers Novaload sowie die direkt mit ihr zusammenhängenden Teile des Hauptprogramms dar. Ausgangspunkt für die Analyse ist der disassemblierte Code aus der Kassettenversion des Spiels Buggy Boy. Das Listing ist gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion vor dem und während des Ladens gruppiert sind.
Interruptroutine[Bearbeiten | Quelltext bearbeiten]
Die folgende Routine wird bei jedem Leseimpuls vom Datassetten-Eingang (jeder fallenden Flanke an Pin FLAG von CIA 1) aufgerufen. Sie überprüft zunächst den Stand des Timers A von CIA 1: Dieser startet bei jedem Leseimpuls beim Startwert 1012 und zählt Systemtakte abwärts; falls seit dem letzten Impuls mehr als 500 Systemtakte vergangen sind (der Zählerstand also kleiner als 512 ist), so wurde ein 1-Bit gelesen, ansonsten ein 0-Bit.
Gelesene Bits werden von links in die Speicherzelle an Adresse $A9 geschrieben. Diese wird während des Ladens vor jedem neuen Byte mit dem Wert 127 = %01111111 geladen und dient damit als Bitzähler: Sobald der Rotationsbefehl ein gelöschtes Bit ins Carry-Flag schiebt, wurde eine Gruppe von 8 Bits von der Datassette empfangen.
Der BCC-Befehl hinter dem Label AA00 ist der Dreh- und Angelpunkt der Routine: Die Sprungdistanz dieses Befehls codiert den Zustand des zugrundeliegenden endlichen Automaten und wird bei jedem Zustandswechsel angepasst.
Die Aufgabe der Routine ist es, eine beliebige Anzahl von 256 Byte-Blöcken von Kassette einzulesen und in — nicht notwendigerweise direkt aufeinander folgenden — Speicherseiten abzulegen. Die Routine kann so konfiguriert werden, dass sie ab dem Erreichen eines bestimmten Programmblocks einen dreistelligen Blockzähler auf dem Bildschirm herunterzählt, dessen Startwert zuvor von einer Initialisierungsroutine dort dargestellt wurde.
ORG $0351
BLOCK_END EQU $F000
; Interruptroutine für Datassetteninterrupt
P_AA: PHA ; Register A retten
TYA ; Register Y retten
PHA
LDA $DC05 ; High-Byte CIA 1 Timer A
LDY #$11 ; Timer A mit Startwert laden und starten
STY $DC0E
EOR #$02 ; Bit 1 des Highbyte von Timer A negieren
LSR A ; Zählerstand<512 ergibt CS, sonst CC
LSR A
ROR *$A9 ; CF von links in Byte an Adresse $A9 schieben
LDA *$A9 ; Byte an Adresse $A9 holen
AA00: BCC AA01 ; Zentraler Sprungverteiler, falls 0-Bit herausgeschoben
BCS AA06 ; sonst Rückkehr aus Interrupt
; 1. Einsprung: Synchronisation (Suche nach dem ersten 1-Bit)
AA01: CMP #$80 ; Erstes 1-Bit gelesen?
BNE AA06 ; sonst Rückkehr aus Interrupt
LDA #AA02-AA00-$02 ; Sprung von AA00 nach AA02 vorbereiten
BNE AA04
; 2. Einsprung: Synchronisation (Suche nach $AA=%10101010)
AA02: CMP #$AA ; Bitkombination $AA=%10101010 gelesen?
BEQ AA03 ; Sprung falls ja
LDA #AA01-AA00-$02 ; sonst wieder Sprung von AA00 nach AA01 vorbereiten
BNE AA04
AA03: LDA #AA09-AA00-$02 ; Sprung von AA00 nach AA09 vorbereiten
AA04: STA AA00+$01
AA05: LDA #$7F ; Schieberegister an Adresse $A9 neu laden
STA *$A9
AA06: LDA $DC0D ; Interrupt Request löschen
PLA ; Gerettetes Register Y zurückholen
TAY
PLA ; Gerettetes Register A zurückholen
RTI ; Rückkehr aus Interrupt
; 5. Einsprung: Gelesenes Byte in Speicher schreiben
AA07: LDY *$A7 ; Lesezeiger Low-Byte nach Y holen
STA ($A5),Y ; Gelesenes Byte in Speicher schreiben
AA08: STA $D401 ; und als High-Byte der Frequenz von Stimme 1 setzen
CLC ; und zur Prüfsumme addieren
ADC *$AA
STA *$AA
INY ; Lesezeiger Low-Byte erhöhen
STY *$A7 ; und merken
BNE AA05 ; Sprung falls kein Überlauf
; Block mit 256 Datenbytes gelesen, zurück zum 3. Einsprung
LDA #AA09-AA00-$02 ; Sprung von AA00 nach AA09 vorbereiten
BNE AA04 ; und aus Interrupt zurückkehren
; 3. Einsprung: Kontrolle der Prüfsumme
AA09: CMP *$AA ; Übermittelte mit errechneter Prüfsumme vergleichen
BNE AA17 ; Sprung falls ungleich
AA10: LDA #AA11-AA00-$02 ; Sprung von AA00 nach AA11 vorbereiten
BNE AA04 ; und mit unbedingen Sprung aus Interrupt zurückkehren
; 4a. Einsprung: Nächstes High-Byte des Schreibzeigers (ohne Blockzähler)
AA11: CMP AB00+$01 ; Ende des zu ladenden Programmteils erreicht?
BNE AA15 ; sonst Sprung zum Verarbeiten des High-Byte
LDY #$4B ; An AA10 Sprung von AA00 nach AA12 vorbereiten
STY AA10+$01 ; also ab jetzt Blockzähler aktualisieren
BNE AA15 ; Sprung zum Verarbeiten des High-Byte
; 4b. Einsprung: Nächstes High-Byte des Schreibzeigers (mit Blockzähler)
AA12: BEQ AA16 ; Sprung falls Programmende (High-Byte der Ladeadresse=0)
LDY $066B ; Niederwertigste Stelle der Fortschrittsanzeige
DEY ; erniedrigen
CPY #$2F ; Unterlauf?
BNE AA14 ; Sprung falls nicht
LDY $066A ; Mittlere Stelle der Fortschrittsanzeige
DEY ; erniedrigen
CPY #$2F ; Unterlauf?
BNE AA13 ; Sprung falls nicht
DEC $0669 ; Höchstwertige Stelle erniedrigen
LDY #$39 ; Ziffer '9'
AA13: STY $066A ; als mittlere Stelle
LDY #$39 ; Ziffer '9'
AA14: STY $066B ; als niederwertigste Stelle
AA15: STA *$AA ; Prüfsumme initialisieren
STA *$A6 ; High-Byte des Schreibzeigers setzen
LDA #AA07-AA00-$02 ; Sprung von AA00 nach AA07 vorbereiten
BNE AA04 ; und aus Interrupt zurückkehren
; 6a. Einsprung: Alle Programmteile geladen
AA16: STA *$AB ; Rückmeldung merken
LDA #$1F ; Alle IRQs von CIA1 deaktivieren
STA $DC0D
BNE AA06 ; Rückkehr aus Interrupt
; 6b. Einsprung: Beenden der Interruptroutine mit Rückmeldung "Fehler"
AA17: LDA #$80 ; Kennzeichen für Lesefehler
BNE AA16 ; Unbedingter Sprung zum Ende der Interruptroutine
Warteschleife[Bearbeiten | Quelltext bearbeiten]
Der folgende kurze Programmabschnitt liest ständig das High-Byte des Schreibzeigers für die Interruptroutine. Er erkennt bei Erreichen der Adresse BLOCK_END = $F000, wenn ein erster, sofort auszuführender Programmteil vollständig gelesen ist, und ruft diesen dann mittels JSR auf. Gleichzeitig beginnt die Interruptroutine damit, den Blockzähler auf dem Bildschirm herunterzuzählen.
; Warten bis erster Programmteil geladen ist, dann ausführen
P_AB: LDA *$A6 ; High-Byte der Schreibadresse holen
AB00: CMP #>BLOCK_END ; Endadresse erreicht?
BNE P_AB ; Nein, weiter warten
JSR $E000 ; Geladenen Programmteil ausführen
LDA #$37 ; Alle ROMS einblenden
STA *$01
JSR $FF84 ; CIAs initialisieren
RTS
T001: DB $3A,$8A,$00,$01,$02,$03
Initialisierung[Bearbeiten | Quelltext bearbeiten]
Der nachfolgende Abschnitt stellt den erste Programmteil dar, das beim Laden des Spiels "Buggy Boy" in den BASIC-Speicher des C64 geladen wird und dort mittels RUN gestartet werden kann. Er lädt den Timer, mit dessen Hilfe die Länge der Datassettensignale vermessen werden, bereitet den SID darauf vor, die gelesenen Programmdaten akustisch wiederzugeben, und initialisiert die Speicherkonfiguration und den IRQ-Vektor.
PRG $0801
; BASIC-Starter in Form der Zeile "0 SYS2061"
P___: DW __00 ; Zeiger auf Ende der BASIC-Zeile
DW 0 ; Zeilennummer
DB $9E ; Token für BASIC-Befehl "SYS"
DB "2061",$00 ; Startadresse der Routine P_AC
__00: DB $00,$00 ; Ende des BASIC-Programms
P_AC: LDA #$93 ; <CLEAR/HOME>
JSR $FFD2 ; BSOUT Ausgabe eines Zeichens
LDA #$8E ; <UPPER CASE>
JSR $FFD2 ; BSOUT Ausgabe eines Zeichens
LDA #$00 ; Farbe "schwarz"
STA $D020 ; als Rahmenfarbe setzen
LDA #$0B ; Farbe "dunkelgrau"
STA $D021 ; als Hintergrundfarbe setzen
LDX #$0F ; Lese-/Schreibzeiger initialisieren
AC00: LDA $0341,X ; Dateinamen lesen
AND #$3F ; Umrechnung ASCII nach Bildschirmcode
STA $054F,X ; und auf Bildschirm anzeigen
LDA #$01 ; Farbe "weiß"
STA $D94F,X ; als Schriftfarbe setzen
LDA T000,X ; "NOVALOAD ..."
STA $07A4,X ; auf Bildschirm anzeigen
LDA #$0F ; Farbe "hellgrau"
STA $DBA4,X ; als Schriftfarbe setzen
DEX ; Lese-/Schreibzeiger erniedrigen
BPL AC00 ; Rücksprung falls noch nicht alles umkopiert
SEI ; Interrupts verbieten, da IRQ-Vektor geändert wird
CLD
LDA #$05 ; Alle ROMs ausblenden, nur I/O-Bereich eingeblendet
STA *$01
LDA #$1F ; Keine Interrupts
STA $DC0D ; von CIA1 erlauben
STA $DD0D ; von CIA2 erlauben
LDA $DC0D ; Aktuelle IRQs von CIA1 löschen
LDA $DD0D ; Aktuelle IRQs von CIA2 löschen
; Ausgabe der geladenen Datenbytes per SID vorbereiten
LDA #$F0
STA $D400 ; Stimme 1 Frequenz (low)
STA $D406 ; Stimme 1 Sustain=15, Release=0
LDA #$0F ; Maximale Lautstärke
STA $D418
STA *$AB
LDA #$00
STA $D404 ; Stimme 1 Wellenform="aus"
STA $D405 ; Stimme 1 Attack=0, Decay=0
STA $D40B ; Stimme 2 Wellenform="aus"
STA $D412 ; Stimme 3 Wellenform="aus"
STA *$A7
STA *$A5
LDA #$21 ; Stimme 1 Wellenform="Dreieck"
STA $D404
; Vorbereitungen für Schnelllader
LDA #$55 ; Zweites Synchronisationsbyte
STA *$AA ; als Prüfsumme setzen
LDA #$F4 ; CIA1 Timer 1 Startwert=$03F4=1012
STA $DC04 ; Low-Byte setzen
LDA #$03
STA $DC05 ; High-Byte setzen
LDA #$90 ; CIA1 löst bei Datasetten-Bit einen IRQ aus
STA $DC0D
LDA #<P_AA ; IRQ-Vektor (Low-Byte)
STA $FFFE
LDA #>P_AA ; IRQ-Vektor (High-Byte)
STA $FFFF
LDA #<T001
STA *$7A
LDA #>T001
STA *$7B
CLI ; Interrupts wieder zulassen
JMP P_AB ; und warten bis erster Programmteil geladen ist
; Bildschirmcodes für "NOVALOAD N101788"
T000: DB $0E,$0F,$16,$01,$0C,$0F,$01,$04,$20,$0E,$31,$30,$31,$37,$38,$38