mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-03-10 21:25:31 +00:00
263 lines
9.9 KiB
C
263 lines
9.9 KiB
C
|
||
//#resource "gb/global.sgb"
|
||
//#link "gb/crt0.sgb"
|
||
//#link "gb/sfr.sgb"
|
||
#include <stdint.h>
|
||
#include "gb/types.h"
|
||
#include "gb/hardware.h"
|
||
#include "gb/gb.h"
|
||
|
||
/* VRAM regions */
|
||
#define TILE_DATA ((volatile uint8_t *)0x8000) /* 384 tiles × 16 bytes */
|
||
#define BG_MAP ((volatile uint8_t *)0x9800) /* 32×32 tile map */
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* wait_vblank
|
||
* Spin until LY reaches 144 (start of V-Blank period).
|
||
* ------------------------------------------------------------------------- */
|
||
static void wait_vblank(void)
|
||
{
|
||
while (rLY != 144);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* Minimal 8×8 ASCII font (space 0x20 … 'Z' 0x5A)
|
||
* Each character is 8 bytes, one byte per row, MSB = leftmost pixel.
|
||
* Only printable upper-case letters, digits, space, and basic punctuation
|
||
* are included.
|
||
* ------------------------------------------------------------------------- */
|
||
static const uint8_t font[][8] = {
|
||
/* 0x20 ' ' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x21 '!' */ {0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x00},
|
||
/* 0x22 '"' */ {0x6C,0x6C,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x23 '#' */ {0x6C,0x6C,0xFE,0x6C,0xFE,0x6C,0x6C,0x00},
|
||
/* 0x24 '$' */ {0x18,0x7E,0xC0,0x7C,0x06,0xFC,0x18,0x00},
|
||
/* 0x25 '%' */ {0xC6,0xCC,0x18,0x30,0x66,0xC6,0x00,0x00},
|
||
/* 0x26 '&' */ {0x38,0x6C,0x68,0x76,0xDC,0xCC,0x76,0x00},
|
||
/* 0x27 ''' */ {0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x28 '(' */ {0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00},
|
||
/* 0x29 ')' */ {0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00},
|
||
/* 0x2A '*' */ {0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00},
|
||
/* 0x2B '+' */ {0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00},
|
||
/* 0x2C ',' */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30},
|
||
/* 0x2D '-' */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00},
|
||
/* 0x2E '.' */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00},
|
||
/* 0x2F '/' */ {0x06,0x0C,0x18,0x30,0x60,0xC0,0x00,0x00},
|
||
/* 0x30 '0' */ {0x7C,0xC6,0xCE,0xD6,0xE6,0xC6,0x7C,0x00},
|
||
/* 0x31 '1' */ {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00},
|
||
/* 0x32 '2' */ {0x7C,0xC6,0x06,0x1C,0x70,0xC6,0xFE,0x00},
|
||
/* 0x33 '3' */ {0x7C,0xC6,0x06,0x3C,0x06,0xC6,0x7C,0x00},
|
||
/* 0x34 '4' */ {0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x1E,0x00},
|
||
/* 0x35 '5' */ {0xFE,0xC0,0xF8,0x0C,0x06,0xCC,0x78,0x00},
|
||
/* 0x36 '6' */ {0x38,0x60,0xC0,0xF8,0xCC,0xCC,0x78,0x00},
|
||
/* 0x37 '7' */ {0xFE,0xC6,0x0C,0x18,0x30,0x30,0x30,0x00},
|
||
/* 0x38 '8' */ {0x78,0xCC,0xCC,0x78,0xCC,0xCC,0x78,0x00},
|
||
/* 0x39 '9' */ {0x78,0xCC,0xCC,0x7C,0x0C,0x18,0x70,0x00},
|
||
/* 0x3A ':' */ {0x00,0x18,0x18,0x00,0x18,0x18,0x00,0x00},
|
||
/* 0x3B ';' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x3C '<' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x3D '=' */ {0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00},
|
||
/* 0x3E '>' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x3F '?' */ {0x7C,0xC6,0x06,0x1C,0x18,0x00,0x18,0x00},
|
||
/* 0x40 '@' */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||
/* 0x41 'A' */ {0x10,0x38,0x6C,0xC6,0xFE,0xC6,0xC6,0x00},
|
||
/* 0x42 'B' */ {0xFC,0xC6,0xC6,0xFC,0xC6,0xC6,0xFC,0x00},
|
||
/* 0x43 'C' */ {0x78,0xCC,0xC0,0xC0,0xC0,0xCC,0x78,0x00},
|
||
/* 0x44 'D' */ {0xF8,0xCC,0xC6,0xC6,0xC6,0xCC,0xF8,0x00},
|
||
/* 0x45 'E' */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xFE,0x00},
|
||
/* 0x46 'F' */ {0xFE,0xC0,0xC0,0xF8,0xC0,0xC0,0xC0,0x00},
|
||
/* 0x47 'G' */ {0x78,0xCC,0xC0,0xDE,0xCC,0xCC,0x7E,0x00},
|
||
/* 0x48 'H' */ {0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0x00},
|
||
/* 0x49 'I' */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x7E,0x00},
|
||
/* 0x4A 'J' */ {0x1E,0x06,0x06,0x06,0xC6,0xC6,0x7C,0x00},
|
||
/* 0x4B 'K' */ {0xC6,0xCC,0xD8,0xF0,0xD8,0xCC,0xC6,0x00},
|
||
/* 0x4C 'L' */ {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xFE,0x00},
|
||
/* 0x4D 'M' */ {0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0x00},
|
||
/* 0x4E 'N' */ {0xC6,0xE6,0xF6,0xDE,0xCE,0xC6,0xC6,0x00},
|
||
/* 0x4F 'O' */ {0x78,0xCC,0xCC,0xCC,0xCC,0xCC,0x78,0x00},
|
||
/* 0x50 'P' */ {0xFC,0xC6,0xC6,0xFC,0xC0,0xC0,0xC0,0x00},
|
||
/* 0x51 'Q' */ {0x78,0xCC,0xCC,0xCC,0xEC,0x78,0x1C,0x00},
|
||
/* 0x52 'R' */ {0xFC,0xC6,0xC6,0xFC,0xD8,0xCC,0xC6,0x00},
|
||
/* 0x53 'S' */ {0x7C,0xC6,0xC0,0x7C,0x06,0xC6,0x7C,0x00},
|
||
/* 0x54 'T' */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00},
|
||
/* 0x55 'U' */ {0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00},
|
||
/* 0x56 'V' */ {0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00},
|
||
/* 0x57 'W' */ {0xC6,0xC6,0xD6,0xFE,0xFE,0xEE,0xC6,0x00},
|
||
/* 0x58 'X' */ {0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00},
|
||
/* 0x59 'Y' */ {0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00},
|
||
/* 0x5A 'Z' */ {0xFE,0x06,0x0C,0x18,0x30,0x60,0xFE,0x00},
|
||
};
|
||
#define FONT_FIRST 0x20
|
||
#define FONT_LAST 0x5A
|
||
#define FONT_COUNT (FONT_LAST - FONT_FIRST + 1)
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* load_font
|
||
* Write font glyphs into VRAM tile data.
|
||
* The Game Boy tile format stores two bits per pixel across two bitplanes;
|
||
* for a 2-colour font we set both bitplanes to the same byte so colour
|
||
* index 3 (black) is used for set pixels and 0 (white) for clear pixels.
|
||
* Tile 0 is reserved as blank; glyphs start at tile 1.
|
||
* ------------------------------------------------------------------------- */
|
||
static void load_font(void)
|
||
{
|
||
uint8_t t, r;
|
||
/* Tile 0: blank (already zeroed by boot ROM, but be explicit) */
|
||
for (r = 0; r < 16; r++) {
|
||
TILE_DATA[r] = 0x00;
|
||
}
|
||
/* Tiles 1 … FONT_COUNT: one tile per glyph */
|
||
for (t = 0; t < FONT_COUNT; t++) {
|
||
volatile uint8_t *dst = TILE_DATA + (t + 1) * 16;
|
||
for (r = 0; r < 8; r++) {
|
||
uint8_t row = font[t][r];
|
||
dst[r * 2] = row; /* bitplane 0 */
|
||
dst[r * 2 + 1] = row; /* bitplane 1 — same → colour index 3 */
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* put_char / put_str
|
||
* Write a tile index into the BG map at tile position (col, row).
|
||
* Tile index = (ascii - FONT_FIRST) + 1, clamped to blank for unknowns.
|
||
* ------------------------------------------------------------------------- */
|
||
static void put_char(uint8_t col, uint8_t row, char c)
|
||
{
|
||
uint8_t tile;
|
||
if (c >= FONT_FIRST && c <= FONT_LAST)
|
||
tile = (uint8_t)(c - FONT_FIRST) + 1;
|
||
else
|
||
tile = 0; /* blank */
|
||
BG_MAP[row * 32u + col] = tile;
|
||
}
|
||
|
||
static void put_str(uint8_t col, uint8_t row, const char *s)
|
||
{
|
||
while (*s) {
|
||
put_char(col++, row, *s++);
|
||
}
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* clear_map
|
||
* Fill the visible 20×18 region of the BG map with tile 0 (blank).
|
||
* ------------------------------------------------------------------------- */
|
||
static void clear_map(void)
|
||
{
|
||
uint8_t r, c;
|
||
for (r = 0; r < 18; r++)
|
||
for (c = 0; c < 20; c++)
|
||
BG_MAP[r * 32u + c] = 0;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* read_joypad
|
||
* Returns the buttons currently held.
|
||
* Bits: 7=start 6=select 5=B 4=A (action keys, P14 low)
|
||
* 3=down 2=up 1=left 0=right (d-pad, P15 low)
|
||
* We return a simple mask where bit 7 = START, bit 0 = RIGHT.
|
||
* ------------------------------------------------------------------------- */
|
||
#define BTN_START (1 << 7)
|
||
#define BTN_SELECT (1 << 6)
|
||
#define BTN_B (1 << 5)
|
||
#define BTN_A (1 << 4)
|
||
#define BTN_DOWN (1 << 3)
|
||
#define BTN_UP (1 << 2)
|
||
#define BTN_LEFT (1 << 1)
|
||
#define BTN_RIGHT (1 << 0)
|
||
|
||
static uint8_t read_joypad(void)
|
||
{
|
||
uint8_t result = 0;
|
||
|
||
/* Read action buttons (P14 = 0) */
|
||
P1_REG = 0x20;
|
||
P1_REG; P1_REG; /* dummy reads for settling */
|
||
result |= ((~P1_REG) & 0x0F) << 4;
|
||
|
||
/* Read d-pad (P15 = 0) */
|
||
P1_REG = 0x10;
|
||
P1_REG; P1_REG;
|
||
result |= ((~P1_REG) & 0x0F);
|
||
|
||
P1_REG = 0x30; /* deselect both */
|
||
return result;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* main
|
||
* ------------------------------------------------------------------------- */
|
||
void main(void)
|
||
{
|
||
uint8_t prev_joy = 0;
|
||
uint8_t curr_joy = 0;
|
||
uint8_t screen = 0;
|
||
|
||
/* --- Disable LCD so we can safely write to VRAM --- */
|
||
wait_vblank();
|
||
LCDC_REG = 0x00;
|
||
|
||
/* --- Load font, clear map, set palette --- */
|
||
load_font();
|
||
clear_map();
|
||
|
||
/*
|
||
* Palette: 0=white, 1=light grey, 2=dark grey, 3=black
|
||
* BGP format: bits[1:0]=colour0, bits[3:2]=colour1, etc.
|
||
* 0xE4 = 11 10 01 00 → shade 0→0,1→1,2→2,3→3 (standard)
|
||
*/
|
||
BGP_REG = 0xE4;
|
||
SCX_REG = 0;
|
||
SCY_REG = 0;
|
||
|
||
/* --- Draw initial screen --- */
|
||
put_str(3, 1, "GAME BOY DEMO");
|
||
put_str(2, 3, "HELLO, WORLD!");
|
||
put_str(1, 5, "BUILT WITH SDCC");
|
||
put_str(0, 16, "PRESS START");
|
||
|
||
/*
|
||
* Re-enable LCD:
|
||
* Bit 7: LCD on
|
||
* Bit 4: tile data at 0x8000 (unsigned indexing)
|
||
* Bit 0: BG enable
|
||
*/
|
||
LCDC_REG = LCDCF_ON | LCDCF_WIN9800 | LCDCF_BG8000 | LCDCF_B_BGON;
|
||
|
||
/* --- Main loop --- */
|
||
for (;;) {
|
||
wait_vblank();
|
||
|
||
curr_joy = read_joypad();
|
||
|
||
/* Edge detect START */
|
||
if ((curr_joy & BTN_START) && !(prev_joy & BTN_START)) {
|
||
if (screen == 0) {
|
||
/* Switch to second screen */
|
||
wait_vblank();
|
||
LCDC_REG = 0x00;
|
||
clear_map();
|
||
put_str(1, 4, "YOU PRESSED START!");
|
||
put_str(3, 6, "HAVE FUN :)");
|
||
put_str(0, 16, "PRESS START AGAIN");
|
||
LCDC_REG = LCDCF_ON | LCDCF_B_BGON;
|
||
screen = 1;
|
||
} else {
|
||
/* Back to title */
|
||
wait_vblank();
|
||
LCDC_REG = 0x00;
|
||
clear_map();
|
||
put_str(3, 1, "GAME BOY DEMO");
|
||
put_str(2, 3, "HELLO, WORLD!");
|
||
put_str(1, 5, "BUILT WITH SDCC");
|
||
put_str(0, 16, "PRESS START");
|
||
LCDC_REG = LCDCF_ON | LCDCF_B_BGON;
|
||
screen = 0;
|
||
}
|
||
}
|
||
|
||
prev_joy = curr_joy;
|
||
}
|
||
}
|