Search code examples
ccompiler-constructionz80gameboy

GameBoy compiler with system registers and interrupts


I have been spending a lot of time learning GameBoy programming, as I was already familiar with Z80 Assembly I wasn't afraid of jumping into using it. I would (of course) find it much more productive to program in C or C++ however cannot find a full compiler for the GameBoy, the compilers I can find manage everything themselves and do not give access to system registers to the programmer and also have some horrible drawbacks such as 100% CPU utilization and no interrupt support.
Would it be possible to address system registers much like Arduino's AVR compiler? being able to address a CPU or system register simply with its name such as DDRD = %10101011
What would I have to do to add interrupts and system registers into a compiler? All but one system register are only one byte memory addresses and interrupts vectors are of course memory locations, the only one system register that isn't a memory address can only be modified with two Assembly instructions EI and DI but that could be inline functions correct?


Solution

  • The usual tactic is to create your own pointers to system registers. I don't know the address of DDRD, but something like this should to the trick:

    volatile unsigned char *reg_DDRD = (unsigned char *)0xE000;
    *reg_DDRD = 0xAB;
    

    Most C compilers don't support binary constants but you can use them with some macro hackery. And you can use macros to make slightly more intuitive syntax:

    #define DDRD (*reg_DDRD)
    DDRD = 0xAB;
    

    There's no sense in modifying the compiler when vanilla C code can work just as well.

    Handling interrupts comes down to solving 3 problems. The first is to have the interrupt vector address do a jump to a C function. As that is in ROM you'll need to modify the C runtime environment to initialize that. This gets pretty system dependent, but usually what you'll want to do is add an assembly language file that looks like this:

         org 38h   ; or wherever the gameboy CPU jumps to on interrupt
     jp _intr_function
    

    This should cause the CPU to go to intr_function() in your C program. You may or may not need the leading underscore. And you may not be able to set the destination address in the assembler file with org but instead have to fool around with the linker and sections.

    The second problem is that the C function won't necessarily save all the registers it should. You can do this by adding in-line assembly to it, along these lines:

    void intr_function()
    {
         asm(" push af");
         asm(" push bc");
         asm(" push de");
         asm(" push hl");
    
         // ... now do what you like here.
    
         asm(" pop hl");
         asm(" pop de");
         asm(" pop bc");
         asm(" pop af");
     }
    

    Finally, the interrupt may have to be acknowledged by manipulating a hardware register. But you can do that in the C code so nothing special about that.

    I'm not clear about the issue with wait loops. Standard C compilers don't have such a feature built in. They call main() and if you want to loop it is up to you. It is true that the C-like language used in the Arduino SDK has its own built-in infinite loop that calls the functions you write, but that's not normal C.