C64 Assembly Rendering a Sprite
Asked Answered
A

2

9

I have written a short program in 6502 assembler for the Commodore 64 using the ca65 assembler and ld65 linker. The program should render a solid square sprite somewhere near the center of the display, but I don't see anything being rendered.

This is my assembly:

    .segment "CODE"

    ; set sprite pointer index
    ; this, multiplied by $40, is the address
    ; in this case, the address is $2000
    ; $80 * $40 = $2000
    lda #$80
    sta $07f8

    ; enable sprite 0
    lda #$01
    sta $d015

    ; set x and y position
    lda #$80
    sta $d001
    sta $d002

loop:
    jmp loop

    .segment "GFXDATA"

    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF

This is my linker script, adapted from ca65's recommended linker script for hand-written assembler on the c64. The only change I made was to add the "GFXDATA" segment, so that I could store my sprites at address $2000.

FEATURES {
    STARTADDRESS: default = $0801;
}
SYMBOLS {
    __LOADADDR__: type = import;
}
MEMORY {
    ZP:       file = "", start = $0002,  size = $00FE,      define = yes;
    LOADADDR: file = %O, start = %S - 2, size = $0002;
    MAIN:     file = %O, start = %S,     size = $D000 - %S;
}
SEGMENTS {
    ZEROPAGE: load = ZP,       type = zp,  optional = yes;
    LOADADDR: load = LOADADDR, type = ro;
    EXEHDR:   load = MAIN,     type = ro,  optional = yes;
    CODE:     load = MAIN,     type = rw;
    RODATA:   load = MAIN,     type = ro,  optional = yes;
    DATA:     load = MAIN,     type = rw,  optional = yes;
    GFXDATA:  load = MAIN, type = ro, optional = yes, start = $2000;
    BSS:      load = MAIN,     type = bss, optional = yes, define = yes;
}

This is the command I am using to compile and link:

cl65 -o graphics.prg --mapfile graphics.map -u __EXEHDR__ -t c64 -C linker.cfg graphics.asm

This is the contents of the mapfile after compiling:

Modules list:
-------------
graphics.o:
    CODE              Offs=000000  Size=000015  Align=00001  Fill=0000
    GFXDATA           Offs=000000  Size=000040  Align=00001  Fill=0000
/usr/share/cc65/lib/c64.lib(exehdr.o):
    EXEHDR            Offs=000000  Size=00000C  Align=00001  Fill=0000
/usr/share/cc65/lib/c64.lib(loadaddr.o):
    LOADADDR          Offs=000000  Size=000002  Align=00001  Fill=0000


Segment list:
-------------
Name                   Start     End    Size  Align
----------------------------------------------------
LOADADDR              0007FF  000800  000002  00001
EXEHDR                000801  00080C  00000C  00001
CODE                  00080D  000821  000015  00001
GFXDATA               002000  00203F  000040  00001


Exports list by name:
---------------------
__EXEHDR__                000001 REA    __LOADADDR__              000001 REA    



Exports list by value:
----------------------
__EXEHDR__                000001 REA    __LOADADDR__              000001 REA    



Imports list:
-------------
__EXEHDR__ (exehdr.o):
    [linker generated]       
__LOADADDR__ (loadaddr.o):
    [linker generated]        linker.cfg(5)

And a hexdump of the final binary file:

0000000 0801 080b 0320 329e 3630 0031 0000 80a9
0000010 f88d a907 8d01 d015 80a9 018d 8dd0 d002
0000020 1f4c 0008 0000 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
*
0001800 ff00 ffff ffff ffff ffff ffff ffff ffff
0001810 ffff ffff ffff ffff ffff ffff ffff ffff
*
0001840 00ff                                   
0001841

The "GFXDATA" segment is my sprite. The sprite is 64 bytes of $FF, so it should look like a solid square. This sprite data is located at address $2000.

The "CODE" segment is starts at the usual BASIC start location, and ca65 is inserting a BASIC loader for me so I can just type run after loading the program.

I have not switched the VIC's bank, so the screen is still at its default address range ($0400-$07FF), with the last 8 bytes of this range being my sprite pointers. I'm only using sprite pointer 0 ($07f8) because I only have one sprite.

When I run the program, everything locks up -- which is to be expected, because the program ends in an infinite loop. But I don't see the sprite anywhere on screen:

Program running in VICE

What am I missing?

Aubyn answered 13/2, 2020 at 22:50 Comment(4)
Oh, the memories. Of course back in the day we did not mess with crazy linker stuff :) Anyway, let me have a look. For starters, you got the X/Y registers wrong. They should be $d000 and $d001.Affricative
@Affricative i should add that 99% of that linker script came directly from ca65 -- all i added was the "GFXDATA" segment.Aubyn
@Affricative oh my gosh, i was sure i had triple-checked those registers. i just updated my code to use the correct X and Y registers... and it works perfectly. i can't believe i spent two afternoons on this and didn't notice that.Aubyn
Just in case you're not aware - there is Retrocomputing.SE, where people love this stuff.Bernabernadene
A
7

As @Jester pointed out in the comments, the X-position and Y-position memory addresses are wrong. The correct addresses are $d000 and $d001:

; set x and y position
lda #$80
sta $d000
sta $d001

This is the corrected code:

    .segment "CODE"

    ; set sprite pointer index
    ; this, multiplied by $40, is the address
    ; in this case, the address is $2000
    ; $80 * $40 = $2000
    lda #$80
    sta $07f8

    ; enable sprite 0
    lda #$01
    sta $d015

    ; set x and y position
    lda #$80
    sta $d000
    sta $d001

loop:
    jmp loop

    .segment "GFXDATA"

    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
    .byte $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF

And here's a picture of it in action:

Aubyn answered 13/2, 2020 at 22:50 Comment(4)
Please don't abuse the "community wiki" feature. This answer has no business being one.Neckcloth
@Neckcloth why not? jester answered the questions in the comments. i invited him to post it as an answer. he did not. so i posted it, but since i wasn't the one who found the answer, it didn't seem appropriate for me to put my name on it.Aubyn
this thread says that the right time to use a community wiki is "if your answer is merely compiled together from what already was answered by others in comments".Aubyn
this thread, with even more upvotes, even goes as far as to say it is always okay to make an answer into a community wiki if you want to.Aubyn
N
3

You can use VIC_SPR0_X and VIC_SPR0_Y if you include c64.inc. This can make your life much easier.

Nadeen answered 28/2, 2020 at 13:51 Comment(1)
good tip, thank you! i'll do that in the future. you're welcome to add this to the community wiki answer if you'd like.Aubyn

© 2022 - 2024 — McMap. All rights reserved.