mirror of
https://github.com/gbdk-2020/gbdk-2020.git
synced 2026-02-20 00:32:21 +01:00
Merge pull request #715 from gbdk-2020/megaduck/laptop_examples
MegaDuck laptop examples (kbd, rtc)
This commit is contained in:
17
gbdk-lib/examples/megaduck/Makefile
Normal file
17
gbdk-lib/examples/megaduck/Makefile
Normal 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:
|
||||
94
gbdk-lib/examples/megaduck/laptop_keyboard/Makefile
Normal file
94
gbdk-lib/examples/megaduck/laptop_keyboard/Makefile
Normal 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
|
||||
54
gbdk-lib/examples/megaduck/laptop_keyboard/Makefile.targets
Normal file
54
gbdk-lib/examples/megaduck/laptop_keyboard/Makefile.targets
Normal 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
|
||||
8
gbdk-lib/examples/megaduck/laptop_keyboard/Readme.md
Normal file
8
gbdk-lib/examples/megaduck/laptop_keyboard/Readme.md
Normal 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
|
||||
|
||||
145
gbdk-lib/examples/megaduck/laptop_keyboard/src/main.c
Normal file
145
gbdk-lib/examples/megaduck/laptop_keyboard/src/main.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
94
gbdk-lib/examples/megaduck/laptop_rtc/Makefile
Normal file
94
gbdk-lib/examples/megaduck/laptop_rtc/Makefile
Normal 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
|
||||
54
gbdk-lib/examples/megaduck/laptop_rtc/Makefile.targets
Normal file
54
gbdk-lib/examples/megaduck/laptop_rtc/Makefile.targets
Normal 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
|
||||
8
gbdk-lib/examples/megaduck/laptop_rtc/Readme.md
Normal file
8
gbdk-lib/examples/megaduck/laptop_rtc/Readme.md
Normal 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
|
||||
|
||||
119
gbdk-lib/examples/megaduck/laptop_rtc/src/main.c
Normal file
119
gbdk-lib/examples/megaduck/laptop_rtc/src/main.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.c
Normal file
109
gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.c
Normal 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;
|
||||
}
|
||||
26
gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.h
Normal file
26
gbdk-lib/examples/megaduck/laptop_rtc/src/megaduck_rtc.h
Normal 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
|
||||
201
gbdk-lib/include/duck/laptop_io.h
Normal file
201
gbdk-lib/include/duck/laptop_io.h
Normal 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
|
||||
210
gbdk-lib/include/duck/laptop_keycodes.h
Normal file
210
gbdk-lib/include/duck/laptop_keycodes.h
Normal 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
|
||||
35
gbdk-lib/include/duck/model.h
Normal file
35
gbdk-lib/include/duck/model.h
Normal 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
|
||||
@@ -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 \
|
||||
|
||||
323
gbdk-lib/libc/targets/sm83/duck/megaduck_laptop_io.c
Normal file
323
gbdk-lib/libc/targets/sm83/duck/megaduck_laptop_io.c
Normal 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);
|
||||
}
|
||||
55
gbdk-lib/libc/targets/sm83/duck/megaduck_model.c
Normal file
55
gbdk-lib/libc/targets/sm83/duck/megaduck_model.c
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user