- For making cartridges of your Super NES games, see Reproduction.
Not only do you have to touch $4300-$4306 and $420b, but if you need to touch other registers depending on what you're trying to do:
$2115 to $2117 for VRAM
$2102 and $2103 for OAM
$2121 for CGRAM
$2181 to $2183 for WRAM
I remember somebody saying "but you only need to make a routine that writes to these registers ONCE" but how do I make a routine that writes to a bunch of registers without having to pass a ton of variables, which would require as much code as I would need to write to said registers in the first place?
I know there are some commonly used arrangements such as:
-Writing bytes to $2104
-Writing bytes to $2122
-Writing bytes to $2180
-Writing words to $2118, incrementing by 1 when $2119 is written
-Writing bytes to $2118, incrementing by 1 when $2118 is written
-Writing bytes to $2119, incrementing by 1 when $2119 is written
-Writing words to $2118, incrementing by 32 when $2119 is written
Actually there are a TON of commonly used arrangements, and even more potentially useful arrangements, but I know for fact that most combinations don't make any sense, so there must be a way of pre-baking in patterns that make sense.
There's also a dilema of immediate parameters vs variable parameters. Immediate parameters you can store in an array, but variable parameters you have to either hardcode or use some kind of parameter passing method.
In commercial games I've seen source code for, most use a combination of macros for some DMA routines, while others are actually jsl-able based on what's passed on the stack, or referenced directly by a small set of variables in DP/RAM that are dedicated solely for this purpose. SNES has a tolerably large amount of RAM, so this isn't very wasteful.
The advice "but you only need to make a routine that writes to these registers ONCE" is almost certainly referring to the fact that you can write a general-purpose routine that handles $4300-4306 + $420b for you, while the rest is up to you.
Some games I believe have some DMA that's done inside of NMI, where some of the "source" values come from DP/RAM variables, while others are hard-coded (e.g. your graphics transfer routine uses a static length of data so you don't need to tweak the DMA length register every time; palette update DMA, etc.) based on a global variable that controls whether or not the DMA should be done (so that it isn't being done every single frame). But that's more about "saving time" (cycle-wise) than "how many registers I have to write to".
if the concern is truly how many registers you need to write to (read: the quantity of), then I don't think anything can be done about that. You're going to have to write to said registers to accomplish DMA in the fashion you want regardless. It's how the system was designed. How can this be fought aside from inventing a time machine and going back in time to Nintendo Co. Ltd and demanding they design the system differently? Hmm, feeling of deja vu...
Code: Select all
.macro FUNC a0, a1, a2, a3 lda #a0 sta arg0 ldy #a1 ldx #a2 lda #a3 jsr func .endmacro ; calling becomes one line in your code FUNC 1, 2, 3, 4
Code: Select all
.macro FUNC a0, a1, a2, a3 jsr func .byte a0, a1, a2, a3 .endmacro
One thing that's hard to deal with in macros is immediate values vs. arguments that should come from memory locations, current registers, etc. So you can't treat it like a C function where any result goes into an argument, but you can make variations of the macro to take different argument types if you need that.
Code: Select all
;; ; Copies data to the S-PPU using DMA channel 0. ; @param X source address ; @param DBR source bank ; @param Y number of bytes to copy ; @param A 15-8: destination PPU register; 7-0: DMA mode .proc ppu_copy php setaxy16 sta $804300 stx $804302 sty $804305 seta8 phb pla sta $804304 lda #%00000001 sta $80420B plp rtl .endproc