diff --git a/controller/controller.asm b/controller/controller.asm new file mode 100644 index 0000000..9753190 --- /dev/null +++ b/controller/controller.asm @@ -0,0 +1,262 @@ + .inesprg 1 ; 1x 16KB PRG code + .ineschr 1 ; 1x 8KB CHR data + .inesmap 0 ; mapper 0 = NROM, no bank swapping + .inesmir 1 ; background mirroring + + +;;;;;;;;;;;;;;; + + + .bank 0 + .org $C000 +RESET: + SEI ; disable IRQs + CLD ; disable decimal mode + LDX #$40 + STX $4017 ; disable APU frame IRQ + LDX #$FF + TXS ; Set up stack + INX ; now X = 0 + STX $2000 ; disable NMI + STX $2001 ; disable rendering + STX $4010 ; disable DMC IRQs + +vblankwait1: ; First wait for vblank to make sure PPU is ready + BIT $2002 + BPL vblankwait1 + +clrmem: + LDA #$00 + STA $0000, x + STA $0100, x + STA $0200, x + STA $0400, x + STA $0500, x + STA $0600, x + STA $0700, x + LDA #$FE + STA $0300, x + INX + BNE clrmem + +vblankwait2: ; Second wait for vblank, PPU is ready after this + BIT $2002 + BPL vblankwait2 + + +LoadPalettes: + LDA $2002 ; read PPU status to reset the high/low latch + LDA #$3F + STA $2006 ; write the high byte of $3F00 address + LDA #$00 + STA $2006 ; write the low byte of $3F00 address + LDX #$00 ; start out at 0 +LoadPalettesLoop: + LDA palette, x ; load data from address (palette + the value in x) + ; 1st time through loop it will load palette+0 + ; 2nd time through loop it will load palette+1 + ; 3rd time through loop it will load palette+2 + ; etc + STA $2007 ; write to PPU + INX ; X = X + 1 + CPX #$20 ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites + BNE LoadPalettesLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero + ; if compare was equal to 32, keep going down + + + +LoadSprites: + LDX #$00 ; start at 0 +LoadSpritesLoop: + LDA sprites, x ; load data from address (sprites + x) + STA $0200, x ; store into RAM address ($0200 + x) + INX ; X = X + 1 + CPX #$20 ; Compare X to hex $20, decimal 32 + BNE LoadSpritesLoop ; Branch to LoadSpritesLoop if compare was Not Equal to zero + ; if compare was equal to 32, keep going down + + + + LDA #%10000000 ; enable NMI, sprites from Pattern Table 1 + STA $2000 + + LDA #%00010000 ; enable sprites + STA $2001 + +Forever: + JMP Forever ;jump back to Forever, infinite loop + + + +NMI: + LDA #$00 + STA $2003 ; set the low byte (00) of the RAM address + LDA #$02 + STA $4014 ; set the high byte (02) of the RAM address, start the transfer + + +LatchController: + LDA #$01 + STA $4016 + LDA #$00 + STA $4016 ; tell both the controllers to latch buttons + +SkipSeveralButtons: + ; discard input from buttons A, B, Select and Start + LDA $4016 + LDA $4016 + LDA $4016 + LDA $4016 + +; LoadSpritesLoop: +; LDA sprites, x ; load data from address (sprites + x) +; STA $0200, x ; store into RAM address ($0200 + x) +; INX ; X = X + 1 +; CPX #$20 ; Compare X to hex $20, decimal 32 +; BNE LoadSpritesLoop ; Branch to LoadSpritesLoop if compare was Not Equal to zero +; ; if compare was equal to 32, keep going down + +ReadUp: + LDA $4016 ; player 1 - Up + AND #%00000001 ; only look at bit 0 + BEQ ReadUpDone ; branch to ReadBDone if button is NOT pressed (0) + ; add instructions here to do something when button IS pressed (1) + LDX #0200 + MoveSpritesLoop: + TXA ; load sprite Y position + SEC ; make sure carry flag is set + SBC #$01 ; A = A - 1 + SBC #$01 ; A = A - 1 + STA ; save sprite Y position + INX + INX + INX + INX + CPX #$10 + BNE MoveSpritesLoop + ReadUpDone: ; handling this button is done + + +ReadDown: + LDA $4016 ; player 1 - Down + AND #%00000001 ; only look at bit 0 + BEQ ReadDownDone ; branch to ReadBDone if button is NOT pressed (0) + ; add instructions here to do something when button IS pressed (1) + LDA $0200 ; load sprite Y position + CLC + ADC #$01 ; A = A - 1 + ADC #$01 ; A = A - 1 + STA $0200 ; save sprite Y position + LDA $0204 ; load sprite Y position + CLC + ADC #$01 ; A = A + 1 + ADC #$01 ; A = A - 1 + STA $0204 ; save sprite Y position + LDA $0208 ; load sprite Y position + CLC + ADC #$01 ; A = A - 1 + ADC #$01 ; A = A - 1 + STA $0208 ; save sprite Y position + LDA $020C ; load sprite Y position + CLC + ADC #$01 ; A = A - 1 + ADC #$01 ; A = A - 1 + STA $020C ; save sprite Y position +ReadDownDone: ; handling this button is done + +ReadLeft: + LDA $4016 ; player 1 - Right + AND #%00000001 ; only look at bit 0 + BEQ ReadLeftDone ; branch to ReadBDone if button is NOT pressed (0) + ; add instructions here to do something when button IS pressed (1) + LDA $0203 ; load sprite X position + SEC ; make sure carry flag is set + SBC #$01 ; A = A - 1 + SBC #$01 ; A = A - 1 + STA $0203 ; save sprite X position + LDA $0207 ; load sprite X position + SEC ; make sure the carry flag is set + SBC #$01 ; A = A + 1 + SBC #$01 ; A = A - 1 + STA $0207 ; save sprite X position + LDA $020B ; load sprite X position + SEC ; make sure the carry flag is set + SBC #$01 ; A = A + 1 + SBC #$01 ; A = A - 1 + STA $020B ; save sprite X position + LDA $020F ; load sprite X position + SEC ; make sure the carry flag is set + SBC #$01 ; A = A + 1 + SBC #$01 ; A = A - 1 + STA $020F ; save sprite X position +ReadLeftDone: ; handling this button is done + + +ReadRight: + LDA $4016 ; player 1 - Left + AND #%00000001 ; only look at bit 0 + BEQ ReadRightDone ; branch to ReadADone if button is NOT pressed (0) + ; add instructions here to do something when button IS pressed (1) + LDA $0203 ; load sprite X position + CLC ; make sure the carry flag is clear + ADC #$01 ; A = A + 1 + ADC #$01 ; A = A + 1 + STA $0203 ; save sprite X position + LDA $0207 ; load sprite X position + CLC ; make sure the carry flag is clear + ADC #$01 ; A = A + 1 + ADC #$01 ; A = A + 1 + STA $0207 ; save sprite X position + LDA $020B ; load sprite X position + CLC ; make sure the carry flag is clear + ADC #$01 ; A = A + 1 + ADC #$01 ; A = A + 1 + STA $020B ; save sprite X position + LDA $020F ; load sprite X position + CLC ; make sure the carry flag is clear + ADC #$01 ; A = A + 1 + ADC #$01 ; A = A + 1 + STA $020F ; save sprite X position +ReadRightDone: ; handling this button is done + + + + RTI ; return from interrupt + +;;;;;;;;;;;;;; + + + + .bank 1 + .org $E000 +palette: + .db $0F,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$0F + .db $0F,$1C,$15,$14,$31,$02,$38,$3C,$0F,$1C,$15,$14,$31,$02,$38,$3C + +mario_sprite_addresses_vert: + .db $00,$04,$08,$0C + +mario_sprite_addresses_horiz: + .db $03,$07,$0B,$0F + +sprites: + ;vert tile attr horiz + .db $80, $32, $00, $80 ;sprite 0 + .db $80, $33, $00, $88 ;sprite 1 + .db $88, $34, $00, $80 ;sprite 2 + .db $88, $35, $00, $88 ;sprite 3 + + .org $FFFA ;first of the three vectors starts here + .dw NMI ;when an NMI happens (once per frame if enabled) the + ;processor will jump to the label NMI: + .dw RESET ;when the processor first turns on or is reset, it will jump + ;to the label RESET: + .dw 0 ;external interrupt IRQ is not used in this tutorial + + +;;;;;;;;;;;;;; + + + .bank 2 + .org $0000 + .incbin "mario.chr" ;includes 8KB graphics file from SMB1 diff --git a/controller/controller.fns b/controller/controller.fns new file mode 100644 index 0000000..24dc3d8 --- /dev/null +++ b/controller/controller.fns @@ -0,0 +1,26 @@ +; controller.asm +LoadSpritesLoop = $C059 +LoadPalettes = $C03D +LatchController = $C07B +Forever = $C06E +palette = $E000 +ReadLeftDone = $C10E +LoadPalettesLoop = $C04C +ReadUp = $C091 +ReadDown = $C0A8 +ReadRightDone = $C141 +ReadDownDone = $C0DB +SkipSeveralButtons = $C085 +LoadSprites = $C057 +vblankwait1 = $C014 +ReadUpDone = $C0A8 +vblankwait2 = $C038 +clrmem = $C019 +RESET = $C000 +sprites = $E028 +ReadLeft = $C0DB +mario_sprite_addresses_vert = $E020 +NMI = $C071 +ReadRight = $C10E +MoveSpritesLoop = $C09A +mario_sprite_addresses_horiz = $E024 diff --git a/controller/controller.nes b/controller/controller.nes new file mode 100644 index 0000000..b32ea5e Binary files /dev/null and b/controller/controller.nes differ diff --git a/controller/mario.chr b/controller/mario.chr new file mode 100644 index 0000000..d150ccc Binary files /dev/null and b/controller/mario.chr differ diff --git a/palette/palette.asm b/palette/palette.asm index 5c1bb9e..a4936bf 100644 --- a/palette/palette.asm +++ b/palette/palette.asm @@ -54,8 +54,9 @@ vblankwait2: ; Second wait for vblank, PPU is ready after this PaletteData: .db $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F .db $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C - ; using a loop, load the values stored in PaletteData into the background and sprite palettes LDX #$00 + +; using a loop, load the values stored in PaletteData into the background and sprite palettes LoadPalettesLoop: LDA PaletteData, x STA $2007 ; store the value of the Accumulator in the next memory slot in the PPU (referred to by this 'port' located at $2007) @@ -63,13 +64,41 @@ LoadPalettesLoop: CPX #$20 ; is X == 32? BNE LoadPalettesLoop +; load sprites +LDA #$00 +STA $2003 ; set the low byte of the RAM address +LDA #$02 +STA $4014 ; set the high byte of the RAM address + +; the DMA (direct memory address) transfer will start automatically after the above is executed. +; +; each sprite needs 4 bytes of data for its position and tile info: +; 1. Y position (vertical) -- $00 is the top, $EF is the bottom +; 2. tile number (0 to 256) +; 3. attributes (color and display info) +; 7 flip vertical +; 6 flip horiz +; 5 priority (0: in front of BG, 1: behind it) +; 1 and 0: color palette of sprite -- choose four of the 16 colors +; 4. X position (horiz) +; there are 64 bytes of sprite memory, four bytes for each of the 16 sprites. They're located at $0200-$02FF. +; +; set up the sprite data +; +LDA #$80 +STA $0200 ; put sprite 0 in center ($80) of screen vertically +STA $0203 ; put sprite 0 in center ($80) of screen horizontally +LDA #$00 +STA $0201 ; tile number = 0 +STA $0202 ; color palette = 0, no flipping + +NMI: + Forever: JMP Forever ;jump back to Forever, infinite loop -NMI: - RTI ;;;;;;;;;;;;;; diff --git a/palette/palette.nes b/palette/palette.nes index 35b65af..ccc18f6 100644 Binary files a/palette/palette.nes and b/palette/palette.nes differ diff --git a/sprites/mario.chr b/sprites/mario.chr new file mode 100644 index 0000000..d150ccc Binary files /dev/null and b/sprites/mario.chr differ diff --git a/sprites/sprites.asm b/sprites/sprites.asm new file mode 100644 index 0000000..68e6638 --- /dev/null +++ b/sprites/sprites.asm @@ -0,0 +1,116 @@ + .inesprg 1 ; 1x 16KB PRG code + .ineschr 1 ; 1x 8KB CHR data + .inesmap 0 ; mapper 0 = NROM, no bank swapping + .inesmir 1 ; background mirroring + + +;;;;;;;;;;;;;;; + + + .bank 0 + .org $C000 +RESET: + SEI ; disable IRQs + CLD ; disable decimal mode + LDX #$40 + STX $4017 ; disable APU frame IRQ + LDX #$FF + TXS ; Set up stack + INX ; now X = 0 + STX $2000 ; disable NMI + STX $2001 ; disable rendering + STX $4010 ; disable DMC IRQs + +vblankwait1: ; First wait for vblank to make sure PPU is ready + BIT $2002 + BPL vblankwait1 + +clrmem: + LDA #$00 + STA $0000, x + STA $0100, x + STA $0300, x + STA $0400, x + STA $0500, x + STA $0600, x + STA $0700, x + LDA #$FE + STA $0200, x ;move all sprites off screen + INX + BNE clrmem + +vblankwait2: ; Second wait for vblank, PPU is ready after this + BIT $2002 + BPL vblankwait2 + + + +; ************** NEW CODE **************** +LoadPalettes: + LDA $2002 ; read PPU status to reset the high/low latch + LDA #$3F + STA $2006 ; write the high byte of $3F00 address + LDA #$00 + STA $2006 ; write the low byte of $3F00 address + LDX #$00 +LoadPalettesLoop: + LDA palette, x ;load palette byte + STA $2007 ;write to PPU + INX ;set index to next byte + CPX #$20 + BNE LoadPalettesLoop ;if x = $20, 32 bytes copied, all done + + + + LDA #$20 + STA $0200 ; put sprite 0 in center ($80) of screen vert + STA $0203 ; put sprite 0 in center ($80) of screen horiz + LDA #$01 + STA $0201 ; tile number = 0 + LDA #$02 + STA $0202 ; color = 0, no flipping + + LDA #%10000000 ; enable NMI, sprites from Pattern Table 0 + STA $2000 + + LDA #%00010000 ; enable sprites + STA $2001 + +Forever: + JMP Forever ;jump back to Forever, infinite loop + + + +NMI: + LDA #$00 + STA $2003 ; set the low byte (00) of the RAM address + LDA #$02 + STA $4014 ; set the high byte (02) of the RAM address, start the transfer + + RTI ; return from interrupt + +;;;;;;;;;;;;;; + + + + .bank 1 + .org $E000 +palette: + .db $0F,$31,$32,$33,$0F,$35,$36,$37,$0F,$39,$3A,$3B,$0F,$3D,$3E,$0F + .db $0F,$1C,$15,$14,$0F,$02,$38,$3C,$0F,$1C,$15,$14,$0F,$02,$38,$3C + + + .org $FFFA ;first of the three vectors starts here + .dw NMI ;when an NMI happens (once per frame if enabled) the + ;processor will jump to the label NMI: + .dw RESET ;when the processor first turns on or is reset, it will jump + ;to the label RESET: + .dw 0 ;external interrupt IRQ is not used in this tutorial + + +;;;;;;;;;;;;;; + + + .bank 2 + .org $0000 + .incbin "mario.chr" ;includes 8KB graphics file from SMB1 diff --git a/sprites/sprites.fns b/sprites/sprites.fns new file mode 100644 index 0000000..f7bbc2c --- /dev/null +++ b/sprites/sprites.fns @@ -0,0 +1,10 @@ +; sprites.asm +LoadPalettes = $C03D +Forever = $C073 +palette = $E000 +LoadPalettesLoop = $C04C +vblankwait1 = $C014 +vblankwait2 = $C038 +clrmem = $C019 +RESET = $C000 +NMI = $C076 diff --git a/sprites/sprites.nes b/sprites/sprites.nes new file mode 100644 index 0000000..1b4804a Binary files /dev/null and b/sprites/sprites.nes differ