Reading Drawing Buffer Code

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

Post Reply
mathe-matician
Posts: 14
Joined: Sat Aug 24, 2019 2:21 pm
Contact:

Reading Drawing Buffer Code

Post by mathe-matician »

I'm confused on how to read in a buffer of data for drawing. I'm mostly going off of how it is explained in the Frame and NMI article: https://wiki.nesdev.com/w/index.php/The_frame_and_NMIs

They explain that a great way to read in a drawing buffer would be to make a .db in the following structure:

Code: Select all

 05 20 16 CA AB 00 EF 05 01 2C 01 00 00 
  | \___/ \____________/  | \___/  |  |
  |   |         |         |   |    |  |
  |   |         |         |   |    |  length (0, so no more)
  |   |         |         |   |    byte to copy
  |   |         |         |   PPU Address $2C01
  |   |         |         length=1
  |   |         bytes to copy
  |   PPU Address $2016
  length=5
How would I read in the two hex numbers for the PPU address and store it in a single register to say I was going to load in the set of following bytes?

I keep coming back to trying to do something like this:

Code: Select all

LoadBuffer:	
	LDA testbuffer, X
	CPX #$00		;see where x is in the buffer
	BNE :+
	STX bufflength   ;iterate this many times through?
	JMP LoadBuffer
	:
	CPX #$01
;;etc... CPXing the entire buffer
;;then INX

;;;further down
testbuffer:
	.db $06,$01,$03,$00,$00,$01,$00
I want to load from the sprite sheet $00 just as a test.
I feel like my above example could be done, but I'd just be CPXing every single thing in the buffer to see where I was, which seems really inefficient.

What am I missing?

Secondly - if you happen to know, what does ">" or "<" do in an opcode? Does this shift right and left like ROL/ROR or LSR?
For example:

Code: Select all

LDA needdma
	BEQ :+
	LDA #$00
	STA $2003
	LDA #$>oam   ;this line here - is this a ROR or LSR?
	STA $4014
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Reading Drawing Buffer Code

Post by tepples »

You can read one byte at time into A and write one byte at a time out to $2006.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Reading Drawing Buffer Code

Post by dougeff »

LDA #$>oam
This should throw an error. The syntax should be

LDA #>oam

LDA immediate mode, from the high byte of the 16 bit address of the label "oam". The > indicates high byte. If it were <, that would get the low byte.
nesdoug.com -- blog/tutorial on programming for the NES
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Reading Drawing Buffer Code

Post by nocash »

Code: Select all

LoadBuffer:	
	LDA testbuffer, X
	CPX #$00		;see where x is in the buffer
	BNE :+
	STX bufflength   ;iterate this many times through?
	JMP LoadBuffer
	:
	CPX #$01
;;etc... CPXing the entire buffer
;;then INX

;;;further down
testbuffer:
	.db $06,$01,$03,$00,$00,$01,$00
There are some bugs in there:
If you want to start at the begin of testbufffer, then you must first set X to zero.
If X is zero, then it nonsense to do CPX 0 and BNE because you already know that it is zero. Remove CPY and BNE.
If X is nonzero at that location, then I've no idea what you are trying to do there.
You might however want to compare if the length value in A is zero (and exit the function if so).
With STX you want to store the length value from A to the variable bufflength. The correct opcode for that is STA, not STX.
JMP LoadBuffer is causing your code to hang in an endless loop (assuming that X was zero). Remove that JMP.
CPX 1 looks nonsense, too. Normally you would use INX (to increment from 0 to 1), and then continue with the etc.
In the etc you say you are CPXing the entire buffer? What do you think what CPX does? Copy something? It means Compare X.
Don't do that CPXing, just go on with using LDA+STA+INX to copy the two address bytes.
Then use further LDA+STA in a loop to copy the data bytes from testbuffer to the address, with bufflength as loop counter.
For writing to PPU memory you will need to know how to write to PPU memory (if you are new to that, then you probably don't know how to do that?).
In the .db line, 06 means six data bytes, 01,03 would be your target address (that would be 0103, do you really want that???), then 03,00,01 would be your six data bytes (but that are only three bytes???), and the ending 00 is the end byte?
Don't say "a great way" (unless you somehow believe that it is white and male and superior to most or all other ways).
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
mathe-matician
Posts: 14
Joined: Sat Aug 24, 2019 2:21 pm
Contact:

Re: Reading Drawing Buffer Code

Post by mathe-matician »

Thanks for the help!
I've modified to this:

Code: Select all

	LDX #$00
	LDY #$00
LoadBuffer:	
	LDA testbuffer, X
	STA bufflength
	INX
	LDA testbuffer, X
	STA $2006
	INX
	LDA testbuffer, X
	STA $2006
	INX
BuffLoop:	
	LDA testbuffer, X
	STA $2007
	INX
	CPX bufflength
	BNE BuffLoop
	INX
	LDA testbuffer, X	;next data set in buffer (in test it is 0 and the end)
	STA bufflength
        ;do something else once at 0
DoDrawingDone:
	RTS
Quote:
LDA #$>oam


This should throw an error. The syntax should be

LDA #>oam
dougeff - I tried switching my #$>oam to #>oam and it threw an error?
Don't say "a great way" (unless you somehow believe that it is white and male and superior to most or all other ways).
nocash, your comment is confusing. Are you saying that people shouldn't refer to things as "great ways" because there is no such thing as a "great way" necessarily? And white males who think they are superior only refer to things like that? If it's an insult I don't get your teaching/belittling process?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Reading Drawing Buffer Code

Post by tokumaru »

mathe-matician wrote:I tried switching my #$>oam to #>oam and it threw an error?
Are you by any chance using NESASM? Because NESASM uses the functions LOW() and HIGH() instead of < and > like most other assemblers.
mathe-matician
Posts: 14
Joined: Sat Aug 24, 2019 2:21 pm
Contact:

Re: Reading Drawing Buffer Code

Post by mathe-matician »

tokumaru wrote:
mathe-matician wrote:I tried switching my #$>oam to #>oam and it threw an error?
Are you by any chance using NESASM? Because NESASM uses the functions LOW() and HIGH() instead of < and > like most other assemblers.
I am using NESASM, that would make sense. Do you have a recommended assembler? asm6?
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Reading Drawing Buffer Code

Post by nocash »

mathe-matician wrote:

Code: Select all

	LDX #$00
	LDY #$00
LoadBuffer:	
	LDA testbuffer, X
	STA bufflength
	INX
	LDA testbuffer, X
	STA $2006
	INX
	LDA testbuffer, X
	STA $2006
	INX
BuffLoop:	
	LDA testbuffer, X
	STA $2007
	INX
	CPX bufflength
	BNE BuffLoop
	INX
	LDA testbuffer, X	;next data set in buffer (in test it is 0 and the end)
	STA bufflength
        ;do something else once at 0
DoDrawingDone:
	RTS
That looks much better!
As it is a function that's ends with RTS, it should also begin with a function name/label, eg. add "DrawingFunction:" in the first line, so you can call the function name via JSR from your main program.
The LDY doesn't do anything useful here.
If LoadBuffer: is meant to be the outer loop address (?) then I would rename it to "OuterLoop:" or the like.
After the first LDA, best "BEQ DoDrawingDone" (LDA does automatically set the zeroflag is A is (EQual to) zero, so you won't even need a separate compare opcode between LDA and BEQ).
CPX bufflength looks wrong (unless you are intentionally doing things differently than in the wiki.nesdev example).
First of, the wiki suggests length of data bytes (whilst X is the total length including the length byte and address bytes).
Second, if you loop to "OuterLoop", then X is the total length including all previous data blocks.
To fix both issues: Use "DEC bufflength" instead of "CPX bufflength".
The INX after BNE BuffLoop is nonsense (you had already used INX a few above, so the extra INX would skip another/unused byte).
Instead of the final LDA/STA, use "B OuterLoop".

I think then it should work and do what you want. Best use the Trace feature in a debugger to see what it is doing when executing the opcodes step by step, and to see if the loop(s) are executed the expected number of times.

I am not so familar with common NES ASM syntax(es), but the "$" in "#$>oam" looked mindblowing and probably wrong to me, too.
The code is probably supposed to use the upper 8bit of the 16bit "oam" address, so better try "#oam/256" or that HIGH/LOW thing, ie. "#HIGH(oam)".

What the code is currently doing, I guess it's "comparing if $ is greater than oam" and replacing the expression by zero (if false), and nonzero (if true).
Uhm, now I've used the word "great" myself, but I didn't mean that it is true or false that greater is better than less or equal. Yes, I am getting offtopic again, sorry. Greatness is a bit politically charged these days (and perhaps it's always been since before roman empire). Anyways, use whatever math operators you want to.
And the "$", your assembler is apparently using it as prefix for hexadecimal numbers. But $ without any following numeric digits is apparently treated as something else... some assemblers treat "$" as "address of the current opcode"... or maybe some assemblers treat anything like $000 and $00 and $0 and $ as "zero".
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Reading Drawing Buffer Code

Post by tokumaru »

mathe-matician wrote:I am using NESASM, that would make sense. Do you have a recommended assembler? asm6?
I do prefer ASM6 over NESASM, but for a good while I've been using ca65. All of those have problems though, and I'm not 100% satisfied with any of them.

NESASM has some cool features, but quite a few annoyances, such as the non-standard syntax and obligatory use of 8kb banks.

ASM6 has less features overall, but is pretty straightforward and versatile when it comes to defining any ROM structure and doesn't force you to do things in any particular way.

ca65 is a powerhouse compared to the other 2, with an insane amount of features, including a powerful macro system that makes the assembler highly customizable. One drawback is that you can't make assembly-time decisions based on not-yet-defined labels/symbols, because it assembles programs in a single pass. It also requires you to configure the whole memory layout in a separate file before coding anything, which can be inconvenient.

All things considered, I think ASM6 is the best one to start with if you don't plan on directly copying code from tutorials. If you do plan on copying code from tutorials, better use whatever assembler the tutorial uses (most likely NESASM). Then, when you're more experienced, you can make an educated decision on whether to switch to ca65.
mathe-matician
Posts: 14
Joined: Sat Aug 24, 2019 2:21 pm
Contact:

Re: Reading Drawing Buffer Code

Post by mathe-matician »

Thank you, that is all helpful information!
Right now I'm trying to load the first 3 sprites for the background, I can't seem to figure out why it won't load the palette info and the right background sprites? I keep getting this image below - which is just the first background sprite printed over and over in only gray.
can't figure it out.png
tokumaru, I'm using your template btw - thank you for those!
I'm not sure if I'm not using the correct ASM6 syntax coming from NESASM.
Do you have any suggestions on this?

Code: Select all

;----------------------------------------------------------------
; constants
;----------------------------------------------------------------

PRG_COUNT = 1 ;1 = 16KB, 2 = 32KB
MIRRORING = %0001 ;%0000 = horizontal, %0001 = vertical, %1000 = four-screen

STATETITLE	= $00
STATEPLAYING	= $01
STATEGAMEOVER	= $02

;----------------------------------------------------------------
; variables
;----------------------------------------------------------------

   .enum $0000

   ;NOTE: declare variables using the DSB and DSW directives, like this:

   ;MyVariable0 .dsb 1
   ;MyVariable1 .dsb 3
oam 	  	.dsw 1,$0200 ;shadow oam
gamestate  	.dsb 1
soft2000  	.dsb 1
soft2001  	.dsb 1
sleeping  	.dsb 1
needma	  	.dsb 1
needdraw  	.dsb 1
needppureg  	.dsb 1
needoam	  	.dsb 1	
buttons1  	.dsb 1
buttons2  	.dsb 1
bufflength  	.dsb 1

   .ende

   ;NOTE: you can also split the variable declarations into individual pages, like this:

   ;.enum $0100
   ;.ende

   ;.enum $0200
   ;.ende

;----------------------------------------------------------------
; iNES header
;----------------------------------------------------------------

   .db "NES", $1a ;identification of the iNES header
   .db PRG_COUNT ;number of 16KB PRG-ROM pages
   .db $01 ;number of 8KB CHR-ROM pages
   .db $00|MIRRORING ;mapper 0 and mirroring
   .dsb 9, $00 ;clear the remaining bytes

;----------------------------------------------------------------
; program bank(s)
;----------------------------------------------------------------

	.base $10000-(PRG_COUNT*$4000)

	;; .include "clearmem.asm"
	.include "macros.asm"
	;; .include "colorbuffers.asm" ;palette & attribute data
	;; .include "background.asm"

RESET:
	SEI			
	CLD			
	LDX #$40
	STX $4017		
	LDX #$FE
	TXS			
	INX
	STX $2000
	STX $2001
	STX $4010

	;; JMP ClearMem
	;; JMP LoadBackground
	
vblankwait1:
	BIT $2002
	BPL vblankwait1

clrmem:
	LDA #$00
	STA $0000, x
	STA $0100, x
	STA $0300, x		
	STA $0400, x
	STA $0500, x
	STA $0600, x
	STA $0700, x
	LDA #$FE
	STA $0200, x		;shadow OAM
	INX
	BNE clrmem
   
vblankwait2:   
	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 palette, x       
	STA $2007             ; write to PPU
	INX                   
	CPX #$20              
	BNE LoadPalettesLoop 

LoadAttribute:
	LDA $2002             ; read PPU status to reset the high/low latch
	LDA #$23
	STA $2006             ; write the high byte of $23C0 address
	LDA #$C0
	STA $2006             ; write the low byte of $23C0 address
	LDX #$00              ; start out at 0
LoadAttributeLoop:
	LDA attribute, x      ; load data from address (attribute + the value in x)
	STA $2007             ; write to PPU
	INX                   ; X = X + 1
	CPX #$28              ; Compare X to hex $08, decimal 8 - copying 8 bytes
	BNE LoadAttributeLoop  ; Branch to LoadAttributeLoop if compare was Not Equal to zero
LoadBackground:
	LDA $2002             ; read PPU status to reset the high/low latch
	LDA #$20
	STA $2006             ; write the high byte of $2000 address
	LDA #$00
	STA $2006             ; write the low byte of $2000 address
	LDX #$00              ; start out at 0
LoadBackgroundLoop1:
	LDA background, x     
	STA $2007            
	INX                   
	CPX #$00             
	BNE LoadBackgroundLoop1  

	LDA #%10010000
	STA $2000
	LDA #%00011110
	STA $2001

GameEngineRunning:
	
Forever:
	JMP Forever
	
WaitFrame:
	inc sleeping
loop:
	lda sleeping
	bne loop
	rts

   ;--------------------------------------
   ; DoFrame - same idea as WaitFrame, but also does some other stuff
   ;   that the game logic will want done every frame.  Things that
   ;   shouldn't be put in NMI

DoFrame:
	lda #1
	sta needdraw
	sta needoam
	sta needppureg
	jsr WaitFrame
	jmp UpdateJoypadData
	RTS
;---------------------------------------
; NMI
;---------------------------------------
NMI:

	PushAll
	LDA needma
	BEQ NeedDraw
	LDA #$00
	STA $2003
	LDA oam
	STA $4014

NeedDraw:
	LDA needdraw
	BEQ NeedPpuReg
	BIT $2002
	JSR DoDrawing
	DEC needdraw
NeedPpuReg:
	LDA needppureg
	BEQ Next
	LDA soft2001
	STA $2001
	LDA soft2000
	STA $2000
Next:	
	BIT $2002
	LDA #$00
	STA $2005
	STA $2005

	;; Optional
	;; 7)run music code
	;; 8)wait for sprite 0 hit and change the VRAM address
	;; Optional

	LDA #0 ;reset sleeping to 0 so WaitFrame exits
	STA sleeping

	;; 9)pull registers/status flags off stack
	PullAll

	RTI


DoDrawing:
	;; draw the stuff needed to draw to the drawingbuf variable ($0300)
	;; DoFrame to wait for the next VBLANK for it to write
;; 	LDX #$00
;; 	LDA $2002
;; OuterLoop:	
;; 	LDA testbuffer, X
;; 	BEQ DoDrawingDone
;; 	STA bufflength
;; 	INX
;; 	LDA testbuffer, X
;; 	STA $2006
;; 	INX
;; 	LDA testbuffer, X
;; 	STA $2006
;; 	INX
;; 	;X should be at #$03 right now (in bytes to load)
;; BuffLoop:	
;; 	LDA testbuffer, X
;; 	STA $2007
;; 	INX
;; 	CPX bufflength
;; 	BNE BuffLoop
DoDrawingDone:
	RTS
UpdateJoypadData:
	LDA #$01
	STA $4016
	LDA #$00
	STA $4016
	LDX #$08
ReadController1Loop:
	LDA $4016
	LSR A            ; bit0 -> Carry
	ROL buttons1     ; bit0 <- Carry
	DEX
	BNE ReadController1Loop
	RTS

testbuffer:
	.db $03,$20,$00,$00,$01,$02

background:
	.db $00,$01,$02,$03
	
palette:
	.db $20,$29,$1A,$0F,  $20,$36,$17,$0F,  $20,$30,$21,$0F,  $20,$27,$17,$0F   ;;background palette
	.db $20,$16,$27,$18,  $20,$02,$38,$3C,  $20,$1C,$15,$14,  $20,$02,$38,$3C   ;;sprite palette
	
attribute:
	.db %00000000, %00010000, %01010000, %00010000, %00000000, %00000000, %00000000, %00110000
	
	.db $01,$01,$01,$01, $01,$01,$01,$01 ,$01,$01,$47,$47, $47,$47,$24,$24 ,$24,$24,$24,$24 ,$24,$24,$24,$24, $24,$24,$24,$24, $55,$56,$24,$24
	.db $01,$01,$01,$01, $24,$24,$24,$24 ,$24,$24,$24,$24, $24,$24,$24,$24 ,$24,$24,$24,$24 ,$24,$24,$24,$24, $24,$24,$24,$24, $24,$24,$24,$24

;----------------------------------------------------------------
; interrupt vectors
;----------------------------------------------------------------

   .org $fffa

   .dw NMI
   .dw RESET
   .dw 0

;----------------------------------------------------------------
; CHR-ROM bank
;----------------------------------------------------------------

   .incbin "gamer.chr"
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Reading Drawing Buffer Code

Post by dougeff »

Among other problems...

LDX #$FE ;!!!!!!!
TXS
INX
STX $2000
STX $2001
STX $4010

should be
LDX #$FF

Would you like me to go step by step through the code pointing out problems?

Edit.
Also significantly problematic...

oam .dsw 1,$0200

Does not do what you think it does.

oam = $200
should suffice. Move it outside the enum.

Then when you reference it later
LDA oam
STA $4014

should be changed to
LDA #>oam
STA $4014
nesdoug.com -- blog/tutorial on programming for the NES
mathe-matician
Posts: 14
Joined: Sat Aug 24, 2019 2:21 pm
Contact:

Re: Reading Drawing Buffer Code

Post by mathe-matician »

dougeff wrote:Among other problems...

LDX #$FE ;!!!!!!!
TXS
INX
STX $2000
STX $2001
STX $4010

should be
LDX #$FF

Would you like me to go step by step through the code pointing out problems?
If you have the care and the will? Otherwise suggestions/direction would be more than fine and greatly appreciated!

I've also managed to get the attributes and background loaded properly. Very neat and exciting.

Thank you for your input so far too.
User avatar
dougeff
Posts: 3079
Joined: Fri May 08, 2015 7:17 pm

Re: Reading Drawing Buffer Code

Post by dougeff »

load attributes

CPX #$28 ; Compare X to hex $08

I'm assuming you meant CPX #$08

Load background

LDA background, x
STA $2007
INX
CPX #$00
BNE...

This will load 256 bytes. It looks like you only have 4 in the array. The CPX is superfluous, when you are comparing zero. INX will set the z flag if it results in X becoming zero.


NMI:

PushAll
LDA needma
BEQ NeedDraw

PushAll is being treated as a label by the assembler. needma is currently unused. The next line is the OAM DMA, which honestly should be done every frame anyway, even if it hasn't changed. On a real NES, the OAM RAM can decay.

LDA soft2001
STA $2001
LDA soft2000
STA $2000

You haven't given values to soft2000 and soft2001 yet. This would turn the screen off and turn off NMIs and crash the game.

The rest seems ok. I'm a little worried about your commented out draw from a buffer routine, but I have a feeling you are still working on it.
nesdoug.com -- blog/tutorial on programming for the NES
Post Reply