So, I am VERY new to 6502 assembly code but I found a rather informative YouTube channel that's helped out a bit:
The Zero Pages (https://www.youtube.com/playlist?list=P ... PqwliHEuEk)
I've been following along, making notes, creating bookmarks, downloading programs, etc. and I'm beginning to understand things (sort of). Well, in "Episode 4 - Hello Mario" I downloaded the files to follow along with the video (except I started with blank files for the coding bit). I got to the very end of the video and ran my newly created ROM (which should just have a blue background and a jumping Mario sprite on screen) and I missed something. It runs... but all I get is a blue screen. I don't know if that's supposed to be right (his screen looked a bit darker than mine). I have went over my code a few times (even comparing it side by side with the originally downloaded file) and I can't find what I've done wrong.
WARNING: I put comments on every line because I didn't want to forget what something does later on.... prepare yourself for ALOT of green (or whatever your highlight color is....
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.segment "HEADER" ;must have for emulator
.byte "NES" ;structures ROM to resemble physical cartridge to emulator
.byte $1a ;signature for verification by emulator
.byte $02 ;two 16KB PRG-ROM
.byte $01 ;one 8KB CHR-ROM
.byte %00000000 ;mapper and mirroring - in binary for ease of reference/modification
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00, $00, $00, $00, $00 ;filler bytes for complete header
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.segment "ZEROPAGE" ;sets most significant bit to zero
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.segment "STARTUP" ;
Reset: ;
SEI ;disable all interrupts
CLD ;disable decimal mode
LDX #$40 ;disable sound IRQ - load value $40 (%0100 0000 / 64) into X register
STX $4017 ;disable sound IRQ - store value in memory address $4017
LDX #$FF ;initialize the stack register - load value #FF into X register
TXS ;initialize the stack register - transfer X value to stack
INX ;increment X value by one to cause rollover to zero (#$FF / %1111 1111 / 255 + 1 => #$00 / %0000 0000 / 0)
STX $2000 ;zero out the PPU registers
STX $2001 ;zero out the PPU registers
STX $4010 ;disable PCM channel
: ;wait for V-Blank - anonymous label
BIT $2002 ;wait for V-Blank - check value of bit seven in $2002 byte
BPL :- ;wait for V-Blank - jump to anonymous label if BIT is 0 (- jump to previous anonymous label, + jump to next anonymous label)
TXA ;transfer value of X register to A register
CLEARMEM: ;clear memory - label for loop to clear memory
STA $0000, X ;clear memory - X register index $0000 => 00FF
STA $0100, X ;clear memory - X register index $0100 => 01FF
;SKIP ADRESSES -- will be used for sprite data
STA $0300, X ;clear memory - X register index $0300 => 03FF
STA $0400, X ;clear memory - X register index $0400 => 04FF
STA $0500, X ;clear memory - X register index $0500 => 05FF
STA $0600, X ;clear memory - X register index $0600 => 06FF
STA $0700, X ;clear memory - X register index $0700 => 07FF
LDA #$FF ;sprite data - load #FF into A register
STA $0200, X ;sprite data - store value in X register index $0200 => $02FF
LDA #$00 ;clear memory - reset A register back to #00 for clear nemory loop
INX ;zero flag - increment X register (#$FF + 1 = #$00 with zero flag set to 1 / otherwise zero flag zet to 0)
BNE CLEARMEM ;zero flag - branch if not equal
: ;wait for V-Blank - anonymous label
BIT $2002 ;wait for V-Blank - check value of bit seven in $2002 byte
BPL :- ;wait for V-Blank - jump to anonymous label if BIT is 0 (- jump to previous anonymous label, + jump to next anonymous label)
LDA #$02 ;load sprite data - load A register with value from MSB (most significant byte)
STA $4014 ;load sprite data - store A register data in adress $4014 (OAM-DMA [Object Attribute Memory])
NOP ;load sprite data - no operation (wait command to allow PPU time to load and store data)
LDA #$3F ;access PPU - load MSD into A register
STA $2006 ;access PPU - store value in PPU address register (PPU-ADDR)
LDA #$00 ;access PPU - load LSD (least significant digit) of zero into A register
STA $2006 ;access PPU - store value in PPU address register (PPU-ADDR)
LDX #$00 ;load sprite data - initialize X register (prepare for sprite palette data loop)
LoadPalettes: ;load palette colors - label for palette loop
LDA PaletteData, X ;load palette colors - load "PaletteData" into A register then increment X register (PaletteData Array)
STA $2007 ;load palette colors - store A register to PPUDATA that auto increments ($3F00 => $3F1F)
INX ;load palette colors - increment X register
CPX #$20 ;load palette colors - compare value X to value 32 (decimal)
BNE LoadPalettes ;load palette colors - BNE to label
LDX #$00 ;load sprite data - initialize X register (prepare for sprite palette data loop)
LoadSprites: ;load sprites - label for sprite loop
LDA SpriteData, X ;load sprites - load "SpriteData" into A register then increment X register (SpriteData Array)
STA $0200 ;load sprites - store value in $0200
INX ;load sprites - increment X register
CPX #$20 ;load sprites - compare value X to value 32 (decimal)
BNE LoadSprites ;load sprites - BNE to label
CLI ;enable interrupts
LDA #%10010000 ;VPHB SINN -- Enable NMI / Background uses second sprite set
;----------------------------------------------------------------------------------------;
;V -- Generate an NMI at V-Blank (0:off, 1:on)
;P -- PPU master/slave select (0:read backdrop from EXT pins, 1:output color on EXT pins)
;H -- Sprite Size (0:8x8 pixels, 1:8x16 pixels)
;B -- Background table address (0:$0000, 1:$1000)
;S -- Sprite Pattern Table for 8x8 sprites (0:$0000, 1:$1000) [ignore if 8x16 pixels]
;I -- VRAM adderss increment per CPU read/write of PPUDATA
;N -- Base Nametable Address (0:$2000, 1:$2400, 2:$2800, 3:$2C00)
;N -- Base Nametable Address (0:$2000, 1:$2400, 2:$2800, 3:$2C00)
;----------------------------------------------------------------------------------------;
STA $2000 ;
LDA #%00011110 ;BGRs bMmG (Toggle 0:off, 1:on) -- enable sprites and background and show both in leftmost 8 pixels
;----------------------------------------------------------------------------------------;
;B -- Emphasize Blue
;G -- Emphasize Green
;R -- Emphasize Red
;s -- Show sprites
;b -- Show background
;M -- Show sprites in leftmost 8 pixels of screen
;m -- Show background in leftmost 8 pixels of screen
;G -- Grayscale
;----------------------------------------------------------------------------------------;
STA $2001 ;
Loop: ;USED ONLY FOR TUTORIAL PURPOSES --- infinite loop to prevent accidental access to NMI
JMP Loop ;USED ONLY FOR TUTORIAL PURPOSES --- infinite loop to prevent accidental access to NMI
NMI: ;NMI label
LDA #$02 ;copy sprite data from $0200
STA $4014 ;store copied data in PPU memory for display
RTI ;return from interrupt
PaletteData: ;
;----------------------------------------------------------------------;
;SPRITE PALETTE ADRESSES -- $3F00 -- Universal Background Color
;SPRITE PALETTE ADRESSES -- $3F01 - $3F03 -- Background Palette Color 0
;SPRITE PALETTE ADRESSES -- $3F05 - $3F07 -- Background Palette Color 1
;SPRITE PALETTE ADRESSES -- $3F09 - $3F0B -- Background Palette Color 2
;SPRITE PALETTE ADRESSES -- $3F0D - $3F0F -- Background Palette Color 3
;SPRITE PALETTE ADRESSES -- $3F11 - $3F13 -- Sprite Palatte Color 0
;SPRITE PALETTE ADRESSES -- $3F15 - $3F17 -- Sprite Palatte Color 1
;SPRITE PALETTE ADRESSES -- $3F19 - $3F1B -- Sprite Palatte Color 2
;SPRITE PALETTE ADRESSES -- $3F1D - $3F1F -- Sprite Palatte Color 3
;----------------------------------------------------------------------;
.byte $22, $29, $1A, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ;Background Palette Color Data
.byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ;Sprite Palette Color Data
SpriteData: ;
;----------------------------------------;
;SPRITE BYTE DATA (4 BYTES)
;1ST BYTE -- Y coordinate on screen
;2ND BYTE -- Tile number on sprite sheet
;3RD BYTE -- !!! UNKNOWN !!!
;4TH BYTE -- X coordinate on screen
;----------------------------------------;
.byte $08, $00, $00, $08 ;
.byte $08, $01, $00, $10 ;
.byte $10, $02, $00, $08 ;
.byte $10, $03, $00, $10 ;
.byte $18, $04, $00, $08 ;
.byte $18, $05, $00, $10 ;
.byte $20, $06, $00, $08 ;
.byte $20, $07, $00, $10 ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.segment "VECTORS" ;interrupt handlers
.word NMI ;non-maskable interrupt handler
.word Reset ;handle RESET being pressed
;handles special hardware interrupts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.segment "CHARS" ;where pretty s#%t lives (sprite data)
.incbin "hellomario.chr" ;name of YY-CHR file (*.chr)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Syntax Error (I hope...)
Moderator: Moderators
Re: Syntax Error (I hope...)
Welcome!
I see a few (potential) problems.
1. CLI is used, but there is no IRQ vector specified. NMI is used, however SEI/CLI has no effect on that. When IRQ is unused, often people either set it to point to an RTI instruction, or point it to the reset vector. The IRQ vector will be used if the CPU happens to run a BRK instruction (SEI/CLI has no effect on that, either).
2. Before you enter Loop, safe practice would have the STA $2000 be the last instruction. As it is, the next instruction is LDA # / STA $2001. Since this NMI routine doesn't save/preserve the registers in this example, if an NMI were to happen, then you may end up writing garbage ($02, in this case) to $2001.
If you can post the .NES file, that would help with debugging. Without knowing exactly what the CHR file has, I can't tell. It could be working but simply showing blank tiles. In that case, check it in Mesen with the sprite viewer.
--- edit ---
OK, I now see the problem! I missed it at first glance, too.
Indexing was forgotten. STA $0200, X is the intended instruction.
I see a few (potential) problems.
1. CLI is used, but there is no IRQ vector specified. NMI is used, however SEI/CLI has no effect on that. When IRQ is unused, often people either set it to point to an RTI instruction, or point it to the reset vector. The IRQ vector will be used if the CPU happens to run a BRK instruction (SEI/CLI has no effect on that, either).
2. Before you enter Loop, safe practice would have the STA $2000 be the last instruction. As it is, the next instruction is LDA # / STA $2001. Since this NMI routine doesn't save/preserve the registers in this example, if an NMI were to happen, then you may end up writing garbage ($02, in this case) to $2001.
If you can post the .NES file, that would help with debugging. Without knowing exactly what the CHR file has, I can't tell. It could be working but simply showing blank tiles. In that case, check it in Mesen with the sprite viewer.
--- edit ---
OK, I now see the problem! I missed it at first glance, too.
Code: Select all
STA $0200 ;load sprites - store value in $0200
-
- Posts: 3
- Joined: Fri May 19, 2023 4:11 pm
Re: Syntax Error (I hope...)
I didn't know I could post the actual file. I tried using the debugger but I really don't know what I am doing yet. I'll do that next time. The missing X was the problem. You fixed it. Thank you. As for the stuff that is used that doesn't point to anything.... The narrator of the videos I've been watching said something about that but he said that even though he's not writing that stuff in, it would be good practice to get used to including that stuff (I'm working on learning what all of that means. I don't even know what SEI and that does... it's been too long since i've used hex on anything and having to remember when i see the number 20 (for example) it's most likely 32 (depending on the symbol) is throwing me off. I do have one question though... Too many comments or no?
Re: Syntax Error (I hope...)
I'd say that is fully commented, heheh. When you're just starting out, one can never have too many comments, if you're referring to them. I think the natural progression is starting out commenting what the code is doing. As you gain experience the what becomes self-evident and can mostly be described with descriptive label and variable names, then comments end up being more about why the code is doing something.
-
- Posts: 3
- Joined: Fri May 19, 2023 4:11 pm
Re: Syntax Error (I hope...)
When I first learned C++ I was an arrogant little s#%t and didn't use comments at all (well, rarely, at least). I fell on hard times and... skipping over a woe-is-me story... I didn't code for about 3 years (I was still learning when I stopped). Using the comments as "notes for the big test on Friday" helps me learn and remember. I learned that much when I had to start from almost scratch learning C++ again. Once it's nature to me, my comments will shift to help other programmers get what I was trying to do and what direction I took.
Side Note... I've got a whole new respect for programmers from the NES era. I used to think things would have been simpler then (less bytes, less graphics, etc.) so the code must be super simple..... Haha. NO! 6502 is like trying to decipher the rosetta stone - written backwards - under water - in pitch darkness... Baby want baba
Side Note... I've got a whole new respect for programmers from the NES era. I used to think things would have been simpler then (less bytes, less graphics, etc.) so the code must be super simple..... Haha. NO! 6502 is like trying to decipher the rosetta stone - written backwards - under water - in pitch darkness... Baby want baba
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Syntax Error (I hope...)
This may help you later…LDX #$00 ;load sprite data - initialize X register (prepare for sprite palette data loop)
LoadSprites: ;load sprites - label for sprite loop
LDA SpriteData, X ;load sprites - load "SpriteData" into A register then increment X register (SpriteData Array)
STA $0200 ;load sprites - store value in $0200
INX ;load sprites - increment X register
CPX #$20 ;load sprites - compare value X to value 32 (decimal)
BNE LoadSprites ;load sprites - BNE to label
If you run your loops in reverse, that saves space and time.
i.e. this accomplishes the same thing:
Code: Select all
LDX #$20 ;load sprite data - initialize X register to 32 (decimal) (prepare for sprite palette data loop)
LoadSprites: ;load sprites - label for sprite loop
LDA SpriteData, X ;load sprites - load "SpriteData" into A register then decrement X register (SpriteData Array)
STA $0200, X ;load sprites - store value in $0200
DEX ;load sprites - decrement X register
BNE LoadSprites ;load sprites - BNE to label
Yes, 3 cycles is almost 0 cycles, but if you make these small improvements throughout your game, you’ll be very happy. I realize that you don’t have a game yet, but learning this early should benefit you.
NOTE: Be careful when reversing loops; this will not work with LoadPalette bc that loop stores to $2007 and $2007 must be written to sequentially bc the PPU always increments $2007 after each write. (Or vertically, if you ever set your PPU to increment the $2007 address vertically after each write.) $0200 is just RAM and therefore, has no preference about writes to it.
Re: Syntax Error (I hope...)
Immediate compares are 2 cycles, not 3, but the in general, saving cycles inside of a loop can be substantial. Just saving a few cycles here or there often isn't worth it because it's so small compared to the 30,000 cycle frame budget, the amount of work to make these tiny optimizations may be a bad trade-off compared to other improvements you could make to your game, and optimizations sometimes reduce clarity and add risk to the code. However, a few cycles across 32 iterations starts to be meaningful and is worth the squeeze.
Over time, though, you'll likely pick up on these strategies to improve performance and start writing things in a more efficient way the first time. In particular, iterating downward where possible is usually the way to go because of the free zero or negative checks.
Over time, though, you'll likely pick up on these strategies to improve performance and start writing things in a more efficient way the first time. In particular, iterating downward where possible is usually the way to go because of the free zero or negative checks.