It is currently Tue Jul 23, 2019 12:40 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Fri Feb 22, 2019 10:15 am 
Offline

Joined: Fri Feb 22, 2019 10:05 am
Posts: 12
I'm a complete newbie when it comes to managing the PPU. My goal is to put a single sprite anywhere on the screen and nothing more. I'm using the same assembly file to try to pull this off. So far, neither of my attempts were successful, nor could I even make the screen black. I'm using a code I found from a YouTuber named Michael Chiaramonte and here's what it looks like:
Code:
.segment "HEADER"
    .byte "NES"
    .byte $1a
    .byte $02
    .byte $01
    .byte %00000000
    .byte $00
    .byte $00
    .byte $00
    .byte $00
    .byte $00,$00,$00,$00,$00

.segment "STARTUP"
.segment "ZEROPAGE"
flag: .res 1
counter: .res 1
.segment "CODE"

WAITVBLANK:
:
    BIT $2002
    BPL :-
    RTS

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

  JSR WAITVBLANK

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

  LDA #%10001000
  STA flag
   
  JSR WAITVBLANK

  LDA #%00000000
  STA counter
  STA $2001
  LDA #%10001000
  STA $2000
  LDA #$3F
  STA $2006
  LDA #$00
  STA $2006
  STA $2007
  CLI
Forever:
  JMP Forever 
 
VBLANK:
  INC counter
  LDA counter
  CMP #$3C
  BNE SkipColorChange
  LDA flag
  EOR #%10000000
  STA flag
  STA $2001
  LDA #$00
  STA counter
 SkipColorChange:
  RTI

.segment "VECTORS"
    .word VBLANK
    .word RESET
    .word 0

.segment "CHARS" 
 

I know it alternates the screen from gray to a grayish blue at intervals, but that's not the goal. I'm trying to figure out what to type in the code to make a sprite appear. I've made a "ball.chr" file which I've attached here. Can anyone help me here? Thanks!


Attachments:
ball.chr [8 KiB]
Downloaded 160 times
Top
 Profile  
 
PostPosted: Fri Feb 22, 2019 11:09 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
One important thing to note is that in this program, sprite rendering is permanently disabled. Your "flag" variable, which is periodically written to PPU register $2001, is configured to display the background but not sprites. To show sprites, you need to set bit 4 (i.e. change #%10001000 to #%10011000).

The OAM is what tells the PPU which sprites go where. It's a block of 256 bytes that contains information about the 64 sprites the NES is able to display, so 4 bytes per sprite. The meaning of these 4 bytes is explained in the wiki. What you have to do is write the information you want to send to OAM somewhere in RAM (most commonly $0200-$02FF), and then, in the NMI handler, request an OAM DMA to have this information copied to the PPU.

First, there are a few important changes you have to make in order to fix the code you have:

1- Get rid of the CLI. It's not necessary and can in fact cause your program to crash.

2- Only enable NMIs once you have fully initialized everything, otherwise an NMI could fire before you're done setting things up. The $2000 write that enables NMIs should be the last thing before your forever loop, so move it down to where the CLI was.

Once those problems are fixed, you need to do the following in order to display a sprite:

1- change the initialization value of the "flag" variable from %10001000 to %10011000, so that the PPU will display sprites;

2- After configuring the background color, also configure a sprite palette, otherwise the sprites won't display correctly. Sprite palettes begin at PPU $3F10, but the first color is transparent, so you can start at $3F01. Something like this:

Code:
  lda #$3F
  sta $2006
  lda #$01
  sta $2006
  lda #$07 ;first color
  sta $2007
  lda #$17 ;second color
  sta $2007
  lda #$37 ;third color
  sta $2007


3- before enabling NMIs, write the information about the sprite you want to display to page $0200 in RAM (refer to the wiki to see what each byte means). For example:

Code:
  lda #$70 ;Y coordinate
  sta $0200
  lda #$03 ;tile number
  sta $0201
  lda #$00 ;first palette, no flipping
  sta $0202
  lda #$80 ;X coordinate
  sta $0203


4- in your NMI handler, before everything, request a sprite DMA to copy the OAM data from RAM to the PPU:

Code:
  lda #$00
  sta $2003 ;have the PPU point to the first byte of OAM
  lda #$02
  sta $4014 ;tell the PPU to copy 256 bytes from page 2 ($0200) to OAM


This should be enough to put a sprite on the screen.


Top
 Profile  
 
PostPosted: Fri Feb 22, 2019 11:37 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
Note that this will place a single 8x8 sprite on the screen. If you want to display a larger character, you'll need to write additional sprite information to $0204-$0207, $0208-$020b, and so on, to form a larger group of 8x8 sprites.

Also note that since you'll be setting up $0200-$02FF only once and never changing it, the sprites will remain stationary. If you want them to move, you'll need to update the sprite positions in $0200-$02FF every frame. This would happen inside the forever loop. Keep in mind that once you add code to the forever loop you'll need to implement some form of synchronization between that loop and the NMI handler, so that they don't interfere with each other's work.

The simplest thing you can do is create a "FrameReady" flag, that you initialize to $00/false somewhere before the main loop. Then in the main loop, do whatever you need to do and at the end set FrameReady to $ff/true, to let the NMI handler know that it's safe for it to send updates to the PPU. If it's not safe, the NMI handler does nothing, otherwise it resets the flag to let the main loop know it can move on and process the next frame. Something like this:

Code:
  ;(initialization here)
  lda #$00
  sta FrameReady ;initialize FrameReady to false
  ;(more initialization here)
Forever:
  ;(game logic here)
  dec FrameReady ;set FrameReady to true
Wait:
  bit FrameReady
  bmi Wait ;keep waiting while bit 7 is set
  jmp Forever ;go process the next frame

Code:
VBLANK:
  bit FrameReady
  bpl Return ;skip everything if bit 7 is clear
  ;(sprite DMA and other updates here)
  inc FrameReady ;set FrameReady to false
Return:
  rti


Top
 Profile  
 
PostPosted: Fri Feb 22, 2019 8:06 pm 
Offline

Joined: Fri Feb 22, 2019 10:05 am
Posts: 12
tokumaru wrote:
Note that this will place a single 8x8 sprite on the screen. If you want to display a larger character, you'll need to write additional sprite information to $0204-$0207, $0208-$020b, and so on, to form a larger group of 8x8 sprites.

Also note that since you'll be setting up $0200-$02FF only once and never changing it, the sprites will remain stationary. If you want them to move, you'll need to update the sprite positions in $0200-$02FF every frame. This would happen inside the forever loop. Keep in mind that once you add code to the forever loop you'll need to implement some form of synchronization between that loop and the NMI handler, so that they don't interfere with each other's work.

The simplest thing you can do is create a "FrameReady" flag, that you initialize to $00/false somewhere before the main loop. Then in the main loop, do whatever you need to do and at the end set FrameReady to $ff/true, to let the NMI handler know that it's safe for it to send updates to the PPU. If it's not safe, the NMI handler does nothing, otherwise it resets the flag to let the main loop know it can move on and process the next frame. Something like this:

Code:
  ;(initialization here)
  lda #$00
  sta FrameReady ;initialize FrameReady to false
  ;(more initialization here)
Forever:
  ;(game logic here)
  dec FrameReady ;set FrameReady to true
Wait:
  bit FrameReady
  bmi Wait ;keep waiting while bit 7 is set
  jmp Forever ;go process the next frame

Code:
VBLANK:
  bit FrameReady
  bpl Return ;skip everything if bit 7 is clear
  ;(sprite DMA and other updates here)
  inc FrameReady ;set FrameReady to false
Return:
  rti


Thanks for arming me with knowledge! However, I'm still having trouble trying to pull this off. Think you could paste the entire code for me to try from the header to the chars?


Top
 Profile  
 
PostPosted: Sat Feb 23, 2019 12:55 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
Sorry, I already gave you step by step instructions, with actual code and where to put it. You need to be able to follow simple instructions and understand what each part of the code does if you expect to have any chance at succeeding in this. Giving you the entire code will teach you nothing.


Top
 Profile  
 
PostPosted: Sat Feb 23, 2019 12:25 pm 
Offline

Joined: Fri Feb 22, 2019 10:05 am
Posts: 12
I followed all the instructions you gave me and this is what I have (that doesn't work):
Code:
.segment "HEADER"
    .byte "NES"
    .byte $1a
    .byte $02
    .byte $01
    .byte %00000000
    .byte $00
    .byte $00
    .byte $00
    .byte $00
    .byte $00,$00,$00,$00,$00

.segment "STARTUP"
.segment "ZEROPAGE"
flag: .res 1
counter: .res 1
.segment "CODE"

WAITVBLANK:
:
    BIT $2002
    BPL :-
    RTS

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

  JSR WAITVBLANK

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

  LDA #%10011000
  STA flag
   
  JSR WAITVBLANK

  LDA #%00000000
  STA counter
  STA $2001
  LDA #%10001000
  STA $2000
   lda #$3F
  sta $2006
  lda #$01
  sta $2006
  lda #$07 ;first color
  sta $2007
  lda #$17 ;second color
  sta $2007
  lda #$37 ;third color
  sta $2007
  lda #$70 ;Y coordinate
  sta $0200
  lda #$03 ;tile number
  sta $0201
  lda #$00 ;first palette, no flipping
  sta $0202
  lda #$80 ;X coordinate
  sta $0203
  LDA #$00
  STA $2006
  STA $2007
 
Forever:
  JMP Forever 
 
VBLANK:
  INC counter
  LDA counter
  CMP #$3C
  BNE SkipColorChange
  LDA flag
  EOR #%10000000
  STA flag
  STA $2001
  LDA #$00
  STA counter
 SkipColorChange:
  RTI
 
nmi:
   lda #$00
   sta $2003 ;have the PPU point to the first byte of OAM
   lda #$02
   sta $4014 ;tell the PPU to copy 256 bytes from page 2 ($0200) to OAM
.segment "VECTORS"
    .word VBLANK
    .word RESET
    .word 0

.segment "CHARS" 
   .incbin "ball.chr"

Am I missing something? I'm sorry but I'm utterly confused. It appears as though drawing a simple dot on the screen is incredibly harder to program than it seems.


Top
 Profile  
 
PostPosted: Sat Feb 23, 2019 1:03 pm 
Offline

Joined: Fri Feb 24, 2012 12:09 pm
Posts: 942
Try using a debugger, and check if it's executing your code, and check if you can see your sprite and colors in tile vewer, and oam viewer, and palette viewer.


Top
 Profile  
 
PostPosted: Sat Feb 23, 2019 3:48 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
You did not follow my instructions correctly. Your NMI handler is called "VBLANK" (your choice), not "nmi", so the code under the "nmi" label will never run. The DMA request must be under the "VBLANK" label, which is your NMI handler.

You also forgot to move the NMI activation (i.e. the $2000 write) to just before the forever loop. Only enable NMIs once the initialization is 100% over, otherwise an NMI might fire before the initialization is over and screw things up. I explained this before.

Another thing that's not correct are the writes to $2006 and $2007 right before the forever loop. That's probably a leftover from when you were setting the background color, but that's now incomplete without the first $2006 write. Either remove those writes completely and don't set the background color (it's not necessary if you just want to see the sprite) or do a proper palette write ($3F to $2006, $00 to $2006, $00 to $2007).

You can't just guess and write to registers left and right without understanding what the purposes of those writes are. The documentation is there to explain what each register does and why they must be written in certain ways at certain times. If you don't understand the documentation, ask for clarification here. If you don't understand what you're doing and just get things working by pure luck, you'll not learn much.


Top
 Profile  
 
PostPosted: Sun Feb 24, 2019 11:18 am 
Offline

Joined: Fri Feb 22, 2019 10:05 am
Posts: 12
tokumaru wrote:
You did not follow my instructions correctly. Your NMI handler is called "VBLANK" (your choice), not "nmi", so the code under the "nmi" label will never run. The DMA request must be under the "VBLANK" label, which is your NMI handler.

You also forgot to move the NMI activation (i.e. the $2000 write) to just before the forever loop. Only enable NMIs once the initialization is 100% over, otherwise an NMI might fire before the initialization is over and screw things up. I explained this before.

Another thing that's not correct are the writes to $2006 and $2007 right before the forever loop. That's probably a leftover from when you were setting the background color, but that's now incomplete without the first $2006 write. Either remove those writes completely and don't set the background color (it's not necessary if you just want to see the sprite) or do a proper palette write ($3F to $2006, $00 to $2006, $00 to $2007).

You can't just guess and write to registers left and right without understanding what the purposes of those writes are. The documentation is there to explain what each register does and why they must be written in certain ways at certain times. If you don't understand the documentation, ask for clarification here. If you don't understand what you're doing and just get things working by pure luck, you'll not learn much.


I've tried all that, but it STILL won't work. I don't understand how something as simple as putting a single sprite on the screen could be such a super complicated task. Do you know anyone who could explain how the NES architecture works without using so much computer lingo I hardly understand?


Top
 Profile  
 
PostPosted: Sun Feb 24, 2019 11:33 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
It's not that complicated, you just need to understand the purpose of each piece of code, why everything is necessary. If that's not clear, you can ask for clarification.

It would help if you commented your code like this:

Code:
  ;set the background color
  lda #$3f
  sta $2006
  lda #$00
  sta $2006
  lda #$07 ;this is the color
  sta $2007

  ;enable NMIs
  lda #%10000000
  sta $2000

What I mean is, use vertical spaces to separate individual tasks, and always comment each task, with extra comments line by line when necessary.

This will help a lot with understanding the structure of the program and the sequence of the tasks. I'm an experienced coder and I have trouble following your programs because everything is lumped together.

Another thing that really helps is debugging your code in an emulator, to see if things are really running in the expected order.


Top
 Profile  
 
PostPosted: Sun Feb 24, 2019 8:27 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
Let's try this: comment the whole program like in the example I gave above, visually breaking up the code into separate tasks, so that we can better see the sequence of things that need to be done before the sprite is displayed. I'll help you fix what's wrong then, it should be easier if the program is well documented. Don't worry if you're unsure about some parts, we can help you improve the comments too.


Top
 Profile  
 
PostPosted: Mon Feb 25, 2019 5:14 am 
Offline
User avatar

Joined: Fri May 08, 2015 7:17 pm
Posts: 2536
Location: DIGDUG
And he's also not setting the #0 Sprite palette, which an emulator would default them to zero (gray), which is the same as the background color currently.

The 0th Sprite palette is addresses 3f11-3f13

_________________
nesdoug.com -- blog/tutorial on programming for the NES


Top
 Profile  
 
PostPosted: Mon Feb 25, 2019 6:17 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11376
Location: Rio de Janeiro - Brazil
That was my mistake. I told him that sprite palettes started at $3F10 and that he could skip the transparent color, and mistakenly said he could start from $3F01. I meant $3F11, sorry for that.


Top
 Profile  
 
PostPosted: Mon Feb 25, 2019 6:18 am 
Offline
User avatar

Joined: Thu Sep 15, 2016 6:29 am
Posts: 896
Location: Denmark (PAL)
Tompis1995 wrote:
Do you know anyone who could explain how the NES architecture works without using so much computer lingo I hardly understand?

Learning the NES architecture is learning computer lingo.
Without knowing what the words mean, you'll never understand the documentation. Without understanding the documentation you will never now how to do anything on the NES - such as displaying a sprite.

If there are any words you don't understand, just ask. Tokumaru already underlined this twice.

Just to clarify a few of the most important ones used here:

PPU: This is just the name of the NES's video chip.

Register: Essentially these are ways to write values to the hardware. On the CPU the most important registers you are working with are of course A, X and Y, but the NES itself has hardware registers for chips such as the PPU, which are used to communicate with it, and tell it what to do. These individual registers are accessed by writing to specific addresses which are always the same.

OAM: "Object Attribute Memory", like Tokumaru explained, this is really just 256 bytes of memory that describe every sprite displayed on an individual frame. "Object" is just the NES's name for hardware sprites.

DMA request: "DMA" is essentially just a way to copy a bunch of data from one place to another. On the NES, there is only one DMA, which has the job of copying data into the PPU's OAM table. When Toku talks about writing to addresses $0200, $0201, etc. that's just a copy of what you want in the PPU's OAM. A single write to the DMA register will make it copy all of this data into the OAM.

Vblank: Any time code is executing on the NES, the console might be in the processing of drawing a visible frame, as this takes time - almost the entire time between the start of two frames. Vblank refers to the short "empty" period between two visible frames in which the NES is actually not drawing anything. This is important, as it's the only time you have to communicate with the PPU during gameplay.
It is however not important during your initialization/reset routine, before you turn on rendering.

Interrupt: To simplify it, this is a routine that can be executed at any time while other code is running. Once the routine ends (with the "RTI" instruction), the code will resume executing where it was.

NMI: This is an interrupt that, in the case of the NES, is run once every frame, during the VBLANK period. So typically your "NMI handler" does the job of communicating with video memory. In this case we are using it to refresh data in our OAM table.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group