Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 899
Lee Fogg Avatar asked Jan 10 '23 01:01

Lee Fogg


2 Answers

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.

like image 88
George Phillips Avatar answered Jan 11 '23 13:01

George Phillips


First off, you can use GBDK, which is a C compiler and library for the Gameboy. It does provide access to the registers in gb/hardware.h (but that isn't listed in the doc file, since there's no comment for each individual register). It also supplies access to interrupts via methods in gb/gb.h: add_VBL, add_LCD, add_TIM, add_SIO, and add_JOY. (There's also remove methods named remove_).

For reference and/or your own use, here's the contents of gb/hardware.h:

#define __REG   volatile UINT8 *

#define P1_REG      (*(__REG)0xFF00)    /* Joystick: 1.1.P15.P14.P13.P12.P11.P10 */
#define SB_REG      (*(__REG)0xFF01)    /* Serial IO data buffer */
#define SC_REG      (*(__REG)0xFF02)    /* Serial IO control register */
#define DIV_REG     (*(__REG)0xFF04)    /* Divider register */
#define TIMA_REG    (*(__REG)0xFF05)    /* Timer counter */
#define TMA_REG     (*(__REG)0xFF06)    /* Timer modulo */
#define TAC_REG     (*(__REG)0xFF07)    /* Timer control */
#define IF_REG      (*(__REG)0xFF0F)    /* Interrupt flags: 0.0.0.JOY.SIO.TIM.LCD.VBL */
#define NR10_REG    (*(__REG)0xFF10)    /* Sound register */
#define NR11_REG    (*(__REG)0xFF11)    /* Sound register */
#define NR12_REG    (*(__REG)0xFF12)    /* Sound register */
#define NR13_REG    (*(__REG)0xFF13)    /* Sound register */
#define NR14_REG    (*(__REG)0xFF14)    /* Sound register */
#define NR21_REG    (*(__REG)0xFF16)    /* Sound register */
#define NR22_REG    (*(__REG)0xFF17)    /* Sound register */
#define NR23_REG    (*(__REG)0xFF18)    /* Sound register */
#define NR24_REG    (*(__REG)0xFF19)    /* Sound register */
#define NR30_REG    (*(__REG)0xFF1A)    /* Sound register */
#define NR31_REG    (*(__REG)0xFF1B)    /* Sound register */
#define NR32_REG    (*(__REG)0xFF1C)    /* Sound register */
#define NR33_REG    (*(__REG)0xFF1D)    /* Sound register */
#define NR34_REG    (*(__REG)0xFF1E)    /* Sound register */
#define NR41_REG    (*(__REG)0xFF20)    /* Sound register */
#define NR42_REG    (*(__REG)0xFF21)    /* Sound register */
#define NR43_REG    (*(__REG)0xFF22)    /* Sound register */
#define NR44_REG    (*(__REG)0xFF23)    /* Sound register */
#define NR50_REG    (*(__REG)0xFF24)    /* Sound register */
#define NR51_REG    (*(__REG)0xFF25)    /* Sound register */
#define NR52_REG    (*(__REG)0xFF26)    /* Sound register */
#define LCDC_REG    (*(__REG)0xFF40)    /* LCD control */
#define STAT_REG    (*(__REG)0xFF41)    /* LCD status */
#define SCY_REG     (*(__REG)0xFF42)    /* Scroll Y */
#define SCX_REG     (*(__REG)0xFF43)    /* Scroll X */
#define LY_REG      (*(__REG)0xFF44)    /* LCDC Y-coordinate */
#define LYC_REG     (*(__REG)0xFF45)    /* LY compare */
#define DMA_REG     (*(__REG)0xFF46)    /* DMA transfer */
#define BGP_REG     (*(__REG)0xFF47)    /* BG palette data */
#define OBP0_REG    (*(__REG)0xFF48)    /* OBJ palette 0 data */
#define OBP1_REG    (*(__REG)0xFF49)    /* OBJ palette 1 data */
#define WY_REG      (*(__REG)0xFF4A)    /* Window Y coordinate */
#define WX_REG      (*(__REG)0xFF4B)    /* Window X coordinate */
#define KEY1_REG    (*(__REG)0xFF4D)    /* CPU speed */
#define VBK_REG     (*(__REG)0xFF4F)    /* VRAM bank */
#define HDMA1_REG   (*(__REG)0xFF51)    /* DMA control 1 */
#define HDMA2_REG   (*(__REG)0xFF52)    /* DMA control 2 */
#define HDMA3_REG   (*(__REG)0xFF53)    /* DMA control 3 */
#define HDMA4_REG   (*(__REG)0xFF54)    /* DMA control 4 */
#define HDMA5_REG   (*(__REG)0xFF55)    /* DMA control 5 */
#define RP_REG      (*(__REG)0xFF56)    /* IR port */
#define BCPS_REG    (*(__REG)0xFF68)    /* BG color palette specification */
#define BCPD_REG    (*(__REG)0xFF69)    /* BG color palette data */
#define OCPS_REG    (*(__REG)0xFF6A)    /* OBJ color palette specification */
#define OCPD_REG    (*(__REG)0xFF6B)    /* OBJ color palette data */
#define SVBK_REG    (*(__REG)0xFF70)    /* WRAM bank */
#define IE_REG      (*(__REG)0xFFFF)    /* Interrupt enable */

These are done in the same way as George Phillips's answer, and thus can be used like normal variables.

The code that is used by GBDK to add and remove interrupts is found in libc\gb\crt0.s, but I don't know enough of assembly to include the relevant sections in this post.

I'm not sure about how to avoid the busy loop either.

like image 42
Pokechu22 Avatar answered Jan 11 '23 15:01

Pokechu22