GBDK 2020 Docs
4.1.1
API Documentation for GBDK 2020
|
Interrupts allow execution to jump to a different part of your code as soon as an external event occurs - for example the LCD entering the vertical blank period, serial data arriving or the timer reaching its end count. For an example see the irq.c sample project.
Interrupts in GBDK are handled using the functions disable_interrupts(), enable_interrupts(), set_interrupts(uint8_t ier) and the interrupt service routine (ISR) linkers add_VBL(), add_TIM, add_low_priority_TIM, add_LCD, add_SIO and add_JOY which add interrupt handlers for the vertical blank, timer, LCD, serial link and joypad interrupts respectively.
Since an interrupt can occur at any time an Interrupt Service Request (ISR) cannot take any arguments or return anything. Its only way of communicating with the greater program is through the global variables. When interacting with those shared ISR global variables from main code outside the interrupt, it is a good idea to wrap them in a critical {}
section in case the interrupt occurs and modifies the variable while it is being used.
Interrupts should be disabled before adding ISRs. To use multiple interrupts, logical OR the relevant IFLAGs together.
ISRs should be kept as small and short as possible, do not write an ISR so long that the Game Boy hardware spends all of its time servicing interrupts and has no time spare for the main code.
For more detail on the Game Boy interrupts consider reading about them in the Pandocs.
The GameBoy hardware can generate 5 types of interrupts. Custom Interrupt Service Routines (ISRs) can be added in addition to the built-in ones available in GBDK.
lcd_isr_wobble
tim
comm
It is possible to install your own interrupt handlers (in C or in assembly) for any of these interrupts. Up to 4 chained handlers may be added, with the last added being called last. If the remove_VBL() function is to be called, only three may be added for VBL.
Interrupt handlers are called in sequence. To install a new interrupt handler, do the following:
__critical { ... }
section, install your interrupt handling routines using the add_XXX() function, where XXX is the interrupt that you want to handle.See the irq
example project for additional details for a complete example.
If you want to use your own Interrupt Dispatcher instead of the GBDK chained dispatcher (for improved performance), then don't call the add_...()
function for the respective interrupt and its dispatcher won't be installed.
Then, ISR_VECTOR() or ISR_NESTED_VECTOR() can be used to install a custom ISR handler.
By default when an Interrupt handler completes and is ready to exit it will check STAT_REG and only return at the BEGINNING of either LCD Mode 0 or Mode 1. This helps prevent graphical glitches caused when an ISR interrupts a graphics operation in one mode but returns in a different mode for which that graphics operation is not allowed.
You can change this behavior using nowait_int_handler() which does not check STAT_REG before returning. Also see wait_int_handler().
GBDK sets up a Shadow OAM which gets copied automatically to the hardware OAM by the default V-Blank ISR. The Shadow OAM allows updating sprites without worrying about whether it is safe to write to them or not based on the hardware LCD mode.
Including stdio.h and using functions such as printf() will use a large number of the background tiles for font characters. If stdio.h is not included then that space will be available for use with other tiles instead.
drawing_lcd
). To exit APA mode and remove the ISR, use remove_LCD(drawing_lcd);
This ISR handler changes the tile data source at start-of-frame and mid-frame so that 384 background tiles can be used instead of the typical 256.There are certain times during each video frame when memory and registers relating to graphics are "busy" and should not be read or written to (otherwise there may be corrupt or dropped data). GBDK handles this automatically for most graphics related API calls. It also ensures that ISR handlers return in such a way that if they interrupted a graphics access then it will only resume when access is allowed.
The ISR return behavior can be turned off using the nowait_int_handler.
For more details see the related Pandocs section: https://gbdev.io/pandocs/Accessing_VRAM_and_OAM.html \
See the ram_function
example project included with GBDK which demonstrates copying functions to RAM and HIRAM.
Warning!
Copying of functions is generally not safe since they may contain jumps to absolute addresses that will not be converted to match the new location.
It is possible to copy functions to RAM and HIRAM (using the memcpy() and hiramcpy() functions), and execute them from C. Ensure you have enough free space in RAM or HIRAM for copying a function.
There are basically two ways for calling a function located in RAM, HIRAM, or ROM:
The second approach is slightly more efficient. Both approaches are demonstrated in the ram_function.c
example.
You can mix C and assembly (ASM) in two ways as described below. For additional detail see the links_sdcc_docs.
Example:
__asm__("nop");
Another Example:
void some_c_function() { // Optionally do something __asm (ASM code goes here) __endasm; }
It is possible to assemble and link files written in ASM alongside files written in C.
i
will be called _i
in assembly.DE
register.SP+2
because the return address is also saved on the stack)..globl
directive._reg_0xXX
where XX
is the register number (see sound.c
for an example).HL
(and DE
when the function returns a result).Here is an example of how to mix assembly with C:
main.c
main() { int16_t i; int16_t add(int16_t, int16_t); i = add(1, 3); }
add.s
.globl _add _add: ; int16_t add(int16_t a, int16_t b) ; There is no register to save: ; BC is not used ; DE is the return register ; HL needs never to be saved LDA HL,2(SP) LD E,(HL) ; Get a in DE INC HL LD D,(HL) INC HL LD A,(HL) ; Get b in HL INC HL LD H,(HL) LD L,A ADD HL,DE ; Add DE to HL LD D,H LD E,L ; There is no register to restore RET ; Return result in DE
Data from binary files can be included in C source files as a const array using the INCBIN() macro.
See the incbin
example project for a demo of how to use it.
somevar[n] = {x}
will NOT get initialized with value x
. This may change when the SDCC RLE initializer is fixed. Use memset for now if you need it.-Wf-ba*
will force that variable to be in WRAM instead.0xA5
to a variable in bank 0
and assigned to address 0xA000
using the AT() directive:// Workaround for initializing variable in SRAM // (MBC RAM and Bank needs to get enabled during GSINIT loading) static uint8_t AT(0x0000) __rRAMG = 0x0a; // Enable SRAM static uint8_t AT(0x4000) __rRAMB = 0x00; // Set SRAM bank 0 // Now SRAM is enabled so the variable can get initialized uint8_t AT(0xA000) initialized_sram_var = 0xA5u;