HDMA-Tutorial für Neulinge

geschrieben am 03.10.2013 13:53:37
( Link )
Das ist mein HDMA-Tutorial für Neulinge (also HDMA Neulinge )
Anmerkung:Sobald ein besseres HDMA Tutorial existiert, soll dieser Thread Archiviert werden (nicht in den Müll, weil es ja etwas brachte ).
Zu allererst: Mein Wissen bezieht sich z.T. vom ASM Workshop von SMWC und von einen HDMA Tutorial.

Startkapitel: Was ist HDMA?
HDMA ist eine Abkürzung für "Horizontal Direct Memory Access" also "Horizontales Speicherdirektzugriff". Aber warum heißt es "HDMA" also "Horizontal Direct Memory Access"? Es ist eine besondere form von DMA* (logischerweise eine Abkürzung für "Speicherdirektzugriff"). Sie bezieht sich auf die "Scanlines"* des Bildschirms.

*DMA: DMA existiert in vielen (oder allen) Computersysteme (wie das SNES). Es wird zur schnellen Datenübertragung genutzt (ein Zyklus pro Byte) und kann sogar nutzvoller sein als die Befehlen "MVN" und "MVP" (die Befehle als auch DMA übertragen einen ganzen Block vom Speicher aber auf unterschiedlicher weise). Dabei gibt es eine kleine Sache:
Es gibt drei "Busse" (einen Datenbus, zwei Adressenbusse). Von den Adressenbussen gibt noch eine Sache: Der eine Bus (wir nennen es Bus A) besitzt die meisten RAM und ROM Adressen, während der andere (Bus B) die Hardware besitzt. Dabei könnt ihr Daten von Bus A zu Bus B übertragen und. Aber besonders wichtig ist diese Sache: Es gibt nur 8 (H)DMA Channels und beide DMA-Arten nutzen die gleichen Channels, d.h. wenn Channel 3 durch DMA vergeben ist, kann man es weder durch normale DMA noch durch HDMA. Nebenbei besitzt HDMA Vorrang.

*Scanline: Eine Scanline ist eine Line auf einen Bildschirm, die ein Pixel hoch ist und sie geht von links nach rechts durch den gesamten Bildschirm. Der SNES kann 224 Scanlines (oder 14 16x16 Blöcker) anzeigen.

Kapitel 1: DMA Befehle und Nutzung
DMA wird für große Datenübertragungen genutzt. Beispiele: RPG's VWF Dialouges-Patch, VWF Cutzscene-Tool, usw.
Und bevor ihr überhaupt mit HDMA anfängt, müsst ihr auch wissen, was die Adressen machen (wichtig, weil die SMW Ram-Map von SMWC sie nicht zeigt, weil es zum SNES gehört), deshalb downloadet ihr euch diese Datei. Downloadet auch einen anderen Editor wie Notepad++, falls die Linebreaks nicht auf den normalen Windows-Editor angezeigt wird!

DMA ist ein bisschen kompliziert zu verstehen, deshalb nutze ich Regs.txt, um einige Adressen zu erklären. Nebenbei wird hier meistens von $43x0-$43xF gesprochen. Das x ist der (H)DMA Channel, den man momentan benutzt. Bei x darf man aber auch nur eine Zahl einsetzen, die nicht gebraucht wird! In SMW kann man 3,4,5 (und auch 6?) benutzen!
Nun kommen wir zur ersten Adresse: $43x0. Sie kontrolliert die Nutzung von DMA eines Channels (nicht umsonst heißt sie auch "DMA Control for Channel x"):
Zitat von Regs.txt:
da-ifttt

d = Transfer Direction. When clear, data will be read from the CPU
memory and written to the PPU register. When set, vice versa.

Contrary to previous belief, this bit DOES affect HDMA! Indirect
mode is more useful, it will read the table as normal and write
from Bus B to the Bus A address specified. Direct mode will work as
expected though, it will read counts from the table and try to
write the data values into the table.

a = HDMA Addressing Mode. When clear, the HDMA table contains the
data to transfar. When set, the HDMA table contains pointers to the
data. This bit does not affect DMA.

i = DMA Address Increment. When clear, the DMA address will be
incremented for each byte. When set, the DMA address will be
decremented. This bit does not affect HDMA.

f = DMA Fixed Transfer. When set, the DMA address will not be
adjusted. When clear, the address will be adjusted as specified by
bit 4. This bit does not affect HDMA.

ttt = Transfer Mode.
000 => 1 register write once (1 byte: p )
001 => 2 registers write once (2 bytes: p, p+1 )
010 => 1 register write twice (2 bytes: p, p )
011 => 2 registers write twice each (4 bytes: p, p, p+1, p+1)
100 => 4 registers write once (4 bytes: p, p+1, p+2, p+3)
101 => 2 registers write twice alternate (4 bytes: p, p+1, p, p+1)
110 => 1 register write twice (2 bytes: p, p )
111 => 2 registers write twice each (4 bytes: p, p, p+1, p+1)

The effect of writing this register during HDMA to the associated
channel is unknown. Most likely, the change takes effect for the
next HDMA transfer.

Was der PPU ist weiß ich nicht, aber hier ein Wikipedia-Link. Wenn ihr z.B. wollt, das der DMA vom PPU zum Hauptprozessor (CPU) auf dem HDMA Channel 1 übertragt, einfach das als Code benutzten:
Code
LDA.b #%10000000
STA $4310

Die nächste DMA Adresse ist $43x1 (DMA Destination Register for Channel x). Dies gibt an, von welcher Hardware-Adresse ($2100-$21ff) es gelesen oder geschrieben wird.
Wenn ihr z.B. wollt, dass von der Adresse $2100 gelesen oder geschrieben wird, einfach
Code
LDA #$00
STA $4351

hinschreiben.
Wenn ihr aber wollt, dass von der Adresse $210D gelesen (nicht geschrieben) wird, gibt es zwei Möglichkeiten. Ich empfehle für euch für das gezeigte, weil es vor allem beim vollständigen Code, Patz spart.
Code
REP #$20
LDA #$0D80
STA $4350
SEP #$20

Wie ihr sieht, ich habe ein "REP #$20" hinzugefügt. Habt ihr übrigens ein ASM Tutorial (wie das von RPG) gelesen? Wenn ja, fahrt fort. Es tut genau das gleiche wie:
Code
LDA #$80
STA $4350
LDA #$0D
STA $4351

Es braucht genau so viele Zyklen wie das obere aber man muss einige Zusatzschritte machen.
Nun kommen wir zur einer weitere Adresse... Moment! Es sind ja drei Adressen: $43x2, $43x3 und $43x4, die Quell-Adressen (low, high, and bank)
Sie geben an, aus welcher Adresse es geschrieben oder gelesen wird. Natürlich kann man bei einer ROM nichts schreiben, weil es ja auch "Read only Memory" heßt. Für $43x2 und $43x3 ist es zu empfehlen, dass man im 16-Bit-Modus ist (REP #$20), bei $43x4 gibt es einen Trick (wird später beim HDMA Code erklärt)
So, das sind (für uns) die wichtigsten Adressen für (H)DMA. Alles andere ist für uns unwichtig (und ich kann sie nicht so gut erklären ).

Kapitel 2: HDMA lernen
Nun, habt ihr alles bis hier hin gelesen? Gut, weiter geht's. Wie oben es stand, HDMA ist eine besondere form von DMA, weil es ja pro Scanline funktioniert. So ist es möglich, einen Farbverlauf-Hintergrund, Hitzewellen, Paralax-Scrolling usw. zu erstellen. Profis können sogar einen coolen Effekt machen wie hier:

Für die, die es nicht checkten:
Spoiler anzeigen
Der Hintergrund wechselt vom Mittags- bis zum Abendverlauf.

Hier ist ein Beispiel Code (nicht von mir):
Code
	REP #$20	; 16 bit A
LDA #$0000 ; $43X0 = 00
STA $4330 ; $43x1 = 00
LDA #LVL1BRIGHT ; get pointer to brightness table
STA $4332 ; store it to low and high byte pointer
PHK ; get bank
PLY ;
STY $4334 ; store to bank pointer byte
SEP #$20 ; 8 bit A
LDA #$08 ; Enable HDMA on channel 3
TSB $0D9F ;
RTS

LVL1BRIGHT:
db $0C,$0F
db $0C,$0E
db $0C,$0D
db $0C,$0C
db $0C,$0B
db $0C,$0A
db $0C,$09
db $0C,$08
db $0C,$07
db $0C,$06
db $0C,$05
db $0C,$04
db $0C,$03
db $0C,$02
db $0C,$01
db $0C,$00
db $00

Und das Resultat:


Und nun erkläre ich diesen Code:
- REP #$20
Das habe ich ober erklärt.
- LDA #$0000
Hier lädt man für den low und high Byte die Zal null.
- STA $4330
Weil man im 16-Bit-Modus ist, wird nicht nur zu $4330 gespeichert, sondern auch zu $4331.
Nebenbei, weil zu $4331 die Zahl null gespeichert wird, wird also die Adresse $2100 (Helligkeit) modifiziert.
- LDA #LVL1BRIGHT
- STA $4332

Ich habe keine Ahnung:
Zitat von Kaijyuu:
This tells xkas to take the location of LVL1BRIGHT and put that number after the LDA. This is the pointer to your table, so the HDMA knows where to pull the values from. If you were to open this part of the code in a hex editor, you'd see that the two byte number after LDA would correspond to the SNES address of your table.

Ich glaube, ich verstehe es. Dort wo "LVL1BRIGHT" steht, steht nun die Adresse wo die Tabelle ist. Und dies ist neben bei der Trick, den ich oben meinte.
- PHK
- PLY
- STY $4334

Ihr braucht "PHK : PLY" nicht zu verstehen. Es wird nur für das Bank-Byte benutzt. Ist zuverlässiger als "LDY #$xx : STY $4334" weil es auch mit asar funktioniert. Ihr wollt doch gar nicht, dass es von der falschen Stelle gelesen wird.
- SEP #$20
Zurück in den 8-Bit-Modus
- LDA #$08
- TSB $0D9F

Nochmal kann ich es nicht erklären:
Zitat von Kaijyuu:
$0D9F holds which HDMA channels are active. You should never STA to this number, or you can screw up things like the goal tape. TSB takes the bits set in A, and sets them in the target address. In this case it would set the fourth bit, and enable HDMA channel 3.

So, bis auf den Tabel habe ich alles erklärt (will ich auch nicht). Dabei möchte ich euch auch über einige Sachen anmerken:
Code
LVL1BRIGHT:
db $xx,$yy
...
db $00

Man ersetzt "xx" durch die Scanlines, die man haben will. Ihr dürft aber keine Zahl, die höher als $80 hinschreiben. yy ist der Wert, bei der so gesehen ein "STA" macht, den man haben will (schaut einfach in Regs.txt, welche Zahl man einsetzen kann).
"db $00" ist die Endung des HDMA-Codes. Vergisst man es, wird das Spiel den HDMA weiterführen, bis zum nächsten $00.

Auch ist es möglich, mehrere Channels zu mischen. Dies ist besonders beim Farbverlauf, wo man es nicht verzichten soll, wichtig. Aber wie erstellt man so einen Code? Wichtig ist, dass man zu $2132 schreibt, wenn einen Farbverlauf macht, und das macht man einfach mit
Code
	LDA #$3200	; $43X0 = 00 
STA $4330 ; $43x1 = 32

Hier sieht man, dass "LDA #$0000" zu "LDA #$3200" ausgetauscht wurde. Und weil $43x1 den low-Byte des Hardware-Registers von der es geladen werden soll, wird zur Adresse $2132 geschrieben (welches die Hintergrundfarbe ist). Noch ein Beispielcode nicht von mir:
Code
			; HDMA stuff
REP #$20 ; 16 bit A
LDA #$3200 ; $43X0 = 00
STA $4330 ; $43x1 = 32
LDA #LVL1RED ; get pointer to red color table
STA $4332 ; store it to low and high byte pointer
PHK ; get bank
PLY ;
STY $4334 ; store to bank pointer byte
LDA #$3200 ; $43X0 = 00
STA $4340 ; $43x1 = 32
LDA #LVL1GREEN ; get pointer to red color table
STA $4342 ; store it to low and high byte pointer
STY $4344 ; store to bank pointer byte
LDA #$3200 ; $43X0 = 00
STA $4350 ; $43x1 = 32
LDA #LVL1BLUE ; get pointer to red color table
STA $4352 ; store it to low and high byte pointer
STY $4354 ; store to bank pointer byte
SEP #$20 ; 8 bit A
LDA #$38 ; Enable HDMA on channels 3 4 and 5
TSB $0D9F ;
RTS ; return


LVL1RED:
db $4F,$21
db $04,$22
db $04,$24
db $04,$25
db $04,$27
db $04,$28
db $04,$2A
db $04,$2B
db $04,$2D
db $04,$2E
db $04,$30
db $00

LVL1GREEN:
db $4F,$50
db $04,$51
db $04,$52
db $04,$53
db $04,$54
db $04,$55
db $04,$56
db $04,$57
db $04,$58
db $04,$59
db $04,$5A
db $00

LVL1BLUE:
db $4F,$99
db $08,$9A
db $08,$9B
db $08,$9D
db $08,$9E
db $08,$9F
db $00

Anders als im vorherigen Code gibt es hier nun drei Tabellen, eine für rot, eine für grün und eine für blau. Auch werden hier mehr HDMA Channels benutzt. Auch sieht man, dass die "blaue" Tabelle kleiner ist, als die anderen zwei. Das ist, weil die Tabellen nicht gleich groß sein müssen und man deshalb ein bisschen Platz sparen kann.

Nebenbei ist es auch möglich, selbst Farben zu erstellen, die man in LM sieht, in dem man die dezimale Zahl durch 8 teilt, ins hexdezimale konvertiert und 20 (für rot), 40 (grün) oder 80 (blau) addiert. Das liegt am Format, wie die Farben im SNES gespeichert werden: BGRCCCCC. Beispiel: Die Zahl in LM ist die 88. Dividiert durch 8 ist 11, in hex B, es soll die Farbe Rot sein, addieren wir $20, das Ergebnis ist $2B.

PS: Einige erstellen HDMA-Codes damit.

Ich hoffe, dieses Tutorial ist verständlich genug und korrigiert bitte die Fehler.
Du kannst auch gerne zu mir MFG659 sagen (ich heiße übrigens in CreepTD wegen dem limitierten Platz wirklich MFG659)
Ich kann einige (ASM)-Codes fixen. <!-- s:) -->:)<!-- s:) -->