This mini article is about compiling your C program into a Commodore 64 8k cartridge binary. This is a nearly pointless exercise because the compiled version is going to go over 8k pretty easily. It doesn't cover burning the binary: that is covered here.
The default cc65 C runtime puts a header on the binary suitable for a prg file, but not a cart. I tinkered with the linker config to get rid of it, catted in my own cart header, and while I got it working in a rudimentary hacky way, there will still other problems. So we need a modified C runtime to do it properly. Joseph Rose's has figured this out and written a modified crt.
cd /c/cc65/lib
cp c64.lib c64-cart.lib
ar65 d c64-cart.lib crt0.o
ca65 cart64_8k.S
ar65 a c64-cart.lib cart64_8k.o
One problem with Joseph Rose's config is that it is for building CRT files, which have extra headers for use with emulators. I'm only interested in binaries that can be burned onto an EPROM. This extra header needs removing. Also, gen cart 64 hasn't been udpated in a couple of years and the linker config file is missing the 'ONCE' line.
I'm using Joseph Rose's with a couple of mods:
cut and paste the below and save it as cc65/cfg/cart64_8k.cfg (your custom linker config)
SYMBOLS {
__STACKSIZE__: value = $0800, type = weak; # 2k stack
}
MEMORY {
ZP: start = $0002, size = $001A, type = rw, define = yes;
#This CRT header stuff is refereced cart64_8k.s but not written to cart binary
CHEADER: start = $0000, size = $0040, type = ro, file = "", fill=yes;
ROMCHIP: start = $0000, size = $0010, type = ro, file = "", fill=yes;
#8k catridge data.
ROM: start = $8000, size = $2000, file = %O, fill=yes, define = yes;
#Usable RAM for data.
RAM: start = $0800, size = $7800, type = rw, define = yes;
#Data at $C000. Not normally used but made available to your cartridge.
#Top 2k is for the stack.
HIRAM: start = $C000, size = $1000-__STACKSIZE__, type = rw;
}
SEGMENTS {
#Cartridge ID and settings - these are not written to cart binary but still req
#because referenced in cart64_8k.S
HEADERDATA: load = CHEADER, type = ro;
CHIP0: load = ROMCHIP, type = ro;
#-----------------------------------------
STARTUP: load = ROM, type = ro;
LOWCODE: load = ROM, type = ro, optional = yes;
INIT: load = ROM, type = ro, define = yes, optional = yes;
CODE: load = ROM, type = ro;
RODATA: load = ROM, type = ro;
DATA: load = ROM, run = RAM, type = rw, define = yes;
ONCE: load = ROM, type = ro, define = yes; # MAX ADDED THIS
BSS: load = RAM, type = bss, define = yes;
HEAP: load = RAM, type = bss, optional = yes; # must sit just below stack
#Data stored at $C000.
BSSHI: load = HIRAM, type = bss, define = yes, optional = yes;
ZEROPAGE: load = ZP, type = zp;
}
FEATURES {
CONDES: segment = INIT,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__;
}
cl65 -O -c program.c
ld65 -o program.bin -C cart64_8k.cfg program.o c64-cart.lib
x64.exe -cart8 yourprogram.bin
Joseph Rose's "gen cart 64"
https://sourceforge.net/projects/cc65extra/files/memory%20cfgs/
This explains the format of the linker config file
https://cc65.github.io/doc/ld65.html
Out of date but useful info on cart creation with C for vic 20
http://sleepingelephant.com/denial/wiki/index.php?title=C_using_CC65