Part 1 - An absolutely minimal C program

To save a bit of development time I'm proposing to do my Z80 code development on a Z80 emulator rather than the long compile / burn to eeprom / turn on breadboard cycle. Which emulator? I've gone for Z80 Workbench from which I run under Windows 11. You can find it available for download here.

Now I'm not proposing to write in Z80 assembler, I want to work in one of the very first languages I programmed, C. So we also need a cross-compiler that will run under Windows. For this, I've chosen SDCC, downloads and manuals all available from here.

Now with both of these installed (just follow the instructions linked above) we need to configure sdcc so that it generates machine code matching the emulator. So what does the emulator give us?

Firstly, the full 64K of memory is "present" so we can put our code and data anywhere, although we know that the Z80 from a cold start begins executing code from location 0, so that seems like a useful place to put at least some of our code! We also a get some pre-configured IO ports:

So how do we tell sdcc about all of this? Firstly, there are some useful compiler flags for defining the memory map:

We also want to program against "bare metal" and provide all our library support (if any) - we want to control every byte so we need to turn off the standard C libraries or runtimes as these can be quite large. (We can provide our own runtime later if we want to), so:

And of course we need to tell our target device!

As we all know, C is pretty free and easy with memory pointers, we can put data anywhere just by casting a number to an address, but how do we access the IO space? sdcc gives us some additional support for that, allowing us to define a variable that will be mapped to an IO port - we'll see the syntax of this in the code below.

Now that we have the information let's write an absolutely minimal C program to turn on those LEDs the emulator gives us, and at the same time investigate some other features. Here's our code:

char test;

__sfr __at 0x03 leds;

void main(void) {
    test = 55;

    leds = 0x55;

What are we doing here? Firstly, we define a global variable test - by writing some value to it we can confirm that the compiler puts data where we think it should.

Secondly we use the magic sdcc incantation to create a variable leds and associate that with the IO port at address 0x03;

Then in the main routine we just write a value to each of these variables. That's pretty minimal alright!

Here's our command line:

sdcc -mz80 --nostdinc --nostdlib --no-std-crt0 minimal.c