Difference between revisions of "C using CC65"
(Update links) |
m |
||
(2 intermediate revisions by one other user not shown) | |||
Line 53: | Line 53: | ||
</pre> | </pre> | ||
− | The following is a Linker Config file that has been setup to generate a ROM image. Note that the start address doesn't make any | + | The following is a Linker Config file that has been setup to generate a ROM image. Note that the start address doesn't make any allowance for the two byte load address of a .prg file. Also, all code and non-volatile data is generated to ROM memory. The ROM memory needs to have the ''fill'' flag set to yes to ensure that all of the contents of ROM will be padded out with zeros. |
<pre> | <pre> | ||
#Obsolete - will not work with the latest version of CC65 | #Obsolete - will not work with the latest version of CC65 | ||
Line 281: | Line 281: | ||
== External Links == | == External Links == | ||
− | [ | + | [https://github.com/cc65/wiki/wiki cc65 Wiki] |
− | [http://www.embedded.com | + | [http://www.embedded.com Embedded.com] |
+ | |||
+ | [[Category:Programming]] |
Latest revision as of 05:51, 12 September 2016
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.
Linker 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.
#Obsolete - will not work with the latest version of CC65 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 }
The following is a Linker Config file that has been setup to generate a ROM image. Note that the start address doesn't make any allowance for the two byte load address of a .prg file. Also, all code and non-volatile data is generated to ROM memory. The ROM memory needs to have the fill flag set to yes to ensure that all of the contents of ROM will be padded out with zeros.
#Obsolete - will not work with the latest version of CC65 MEMORY { ZP: start = $0002, size = $001A, type = rw, define = yes; RAM: start = $1200, size = $0E00, type = rw, define = yes; ROM: start = $A000, size = $2000, type = ro, define = yes, file = %O, fill = yes; } SEGMENTS { 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; 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.
;Obsolete - will not work with the latest version of CC65 ; ; Startup code for cc65 (Vic20 version) ; ; This must be the *first* file on the linker command line ; .export _exit .export __STARTUP__ : absolute = 1 ; Mark as startup .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
The above crt0 file does some other intersting things during startup. It changes the character set to Mixed Case, rather than Upper + Graphics. It also includes cleanup for when the main() routine returns. If you never intend your main() routine to exit then this code could be eliminated, and a few bytes of memory saved.
The following modification to the above crt0 file demonstartes how the startup code can be modified to suit the generation of a ROM file.
If the first "Load address" is included in the output, then the file will be in the form of a .prg file suitable for loading into memory. If this line is commented out (and the memory location of the STARTUP segment changed by the same 2 bytes) then the file generated is suitable for attaching as a ROM file in an emulator, or burning to an EEPROM.
; ------------------------------------------------------------------------ ; Place the startup code in a special segment. .segment "STARTUP" ; .word Head ; Load address Head: .word start .word restore .byte $41,$30,$C3,$C2,$CD restore: jsr IUDTIM ; Update time jsr STOP ; Scan STOP key start: jsr RAMTAS ; Initialise System Constants jsr RESTOR ; Restore Kernal vectors jsr IOINIT ; Initialise IO jsr CINT ; ------------------------------------------------------------------------