git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

docs/pages/03_using_gbdk.md

      1 @page docs_using_gbdk Using GBDK
      2 
      3 
      4 # Interrupts
      5 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.
      6 
      7 Interrupts in GBDK are handled using the functions @ref disable_interrupts(), @ref enable_interrupts(), @ref set_interrupts(uint8_t ier) and the interrupt service routine (ISR) linkers @ref add_VBL(), @ref add_TIM, @ref add_low_priority_TIM, @ref add_LCD, @ref add_SIO and @ref add_JOY which add interrupt handlers for the vertical blank, timer, LCD, serial link and joypad interrupts respectively.
      8 
      9 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.
     10 
     11 Interrupts should be disabled before adding ISRs. To use multiple interrupts, _logical OR_ the relevant IFLAGs together.
     12 
     13 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.
     14 
     15 For more detail on the Game Boy interrupts consider reading about them in the @ref Pandocs.
     16 
     17 ## Available Interrupts
     18 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.
     19 
     20   - VBL : LCD Vertical Blanking period start
     21     - The default VBL ISR is installed automatically.
     22       - See @ref add_VBL() and @ref remove_VBL()
     23 
     24   - LCD : LCDC status (such as the start of a horizontal line)
     25     - See @ref add_LCD() and @ref remove_LCD()
     26     - Example project: `lcd_isr_wobble`
     27 
     28   - TIM : Timer overflow
     29     - See @ref add_TIM() (or @ref add_low_priority_TIM() ) and @ref remove_TIM()
     30     - Example project: `tim`
     31 
     32   - SIO : Serial Link I/O transfer end
     33     - The default SIO ISR gets installed automatically if any of the standard SIO calls are used (send_byte(), receive_byte()).
     34     - Once installed the default SIO ISR cannot be removed. Only secondary chained SIO ISRs (added with add_SIO() ) can be removed.
     35     - See @ref add_SIO() and @ref remove_SIO()
     36     - Example project: `comm`
     37 
     38   - JOY : Transition from high to low of a joypad button
     39       - See @ref add_JOY() and @ref remove_JOY()
     40 
     41 ## Adding your own interrupt handler
     42 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.
     43 
     44 Interrupt handlers are called in sequence. To install a new interrupt handler, do the following:
     45 
     46   1. Write a function (say foo()) that takes no parameters, and that returns nothing. Remember that the code executed in an interrupt handler must be short.
     47   2. Inside a `__critical { ... }` section, install your interrupt handling routines using the add_XXX() function, where XXX is the interrupt that you want to handle.
     48   3. Enable interrupts for the IRQ you want to handle, using the set_interrupts() function. Note that the VBL interrupt is already enabled before the main() function is called. If you want to set the interrupts before main() is called, you must install an initialization routine.
     49 
     50 See the `irq` example project for additional details for a complete example.
     51 
     52 ## Using your own Interrupt Dispatcher
     53 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.
     54   - Exception: the VBL dispatcher will always be linked in at compile time.
     55   - For the SIO interrupt, also do not make any standard SIO calls to avoid having its dispatcher installed.
     56 
     57 Then, @ref ISR_VECTOR() or @ref ISR_NESTED_VECTOR() can be used to install a custom ISR handler.
     58 
     59 @anchor isr_nowait_info
     60 ## Returning from Interrupts and STAT mode
     61 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.
     62 
     63 You can change this behavior using @ref nowait_int_handler() which does not check @ref STAT_REG before returning. Also see @ref wait_int_handler().
     64 
     65 
     66 # What GBDK does automatically and behind the scenes
     67 
     68 ## NES console
     69 For implementation details on the NES console in GBDK, see the @ref nes_technical_details "NES entry" in @ref docs_supported_consoles "Supported Consoles & Cross Compiling"
     70 
     71 ## OAM (VRAM Sprite Attribute Table)
     72 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.
     73 
     74 ## Graphics Tile Maps and Data on Startup
     75 By default for the Game Boy GBDK assigns:
     76 - Background and Window Tile data starting at `0x8800`
     77 - Background Tile Map starting at `0x9800`
     78 - Window Tile Map starting at `0x9C00`
     79 - Sprites to `8x8` mode
     80 
     81 ## Font tiles when using stdio.h
     82 Including @ref stdio.h and using functions such as @ref 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.
     83 
     84 ## Default Interrupt Service Handlers (ISRs)
     85   - V-Blank: A default V-Blank ISR is installed on startup which copies the Shadow OAM to the hardware OAM and increments the global @ref sys_time variable once per frame.
     86   - Serial Link I/O: If any of the GBDK serial link functions are used such as @ref send_byte() and @ref receive_byte(), the default SIO serial link handler will be installed automatically at compile-time.
     87   - APA Graphics Mode: When this mode is used (via @ref drawing.h) a custom LCD ISR handler will be installed (`drawing_lcd`). Changing the mode to (`mode(M_TEXT_OUT);`) will cause them to be de-installed. These handlers are used to change the tile data source at start-of-frame and mid-frame so that 384 background tiles can be used instead of the typical 256.
     88 
     89 
     90 ## Ensuring Safe Access to Graphics Memory
     91 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.
     92 
     93 The ISR return behavior @ref isr_nowait_info "can be turned off" using the @ref nowait_int_handler.
     94 
     95 For more details see the related Pandocs section: https://gbdev.io/pandocs/Accessing_VRAM_and_OAM.html
     96 
     97 
     98 @anchor using_compression
     99 # Compression
    100 For programs that would benefit from compression GBDK includes the @ref utility_gbcompress "gbcompress" utility and companion API functions.
    101 
    102 In addition to the built-in compression unapack is another option:
    103 - UnaPACK aPack decompression by Toxa: https://github.com/untoxa
    104 - apultra aPack compression: https://github.com/emmanuel-marty/apultra
    105 
    106 Another way to save space is using 1 bit-per-pixel (bpp) tile pattern data instead of 2-bpp or 4-bpp data. This can reduce the ROM size for groups of tiles which only require two shades of color.
    107 - See: set_1bpp_colors(), set_bkg_1bpp_data(), set_win_1bpp_data(), set_sprite_1bpp_data()
    108 
    109 Use of 1-bpp tile pattern data may be combined with the compression described above to save even more space, however that approach requires using an intermediary RAM buffer before the tile pattern data can be written to VRAM with the set_*_1bpp_data() functions.
    110 
    111 
    112 # Copying Functions to RAM and HIRAM
    113 See the `ram_function` example project included with GBDK which demonstrates copying functions to RAM and HIRAM.
    114 
    115 `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.
    116 
    117 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.
    118 
    119 There are basically two ways for calling a function located in RAM, HIRAM, or ROM:
    120 
    121   - Declare a pointer-to-function variable, and set it to the address of the function to call.
    122   - Declare the function as extern, and set its address at link time using the -Wl-gXXX=# flag (where XXX is the name of the function, and # is its address).
    123 
    124 The second approach is slightly more efficient. Both approaches are demonstrated in the `ram_function.c` example.
    125 
    126 
    127 # Mixing C and Assembly
    128 _The following is primarily oriented toward the Game Boy and related clones (sm83 devices), other targets such as sms/gg may vary._
    129 
    130 You can mix C and assembly (ASM) in two ways as described below.
    131 - For additional detail see the @ref links_sdcc_docs and @ref sdcc_calling_convention "SDCC Calling Conventions".
    132 
    133 ## Inline ASM within C source files
    134 - The optional `NAKED` keyword may be used to indicate that the funtion setup and return should have no handling done by the compiler, and will instead be handled entirely by user code.
    135 - If the entire function preserves some registers the optional `PRESERVES_REGS` keyword may be used as additional hinting for the compiler. For example `PRESERVES_REGS(b, c)`. By default it is assumed by the compiler that no registers are preserved.
    136 
    137 Example:  
    138 
    139        __asm__("nop");
    140 
    141 Another Example:  
    142 
    143       void some_c_function() 
    144       {
    145         // Optionally do something
    146         __asm
    147             (ASM code goes here)
    148         __endasm;
    149       }
    150 
    151 
    152 ## In Separate ASM files
    153 
    154 It is possible to assemble and link files written in ASM alongside files written in C.
    155 
    156   - A C identifier `i` will be called `_i` in assembly.
    157   - Parameters will be passed, registers saved and results returned in a manner based on the @ref sdcc_calling_convention "SDCC Calling Convention" used and how the function is declared.
    158   - Assembly identifiers are exported using the `.globl` directive.
    159   - See global.s for examples of hardware register deginitions.
    160 
    161 Here is an example of how to mix assembly with C:
    162 
    163 `main.c`
    164 
    165     uint16_t add(uint16_t, uint16_t);
    166 
    167     main()
    168     {
    169       uint16_t i;
    170 
    171       i = add(1, 3);
    172     }
    173 
    174 `add.s`
    175 
    176     .globl _add
    177 
    178     .area _CODE    
    179     _add:         ; uint16_t add(uint16_t First, uint16_t Second)
    180                   ;
    181                   ; In this particular example there is no use and modification of the stack
    182                   ; No need to save and restore registers
    183                   ;
    184                   ; For calling convention __sdcccall(1)
    185                   ; - first 16 bit param is passed in DE
    186                   ; - second 16 bit param is passed in BC
    187              
    188     ; Load Second Parameter ("Second") into HL
    189     ld  l,  c
    190     ld  h,  b
    191 
    192     ; Add Parameters "Second" + "First"
    193     add hl, de
    194 
    195     ; Return result in BC
    196     ld  c,  l
    197     ld  b,  h
    198     ret           ; 16 bit values are returned in BC
    199 
    200 
    201 # Including binary files in C source with incbin
    202 Data from binary files can be included in C source files as a const array using the @ref INCBIN() macro.
    203 
    204 See the `incbin` example project for a demo of how to use it.
    205 
    206 
    207 # Known Issues and Limitations
    208 
    209 ## SDCC
    210   - Const arrays declared with `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.
    211 
    212   - SDCC banked calls and @ref far_pointers in GBDK only save one byte for the ROM bank, so for example they are limited to __bank 15__ max for MBC1 and __bank 255__ max for MBC5. See @ref banked_calls for more details.
    213   - In SDCC __pre-initializing a variable__ assigned to SRAM with `-Wf-ba*` will force that variable to be in WRAM instead.
    214     - The following is a workaround for initializing a variable in SRAM. It assignes value `0xA5` to a variable in `bank 0` and assigned to address `0xA000` using the AT() directive:
    215 
    216 
    217             // Workaround for initializing variable in SRAM
    218             // (MBC RAM and Bank needs to get enabled during GSINIT loading)
    219             static uint8_t AT(0x0000) __rRAMG = 0x0a; // Enable SRAM
    220             static uint8_t AT(0x4000) __rRAMB = 0x00; // Set SRAM bank 0
    221             // Now SRAM is enabled so the variable can get initialized
    222             uint8_t AT(0xA000)      initialized_sram_var = 0xA5u;
    223 
    224 

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.