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