Need help with a loop that has 16 bit counter

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Baka94
Posts: 12
Joined: 23 Feb 2014

Need help with a loop that has 16 bit counter

Post by Baka94 »

So, I'm trying to make a loop that counts to 960 (decimal), but I have never used 16 bit numbers so far, so I have no idea how to do it. What the loop is supposed to do is that it loads data from different location with indexed X addressing mode and stores it to $2007 (which automatically stores the data to pre-defined location and stores the next data to the next slot). Here is example (8 bit tough):

LDA $2002 ; Reset High/Low latch
LDA #$20 ; Defines high and low bytes
STA $2006
LDA #$00
STA $2006
LDX #$00 ; Load 0 to X for loop

DrawBackground: ; Loop for drawing background
LDA background, x
STA $2007
INX
CPX #$FF
BNE DrawBackground ; Branch to DrawBackground if X is less than $FF
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

Re: Need help with a loop that has 16 bit counter

Post by GARTHWILSON »

Here's one way to do it:

Code: Select all

        LDA  $2002            ; (Instead of a number, it's better to give addresses meaningful names.)
        LDA  $20
        STA  $2006
        STZ  $2006            ; 65c02 STZ allows you to skip the LDA #0.

        LDA  #<background     ; One way to do it is to put the address of "background" into a pair of ZP bytes
        STA  BG_ind           ; to use for indirect addressing, then increment it, instead of indexing by X
        LDA  #>background     ; (since X doesn't have enough bits).  Remember it's low-byte-first order.
        STA  BG_ind + 1

DrawBackground: 
        LDA  (BG_ind)         ; Do the transfer.  (If you use an NMOS 6502, you'll have to use LDA (BG_ind),Y or
        STA  $2007            ; LDA (BG_ind,X), with Y or X zeroed.  65c02 allows the indirect without the X or Y.

        INC  BG_ind           ; Now increment and test your indirect number.
        BNE  db1
        INC  BG_ind + 1

db1:    LDA  BG_ind                   ; After the incr, compare to target value, starting with low byte.
        CMP  #<{background + $3C0}    ; $3C0 is the hex value of 960.  "<" is for low byte.
        BNE  DrawBackground

        LDA  BG_ind + 1               ; Now compare high byte (hence the "+1").
        CMP  #>{background + $3C0}    ; The ">" in many assemblers puts the high byte in the operand.
        BNE  DrawBackground
I didn't test it. Hopefully I don't have mistakes.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Need help with a loop that has 16 bit counter

Post by barrym95838 »

You can easily loop 960 times by nesting loops. If the y register is available, you could just do 15 outer loops with y and 64 inner loops with x (15 * 64 == 960).

What about your background data? Is it repetitive in a Flintstones kind of way? If not, then you need to construct a 16-bit address pointer into your background data using the (dp),y addressing mode, and increment the high-byte of the pointer address when y wraps around.

Code: Select all

        ...
DrawBackground:
        lda  #<background       ; init background pointer
        sta  zppointer
        lda  #>background
        sta  zppointer+1
        ldy  #0
DrawLoop:                       ; transfer 3 full pages of data
        lda  (zppointer),y
        sta  $2007
        iny
        bne  DrawLoop
        inc  zppointer+1        ; point to next page
        lda  zppointer+1
        cmp  #>background+3     ; last page of data?
        bne  Drawloop           ; No.  transfer another full page
Drawloop2:                      ; Yes.  transfer final (partial) page
        lda  (zppointer),y
        sta  $2007
        iny
        cpy  #960-768           ; done?
        bne  Drawloop2
        ...
Mike

[Edit: Darn! I see that Garth scooped me by a few minutes! My version is about 5 bytes longer, but works on any 6502, and is a tiny bit faster than Garth's version.]
Baka94
Posts: 12
Joined: 23 Feb 2014

Re: Need help with a loop that has 16 bit counter

Post by Baka94 »

GARTHWILSON wrote:
One way to do it is to put the address of "background" into a pair of ZP bytes
How do I know where the background is in the program?

The way I learned to load the background is doing this (not sure if this is how everybody does it):

Code: Select all

background:
 .db $2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F ; Row 0 1/2 - Writes
 .db $2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F ; Row 0 2/2 - 'HELLO!
 .db $2F,$11,$0E,$15,$15,$18,$27,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F ; Row 1 1/2 - THIS IS A TEST
 .db $2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F ; Row 1 2/2 - FOR DRAWING
 .db $2F,$1D,$11,$12,$1C,$2F,$12,$1C,$2F,$0A,$2F,$1D,$0E,$1C,$1D,$2F ; Row 2 1/2 - BACKGROUND.'
 .db $2F,$0F,$18,$1B,$2F,$0D,$1B,$0A,$20,$12,$17,$10,$2F,$2F,$2F,$2F ; Row 2 2/2 - on screen
 .db $2F,$0B,$0A,$0C,$14,$10,$1B,$18,$1E,$17,$0D,$24,$2F,$2F,$2F,$2F ; Row 3 1/2
 .db $2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F,$2F ; Row 3 2/2
The rest is just blank tiles ($2F) for now, but I will change that once I get this working (undefined tiles display as a '0' since it's the first tile). So the loop should go through this list and take the tiles from the included .chr file which is in the chr data bank. (Hopefully that made sense, lol)
User avatar
BitWise
In Memoriam
Posts: 996
Joined: 02 Mar 2004
Location: Berkshire, UK
Contact:

Re: Need help with a loop that has 16 bit counter

Post by BitWise »

You can squeeze a few cycles (and one byte!) using X to count the pages

Code: Select all

DrawBackground:
        lda  #<background       ; init background pointer
        sta  zppointer
        lda  #>background
        sta  zppointer+1
        ldx  #>960
        ldy  #0
DrawLoop:                       ; transfer 3 full pages of data
        lda  (zppointer),y
        sta  $2007
        iny
        bne  DrawLoop
        inc  zppointer+1        ; point to next page
        dex                     ; last page of data?
        bne  Drawloop           ; No.  transfer another full page
Drawloop2:                      ; Yes.  transfer final (partial) page
        lda  (zppointer),y
        sta  $2007
        iny
        cpy  #<960             ; done?
        bne  Drawloop2
        ...
It shouldn't matter where the data for the background is. The assembler will work out the actual address and use it in the <background and >background expressions used to initialise the pointer.
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
Baka94
Posts: 12
Joined: 23 Feb 2014

Re: Need help with a loop that has 16 bit counter

Post by Baka94 »

BitWise wrote:
You can squeeze a few cycles (and one byte!) using X to count the pages
It shouldn't matter where the data for the background is. The assembler will work out the actual address and use it in the <background and >background expressions used to initialise the pointer.
So, using the < and > with a label tells the assembler to get the start and end point of the .db?
Martin_H
Posts: 837
Joined: 08 Jan 2014

Re: Need help with a loop that has 16 bit counter

Post by Martin_H »

Baka94 wrote:
So, using the < and > with a label tells the assembler to get the start and end point of the .db?
It's a way to indicate the address of the low (>) and high (<) bytes of a label.

I like the 16 bit value in the zero page. It's nice and clear and leaves X and Y free.
Baka94
Posts: 12
Joined: 23 Feb 2014

Re: Need help with a loop that has 16 bit counter

Post by Baka94 »

Okay. I tried this code (I of course edited the pointer names and labels to fit in my program), but I got few syntax errors (marked red in the code)
barrym95838 wrote:
...
DrawBackground:
lda #<background ; init background pointer
sta zppointer
lda #>background
sta zppointer+1
ldy #0
DrawLoop: ; transfer 3 full pages of data
lda (zppointer),y
sta $2007
iny
bne DrawLoop
inc zppointer+1 ; point to next page
lda zppointer+1
cmp #>background+3 ; last page of data?
bne Drawloop ; No. transfer another full page
Drawloop2: ; Yes. transfer final (partial) page
lda (zppointer),y
sta $2007
iny
cpy #960-768 ; done?
bne Drawloop2
...
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: Need help with a loop that has 16 bit counter

Post by teamtempest »

Quote:
So, using the < and > with a label tells the assembler to get the start and end point of the .db?
No. "label" is the symbolic name of a 16-bit value. Typically the value denotes some location in the 16-bit 6502 address space. The "<" operator extracts the low eight bits of "label"'s value, ">" extracts the high eight bits.

So if, for example, the value of "label" is $1234, then "<label" has a value of $34 and "">label" has a value of $12.
teamtempest
Posts: 443
Joined: 08 Nov 2009
Location: Minnesota
Contact:

Re: Need help with a loop that has 16 bit counter

Post by teamtempest »

Quote:
but I got few syntax errors
That suggests the assembler you're using doesn't like/understand standard 6502 syntax. What assembler are you using?
Baka94
Posts: 12
Joined: 23 Feb 2014

Re: Need help with a loop that has 16 bit counter

Post by Baka94 »

teamtempest wrote:
That suggests the assembler you're using doesn't like/understand standard 6502 syntax. What assembler are you using?
I'm using NESASM3.

EIDT: Also there is some weird issue that I forgot to mention that started around the time I started doing the 16 Bit counter stuff. It happens whenever I start up or reset
https://www.youtube.com/watch?v=HiNY6d4 ... e=youtu.be
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Need help with a loop that has 16 bit counter

Post by barrym95838 »

BitWise wrote:
You can squeeze a few cycles (and one byte!) using X to count the pages
What do you think about this optimization? It's the smallest and fastest I can create at the moment.

Code: Select all

        ...
DrawBackground:
        lda  #<(background-64)  ; init background pointer with offset
        sta  zppointer
        lda  #>(background-64)
        sta  zppointer+1
        ldx  #4
        ldy  #64                ; first page transfer is only 192 bytes
DrawLoop:                       ; transfer 3.75 pages of data (960 bytes)
        lda  (zppointer),y
        sta  $2007
        iny
        bne  DrawLoop
        inc  zppointer+1        ; point to next page
        dex                     ; are we done?
        bne  Drawloop           ; No.  transfer another full page
        ...
Mike
[Edit: added parentheses around initial address operands for safety. NESASM3 must have a way to select the upper and lower halves of a 16-bit value, but I'm not familiar with it.]
Baka94
Posts: 12
Joined: 23 Feb 2014

Re: Need help with a loop that has 16 bit counter

Post by Baka94 »

I almost got it working. This is what I used:

Code: Select all

DrawBackground:		; Loop for drawing background
 LDA #$B3
 STA pointerLo
 LDA #$80
 STA pointerHi
 LDA #$00
 STA counterLo
 LDA #$04
 STA counterHi

BgLoop:
 LDA [pointerLo], y
 STA $2007
 LDA pointerLo
 CLC
 ADC #$01
 STA pointerLo
 LDA pointerHi
 ADC #$00
 STA pointerHi
  
 LDA counterLo
 SEC
 SBC #$01
 STA counterLo
 LDA counterHi
 SBC #$00
 STA counterHi
  
 LDA counterLo
 CMP #$00
 BNE BgLoop
 LDA counterHi
 CMP #$00
 BNE BgLoop
Now the problem is that the 'H' ($11) on the word 'Hello!' is replaced with a blank tile ($2F).

Here is the background tile address list:
http://pastebin.com/2MBdrRSN

Here is a list of the tiles in the .chr and their hex values:
http://pastebin.com/ssmABx7F

Hopefully with these someone can find what's going wrong.
Movax12
Posts: 16
Joined: 09 Nov 2012

Re: Need help with a loop that has 16 bit counter

Post by Movax12 »

You can directly INC or DEC a memory location without using a register.

Replace:

Code: Select all

 LDA pointerLo
 CLC
 ADC #$01
 STA pointerLo
with:

Code: Select all

INC pointerlo
But anyway, here is a suggested way of doing things that is simple.. if you make sure your data is a multiple of 4 bytes (4 bytes of data, 8 bytes of data, 12 bytes, etc..)
If you are writing the entire nametable it will work, otherwise you are going to have to decide on a terminating value and bail out of the loop.

Code: Select all

    
    ; replace symbol names with your names..
    ; PPU_NAMETABLEADDRESS - where you want to write
    ; p - your pointer
    ; myData - data you want to write
    ; PPU_ADDRESS ; $2006
    ; PPU_DATA ; $2007

    ; set PPU address
    lda #>PPU_NAMETABLEADDRESS
    sta PPU_ADDRESS ; $2006
    lda #<PPU_NAMETABLEADDRESS
    sta PPU_ADDRESS

    ; nametable is 32 x 30 = 960
    ; this loop is 240 x 4 = 960
    
    lda #<mydata
    sta p
    lda #>mydata
    sta p+1

    ldy #0
    ldx #240  
  
    loop:
        lda (p), y
        sta PPU_DATA
        iny 

        lda (p), y
        sta PPU_DATA
        iny

        lda (p), y
        sta PPU_DATA
        iny

        lda (p), y
        sta PPU_DATA
        iny
       
        bne loop
        inc p+1
    dex
    bne loop
    
    ; here you could set your attributes
Untested! .. barrym95838's code should work too, this code may be a touch faster to execute, but is slightly more bytes of code.
Baka94
Posts: 12
Joined: 23 Feb 2014

Re: Need help with a loop that has 16 bit counter

Post by Baka94 »

Movax12 wrote:
You can directly INC or DEC a memory location without using a register.

Replace:

Code: Select all

 LDA pointerLo
 CLC
 ADC #$01
 STA pointerLo
with:

Code: Select all

INC pointerlo
But anyway, here is a suggested way of doing things that is simple.. if you make sure your data is a multiple of 4 bytes (4 bytes of data, 8 bytes of data, 12 bytes, etc..)
If you are writing the entire nametable it will work, otherwise you are going to have to decide on a terminating value and bail out of the loop.

Code: Select all

    
    ; replace symbol names with your names..
    ; PPU_NAMETABLEADDRESS - where you want to write
    ; p - your pointer
    ; myData - data you want to write
    ; PPU_ADDRESS ; $2006
    ; PPU_DATA ; $2007

    ; set PPU address
    lda #>PPU_NAMETABLEADDRESS
    sta PPU_ADDRESS ; $2006
    lda #<PPU_NAMETABLEADDRESS
    sta PPU_ADDRESS

    ; nametable is 32 x 30 = 960
    ; this loop is 240 x 4 = 960
    
    lda #<mydata
    sta p
    lda #>mydata
    sta p+1

    ldy #0
    ldx #240  
  
    loop:
        lda (p), y
        sta PPU_DATA
        iny 

        lda (p), y
        sta PPU_DATA
        iny

        lda (p), y
        sta PPU_DATA
        iny

        lda (p), y
        sta PPU_DATA
        iny
       
        bne loop
        inc p+1
    dex
    bne loop
    
    ; here you could set your attributes
Untested! .. barrym95838's code should work too, this code may be a touch faster to execute, but is slightly more bytes of code.
Unfortunately your code didn't work. I just got a messed up mix of 0 and blank tiles (like usually when it doesn't work). I was hoping it would have worked tough.
Post Reply