Merge pull request #715 from gbdk-2020/megaduck/laptop_examples

MegaDuck laptop examples (kbd, rtc)
This commit is contained in:
bbbbbr
2024-10-02 03:41:53 -07:00
committed by GitHub
21 changed files with 2047 additions and 1 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,145 @@
#include <gbdk/platform.h>
#include <gbdk/console.h>
#include <gb/isr.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <duck/laptop_io.h>
#include <duck/model.h>
#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);
}
}
}
}

View File

@@ -0,0 +1,329 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <duck/model.h>
#include <duck/laptop_keycodes.h>
#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;
}

View File

@@ -0,0 +1,9 @@
#include <gbdk/platform.h>
#include <stdint.h>
#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

View File

@@ -0,0 +1,111 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <stdbool.h>
#include <duck/laptop_io.h>
#include <duck/laptop_keycodes.h>
#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;
}

View File

@@ -0,0 +1,44 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <stdbool.h>
#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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,119 @@
#include <gbdk/platform.h>
#include <gbdk/console.h>
#include <gb/isr.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <duck/laptop_io.h>
#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);
}
}
}
}
}

View File

@@ -0,0 +1,109 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <stdbool.h>
#include <duck/laptop_io.h>
#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;
}

View File

@@ -0,0 +1,26 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <stdbool.h>
#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

View File

@@ -0,0 +1,201 @@
#include <gbdk/platform.h>
#include <stdint.h>
/** @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

View File

@@ -0,0 +1,210 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <stdbool.h>
#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

View File

@@ -0,0 +1,35 @@
#include <gbdk/platform.h>
#include <stdint.h>
#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

View File

@@ -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 \

View File

@@ -0,0 +1,323 @@
#include <gbdk/platform.h>
#include <gb/isr.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <duck/laptop_io.h>
// 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);
}

View File

@@ -0,0 +1,55 @@
#include <gbdk/platform.h>
#include <stdint.h>
#include <stdbool.h>
#include <duck/model.h>
#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
}