Results 1 to 2 of 2

Thread: NES 6502 Programming Tutorial - Part 2: Palette Setup Plus Colored Background

  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 2: Palette Setup Plus Colored Background

    Hey and welcome back to my NES programming tutorial series. Now that you got a taste of it, it's time to get out of that dull grey background and finally add some color! But before we can setup the color, well need a palette. You can either load a palette from file, or you can manually type out palette data and load it from that. In our case, well just manually code it. The NES has a color palette of 54 colors, with room for 64. However, only 25 colors can be used at a time. Not to mention I heard it's not a good idea to use color $3D for some reason. Probably cause different TV's or chipsets show black instead of grey.



    Using 2 palettes, you can load upto a 16 color palette for image data and 16 color palette for sprite data. However, since the first color (the transparency color) mirrors every 4 bytes starting at $3F00 at $3F04, $3F08, $3F0C for one palette, and $3F10, $3F14, $3F18, $3F1C for the other palette, you now only have 13 colors per palette, well 13 and 12 since the first one mirrored all across, giving you a total of 25 colors, which by the way isn't RGB. The NES really doesn't have an RGB system built in, and the colors can vary from TV to TV. On top of that, each 16 color palette section is actually 4 semi palettes which consists of 4 colors; transparency plus 3 other colors. So you can only select 1 palette out of 4 per 8x8 pixel region using the attribute table, no matter if you are drawing the background or a sprite.

    Since I'm getting anxious to show you some code, were going to use the same palette as Super Mario Bros. Level 1-1
    Code:
    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
    Basically these are the colors we are using:



    And just so you don't have to go back and copy and paste, here is the old code from the last tutorial:

    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
      
    ;;;;;;;;;;;;;;;
    
      .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
    
    Foreverloop:
      JMP Foreverloop     ;jump back to Forever, infinite loop
    
    NMI:
      RTI
    ;;;;;;;;;;;;;;  
    
      .bank 1
      .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
    Where it says bank 1 is where we will put our palette database over in the origin of $E000

    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
      
    ;;;;;;;;;;;;;;;
    
      .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
    
    Foreverloop:
      JMP Foreverloop     ;jump back to Forever, infinite loop
    
    NMI:
      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
    
    ;;;;;;;;;;;;;;  
    
      .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
    After the vblankwait period 2 we need to load the palette into the address $3F00-$3F0F for the background palette. Just for now since we don't have sprites yet. But in order to do this, well need to reset the high low latch over in the address register $2002 (the PPU status register) which is Read-Only to High. Since the 6502 processor (or in our case the 2A03) is little endian, I kinda reversed it to big endian. So we are going to write $3F to the address $2006 (VRAM address Register 2, Write-Only) which should toggle the latch to Low, and then we are going to write $00 to $2006 again. This let's it know that we are going to change the palette. (Fun fact: This can be done during gameplay at anytime, so you are not limited to the palette you have.) Then we are going to load the palette values one by one from the data base using a loop and write them to register $2007, officially loading the palette.

    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
      
    ;;;;;;;;;;;;;;;
    
      .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
    
    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 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 - copying 16 bytes = 4 sprites
      BNE LoadPalettesLoop  ; Branch to LoadPalettesLoop if compare was Not Equal to zero
    
    
    Foreverloop:
      JMP Foreverloop     ;jump back to Forever, infinite loop
    
    NMI:
      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
    
    ;;;;;;;;;;;;;;  
    
      .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
    Go ahead and compile it using NESASM3 (resort to tutorial one if you didn't get a chance to see it to know what I'm talking about), and run it through the NES emulator FCEUXD SP or NO$NES. You should see a blue background! If you wanna mess around, you can feel free to change the palette values. Well this concludes my tutorial. Next time I am going to show you how to get a sprite to appear on screen. See you next time
    Attached Files Attached Files

  2. #2
    New Member
    Join Date
    Sep 2020
    Posts
    1

    Re: NES 6502 Programming Tutorial - Part 2: Palette Setup Plus Colored Background

    Thanks for writing up these tutorials. I'm trying to learn 6502asm from scratch from these (and your Atari 2600 tutorial), but I'm having some difficulty understand what your palette-loading code does.

    I think there might be typos or omissions in the codeblock you provided above.

    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              ; start out at 0
    Should one of those STA codes point to $2005 rather than $2006? In Part 1, your memory map says that these are VRAM address register 1 and register 2, but here it looks like you're writing 3F to $2006 and then immediately overwriting it with 00. Which of $2005 or $2006 is the low register?

    Code:
    LoadPalettesLoop:
      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 - copying 16 bytes = 4 sprites
      BNE LoadPalettesLoop  ; Branch to LoadPalettesLoop if compare was Not Equal to zero
    This block of code looks like it will write each of the 16 colors you defined in background_palette to the same VRAM location, $3F00. That is, you're incrementing X so that the next LDA will grab the next color from your palette table, but you never increment the low VRAM address register so that the next color will actually be loaded into the next VRAM location. Am I correct here, or does the 6502 know to automatically advance?

    Also, what *is* the "high low latch"? It could be that I'm misunderstanding how this works because putting a value into $2006 (or reading one from $2002?) somehow changes the system state such that the next write to $2006 does something different.

    EDIT: I withdraw both questions, there are no mistakes here. The read/write address for VRAM is set by consecutive writes to $2006, and reading from $2002 ensures that the first write will be interpreted as the high byte. On my second question, the PPU *does* know to advance the read/write address after an operation, with the increment determined by bit 2 of $2000.
    Last edited by OneWingedAngel; Sep 3rd, 2020 at 09:38 AM.

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