SNES dev newbie questions

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

SNES dev newbie questions

Post by juef »

Hey all,

I've just started fiddling around with SNES stuff, more specifically with the Wikibooks tutorial. I have some (probably very) basic questions for which I haven't found an answer on these boards (or byuu's); if I am supposed to find the answers by more research or by moving forward, please don't hesitate to me so. Here's a basic change the BG color example I will refer to in my questions:

Code: Select all

.include "Header.inc"
.include "Snes_Init.asm"

VBlank:
  RTI

.bank 0
.section "MainCode"

Start:
    Snes_Init

    ; Set the background color to green.
    sep     #$20        ; Set the A register to 8-bit.
    lda     #%10000000  ; Force VBlank by turning off the screen.
    sta     $2100
    lda     #%11100000  ; Load the low byte of the green color.
    sta     $2122
    lda     #%00000000  ; Load the high byte of the green color.
    sta     $2122
    lda     #%00001111  ; End VBlank, setting brightness to 15 (100%).
    sta     $2100

Forever:
    jmp Forever

.ends
  1. Is there a way to load both bytes into $2122 at once? It seems kinda strange to me to set the register to 8 bits, load 8 bits, then load 8 more bits, rather than doing all of this with just one lda/sta.
  2. Speaking of which... How come doing sta $2122 twice with a byte both times doesn't end up just writing the latest byte of the two? Is writing the low byte first, and the the high byte the "standard way" of setting a color value here?
  3. I tried copying and pasting the "Set the background [...]" block so that there's two of them, changing the color to red the second time. By moving the Forever label at the top of the first of these blocks, I expected alternating red and green as the background color, but I end up with a rather trippy effect involving red and black only. Am I supposed to do something between (or during) VBlanks?
Thanks a bunch for reading, and big thanks to all the passionnate and helpful people around here - I admire all you've done and are doing for the community and beyond! :)
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

juef wrote:

Code: Select all

    ; Set the background color to green.
    sep     #$20        ; Set the A register to 8-bit.
    lda     #%10000000  ; Force VBlank by turning off the screen.
    sta     $2100
    lda     #%11100000  ; Load the low byte of the green color.
    sta     $2122
    lda     #%00000000  ; Load the high byte of the green color.
    sta     $2122
    lda     #%00001111  ; End VBlank, setting brightness to 15 (100%).
    sta     $2100
Is there a way to load both bytes into $2122 at once? It seems kinda strange to me to set the register to 8 bits, load 8 bits, then load 8 more bits, rather than doing all of this with just one lda/sta.
No. $2122 is an 8-bit memory-mapped register for colour data, the data of which is 16-bits; each write to it is handled internally by the PPU unit on the system. There's no "direct" 16-bit equivalent. If you were to write a 16-bit value to $2122, the LSB would end up going into $2122, and the MSB would end up going into $2123 which is a completely different register (window mask setting).

Per my old SNES documentation:

Code: Select all

| w   |$2121  |Colour # (or pallete) selection register [CGADD]              |
|     |       |xxxxxxxx              x: Address (color #).                   |
|     |       |                                                              |
|     |       |                                                              |
| wd  |$2122  |Colour data register [CGDATA]                                 |
|     |       |xxxxxxxx              x: Value of colour.                     |
|     |       |                                                              |
|     |       |SNES colour is 15 bit; 5 bits for red, green, and blue. The   |
|     |       |order isn't RGB though: It's BGR (RGB reversed!).             |
I should note you aren't setting $2121 to the index/offset of the palette entry you wish to modify prior to doing it -- so I assume your init_snes routine is what's responsible. I have to assume you're modifying colour #0. Just an FYI.
juef wrote:Speaking of which... How come doing sta $2122 twice with a byte both times doesn't end up just writing the latest byte of the two? Is writing the low byte first, and the the high byte the "standard way" of setting a color value here?
Because it's an 8-bit memory-mapped register. Each write is handled by the PPU. You should probably find a copy of my old SNES documentation and review the SNES.1 file. Both bytes written via the STA statements are honoured by the PPU/used by VRAM.
juef wrote:I tried copying and pasting the "Set the background [...]" block so that there's two of them, changing the color to red the second time. By moving the Forever label at the top of the first of these blocks, I expected alternating red and green as the background color, but I end up with a rather trippy effect involving red and black only. Am I supposed to do something between (or during) VBlanks?
What's happening is that you're updating the screen "too fast" and not seeing what's going on (visually). This is further complicated by your desire to turn on/off VBlank.

You have two options, and I recommend the former:

1) Leave the screen on at all times/set brightness to 100% and do your screen updates (writes to $2122) inside of VBlank time (NMI). You can wait for the NMI by examining bit 7 of $4210. You'll need to tie VBlank to NMI by setting bit 7 of $4200 as well. You can use the WAI opcode to wait for NMI rather than have to poll $4210.

2) Leave the screen on at all times/set brightness to 100% and do everything in your main loop but instead of disabling VBlank, you need to wait for it. See bit 7 of $4212.

In either case you need to add some delays. The screen is updated at 60 times a second (50 if PAL). The easiest way to delay is to wait for VBlank repetitively (more waits = more delay). I used to use something like this:

Code: Select all

        rep #$10
        ldx #200        ; Higher value = longer delay
delay   jsr WaitVBL
        dex
        bpl delay
        sep #$10
...

;
; Note: Should make sure accum is 8-bit here, not 16-bit
;
WaitVBL:
        pha
xxx     lda $4210
        bpl xxx
        lda $4210       ; Reset it
        pla
        rts

It might be worthwhile, as I said earlier, to download my old SNESdoc. Inside of that archive is an another archive called TEST.LZH, which contains some source code to a very small demo used to show off some of the features on the SNES. The code is simple and should get you started, and it reads fairly easily.
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Re: SNES dev newbie questions

Post by juef »

koitsu wrote:It might be worthwhile, as I said earlier, to download my old SNESdoc. Inside of that archive is an another archive called TEST.LZH, which contains some source code to a very small demo used to show off some of the features on the SNES. The code is simple and should get you started, and it reads fairly easily.
I have found your docs; they seem rather helpful and complete. Thanks for writing them, I definitely have to read a bit more before continuing. But most of all, thank you very much for your reply: it is much more complete and helpful than what I hoped!
gilligan
Posts: 35
Joined: Tue Jan 04, 2005 7:31 am
Contact:

Post by gilligan »

As much as koitsu/yoshi deserves credit for his snes documentation which were released /long/ ago you should now refer to more complete documentation most of which can be found at http://romhacking.net or possibly http://wiki.superfamicom.org/ First and foremost you want to grab http://www.romhacking.net/docs/196/ which is a detailed description of all registers including additional info. Looking around on romhacking you will also find an (most certainly illegal) copy of the complete snes development manuals as issued by nintendo. I do have a printed copy of those but tbh the txt file mentioned before is much more useful.

Hope that helps. Enjoy!
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Post by juef »

This is probably more of a general assembly question, but I can't figure this one out. I tried to isolate the issue, which is why this code is missing a lot of important stuff:

Code: Select all

.include "header.inc"
.include "Snes_Init.asm"
VBlank:
    RTI
.bank 0
.section "MainCode"
Start:
	Snes_Init
	sep     #$20
	
;TestTable:
;	.db $00

Forever:
	lda	#%00011111
	sta	$2122
	lda	#%00000000
	sta	$2122
	lda	#%00001111
	sta	$2100

    jmp Forever
.ends
As it is above, the code colors the screen red. What confuses me is that uncommenting the two TestTable lines makes the screen green instead. I don't refer to this table anywhere, so I guess somehow adding this piece of code messes with the registers?

Also, thanks for the help gilligan, I've checked those links out a little. Very helpful indeed!
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: the universe
Contact:

Post by thefox »

juef wrote:What confuses me is that uncommenting the two TestTable lines makes the screen green instead. I don't refer to this table anywhere, so I guess somehow adding this piece of code messes with the registers?
That is because it executes the data as code since you placed it in the middle of code. Move it to a place which execution will never reach, such as below the "jmp Forever" instruction.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Post by juef »

Thank you for the reply, thefox! There's a lot of stuff I clearly haven't understood correctly yet, and that's one less of them now.

As an effort to understand better what I'm doing exactly, I've been trying to convert my source code to something that byuu's bass can compile. It's been easier than I thought, except for just this part:

Code: Select all

.SNESHEADER
  ; [...] some useless header stuff happens here (it can be commented without problem)
  LOROM
  ; [...] some useless header stuff happens here (it can be commented without problem)
.ENDSNES

.SNESNATIVEVECTOR               ; Define Native Mode interrupt vector table
  COP EmptyHandler
  BRK EmptyHandler
  ABORT EmptyHandler
  NMI VBlank
  IRQ EmptyHandler
.ENDNATIVEVECTOR

.SNESEMUVECTOR                  ; Define Emulation Mode interrupt vector table
  COP EmptyHandler
  ABORT EmptyHandler
  NMI EmptyHandler
  RESET Start                   ; where execution starts
  IRQBRK EmptyHandler
.ENDEMUVECTOR
This is part of a 'header.inc' file that my tutorial uses. When assembled, it writes some code at the end, without which the program cannot run. For now, just copying the assembled code from WLA into the file bass assembles works just fine. I'm not too sure what it that code is/does, but mostly, I'd be interested in knowing how to translate it to bass. I'm guessing a different syntax will provide me another way of understanding it, as has happened with plenty of other stuff in the conversion so far.

Thank you all again very much!
User avatar
Hamtaro126
Posts: 823
Joined: Thu Jan 19, 2006 5:08 pm

Post by Hamtaro126 »

juef wrote:Thank you for the reply, thefox! There's a lot of stuff I clearly haven't understood correctly yet, and that's one less of them now.

As an effort to understand better what I'm doing exactly, I've been trying to convert my source code to something that byuu's bass can compile. It's been easier than I thought, except for just this part:
Refer to MukundaJay's SNES Devkit '09, for CA65.

http://snes.mukunda.com/

Header examples can be found there, More portable than WLA-DX's

or at least steal the header from your favorite ROM (at least FFC0-FFFF) such as Super Mario World (That will be terrible, though).
User avatar
Memblers
Posts: 4100
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Post by Memblers »

That isn't code at all, but it just a list of address to your interrupt routines. I guess this was considered significant enough to have it's own Wikipedia article: http://en.wikipedia.org/wiki/Interrupts ... processors

Normally you would use .word Emptyhandler (or in CA65 .addr EmptyHandler is the same thing). You can place this stuff manually with the .org command, but it seems like assemblers usually provide some kind of way of defining it.
Near
Founder of higan project
Posts: 1553
Joined: Mon Mar 27, 2006 5:23 pm

Post by Near »

I don't provide additional markup, as it's really not necessary.

Code: Select all

lorom
At the top of the file is all you need for LoROM addressing.

As for the vectors, write them directly:

Code: Select all

org $ffe4; dw COP
org $ffe6; dw BRK
org $ffea; dw NMI
org $ffee; dw IRQ
org $fffc; dw Reset
Those are just labels to code that appears between $008000 and $00ffff. So Reset would be Start in your case. You don't HAVE to define ones you don't ever use.

You'll never use the emulation-mode ones.
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Post by juef »

Thanks you all for your answers! Now I can compile what I had done so far with bass, with which I'll probably be stickin' from now on!
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Post by juef »

I have been asked to share my code that compiles with bass. I have progressed beyond what I'm posting below, but as there's still some stuff I can't figure out and I won't have the time to look at it for a few days, I'll just share what I had successfully compiled in my previous post. All that happens is that the screen gets colored red.

File "test.asm"

Code: Select all

incsrc "SnesInit.asm"

Start:
	{SnesInit}
	
   lda   #%00011111
   sta   $2122
   lda   #%00000000
   sta   $2122
   lda   #%00001111
   sta   $2100

Forever:
	jmp Forever
File "SnesInit.asm"

Code: Select all

mapper lorom
org $8000; fill $010000
org $ffea; dw EmptyHandler	// SNES vectors
org $fffc; dw Start
org $8000;

macro SnesInit;
        sei;             // Disabled interrupts
        clc;             // clear carry to switch to native mode
        xce;             // Xchange carry & emulation bit. native mode
        rep     #$18;    // Binary mode (decimal mode off), X/Y 16 bit
        ldx     #$1FFF;  // set stack to $1FFF
        txs;
        jsr Init;
endmacro

Init:
        sep     #$30    // X,Y,A are 8 bit numbers
        lda     #$8F    // screen off, full brightness
        sta     $2100   // brightness + screen enable register 
        stz     $2101   // Sprite register (size + address in VRAM) 
        stz     $2102   // Sprite registers (address of sprite memory [OAM])
        stz     $2103   //    ""                       ""
        stz     $2105   // Mode 0, = Graphic mode register
        stz     $2106   // noplanes, no mosaic, = Mosaic register
        stz     $2107   // Plane 0 map VRAM location
        stz     $2108   // Plane 1 map VRAM location
        stz     $2109   // Plane 2 map VRAM location
        stz     $210A   // Plane 3 map VRAM location
        stz     $210B   // Plane 0+1 Tile data location
        stz     $210C   // Plane 2+3 Tile data location
        stz     $210D   // Plane 0 scroll x (first 8 bits)
        stz     $210D   // Plane 0 scroll x (last 3 bits) #$0 - #$07ff
        lda     #$FF    // The top pixel drawn on the screen isn't the top one in the tilemap, it's the one above that.
        sta     $210E   // Plane 0 scroll y (first 8 bits)
        sta     $2110   // Plane 1 scroll y (first 8 bits)
        sta     $2112   // Plane 2 scroll y (first 8 bits)
        sta     $2114   // Plane 3 scroll y (first 8 bits)
        lda     #$07    // Since this could get quite annoying, it's better to edit the scrolling registers to fix this.
        sta     $210E   // Plane 0 scroll y (last 3 bits) #$0 - #$07ff
        sta     $2110   // Plane 1 scroll y (last 3 bits) #$0 - #$07ff
        sta     $2112   // Plane 2 scroll y (last 3 bits) #$0 - #$07ff
        sta     $2114   // Plane 3 scroll y (last 3 bits) #$0 - #$07ff
        stz     $210F   // Plane 1 scroll x (first 8 bits)
        stz     $210F   // Plane 1 scroll x (last 3 bits) #$0 - #$07ff
        stz     $2111   // Plane 2 scroll x (first 8 bits)
        stz     $2111   // Plane 2 scroll x (last 3 bits) #$0 - #$07ff
        stz     $2113   // Plane 3 scroll x (first 8 bits)
        stz     $2113   // Plane 3 scroll x (last 3 bits) #$0 - #$07ff
        lda     #$80    // increase VRAM address after writing to $2119
        sta     $2115   // VRAM address increment register
        stz     $2116   // VRAM address low
        stz     $2117   // VRAM address high
        stz     $211A   // Initial Mode 7 setting register
        stz     $211B   // Mode 7 matrix parameter A register (low)
        lda     #$01
        sta     $211B   // Mode 7 matrix parameter A register (high)
        stz     $211C   // Mode 7 matrix parameter B register (low)
        stz     $211C   // Mode 7 matrix parameter B register (high)
        stz     $211D   // Mode 7 matrix parameter C register (low)
        stz     $211D   // Mode 7 matrix parameter C register (high)
        stz     $211E   // Mode 7 matrix parameter D register (low)
        sta     $211E   // Mode 7 matrix parameter D register (high)
        stz     $211F   // Mode 7 center position X register (low)
        stz     $211F   // Mode 7 center position X register (high)
        stz     $2120   // Mode 7 center position Y register (low)
        stz     $2120   // Mode 7 center position Y register (high)
        stz     $2121   // Color number register ($0-ff)
        stz     $2123   // BG1 & BG2 Window mask setting register
        stz     $2124   // BG3 & BG4 Window mask setting register
        stz     $2125   // OBJ & Color Window mask setting register
        stz     $2126   // Window 1 left position register
        stz     $2127   // Window 2 left position register
        stz     $2128   // Window 3 left position register
        stz     $2129   // Window 4 left position register
        stz     $212A   // BG1, BG2, BG3, BG4 Window Logic register
        stz     $212B   // OBJ, Color Window Logic Register (or,and,xor,xnor)
        sta     $212C   // Main Screen designation (planes, sprites enable)
        stz     $212D   // Sub Screen designation
        stz     $212E   // Window mask for Main Screen
        stz     $212F   // Window mask for Sub Screen
        lda     #$30
        sta     $2130   // Color addition & screen addition init setting
        stz     $2131   // Add/Sub sub designation for screen, sprite, color
        lda     #$E0
        sta     $2132   // color data for addition/subtraction
        stz     $2133   // Screen setting (interlace x,y/enable SFX data)
        stz     $4200   // Enable V-blank, interrupt, Joypad register
        lda     #$FF
        sta     $4201   // Programmable I/O port
        stz     $4202   // Multiplicand A
        stz     $4203   // Multiplier B
        stz     $4204   // Multiplier C
        stz     $4205   // Multiplicand C
        stz     $4206   // Divisor B
        stz     $4207   // Horizontal Count Timer
        stz     $4208   // Horizontal Count Timer MSB (most significant bit)
        stz     $4209   // Vertical Count Timer
        stz     $420A   // Vertical Count Timer MSB
        stz     $420B   // General DMA enable (bits 0-7)
        stz     $420C   // Horizontal DMA (HDMA) enable (bits 0-7)
        stz     $420D   // Access cycle designation (slow/fast rom)
        cli             // Enable interrupts
        rts

EmptyHandler:
		rti
User avatar
juef
Posts: 67
Joined: Thu Jul 15, 2010 8:20 am
Location: Québec, Canada

Re: SNES dev newbie questions

Post by juef »

Sorry to bring back such an old topic; I know it's been a long time but I've felt the desire to go back to these things recently and the above posts are still relevant to my following questions.

1) In the SNES init routine above, there is this one line:

Code: Select all

stz     $4200   // Enable V-blank, interrupt, Joypad register
Now, from what I understand, this sets $4200 to zero, which... disables VBlank NMI, right? Shouldn't this be the following instead?

Code: Select all

lda #$80
sta $4200
2) Following koitsu's above advice, I tried inplementing delays between my background color change. The main code goes like this: init the SNES, set the background green, wait, set it red, dim the screen a little, and then do nothing forever. The screen is indeed dimmed, but the color is still red. Any clue to why I can't get some red?

Code: Select all

Snes_Init

sep	#$20
lda	#%10000000
sta	$2100
lda	#%11100000
sta	$2122
lda	#%00000011
sta	$2122
lda	#%00001111
sta	$2100
	
rep #$10
ldx #200
delay:
	jsr WaitVBL
	dex
	bpl delay
sep #$10

sep	#$20
lda	#%10000000
sta	$2100
lda	#%00011111
sta	$2122
lda	#%00000000
sta	$2122
lda	#%00000100
sta	$2100

Forever:
	jmp Forever

WaitVBL:
	sep	#$20
	pha
	wai
	pla
	rts
(Note that I understand the advice about keeping the screen on, but I tried this as a test when I couldn't get red.)

This is with WLADX, as I was unable to make my code suitable for the latest bass version.
Any help would be much appreciated! Thank you!
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES dev newbie questions

Post by koitsu »

I don't see you setting $2121 anywhere, which means you're just blindly appending onto the end of the palette rather than changing existing on-screen colours. You probably want an stz $2121 before tinkering with $2122.

As for the // Enable V-blank, interrupt, Joypad register comment: the comment itself is wrong and you are correct -- stz $4200 would DISABLE NMI when VBlank fires, not enable. You really should turn on NMI when VBlank fires, otherwise your wai in WaitVBL doesn't serve a purpose.

Also, WaitVBL doesn't look quite right (you could replace that whole thing with just wai ; rts and be done with it; I don't see the point behind the stack operations or the sep #$20). The way the routine "waits for VBlank", BTW, is by waiting for NMI to fire using wai, which you want tied to VBlank, hence what I said in my above paragraph. :-)

You could also replace Forever: jmp Forever with just stp (STop Processor) if you wanted.
DoNotWant
Posts: 83
Joined: Sun Sep 30, 2012 3:44 am

Re: SNES dev newbie questions

Post by DoNotWant »

Upload your project somewhere in a rar file, and I can make it into bass v14 code for you, and put some comments in there of what to think of when using bass. I'm no expert, but I got some stuff working with it.
Post Reply