SNES programming tutorial. Example 3.

So, this is the big lesson. There are about a dozen things we need to do just to see a picture on the screen. We need to set a video mode. We need to enable a layer on the main screen. We need to set the address for tiles. We need to set the address for a tilemap. We need to make tiles and a tilemap (and a palette). We need to copy all the things to the VRAM. And we need to turn screen brightness on (and end forced blank).

I picked a random picture of a moon. Open in GIMP, resize to 128×128. Resize canvas to 256 pixel wide. Change picture mode to index, 16 colors, saved (export) as PNG with no compression.

(a little later, the image looked stretched out when I got it running on my actual SNES, due to the aspect ratio of SNES pixels being about 8/7. And also the order of the palette colors seemed oddly out of order, so I started over…)

OK. GIMP. Open file, resize to 112×128 to fix the sideways stretching. Resize canvas to 256 pixel wide. Then I made a 16 color grayscale palette like #000000 #101010 #202020 etc. Then I changed color mode to indexed using the custom palette (I shouldn’t have left the “remove unused colors” checked… oh, well). This fixed the out of order palette issue. That isn’t a big deal now, but if I had to process multiple images using the same SNES palette, you should have a standard GIMP palette for all the images to use. YY-CHR can rearrange color indexes, but it becomes a real pain for 16 colors over multiple images.

Then export as PNG with no compression.


Now we need superfamiconv.

This is a great tool to convert PNG to SNES palette, tiles, and map. The image needs to be 256 wide to correctly make a map. The PNG needs to be 16 color indexed and no compression. Superfamiconv is a command line tool, but I wrote a batch file (.bat) to automate it. (Just double click on the batch file)

and it produced moon.pal, moon.chr, and

The .chr file is in 4bpp SNES format.

Now I can load all 3 files in M1TE (my mode 1 tile editor tool).

When I loaded the .map file, I clicked a few tiles down from the top and chose “load map to selected Y”. I changed the map height to 28, and resaved the full map as

All these files (moon.pal, moon.chr, and were added to the project by adding .incbin lines at the bottom of main.asm.

Now the code, which I will go over, line by line… but first I want to figure out where we are putting things in the VRAM. This is what I have been using, and it seems to work for my current needs. This arrangement is optional. You can rearrange the VRAM any way you like.


$0000 4bpp BG tiles (768 of them)
$3000 2bpp BG tiles (512 of them)
$4000 4bpp sprite tiles (512 of them)
$6000 layer 1 map (up to 2 screens)
$6800 layer 2 map (up to 2 screens)
$7000 layer 3 map (up to 2 screens)
$7800 layer 4 map (up to 2 screens)

So we need to put the 4bpp tiles at $0000 and the layer 1 map at $6000.


; DMA from Palette_Copy to CGRAM
; see previous tutorial page for explanation
; with A8 and XY16

; DMA from Tiles to VRAM 
lda #V_INC_1 ; the value $80
sta vram_inc ; $2115 register, set the increment mode +1
; each write will go +1 the previous write address
ldx #$0000
stx vram_addr ; set an address in the vram of $0000

; now we set up the DMA
lda #1
sta $4300 ; transfer mode, 2 registers 1 write
; $2118 and $2119 are a pair Low/High
lda #$18 ; $2118
sta $4301 ; destination, vram data
ldx #.loword(Tiles)
stx $4302 ; source
lda #^Tiles
sta $4304 ; bank
ldx #(End_Tiles-Tiles)
; let the assembler calculate the size of transfer
; using 2 labels before and after our tiles.
stx $4305 ; length
lda #1
sta $420b ; start dma, channel 0

; DMA from Tilemap to VRAM 
ldx #$6000
stx vram_addr ; set an address in the vram of $6000

lda #1
sta $4300 ; transfer mode, 2 registers 1 write
; $2118 and $2119 are a pair Low/High
lda #$18 ; $2118
sta $4301 ; destination, vram data
ldx #.loword(Tilemap)
stx $4302 ; source
lda #^Tilemap
sta $4304 ; bank
ldx #$700
stx $4305 ; length
lda #1
sta $420b ; start dma, channel 0 

; a is still 8 bit.
lda #1 ; mode 1, tilesize 8x8 all
sta bg_size_mode ; $2105

stz bg12_tiles ; $210b tiles for BG 1+2 at VRAM address $0000

lda #$60 ; bg1 map at VRAM address $6000
sta tilemap1 ; $2107

lda #BG1_ON ; $01 = only bg 1 is active
sta main_screen ; $212c

lda #FULL_BRIGHT ; $0f = turn the screen on (end forced blank)
sta fb_bright ; $2100

Let’s go over these last lines. $2105 is for video mode. We want mode 1, and 8×8 tiles. Our tiles will need to be 4bpp for layers 1 and 2. (we are only using layer 1). The bits of $2105 are 4321Emmm, with mmm for BG Mode. E will affect priority of BG3. 4321 are zero so all the layers have 8×8 tiles. If any of these are set, the corresponding layer will have 16×16 tiles.

$210b tells the PPU where our tiles are (for layer 1 and 2). Low nibble for layer 1 and high nibble for layer 2. Our tiles are at $0000 so we are just storing zero. But if we wanted to, we could put our tiles at $1000 for layer 1 by storing #1 to 210b. They are steps of $1000.

This is the perfect opportunity to point out that for all these “VRAM address X is for Y” registers the upper bit is always zero. There are only $8000 VRAM addresses, and the registers always look like they go up to $FFFF, but they don’t. My guess is that the original engineers were told that there would be 128 kB of VRAM, but some bean counter said “128 kB is too expensive, we only need 64 kB”.

So, bbbb aaaa is really -bbb -aaa (a = layer 1, b = layer2).

So, for 210b, only values 0-7 make sense for each nibble.

Maps have 6 bits for VRAM address. They are steps of $400, but the low 2 bits of the $2107 register are for map size… aaaaaayx where a is VRAM address and yx is map size (is really -aaaaayx with upper bit always 0 since VRAM addresses don’t go above $8000). It looks like you are just multiplying by $100. The value $60 is for VRAM address $6000, where our tile map for layer 1 will go.

Next time I will discuss the yx bits. You can reference this page for more information.

And we need to turn on layer 1 on the main screen $212c. (It would look really weird if we turned on ALL layers right now. All the other maps are still set to $0000 where our tiles are).

My main focus was setting up a tool chain for putting an image on screen. Here’s what it looks like. Try to repeat the process with a different picture. Maybe something more colorful?



What’s that RLE folder?

Another option for M1TE files (map and tiles) is to save as .rle (run length encoding). It’s slightly more advanced than a simple rle. It is designed for map compression. It works especially well for this particular tool chain (superfamiconv) because that tool puts tiles in order 1,2,3,4,etc. on the map and this .rle format can compress that. The full screen map is 1792 bytes and the compressed version is 76 bytes. It didn’t do so well for compressing tiles (5632 bytes became 4763 bytes). Other tilesets compress 50% or so.

The code to decompress is unrle.asm provided in the same main folder. It is set to decompress to $7f0000 and has a original maximum file size of $8000 (32k) bytes.

See mainB.asm. You pass a pointer to the compressed file to unrle the subroutine. With AXY16, A = source address, X = source bank. JSR unrle, it decompresses to $7f0000 (sometimes does a second rearrangement of bytes, so it may not be exactly 7f0000). And it returns AX with the address of the unpacked data, and Y the number of bytes.) We pass those to the DMA registers and send our data to the VRAM.

You may choose to leave everything uncompressed and skip the RLE stuff, and only use it if you run out of ROM space. You decide.


SNES main page


2 thoughts on “Backgrounds

  1. Hi Doug, it’s OMG, again ^^
    I work with your tutorials, and your tools.
    Really, it’s a very good work.

    So, I have a question about M1TE v1.7.
    I just want to copy all the tiles of the moon, to another place in my map, and I can’t do it. So I must do it one by one T-T
    How to select all the tiles in the map (by clic right for example) and copy its on another part of the map?

    Thank you.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s