Programming Neo6502 in C/C++ Using CC65

6 min read Original article ↗

You can use CC65 to code games for the Neo6502 using C/C++! In this tutorial I show you how I converted my Dungeon crawler to the computer.

Programming the Neo6502 in C? Yes please!

Dungeon game written in C/C++ for the Neo6502
Dungeon game written in C/C++ for the Neo6502

Bit of Background Before We Code the Neo6502

In my Neo6502 review I promised I would come back and write about how to code for the machine.

Well, first I had to help the contribution team, especially the fabulous Paul Robson, with a tiny issue that I do most of my development on a Mac and there wasn’t yet a MacOS version of the emulator!

All sorted now (hopefully), at the very least I have a working version compiling and building on my Apple Silicon Mac.

Neo6502 emulator for mac
Neo6502 emulator for macOS

The second issue was which language should I code in?

While the NeoBASIC runs fast, as shown in my review, and there is a Turbo Pascal for it, lately I have been using my Dungeon game that I have been writing in C as my test case. How difficult would it be to get it up and running on the Neo?

There wasn’t a Conio.H but that is often the case. Are there enough OS/BIOS routines that I could make one?

With Paul’s help, yes there were!

CC65 on the Neo6502

Fortunately for me, the hard part of setting up the Neo6502 as a CC65 target had already been done.

You can use my Github repo, or go back to the original for the config. It’s a moving target, as with all of the stuff for this computer, because it’s constantly being worked on, but it is plenty good enough to use right away.

Paul had written a Hello World that provided examples of:

  • SoundEffect
  • SetCursorPosition
  • puts (Put String)

Being able to output strings at a specific screen console position, plus being able to see how it is done, was the start I needed.

Especially helpful was the build script to show how to compile and launch the emulator with your new binary!

The Neo6502 API

Neo6502 programming relies heavily on a memory-mapped API. Think of it like Poking on 1980s home computers, where if you want to set the background colour you poke a number into an address in memory.

In this case there is an API where there are groups of functions, and then functions are numbered.

Commands go through the address FF00, and you specify the function you want in FF01. If you want to send or receive parameters, for example the X and Y coordinate, those go into the 8 bytes starting at FF04.

/* Neo6502 Kernel API control addresses */
// Command address
uint8_t* API_COMMAND_ADDR    = (uint8_t*)0xFF00 ; 
// Address for functions
uint8_t* API_FUNCTION_ADDR   = (uint8_t*)0xFF01 ;
 
// function parameters (8 bytes)
uint8_t* API_PARAMETERS_ADDR = (uint8_t*)0xFF04 ; 
// Write text character to screen (Group 2, Function 6)
uint8_t API_GROUP_CONSOLE   = (uint8_t )0x02 ; // Group ID
uint8_t API_FN_WRITE_CHAR   = (uint8_t )0x06 ; // Function ID

It’s very important you do things in the correct order, and you wait for completion before sending anything else:

My Neo6502 Conio.H

Being a bear of simple brain, I created a couple of helper functions so I could write my version of conio.h for the Neo6502.

// API COMMANDS address
uint8_t* cmd = (uint8_t*)0xFF00;
// API parameters base address (8 bytes)
uint8_t* API_PARAMS = (uint8_t*)0xFF04 ; 
void wait_cmd() {
  while(cmd[0]) {}                // Wait
}
// Call API command
void call_cmd(int group, int command) {
    cmd[1] = command;                     // Command 
    cmd[0] = group;                       // Group 
}

Wait, as the name suggests, keeps looping and doing nothing until there is no data at the command base address.

call_cmd allows you to supply the group and function numbers that you want to execute, supplying the actual command first and then the group, so that it does not execute before it knows what it is meant to be doing.

Now I could simply write the familiar functions using the API:

// Position cursor (API Group 2, Function 7)
void gotoxy(uint8_t sx, uint8_t sy)
{
  API_PARAMS[0] = sx;
  API_PARAMS[1] = sy;
  call_cmd(2,7);
}
// Get character from keyboard
int inkey() {
    wait_cmd();
    call_cmd(2,1);                     // Group 2, Command 1
    wait_cmd();
    return API_PARAMS[0];              // Return parameter
}

int cgetc() {
  char ch=0;
  while(ch==0) {
    ch=inkey();
  }
  return ch;
}

All apart from clrscr() which simply outputs the ASCII character number 12, which is correctly interpreted as a clear screen command by the firmware.

// Clear screen
void clrscr() {
    cputc(12);
    gotoxy(0,0);
}

You will notice I did code a cgetc to get a single character/keypress from the keyboard, but I made it a wrapper around the function that actually checks the keyboard buffer, so that we can optionally have it wait until there is a key available in the keyboard buffer. That said, the API actually has another option where you can check flags for keys which would be quicker for action-oriented games.

Changing the Text Color on Neo6502

Neo6502 color codes

As I was converting my C code over from the Commodore, I noticed sometimes the screen would go pink. Well, it turned out I had accidentally happened upon how text colors are selected on the Neo6502.

Character codes from 128 to 143 set the foreground or ink color, and 144 to 159 set the background or paper color.

Creating a CC65 ‘Sleep’ Delay Function

Ordinarily, I would create a delay using the actual clock timer or I would just do a for-next loop that cycles a number of times. The Neo6502 is a fair bit faster than usual 8 bit computers, however, and the time code isn’t there. What can I do?

What I did was some time-wasting in-line assembler, which might not be the best way, but it works.

unsigned int nop_delay(unsigned int delay) {
    for(timer=0; timer

So I could mostly use the old values, I multiply the provided delay by 7. I guess this means if the emulator was cycle accurate (it is not) then the Neo6502 is roughly 7x faster than the other computers, which is pretty much correct on paper!

Compiling Your C Program

Building (sorry) on Paul’s shell script, I created build.sh which takes as a parameter the name of your C code without the .C.

Eg. ./build.sh dungeon

The one part you will definitely need to change is the location of your CC65 because I am on a Mac and my CC65 lives in the Homebrew location:

readonly _CC65_HOME="${CC65_HOME:-$HOMEBREW_CELLAR/cc65/2.19/share/cc65}"

Paul’s original on his Linux was:

readonly _CC65_HOME="${CC65_HOME:-/usr/share/cc65}"

The script will build your code, linking in the necessary neo6502 libraries and then it will launch the emulator (named neo) and tell it to read the binary into memory at address $800.

What Next?

A couple of ideas come to me immediately now that I have gotten this far:

  1. The API allows for custom characterset like I did on the Agon and C64.
  2. Full bitmap graphics, with tiles and sprites is also available.
  3. Write a whole different game?

What do you think?