Software Development

Contents | Back: Case | Next: BASIC Interpreter

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.

C

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