From 2da42f1eb1b1e0b46a7547451aaebea976187f89 Mon Sep 17 00:00:00 2001 From: Colin McMillen Date: Sat, 23 May 2015 14:00:43 -0400 Subject: [PATCH] Add a "ship" and let the player move around. This includes some tile / palette data in a separate file and some code for DMA'ing that data into VRAM. This is used to render a "ship" (white circle) that the player can move around with the D-Pad. Added a whole bunch of named registers (& documentation for them) to registers.asm. --- InitSNES.asm | 2 +- header.asm | 2 +- pewpew.asm | 246 ++++++++++++++++++++++++++++++++++++++++++-------- registers.asm | 140 ++++++++++++++++++++++++++-- tiles.asm | 39 ++++++++ 5 files changed, 385 insertions(+), 44 deletions(-) create mode 100644 tiles.asm diff --git a/InitSNES.asm b/InitSNES.asm index 6693c87..f898075 100644 --- a/InitSNES.asm +++ b/InitSNES.asm @@ -2,7 +2,7 @@ ;- Standard SNES initialization routine, originally by Neviksti ;------------------------------------------------------------------------ -.MACRO InitSNES +.MACRO InitializeSNES sei ;disable interrupts clc ;switch to native mode xce diff --git a/header.asm b/header.asm index cca4917..bebbd60 100644 --- a/header.asm +++ b/header.asm @@ -12,7 +12,7 @@ .SNESHEADER ID "SNES" - NAME "PEW PEW " ; Program title. Should be 21 bytes long; + NAME "PEW! PEW! " ; Program title. Should be 21 bytes long; ; "123456789012345678901" ; use spaces for unused bytes of the name. SLOWROM diff --git a/pewpew.asm b/pewpew.asm index 1ccb601..bf08646 100644 --- a/pewpew.asm +++ b/pewpew.asm @@ -2,6 +2,34 @@ .INCLUDE "InitSNES.asm" .INCLUDE "registers.asm" + +; The JSR and RTS instructions add a total of 12 cycles of overhead. For +; short, commonly-used functions, it makes sense to declare them as macros, +; which get inlined by the assembler at the point of use. This saves on +; CPU cycles, at the cost of code size. + + +.MACRO ConvertXCoordinate +; Data in: world x-coordinate, in A register. +; Data out: SNES scroll data, in C (the 16-bit A register). +rep #%00100000 ; 16-bit A +eor #$FFFF ; Flip bits +ina +sep #%00100000 ; 8-bit A +.ENDM + + + +.MACRO ConvertYCoordinate +; Data in: world y-coordinate, in A register. +; Data out: SNES scroll data, in C (the 16-bit A register). +rep #%00100000 ; 16-bit A +eor #$FFFF ; Flip bits +sep #%00100000 ; 8-bit A +.ENDM + + + .BANK 0 SLOT 0 .ORG 0 .SECTION "MainCode" @@ -9,14 +37,18 @@ ; Memory layout: ; 00-0F: scratch space for functions. -; 10-13: controller state. +; 10-11: controller state of joypad #1. +; 12-13: controller state of joypad #2. ; 14-17: 32-bit counter of vblanks. -; 20-22: RGB color values to use for background color, from [0-31]. +; 20-21: (x, y) coordinates of player. +; 22-24: RGB color values to use for background color, from [0-31]. Start: - InitSNES ; Initialize SNES. - + InitializeSNES + + jsr LoadPaletteAndTileData + ; Turn on the screen. ; Format: x000bbbb ; x: 0 = screen on, 1 = screen off, bbbb: Brightness ($0-$F) @@ -26,7 +58,7 @@ Start: ; Enable NMI interrupt & joypad. ; n-vh---j n: NMI interrupt enable v: vertical counter enable ; h: horizontal counter enable j: joypad enable - lda #$81 + lda #%10000001 sta NMITIMEN ; Store zeroes to the controller status registers. @@ -38,6 +70,10 @@ Start: ; Write something recognizable into our scratch space. jsr FillScratch + ; Start the background color as a dark blue. + lda #16 + sta $24 + MainLoop: @@ -46,11 +82,77 @@ MainLoop: +LoadPaletteAndTileData: + ; 16-bit X/Y registers. Used for DMA source address & transfer size, both of + ; which want 16-bit values. + ; TODO(mcmillen): change back to 8-bit when we're done? + rep #%00010000 + ; 8-bit A/B registers. Used for DMA source bank & destination address. + sep #%00100000 + + ; We only need one palette entry, so we just initialize it manually. + ; We could also do this with a DMA transfer (like we do with the tile data + ; below), but it seems overkill for just one entry :) + lda #34 ; Set the 34th palette entry. + sta CGADDR + lda.l PaletteData + sta CGDATA + lda.l PaletteData + 1 + sta CGDATA + + ; DMA 0 source address & bank. + ldx #TileData + stx DMA0SRC + lda #:TileData + sta DMA0SRCBANK + ; DMA 0 transfer size. + ldy #(15 * 16 *2) ; Also see the helpful "480 bytes" comment in tiles.asm. + sty DMA0SIZE + ; DMA 0 control register. + ; Transfer type 001 = 2 addresses, LH. + lda #%00000001 + sta DMA0CTRL + ; DMA 0 destination. + lda #$18 ; Upper-byte is assumed to be $21, so this is $2118 & $2119. + sta DMA0DST + ; $2116 sets the word address for accessing VRAM. + ldy #$0000 + sty VMADDR + ; Enable DMA channel 0. + lda #%00000001 + sta DMAENABLE + + ; VRAM writing mode. Increments the address every time we write to $2119. + lda #%10000000 + sta VMAIN + ; Set word address for accessing VRAM to $6000. + ldx #$6000 ; BG 2 starts here. + stx VMADDR + ldx #$000A ; Stick one tile into BG2. + stx VMDATA + + ; Set up the screen. 16x16 tiles for BG2, 8x8 tiles elsewhere, mode 0. + lda #%00100000 + sta BGMODE + ; $2108 is the BG2 VRAM location register. + ; This tells it that the BG2 data starts at $6000. + lda #%01100000 + sta BG2SC + stz BG12NBA + + ; Main screen: enable BG2. + lda #%00000010 + sta MSENABLE + + rts + + + VBlankHandler: jsr VBlankCounter ; DEBUG jsr JoypadHandler jsr SetBackgroundColor - ; jsr FillScratch ; DEBUG + jsr SetPlayerPosition rti @@ -76,72 +178,113 @@ VBlankCounterDone: JoypadHandler: - ; $4218: Joypad #1 status [JOY1L] - ; Format: AXLR0000 - ; $4219: Joypad #1 status [JOY1H] - ; Format: BYsSudlr (s=select, S=start, udlr = joypad) jsr JoypadDebug ; DEBUG -; TODO(mcmillen): read joypad from local memory instead of registers? JoypadUp: lda JOY1H and #$08 ; Up cmp #$08 bne JoypadDown ; Button not pressed. - lda $20 - cmp #31 + lda $21 + cmp #0 beq JoypadDown ; Value saturated. - inc $20 + dec $21 + dec $21 JoypadDown: lda JOY1H and #$04 cmp #$04 bne JoypadLeft ; Button not pressed. - lda $20 - cmp #0 + lda $21 + cmp #(224 - 16) beq JoypadLeft ; Value saturated. - dec $20 + inc $21 + inc $21 JoypadLeft: lda JOY1H and #$02 ; Left cmp #$02 bne JoypadRight ; Button not pressed. - lda $22 + lda $20 cmp #0 beq JoypadRight ; Value saturated. - dec $22 + dec $20 + dec $20 JoypadRight: lda JOY1H and #$01 cmp #$01 ; Right bne JoypadB ; Button not pressed. - lda $22 - cmp #31 + lda $20 + cmp #(256 - 16) beq JoypadB ; Value saturated. - inc $22 + inc $20 + inc $20 JoypadB: lda JOY1H and #$80 ; B cmp #$80 + bne JoypadA ; Button not pressed. + lda $22 + cmp #0 + beq JoypadA ; Value saturated. + dec $22 + +JoypadA: + lda JOY1L + and #$80 ; A + cmp #$80 bne JoypadY ; Button not pressed. - lda $21 + lda $22 cmp #31 beq JoypadY ; Value saturated. - inc $21 + inc $22 JoypadY: lda JOY1H and #$40 ; Y cmp #$40 - bne JoypadDone ; Button not pressed. - lda $21 + bne JoypadX ; Button not pressed. + lda $23 + cmp #0 + beq JoypadX ; Value saturated. + dec $23 + +JoypadX: + lda JOY1L + and #$40 ; X + cmp #$40 + bne JoypadL ; Button not pressed. + lda $23 + cmp #31 + beq JoypadL ; Value saturated. + inc $23 + +JoypadL: + lda JOY1L + and #$20 ; L + cmp #$20 + bne JoypadR ; Button not pressed. + lda $24 cmp #0 + beq JoypadR ; Value saturated. + dec $24 + +JoypadR: + lda JOY1L + and #$10 ; R + cmp #$10 + bne JoypadDone ; Button not pressed. + lda $24 + cmp #31 beq JoypadDone ; Value saturated. - dec $21 + inc $24 + +; TODO(mcmillen): have Start and Select do something too. JoypadDone: rts @@ -163,34 +306,57 @@ JoypadDebug: SetBackgroundColor: - ; $20 $21 $22 are (R, G, B), each ranging from [0-31]. + ; $22 $23 $24 are (R, G, B), each ranging from [0-31]. ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr] + ; Set the background color. + ; Entry 0 corresponds to the SNES background color. + stz CGADDR + ; Compute and the low-order byte and store it in CGDATA. - lda $21 ; Green. + lda $23 ; Green. .rept 5 asl .endr - ora $20 ; Red. + ora $22 ; Red. sta CGDATA ; Compute the high-order byte and store it in CGDATA. - lda $22 ; Blue. + lda $24 ; Blue. .rept 2 asl .endr sta $00 - lda $21 ; Green. + lda $23 ; Green. .rept 3 lsr .endr ora $00 sta CGDATA + rts - ; Set the background color. - ; $2121 is the color palette selection register [CGADD]. - ; Entry 0 corresponds to the SNES background color. - stz CGADD + + +SetPlayerPosition: + ; Make sure the high byte of A is zeroed out. + lda #$00 + xba + ; Get player x and convert it to a scroll offset. + lda $0020 + ConvertXCoordinate + sta BG2HOFS + xba + sta BG2HOFS + + ; Make sure the high byte of A is zeroed out. + lda #$00 + xba + ; Get player y and convert it to a scroll offset. + lda $0021 + ConvertYCoordinate + sta BG2VOFS + xba + sta BG2VOFS rts @@ -206,3 +372,11 @@ FillScratchLoop: rts .ENDS + + + +.BANK 1 SLOT 0 +.ORG 0 +.SECTION "TileData" +.INCLUDE "tiles.asm" +.ENDS \ No newline at end of file diff --git a/registers.asm b/registers.asm index 9f7fc78..79fb9e7 100644 --- a/registers.asm +++ b/registers.asm @@ -5,17 +5,98 @@ ; the same sense as PC, A, X, Y, and so on. Despite that, I call them ; "registers" too, since that's what everyone else calls them. ; -; Where possible, I have named these register definitions in the same way that -; they're named in Yoshi's venerable snes.txt document. +; I've often named these register definitions in the same way that they're +; named in Yoshi's venerable snes.txt document. In some cases (where the +; mnemonic is too obscure) I've invented a different name. In particular, +; I've changed "ADD" to "ADDR" to reduce possible confusion between "addresses" +; and "addition". The original name from Yoshi's doc is still listed in +; brackets, like [CGADD], for easy cross-referencing. +; +; I've also heavily borrowed from Yoshi's descriptions of what these registers +; do, though in many cases I've clarified / simplified the descriptions based +; on my own understanding, or simply reformatted them a bit. -; $2100: Screen display register [INIDISP] +; $2100: Screen display initialization [INIDISP] ; Format: x000bbbb ; x: 0 = screen on, 1 = screen off, bbbb: Brightness ($0-$F) .define INIDISP $2100 +; $2105: Screen mode register [BGMODE] +; abcdefff a: BG4 tile size (0=8x8, 1=16x16). +; b: BG3 tile size (0=8x8, 1=16x16). +; c: BG2 tile size (0=8x8, 1=16x16). +; d: BG1 tile size (0=8x8, 1=16x16). +; e: Highest priority for BG3 in MODE 1. +; f: MODE definition. +.define BGMODE $2105 + +; $2107-210A: BG1-4 VRAM location registers [BGxSC] +; xxxxxxab x: Base address +; ab: SC size +.define BG1SC $2107 +.define BG2SC $2108 +.define BG3SC $2109 +.define BG4SC $210A + +; $210B: BG1 & BG2 VRAM location register [BG12NBA] +; $210C: BG3 & BG4 VRAM location register [BG34NBA] +; aaaabbbb a: Base address for BG2 (or BG4). +; b: Base address for BG1 (or BG3). +.define BG12NBA $210B +.define BG34NBA $210C + +; BG1 horizontal scroll offset. [BG1HOFS] +; BG1 vertical scroll offset. [BG1VOFS] +; ... and similar registers for BG2-4. +; Write to all of these twice, as they want 2 bytes of data. +; mmmmmaaa aaaaaaaa a: Horizontal offset. +; m: Only set with MODE 7. +.define BG1HOFS $210D +.define BG1VOFS $210E +.define BG2HOFS $210F +.define BG2VOFS $2110 +.define BG3HOFS $2111 +.define BG3VOFS $2112 +.define BG4HOFS $2113 +.define BG4VOFS $2114 + +; $2115: Video port control [VMAIN] +; i000abcd i: 0 = Address increment after writing to $2118 or reading +; from $2139. +; 1 = Address increment after writing to $2119 or reading +; from $213A. +; ab: Full graphic (see table below). +; cd: SC increment (see table below). +; +; abcd Result +; 0100 Increment by 8 for 32 times (2-bit formation). +; 1000 Increment by 8 for 64 times (4-bit formation). +; 1100 Increment by 8 for 128 times (8-bit formation). +; 0000 Address increments 1x1. +; 0001 Address increments 32x32. +; 0010 Address increments 64x64. +; 0011 Address increments 128x128. +.define VMAIN $2115 + +; $2116-$2117: Video port address. 2 bytes. [VMADDL/VMADDH] +.define VMADDR $2116 + +; $2118-$2119: Video port data. 2 bytes. [VMDATAL/VMDATAH] +; According to bit 7 of VMAIN, the data can be stored as: +; Bit 7 +; 0 Write to $2118 only. Lower 8-bits written then +; address is increased. +; 0 Write to $2119 then $2118. Address increased when both +; are written to (in order). +; 1 Write to $2119 only. Upper 8-bits written, then +; address is increased. +; 1 Write to $2118 then $2119. Address increased when both +; are written to (in order). +.define VMDATA $2118 + ; $2121: Color palette selection register [CGADD] ; Entry 0 corresponds to the SNES background color. -.define CGADD $2121 +.define CGADDR $2121 ; $2122: Color data register [CGDATA] ; The palette color format is 15-bit: [0bbbbbgg][gggrrrrr]. @@ -24,11 +105,23 @@ ; (containing blue and green). .define CGDATA $2122 +; $212C: Main screen designation [TM] +; 000abcde a: OBJ/OAM disable/enable. +; b: Disable/enable BG4. +; c: Disable/enable BG3. +; d: Disable/enable BG2. +; e: Disable/enable BG1. +.define MSENABLE $212C + ; $4200: Counter enable [NMITIMEN] -; n-vh---j n: NMI interrupt enable v: vertical counter enable -; h: horizontal counter enable j: joypad enable +; n-vh---j n: NMI interrupt enable v: vertical counter enable +; h: horizontal counter enable j: joypad enable .define NMITIMEN $4200 +; $420B: DMA enable [MDMAEN] +; Each bit that's set enables one channel: 76543210 +.define DMAENABLE $420B + ; $4218: Joypad #1 status [JOY1L] ; Format: AXLR0000 .define JOY1L $4218 @@ -44,3 +137,38 @@ ; $421B: Joypad #2 status [JOY2H] ; Format: BYsSudlr (s=select, S=start, udlr = joypad) .define JOY2H $421B + +; $43x0: DMA control for channel x. [DMAPX] +; vh0cbaaa v: 0 = CPU memory -> PPU. +; 1 = PPU -> CPU memory. +; h: For HDMA only: +; 0 = Absolute addressing. +; 1 = Indirect addressing. +; c: 0 = Auto address inc/decrement. +; 1 = Fixed address (for VRAM, etc.). +; b: 0 = Automatic increment. +; 1 = Automatic decrement. +; a: Transfer type: +; 000 = 1 address write twice: LH. +; 001 = 2 addresses: LH. +; 010 = 1 address write once. +; 011 = 2 addresses write twice: LLHH +; 100 = 4 addresses: LHLH +.define DMA0CTRL $4300 + +; $43x1: DMA destination for channel x. [BBADX] +; The upper byte is assumed to be $21, so the possible destinations are +; $2100-$21FF. +.define DMA0DST $4301 + +; $43x2-$43x3: DMA source address for channel x. 2 bytes. [AITXL/AITXH] +.define DMA0SRC $4302 + +; $43x4: DMA source bank for channel x [AIBX] +.define DMA0SRCBANK $4304 + +; $43x5: DMA transfer size & HDMA address. 2 bytes. [DASXL/DASXH] +; When using DMA, $43x5 defines the # of bytes to be transferred via DMA +; itself. When using HDMA, $43x5 defines the data address ($43x5 = low byte, +; $43x6 = hi byte). +.define DMA0SIZE $4305 \ No newline at end of file diff --git a/tiles.asm b/tiles.asm new file mode 100644 index 0000000..46d6c0b --- /dev/null +++ b/tiles.asm @@ -0,0 +1,39 @@ +; Created with eKid's pcx2snes converter ; + +TileData: + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03 + .db $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0 + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF, $FF + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $FF, $FF, $FF, $FF + .db $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $FF, $FF, $FF, $FF + .db $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $FF, $FF, $FF, $FF + .db $00, $C0, $00, $E0, $00, $70, $00, $38, $00, $1C, $00, $0E, $00, $07, $00, $03 + .db $00, $03, $00, $07, $00, $0E, $00, $1C, $00, $38, $00, $70, $00, $E0, $00, $C0 + .db $00, $07, $00, $0F, $00, $18, $00, $30, $00, $60, $00, $C0, $00, $C0, $00, $C0 + .db $00, $E0, $00, $F0, $00, $18, $00, $0C, $00, $06, $00, $03, $00, $03, $00, $03 + .db $FC, $00, $F8, $00, $F0, $00, $E0, $00, $C0, $00, $80, $00, $00, $00, $00, $00 + .db $3F, $00, $1F, $00, $0F, $00, $07, $00, $03, $00, $01, $00, $00, $00, $00, $00 + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03 + .db $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0 + .db $FF, $FF, $FF, $FF, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $FF, $FF, $FF, $FF, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .db $FF, $FF, $FF, $FF, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03, $03 + .db $FF, $FF, $FF, $FF, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0, $C0 + .db $00, $03, $00, $07, $00, $0E, $00, $1C, $00, $38, $00, $70, $00, $E0, $00, $C0 + .db $00, $C0, $00, $E0, $00, $70, $00, $38, $00, $1C, $00, $0E, $00, $07, $00, $03 + .db $00, $C0, $00, $C0, $00, $C0, $00, $60, $00, $30, $00, $18, $00, $0F, $00, $07 + .db $00, $03, $00, $03, $00, $03, $00, $06, $00, $0C, $00, $18, $00, $F0, $00, $E0 + .db $00, $00, $00, $00, $80, $00, $C0, $00, $E0, $00, $F0, $00, $F8, $00, $FC, $00 + .db $00, $00, $00, $00, $01, $00, $03, $00, $07, $00, $0F, $00, $1F, $00, $3F, $00 + +PaletteData: + .db $FF, $7F + +; 30 tiles (2 spaces) +; 480 bytes