Now we have a working computer - or more precisely a working computer hardware. From here on we need to concentrate on software development, a task that can easily take just as long or even longer than the hardware part. For example we could implement a calculator, a BASIC interpreter or a more advanced synthesizer or SID file player.
Until now, all the programs were written in 6502 assembler code. Another improvement in the software development process is to use a high level language like C. Since cc65 contains a C compiler, we only need some small changes to make use of it:
The startup code looks like this
.setcpu "6502"
.export __STARTUP__ : absolute = 1
.import __RAM_START__
.import __RAM_SIZE__
.import _main
.import zerobss
.import copydata
.include "zeropage.inc65"
.segment "VECTORS"
.word nmi
.word reset
.word irq
.segment "STARTUP"
reset: jmp init
nmi: rti
irq: rti
init: cld
ldx
txs
lda #<(__RAM_START__ + __RAM_SIZE__)
sta sp
lda #>(__RAM_START__ + __RAM_SIZE__)
sta sp + 1
jsr zerobss
jsr copydata
jsr _main
It disables the decimal mode, initializes the 6502 stack pointer, the cc65 argument stack pointer, clears the BSS segment and copies the data from ROM into the RAM DATA segment. It then calls the _main routine, which is defined by the C function main().
Now we can write our blink example as follows
#include "led.h"
#include "tools.h"
int main() {
led_init();
for (;;) {
led_set(0);
delay_ms(250);
led_set(1);
delay_ms(250);
}
return 0;
}
The LED functions are declared in a header file led.h
#ifndef _LED_H
#define _LED_H
extern void led_init();
extern void __fastcall__ led_set(char state);
#endif
and are implemented as assembler routines
.export _led_init
.export _led_set
.import popa
.import incsp1
.include "io.inc65"
.include "zeropage.inc65"
.include "macros.inc65"
.code
_led_init: lda LED_DDR
ora #LED
sta LED_DDR
lda LED_OUT
and #<~LED
sta LED_OUT
rts
_led_set: cmp #0
bne @on
@off: lda LED_OUT
and #<~LED
sta LED_OUT
rts
@on: lda LED_OUT
ora #LED
sta LED_OUT
rts
We only need to write some low-level hardware routines in assembler and can use the C language for most of the system functions. This also enables us to use the many already implemented functions of the standard C library that comes with cc65.
The only thing that's missing is a Makefile containing all commands to build the firmware binary from the source code:
C_SOURCES = main.c
ASM_SOURCES = startup.s65 led.s65
%.o: %.c
cc65 --cpu 6502 -O -t none -o $(@:.o=.s) $<
ca65 --cpu 6502 -o $@ -l $(@:.o=.lst) $(<:.c=.s)
%.o: %.s65
ca65 --cpu 6502 -o $@ -l $(@:.o=.lst) $<
firmware: $(ASM_SOURCES:.s65=.o) $(C_SOURCES:.c=.o)
cl65 -C firmware.cfg -m firmware.map -o $@ $^ cc65.lib