It is currently Tue Apr 23, 2019 1:15 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 4 posts ] 
Author Message
PostPosted: Mon Mar 18, 2019 5:04 pm 
Offline

Joined: Mon Mar 18, 2019 4:44 pm
Posts: 1
Hello! I'll like some clarifications about some code I wrote right now.

I followed the tutorial of the site https://book.famicom.party/chapters/05-6502assembly.html , and I copy pasted the program that simply changed the background and figured I'll get my hands dirty and decided to write a loop that will go over a bunch of different colors.

So anyway, this is the relevant piece of code:

Code:
.proc main
  LDA #$00
background:
  LDX PPUSTATUS
  ADC #$01
  LDX #$3f
  STX PPUADDR
  LDX #$00
  STX PPUADDR
  STA PPUDATA
  LDY #%00011110
  STY PPUMASK
  LDX #$FF 
loop:
  LDY #$FF
wait:
  NOP
  DEY
  CPY #$00
  BNE wait
  NOP
  DEX
  CPX #$00
  BNE loop
  JMP background


The reason I have loop: and wait: is because, when I assembled the rom without those, I got a mess of colors in the emulator (Nestopia if its relevant):
Image
I figured this is because the color is changing too fast, so I add the two nested loops to add a bunch of NOPS just to see if that was the reason and sure enough, now I get a nice, solid background that last for a few seconds.

So my question is: This feel incredibly hacky, how does one exactly wait a certain ammount of time for events to happen? I guess Ideally you'll be able to wait for certain ammount of frames to have passed? This is my first time programming for a console and coding with ASM, so I'm a little bit lost here.


Top
 Profile  
 
PostPosted: Mon Mar 18, 2019 5:13 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 8265
Location: Seattle
The NES itself only provides you with four total possible clock sources:

1- The CPU itself runs at a certain speed. Each instruction takes a certain amount of time. However, this method is a total pain to do anything with, so few people have bothered unless they need it in addition to one of the following.

2- The PPU can provide a signal every vertical redraw. Almost everything ever written for the NES uses this. In NTSC territory, this signal happens at 60Hz; in PAL it happens at 50Hz.

3- The CPU's sound engine provides another signal that's almost the same as #2, but slightly different. I don't think anything really used it.

4- The CPU's sound engine can provide a signal every time its audio hardware needs more data for its sample playback mechanism. Although there(Bee 52) are things that used this for non-audio purposes, most of the time this is just used for sample playback.

(5- Some cartridges provide the ability to request specific times more precisely. But not all!)


Top
 Profile  
 
PostPosted: Mon Mar 18, 2019 6:08 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4015
Location: A world gone mad
If that's literally all your code is doing, then yes, the behaviour you get is (more or less) correct. You're just spewing data at the PPU constantly, with no concept of when it's proper to do so. You need to learn what VBlank/NMI is, which should answer all your questions (and ensure you can do something at a set rate of time, i.e. 60 times a second for NTSC, or 50 times a second for PAL). It's partially (but not entirely) covered in the next chapter of what you're reading. Otherwise, refer to the wiki, else ask questions w/ code here.


Top
 Profile  
 
PostPosted: Mon Mar 18, 2019 6:40 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11295
Location: Rio de Janeiro - Brazil
Basically you shouldn't be writing to the PPU in the main loop. The CPU and PPU run in parallel, so if you have the CPU change colors whenever, without any regard for what the PPU is doing, you're gonna get corrupted results during the entire frame. You may get away with writing to the PPU in the main loop during initialization when the screen is off, but other than that, you shouldn't do it.

The absolute reference of time on the NES is the vertical blank (vblank), which's the little bit of time that comes after the PPU is done drawing a frame and lasts until it starts drawing the next frame. It's during this time that you can make changes to the screen, because the PPU itself isn't using the video memory then. To help programmers know when vblank starts, the engineers of the NES made it so that an NMI fires when vblank starts, causing the CPU to stop whatever it's doing and jump to the NMI handler... And it's via the NMI handler that you keep track of time, because we know that vblanks happen at the rate of the video signal (60fps on NTSC, 50fps on PAL).

What you have to do is limit the rate at which your main loop runs to once per frame, by having it wait for the next frame before looping back. A common way to do this is the following:

Code:
NMI:
  ;(...)
  inc FrameCounter ;change the frame counter
  rti ;return from the NMI

Code:
Main:
  ;(...)
  lda FrameCounter ;get the frame counter
Wait:
  cmp FrameCounter ;check if it changed
  beq Wait ;go check again if it didn't
  jmp Main ;now you can process the next frame

This just increments a variable once every vblank, so the main loop can simply wait for this variable to change before processing another frame, and since this variable only changes 60 times per second, the main loop will also run 60 times per second, and your game logic is now synced to the video.

Like I said before, you cannot change the video memory (which includes the palette) while the PPU is drawing a picture, so you need to put the actual write to the palette in the NMI handler, to make sure it happens during vblank:

Code:
NMI:
  lda #$3F
  sta PPUADDR
  lda #$00
  sta PPUADDR
  lda BackgroundColor
  sta PPUDATA
  inc FrameCounter
  rti

This is a simple NMI handler that just writes the current background color to the palette every frame. It's up to the main loop to change that value periodically:

Code:
  lda #1 ;make it so the delay expires immediately
  sta Delay
  lda #$00
  sta BackgroundColor

Main:

  dec Delay ;decrement the delay
  bne ColorReady ;don't do anything if it didn't reach 0 yet
  lda #60 ;prepare to wait 60 frames (1 second)
  sta Delay
  inc BackgroundColor ;change the color
ColorReady:

  lda FrameCounter
Wait:
  cmp FrameCounter
  beq Wait

  jmp Main

Something along these lines.


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

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google [Bot] 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