8x16 and whatever else unreg wants to know

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Most of the time, bad opcode means either A. you forgot the RTS at the end of a subroutine, B. you messed up a bankswitch, or C. you did RTS on a messed-up stack.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

tepples wrote:Most of the time, bad opcode means either A. you forgot the RTS at the end of a subroutine, B. you messed up a bankswitch, or C. you did RTS on a messed-up stack.
tepples, thanks! :D I guess it was kind of A. I put your code in the middle of a subroutine. So it had the first part of the first subroutine missing an RTS at the end.... like

Code: Select all

[subroutineA...[subroutineB...rts]...rts]
[/quote]
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

Code: Select all

		draw_sprite:
		  ; t0-t2 are temporary zero page locations holding
		  ; the address of a particular sprite layout and the
		  ; number of sprites left to draw
		  lda sprite_layouts_lo, x
		  sta t0
		  lda sprite_layouts_hi, x
		  sta t0+1
		  lda sprite_layouts_count, x
		  sta t2
		  ldy #0
		  ; oamIndex is a variable tracking how far you've written
		  ; into shadow OAM (customarily at $0200-$02FF)
		  ldx oamIndex
		@loop:
		  ; If you have the address of an array in a zero page pointer,
		  ; you use the (d),y addressing mode and increase Y to go
		  ; to the next byte.
		  lda (t0), y
		  iny
		  ;ect. start
		  sta sprite+4;+oamIndex
		  
		  inx
		  inc oamIndex
		  ;end ect.		  
		  dec t2
		  bne @loop
		  stx oamIndex
		  rts
		  
This is what I have so far. :oops: My brain doesn't work right now, but I want to make progress. My code always draws the sprite with just one tile; how do i increase the size? I used

Code: Select all

sta sprite+oamIndex
but that didnt help. And also, is that code possible... like for me, it is hard to understand why "sta sprite+aVariable" would work...? Found my assembly textbook from college and read the entire first chapter and going to finish the second chapter by today. It is really fun reading it! :)
3gengames
Formerly 65024U
Posts: 2284
Joined: Sat Mar 27, 2010 12:57 pm

Post by 3gengames »

Nope, that code isn't possible. You have to store a sprite next to/below in the next sprite address.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

Thank you 3gengames! :D
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

I think I've found the problem... put tepples' code inside the code that runs before the

Code: Select all

cli
.

Inside the NES 101 Tutorial the only code that runs after cli is the endless loop transfering control to the vblank interrupt, i think. Where do you think tepples' code should go? I'm asking beccause my skill with rearanging others' code is very poor; guess i could keep try ing though.
Jaffe
Posts: 48
Joined: Sun May 14, 2006 10:26 am
Location: Norway

Post by Jaffe »

The code tepples posted is a subroutine. You can place it wherever you want (of course not in the middle of some other code). Was this what you meant, or do you mean where to place the actual calls (jsr) to the routine?
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

Jaffe wrote:The code tepples posted is a subroutine.
Yes, thank you; I agree. :)
Jaffe wrote:You can place it wherever you want (of course not in the middle of some other code). Was this what you meant, or do you mean where to place the actual calls (jsr) to the routine?
Well, i think i ment both... My first problem was that I placed it in the middle of some other code. I've moved the

Code: Select all

; Background data
bg:
.incbin "charSelect1.nam"
; Attribute table
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
.byte $00,$00,$00,$00,$00,$00,$00,$00,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0
.byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F
;sprite data
hero_frame1:
.db aY,    $80, $00, aX, aY,    $81, $00, aX+8
.db aY+8,  $90, $00, aX, aY+8,  $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame2:
.db aY,    $80, $00, aX, aY,    $81, $00, aX+8
.db aY+8,  $90, $00, aX, aY+8,  $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame3:
.db aY,    $80, $00, aX, aY,    $81, $00, aX+8
.db aY+8,  $90, $00, aX, aY+8,  $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
hero_frame4:
.db aY,    $80, $00, aX, aY,    $81, $00, aX+8
.db aY+8,  $90, $00, aX, aY+8,  $91, $00, aX+8
.db aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8
.db aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
;Operator < produces the low byte of an address
sprite_layouts_lo:
.db <hero_frame1, <hero_frame2, <hero_frame3, <hero_frame4
;Operator > produces the high byte of an address
sprite_layouts_hi:
.db >hero_frame1, >hero_frame2, >hero_frame3, >hero_frame4
;Number of hardware sprites in each layout
sprite_layouts_count:
.db 8, 8, 8, 8
that part underneath the background and attribute data code at the end... that was obvious to me; after i did it. :oops: but, it's ok to make a mistake and then learn from that. : )
Jaffe wrote:...or do you mean where to place the actual calls (jsr) to the routine?
After many ideas and hours of reading I have decided to place the jsr in my vblank code because vblank is the period where you write to the sprite memory, right?

Thank you Jaffe for helping me! :)

This code has not worked for me, yet... now, it draws the first sprite in the correct spot on the screen; but only the first sprite. :? Back to my assembly book.
3
edit: forgot to post my code

Code: Select all

		draw_sprite:
		  ...
		  ldx oamIndex
		@loop:
		  ; If you have the address of an array in a zero page pointer,
		  ; you use the (d),y addressing mode and increase Y to go
		  ; to the next byte.
		  lda (t0), y
		  iny

		  ;ect. start
		  sta sprite, x
		  inx
		  ;end ect.  THIS IS THE END OF MY CODE.

		  dec t2
		  bne @loop
		  stx oamIndex
		  rts
Jaffe
Posts: 48
Joined: Sun May 14, 2006 10:26 am
Location: Norway

Post by Jaffe »

I have a bit of a hard time getting an overview of your code. Could you post it in its entirety?

The subroutine should not be called during vblank time, at least not before all PPU updates are done. You'll want the subroutine to store sprite data in a page of RAM, and let the contents of this page be copied into OAM by a DMA transfer during vblank (from reading this thread it seems you did this before?) That way you can use all of the non-vblank time to do complex calculations etc. to compute what objects are to be shown in the next frame, where they should be placed, etc. Then when vblank comes, you'll have the sprite data ready to be copied into OAM.

I assume your game has some kind of a main loop that just runs over and over? This is usually where you'd like to place your call to the routine, but that requires that you have some way of controlling how often the loop runs. You could also place it at the end of your vblank code, after you're sure that all code accessing the PPU has run. If you just want to show one object now as a test, you can just as well call it once before your code enters the main infinite loop.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

Jaffe wrote:I have a bit of a hard time getting an overview of your code. Could you post it in its entirety?
It's not really my code yet; it is really the code of Michael Martin in the NES 101 Tutorial and the code of tepples in his post on page 4, i think, of this thread.

Code: Select all

	; Transfer control to the VBLANK routines.
loop:   jmp loop

;80 81       
;90 91       
;a0 a1       
;b0 b1   
;        .db aY, $80, $00, aX, aY, $81, $00, aX+8,
;		    aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
;		    aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
;			aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
        		
		  
		draw_sprite:
		  ; t0-t2 are temporary zero page locations holding
		  ; the address of a particular sprite layout and the
		  ; number of sprites left to draw
		  lda sprite_layouts_lo, x
		  sta t0
		  lda sprite_layouts_hi, x
		  sta t0+1
		  lda sprite_layouts_count, x
		  sta t2
		  ldy #0
		  ; oamIndex is a variable tracking how far you've written
		  ; into shadow OAM (customarily at $0200-$02FF)
		  ldx oamIndex
		@loop:
		  ; If you have the address of an array in a zero page pointer,
		  ; you use the (d),y addressing mode and increase Y to go
		  ; to the next byte.
		  lda (t0), y
		  iny
		  ;ect. start
		  sta sprite, x
		  inx
		  ;end ect.		  
		  dec t2
		  bne @loop
		  stx oamIndex
		  rts
		  
		

Yes, I remember doing that from before in this thread. Right now, my file for vblank looks like:

Code: Select all

jsr scroll_screen
jsr update_sprite
jsr react_to_input
rti

scroll_screen:
        ldx #$00                ; Reset VRAM
        stx $2006
        stx $2006

        ldx scroll                ; Do we need to scroll at all?
        beq no_scroll
        dex
        stx scroll
        lda #$00
        sta $2005                ; Write 0 for Horiz. Scroll value
        stx $2005                ; Write the value of 'scroll' for Vert. Scroll value
                
no_scroll:
        rts
 
update_sprite:
        ldx #3
		jsr draw_sprite
		
		lda #>sprite
        sta $4014 ;OAM_DMA register ; Jam page $200-$2FF into SPR-RAM
                      ;takes 513 cycles.
					  
       
react_to_input:
        lda #$01        ; strobe joypad
        sta $4016
        lda #$00
        sta $4016

        lda $4016        ; Is the A button down?
        and #1
		beq @b
		jsr low_c
@b:		lda $4016		;Is the B button down?
		and #1
		beq not_dn
		jsr high_c
not_dn: rts                                ; Ignore left and right, we don't use 'em

low_c:
        pha
        lda #$84
        sta $4000
        lda #$AA
        sta $4002
        lda #$09
        sta $4003
        pla
        rts

high_c:
        pha
        lda #$86
        sta $4000   ;audio
        lda #$69
        sta $4002
        lda #$08
        sta $4003
        pla
        rts
Jaffe wrote: That way you can use all of the non-vblank time to do complex calculations etc. to compute what objects are to be shown in the next frame, where they should be placed, etc. Then when vblank comes, you'll have the sprite data ready to be copied into OAM.
So the first thing in vblank should be the copying of page 2 to OAM? And then is it right to do all the updating of page 2 after vblank ends? At vblank we are suspossed to make our changes known to OAM first and writes to page 2 are for between Vblank times. That's what im learning right now, thank you. :)
tokumaru wrote:The start of VBlank is an important event, so it makes sense to interrupt whatever the PPU is doing so that the full duration of VBlank can be used for VRAM updates.
What is VRAM? :?
Jaffe wrote:If you just want to show one object now as a test, you can just as well call it once before your code enters the main infinite loop.
Ok, thanks! :) I changed it, but have the poblem again... it's only drawing the first of the 8 sprite object/meta-sprite. It's in the right place though and it's the right sprite.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Yes, you can update the CPU's copy of OAM outside of vblank. In fact, you're supposed to.

VRAM is any RAM mapped into PPU $0000-$2FFF. It includes CIRAM, the memory inside the NES used for nametables, and CHR RAM, the memory inside the cartridge used for pattern tables. Some cartridges have CHR ROM instead of CHR RAM; for these, the only VRAM is CIRAM.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Are you using the exact code you pasted here? You know it's incomplete, right? The sprite drawing routine is not copying all the bytes it's supposed to from the meta-sprite definitions, and it's not adding the coordinates to the object's coordinates either. That routine was provided as an example, not as something you can copy & paste.

I advise you start writing your own code, or at least read all the code you use very carefully so that you know exactly what it's doing. Making Frankenstein programs (copying blocks of code from different sources) is a terrible way to learn, because you have no idea of what is actually going on in the program and are only hoping that it will somehow work.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Post by unregistered »

tepples wrote:Yes, you can update the CPU's copy of OAM outside of vblank. In fact, you're supposed to.
Thank you. :) Would testing the input be ok for during vblank? No, testing the input hasn't anything to do with VRAM... right?
tepples wrote:VRAM is any RAM mapped into PPU $0000-$2FFF. It includes CIRAM, the memory inside the NES used for nametables, and CHR RAM, the memory inside the cartridge used for pattern tables. Some cartridges have CHR ROM instead of CHR RAM; for these, the only VRAM is CIRAM.
Thanks! :)
tokumaru wrote:Are you using the exact code you pasted here? You know it's incomplete, right? The sprite drawing routine is not copying all the bytes it's supposed to from the meta-sprite definitions, and it's not adding the coordinates to the object's coordinates either. That routine was provided as an example, not as something you can copy & paste.

I advise you start writing your own code, or at least read all the code you use very carefully so that you know exactly what it's doing. Making Frankenstein programs (copying blocks of code from different sources) is a terrible way to learn, because you have no idea of what is actually going on in the program and are only hoping that it will somehow work.
Yes ok, you are right on all points. Thank you for your help. :) God really helped me with it and so it kind of works now! :D The code... it's able to print out the entire meta-sprite now using tepples' code and my code! Now i'm trying to figure out how to move it with the control pad... and I think maybe calling

Code: Select all

ldx #3
jsr draw_sprite
after each input-check would work... but how should I try this?
Here's code that appplies to this question. :)

Code: Select all

        lda #%00011110
        sta $2001

	;matthew's init
	
	
	cli
;----------------------------END OF RESET----------------------------------
	; Transfer control to the VBLANK routines.
	        ldx #3
		jsr draw_sprite

loop:   jmp loop

;80 81       
;90 91       
;a0 a1       
;b0 b1   
;        .db aY, $80, $00, aX, aY, $81, $00, aX+8,
;		    aY+8, $90, $00, aX, aY+8, $91, $00, aX+8,
;		    aY+16, $a0, $00, aX, aY+16, $a1, $00, aX+8,
;			aY+24, $b0, $00, aX, aY+24, $b1, $00, aX+8
        		
		  
		draw_sprite:
		  ; t0-t2 are temporary zero page locations holding
		  ; the address of a particular sprite layout and the
Would jsr draw_sprite be ok to include inside of the loop: jmp loop?
Is that the only place I can put it? :? (Because the rest of the file is subroutines and then data.) I'm missing knowlege about a way to organize my code with files. :? I remember reading some post by tokumaru that's about organizing code with different files... will search for that tomorrow. Good night. :)
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

unregistered wrote:Would jsr draw_sprite be ok to include inside of the loop: jmp loop?
It depends. There are 3 common ways to organize your frame logic in NES programs in order to make sure it will run 60 times per second (as it should):

1. Put the game logic after VRAM-related operations in your NMI. The NMI routine is automatically called at the start of VBlank, and once the VRAM updates are finished, you are free to do other kinds of operations. Since the NES runs at 60fps, there are 60 VBlanks in a second, so your game logic will also run at 60 Hz. Many commercial games do this, including SMB and Final Fantasy. There are some problems with this method though, which make lag frames (these happen when the frame logic takes longer than a frame to complete) difficult to manage. SMB suffers from this when there are too many enemies on screen (apparently the status bar "jumps" and the music slows down). Most tutorials appear to use this.

Code: Select all

Loop:
	jmp Loop

NMI:
	;DO PPU UPDATES HERE;

	;RUN THE GAME LOGIC HERE;

	rti
2. Use the NMI just for signaling the start of VBlank, and both the frame logic and the VRAM-updating in the main thread (the "loop: jmp loop" part). This is very similar to the above, only the order of the updates is different. Here we update the frame logic first and then we wait for VBlank (by checking a flag which is modified by the NMI when VBlank starts) before performing all VRAM updates. This technique suffers from the same lag frame problems. AFAIK, no commercial games did it like this.

Code: Select all

Loop:
	;RUN THE GAME LOGIC HERE;

	;WAIT FOR THE NMI;

	;DO PPU UPDATES HERE;

	jmp Loop

NMI:
	;INDICATE THE START OF VBLANK;

	rti
3. Separate your main thread from your NMI thread and run them in parallel. This is my favorite, because it allows for safe handling of lag frames. When the NMI fires, it can detect if the frame logic is complete or not, so it's able to detect lag frames and make sura that status bars, the music and other things still work even though the frame logic isn't finished. AFAIK, most commercial games use this.

Code: Select all

Loop:
	;RUN THE GAME LOGIC HERE;

	;WAIT FOR THE PPU UPDATES TO COMPLETE;

	jmp Loop

NMI:
	;DO PPU UPDATES HERE;

	rti
So, to answer your question more directly: If you just place the sprite-drawing call in the main loop without any other modifications, that routine will be called 100's of times in a frame, because there isn't any sort of timing control in there. In your case, the simplest fix would probably be to place that call right after the controller is checked and the player's coordinates are changed. Since it appears you are building this from a tutorial, it would be better to stick to the first solution I explained in this post. Just keep in mind that there are other options, and that you might want to take a look at them once your games become more complex and need more "robust" timing.
I remember reading some post by tokumaru that's about organizing code with different files...
How you organize your files doesn't make any difference in the program... Separating files and using includes is something that's supposed to help you keep track of what's where and make your code more easily maintainable, but in the end the assemble still sees all the code as if it were in a single huge file. What matters for NES programs is the order in which things are processed, which doesn't necessarily match the order in which they are written.
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

tokumaru wrote:2. Use the NMI just for signaling the start of VBlank, and both the frame logic and the VRAM-updating in the main thread (the "loop: jmp loop" part). [...] AFAIK, no commercial games did it like this.
Don't Squaresoft games such as Final Fantasy do it this way?

I'll have to see if Nintendulator and NESICIDE can measure /NMI-to-RTI time.
Post Reply