Results 1 to 1 of 1

Thread: NES 6502 Programming Tutorial - Part 5: Controller Commands

  1. #1

    Thread Starter
    College Grad!!! Jacob Roman's Avatar
    Join Date
    Aug 2004
    Location
    Miami Beach, FL
    Posts
    5,339

    NES 6502 Programming Tutorial - Part 5: Controller Commands

    Hey and welcome back to my NES 6502 programming tutorial. Today I'm going to show you how to move the sprite using the controller. The NES controller, as you see here, has eight buttons:



    which obviously consists of up, down, left, right, B, A, select, and start. And the NES has two controller ports. But believe it or not, some NES games have 4 player simultaneous play requiring four controllers, which is only possible if you have an NES Four Score or an NES Satellite:



    And there are too many peripherals to count, such as the Zapper, the Power Pad, the Miracle Piano, R.O.B. the Robot, the Power Glove, etc. All of which use different states the controller ports read besides the basic eight. So in this tutorial, I'm only going to focus on Controller 1 which reads at the address $4016. If you wanna attempt Controller 2 in the mix, it's at address $4017. So we are going to rework our Sprite Movement project and change up a couple of things.

    The first step is that we are going to need to latch both the controllers right after the small chunk of code in the NMI:

    Code:
    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
    Next, we will need to read the memory address $4016 in this exact order and check if there has been an input pressed, and remember, it is in this exact order!

    • (1) A
    • (2) B
    • (3) Select
    • (4) Start
    • (5) Up
    • (6) Down
    • (7) Left
    • (8) Right


    As an example, we will proceed directly after the LatchController code starting with A for simplicity:

    Code:
    LatchController:
      LDA #$01
      STA $4016
      LDA #$00
      STA $4016       ; tell both the controllers to latch buttons
    
    ReadA: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                      ; add instructions here to do something when button IS pressed (1)
    
    ReadADone:        ; handling this button is done
    Basically, any action you want to happen when the button is pressed goes in between the BEQ and the subroutine ReadADone. Otherwise it jumps when it is not pressed. And it will continue on to the other buttons in that specific order. So basically, you are adding all this:

    Code:
    LatchController:
      LDA #$01
      STA $4016
      LDA #$00
      STA $4016       ; tell both the controllers to latch buttons
    
    ReadA: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                      ; add instructions here to do something when button IS pressed (1)
    
    ReadADone:        ; handling this button is done
      
    ReadB: 
      LDA $4016       ; player 1 - B
      AND #%00000001  ; only look at bit 0
      BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                      ; add instructions here to do something when button IS pressed (1)
    
    ReadBDone:        ; handling this button is done
    
    ReadSelect: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadSelectDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadSelectDone:
    
    ReadStart: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadStartDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadStartDone:
    
    ReadUp: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadUpDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadUpDone:
    
    ReadDown: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadDownDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadDownDone:
    
    ReadLeft: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      JMP ReadLeftDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadLeftDone:
    
    ReadRight: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      JMP ReadRightDone ; branch to ReadADone if button is NOT pressed (0)
    
    ReadRightDone:
    
    In your sprite movement project, you had a chunk of code to automatically move Mario across the screen towards the right. Go ahead and copy and paste this chunk here that you had:

    Code:
      LDA player_x
      STA $0203
      STA $020B
      TAX
      CLC
      ADC #$08
      STA $0207
      STA $020F
      INX
      STX player_x
    And put it inside the controls for the Right:

    Code:
    ReadRight: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      JMP ReadRightDone ; branch to ReadADone if button is NOT pressed (0)
    
    
    ;Move Mario to the right  
      LDA player_x
      STA $0203
      STA $020B
      TAX
      CLC
      ADC #$08
      STA $0207
      STA $020F
      INX
      STX player_x
      
    ReadRightDone:
    Compile the code using either the batch file, or dragging the asm file into NESASM3, and run it in your emulator. DOH! It's not moving. Looks like we left something out. If the AND result is not equal, well need to jump to the movement code:

    Code:
    ReadRight: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BNE DoRight
      JMP ReadRightDone ; branch to ReadADone if button is NOT pressed (0)
    
    ;Move Mario to the right  
    DoRight:
      LDA player_x
      STA $0203
      STA $020B
      TAX
      CLC
      ADC #$08
      STA $0207
      STA $020F
      INX
      STX player_x
      
    ReadRightDone:
    Now recompile it, and run it. Now Mario moves right....and only right . Well need some code to move him left as well:

    Code:
    ReadLeft: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BNE DoLeft
      JMP ReadLeftDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ;Move Mario to the left
    DoLeft:
      LDA player_x
      STA $0203
      STA $020B
      TAX
      CLC
      ADC #$08
      STA $0207
      STA $020F
      DEX
      STX player_x  
      
    ReadLeftDone:
    Compile and run the code again, and now Mario moves left and right seamlessly. By now your completed code looks like this:

    Code:
      .inesprg 1   ; 1x 16KB PRG code
      .ineschr 1   ; 1x  8KB CHR data
      .inesmap 0   ; mapper 0 = NROM, no bank swapping
      .inesmir 1   ; background mirroring
      
    ;;;;;;;;;;;;;;;
    ;; DECLARE SOME VARIABLES HERE
      .rsset $0000  ;;start variables at ram location 0
      
    player_x  .rs 1  ; .rs 1 means reserve one byte of space  
    ;;;;;;;;;;;;;;;
    
      .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 $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
      
    LoadPalette:
      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
    LoadBackgroundPaletteLoop:
      LDA background_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 #$10              ; Compare X to hex $10, decimal 16
      BNE LoadBackgroundPaletteLoop  ; Branch to LoadBackgroundPaletteLoop if compare was Not Equal to zero
      
      LDX #$00      
            
    LoadSpritePaletteLoop:
      LDA sprite_palette, x     ;load palette byte
      STA $2007					;write to PPU
      INX                   	;set index to next byte
      CPX #$10            
      BNE LoadSpritePaletteLoop  ;if x = $10, all done
    
      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 #$10              ; Compare X to hex $10, decimal 16
      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 0
      STA $2000
      
      LDA #%00010000   ; enable sprites
      STA $2001
      
      LDA $0203
      STA player_x
    
    Foreverloop:
      JMP Foreverloop     ;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
    
    ReadA: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
                      ; add instructions here to do something when button IS pressed (1)
    
    ReadADone:        ; handling this button is done
      
    ReadB: 
      LDA $4016       ; player 1 - B
      AND #%00000001  ; only look at bit 0
      BEQ ReadBDone   ; branch to ReadBDone if button is NOT pressed (0)
                      ; add instructions here to do something when button IS pressed (1)
    
    ReadBDone:        ; handling this button is done
    
    ReadSelect: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadSelectDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadSelectDone:
    
    ReadStart: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadStartDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadStartDone:
    
    ReadUp: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadUpDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadUpDone:
    
    ReadDown: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BEQ ReadDownDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ReadDownDone:
    
    ReadLeft: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BNE DoLeft
      JMP ReadLeftDone   ; branch to ReadADone if button is NOT pressed (0)
    
    ;Move Mario to the left 
    DoLeft:
      LDA player_x
      STA $0203
      STA $020B
      TAX
      CLC
      ADC #$08
      STA $0207
      STA $020F
      DEX
      STX player_x  
      
    ReadLeftDone:
    
    ReadRight: 
      LDA $4016       ; player 1 - A
      AND #%00000001  ; only look at bit 0
      BNE DoRight
      JMP ReadRightDone ; branch to ReadADone if button is NOT pressed (0)
    
    ;Move Mario to the right  
    DoRight:
      LDA player_x
      STA $0203
      STA $020B
      TAX
      CLC
      ADC #$08
      STA $0207
      STA $020F
      INX
      STX player_x
      
    ReadRightDone:
    
      RTI
    
    ;;;;;;;;;;;;;;  
    
      .bank 1
      .org $E000
    background_palette:
      .db $22,$29,$1A,$0F	;background palette 1
      .db $22,$36,$17,$0F	;background palette 2
      .db $22,$30,$21,$0F	;background palette 3
      .db $22,$27,$17,$0F	;background palette 4
      
    sprite_palette:
      .db $22,$16,$27,$18	;sprite palette 1
      .db $22,$1A,$30,$27	;sprite palette 2
      .db $22,$16,$30,$27	;sprite palette 3
      .db $22,$0F,$36,$17	;sprite palette 4
      
    sprites:
         ;vert tile attr horiz
      .db $08, $3A, %00000000, $08   ;sprite 0
      .db $08, $37, %00000000, $10   ;sprite 1
      .db $10, $4f, %00000000, $08   ;sprite 2
      .db $10, $4f, %01000000, $10   ;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
    For an added challenge, try moving Mario up and down as well! I haven't decided what I want to do for my next tutorial yet, but maybe I might touch into drawing background tiles, since animation is too advanced of a jump for you noobies. But I'll eventually get to it. Have an awesome day!

    [EDIT] For more advanced controller tricks, I found this on NESWiki: https://wiki.nesdev.com/w/index.php/Controller_Reading
    Attached Files Attached Files

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width