Magic Disk 64

home to index to html: MD9407-KURSE-IRQ-KURS_9.2.html
      Fortsetzung IRQ-Kurs (Teil9)      
----------------------------------------
Wie Sie sehen, wird hier  lediglich  der
Rasterinterrupt  auf  Zeile $E0 gesetzt,
sowie die Bord-Routine  als  IRQ-Routine
eingestellt. Die eigentliche Arbeit wird
von  den  Routinen  "PLEX"  und "SETSPR"
durchgeführt, wovon wir uns die  Erstere
nun genauer anschauen möchten. Sie steht
ab Adresse $1300:                       
PLEX:clc        ;Sprites zaehlen, indem 
     lda $80    ; die Inhalte der Ein-/ 
     adc $81    ; Ausschaltregister     
     adc $82    ; aller Sprites einfach 
     adc $83    ; im Akku aufaddiert    
     adc $84    ; werden.               
     adc $85                            
     adc $86                            
     adc $87                            
     adc $88                            
     adc $89                            
     adc $8a                            
     adc $8b                            
     adc $8c                            
     adc $8d                            
     adc $8e                            
     adc $8f                            
     sta $7e    ;Anzahl merken          
     tax        ;Aus Tabelle ONTAB      
     lda ONTAB,x; den VIC-Wert zum Ein- 
     sta $7f    ; schalten holen und in 
                ; $7F ablegen           
     cpx #$00   ;Keine Sprites an?      
     bne clry   ;Nein, also weiter      
     rts        ;Sonst Prg. beenden     
Diese Routine ermittelt zunächst einmal,
wieviele Sprites überhaupt eingeschaltet
sind. Dies tut sie, indem Sie die Inhal-
te  der  Einschalt-Register  des Pseudo-
VICs aufaddiert, wobei das Ergebnis  die
Anzahl  eingeschalteter  Sprites  ergibt
(wenn an, dann  Wert=1,  sonst  0).  An-
schließend  wird aus der Tabelle "ONTAB"
der Wert ausgelesen,  der  in  das  VIC-
Register  zum  Einschalten  der  Sprites
kommen  muß,  um  die  gefundene  Anzahl
Sprites zu aktivieren. Die Liste enthält
folgende Werte:                         
$00,$01,$03,$07,$0F,$1F,$3F,$7F         
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF         
Sind nun weniger als acht Sprites einge-
schaltet, so wird auch nur diese  Anzahl
aktiviert  werden. Sind es mehr, so müs-
sen immer alle acht  eingeschaltet  wer-
den. Der so ermittelte Wert wird dann in
der  Speicherzelle  $7F  zwischengespei-
chert. Für den weiteren Verlauf der Rou-
tine ist auch die ermittelte Anzahl not-
wendig, die in der  Zeropageadresse  $7E
untergebracht wird. Zum Schluß prüft die
Routine  noch,  ob  überhaupt ein Sprite
eingeschaltet werden soll, und kehrt bei
einer Anzahl von 0 unverrichteter  Dinge
zur IRQ-Routine zurück.                 
Wenn mindestens ein Sprite eingeschaltet
ist, so wird die eigentliche  Multiplex-
Routine   aktiv.  Sie  muß  nun  die  Y-
Koordinaten der Sprites  sortieren,  und
die Verteilung der 16 virtuellen Sprites
auf  die  echten VIC-Sprites übernehmen.
Zur Sortierung benötigen wir  noch  zwei
weitere, jeweils 16 Byte große, Felder. 
Im ersten, von Zeropageadresse  $E0  bis
$EF,  wird  eine Kopie der Y-Koordinaten
angelegt, welche dann sortiert wird.  Da
die Sortierroutine die Werte der einzel-
nen Koordinaten  manipulieren  muß,  ist
diese  Maßnahme  notwendig.  Desweiteren
darf sie die Spritedaten  in  den  Regi-
stern  von  $80 bis $DF nicht verändern,
bzw. vertauschen, da ein  Programm,  das
die  viruellen  VIC-Register  mit  Daten
füttert, ja immer  davon  ausgehen  muß,
daß  es  bei  Sprite0  immer das ein und
selbe Sprite anspricht, und nicht eines,
das von  einer  Position  weiter  hinten
hierhin sortiert wurde. Deshalb sortiert
die  Multiplex-Routine nicht die eigent-
lichen Sprites in die benötigte  Reihen-
folge,  sondern  legt  eine  Tabelle mit
Zeigern auf die Reihenfolge der  Sprites
an. Selbige wird in den Zeropageadressen
von  $F0  bis  $FF abgelegt. Sie enthält
nach der Sortierung Werte zwischen 0 und
15, die als Index auf die dem virtuellen
Sprite entsprechenden Register dienen.  
Zur  eigentlichen  Sortierung  verwenden
wir nun einen ganz einfachen Bubblesort-
Algorithmus, der so oft über dem zu sor-
tierenden Feld angewandt wird, wie Spri-
tes  eingeschaltet  sind.  Er  ermittelt
durch eine Reihe von  Vergleichen  immer
den  kleinsten  Wert innerhalb der Kopie
der Y-Positionen, legt ihn in  der  Zei-
ger-Liste bei $F0 ab, und setzt die ent-
sprechende Koordinate auf $FF, damit sie
im   nächsten    Sortierdurchlauf    den
größtmöglichen  Wert  enthält  und somit
nicht noch einmal das Minimum sein kann.
Ebenso müssen wir mit den  Y-Koordinaten
von  abgeschalteten  Sprites  verfahren,
die vor dem eigentlichen Sortieren eben-
falls  auf  $FF  gesetzt werden. Dadurch
stehen sie immer am Ende der Liste. Nach
der Sortierung kann die Routine dann mit
Hilfe der Zeiger die Daten zu den  Spri-
tes  aus  den  entsprechenden  Registern
holen, und in den VIC schreiben.        
Bevor  wir nun zur Beschreibung der Rou-
tine kommen, noch einige technische Hin-
weise:   damit   die  Routine  möglichst
schnell arbeitet, wurde auf die  Verwen-
dung  von  Schleifen so weit wie möglich
verzichtet. Das heißt, daß z.B jeder der
Y-Werte über  einen  eigenen  CMP-Befehl
verfügt, der ihn mit dem aktuellen Mini-
mum vergleicht. Analog ist es bei  ande-
ren Vorgängen (so auch bei der Aufaddie-
rung der Sprite-Einschalt-Register oben,
wo eigentlich auch eine  Schleife  hätte
benutzt  werden können). Dadurch verlän-
gert sich  das  Programm  natürlich  ein
wenig,  jedoch  ist der damit verbundene
Speicheraufwand noch erträglich und  wir
erreichen zudem eine hohe Verarbeitungs-
geschwindigkeit. Da sich viele  Vorgänge
oft  wiederholen,  werde  ich  an diesen
Stellen mit "..." eine  Folge  andeuten,
die  analog auch für alle weiteren Spri-
tes ausgeführt wird.                    
Kommen  wir  nun  also  zur eigentlichen
Multiplex-Routine,  die  mit  dem  Label
"CLRY"  beginnt,  wohin  auch die letzte
Routine verzweigt, wenn  Sprites  einge-
schaltet sind. Hier wird nun geprüft, ob
ein   Sprite   eingeschaltet  ist,  oder
nicht, und  in  letzterem  Fall  die  Y-
Koordinate  mit dem Wert $FF überschrie-
ben, damit das abgeschaltete Sprite  bei
der Sortierung später nicht mehr berück-
sichtigt wird:                          
CLRY:ldy #$ff  ;Y-Reg mit $FF laden     
sx0: lda $80   ;Sprite0 an?             
     bne sx1   ;Ja, also weiter         
     sty $B0   ;Nein, also $FF in Y-Pos 
sx1: lda $81   ;Sprite 1 an?            
     bne sx2   ;Ja, also weozer         
     sty $B1   ;Nein, also $FF in Y-Pos 
sx2: ...Analog für Sprites 3-14         
sx15: lda $8F   ;Sprite 15 an?          
      bne YCOPY ;Ja, also zu YCOPY spr. 
      sty $BF   ;Nein, also $FF in Y-Pos
Damit  hätten  wir nun also alle Y-Posi-
tionen abgeschalteter Sprites  gelöscht.
Da die Routine die Y-Positionen der ein-
geschalteten  Sprites  nicht   verändern
darf,  wird  nun eine Kopie dieser Werte
in $E0 bis $EF angelegt:                
YCOPY:lda $B0  ;Y-Wert Sprite 0         
      sta $E0  ; kopieren               
      ...Analog für Sprites 1-14        
      lda $BF  ;Y-Wert Sprite 15        
      sta $EF  ; kopieren               
Nachdem nun auch das Sorierfeld angelegt
wurde, können wir endlich mit  der  Sor-
tierung  beginnen.  Hierbei benutzen wir
das Y-Register um den momentan kleinsten
Y-Wert zu speichern. Der Akku wird  beim
Auffinden  eines  minimalen  Wertes dann
immer mit einem Zeiger auf das  entspre-
chende  Sprite  geladen, der der Sprite-
nummer   entspricht.   Das    X-Register
enthält den aktuellen Index auf die Zei-
gertabelle und  wird  pro  Durchlauf  um
eins erhöht. Die Sortierung ist beendet,
wenn  die Vergleichsroutine insgesamt so
oft durchlaufen wurde, wie Sprites  ein-
geschaltet sind:                        
SORT: ldx #$00  ;Index init.            
loop: ldy #$ff  ;YMin. init.            
      cpy $E0   ;YSpr0 < YMin?          
      bcc s1    ;Nein, also weiter      
      ldy $E0   ;Ja, also YMin=YSpr0    
      lda #$00  ;Zeiger Spr0=0 laden    
s1:   cpy $E1   ;YSpr1 < YMin?          
      bcc s2    ;Nein, also weiter      
      ldy $E1   ;Ja, also YMin=YSpr1    
      lda #$01  ;Zeiger Spr1=1 laden    
s2:   ...Analog für Sprites 3-14        
s15:  cpy $EF   ;YSpr15 < YMin?         
      bcc s16   ;Nein, also weiter      
      ldy $EF   ;Ja, also YMin=YSpr15   
      lda #$0F  ;Zeiger Spr15=15        
s16:  sta $F0,x ;Zeiger ablegen         
      tay       ;Zgr. als Index in Y-Reg
      lda #$ff  ;YSpr mit YMin auf $FF  
      sta $E0,y ; setzen.               
      inx       ;Zeiger-Index+1         
      cpx $7E   ;mit Anzahl Sprites vgl.
      beq end   ;Gleich, also Ende      
      jmp loop  ;Sonst nochmal sortieren
end:  rts                               
Wie Sie sehen, wird nach jedem ermittel-
ten Minimum der entsprechende Y-Wert auf
$FF  gesetzt,  damit er im nächsten Ver-
gleich nicht mehr herangezogen wird. Auf
diese Weise wird nun nach und nach immer
wieder der kleinste Wert ermittelt,  so-
lange, bis der Puffer nur noch $FF-Werte
enthält,   und   die  Schleife  "Anzahl-
Sprites"-Mal durchlaufen wurde. Die Sor-
tierung  ist damit beendet, und die Mul-
tiplex-Routine  kehrt  wieder  zur  IRQ-
Routine zurück.                         
Hier nun wird als Nächstes die "SETSPR"-
Routine aufgerufen, die anhand  der  er-
mittelten  Werte  zunächst die Daten der
ersten acht virtuellen  Sprites  in  den
VIC  überträgt.  Gleichzeitig  berechnet
sie  mit  Hilfe  der  Y-Position  dieser
Sprites  die Rasterzeile, an der ein IRQ
ausgelöst werden  muß,  um  das  jeweils
achte Sprite nach dem aktuellen anzuzei-
gen, und setzt den nächsten IRQ-Auslöser
entsprechend. Zunächst einmal wollen wir
uns den ersten Teil dieser  Routine  an-
schauen. Er beginnt ab Adresse $1500:   
SETSPR:                                 
      lda $7F    ;VIC-Wert für eingesch.
      sta $d015  ; Sprites setzen       
      lda #$00   ;High-Bits der X-Pos   
      sta $d010  ; löschen              
      lda $7E    ;Anzahl Sprites holen  
      cmp #$01   ;Wenn mind. 1 Spr. ein-
      bcs spr00  ;gesch., dann weiter   
      rts        ;Sonst Ende            
spr00:clc        ;C-Bit für Add. löschen
      ldx $E0    ;Zgr. aus Tabelle holen
      lda $90,x  ;X-Pos. holen und für  
      sta $d000  ; VIC-Spr.0 setzen     
      lda $B0,x  ;Y-Pos. holen und für  
      sta $d001  ; VIC-Spr.0 setzen     
      adc #22    ;Raster für Spr.Ende=  
      sta ras8+1 ; YPos+22 setzen       
      lda $D0,x  ;Spr.Zeiger holen und  
      sta $07f8  ; für VIC-Spr.0 setzen 
      lda $C0,x  ;Spr.Farbe holen und   
      sta $d027  ; für VIC-Spr.0 setzen 
      ldy $a0,x  ;X-Pos-High holen,     
      lda $d010  ;X-High-Bit-Reg. holen 
      ora high0,y;Wert f. VIC-Spr.0 ein-
      sta $d010  ; odern und zurückschr.
      lda $7E    ;Anzahl holen          
      cmp #$02   ; Mehr als 1 Spr. an?  
      bcs spr01  ; Ja, also weiter.     
      rts        ;Sonst Ende            
spr01:Analog für Sprite 1-7             
      ...                               
spr07:...        ;Werte f. Spr.7 setzen 
      lda $7E    ;Falls mehr als acht   
      cmp #$09   ; Sprites, dann        
      bcs acht   ; neuen IRQ setzen     
      rts        ; Sonst Ende           
acht: lda #<spr08;Adr. Routine "Spr8"   
      sta $fffe  ; in IRQ-Vektor bei    
      lda #>spr08; $FFFE/$FFF ein-      
      sta $ffff  ; tragen               
ras8: lda #$00   ;Rasterz. Spr0+22 als  
      sta $d012  ; IRQ-Quelle setzen    
      dec $d019  ;VIC-IRQs freigeben    
      rts        ;Ende                  
Wie Sie sehen, holt  die  SETSPR-Routine
nun  nacheinander  alle  Zeiger  aus der
sortierten Tabelle bei $F0 und überträgt
die Werte der ersten acht Sprites in den
VIC. Auf die  Register  des  Pseudo-VICs
wird  dabei  über  X-Register-indizierte
Adressierung  zugegriffen.  Zwei   Dinge
sollten  nun  noch erläutert werden: Zum
Einen wird nach  Setzen  der  Y-Position
eines Sprites die Rasterzeile berechnet,
an der es zu Ende gezeichnet ist. Der so
ermittelte  Wert  wird nun  an dem Label
"RAS8" plus 1 eingetragen, womit wir den
Operanden des LDA-Befehls  am  Ende  der
Routine  modifizieren.  Er  lädt nun den
Akku mit der besagten Rasterzeilennummer
und  schreibt  ihn  in  das  Raster-IRQ-
Register, womit der VIC nachdem er Spri-
te0 auf dem Bildschirm dargestellt  hat,
einen  Raster-IRQ  erzeugt. Hierbei wird
dann zur Routine "SPR8"  verzweigt,  die
ich Ihnen gleich erläutern werde. Analog
wird  mit den Sprites von 1-7 verfahren,
wobei nach jedem Sprite geprüft wird, ob
noch ein weiteres  Sprite  eingeschaltet
ist,  und  demnach  initialisiert werden
muß. Ist das nicht  der  Fall,  so  wird
direkt  zum  IRQ  zurückgekehrt. Dadurch
wird ebenfalls nur dann ein IRQ auf  die
Routine    "SPR8"    eingestellt,   wenn
tatsächlich  mehr  als  acht   virtuelle
Sprites  eingeschaltet  sind. Im anderen
Fall brauchen wir ja keine  Manipulation
vorzunehmen,  weswegen  der  nächste Ra-
ster-IRQ, wieder auf "BORD" springt,  wo
wir  die  Multiplex-Routine ein weiteres
Mal durchlaufen. Sollen  nun  aber  mehr
als  acht Sprites dargestellt werden, so
wird ein IRQ erzeugt, der auf die Routi-
ne  "SPR08"  springt, die wir uns gleich
näher anschauen werden.                 
Die zweite, etwas undurchsichtige Stelle
ist  das  Setzen  der  High-Bits für die
X-Position eines Sprites. Hier gehen wir
wiefolgt vor: Zunächst  wird  der  High-
Wert  der  X-Position  geholt, der nur 0
oder 1 sein,  je  nach  dem  ob  die  X-
Position  kleiner oder größer/gleich 256
ist. Dieser Wert wird nun als Zeiger auf
eine Tabelle mit  X-High-Bit-Werten  für
das  jeweilige Sprite benutzt. Sie steht
ganz am Ende  des  Programms  und  sieht
folgendermaßen aus:                     
high0:    $00,$01                       
high1:    $00,$02                       
high2:    $00,$04                       
high3:    $00,$08                       
high4:    $00,$10                       
high5:    $00,$20                       
high6:    $00,$40                       
high7:    $00,$80                       
Wie  Sie  sehen,  enthält  sie für jedes
Sprite einmal ein  Nullbyte,  das  durch
die  SETSPR-Routine  geladen  wird, wenn
der X-High-Wert Null  ist,  sowie  einen
Wert, in dem das Bit gesetzt ist, das im
X-High-Bit-Register für das entsprechen-
de Sprite zuständig ist. Durch das  Ein-
odern  des  ermittelten Wertes in dieses
Register setzen wir nun letztendlich das
High-Bit der X-Position  eines  Sprites.
Hierbei  wird  bei  den  Sprites von 1-7
jeweils  auf  ein  eigenes  "High"-Label
zugegriffen,   bei  Sprite  1  also  auf
"High1", bei Sprite 2 auf "High2" und so
weiter.                                 
Kommen   wir  nun  jedoch  zur  "SPR08"-
Routine, die als IRQ-Routine  aufgerufen
wird,  und  zwar nachdem Sprite0 auf dem
Bildschirm dargestellt wurde:           
SPR08:pha        ;Prozessor-Regs. retten
      txa                               
      pha                               
      tya                               
      pha                               
      ldx $F8    ;Zgr. aus Sort-Liste   
      lda $90,x  ;X-Pos. in VIC-Spr0    
      sta $d000  ; eintragen            
      lda $B0,x  ;Y-Pos. in VIC-Spr0    
      sta $d001  ; eintragen            
      lda $D0,x  ;Spr.Zgr. in VIC-Spr0  
      sta $07f8  ; eintragen            
      lda $C0,x  ;Spr.Farbe in VIC-Spr0 
      sta $d027  ; eintragen            
      ldy $90,x  ;X-High-Wert holen     
      lda $d010  ;VIC-High-Reg. lesen   
      and #$FE   ;Bit f. Spr0 ausmask.  
      ora high0,y;Mit Wert f. X-High    
      sta $d010  ; odern u. zurückschr. 
      lda #<spr09;Adresse f. Raster-IRQ 
      sta $fffe  ; des nächsten Sprite  
      lda #>spr09; in IRQ-Vektoren      
      sta $ffff  ; schreiben            
      dec $d019  ;VIC-IRQs freigeben    
      lda $7E    ;Anzahl holen,         
      cmp #$0a   ;Spr9 benutzt?         
      bcs ras9   ;Ja, also weiter       
      jmp bordirq;Sonst IRQ rücksetzen  
ras9: lda #$00   ;Rasterz. f. Spr9 laden
      cmp $d012  ;m. akt. Strahlpos vgl.
      bmi direct9;Wenn größer o. gleich 
      beq direct9; Spr.9 sofort zeichnen
      sta $d012  ;Sonst n. IRQ festlegen
      pla        ;Prozessor-Regs zurück-
      tay        ; holen und IRQ        
      pla        ; beenden              
      tax                               
      pla                               
      rti                               
Wie  Sie  sehen,  werden zunächst wieder
wie  schon  bei  den   anderen   Sprite-
Routinen, die vituellen VIC-Werte in den
echten  VIC  übertragen.  Hiernach  wird
verglichen, ob  mehr  als  neun  Sprites
dargestellt  werden  sollen, und wenn ja
zur Routine "RAS9"  verzweigt,  die  den
IRQ  für  Sprite9  vorbeitet.  An diesem
Label  befindet  sich  wieder  der  LDA-
Befehl,  der  von der Darstellungroutine
für Sprite1 so abgeändert wurde, daß  er
die Rasterzeile des Endes dieses Sprites
in  den  Akku lädt. Bevor nun der Inter-
rupt gesetzt wird,  prüft  das  Programm
durch  einen  Vergleich  des Akkuinhalts
mit der aktuellen  Rasterstrahlposition,
ob  der  Rasterstrahl  an besagter Zeile
nicht schon vorüber  ist.  In  dem  Fall
wird  kein  IRQ vorbereitet, sondern di-
rekt auf die Routine zur Darstellung des
nächsten  Sprites  verzweigt  (sh.  BEQ-
bzw.  BMI-Befehle).  Diese  beginnt dann
folgendermaßen:                         
  (Bitte wählen Sie nun den 3.Teil des  
      IRQ-Kurses aus dem Textmenu!)     
Valid HTML 4.0 Transitional Valid CSS!