Maggi-Fix für SNES Programmierung

Es werden stets neue Spieletests, Berichte und Artikel für die Homepage gesucht. Beweist hier Euer Schreibtalent.

Moderator: Perikles

Benutzeravatar
ikari_01
snesfreaks.com-Team
snesfreaks.com-Team
Beiträge: 441
Registriert: 15. Juni 2010, 23:19
+Positive Tradingpoints+: 6 von 6
Wohnort: Wunstorf

Re: Maggi-Fix für SNES Programmierung

Beitrag von ikari_01 » 31. Januar 2013, 10:03

Hab ich noch nie ausprobiert, unwahrscheinlich ist das aber nicht. :)
sd2snes news: https://sd2snes.de

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 31. Januar 2013, 10:23

lolzn.

Habe gerade in meinem Textbox-Shakalaka nachgeschaut, wo ich wusste, dass ich dort auch über $7FFF hinaus das genutzt habe.

Code: Alles auswählen

;===================================================================================================================
; VRAM CONTENT:
; $0000-$07FF - Sprite
; $9000-$93FF - Landscape Tiles
; $A000-$A4FF - Textbox Tiles
; $F000-$F7FF - Tilemap BG1 (Landscape)            Isn't set yet - happens further down
; $F800-$FFFF - Tilemap BG3 (Textbox)              Isn't set yet - happens further down
;===================================================================================================================
Klasse, was? Im Falle des Falls, dass es in der Tat eine Spiegelung ist, habe ich unwissendlich die Sachen genau so abgelegt, dass sie nicht kollidieren.

Aber dann habe ich zumindest eine Testumgebung, wo ich es einfach überprüfen kann. Heute abend. Oder demnächst. Oder so. ;)
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
ikari_01
snesfreaks.com-Team
snesfreaks.com-Team
Beiträge: 441
Registriert: 15. Juni 2010, 23:19
+Positive Tradingpoints+: 6 von 6
Wohnort: Wunstorf

Re: Maggi-Fix für SNES Programmierung

Beitrag von ikari_01 » 31. Januar 2013, 10:35

Das sind einfach Byteadressen da in der Tabelle. Wenn die tatsächlich so in die Register geschrieben werden, funktioniert die Spiegelung wohl wirklich. Überschneidungen gibt es ja glücklicherweise keine ;)
sd2snes news: https://sd2snes.de

Fredward
SNES-Newbie
SNES-Newbie
Beiträge: 1
Registriert: 15. Februar 2013, 18:08

Re: Maggi-Fix für SNES Programmierung

Beitrag von Fredward » 15. Februar 2013, 18:28

Hallo lytron,

ich habe mich exklusiv wegen des Artikels, welchen du hier im Forum verfasst, registriert. Ein großes Lob für die Mühe, die du dir dabei machst. Das Verfolgen macht sehr viel Spaß und ich versuche, dran zu bleiben, denn das Entwickeln in Assembler ist sehr interessant.

Ich warte gebannt auf deinen nächsten Post und wünsche dir viel Energie, dass du dies durchziehst.

Gruß
Fredward

Benutzeravatar
Moi-Même
Mr SNES
Mr SNES
Beiträge: 4025
Registriert: 15. März 2009, 01:04
+Positive Tradingpoints+: 6 von 6

Re: Maggi-Fix für SNES Programmierung

Beitrag von Moi-Même » 15. Februar 2013, 18:30

(...dieser Mann wird sich hier noch bis ganz nach oben schlafen.)

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 15. Februar 2013, 18:45

Fredward hat geschrieben:Ich warte gebannt auf deinen nächsten Post (...)
Keine Panik, solange du niemanden beleidigst, wirst du auch weiter ein User dieses Boards bleiben. :D ;)

Moin Fredward,

danke für das Feedback! Und - wowy, ich hätte nicht gedacht, dass sich deshalb jemand hier extra für Registrieren wird! :oops:

Den nächsten Post sollte ich auf jeden Fall demnächst mal zusammenschreiben; mit dem "Grundkurs" werde ich bald durchsein und ab dann kommen wohl Einzelartikel, die in keiner festen Reihenfolge zu lesen sind und die dann auch nicht regelmäßig geschrieben werden. Wohl eher nach Bedarf, nach Laune und/oder Nachfrage.

Aber viel von dem Zeug müsste ich wahrscheinlich eh nochmal korrigieren... :/

Whatever! Danke dir für deine aufmunternden Worte und ein "Herzlich Willkommen!" bei uns! :)

Moi-Même hat geschrieben:(...dieser Mann wird sich hier noch bis ganz nach oben schlafen.)
Sei doch nicht so gemein zu einem neuregistrierten User! ;)
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
ChronoMoogle
snesfreaks.com-Team
snesfreaks.com-Team
Beiträge: 9895
Registriert: 26. Februar 2007, 14:56
+Positive Tradingpoints+: 98 von 98
Wohnort: Nippon
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von ChronoMoogle » 15. Februar 2013, 18:58

Ich glaube nicht, dass er dem neuen User vorwirft unheimlich schlaefrig und auf hohe Ziele bedacht zu sein, sondern dir :DDD
Bild
| Mein YT Channel | Mein Twitter | Ich suche | Ich verkaufe | Foren SuFu |
...weil Terranigma einfach das Größte ist!

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 15. Februar 2013, 19:13

ChronoMoogle hat geschrieben:Ich glaube nicht, dass er dem neuen User vorwirft unheimlich schlaefrig und auf hohe Ziele bedacht zu sein, sondern dir :DDD
Ich denke gerade eher die ganze Zeit darüber nach, ob er beim Thema "hochschlafen" darauf abzielt, dass er dann in dem Falle meine "letzte Hürde" ist. o_O
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
Moi-Même
Mr SNES
Mr SNES
Beiträge: 4025
Registriert: 15. März 2009, 01:04
+Positive Tradingpoints+: 6 von 6

Re: Maggi-Fix für SNES Programmierung

Beitrag von Moi-Même » 15. Februar 2013, 19:33

Nein, ich habe dich im Chat schon mehrfach herzlich zum Sex mit mir eingeladen. Erinnere dich da bitte genau.

Benutzeravatar
ChronoMoogle
snesfreaks.com-Team
snesfreaks.com-Team
Beiträge: 9895
Registriert: 26. Februar 2007, 14:56
+Positive Tradingpoints+: 98 von 98
Wohnort: Nippon
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von ChronoMoogle » 15. Februar 2013, 19:34

Ich glaube mir wird schlecht.
Bild
| Mein YT Channel | Mein Twitter | Ich suche | Ich verkaufe | Foren SuFu |
...weil Terranigma einfach das Größte ist!

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 15. Februar 2013, 20:22

Moi-Même hat geschrieben:Nein, ich habe dich im Chat schon mehrfach herzlich zum Sex mit mir eingeladen. Erinnere dich da bitte genau.
Jaaa, jaaa... verdammt, ja. ~_~
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 17. Februar 2013, 14:42

Svambo hat geschrieben:Mal so als Anregung / Idee: Hast Du Lust, in deinem Maggi-Fix Thread die unterschiedlichen Steuergeräte zu erläutern (mit Codebeispiel)? Also Steuerung über Pad, Maus, Justifier, Super Scope ... Kam mir grad so in den Kopf, als du von der Maus schriebst.

Ansonsten: Weiter so. Find deine beiden Coding Threads immer wieder spannend.
DJ Svam-Bobo! Nice to see you again! :)
Danke für die warmen Worte! ;)

An derlei habe ich auch schon gedacht. Den Controller würde ich definitiv machen, das wird auch im Rahmen des normalen "Grundkurses" auftauchen. Die anderen Sachen würde ich auch machen, wobei ich mich da erst selbst reinfuchsen müsste, weil ich das (noch) nicht kann. Der Vorteil ist, dass die gesamte Sonderperipherie (also alles außer dem Controller) über dieselbe Schiene läuft (Register $4016/7), das heißt, ich muss dann nur noch einmal genau heraussuchen, wie die Daten in diese Register übertragen werden.

Das einzige Problem wird nur die Hardware zum Testen sein, da ich niemals-nicht einen Justifier oder SuperScope besessen habe, und gerade auch kein Mario Paint zur Hand habe, aber das wird wohl das Geringste aller Probleme sein. ;)

Falls du es eilig wegen der Maus hast:
Auf der Seite, die duoGS aufgetan hat, die ich auch schon unter den Development-Ressources gepostet habe, gibt es ein Maus-Tutorial; das habe ich mir bisher nur flüchtig angesehen. Wenn du demnächst einen direkten Anwendungsbereich hast, kann dir das vielleicht schon weiterhelfen.
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 20. Februar 2013, 17:57

Kurze Info vorab: Ich habe bei dem letzten langen Beitrag, in dem die Grafikdemo erstellt wurde, jetzt auch das komplette Projekt gezippt eingefügt.
Kategorie: SNES-Programmierung



Projekttage: DMA
Liebe Schüler! Es sind Projekttage!
Wir werden im Kommenden einiges an unserer Grafikdemo herumschrauben. Dabei werden wir viel Neues sehen, viel Spaß haben, viel neue Erkenntnisse machen… nur Private wird wohl dabei draufgehen.
Zunächst betrachten wir noch einmal den wichtigen Teil (d. h. nach dem Aufruf des InitSNES bis zum forever-Loop) des Programms. Als Auffrischung.

Code: Alles auswählen

 	LDA #INIDSP_FORCE_BLANK.b
	STA INIDSP.w
	LDA #BGMODE_MODE_1.b
	STA BGMODE.w
	LDA #T_BG1_ENABLE.b
	STA TMAIN.w
	LDA #$10.b
	STA BG1SC.w
	STZ BG12NBA.w

	STZ CGADD.w

	LDX #$0000.w
	TransferCycle0:
	LDA Palette.w, X
	STA CGDATA.w
	INX
	LDA Palette.w, X
	STA CGDATA.w
	INX
	CPX #$0080.w
	BNE TransferCycle0

	LDA #VMAIN_INCREMENT_MODE.b
	STA VMAIN.w

	LDX #$0000.w
	STX VMADDL.w
	TransferCycle1:
	LDY Tileset.w, X
	STY VMDATAL.w
	INX
	INX
	CPX #$0200.w
	BNE TransferCycle1

	LDX #$1000.w
	STX VMADDL.w
	LDX #$0000.w
	TransferCycle2:
	LDY Tilemap.w, X
	STY VMDATAL.w
	INX
	INX
	CPX #$0800.w
	BNE TransferCycle2

	LDA #$0F.b
	STA INIDSP.w
 
Im Ganzen ist das:
  • Erster Absatz: Bildschirmeinstellungen; Forced Blank, BG-Mode, BG1 an und „wo finde ich die Daten für BG1 im VRAM?“.
  • „STZ CGADD“: Vorbereitung für die Farbübertragungsschleife.
  • Dritter Absatz: Farbübertragungsschleife.
  • „VMAIN“-Absatz: Vorbereitung für die Tileset-Übertragungsschleife.
  • Fünfter Absatz: Tileset-Übertragungsschleife.
  • Sechster Absatz: Tilemap-Übertragungsschleife.
  • Siebter Absatz: Anschalten des Bildschirms
Wir ersetzen jetzt die Schleifen mit DMA-Befehlen. Ich weise vorab darauf hin, dass das bei geringen Datenmengen keinen Sinn macht, einen DMA zu machen. Folglich ist das in diesem Fall für die Farben Unsinn, einen DMA zu machen. Der Prozessor hat mit der Einrichtung mehr zu tun, als er durch die Durchführung spart. Mit Kanonen auf Spatzen. You know?
Trotzdem machen wir das jetzt hier. Erstens bringen wir mit dem Programm den Prozessor nicht an die Grenzen seiner Belastbarkeit, insofern können wir uns diesen Schlendrian leisten, und zweitens haben wirs dann einheitlich, und das ist für Tutorials immer chic.
Wir löschen den dritten Absatz und setzen dort folgendes ein (um nicht immer wieder dasselbe zweimal zu schreiben, setze ich jetzt die Adressen der Register immer als Kommentar hinter die Bezeichnung aus d4s‘ define-Datei):

Code: Alles auswählen

STZ DMAP0.w ; $4300
LDA #$22.b ; für $2122
STA DMADEST0.w ; $4301
LDX #Palette.w
STX DMASRC0L.w ; $4302 (indirekt auchDMASRC0H/ $4303)
LDA #:Palette.b
STA DMASRC0B.w ; $4304
LDX #$0080.w
STX DMALEN0L.w ; $4305 (indirekt auch DMALEN0H / $4306)
LDA #DMA_CHANNEL0_ENABLE.b ; #$01.b
STA MDMAEN.w ; $420B
Sprechen wir das im Einzelnen durch:
Da du nur immer ein Register beschreiben willst, speichern wir null in DMAP0. Das Ziel des DMAs ist Register $2122 (CGDATA), folglich schreiben wir in DMADEST0 #$22 (wir erinnern uns: Wir können immer nur auf Register „zielen“, die mit $21xx anfangen).
Im nächsten Schritt wird die Source-Adresse gesetzt. Dabei schreiben wir beim Laden-Befehl zunächst „#Palette“. Zur Erklärung: „Palette“ ist ein Label und wird von WLA-DX durch eine Zahl setzt. Das ist dasselbe wie die Labels, die wir benutzt haben, als wir die Schleife geschrieben haben. Dort schrieben wir ja zum Beispiel auch „BNE TransferCycle0“. WLA DX, wie bereits schon einmal beschrieben, schaut, vor welchem Befehl „TransferCycle0:“ steht, nimmt die Adresse von diesem Befehl und setzt sie dann anstelle des Labels überall im Progamm ein. Wenn TransferCycle0 irgendwo ganz am Anfang einer Bank steht, steht da dann „$8000“ (weil, in diesem Fall, Bankadressen ab $8000 erst anfangen. Frag nich. Isso. - Oder doch, frag. Aber später.).
Wir wollen jetzt an dieser Stelle nicht den Inhalt einer Adresse laden, sondern die Adresse selbst. Würden wir mit Adressen statt mit Labels arbeiten, würden wir ja auch schreiben:

Code: Alles auswählen

LDX #$8000.w
STX DMASRC0L.w
Ein einfaches „LDX $8000“ würde den Inhalt von Register $8000 und $8001 laden (die ersten beiden Bytes einer Palette, in diesem Fall) und die im DMA-Source-Register speichern. Wenn man den DMA jetzt starten würde, würde er diese Paletten-Bytes als Startadresse für seinen Transfer interpretieren. Ich denke, es ist klar, dass das nichts werden wird.
Also. Folglich schreibt man „#$8000“ statt „$8000“. Und logischerweise schreibt man auch „#Palette“ statt „Palette“.
Beim nächsten Ladebefehl ist das eine besondere Besonderheit von WLA DX. „#:Palette“ nimmt nicht die Adresse innerhalb einer Bank, sondern die Bank-Nummer. In diesem Fall ergibt „#:Palette“ übrigens „#$00“, da wie die Grafikdaten ebenfalls auf Bank $00 gelassen haben.
Abschließend setzen wir die Menge der zu übertragenden Daten auf #$0080 und aktivieren DMA-Channel 0 via Register MDMAEN ($420B).

Okay. Das war die Erklärung. Und jetzt macht ihr den DMA anstelle des TransferLoop1 allein, während ich rumgehe und die Hausaufgaben kontrolliere.
Aber, bevor ihr anfangt, löse ich mal drei Hinweismünzen ein:
  • HINWEIS 1: Du musst den Wert DMAP_2_REG_WRITE_ONCE (#$01) in DMAP ($4300) speichern.
  • HINWEIS 2: Lösch nicht die Befehle, wo in VMADDL gespeichert wird.
  • HINWEIS 3: Falls du es vergessen haben solltest: VMDATAL ist $2118.
So. Die Lösung gibt es hier:
versteckter Inhalt:

Code: Alles auswählen

.INCLUDE "header.inc"
.INCLUDE "InitSNES.asm"
.INCLUDE "define.asm"

.BANK 0 SLOT 0
.ORG 0
.SECTION "MainCode"

Start:
    InitSNES

	LDA #INIDSP_FORCE_BLANK.b
	STA INIDSP.w
	LDA #BGMODE_MODE_1.b
	STA BGMODE.w
	LDA #T_BG1_ENABLE.b
	STA TMAIN.w
	LDA #$10.b
	STA BG1SC.w
	STZ BG12NBA.w

	STZ CGADD.w

	STZ DMAP0.w
	LDA #$22.b
	STA DMADEST0.w
	LDX #Palette.w
	STX DMASRC0L.w
	LDA #:Palette.b
	STA DMASRC0B.w
	LDX #$0080.w
	STX DMALEN0L.w
	LDA #DMA_CHANNEL0_ENABLE.b
	STA MDMAEN.w

	LDA #VMAIN_INCREMENT_MODE.b
	STA VMAIN.w
	LDX #$0000.w
	STX VMADDL.w

	LDA #DMAP_2_REG_WRITE_ONCE.b
	STA DMAP0.w
	LDA #$18.b
	STA DMADEST0.w
	LDX #Tileset.w
	STX DMASRC0L.w
	LDA #:Tileset.b
	STA DMASRC0B.w
	LDX #$0200.w
	STX DMALEN0L.w
	LDA #DMA_CHANNEL0_ENABLE.b
	STA MDMAEN.w

	LDX #$1000.w
	STX VMADDL.w

	LDA #DMAP_2_REG_WRITE_ONCE.b
	STA DMAP0.w
	LDA #$18.b
	STA DMADEST0.w
	LDX #Tilemap.w
	STX DMASRC0L.w
	LDA #:Tilemap.b
	STA DMASRC0B.w
	LDX #$0800.w
	STX DMALEN0L.w
	LDA #DMA_CHANNEL0_ENABLE.b
	STA MDMAEN.w

	LDA #$0F.b
	STA INIDSP.w

forever:
    jmp forever

.ENDS

.SECTION "Bilddaten"

Tileset:
.INCBIN "Tileset1.pic"

Palette:
.INCBIN "Tileset1.clr"

Tilemap:
.INCBIN "Tilemap.bin"
.ENDS

 
So. Kurz vor Schluss verschlanken wir das Ding nämlich noch einmal. Als Erstes löscht ihr den ersten „LDA #DMA_CHANNEL0_ENABLE.b“ und „STA MDMAEN.w“.
Dann geht ihr zum zweiten und DMA-Befehl und weist ihm Channel 1 zu. (Die Adressnamen sind glücklicher- wie logischerweise dieselben, nur die Nullen müssen in Einsen geändert werden).
Als letztes ersetzt ihr das vormals zweite, jetzt erste „LDA #DMA_CHANNEL0_ENABLE.b“ (= „LDA #$01.b“) mit „LDA #DMA_CHANNEL0_ENABLE|DMA_CHANNEL1_ENABLE.b“ (= „LDA #$03.b“).
Jetzt werden die ersten beiden DMAs direkt nacheinander ausgeführt.
Den dritten können wir leider nicht mit in die Runde aufnehmen, weil wir nach dem zweiten und vor dem dritten DMA noch die VRAM-Zieladresse ändern müssen.

Diese letzte Fassung findet ihr in der Zip. Viel Spaß noch mit euren Projekttagen, liebe Klasse!
Dateianhänge
Grafik-Demo.rar
(117.15 KiB) 1075-mal heruntergeladen
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 22. Februar 2013, 07:55

Kategorie: SNES-Programmierung



Projekttage: Subroutinen und Stack
Liebe Schüler! Es sind immer noch Projekttage!
Heute machen wir einen kleinen theoretischen Kunststich in Aspik, während wir ein wenig über Subroutinen lernen. Wir gliedern es in drei Teile: Die Trockenübung, die Anwendung, die Erklärung. Die geneigten Schüler können dann früher oder später abspringen, sobald ihnen das alles bekannt ist.

Die Trockenübung:

Code: Alles auswählen

DasHauptProgramm:
LDA #$08.b
JSR DieSubroutine
STA $1000.w
(…)

DieSubroutine:
INC a
INC a
INC a
INC a
RTS
Erklärung des Ablaufs und der einzelnen Befehle:
Es wird der Wert #$08 geladen. Dann erfolgt ein Jump zur Subroutine (JSR), die vielsagend „DieSubroutine“ heißt. Dort wird der Inhalt von A viermal inkrementiert, dann wird erfolgt der Return from Subroutine (RTS) und es geht im Hauptprogramm da weiter, wo wir aufgehört haben. In diesem Falle wird A dann in die Adresse $1000 gespeichert (das ist RAM).

Wir machen noch eine zweite Variante der Trockenübung:

Code: Alles auswählen

DasHauptProgramm:
LDA #$08.b
JSL DieSubroutine
STA $1000.w
(…)

DieSubroutine:
INC a
INC a
INC a
INC a
RTL
Was ist hier der Unterschied? Wir verwenden JSL statt JSR und RTL statt RTS. Das „L“ in beiden Begriffen steht für „Long“, und hier geht es darum, dass Subroutinen, die sich auf einer anderen Bank befinden, nur mit den Long-Befehlen aufrufen lassen. Dazu später mehr.

Es folgt die Direktanwendung in unserer Beispieldatei. Wir wollen jetzt den kompletten siebten Absatz, das ist der Tilemap-DMA, in eine Subroutine auslagern. Diese Subroutine nennen wir „TilemapDMA“, und wir definieren sie unter dem Forever-Loop. Benutze keine Long-Befehle.

Die Lösung:
versteckter Inhalt:

Code: Alles auswählen

 .INCLUDE "header.inc"
.INCLUDE "InitSNES.asm"
.INCLUDE "define.asm"

.BANK 0 SLOT 0
.ORG 0
.SECTION "MainCode"

Start:
    InitSNES

	LDA #INIDSP_FORCE_BLANK.b
	STA INIDSP.w
	LDA #BGMODE_MODE_1.b
	STA BGMODE.w
	LDA #T_BG1_ENABLE.b
	STA TMAIN.w
	LDA #$10.b
	STA BG1SC.w
	STZ BG12NBA.w

	STZ CGADD.w

	STZ DMAP0.w
	LDA #$22.b
	STA DMADEST0.w
	LDX #Palette.w
	STX DMASRC0L.w
	LDA #:Palette.b
	STA DMASRC0B.w
	LDX #$0080.w
	STX DMALEN0L.w

	LDA #VMAIN_INCREMENT_MODE.b
	STA VMAIN.w
	LDX #$0000.w
	STX VMADDL.w

	LDA #DMAP_2_REG_WRITE_ONCE.b
	STA DMAP1.w
	LDA #$18.b
	STA DMADEST1.w
	LDX #Tileset.w
	STX DMASRC1L.w
	LDA #:Tileset.b
	STA DMASRC1B.w
	LDX #$0200.w
	STX DMALEN1L.w
	LDA #DMA_CHANNEL0_ENABLE|DMA_CHANNEL1_ENABLE.b
	STA MDMAEN.w

	LDX #$1000.w
	STX VMADDL.w

	JSR TilemapDMA

	LDA #$0F.b
	STA INIDSP.w

forever:
    jmp forever

TilemapDMA:
	LDA #DMAP_2_REG_WRITE_ONCE.b
	STA DMAP0.w
	LDA #$18.b
	STA DMADEST0.w
	LDX #Tilemap.w
	STX DMASRC0L.w
	LDA #:Tilemap.b
	STA DMASRC0B.w
	LDX #$0800.w
	STX DMALEN0L.w
	LDA #DMA_CHANNEL0_ENABLE.b
	STA MDMAEN.w
	RTS
.ENDS

.SECTION "Bilddaten"

Tileset:
.INCBIN "Tileset1.pic"

Palette:
.INCBIN "Tileset1.clr"

Tilemap:
.INCBIN "Tilemap.bin"
.ENDS
Die Erklärung: (Darauf haben wir alle schon gewartet)

Als wir unsere Daten noch mit der Schleife übertragen haben, hatten wir einen „Branch“-Befehl (also eine Abzweigung), in diesem Fall Springen wir. Es gibt ein paar Jump-Befehle und einen ganzen Stapel Branch-Befehle. Wann benutzt man was?

Zunächst muss man wissen:
Wird ein Jump-Befehl ausgeführt, notiert sich der Prozessor zuvor, wo er „stehengeblieben“ ist, als er jumpen sollte. Wenn er an ein „RTS“ oder „RTL“ kommt, bedeutet das für ihn, dass er mit der Ausführung der Subroutine fertig ist, und wieder an den Ausgangspunkt zurückkehrt, also schaut er in seine „Notizen“ und springt an die Stelle zurück. Beim Branchen ist das nicht der Fall.

Wo schreibt er sich das auf? Er legt das im Stack ab. Der Stack (englisch für Stapel) ist ein Bestimmter gekennzeichneter Bereich im RAM, der vom Prozessor als eigener Notizblock genutzt wird. Wenn du Lust hast, kannst du in die InitSNES.asm schauen, dort findest du ganz weit oben den Text:

Code: Alles auswählen

  LDX #$1FFF	;Setup the stack
  TXS			;Transfer Index X to Stack Pointer Register
Dabei wird der Anfang vom Stack auf $1FFF gesetzt. Das heißt: Wenn du einen JSR-Befehl benutzt, also ein Subroutinenaufruf auf derselben Bank durchgeführt wird, dann wird die Rückkehradresse in Register $1FFF und $1FFE abgelegt. Jaaa, genau, er zählt rück-, bzw. abwärts. Der Prozessor hat einen Pointer (also einen (An)zeiger), der ihm verrät, wo der „Pegelstand“ des Stacks sich beläuft. Welches Register er als nächstes Beschreiben kann. Dementsprechend kann er dann zurückrechnen, wo er die einzelnen Werte für seine Rucksprungadresse findet.
Obacht: Die RAM-Register, die für den Stack benutzt werden, sind in keinster Weise besonders geschützt. Das heißt: Wenn du eine Subroutine aufrufst und dabei deine Rücksprungadresse in $1FFF und aus Jux dasselbe Register als Zwischenspeicher für Rechenergebnisse nutzt... dann wird dein Programm ab dem RTS sicherlich sehr faszinierende Resultate zu Tage fördern. Allerdings rechne ich nicht damit, dass es eine besondere Augenweide sein wird.
Folglich nimmt man für soetwas eigentlich immer einen Recht abgeschotteten Bereich. Ich denke, mit $100 Byte wäre der Stack gut bedient (mich wundert, weshalb er hier mit #$1FFF bemessen wird). Einfache Empfehlung: Setz ihn auf #$01FF. Dann werden $0100 bis $01FF für Stack, du hast ab $0200 absolut freie Hand, und außerdem kommt er nicht mit den Direct Page Registern ins Gehege (Register $00FF kannst du z. B. auch einfach als $FF ansprechen).

Gut. Soweit, so, äh…

Also:
Wann verwendet man nun Branch-Befehle, wann macht man Jump to Subroutine? Exakt in Abhängigkeit davon, ob man an den Ausgangspunkt zurückkehren möchte oder nicht.
Subroutinen haben den Vorteil, dass du sie von überall aufrufen kannst und mit Hilfe des „RTS“/„RTL“ den Prozessor wieder dahin zurückschicken kannst, wo er herkam. Bei Branchbefehlen kannst du am Ende nur wiederum branchen, und das hieße: Egal, von wo du kommst, durch diese hohle Gasse musst du gehen, und den Branch am Ende musst du nehmen. Da dann mit unterschiedlichen Branchbefehlen in Abhängigkeit von Werten in bestimmmten Registern zu hantieren wäre vergleichsweise kompliziert.
Andererseits würdest du dir deinen schönen, schönen Stack mit Rücksprungadressen zukleistern, wenn du diese Rücksprungadressen auch nie nutzt.

Abschließend die Frage:
Wenn du eine Subroutine so aufbereitet hast, dass du sie von überall aus dem Programm heraus aufrufen kannst, was steht dann am Ende der Subroutine? Und wie rufst du sie auf, wenn du dich auf derselben Bank befindest?

Lösung 1:
versteckter Inhalt:
Am Ende steht ein „RTL“
Bei „RTS“ werden nur zwei Byte vom Stack als Rückkehradresse genommen, nämlich für die genaue Adresse auf der Bank, auf der man sich gerade selbst befindet. Bei „RTL“ werden drei Byte vom Stack gezogen, der dritte Byte enthält die Nummer der Bank, auf die man zurückkehren will.
Lösung 2:
versteckter Inhalt:
Da am Ende ein „RTL“ steht, musst du auch auf derselben Bank mit „JSL“ arbeiten, damit eine 3-Byte-Adresse für den Rücksturz entsteht. Andernfalls wird das Programm beim „RTL“ entweder in die Binsen gehen oder crashen.
So. Und hier haste auch noch den Sourcecode von heute.
Dateianhänge
subroutine.zip
(118.36 KiB) 1064-mal heruntergeladen
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Benutzeravatar
lytron
Code Bro
Code Bro
Beiträge: 2664
Registriert: 12. August 2012, 20:18
+Positive Tradingpoints+: 12 von 12
Wohnort: ハノーファー区
Kontaktdaten:

Re: Maggi-Fix für SNES Programmierung

Beitrag von lytron » 22. Februar 2013, 13:49

Kategorie: SNES-Programmierung


Projekttage: Stack-Befehle (Push/Pull) und CPU-Register
Morgen back ich, übermorgen (heim)brau ich,
heute erschlag ich eine Menge an ASM-Befehlen.

Erklärung von Push- und Pull-Befehlen in einem Satz: Man kann einiges an Registern direkt auf den Stack schmeißen oder von selbigem ziehen. Nachfolgend eine Liste, was alles geht.
  • PHA/PLA – Akkumulator auf den Stack / Stackwert in den Akkumulator
  • PHX/PLX – X auf den Stack / Stackwert in X
  • PHY/PLY – Y auf den Stack / Stackwert in Y
  • PHB/PLB – Data Bank Register auf den Stack / Stackwert in das Data Bank Register
  • PHD/PLD – Direct Page Register auf den Stack / Stackwert in das Direct Page Register
  • PHK/PLK – Program Bank Register auf den Stack / Stackwert in das Program Bank Register
  • PHP/PLP – Status Register auf den Stack / Stackwert in das Status Register
  • PEA – „Push Effective Absolute Address“
  • PEI – „Push Effective Indirect Adress“
  • PER – „Push Effective PC Relative Indirect Address“
Die ersten drei Befehle sollten klar sein.
Für die nächsten drei Befehle nenne ich die Bedeutung der Register (beim vierten Weise ich einfach nur noch einmal darauf hin, dass das das Flag-Register ist). Es handelt sich dabei um Register, in denen „Einstellungen“ vorgenommen werden.
Das Data Bank Register enthält einen Byte. Dieser Byte sagt, wenn man 16-Bit-Adressen benutzt, welche Bank gemeint ist. Das heißt: Wenn du irgendwann einmal so viel gecodet hast, dass du eine kompette Bank voll hast und auf die nächste wechselst, solltest du den Byte im Data Bank Register der aktuellen Bank anpassen. Denn sonst springst du mit den Labels (die dann in 16-Bit-Registeradressen umgewandelt werden) wieder auf die alte Bank zurück.
Übrigens, fällt mir ein, wird hier auch schön ersichtlich, weshalb der Code einer Bank erst ab Register $8000 adressiert wird: Die anderen Adressen dadrunter sind ja anderweitig reserviert ($0 - $1FFF RAM, $2000-$7FFF für Memory-Mapped-Input/Output-Register).

Das Direct Page Register setzt fest, wo die Direct Page Register sitzen. Direct-Page-Register meint: Die Register, die man mit 8-Bit-Adressen anspricht. Also $00 bis $FF. Der Inhalt des Direct Page Register ist 16-Bit groß und wird normalerweise auf $0000 gesetzt, somit sind die Register $0000 bis $00FF ansprechbar als $00 bis $FF. Setzt du $0021 in Direct Page Register, ist $00 = $0021, $01 = $0022 usw.
Das heißt, wenn es dich nervös macht, dass dein Stack potenziell deine Direct Page Register beschreiben könnte, kannst du mit diesem Wissen das jetzt ändern.

Das Program Bank Register ist… ach, fangen wir anders an.
Es gibt einen Counter. Der sagt dem Prozessor, was der nächste Byte ist, die er zu bearbeiten hat. Das Ding wird zum Beispiel durch den Reset-Vektor ganz am Anfang der ROM gesetzt (du erinnerst dich? Das Ding im SNES-Header, das auf das Label „Start“ zeigt).
Du erinnerst dich an die Geschichte mit den Subroutinen vom letzten Mal, wo ich sagte, der Prozessor macht sich eine Notiz auf dem Stack, wo er hin muss, wenn die Subroutine fertig ist? Die „Notiz“ ist, um genau zu sein, der Inhalt dieses Counters. Er schubst den auf den Stack und nimmt die Adresse der Subroutine in diesen Counter; wenn er fertig ist, lädt er einfach den alten Wert vom Stack in besagten Counter.
Das Program Bank Register wiederum enthält wiederum nicht den gesamten Counter (das wären 24 Bit für eine Long-Adresse), sondern nur die Bank. Normalerweise lässt man da den Prozessor sein eigenes Ding machen. Aber wenn man mal da herummanipulieren will, kann man das mit diesen Befehlen machen.

So. Bevor ich mit den letzten drei Befehlen weitermache, noch ein kleiner Auszug aus unserer InitSNES.asm. Ich möchte nochmal zeigen, wo diese Register oben in der Initialisierung „bedient“ werden:

Code: Alles auswählen

InitializeSNES:
PHK	;set Data Bank = Program Bank
PLB

LDA #$0000	;set Direct Page = $0000
TCD	;Transfer Accumulator to Direct Register
Wie der Kommentar es bereits treffend beschreibt, wird die Data Bank der Program Bank gleichgesetzt, indem der Wert des Program Bank Registers auf den Stack geschmissen wird und sofort in das Data Bank Register gezogen wird.
Und damit kommen wir zu einem wichtigen Teil (der ist so wichtig, dass ich ihn am besten hier gelegentlich noch einmal wiederholen sollte): Push kopiert. Wenn du aus einem beliebigen Register auf den Stack pushst, ist der Ausgangswert immer noch im Ausgangsregister vorhanden. Du hast nur eine „Sicherheitskopie“ auf dem Stack abgelegt. Ebenso ist der Wert, den du vom Stack ziehst, immer noch da (bis er überschrieben wird), aber an den ranzukommen, ist ein bisschen mühselig (man müsste erst den Stack Pointer in den Akkumulator (Befehl TSC) oder das Register X (Befehl TSX) laden, die Adresse anpassen und dann laden).

Der zweite Teil ist: Es wird #$0000 in den Akkumulator geladen und dann mit TCD in das Direct Page Register übertragen. T steht für Transfer, D ist das Kürzel des Direct Page Register, C ist die Bezeichnung für den 16-Bit-Akkumulator. A ist eigentlich die Abkürzung für den 8-Bit-Akkumulator, B steht für den „ausgeblendeten“ Teil des Akkumulators (also die „versteckten“ 8 Bit), C ist der „große“ Akkumulator. Sollte man mal gehört haben, wobei dieses Wissen nur unregelmäßig von Nöten ist (zum Beispiel beim Befehl XBA, der die Inhalte von A und B austauscht).

Die letzten drei Befehle sind schon was für den Fortgeschrittenen-Bereich:

Mit PEA und einem nachfolgenden 16-Bit-Wert schmeißt man besagten Wert auf den Stack. Da es „Push Effective Absolute Address“ heißt, impliziert das schon, dass dieser Befehl dazu genutzt wird, Adressen auf den Stack zu schmeißen.

Mit PEI und einer nachfolgenden Direct-Page-Adresse wird der Inhalt des angegebenen DP-Registers (und dessen Nachbarn) auf den Stack gehauen. Beispiel: $1A enthält $80, $1B enthält $00. Ein „PEI $1A“ und ein anschließendes „RTS“ lassen einen zu $8000, also den Bankanfang zurückspringen.
Wie man sieht, wir hier der High Byte zuerst genannt. Aber das kann einem egal sein, weil man diesen Befehl selten nutzen wird.
Ach ja, übrigens, beim Push bleibt der Wert in den ursprünglichen Registern erhalten. Alle diese Dinge musst du nicht behalten, wenn du lieber mit dem Game Maker Spiele machen möchtest.

Mit PER (Push Effective PC Relative Indirect Address) kann man eine Adresse auf den Stack werfen, die aus einem Wert, den man mit dem Befehl übergibt, und dem aktuellen Programmcounter-Wert besteht. Mehr will ich mich damit nicht befassen, weil das definitiv zu sehr Special Case ist; wer Interesse hat, kann es im Handbuch nachlesen, Seite 374. Dort steht auch etwas über den Verwendungszweck:
this instruction is helpful in writing self-relocatable code in which an address within
the program (typically of a data area) must be accessed
Quaaasiii… Code auf dem Rücken einer wandernden Schildkröte. Soetwas ist beim SNES theoretisch machbar (wir haben schließlich den RAM), allerdings kann ich mir nicht vorstellen, dass soetwas jemals nötig ist.

Soweit dazu. Jetzt mal Anwendungsbeispiele:

Code: Alles auswählen

Houdini_Subroutini:
PHP
SEP #$20.b
REP #$10.b
(…)
PLP
RTS
Das macht man, wenn man nicht weiß, wie groß die A, X und Y sind, wenn die Subroutine aufgerufen wird. Es wird eine „Sicherheitskopie“ der Flags auf dem Stack abgelegt, danach kann man die Flags nach Belieben anpassen. Am Ende zieht man vor dem RTS noch den alten Status in das Flag-Register, und alles geht wieder seinen gewohnten Gang.

Code: Alles auswählen

LDX #$0000.w
Aussen:
PHX
LDX #$0000.w
Innen:
(…)
INX
CPX #$0004.w
BNE Innen
(…)
PLX
INX
INX
INX
INX
INX
INX
INX
INX
CPX #$0040.w
BNE Aussen
Der Lehrpunkt ist: Hier benutze ich für zwei ineinander verschachtelte Schleifen dasselbe Register als Counter. Zunächst setze ich X auf 0, dann beginnt „Aussen“: X wird auf den Stack geschoben, X noch einmal als Counter für „Innen“ geleert. „Innen“ wird viermal durchlaufen. Nach Abschluss von „Innen“ wird bei „Aussen“ der alte Counterwert wieder vom Stack gezogen, um acht erhöht, und mit $40 (als Endwert der „Aussen“-Schleife) verglichen.

Zum Abschluss, ein Mini, der super nützlich ist:

Code: Alles auswählen

PHX
PHY
PLX
PLY
Es gibt keine Möglichkeit, X- und Y-Register noch schneller zu tauschen. 
pantalytron: ルトロンはくそのディスアセンブラだよ!
Perikles hat geschrieben:Man muss sich das mal reinziehen: die Idee ist scheiße, die theoretische Ausarbeitung ist scheiße, die praktische Umsetzung ist scheiße und der so entstehende Anspruch noch beschissener.

Antworten