diff --git a/gbdk-lib/examples/megaduck/Makefile b/gbdk-lib/examples/megaduck/Makefile new file mode 100644 index 00000000..f891d672 --- /dev/null +++ b/gbdk-lib/examples/megaduck/Makefile @@ -0,0 +1,17 @@ +# Register all subdirectories in the project's root directory. +SUBDIRS := $(wildcard */.) + +# Top-level phony targets. +all clean compile.bat: $(SUBDIRS) FORCE +# Similar to: +# .PHONY: all clean +# all clean: $(SUBDIRS) +# GNU's .PHONY target is more efficient in that it explicitly declares non-files. + +# Recurse `make` into each subdirectory +# Pass along targets specified at command-line (if any). +$(SUBDIRS): FORCE + $(MAKE) -C $@ $(MAKECMDGOALS) + +# Force targets. +FORCE: \ No newline at end of file diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/Makefile b/gbdk-lib/examples/megaduck/laptop_keyboard/Makefile new file mode 100644 index 00000000..f8b3aea8 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/Makefile @@ -0,0 +1,94 @@ +# If you move this project you can change the directory +# to match your GBDK root directory (ex: GBDK_HOME = "C:/GBDK/" +ifndef GBDK_HOME + GBDK_HOME = ../../../ +endif + +LCC = $(GBDK_HOME)bin/lcc + +# Set platforms to build here, spaced separated. (These are in the separate Makefile.targets) +# They can also be built/cleaned individually: "make gg" and "make gg-clean" +# Possible are: gb gbc pocket megaduck sms gg +TARGETS= megaduck # gb pocket megaduck sms gg nes + +# Configure platform specific LCC flags here: +LCCFLAGS_gb = # -Wl-yt0x1B # Set an MBC for banking (1B-ROM+MBC5+RAM+BATT) +LCCFLAGS_pocket = # -Wl-yt0x1B # Usually the same as required for .gb +LCCFLAGS_duck = # -Wl-yt0x1B # Usually the same as required for .gb +LCCFLAGS_gbc = # -Wl-yt0x1B -Wm-yc # Same as .gb with: -Wm-yc (gb & gbc) or Wm-yC (gbc exclusive) +LCCFLAGS_sms = +LCCFLAGS_gg = +LCCFLAGS_nes = + +LCCFLAGS += $(LCCFLAGS_$(EXT)) # This adds the current platform specific LCC Flags + +LCCFLAGS += -Wl-j # -Wm-yS -Wm-yoA -Wm-ya4 -autobank -Wb-ext=.rel -Wb-v # MBC + Autobanking related flags +# LCCFLAGS += -debug # Uncomment to enable debug output +# LCCFLAGS += -v # Uncomment for lcc verbose output +LCCFLAGS += -Wf-MMD -Wf-Wp-MP # Header file dependency output (-MMD) for Makefile use + per-header Phony rules (-MP) +CFLAGS += -Wf-MMD -Wf-Wp-MP # Header file dependency output (-MMD) for Makefile use + per-header Phony rules (-MP) + +# You can set the name of the ROM file here +PROJECTNAME = megaduck_keyboard + +# EXT?=gb # Only sets extension to default (game boy .gb) if not populated +SRCDIR = src +OBJDIR = obj/$(EXT) +RESDIR = res +BINDIR = build/$(EXT) +MKDIRS = $(OBJDIR) $(BINDIR) # See bottom of Makefile for directory auto-creation + +BINS = $(OBJDIR)/$(PROJECTNAME).$(EXT) +CSOURCES = $(foreach dir,$(SRCDIR),$(notdir $(wildcard $(dir)/*.c))) $(foreach dir,$(RESDIR),$(notdir $(wildcard $(dir)/*.c))) + +ASMSOURCES = $(foreach dir,$(SRCDIR),$(notdir $(wildcard $(dir)/*.s))) +OBJS = $(CSOURCES:%.c=$(OBJDIR)/%.o) $(ASMSOURCES:%.s=$(OBJDIR)/%.o) + +# Dependencies (using output from -Wf-MMD -Wf-Wp-MP) +DEPS = $(OBJS:%.o=%.d) + +-include $(DEPS) + +# Builds all targets sequentially +all: $(TARGETS) + +test: + echo $(CSOURCES) + + +# Compile .c files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.c + $(LCC) $(CFLAGS) -c -o $@ $< + +# Compile .c files in "res/" to .o object files +$(OBJDIR)/%.o: $(RESDIR)/%.c + $(LCC) $(CFLAGS) -c -o $@ $< + +# Compile .s assembly files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.s + $(LCC) $(CFLAGS) -c -o $@ $< + +# If needed, compile .c files in "src/" to .s assembly files +# (not required if .c is compiled directly to .o) +$(OBJDIR)/%.s: $(SRCDIR)/%.c + $(LCC) $(CFLAGS) -S -o $@ $< + +# Link the compiled object files into a .gb ROM file +$(BINS): $(OBJS) + $(LCC) $(LCCFLAGS) $(CFLAGS) -o $(BINDIR)/$(PROJECTNAME).$(EXT) $(OBJS) + +clean: + @echo Cleaning + @for target in $(TARGETS); do \ + $(MAKE) $$target-clean; \ + done + +# Include available build targets +include Makefile.targets + + +# create necessary directories after Makefile is parsed but before build +# info prevents the command from being pasted into the makefile +ifneq ($(strip $(EXT)),) # Only make the directories if EXT has been set by a target +$(info $(shell mkdir -p $(MKDIRS))) +endif diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/Makefile.targets b/gbdk-lib/examples/megaduck/laptop_keyboard/Makefile.targets new file mode 100644 index 00000000..9c3dab9e --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/Makefile.targets @@ -0,0 +1,54 @@ + +# Platform specific flags for compiling (only populate if they're both present) +ifneq ($(strip $(PORT)),) +ifneq ($(strip $(PLAT)),) +CFLAGS += -m$(PORT):$(PLAT) +endif +endif + +# Called by the individual targets below to build a ROM +build-target: $(BINS) + +clean-target: + rm -rf $(OBJDIR) + rm -rf $(BINDIR) + +gb-clean: + ${MAKE} clean-target EXT=gb +gb: + ${MAKE} build-target PORT=sm83 PLAT=gb EXT=gb + + +gbc-clean: + ${MAKE} clean-target EXT=gbc +gbc: + ${MAKE} build-target PORT=sm83 PLAT=gb EXT=gbc + + +pocket-clean: + ${MAKE} clean-target EXT=pocket +pocket: + ${MAKE} build-target PORT=sm83 PLAT=ap EXT=pocket + + +megaduck-clean: + ${MAKE} clean-target EXT=duck +megaduck: + ${MAKE} build-target PORT=sm83 PLAT=duck EXT=duck + + +sms-clean: + ${MAKE} clean-target EXT=sms +sms: + ${MAKE} build-target PORT=z80 PLAT=sms EXT=sms + + +gg-clean: + ${MAKE} clean-target EXT=gg +gg: + ${MAKE} build-target PORT=z80 PLAT=gg EXT=gg + +nes-clean: + ${MAKE} clean-target EXT=nes +nes: + ${MAKE} build-target PORT=mos6502 PLAT=nes EXT=nes \ No newline at end of file diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/Readme.md b/gbdk-lib/examples/megaduck/laptop_keyboard/Readme.md new file mode 100644 index 00000000..93fbcc52 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/Readme.md @@ -0,0 +1,8 @@ +# GBDK example for the Mega Duck Laptop +How to interface with the special hardware on the Mega Duck Laptop models ("Super QuiQue" and "Super Junior Computer"). + +## Keyboard example +- Initializing the external controller connected over the serial link port +- Polling the keyboard for input and processing the returned keycodes +- Displays the typed keys on the screen along with a cursor movable using the arrow keys + diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/src/main.c b/gbdk-lib/examples/megaduck/laptop_keyboard/src/main.c new file mode 100644 index 00000000..8be9fd3c --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/src/main.c @@ -0,0 +1,145 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "megaduck_keyboard.h" + +uint8_t cursor_x, cursor_y; +bool keyboard_read_ok; + + +duck_keyboard_data_t keydata; + +// A dashed underscore cursor sprite +const uint8_t cursor_tile[16] = { + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b01010101, 0b01010101, + 0b10101010, 0b10101010, +}; + +#define SPR_CURSOR 0u + +static void update_edit_cursor(int8_t delta_x, int8_t delta_y); +static void use_key_data(char key); +static void gfx_init(void); + + +// Moves sprite based cursor and changes console.h current text position +static void update_edit_cursor(int8_t delta_x, int8_t delta_y) { + gotoxy(posx() + delta_x, posy() + delta_y); + move_sprite(SPR_CURSOR, (posx() * 8) + DEVICE_SPRITE_PX_OFFSET_X, (posy() * 8) + DEVICE_SPRITE_PX_OFFSET_Y); +} + + +// Example of typing and moving a cursor around the screen with arrow keys +static void use_key_data(char key) { + + switch (key) { + + case NO_KEY: break; + + case KEY_ARROW_UP: update_edit_cursor( 0, -1); break; + case KEY_ARROW_DOWN: update_edit_cursor( 0, 1); break; + case KEY_ARROW_LEFT: update_edit_cursor(-1, 0); break; + case KEY_ARROW_RIGHT: update_edit_cursor( 1, 0); break; + + // Clears the screen + case KEY_HELP: + cls(); + update_edit_cursor(1, 0); + break; + + case KEY_ENTER: + gotoxy(0, posy() + 1); + update_edit_cursor(1, 0); + break; + + case KEY_BACKSPACE: + gotoxy(posx() - 1, posy()); + putchar(' '); + update_edit_cursor(-1, 0); + break; + + // All other keys + default: + putchar(key); + update_edit_cursor(0, 0); + break; + } +} + + +static void gfx_init(void) { + + // Set up sprite cursor + set_sprite_data(0,1,cursor_tile); + set_sprite_tile(SPR_CURSOR,0); + hide_sprite(SPR_CURSOR); + + SPRITES_8x8; + SHOW_SPRITES; + SHOW_BKG; +} + + +void main(void) { + + uint8_t megaduck_model = duck_check_model(); // This must be called before any vram tiles are loaded or cleared + bool megaduck_laptop_init_ok = duck_io_laptop_init(); + + gfx_init(); + printf("Initializing..\n"); + + if (!megaduck_laptop_init_ok) { + // If laptop hardware is not present then there isn't anything + // for this program to do, so just idle in a loop + printf("Laptop not detected\n" + "or Failed to Initialize"); + while(1) { + vsync(); + } + } + + + // Otherwise laptop init succeeded + printf("Laptop Detected! >%hu<\n", megaduck_model); + + // This may not work in emulators which don't simulate + // the preloaded Laptop System ROM font tiles in VRAM + if (megaduck_model == MEGADUCK_LAPTOP_SPANISH) + printf("Spanish model\n"); + else if (megaduck_model == MEGADUCK_LAPTOP_GERMAN) + printf("German model\n"); + + // Put the editing cursor in a starting location + update_edit_cursor(1,1); + + + while(1) { + vsync(); + + // Laptop serial command intervals below 20 msec may cause laptop hardware lockup + // Poll for keyboard every other frame (~32 msec) + if (sys_time & 0x01u) { + + if (duck_io_poll_keyboard(&keydata)) { + // Convert from key scancodes to ascii and apply key repeat + char current_key = duck_io_process_key_data(&keydata, megaduck_model); + + use_key_data(current_key); + } + } + } +} diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_key2ascii.c b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_key2ascii.c new file mode 100644 index 00000000..be8515fc --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_key2ascii.c @@ -0,0 +1,329 @@ +#include +#include + +#include +#include + +#include "megaduck_key2ascii.h" +#include "megaduck_keyboard.h" + + +// TODO: Not a very efficient use of space, lots of null entries +const char scancode_to_ascii_LUT_spanish[] = { + + // == Start of shift adjusted scan code range (0x00 through 0x7F) == + // + // Caps lock is handled separate by manually adjusting a-z + + NO_KEY, // 0x80 + NO_KEY, // 0x81 + NO_KEY, // 0x82 + NO_KEY, // 0x83 + NO_KEY, // 0x84 + '!', // 0x85 Shift alt: ! + 'Q', // 0x86 Shift alt: Q + 'A', // 0x87 Shift alt: A + NO_KEY, // 0x88 + '"', // 0x89 Shift alt: " + 'W', // 0x8A Shift alt: W + 'S', // 0x8B Shift alt: S + NO_KEY, // 0x8C + '·', // 0x8D Shift alt: · (Spanish, mid-dot) | § (German, legal section) + 'E', // 0x8E Shift alt: E + 'D', // 0x8F Shift alt: D + + NO_KEY, // 0x90 + '$', // 0x91 Shift alt: $ + 'R', // 0x92 + 'F', // 0x93 + NO_KEY, // 0x94 + '%', // 0x95 Shift alt: % + 'T', // 0x96 + 'G', // 0x97 + NO_KEY, // 0x98 + '&', // 0x99 Shift alt: & + 'Y', // 0x9A + 'H', // 0x9B + NO_KEY, // 0x9C + '/', // 0x9D Shift alt: / + 'U', // 0x9E + 'J', // 0x9F + + NO_KEY, // 0xA0 + '(', // 0xA1 Shift alt: ( + 'I', // 0xA2 + 'K', // 0xA3 + NO_KEY, // 0xA4 + ')', // 0xA5 Shift alt: ) + 'O', // 0xA6 + 'L', // 0xA7 + NO_KEY, // 0xA8 + '\\', // 0xA9 Shift alt: "\" + 'P', // 0xAA + 'Ñ', // 0xAB + NO_KEY, // 0xAC + '?', // 0xAD Shift alt: ? + '[', // 0xAE Shift alt: [ (Spanish, only shift mode works) | German version: Ü + 'Ü', // 0xAF + + NO_KEY, // 0xB0 + '¿', // 0xB1 Shift alt: ¿ (Spanish) | ` (German) ; German version: ' (single quote?) + '*', // 0xB2 Shift alt: * | German version: · (mid-dot) + 'ª', // 0xB3 Shift alt: Feminine Ordinal [A over line] (Spanish) | ^ (German) + NO_KEY, // 0xB4 + NO_KEY, // 0xB5 + NO_KEY, // 0xB6 + NO_KEY, // 0xB7 + 'Z', // 0xB8 // German version : 'Y' + NO_KEY, // 0xB9 + NO_KEY, // 0xBA + NO_KEY, // 0xBB + 'X', // 0xBC + '>', // 0xBD Shift alt: > + NO_KEY, // 0xBE + NO_KEY, // 0xBF + + 'C', // 0xC0 + NO_KEY, // 0xC1 + NO_KEY, // 0xC2 + NO_KEY, // 0xC3 + 'V', // 0xC4 + NO_KEY, // 0xC5 + NO_KEY, // 0xC6 + NO_KEY, // 0xC7 + 'B', // 0xC8 + NO_KEY, // 0xC9 + NO_KEY, // 0xCA + NO_KEY, // 0xCB + 'N', // 0xCC + NO_KEY, // 0xCD + NO_KEY, // 0xCE + NO_KEY, // 0xCF + + 'M', // 0xD0 + NO_KEY, // 0xD1 + NO_KEY, // 0xD2 + NO_KEY, // 0xD3 + ';', // 0xD4 ; Shift alt: ; + NO_KEY, // 0xD5 + NO_KEY, // 0xD6 + NO_KEY, // 0xD7 + ':', // 0xD8 ; Shift alt: : + NO_KEY, // 0xD9 + NO_KEY, // 0xDA + NO_KEY, // 0xDB + '_', // 0xDC ; Shift alt: _ | German version: @ + NO_KEY, // 0xDD + NO_KEY, // 0xDE + NO_KEY, // 0xDF + + NO_KEY, // 0xE0 + NO_KEY, // 0xE1 + NO_KEY, // 0xE2 + NO_KEY, // 0xE3 + NO_KEY, // 0xE4 + NO_KEY, // 0xE5 + NO_KEY, // 0xE6 + NO_KEY, // 0xE7 + NO_KEY, // 0xE8 + NO_KEY, // 0xE9 + NO_KEY, // 0xEA + NO_KEY, // 0xEB + NO_KEY, // 0xEC + NO_KEY, // 0xED + NO_KEY, // 0xEE + NO_KEY, // 0xEF + + NO_KEY, // 0xF0 + NO_KEY, // 0xF1 + NO_KEY, // 0xF2 + NO_KEY, // 0xF3 + NO_KEY, // 0xF4 + NO_KEY, // 0xF5 + NO_KEY, // 0xF6 + NO_KEY, // 0xF7 + NO_KEY, // 0xF8 + NO_KEY, // 0xF9 + NO_KEY, // 0xFA + NO_KEY, // 0xFB + NO_KEY, // 0xFC + NO_KEY, // 0xFD + NO_KEY, // 0xFE + NO_KEY, // 0xFF + + + + // == Start of actual scan code range (0x80 through 0xFF) == + + NO_KEY, // 0x80 SYS_KBD_CODE_F1 + KEY_ESCAPE, // 0x81 SYS_KBD_CODE_ESCAPE + KEY_HELP, // 0x82 SYS_KBD_CODE_HELP + NO_KEY, // 0x83 + NO_KEY, // 0x84 SYS_KBD_CODE_F2 + '1', // 0x85 SYS_KBD_CODE_1 + 'q', // 0x86 SYS_KBD_CODE_Q + 'a', // 0x87 SYS_KBD_CODE_A + NO_KEY, // 0x88 SYS_KBD_CODE_F3 + '2', // 0x89 SYS_KBD_CODE_2 + 'w', // 0x8A SYS_KBD_CODE_W + 's', // 0x8B SYS_KBD_CODE_S + NO_KEY, // 0x8C SYS_KBD_CODE_F4 + '3', // 0x8D SYS_KBD_CODE_3 + 'e', // 0x8E SYS_KBD_CODE_E + 'd', // 0x8F SYS_KBD_CODE_D + + NO_KEY, // 0x90 SYS_KBD_CODE_F5 + '4', // 0x91 SYS_KBD_CODE_4 + 'r', // 0x92 SYS_KBD_CODE_R + 'f', // 0x93 SYS_KBD_CODE_F + NO_KEY, // 0x94 SYS_KBD_CODE_F6 + '5', // 0x95 SYS_KBD_CODE_5 + 't', // 0x96 SYS_KBD_CODE_T + 'g', // 0x97 SYS_KBD_CODE_G + NO_KEY, // 0x98 SYS_KBD_CODE_F7 + '6', // 0x99 SYS_KBD_CODE_6 + 'y', // 0x9A SYS_KBD_CODE_Y + 'h', // 0x9B SYS_KBD_CODE_H + NO_KEY, // 0x9C SYS_KBD_CODE_F8 + '7', // 0x9D SYS_KBD_CODE_7 + 'u', // 0x9E SYS_KBD_CODE_U + 'j', // 0x9F SYS_KBD_CODE_J + + NO_KEY, // 0xA0 SYS_KBD_CODE_F9 + '8', // 0xA1 SYS_KBD_CODE_8 + 'i', // 0xA2 SYS_KBD_CODE_I + 'k', // 0xA3 SYS_KBD_CODE_K + NO_KEY, // 0xA4 SYS_KBD_CODE_F10 + '9', // 0xA5 SYS_KBD_CODE_9 + 'o', // 0xA6 SYS_KBD_CODE_O + 'l', // 0xA7 SYS_KBD_CODE_L + NO_KEY, // 0xA8 SYS_KBD_CODE_F11 + '0', // 0xA9 SYS_KBD_CODE_0 + 'p', // 0xAA SYS_KBD_CODE_P + 'ñ', // 0xAB SYS_KBD_CODE_N_TILDE + NO_KEY, // 0xAC SYS_KBD_CODE_F12 + '\'', // 0xAD SYS_KBD_CODE_SINGLE_QUOTE + '`', // 0xAE SYS_KBD_CODE_BACKTICK + 'ü', // 0xAF SYS_KBD_CODE_U_UMLAUT + + NO_KEY, // 0xB0 + '¡', // 0xB1 SYS_KBD_CODE_EXCLAMATION_FLIPPED + ']', // 0xB2 SYS_KBD_CODE_RIGHT_SQ_BRACKET + 'º', // 0xB3 SYS_KBD_CODE_O_OVER_LINE Masculine Ordinal + NO_KEY, // 0xB4 + KEY_BACKSPACE, // 0xB5 SYS_KBD_CODE_BACKSPACE + KEY_ENTER, // 0xB6 SYS_KBD_CODE_ENTER + NO_KEY, // 0xB7 + 'z', // 0xB8 SYS_KBD_CODE_Z // German version : 'y' + ' ', // 0xB9 SYS_KBD_CODE_SPACE + NO_KEY, // 0xBA SYS_KBD_CODE_PIANO_DO_SHARP + NO_KEY, // 0xBB SYS_KBD_CODE_PIANO_DO + 'x', // 0xBC SYS_KBD_CODE_X + '<', // 0xBD SYS_KBD_CODE_LESS_THAN + NO_KEY, // 0xBE SYS_KBD_CODE_PIANO_RE_SHARP + NO_KEY, // 0xBF SYS_KBD_CODE_PIANO_RE + + 'c', // 0xC0 SYS_KBD_CODE_C + NO_KEY, // 0xC1 SYS_KBD_CODE_PAGE_UP + NO_KEY, // 0xC2 + NO_KEY, // 0xC3 SYS_KBD_CODE_PIANO_MI + 'v', // 0xC4 SYS_KBD_CODE_V + NO_KEY, // 0xC5 SYS_KBD_CODE_PAGE_DOWN + NO_KEY, // 0xC6 SYS_KBD_CODE_PIANO_FA_SHARP + NO_KEY, // 0xC7 SYS_KBD_CODE_PIANO_FA + 'b', // 0xC8 SYS_KBD_CODE_B + NO_KEY, // 0xC9 SYS_KBD_CODE_MEMORY_MINUS + NO_KEY, // 0xCA SYS_KBD_CODE_PIANO_SOL_SHARP + NO_KEY, // 0xCB SYS_KBD_CODE_PIANO_SOL + 'n', // 0xCC SYS_KBD_CODE_N + NO_KEY, // 0xCD SYS_KBD_CODE_MEMORY_PLUS + NO_KEY, // 0xCE SYS_KBD_CODE_PIANO_LA_SHARP + NO_KEY, // 0xCF SYS_KBD_CODE_PIANO_LA + + 'm', // 0xD0 SYS_KBD_CODE_M + NO_KEY, // 0xD1 SYS_KBD_CODE_MEMORY_RECALL + NO_KEY, // 0xD2 + NO_KEY, // 0xD3 SYS_KBD_CODE_PIANO_SI + ',', // 0xD4 SYS_KBD_CODE_COMMA + NO_KEY, // 0xD5 SYS_KBD_CODE_SQUAREROOT + NO_KEY, // 0xD6 SYS_KBD_CODE_PIANO_DO_2_SHARP + NO_KEY, // 0xD7 SYS_KBD_CODE_PIANO_DO_2 + '.', // 0xD8 SYS_KBD_CODE_PERIOD + '*', // 0xD9 SYS_KBD_CODE_MULTIPLY + NO_KEY, // 0xDA SYS_KBD_CODE_PIANO_RE_2_SHARP + NO_KEY, // 0xDB SYS_KBD_CODE_PIANO_RE_2 + '-', // 0xDC SYS_KBD_CODE_DASH + KEY_ARROW_DOWN, // 0xDD SYS_KBD_CODE_ARROW_DOWN // TODO: Temporary + NO_KEY, // 0xDE SYS_KBD_CODE_PRINTSCREEN_RIGHT + NO_KEY, // 0xDF + + KEY_DELETE, // 0xE0 SYS_KBD_CODE_DELETE + '-', // 0xE1 SYS_KBD_CODE_MINUS + NO_KEY, // 0xE2 SYS_KBD_CODE_PIANO_FA_2_SHARP + NO_KEY, // 0xE3 SYS_KBD_CODE_PIANO_FA_2 + '÷', // 0xE4 SYS_KBD_CODE_DIVIDE + KEY_ARROW_LEFT, // 0xE5 SYS_KBD_CODE_ARROW_LEFT // TODO: Temporary + NO_KEY, // 0xE6 SYS_KBD_CODE_PIANO_SOL_2_SHARP + NO_KEY, // 0xE7 SYS_KBD_CODE_PIANO_SOL_2 + KEY_ARROW_UP, // 0xE8 SYS_KBD_CODE_ARROW_UP // TODO: Temporary + '=', // 0xE9 SYS_KBD_CODE_EQUALS + NO_KEY, // 0xEA SYS_KBD_CODE_PIANO_LA_2_SHARP + NO_KEY, // 0xEB SYS_KBD_CODE_PIANO_LA_2 + '+', // 0xEC SYS_KBD_CODE_PLUS + KEY_ARROW_RIGHT, // 0xED SYS_KBD_CODE_ARROW_RIGHT // TODO: Temporary + NO_KEY, // 0xEE + NO_KEY, // 0xEF SYS_KBD_CODE_PIANO_MI_2 + + 't', // 0xF0 SYS_KBD_CODE_MAYBE_SYST_CODES_START + NO_KEY, // 0xF1 + NO_KEY, // 0xF2 + NO_KEY, // 0xF3 + NO_KEY, // 0xF4 + NO_KEY, // 0xF5 + NO_KEY, // 0xF6 SYS_KBD_CODE_MAYBE_RX_NOT_A_KEY + NO_KEY, // 0xF7 + NO_KEY, // 0xF8 + NO_KEY, // 0xF9 + NO_KEY, // 0xFA + NO_KEY, // 0xFB + NO_KEY, // 0xFC + NO_KEY, // 0xFD + NO_KEY, // 0xFE + NO_KEY // 0xFF +}; + + +char duck_io_scancode_to_ascii(const uint8_t key_code, const uint8_t megaduck_model) { + + char ascii_char = scancode_to_ascii_LUT_spanish[key_code]; + + // Handle alternate German keyboard layout + if (megaduck_model == MEGADUCK_LAPTOP_GERMAN) + switch (ascii_char) { + // Row 1 + // case '·': ascii_char = '§'; break; // TODO: handling for these + case '\'': ascii_char = 'ß'; break; + // case '¿': ascii_char = '`'; break; // TODO: handling for these + // case '¡': ascii_char = '\''; break; // TODO: handling for these + case '÷': ascii_char = ':'; break; + // Row 2 + case '[': // maps to same char below + case '`': ascii_char = 'Ü'; break; + case ']': ascii_char = '·'; break; + case 'y': ascii_char = 'z'; break; + case 'Y': ascii_char = 'Z'; break; + // Row 3 + // case 'ñ': ascii_char = 'ö'; break; // TODO: handling for these + // case 'Ñ': ascii_char = 'Ö'; break; // TODO: handling for these + // case 'ü': ascii_char = 'ä'; break; // TODO: handling for these + // case 'Ü': ascii_char = 'Ä'; break; // TODO: handling for these + // case 'ª': ascii_char = '^'; break; // TODO: handling for these + // case 'º': ascii_char = '#'; break; // TODO: handling for these + // Row 4 + case 'z': ascii_char = 'y'; break; + case 'Z': ascii_char = 'Y'; break; + case '-': ascii_char = '@'; break; + } + + return ascii_char; +} diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_key2ascii.h b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_key2ascii.h new file mode 100644 index 00000000..4ec47583 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_key2ascii.h @@ -0,0 +1,9 @@ +#include +#include + +#ifndef _MEGADUCK_KEY2ASCII_H +#define _MEGADUCK_KEY2ASCII_H + +char duck_io_scancode_to_ascii(const uint8_t key_code, const uint8_t megaduck_model); + +#endif // _MEGADUCK_KEY2ASCII_H diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_keyboard.c b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_keyboard.c new file mode 100644 index 00000000..5be9d282 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_keyboard.c @@ -0,0 +1,111 @@ +#include +#include +#include + +#include +#include + +#include "megaduck_key2ascii.h" +#include "megaduck_keyboard.h" + + +#define REPEAT_OFF 0u +#define REPEAT_FIRST_THRESHOLD 8u +#define REPEAT_CONTINUE_THRESHOLD 4u + +bool key_repeat_allowed = false; +uint8_t key_repeat_timeout = REPEAT_OFF; +uint8_t key_pressed = NO_KEY; +uint8_t key_previous = NO_KEY; + + +// RX Bytes for Keyboard Serial Reply Packet +// - 1st: +// - Always 0x04 (Length) +// - 2nd: +// - KEY REPEAT : |= 0x01 (so far looks like with no key value set in 3rd Byte) +// - CAPS_LOCK: |= 0x02 +// - SHIFT: |= 0x04 +// - LEFT_PRINTSCREEN: |= 0x08 +// - 3rd: +// - Carries the keyboard key scan code +// - 0x00 when no key pressed +// - 4th: +// - Two's complement checksum byte +// - It should be: #4 == (((#1 + #2 + #3) XOR 0xFF) + 1) [two's complement] +// - I.E: (#4 + #1 + #2 + #3) == 0x100 -> unsigned overflow -> 0x00 + + +// Request Keyboard data and handle the response +// +// Returns success or failure (Keyboard struct data not updated if polling failed) +bool duck_io_poll_keyboard(duck_keyboard_data_t * key_data) { + + if (duck_io_send_cmd_and_receive_buffer(DUCK_IO_CMD_GET_KEYS)) { + if (duck_io_rx_buf_len == DUCK_IO_LEN_KBD_GET) { + key_data->flags = duck_io_rx_buf[DUCK_IO_KBD_FLAGS]; + key_data->scancode = duck_io_rx_buf[DUCK_IO_KBD_KEYCODE]; + return true; + } + } + return false; +} + + +// Translates key scancodes to ascii +// Handles Shift/Caps Lock and Repeat flags +// +// Returns translated key (if no key pressed, invalid, etc value will be NO_KEY) +char duck_io_process_key_data(duck_keyboard_data_t * key_data, uint8_t megaduck_model) { + + // Optional program layer of key repeat on top of + // hardware key repeat (which is too fast, mostly) + // + // The hardware repeat works like this: + // - First packet after key press: Repeat Flag *NOT* set, Scan code matches key + // - N+1 packets after key press: Repeat Flag *IS* set, Scan code set to 0x00 (None) remains this way until key released + if ((key_data->flags & DUCK_IO_KEY_FLAG_KEY_REPEAT) && (key_repeat_allowed)) { + + // Default to no key repeat + key_pressed = NO_KEY; + + if (key_repeat_timeout) { + key_repeat_timeout--; + } else { + // If there is a repeat then send the previous pressed key + // and set a small delay until next repeat + key_pressed = key_previous; + key_repeat_timeout = REPEAT_CONTINUE_THRESHOLD; + } + } + else { + key_data->flags = key_data->flags; + uint8_t temp_key_scancode = key_data->scancode; + + // If only shift is enabled, use keycode translation for shift alternate keys (-= 0x80u) + if ((key_data->flags & (DUCK_IO_KEY_FLAG_CAPSLOCK | DUCK_IO_KEY_FLAG_SHIFT)) == DUCK_IO_KEY_FLAG_SHIFT) + temp_key_scancode -= DUCK_IO_KEY_BASE; + + key_pressed = duck_io_scancode_to_ascii(temp_key_scancode, megaduck_model); + + // If only caps lock is enabled, just translate a-z -> A-Z with no other shift alternates + if ((key_data->flags & (DUCK_IO_KEY_FLAG_CAPSLOCK | DUCK_IO_KEY_FLAG_SHIFT)) == DUCK_IO_KEY_FLAG_CAPSLOCK) + if ((key_pressed >= 'a') && (key_pressed <= 'z')) + key_pressed -= ('a' - 'A'); + + // Only allow repeat for the range from: + // - ASCII 32 (space) and higher + // - As well as arrow keys + if ((key_pressed >= ' ') || + ((key_pressed >= KEY_ARROW_UP) && (key_pressed <= KEY_ARROW_LEFT))) { + key_repeat_allowed = true; + key_repeat_timeout = REPEAT_FIRST_THRESHOLD; + } else + key_repeat_allowed = false; + + // Save key for repeat + key_previous = key_pressed; + } + + return key_pressed; +} diff --git a/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_keyboard.h b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_keyboard.h new file mode 100644 index 00000000..58b3b65a --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_keyboard/src/megaduck_keyboard.h @@ -0,0 +1,44 @@ +#include +#include +#include + +#ifndef _MEGADUCK_KEYBOARD_H +#define _MEGADUCK_KEYBOARD_H + + +#define NO_KEY 0u + +// Arrow keys are arbitrarily chosen to not clash with common +// ascii chars, though they do match the GBDK ibm_fixed font +#define KEY_ARROW_UP 1u +#define KEY_ARROW_DOWN 2u +#define KEY_ARROW_RIGHT 3u +#define KEY_ARROW_LEFT 4u +#define KEY_HELP 5u + + +// Use ascii values for these keys +#define KEY_BACKSPACE 8u +#define KEY_ENTER 13u +#define KEY_ESCAPE 27u +#define KEY_DELETE 127u + + +// RTC data +typedef struct duck_keyboard_data_t { + uint8_t flags; + uint8_t scancode; +} duck_keyboard_data_t; + + +// Post-Processed key data +extern char megaduck_key_pressed; +extern char megaduck_key_previous; +extern uint8_t megaduck_key_flags; + + +bool duck_io_poll_keyboard(duck_keyboard_data_t * key_data); +char duck_io_process_key_data(duck_keyboard_data_t * key_data, uint8_t megaduck_model); + + +#endif // _MEGADUCK_KEYBOARD_H diff --git a/gbdk-lib/examples/megaduck/laptop_rtc/Makefile b/gbdk-lib/examples/megaduck/laptop_rtc/Makefile new file mode 100644 index 00000000..b840565b --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_rtc/Makefile @@ -0,0 +1,94 @@ +# If you move this project you can change the directory +# to match your GBDK root directory (ex: GBDK_HOME = "C:/GBDK/" +ifndef GBDK_HOME + GBDK_HOME = ../../../ +endif + +LCC = $(GBDK_HOME)bin/lcc + +# Set platforms to build here, spaced separated. (These are in the separate Makefile.targets) +# They can also be built/cleaned individually: "make gg" and "make gg-clean" +# Possible are: gb gbc pocket megaduck sms gg +TARGETS= megaduck # gb pocket megaduck sms gg nes + +# Configure platform specific LCC flags here: +LCCFLAGS_gb = # -Wl-yt0x1B # Set an MBC for banking (1B-ROM+MBC5+RAM+BATT) +LCCFLAGS_pocket = # -Wl-yt0x1B # Usually the same as required for .gb +LCCFLAGS_duck = # -Wl-yt0x1B # Usually the same as required for .gb +LCCFLAGS_gbc = # -Wl-yt0x1B -Wm-yc # Same as .gb with: -Wm-yc (gb & gbc) or Wm-yC (gbc exclusive) +LCCFLAGS_sms = +LCCFLAGS_gg = +LCCFLAGS_nes = + +LCCFLAGS += $(LCCFLAGS_$(EXT)) # This adds the current platform specific LCC Flags + +LCCFLAGS += -Wl-j # -Wm-yS -Wm-yoA -Wm-ya4 -autobank -Wb-ext=.rel -Wb-v # MBC + Autobanking related flags +# LCCFLAGS += -debug # Uncomment to enable debug output +# LCCFLAGS += -v # Uncomment for lcc verbose output +LCCFLAGS += -Wf-MMD -Wf-Wp-MP # Header file dependency output (-MMD) for Makefile use + per-header Phony rules (-MP) +CFLAGS += -Wf-MMD -Wf-Wp-MP # Header file dependency output (-MMD) for Makefile use + per-header Phony rules (-MP) + +# You can set the name of the ROM file here +PROJECTNAME = megaduck_rtc + +# EXT?=gb # Only sets extension to default (game boy .gb) if not populated +SRCDIR = src +OBJDIR = obj/$(EXT) +RESDIR = res +BINDIR = build/$(EXT) +MKDIRS = $(OBJDIR) $(BINDIR) # See bottom of Makefile for directory auto-creation + +BINS = $(OBJDIR)/$(PROJECTNAME).$(EXT) +CSOURCES = $(foreach dir,$(SRCDIR),$(notdir $(wildcard $(dir)/*.c))) $(foreach dir,$(RESDIR),$(notdir $(wildcard $(dir)/*.c))) + +ASMSOURCES = $(foreach dir,$(SRCDIR),$(notdir $(wildcard $(dir)/*.s))) +OBJS = $(CSOURCES:%.c=$(OBJDIR)/%.o) $(ASMSOURCES:%.s=$(OBJDIR)/%.o) + +# Dependencies (using output from -Wf-MMD -Wf-Wp-MP) +DEPS = $(OBJS:%.o=%.d) + +-include $(DEPS) + +# Builds all targets sequentially +all: $(TARGETS) + +test: + echo $(CSOURCES) + + +# Compile .c files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.c + $(LCC) $(CFLAGS) -c -o $@ $< + +# Compile .c files in "res/" to .o object files +$(OBJDIR)/%.o: $(RESDIR)/%.c + $(LCC) $(CFLAGS) -c -o $@ $< + +# Compile .s assembly files in "src/" to .o object files +$(OBJDIR)/%.o: $(SRCDIR)/%.s + $(LCC) $(CFLAGS) -c -o $@ $< + +# If needed, compile .c files in "src/" to .s assembly files +# (not required if .c is compiled directly to .o) +$(OBJDIR)/%.s: $(SRCDIR)/%.c + $(LCC) $(CFLAGS) -S -o $@ $< + +# Link the compiled object files into a .gb ROM file +$(BINS): $(OBJS) + $(LCC) $(LCCFLAGS) $(CFLAGS) -o $(BINDIR)/$(PROJECTNAME).$(EXT) $(OBJS) + +clean: + @echo Cleaning + @for target in $(TARGETS); do \ + $(MAKE) $$target-clean; \ + done + +# Include available build targets +include Makefile.targets + + +# create necessary directories after Makefile is parsed but before build +# info prevents the command from being pasted into the makefile +ifneq ($(strip $(EXT)),) # Only make the directories if EXT has been set by a target +$(info $(shell mkdir -p $(MKDIRS))) +endif diff --git a/gbdk-lib/examples/megaduck/laptop_rtc/Makefile.targets b/gbdk-lib/examples/megaduck/laptop_rtc/Makefile.targets new file mode 100644 index 00000000..9c3dab9e --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_rtc/Makefile.targets @@ -0,0 +1,54 @@ + +# Platform specific flags for compiling (only populate if they're both present) +ifneq ($(strip $(PORT)),) +ifneq ($(strip $(PLAT)),) +CFLAGS += -m$(PORT):$(PLAT) +endif +endif + +# Called by the individual targets below to build a ROM +build-target: $(BINS) + +clean-target: + rm -rf $(OBJDIR) + rm -rf $(BINDIR) + +gb-clean: + ${MAKE} clean-target EXT=gb +gb: + ${MAKE} build-target PORT=sm83 PLAT=gb EXT=gb + + +gbc-clean: + ${MAKE} clean-target EXT=gbc +gbc: + ${MAKE} build-target PORT=sm83 PLAT=gb EXT=gbc + + +pocket-clean: + ${MAKE} clean-target EXT=pocket +pocket: + ${MAKE} build-target PORT=sm83 PLAT=ap EXT=pocket + + +megaduck-clean: + ${MAKE} clean-target EXT=duck +megaduck: + ${MAKE} build-target PORT=sm83 PLAT=duck EXT=duck + + +sms-clean: + ${MAKE} clean-target EXT=sms +sms: + ${MAKE} build-target PORT=z80 PLAT=sms EXT=sms + + +gg-clean: + ${MAKE} clean-target EXT=gg +gg: + ${MAKE} build-target PORT=z80 PLAT=gg EXT=gg + +nes-clean: + ${MAKE} clean-target EXT=nes +nes: + ${MAKE} build-target PORT=mos6502 PLAT=nes EXT=nes \ No newline at end of file diff --git a/gbdk-lib/examples/megaduck/laptop_rtc/Readme.md b/gbdk-lib/examples/megaduck/laptop_rtc/Readme.md new file mode 100644 index 00000000..766d81fc --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_rtc/Readme.md @@ -0,0 +1,8 @@ +# GBDK example for the Mega Duck Laptop +How to interface with the special hardware on the Mega Duck Laptop models ("Super QuiQue" and "Super Junior Computer"). + +## RTC example +- Initializing the external controller connected over the serial link port +- Polling the laptop RTC for date and time +- Setting a new date and time for the laptop RTC + diff --git a/gbdk-lib/examples/megaduck/laptop_rtc/src/main.c b/gbdk-lib/examples/megaduck/laptop_rtc/src/main.c new file mode 100644 index 00000000..f3641e53 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_rtc/src/main.c @@ -0,0 +1,119 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#include "megaduck_rtc.h" + +static void use_rtc_data(duck_rtc_data_t * p_rtc); +static void send_some_rtc_data(duck_rtc_data_t * p_rtc); + +duck_rtc_data_t rtc; + + +// Example of displaying RTC date info +static void use_rtc_data(duck_rtc_data_t * p_rtc) { + + const char * ampm_str[] = {"am", "pm"}; + const char * dow_str[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + + gotoxy(0,6); + + printf("Year: %d \n" + "Month: %d \n" + "Day: %d \n" + "DoW: %s \n" + "Time: %d:%d:%d %s \n", + (uint16_t)p_rtc->year, + (uint16_t)p_rtc->mon, + (uint16_t)p_rtc->day, + (uint16_t)dow_str[p_rtc->weekday], + + (uint16_t)p_rtc->hour, + (uint16_t)p_rtc->min, + (uint16_t)p_rtc->sec, + ampm_str[p_rtc->ampm] ); +} + + +// This date and time used is just for example, they could be different values +static void send_some_rtc_data(duck_rtc_data_t * p_rtc) { + + // For this example set the first power-on defaults + // to those used by the Spanish model laptop + p_rtc->year = 1993u; + p_rtc->mon = 6u; // June + p_rtc->day = 1u; // 1st + p_rtc->weekday = 2u; // Tuesday + + p_rtc->ampm = 0u; // AM + p_rtc->hour = 0u; + p_rtc->min = 0u; + p_rtc->sec = 0u; + + gotoxy(0,12); + + if (duck_io_set_rtc(p_rtc)) { + printf("Send RTC: Success"); + } + else { + printf("Send RTC: Failed!"); + } +} + + +void main(void) { + + uint8_t gamepad; + + bool megaduck_laptop_init_ok = duck_io_laptop_init(); + + SPRITES_8x8; + SHOW_SPRITES; + SHOW_BKG; + printf("Initializing..\n"); + + if (!megaduck_laptop_init_ok) { + // If laptop hardware is not present then there isn't anything + // for this program to do, so just idle in a loop + printf("Laptop not detected\n" + "or Failed to Initialize"); + while(1) { + vsync(); + } + } + + // Otherwise laptop init succeeded + printf("Laptop Detected!\n"); + + printf("\n*SELECT to Send RTC\n Sys rom defaults"); + + while(1) { + vsync(); + gamepad = joypad(); + + // Laptop serial command intervals below 20 msec may cause laptop hardware lockup + // Poll for RTC every other frame (~32 msec) + if (sys_time & 0x01u) { + + // Send RTC data to device if SELECT is pressed + // otherwise Read RTC data + if (gamepad & J_SELECT) { + send_some_rtc_data(&rtc); + + // Wait until SELECT is released before continuing + waitpadup(); + } + else { + if (duck_io_get_rtc(&rtc)) { + use_rtc_data(&rtc); + } + } + } + } +} diff --git a/gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.c b/gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.c new file mode 100644 index 00000000..64913742 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.c @@ -0,0 +1,109 @@ +#include +#include +#include + +#include + +#include "megaduck_rtc.h" + +// These BCD conversions are for simplicity, in +// actual programs for performance it may be +// optimal to use the Game Boy / GBDK BCD features. +static uint8_t bcd_to_u8(uint8_t i) +{ + return (i & 0xFu) + ((i >> 4) * 10u); +} + +static uint8_t u8_to_bcd(uint8_t i) +{ + return (i % 10u) + ((i / 10u) << 4); +} + +// Get RTC command reply (Peripheral -> Duck) +// All values are in BCD format +// Ex: Month = December = 12th month = 0x12 (NOT 0x0C) +// [0]: Length of reply (always 4) +// [1..8] RTC Data +// [9]: checksum +// +// RTC Data: +// if (tm.tm_year > (2000 - 1900)) +// [1] = int_to_bcd(tm.tm_year - (2000 - 1900)); +// else +// [1] = int_to_bcd(tm.tm_year); // Years in BCD since 1900 (tm_year is already in since 1900 format) +// [2] = int_to_bcd(tm.tm_mon + 1); +// [3] = int_to_bcd(tm.tm_mday); +// [4] = int_to_bcd(tm.tm_wday); // DOW +// +// [5] = int_to_bcd( (tm.tm_hour < 12) ? 0 : 1 ); // AMPM +// [6] = int_to_bcd(tm.tm_hour % 12); +// [7] = int_to_bcd(tm.tm_min); +// [8] = int_to_bcd(tm.tm_sec); + + +// Send RTC data to the Laptop Hardware and handle the response +// +// Returns success or failure +bool duck_io_set_rtc(duck_rtc_data_t * p_rtc) { + + uint8_t year_to_send; + // Calculate as number of years since either 1900 or 2000 + // Strip year off, handle + if (p_rtc->year < 2000u) year_to_send = p_rtc->year - 1900u; + else year_to_send = p_rtc->year - 2000u; + + duck_io_tx_buf[0] = u8_to_bcd(year_to_send); + duck_io_tx_buf[1] = u8_to_bcd(p_rtc->mon); + duck_io_tx_buf[2] = u8_to_bcd(p_rtc->day); + duck_io_tx_buf[3] = u8_to_bcd(p_rtc->weekday); + + duck_io_tx_buf[4] = p_rtc->ampm; + duck_io_tx_buf[5] = p_rtc->hour; + duck_io_tx_buf[6] = p_rtc->min; + duck_io_tx_buf[7] = p_rtc->sec; + + duck_io_tx_buf_len = DUCK_IO_LEN_RTC_SET; + + if (duck_io_send_cmd_and_buffer(DUCK_IO_CMD_SET_RTC)) { + return true; + } else { + return false; + } +} + + +// Request RTC data and handle the response +// +// Returns success or failure (RTC struct data not updated if polling failed) +// Raw RTC data is converted into BCD format +bool duck_io_get_rtc(duck_rtc_data_t * p_rtc) { + + if (duck_io_send_cmd_and_receive_buffer(DUCK_IO_CMD_GET_RTC)) { + if (duck_io_rx_buf_len == DUCK_IO_LEN_RTC_GET) { + + // Translate raw RTC data in BCD format to decimal + + // For Year the 1992 wraparound is optional, but it's how + // the Super Junior SameDuck emulation does it in order + // to remain compatible with the MegaDuck Laptop System ROM + // which defaults to 1993 on startup (so BCD 93 for year) + // and supports years as early as 1992 (BCD 92) within an + // 8 bit bcd number (max being 99 years) + p_rtc->year = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_YEAR]); + if (p_rtc->year >= 92) p_rtc->year += 1900u; + else p_rtc->year += 2000u; + + p_rtc->mon = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_MON]); + p_rtc->day = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_DAY]); + p_rtc->mon = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_MON]); + p_rtc->weekday = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_WEEKDAY]); + + p_rtc->ampm = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_AMPM]); + p_rtc->hour = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_HOUR]); + p_rtc->min = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_MIN]); + p_rtc->sec = bcd_to_u8(duck_io_rx_buf[DUCK_IO_RTC_SEC]); + return true; + } + } + return false; +} diff --git a/gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.h b/gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.h new file mode 100644 index 00000000..5966bd88 --- /dev/null +++ b/gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.h @@ -0,0 +1,26 @@ +#include +#include +#include + +#ifndef _MEGADUCK_RTC_H +#define _MEGADUCK_RTC_H + + +// RTC data +typedef struct duck_rtc_data_t { + uint16_t year; + uint8_t mon; + uint8_t day; + uint8_t weekday; + + uint8_t ampm; + uint8_t hour; + uint8_t min; + uint8_t sec; +} duck_rtc_data_t; + + +bool duck_io_set_rtc(duck_rtc_data_t * p_rtc); +bool duck_io_get_rtc(duck_rtc_data_t * p_rtc); + +#endif // _MEGADUCK_RTC_H diff --git a/gbdk-lib/include/duck/laptop_io.h b/gbdk-lib/include/duck/laptop_io.h new file mode 100644 index 00000000..54193334 --- /dev/null +++ b/gbdk-lib/include/duck/laptop_io.h @@ -0,0 +1,201 @@ +#include +#include + +/** @file duck/laptop_io.h + + @anchor megaduck_laptop_io_docs + # MegaDuck Laptop Peripheral IO support + + The MegaDuck Laptop models (Spanish and German) have + several built-in hardware peripherals which are attached + via a controller that is communicated with using the + serial link port. + + @note Using the duck_io_* functions referenced from this + header file will cause the duck_io_ serial interrupt + handler to be automatically installed. + + To use any functions here, @ref duck_io_laptop_init() + must be called first (just once). + + For the present time all of the serial operations are + blocking, they do not return until completed. +*/ + +#ifndef _MEGADUCK_LAPTOP_IO_H +#define _MEGADUCK_LAPTOP_IO_H + + +// Commands sent via serial IO to the Duck laptop peripheral hardware +#define DUCK_IO_CMD_INIT_START 0x00u /**< Command to request starting the hardware init counter sequence process */ +#define DUCK_IO_CMD_GET_KEYS 0x00u /**< Command to get hardware keyboard data by receiving a multi-byte packet*/ +#define DUCK_IO_CMD_DONE_OR_OK 0x01u +// #define DUCK_IO_CMD_DONE_OR_OK_AND_SOMETHING 0x81u +#define DUCK_IO_CMD_ABORT_OR_FAIL 0x04u +#define DUCK_IO_CMD_RUN_CART_IN_SLOT 0x08u +#define DUCK_IO_CMD_INIT_UNKNOWN_0x09 0x09u /**< May also be PrintScreen related */ +#define DUCK_IO_CMD_SET_RTC 0x0Bu /**< Command to set hardware RTC by sending a multi-byte packet */ +#define DUCK_IO_CMD_GET_RTC 0x0Cu /**< Command to get hardware RTC by receiving a a multi-byte packet */ + + +// #define FF60_REG_BEFORE_XFER 0x00u +#define DUCK_IO_REPLY_BOOT_UNSET 0x00u +#define DUCK_IO_REPLY_BOOT_FAIL 0x01u +#define DUCK_IO_REPLY_BUFFER_XFER_OK 0x01u +#define DUCK_IO_REPLY_SEND_BUFFER_OK 0x03u +// #define DUCK_IO_REPLY_READ_FAIL_MAYBE 0x00u +#define DUCK_IO_REPLY_BOOT_OK 0x01u + +#define DUCK_IO_LEN_KBD_GET 2u /**< GET Keyboard key payload size: 2 bytes Payload (excludes 1 length header byte, 1 byte Checksum) */ +#define DUCK_IO_LEN_RTC_GET 8u /**< GET RTC payload size: 8 bytes Payload (excludes 1 length header byte, 1 byte Checksum) */ +#define DUCK_IO_LEN_RTC_SET 8u /**< SET RTC payload size: 8 bytes Payload (excludes 1 length header byte, 1 byte Checksum) */ + +// #define MEGADUCK_KBD_BYTE_1_EXPECT 0x0Eu +// #define MEGADUCK_SIO_BOOT_OK 0x01u + +#define DUCK_IO_LEN_RX_MAX 14u // 13 data bytes + 1 checksum byte max reply length? +#define DUCK_IO_LEN_TX_MAX 14u // 13 data bytes + 1 checksum byte max reply length? + +#define DUCK_IO_TIMEOUT_2_MSEC 2u // Used for hardware init counter sequence +#define DUCK_IO_TIMEOUT_100_MSEC 100u +#define DUCK_IO_TIMEOUT_200_MSEC 200u + + + +// RTC packet byte ordering (all in BCD format) +#define DUCK_IO_RTC_YEAR 0u +#define DUCK_IO_RTC_MON 1u +#define DUCK_IO_RTC_DAY 2u +#define DUCK_IO_RTC_WEEKDAY 3u +#define DUCK_IO_RTC_AMPM 4u +#define DUCK_IO_RTC_HOUR 5u +#define DUCK_IO_RTC_MIN 6u +#define DUCK_IO_RTC_SEC 7u + + +// Keyboard packet byte ordering (all in BCD format) +#define DUCK_IO_KBD_FLAGS 0u +#define DUCK_IO_KBD_KEYCODE 1u + + +// TODO: change these to user supplied buffers? +extern uint8_t duck_io_rx_buf[DUCK_IO_LEN_RX_MAX]; +extern uint8_t duck_io_rx_buf_len; + +extern uint8_t duck_io_tx_buf[DUCK_IO_LEN_TX_MAX]; +extern uint8_t duck_io_tx_buf_len; + + +// ===== Low level helper IO functions ===== + + +// TODO: No longer in use(?) +// +// Waits for a serial transfer to complete with a timeout +// +// @param timeout_len_ms Unit size is in msec (100 is about ~ 103 msec or 6.14 frames) +// +// Serial ISR populates status var if anything was received +// +// void duck_io_wait_done_with_timeout(uint8_t timeout_len_ms); + + +/** Sends a byte over serial to the MegaDuck laptop peripheral + + @param tx_byte Byte to send + +*/ +void duck_io_send_byte(uint8_t tx_byte); + + +/** Reads a byte over serial from the MegaDuck laptop peripheral with NO timeout + + Returns: the received byte + + If there is no reply then it will hang forever +*/ +uint8_t duck_io_read_byte_no_timeout(void); + + +/** Prepares to receive serial data from the MegaDuck laptop peripheral + + \li Sets serial IO to external clock and enables ready state. + \li Turns on Serial interrupt, clears any pending interrupts and + then turns interrupts on (state of @ref IE_REG should be + preserved before calling this and then restored at the end of + the serial communication being performed). +*/ +void duck_io_enable_read_byte(void); + + +/** Performs init sequence over serial with the MegaDuck laptop peripheral + + Returns `true` if successful, otherwise `false` + + Needs to be done *just once* any time system is powered + on or a cartridge is booted. + + Sends count up sequence + some commands, then waits for + a matching count down sequence in reverse. +*/ +bool duck_io_laptop_init(void); + + + +// ===== Higher level IO functions ===== + + +/** Waits to receive a byte over serial from the MegaDuck laptop peripheral with a timeout + + @param timeout_len_ms Unit size is in msec (100 is about ~ 103 msec or 6.14 frames) + + Returns: + \li `true`: Success, received byte will be in `duck_io_rx_byte` global + \li `false`: Read timed out with no reply +*/ +bool duck_io_read_byte_with_msecs_timeout(uint8_t timeout_len_ms); + + +/** Sends a byte over over serial to the MegaDuck laptop peripheral and waits for a reply with a timeout + + @param tx_byte Byte to send + @param timeout_len_ms Unit size is in msec (100 is about ~ 103 msec or 6.14 frames) + @param expected_reply The expected value of the reply byte + + Returns: + \li `true`: Success + \li `false`: if timed out or reply byte didn't match expected value +*/ +bool duck_io_send_byte_and_check_ack_msecs_timeout(uint8_t tx_byte, uint8_t timeout_len_ms, uint8_t expected_reply); + + +/** Sends a command and a multi-byte buffer over serial to the MegaDuck laptop peripheral + + @param io_cmd Command byte to send + + The data should be pre-loaded into these globals: + \li @ref duck_io_tx_buf : Buffer with data to send + \li @ref duck_io_tx_buf_len : Number of bytes to send + + Returns: `true` if succeeded + + @see DUCK_IO_CMD_GET_KEYS, DUCK_IO_CMD_SET_RTC +*/ +bool duck_io_send_cmd_and_buffer(uint8_t io_cmd); + + +/** Sends a command and then receives a multi-byte buffer over serial from the MegaDuck laptop peripheral + + @param io_cmd Command byte to send + + If successful, the received data and length will be in these globals: + \li @ref duck_io_rx_buf : Buffer with received data + \li @ref duck_io_rx_buf_len : Number of bytes received + + Returns: `true` if succeeded, `false` if failed (could be no reply, failed checksum, etc) + + @see DUCK_IO_CMD_GET_RTC +*/ +bool duck_io_send_cmd_and_receive_buffer(uint8_t io_cmd); + +#endif // _MEGADUCK_LAPTOP_IO_H diff --git a/gbdk-lib/include/duck/laptop_keycodes.h b/gbdk-lib/include/duck/laptop_keycodes.h new file mode 100644 index 00000000..1b572d1d --- /dev/null +++ b/gbdk-lib/include/duck/laptop_keycodes.h @@ -0,0 +1,210 @@ +#include +#include +#include + +#ifndef _MEGADUCK_LAPTOP_KEYCODES_H +#define _MEGADUCK_LAPTOP_KEYCODES_H + + +// - Left /right shift are shared +// +// Keyboard serial reply scan codes have different ordering than System ROM character codes +// - They go diagonal down from upper left for the first *4* rows +// - The bottom 4 rows (including piano keys) are more varied + +// Modifier Keys / Flags +// +// See input_key_modifier_flags__RAM_D027_ +#define DUCK_IO_KEY_FLAG_KEY_REPEAT 0x01u +#define DUCK_IO_KEY_FLAG_KEY_REPEAT_BIT 0x0u +#define DUCK_IO_KEY_FLAG_CAPSLOCK 0x02u +#define DUCK_IO_KEY_FLAG_CAPSLOCK_BIT 0x1u +#define DUCK_IO_KEY_FLAG_SHIFT 0x04u +#define DUCK_IO_KEY_FLAG_SHIFT_BIT 0x2u +// Right Print Screen has actual scancode vs Left being in a flag +#define DUCK_IO_KEY_FLAG_PRINTSCREEN_LEFT 0x08u +#define DUCK_IO_KEY_FLAG_PRINTSCREEN_LEFT_BIT 0x3u + +// Keyboard scancodes +// All valid keys seem to have bit 7 set (0x80+) +#define DUCK_IO_KEY_BASE_BIT 0x7u +#define DUCK_IO_KEY_BASE 0x80u + + +// First 4 rows (top of keyboard) ~ 0x80 - 0xB7 +// +// - For each row, most chars are +4 vs char to immediate left +// due to the diagonal down-right scancode ordering +// +// Starting values +// - Row 1: 0x80 +// - Row 2: 0x81 +// - Row 3: 0x82 +// - Row 4: 0x83 + +// Row 1 +#define DUCK_IO_KEY_F1 0x80u +#define DUCK_IO_KEY_F2 0x84u +#define DUCK_IO_KEY_F3 0x88u +#define DUCK_IO_KEY_F4 0x8Cu +#define DUCK_IO_KEY_F5 0x90u +#define DUCK_IO_KEY_F6 0x94u +#define DUCK_IO_KEY_F7 0x98u +#define DUCK_IO_KEY_F8 0x9Cu +#define DUCK_IO_KEY_F9 0xA0u +#define DUCK_IO_KEY_F10 0xA4u +#define DUCK_IO_KEY_F11 0xA8u +#define DUCK_IO_KEY_F12 0xACu +// GAP at 0xB0 maybe Blank spot where F13 would be +// GAP at 0xB4 maybe ON Key? + +// Row 2 +#define DUCK_IO_KEY_ESCAPE 0x81u // Spanish label: Salida | German label: Esc +#define DUCK_IO_KEY_1 0x85u // Shift alt: ! +#define DUCK_IO_KEY_2 0x89u // Shift alt: " +#define DUCK_IO_KEY_3 0x8Du // Shift alt: · (Spanish, mid-dot) | § (German, legal section) +#define DUCK_IO_KEY_4 0x91u // Shift alt: $ +#define DUCK_IO_KEY_5 0x95u // Shift alt: % +#define DUCK_IO_KEY_6 0x99u // Shift alt: & +#define DUCK_IO_KEY_7 0x9Du // Shift alt: / +#define DUCK_IO_KEY_8 0xA1u // Shift alt: ( +#define DUCK_IO_KEY_9 0xA5u // Shift alt: ) +#define DUCK_IO_KEY_0 0xA9u // Shift alt: "\" +#define DUCK_IO_KEY_SINGLE_QUOTE 0xADu // Shift alt: ? | German version: ß (eszett) +#define DUCK_IO_KEY_EXCLAMATION_FLIPPED 0xB1u // Shift alt: ¿ (Spanish) | ` (German) // German version: ' (single quote?) +#define DUCK_IO_KEY_BACKSPACE 0xB5u // German label: Lösch +// See Continued Row 2 below + +// Row 3 +#define DUCK_IO_KEY_HELP 0x82u // Spanish label: Ayuda | German label: Hilfe +#define DUCK_IO_KEY_Q 0x86u +#define DUCK_IO_KEY_W 0x8Au +#define DUCK_IO_KEY_E 0x8Eu +#define DUCK_IO_KEY_R 0x92u +#define DUCK_IO_KEY_T 0x96u +#define DUCK_IO_KEY_Y 0x9Au // German version: z +#define DUCK_IO_KEY_U 0x9Eu +#define DUCK_IO_KEY_I 0xA2u +#define DUCK_IO_KEY_O 0xA6u +#define DUCK_IO_KEY_P 0xAAu +#define DUCK_IO_KEY_BACKTICK 0xAEu // Shift alt: [ (Spanish, only shift mode works) | German version: Ü +#define DUCK_IO_KEY_RIGHT_SQ_BRACKET 0xB2u // Shift alt: * | German version: · (mid-dot) +#define DUCK_IO_KEY_ENTER 0xB6u // Spanish label: Entra | German label: Ein-gabe +// See Continued Row 3 below + +// Row 4 +// GAP at 0x83 maybe CAPS LOCK (Spanish label: Mayuscula, German label: Groß) +#define DUCK_IO_KEY_A 0x87u +#define DUCK_IO_KEY_S 0x8Bu +#define DUCK_IO_KEY_D 0x8Fu +#define DUCK_IO_KEY_F 0x93u +#define DUCK_IO_KEY_G 0x97u +#define DUCK_IO_KEY_H 0x9Bu +#define DUCK_IO_KEY_J 0x9Fu +#define DUCK_IO_KEY_K 0xA3u +#define DUCK_IO_KEY_L 0xA7u +#define DUCK_IO_KEY_N_TILDE 0xABu // German version: ö +#define DUCK_IO_KEY_U_UMLAUT 0xAFu // German version: ä +#define DUCK_IO_KEY_O_OVER_LINE 0xB3u // Shift alt: [A over line] (Spanish) | ^ (German) | German version: # +// ? GAP at 0x87 ? + + +// Second 4 rows (bottom of keyboard) ~ 0x80 - 0xB7 +// +// - For each row, most chars are +4 vs char to immediate left +// +// Starting values +// - Row 5: 0xB8 +// - Row 6: 0xB9 +// - Row 7: 0xBA +// - Row 8: 0xBB + +// Row 5 +#define DUCK_IO_KEY_Z 0xB8u // German version: y +#define DUCK_IO_KEY_X 0xBCu +#define DUCK_IO_KEY_C 0xC0u +#define DUCK_IO_KEY_V 0xC4u +#define DUCK_IO_KEY_B 0xC8u +#define DUCK_IO_KEY_N 0xCCu +#define DUCK_IO_KEY_M 0xD0u +#define DUCK_IO_KEY_COMMA 0xD4u +#define DUCK_IO_KEY_PERIOD 0xD8u +#define DUCK_IO_KEY_DASH 0xDCu // Shift alt: _ | German version: @ +// See Continued Row 5 below +// Row 6 Continued (from below) +#define DUCK_IO_KEY_DELETE 0xE0u // * Spanish label: Borrar | German label: Entf. + + + +// Encoding is less orderly below + +// Row 6 +#define DUCK_IO_KEY_SPACE 0xB9u // Spanish label: Espacio | German label (blank) +// Continued Row 5 +#define DUCK_IO_KEY_LESS_THAN 0xBDu // Shift alt: > +// Continued Row 6 +#define DUCK_IO_KEY_PAGE_UP 0xC1u // Spanish label: Pg Arriba | German label: Zu-rück +#define DUCK_IO_KEY_PAGE_DOWN 0xC5u // Spanish label: Pg Abajo | German label: Wei-ter +#define DUCK_IO_KEY_MEMORY_MINUS 0xC9u +// Continued Row 5 +#define DUCK_IO_KEY_MEMORY_PLUS 0xCDu +#define DUCK_IO_KEY_MEMORY_RECALL 0xD1u +#define DUCK_IO_KEY_SQUAREROOT 0xD5u +// ** 3x3 Arrow and Math Key area ** +// Continued Row 6 +#define DUCK_IO_KEY_MULTIPLY 0xD9u +#define DUCK_IO_KEY_ARROW_DOWN 0xDDu +#define DUCK_IO_KEY_MINUS 0xE1u +// Continued Row 3 +#define DUCK_IO_KEY_ARROW_LEFT 0xE5u +#define DUCK_IO_KEY_EQUALS 0xE9u +#define DUCK_IO_KEY_ARROW_RIGHT 0xEDu +// Continued Row 2 +#define DUCK_IO_KEY_DIVIDE 0xE4u // German version: : +#define DUCK_IO_KEY_ARROW_UP 0xE8u +#define DUCK_IO_KEY_PLUS 0xECu + +// Row 7 +// Piano Sharp Keys +#define DUCK_IO_KEY_PIANO_DO_SHARP 0xBAu +#define DUCK_IO_KEY_PIANO_RE_SHARP 0xBEu +// GAP at 0xC2 where there is no key +#define DUCK_IO_KEY_PIANO_FA_SHARP 0xC6u +#define DUCK_IO_KEY_PIANO_SOL_SHARP 0xCAu +#define DUCK_IO_KEY_PIANO_LA_SHARP 0xCEu +// GAP at 0xD2 where there is no key +// +// Octave 2 maybe +#define DUCK_IO_KEY_PIANO_DO_2_SHARP 0xD6u +#define DUCK_IO_KEY_PIANO_RE_2_SHARP 0xDAu +// Row 6 Continued +#define DUCK_IO_KEY_PRINTSCREEN_RIGHT 0xDEu // German label: Druck (* Mixed in with piano keys) +// Row 7 Continued +#define DUCK_IO_KEY_PIANO_FA_2_SHARP 0xE2u +#define DUCK_IO_KEY_PIANO_SOL_2_SHARP 0xE6u +#define DUCK_IO_KEY_PIANO_LA_2_SHARP 0xEAu + +// Row 8 +// Piano Primary Keys +#define DUCK_IO_KEY_PIANO_DO 0xBBu +#define DUCK_IO_KEY_PIANO_RE 0xBFu +#define DUCK_IO_KEY_PIANO_MI 0xC3u +#define DUCK_IO_KEY_PIANO_FA 0xC7u +#define DUCK_IO_KEY_PIANO_SOL 0xCBu +#define DUCK_IO_KEY_PIANO_LA 0xCFu +#define DUCK_IO_KEY_PIANO_SI 0xD3u +#define DUCK_IO_KEY_PIANO_DO_2 0xD7u +#define DUCK_IO_KEY_PIANO_RE_2 0xDBu +#define DUCK_IO_KEY_PIANO_MI_2 0xDFu +#define DUCK_IO_KEY_PIANO_FA_2 0xE3u +#define DUCK_IO_KEY_PIANO_SOL_2 0xE7u +#define DUCK_IO_KEY_PIANO_LA_2 0xEBu +#define DUCK_IO_KEY_PIANO_SI_2 0xEFu + +#define DUCK_IO_KEY_LAST_KEY (DUCK_IO_KEY_PIANO_SI_2u) + +// Special System Codes? 0xF0+ +#define DUCK_IO_KEY_MAYBE_SYST_CODES_START 0xF0u +#define DUCK_IO_KEY_MAYBE_RX_NOT_A_KEY 0xF6u + +#endif // _MEGADUCK_LAPTOP_KEYCODES_H diff --git a/gbdk-lib/include/duck/model.h b/gbdk-lib/include/duck/model.h new file mode 100644 index 00000000..6088f09b --- /dev/null +++ b/gbdk-lib/include/duck/model.h @@ -0,0 +1,35 @@ +#include +#include + +#ifndef _MEGADUCK_MODEL_H +#define _MEGADUCK_MODEL_H + +#define MEGADUCK_HANDHELD_STANDARD 0u +#define MEGADUCK_LAPTOP_SPANISH 1u +#define MEGADUCK_LAPTOP_GERMAN 2u + + +/** Returns which MegaDuck Model the program is being run on + + Possible models are: + - Handheld: @ref MEGADUCK_HANDHELD_STANDARD + - Spanish Laptop "Super QuiQue": @ref MEGADUCK_LAPTOP_SPANISH + - German Laptop "Super Junior Computer": @ref MEGADUCK_LAPTOP_GERMAN + + This detection should be called immediately at the start of the program + for most reliable results, since it relies on inspecting uncleared VRAM + contents. + + It works by checking for distinct font VRAM Tile Patterns (which aren't + cleared before cart program launch) between the Spanish and German Laptop + models which have slightly different character sets. + + So VRAM *must not* be cleared or modified at program startup until after + this function is called (not by the crt0.s, not by the program itself). + + @note This detection may not work in emulators which don't simulate + the preloaded Laptop System ROM font tiles in VRAM. +*/ +uint8_t duck_check_model(void); + +#endif // _MEGADUCK_MODEL_H diff --git a/gbdk-lib/libc/targets/sm83/duck/Makefile b/gbdk-lib/libc/targets/sm83/duck/Makefile index f64bc39d..9287ac5e 100644 --- a/gbdk-lib/libc/targets/sm83/duck/Makefile +++ b/gbdk-lib/libc/targets/sm83/duck/Makefile @@ -5,7 +5,8 @@ TOPDIR = ../../../.. THIS = duck PORT = sm83 -CSRC = crlf.c digits.c gprint.c gprintf.c gprintln.c gprintn.c +CSRC = crlf.c digits.c gprint.c gprintf.c gprintln.c gprintn.c \ + megaduck_laptop_io.c megaduck_model.c ASSRC = cgb.s cgb_palettes.s cgb_compat.s \ cpy_data.s \ diff --git a/gbdk-lib/libc/targets/sm83/duck/megaduck_laptop_io.c b/gbdk-lib/libc/targets/sm83/duck/megaduck_laptop_io.c new file mode 100644 index 00000000..a21cbbcf --- /dev/null +++ b/gbdk-lib/libc/targets/sm83/duck/megaduck_laptop_io.c @@ -0,0 +1,323 @@ +#include +#include + +#include +#include +#include + +#include + +// volatile SFR __at(0xFF60) FF60_REG; + +// TODO: namespace to megaduck +static volatile bool duck_io_rx_byte_done; +static volatile uint8_t duck_io_rx_byte; + + uint8_t duck_io_rx_buf[DUCK_IO_LEN_RX_MAX]; + uint8_t duck_io_rx_buf_len; + + uint8_t duck_io_tx_buf[DUCK_IO_LEN_TX_MAX]; + uint8_t duck_io_tx_buf_len; + + uint8_t serial_cmd_0x09_reply_data; // In original hardware it's requested, but used for nothing? + + +static void _delay_1_msec(void); +static bool duck_io_controller_init(void); + +/** Serial link handler for receiving data send by the MegaDuck laptop peripheral + +*/ +void duck_io_sio_isr(void) CRITICAL INTERRUPT { + + // Save received data and update status flag + // Turn Serial ISR back off + duck_io_rx_byte = SB_REG; + duck_io_rx_byte_done = true; + set_interrupts(IE_REG & ~SIO_IFLAG); +} + +ISR_VECTOR(VECTOR_SERIAL, duck_io_sio_isr) + + + +static void _delay_1_msec(void) { + volatile uint8_t c = 75u; + while (c--); +} + + +// TODO: No longer in use(?) +// +// void duck_io_wait_done_with_timeout(uint8_t timeout_len_ms) { +// while (timeout_len_ms--) { +// _delay_1_msec(); +// if (duck_io_rx_byte_done) +// return; +// } +// } + + +void duck_io_send_byte(uint8_t tx_byte) { + + // FF60_REG = FF60_REG_BEFORE_XFER; // Seems optional in testing so far + SB_REG = tx_byte; + SC_REG = SIOF_XFER_START | SIOF_CLOCK_INT; + + // TODO: the delay here seems inefficient, but need to find out actual timing on the wire first + _delay_1_msec(); + + // Restore to SIO input and clear pending interrupt + IF_REG = 0x00; + SC_REG = SIOF_XFER_START | SIOF_CLOCK_EXT; +} + + +void duck_io_enable_read_byte(void) { + // FF60_REG = FF60_REG_BEFORE_XFER; + SC_REG = (SIOF_XFER_START | SIOF_CLOCK_EXT); + IE_REG |= SIO_IFLAG; + IF_REG = 0x00; + enable_interrupts(); +} + + +uint8_t duck_io_read_byte_no_timeout(void) { + CRITICAL { + duck_io_rx_byte_done = false; + } + + duck_io_enable_read_byte(); + while (!duck_io_rx_byte_done); + return duck_io_rx_byte; +} + + +bool duck_io_read_byte_with_msecs_timeout(uint8_t timeout_len_ms) { + uint8_t msec_counter; + CRITICAL { + duck_io_rx_byte_done = false; + } + + duck_io_enable_read_byte(); + + while (timeout_len_ms--) { + // Each full run of the inner loop is ~ 1msec + msec_counter = 75u; + while (msec_counter--) { + if (duck_io_rx_byte_done) + return true; + } + } + + return duck_io_rx_byte_done; +} + + +bool duck_io_send_byte_and_check_ack_msecs_timeout(uint8_t tx_byte, uint8_t timeout_len_ms, uint8_t expected_reply) { + + duck_io_send_byte(tx_byte); + + // A reply for the byte sent above should be incoming, fail if there is no reply + if (!duck_io_read_byte_with_msecs_timeout(timeout_len_ms)) return false; + + // Then check reply byte vs expected reply + return (duck_io_rx_byte == expected_reply); +} + + +bool duck_io_send_cmd_and_buffer(uint8_t io_cmd) { + + // Send buffer length + 2 (for length header and checksum bytes) + uint8_t packet_length = duck_io_tx_buf_len + 2; + uint8_t checksum_calc = packet_length; // Use total tx length (byte) as initial checksum + + // Save interrupt enables and then set only Serial to ON + uint8_t int_enables_saved = IE_REG; + IE_REG = SIO_IFLAG; + + // Send command to initiate buffer transfer, then check for reply + if (!duck_io_send_byte_and_check_ack_msecs_timeout(io_cmd, DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_SEND_BUFFER_OK)) { + IE_REG = int_enables_saved; + return false; + } + + // Send buffer length + 2 (for length header and checksum bytes) + _delay_1_msec; // Delay for unknown reasons (present in system rom) + if (!duck_io_send_byte_and_check_ack_msecs_timeout(packet_length, DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_SEND_BUFFER_OK)) { + IE_REG = int_enables_saved; + return false; + } + + // Send the buffer contents + uint8_t buffer_bytes_to_send = duck_io_tx_buf_len; + for (uint8_t idx = 0; idx < duck_io_tx_buf_len; idx++) { + + // Update checksum with next byte + checksum_calc += duck_io_tx_buf[idx]; + + // Send a byte from the buffer + if (!duck_io_send_byte_and_check_ack_msecs_timeout(duck_io_tx_buf[idx], DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_SEND_BUFFER_OK)) { + IE_REG = int_enables_saved; + return false; + } + } + + // Done sending buffer bytes, last byte to send is checksum + // Tx Checksum Byte should == (((sum of all bytes except checksum) XOR 0xFF) + 1) [two's complement] + checksum_calc = ~checksum_calc + 1u; // 2's complement + // Note different expected reply value versus previous reply checks + if (!duck_io_send_byte_and_check_ack_msecs_timeout(checksum_calc, DUCK_IO_TIMEOUT_200_MSEC, DUCK_IO_REPLY_BUFFER_XFER_OK)) { + IE_REG = int_enables_saved; + return false; + } + + // Success + IE_REG = int_enables_saved; + return true; +} + + +bool duck_io_send_cmd_and_receive_buffer(uint8_t io_cmd) { + + uint8_t packet_length = 0u; + uint8_t checksum_calc = 0x00u; + + // Reset global rx buffer length + duck_io_rx_buf_len = 0u; + + // Save interrupt enables and then set only Serial to ON + uint8_t int_enables_saved = IE_REG; + IE_REG = SIO_IFLAG; + + // _delay_1_msec() // Another mystery, ignore it for now + duck_io_send_byte(io_cmd); + + // Fail if first rx byte timed out + if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_100_MSEC)) { + + // First rx byte will be length of all incoming bytes + if (duck_io_rx_byte <= DUCK_IO_LEN_RX_MAX) { + + // Save rx byte as length and use to initialize checksum + // Reduce length by 1 (since it includes length byte already received) + checksum_calc = duck_io_rx_byte; + packet_length = duck_io_rx_byte - 1u; + + while (packet_length--) { + // Wait for next rx byte + if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_100_MSEC)) { + // Save rx byte to buffer and add to checksum + checksum_calc += duck_io_rx_byte; + duck_io_rx_buf[duck_io_rx_buf_len++] = duck_io_rx_byte; + } else { + // Error: Break out and set checksum so it fails test below (causing return with failure) + checksum_calc = 0xFFu; + break; + } + } + + // Done receiving buffer bytes, last rx byte should be checksum + // Rx Checksum Byte should == (((sum of all bytes except checksum) XOR 0xFF) + 1) [two's complement] + // so ((sum of received bytes including checksum byte) should == -> unsigned 8 bit overflow -> 0x00 + if (checksum_calc == 0x00u) { + // Return success + duck_io_send_byte(DUCK_IO_CMD_DONE_OR_OK); + + // Reduce number of received bytes by 1 to strip off trailing checksum byte + duck_io_rx_buf_len--; + IE_REG = int_enables_saved; + return true; + } + } + } + + // Something went wrong, error out + duck_io_send_byte(DUCK_IO_CMD_ABORT_OR_FAIL); + IE_REG = int_enables_saved; + return false; +} + + +static bool duck_io_controller_init(void) { + uint8_t counter; + + IE_REG = SIO_IFLAG; + bool serial_system_init_is_ok = true; + + // Send a count up sequence through the serial IO (0,1,2,3...255) + // Exit on 8 bit unsigned wraparound to 0x00 + counter = 0u; + do { + duck_io_send_byte(counter++); + } while (counter != 0u); + + // Then wait for a response + // Fail if reply back timed out or was not expected response + if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_2_MSEC)) { + if (duck_io_rx_byte != DUCK_IO_REPLY_BOOT_OK) serial_system_init_is_ok = false; + } else + serial_system_init_is_ok = false; + + // Send a command that seems to request a 255..0 countdown sequence from the external controller + if (serial_system_init_is_ok) { + duck_io_send_byte(DUCK_IO_CMD_INIT_START); + + // Expects a reply sequence through the serial IO of (255,254,253...0) + counter = 255u; + + // Exit on 8 bit unsigned wraparound to 0xFFu + do { + // Fail if reply back timed out or did not match expected counter + // TODO: OEM approach doesn't break out once a failure occurs, + // but maybe that's possible + sending the abort command early? + if (duck_io_read_byte_with_msecs_timeout(DUCK_IO_TIMEOUT_2_MSEC)) { + if (counter != duck_io_rx_byte) serial_system_init_is_ok = false; + } else + serial_system_init_is_ok = false; + counter--; + } while (counter != 255u); + + // Check for failures during the reply sequence + // and send reply byte based on that + if (serial_system_init_is_ok) + duck_io_send_byte(DUCK_IO_CMD_DONE_OR_OK); + else + duck_io_send_byte(DUCK_IO_CMD_ABORT_OR_FAIL); + } + + return serial_system_init_is_ok; +} + + +bool duck_io_laptop_init(void) { + + uint8_t int_enables_saved; + bool duck_io_init_ok = true; + + disable_interrupts(); + int_enables_saved = IE_REG; + SC_REG = 0x00u; + SB_REG = 0x00u; + + // Initialize Serially attached peripheral + duck_io_init_ok = duck_io_controller_init(); + if (duck_io_init_ok) { + // Save response from some command + // (so far not seen being used in System ROM 32K Bank 0) + duck_io_send_byte(DUCK_IO_CMD_INIT_UNKNOWN_0x09); + + // TODO: This wait with no timeout is how the System ROM does it, + // but it can probably be changed to a long delay and + // attempt to fail somewhat gracefully. + duck_io_read_byte_no_timeout(); + serial_cmd_0x09_reply_data = duck_io_rx_byte; + } + + // Ignore the RTC init check for now + + IE_REG = int_enables_saved; + enable_interrupts(); + + return (duck_io_init_ok); +} diff --git a/gbdk-lib/libc/targets/sm83/duck/megaduck_model.c b/gbdk-lib/libc/targets/sm83/duck/megaduck_model.c new file mode 100644 index 00000000..51baae60 --- /dev/null +++ b/gbdk-lib/libc/targets/sm83/duck/megaduck_model.c @@ -0,0 +1,55 @@ +#include +#include +#include + +#include + + +#define MEGADUCK_MODEL_TILE_ADDR_CHECK 0x8D00u // First tile at 0x8D00, Second tile at 0x8D10 + +// 2 consecutive Tiles: +// * Spanish model: Upside-down black Question Mark and Exclamation Point +// * German model: 2 pixel tall black Underscore and Inverted 0 on dark grey background +// +// Note: It may be sufficient to just check 1 tile +static const uint8_t model_spanish_tiles[32] = { + 0x00u, 0x00u, 0x18u, 0x18u, 0x00u, 0x00u, 0x38u, 0x38u, 0x70u, 0x70u, 0x72u, 0x72u, 0x76u, 0x76u, 0x3Cu, 0x3Cu, // Upside-down black Question Mark + 0x00u, 0x00u, 0x18u, 0x18u, 0x00u, 0x00u, 0x18u, 0x18u, 0x3Cu, 0x3Cu, 0x3Cu, 0x3Cu, 0x3Cu, 0x3Cu, 0x18u, 0x18u, // Upside-down black Exclamation Point +}; + +static const uint8_t model_german_tiles[32] = { + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 2 pixel tall black Underscore + 0x00u, 0xFFu, 0x00u, 0xC3u, 0x00u, 0x99u, 0x00u, 0x99u, 0x00u, 0x99u, 0x00u, 0x99u, 0x00u, 0xC3u, 0x00u, 0xFFu, // Inverted 0 on dark grey background +}; + +#define MODEL_SPANISH_TILES_SZ (sizeof(model_spanish_tiles) / sizeof(model_spanish_tiles[0])) +#define MODEL_GERMAN_TILES_SZ (sizeof(model_german_tiles) / sizeof(model_german_tiles[0])) + + +static bool vram_memcmp(const uint8_t * p_buf, uint8_t * p_vram, uint8_t cmp_size) { + + while (cmp_size--) { + if (get_vram_byte(p_vram++) != *p_buf++) { + return false; + } + } + return true; +} + + +// This detection should be called immediately at the start of +// the program for most reliable results, since it relies on +// inspecting uncleared VRAM contents. +uint8_t duck_check_model(void) { + + if (vram_memcmp(model_spanish_tiles, (uint8_t *)MEGADUCK_MODEL_TILE_ADDR_CHECK, MODEL_SPANISH_TILES_SZ)) { + return MEGADUCK_LAPTOP_SPANISH; + } + + // Check German + if (vram_memcmp(model_german_tiles, (uint8_t *)MEGADUCK_MODEL_TILE_ADDR_CHECK, MODEL_SPANISH_TILES_SZ)) { + return MEGADUCK_LAPTOP_GERMAN; + } + + return MEGADUCK_HANDHELD_STANDARD; // Default +}