Difference between revisions of "C using CC65"

From DenialWIKI
Jump to navigation Jump to search
Line 49: Line 49:
 
     __STACKSIZE__ = $400; # 1K stack
 
     __STACKSIZE__ = $400; # 1K stack
 
}
 
}
 +
</pre>
 +
 +
== C Runtime File ==
 +
 +
Another important file used by CC65 is the runtime file.  It contains the initial entry point of the program, with the startup code.  The default startup code also includes the BASIC header code, with a single line with a SYS call that jumps to the entry point of the program.  This allows the program to autostart when it is loaded, rather than having to manually perform the SYS call to the entry point.  The runtime file is linked into the program at the location specified in the Linker Config file.
 +
 +
<pre>
 +
;
 +
; Startup code for cc65 (Vic20 version)
 +
;
 +
; This must be the *first* file on the linker command line
 +
;
 +
 +
.export _exit
 +
.import initlib, donelib, callirq
 +
      .import       zerobss, push0
 +
.import     callmain
 +
        .import        RESTOR, BSOUT, CLRCH
 +
.import       __INTERRUPTOR_COUNT__
 +
    .import __RAM_START__, __RAM_SIZE__ ; Linker generated
 +
 +
        .include        "zeropage.inc"
 +
    .include    "vic20.inc"
 +
 +
; ------------------------------------------------------------------------
 +
; Place the startup code in a special segment.
 +
 +
.segment      "STARTUP"
 +
 +
; BASIC header with a SYS call
 +
 +
        .word  Head            ; Load address
 +
Head:  .word  @Next
 +
        .word  .version        ; Line number
 +
        .byte  $9E            ; SYS token
 +
        .byte  <(((@Start / 1000) .mod 10) + $30)
 +
        .byte  <(((@Start /  100) .mod 10) + $30)
 +
        .byte  <(((@Start /  10) .mod 10) + $30)
 +
        .byte  <(((@Start /    1) .mod 10) + $30)
 +
        .byte  $00            ; End of BASIC line
 +
@Next:  .word  0              ; BASIC end marker
 +
@Start:
 +
 +
; ------------------------------------------------------------------------
 +
; Actual code
 +
 +
ldx    #zpspace-1
 +
L1: lda sp,x
 +
  sta zpsave,x ; Save the zero page locations we need
 +
dex
 +
      bpl L1
 +
 +
; Close open files
 +
 +
jsr CLRCH
 +
 +
; Switch to second charset
 +
 +
lda #14
 +
jsr BSOUT
 +
 +
; Clear the BSS data
 +
 +
jsr zerobss
 +
 +
; Save system stuff and setup the stack
 +
 +
      tsx
 +
      stx    spsave ; Save the system stack ptr
 +
 +
lda    #<(__RAM_START__ + __RAM_SIZE__)
 +
sta sp
 +
lda #>(__RAM_START__ + __RAM_SIZE__)
 +
      sta sp+1  ; Set argument stack ptr
 +
 +
; Call module constructors
 +
 +
jsr initlib
 +
 +
; If we have IRQ functions, chain our stub into the IRQ vector
 +
 +
        lda    #<__INTERRUPTOR_COUNT__
 +
      beq NoIRQ1
 +
      lda IRQVec
 +
      ldx IRQVec+1
 +
      sta IRQInd+1
 +
      stx IRQInd+2
 +
      lda #<IRQStub
 +
      ldx #>IRQStub
 +
      sei
 +
      sta IRQVec
 +
      stx IRQVec+1
 +
      cli
 +
 +
; Push arguments and call main()
 +
 +
NoIRQ1: jsr    callmain
 +
 +
; Back from main (This is also the _exit entry). Reset the IRQ vector if we
 +
; chained it.
 +
 +
_exit: pha  ; Save the return code on stack
 +
lda    #<__INTERRUPTOR_COUNT__
 +
beq NoIRQ2
 +
lda IRQInd+1
 +
ldx IRQInd+2
 +
sei
 +
sta IRQVec
 +
stx IRQVec+1
 +
cli
 +
 +
; Run module destructors
 +
 +
NoIRQ2: jsr donelib
 +
 +
; Copy back the zero page stuff
 +
 +
      ldx #zpspace-1
 +
L2: lda zpsave,x
 +
sta sp,x
 +
dex
 +
      bpl L2
 +
 +
; Place the program return code into ST
 +
 +
pla
 +
sta ST
 +
 +
; Restore the stack pointer
 +
 +
  ldx spsave
 +
txs
 +
 +
; Reset changed vectors, back to basic
 +
 +
jmp RESTOR
 +
 +
 +
; ------------------------------------------------------------------------
 +
; The IRQ vector jumps here, if condes routines are defined with type 2.
 +
 +
IRQStub:
 +
cld            ; Just to be sure
 +
      jsr    callirq                ; Call the functions
 +
      jmp    IRQInd ; Jump to the saved IRQ vector
 +
 +
; ------------------------------------------------------------------------
 +
; Data
 +
 +
.data
 +
 +
zpsave: .res zpspace
 +
IRQInd: jmp    $0000
 +
 +
.bss
 +
 +
spsave: .res 1
 +
 
</pre>
 
</pre>
  

Revision as of 09:35, 2 July 2009

Introduction

This section is not intended to be a tutorial on how to program in C. There are many tutorials and books on C programming in the internet. This is intended to be information on how to use the freeware C compiler CC65 to write software for the VIC-20.

There are pros and cons associated with using C to program a device with as few resources as the VIC-20. For small tasks, the overhead of the compiled code and associated libraries is significant when compared to the smaller interpreted programs used by BASIC. However, for slightly larger programs, C offers a comprimise between the speed and efficiency of assembly language, and the size of BASIC. However, why you would choose to program in C is like asking "Why program for the VIC-20 at all?" And the only real answer that's needed is "Because I want to."

In many respects, programming in C for the VIC-20 can be considered programming an embedded system when compared with programming for a modern desktop PC. There are additional factors that programmers need to consider when targeting hardware that has severe limitations. In this respect, the articles and tutorials at sites like Embedded.com are more suited than sites that target how to program using Visual C++.

Overview

The CC65 homepage has information regarding the origins of the compiler and it's limitations. Suffice it to say that it's an "almost" ISO C compatible compiler, with many of the features missing from the implementation not appropriate for the target environment anyway. It is well supported by both it's developer, Ullrich von Bassewitz, and it's large user base across all the compatible target platforms. CC65 is more than just a C compiler. It is a suite of tools including a C compiler, assembler, linker and librarian for the 6502 family of microprocessors. Libraries have been written to support the various home computers and games consoles that utilize the 6502.

The CC65 is a cross compiler and is available to run on various host platforms. This tutorial will attempt to be as platform independent as possible regarding the host platform that the compiler is running on.

Config File

The key to controlling how CC65 creates it's output file is the linker config file. The linker config file says where to compile the code to, where to allocate variables both static and dynamic. By changing the linker config file, a program can be made to run in an Unexpanded memory configuration, a 3K, 8K or even ROM image at $A000. (Although ROM images also need some changes to ensure that the ROM header is included.)

The default linker config that is included for the VIC-20 is for an unexpanded memory model. The default 1K stack size is likely to be HUGE compared with what's really needed.

MEMORY {
    ZP: start =  $0002, size = $001A, type = rw, define = yes;
    RAM: start = $0FFF, size = $0E01, define = yes, file = %O;
}
SEGMENTS {
    STARTUP:  load = RAM, type = ro;
    LOWCODE:  load = RAM, type = ro,               optional = yes;
    INIT:     load = RAM, type = ro, define = yes, optional = yes;
    CODE:     load = RAM, type = ro;
    RODATA:   load = RAM, type = ro;
    DATA:     load = RAM, type = rw;
    BSS:      load = RAM, type = bss, define = yes;
    HEAP:     load = RAM, type = bss, optional = yes; # must sit just below stack
    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__;
}
SYMBOLS {
    __STACKSIZE__ = $400;	# 1K stack
}

C Runtime File

Another important file used by CC65 is the runtime file. It contains the initial entry point of the program, with the startup code. The default startup code also includes the BASIC header code, with a single line with a SYS call that jumps to the entry point of the program. This allows the program to autostart when it is loaded, rather than having to manually perform the SYS call to the entry point. The runtime file is linked into the program at the location specified in the Linker Config file.

;
; Startup code for cc65 (Vic20 version)
;
; This must be the *first* file on the linker command line
;

	.export		_exit
	.import		initlib, donelib, callirq
       	.import	       	zerobss, push0
	.import	     	callmain
        .import         RESTOR, BSOUT, CLRCH
	.import	       	__INTERRUPTOR_COUNT__
     	.import		__RAM_START__, __RAM_SIZE__	; Linker generated

        .include        "zeropage.inc"
     	.include     	"vic20.inc"

; ------------------------------------------------------------------------
; Place the startup code in a special segment.

.segment       	"STARTUP"

; BASIC header with a SYS call

        .word   Head            ; Load address
Head:   .word   @Next
        .word   .version        ; Line number
        .byte   $9E             ; SYS token
        .byte   <(((@Start / 1000) .mod 10) + $30)
        .byte   <(((@Start /  100) .mod 10) + $30)
        .byte   <(((@Start /   10) .mod 10) + $30)
        .byte   <(((@Start /    1) .mod 10) + $30)
        .byte   $00             ; End of BASIC line
@Next:  .word   0               ; BASIC end marker
@Start:

; ------------------------------------------------------------------------
; Actual code

	ldx    	#zpspace-1
L1:	lda	sp,x
   	sta	zpsave,x	; Save the zero page locations we need
	dex
       	bpl	L1

; Close open files

	jsr	CLRCH

; Switch to second charset

	lda	#14
	jsr	BSOUT

; Clear the BSS data

	jsr	zerobss

; Save system stuff and setup the stack

       	tsx
       	stx    	spsave 		; Save the system stack ptr

	lda    	#<(__RAM_START__ + __RAM_SIZE__)
	sta	sp
	lda	#>(__RAM_START__ + __RAM_SIZE__)
       	sta	sp+1   		; Set argument stack ptr

; Call module constructors

	jsr	initlib

; If we have IRQ functions, chain our stub into the IRQ vector

        lda     #<__INTERRUPTOR_COUNT__
      	beq	NoIRQ1
      	lda	IRQVec
       	ldx	IRQVec+1
      	sta	IRQInd+1
      	stx	IRQInd+2
      	lda	#<IRQStub
      	ldx	#>IRQStub
      	sei
      	sta	IRQVec
      	stx	IRQVec+1
      	cli

; Push arguments and call main()

NoIRQ1: jsr     callmain

; Back from main (This is also the _exit entry). Reset the IRQ vector if we
; chained it.

_exit: 	pha  			; Save the return code on stack
	lda     #<__INTERRUPTOR_COUNT__
	beq	NoIRQ2
	lda	IRQInd+1
	ldx	IRQInd+2
	sei
	sta	IRQVec
	stx	IRQVec+1
	cli

; Run module destructors

NoIRQ2: jsr	donelib

; Copy back the zero page stuff

       	ldx	#zpspace-1
L2:	lda	zpsave,x
	sta	sp,x
	dex
       	bpl	L2

; Place the program return code into ST

	pla
	sta	ST

; Restore the stack pointer

  	ldx	spsave
	txs

; Reset changed vectors, back to basic

	jmp	RESTOR


; ------------------------------------------------------------------------
; The IRQ vector jumps here, if condes routines are defined with type 2.

IRQStub:
	cld    	       		   	; Just to be sure
       	jsr    	callirq                 ; Call the functions
       	jmp    	IRQInd			; Jump to the saved IRQ vector

; ------------------------------------------------------------------------
; Data

.data

zpsave:	.res	zpspace
IRQInd: jmp     $0000

.bss

spsave:	.res	1

External Links

CC65 Homepage

Embedded.com