Using cc65 C-compiler with Commander X16 and Wintel

This guide will not cover basic OS skills such as creating folders, opening the command console, and navigating the file system from the command line. You will need to be somewhat familiar with these basic concepts first. Tools I use are:

  • notepad++ (code editor, syntax highlight)
  • FileLocator (aka AgentRansack) (file search, since Windows built-in search is hopeless)
  • HxD64 (hex editor)
  • BeyondCompare (many uses, but can compare .s fies to monitor what the assembler is doing across builds)

Introduction and Starting the Emulator

Wintel = Windows and “x86” (or now aka x64). I still use the old MS-DOS style black console window (CMD.EXE), not the fancy PowerShell stuff.

One thing to know about Windows is that each drive sort of has its own CWD (current working directory). That means, if you type “cd f:\x16” then the CWD of the F: drive is set to \x16. You still have to actually go to the F: drive (if not the current drive already) to make it the current working folder.

When you start the X16 emulator, your current folder will act as the SD-card content by default. You can override this with the -fsroot (fs = file system) or -startin command line arguments, but I just stick to running the emulator from where I want the SD-card content to be. I make sub-folders for each SD-card (like an “sd” working files/folder, then a folder for the standard as-included SD-card content). Here is an example of one way to startup the emulator:

Note that the emulator does not start in its own process, so the console window is not available when the emulator is running. If you need to get around that, use the “start” command (i.e. “start ..\x16emu.exe“).

Use CTRL-C (or just “X” the emulator window) to terminate the emulation.

Preparing the cc65 Development Folders

When you “install” cc65 (download and decompress to a folder), an easy way to start is to just create a sub-folder for your project in that same folder. It is generally not a good practice to do this (i.e. your project content should be isolated from whatever development tool you happen to be using– so you can update versions of tool without risking losing your project files), but for starters this can make dealing with build paths easier.

Now with these two console windows and the emulator running, I can just ALT-TAB between what I need to do next. If I need to restart the emulator, then ALT-TAB to the “emulator console” and CTRL-C (or just up-arrow and ENTER to restart it). If I need to adjust code, then go to the project sub-folder (“cd” change directory commands), and use notepad++ to modify the necessary source.

Within the project sub-folder, I create two folders for “include” and “source.” Being C source code, this is just “best practice” to keep interface (.h) files separated from source code (.c) files. It takes some experience to understand why – one reason is for cross-platform support, the .h files may remain the same but then different .c source implementation files used for each platform being ported to (main_x16.c versus main_a2.c for Apple ][ versus main_c64.c for C64 version, etc.).

Going Through the Motions: Compiling and Running

Within the source folder, I create two batch files: go_main_x16.bat and go_update.bat. The go_main_x16.bat script is used to compile the current project. So when I need to re-compile, I just ALT-TAB to my “compiler console”, mash UP ARROW, and ENTER. Then go_update.bat copies the “executable” (generated .PRG, if the compile was successful) to the emulator SD sub-folder I’m working with. To run the update script, ALT-TAB to the “emulator console”, CTRL-C, UP ARROW, and ENTER. With experience, you’ll realize to run the update script after compiling, you can just mash the DOWN ARROW and ENTER. Be sure to examine the compiler output to make sure it actually did compile correctly.

If you want to use other tools to edit or manipulate the files in the folder, use “start .” command to open the current folder in an Explorer Window. Obviously, you can set those tools to be in the environment path and all that, though I generally don’t bother (because as I migrate across systems to do my development, I try to avoid being dependent on a particular environment setup). Just use “start .“, then type first few letters of the filename to highlight it, hit Context menu key, select desired tool (like “notepad++”). Hardly need to ever touch the mouse.

Below is sample content of the go_main_x16.bat file.

set CC65PATHBINROOT=F:\X16\cc65-snapshot-win32\bin
set CC65PATHINCROOT=F:\X16\cc65-snapshot-win32\include

REM COMPILER to assembly
rem set OPTIMIZE=
set OPTIMIZE=-O -Oi -Or -Os
%CC65PATHBINROOT%\cc65  %OPTIMIZE% --target cx16 --include-dir ..\include --include-dir %CC65PATHINCROOT% main.c 
REM repeat the above for each .c file you need

REM ASSEMBLER to object code
%CC65PATHBINROOT%\ca65 --target cx16 main.s
REM repeat the above for each .s file you generate

REM LINKER to executable
%CC65PATHBINROOT%\ld65 --target cx16 --obj main.o --lib ..\lib\cx16.lib -o main_cx16.out
REM if multiple .o files, just list them after the --obj argument

Describing this script: First set variables to the root of your cc65 installation (I do one for the compiler executables and a separate for the include folder of the Standard Library). Doing this helps as you scale up to more files involved in the project and helps make your source code folder be “more portable” and not locked to being placed in the compiler tool folder.

At some point you may want to disable optimization (such as when you want to compare .s outputs to see exactly what the optimization is doing). But typically, you’d just leave all the optimization options on (the -O options).

COMPILE: Then you need a call to cc65 for each “.c” needed in your project. Typically, I always put the main() function in a main.c file. You can do a for-loop in the script, but I generally just list each .c for a modest sized project in case I want to disable or comment out just one of them later (especially if a portion of code is “done” and I don’t need to re-compile it for awhile).

ASSEMBLE: Next you need to assemble your resulting compiled file using ca65. From the above step, each .c becomes a .s file that is the assembly language representation of the C-code. Now the assemble process here parses the .s files to produce .o (object) files, that is essentially raw machine code of the assembly code (with things like branch offset targets more formally computed and represented by the appropriate opcodes).
NOTE: When I’m analyzing performance (like when using the “register” keyword explicitly), I’ll copy the prior build .s to another filename. Then in the next build, use BeyondCompare (BC) to examine the new and old .s files.

LINK: The resulting .o file (or files) need to be linked together into an “executable.” In general, there can be a lot that goes into linking, depending on the ROM build and OS being targeted in more complex systems. The linker makes decision on which addresses the code should go at. There is a “cx16.cfg” file in the cc65 installation that is used to define which addresses are “blocked out” (reserved for system use, not application use), and how much stack space to reserve. There are a lot of intricacies here for linking, but in general all your .o files get packaged into a single .PRG that represents the “executable” form of your program.

NOTE: When a binary PRG file is loaded via the LOAD command, it has some precise “rules” on where the code in that PRG is loaded into so that initial execution can be started.
NOTE: “cx16.lib” is cc65’s implementation of the C-standard library, as needed for the X16 platform. The C-standard library provides a lot of utility to quickly help test out some concepts, but in general a goal is to minimize usage and dependency on that standard library (especially since it quickly consumes up a lot of code space). This is not saying there is anything wrong with the C-standard library, just it contains a lot of features that either consume code space or cost extra runtime that you may not need. That said, even if do nothing with the C-Standard Library, linking in cx16.lib is still necessary for some minimum support needs (like some rules on how stack allocation is performed).
NOTE: Be aware that conio.h is not really “standard C library” content. It is a convenience header provided

Once the compiler, assembler, and link steps are all completed I then use the UPDATE script to copy the built executable over to the emulator working folder. After doing this at least once, thereafter I just DOWN ARROW and ENTER, then ALT-TAB over to the emulator console and restart it (with that -prg specified in the command line argument; just UP ARROW and ENTER).

copy main_cx16.out ..\..\..\x16emu_win64-r43\MAIN_CX16.PRG

So, a typical “update session” might look like this:

NOTE: When using the emulator -prg command line argument, it is very accommodating on loading the specified filename. But the real system is a bit more picky since users will have to use the BASIC LOAD command. It is best to go ahead and specify your PRG output using UPPERCASE letters and (if needed) use DASHES instead of UNDERSCORE to separate words in your filename. Filenames containing lower case letters and underscore symbol end up with more difficult to type characters when translated over to the X16 BASIC system.

And there you go: the sample program is now running in the emulator.

Note that VTERM is not yet completed as an actual RS232 serial terminal. But it does demonstrate a low overhead ways to output characters and detect keyboard press and modifiers, monitor time increments, do binary to decimal conversion, etc.

Transferring PRG to the Hardware

You will need an SD-card USB reader for your system. I use this “SmartQ C368 Pro USB 3.0 Multi-Card Reader” model, under $20 and haven’t had any troubles with it. Copy your PRG file to the SD-card, then migrate the SD-card over to the X16.

From BASIC, LOAD your PRG file (e.g. LOAD “MAIN TEST.PRG”). If you do a listing, it should say something like “531 SYS2061” which is a SYSTEM CALL to address $80D (hex, or 2061 decimal). The cx16.cfg has a STARTADDRESS of $0801 (by default), but a brief BASIC header is used to kickoff starting the actual program binary (which ends up starting at $080D).

VTERM running on real hardware.

Notice the same content from the PRG file ends up at the address listed in the first two bytes of the PRG (01 08, reversed to $0801).

Next Steps

github VTERMX16

So far VTERM is a very barebones application. The core_x16.h file may be a useful starting point as a library to do a few useful things, without depending on the C Standard Library.


  • X16 textmode output and FG/BG constants (without using conio.h)
  • Key modifier bits
  • “mode 2” keyboard constants
  • Macros to write directly to screen: CX16_WRITE_XY, etc.
  • check_kbd() // inline assembly
  • check_kbd_modifiers() // inline assembly
  • cx16_init_clock() // inline assembly
  • cx16_update_clock() // inline assembly

TBD. Work in progress… (walkthru core_x16.h content)

%d bloggers like this: