Novaload/Quellcode1

Aus C64-Wiki
Zur Navigation springenZur Suche springen

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