Skip to content

Commit ee5da01

Browse files
GBA tester variant + screensaver; fix joybus reset path.
gba/source/main_tester.c is a new Doridian-handshake payload that runs the GBA in console mode showing live button state, falling through to a Mode-4 page-flipped screensaver after 30 s of zero input. The screensaver renders gen_logo.h's 64x54 logo mask through the same 7-colour cycle as the GC version (red/green/yellow/blue/magenta/cyan/ white), bounces at 60 Hz, and exits on any button press. gba/Makefile now builds two variants from one source tree — joypad/ (eyes, for joypad-os to consume via submodule) and tester/ (embedded into the GameCube host). Joybus reset fixes: - REG_JOYCNT polled inside the GBA payload's wait-vblank busy loop so a host cmd 0xFF triggers SystemCall(0x26) within microseconds — fixes hot-swap + host-reboot re-multiboot, which had stalled because the payload reacted too slowly to the reset. - JOYCNT register address corrected in main_tester.c (0x4000140; was pointing at JOYSTAT, 0x4000158). - Host (gamecube/ppc/gba.c): cmd 0xFF sent once then status polled until PSF0 instead of resetting every iteration; ready timeout bumped to 4 s for joypad-gba's full re-init; handshake-complete write made unconditional so the post-multiboot stall path matches joypad-os's "proceed anyway" behaviour. Host main loop: wheel detection sticky-cached the same way Keyboard is, so transient SI_GetType states during disconnect no longer flicker the port style to "Wheel". First multiboot attempt deferred ~30 frames so the UI draws before the blocking upload kicks in. Adds .github/FUNDING.yml mirroring joypad-os.
1 parent b3f00d6 commit ee5da01

12 files changed

Lines changed: 10425 additions & 1849 deletions

File tree

.github/FUNDING.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# These are supported funding model platforms
2+
3+
github: RobertDaleSmith
4+
# patreon: RobertDaleSmith
5+
open_collective: # Replace with a single Open Collective username
6+
ko_fi: # Replace with a single Ko-fi username
7+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9+
liberapay: # Replace with a single Liberapay username
10+
issuehunt: # Replace with a single IssueHunt username
11+
otechie: # Replace with a single Otechie username
12+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

gamecube/ppc/gba.c

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ extern const u32 gba_payload_len;
3434
#define CRC_SEED 0x15a0u
3535

3636
#define GBA_DELAY_US 70
37-
#define GBA_READY_TIMEOUT_MS 1500
37+
#define GBA_READY_TIMEOUT_MS 4000 // accommodate soft-reset + payload reinit
3838
#define GBA_ECHO_ATTEMPTS 200 // 200 × 10ms = 2s, matches joypad-os's poll
3939
#define GBA_ECHO_INTERVAL_MS 10
4040

@@ -163,18 +163,27 @@ int GBA_BootEmbedded(int chan) {
163163
u16 type;
164164
u8 js = 0;
165165

166-
// Loop reset+status until the GBA reports ready (PSF0 set in jstat).
167-
// Matches FIX94/gba-link-cable-rom-sender's wait pattern; the BIOS may
168-
// not be ready on the first try.
166+
// One reset, then poll status until ready. cmd 0xFF triggers a
167+
// SystemCall(0x26) hard-reset on the GBA side (when the GBA is
168+
// already running a multibooted payload that polls REG_JOYCNTRL.RST),
169+
// which takes ~1-2s for joypad-gba's display + libgba re-init. If we
170+
// re-sent cmd 0xFF on every iteration we'd keep restarting that
171+
// reset, never reaching the multiboot-wait state. Send the reset
172+
// once; the cmd 0x00 status polls below tolerate transient timeouts
173+
// while the GBA's reset cycle completes.
174+
(void)gba_handshake(chan, true, &type, &js);
175+
169176
u64 ready_deadline = gettime() + millisecs_to_ticks(GBA_READY_TIMEOUT_MS);
170-
do {
171-
if (!gba_handshake(chan, true, &type, &js)) return -2;
172-
if (type != GBA_TYPE_ID) return -2;
173-
if (!gba_handshake(chan, false, &type, &js)) return -2;
174-
if (type != GBA_TYPE_ID) return -2;
177+
bool ready = false;
178+
while (!ready) {
175179
if (gettime() > ready_deadline) return -2;
176-
busy_us(GBA_DELAY_US);
177-
} while (!(js & JSTAT_PSF0));
180+
if (gba_handshake(chan, false, &type, &js)
181+
&& type == GBA_TYPE_ID
182+
&& (js & JSTAT_PSF0))
183+
ready = true;
184+
else
185+
busy_us(GBA_DELAY_US);
186+
}
178187

179188
busy_us(GBA_DELAY_US);
180189

gamecube/ppc/gba_payload.c

Lines changed: 4658 additions & 1262 deletions
Large diffs are not rendered by default.

gamecube/ppc/main.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,11 @@ int main(int argc, char **argv) {
451451
u64 gba_retry_at[SI_MAX_CHAN] = {0};
452452
u8 gba_keys[SI_MAX_CHAN][2] = {{0}};
453453
u16 gba_missing[SI_MAX_CHAN] = {0};
454+
// Frame counter used to defer the very first GBA multiboot attempt
455+
// until after the UI has had a chance to draw. GBA_BootEmbedded
456+
// blocks the main loop for ~5 s during upload, and if it fires on
457+
// the first frame the user sees half-drawn UI then a freeze.
458+
u32 frame_count = 0;
454459

455460
// Idle screensaver state — bouncing "Joypad" tag protects CRTs from
456461
// burn-in. Activates after IDLE_THRESHOLD_MS of no controller activity;
@@ -470,6 +475,7 @@ int main(int argc, char **argv) {
470475
for (int i = 0; i < 4; i++) {
471476
keysHeld[i] = PAD_ButtonsHeld(i);
472477
}
478+
frame_count++;
473479

474480
// GBA: auto-multiboot any channel reporting a GBA, then poll the
475481
// running payload for REG_KEYINPUT each frame. Single-shot per
@@ -482,7 +488,9 @@ int main(int argc, char **argv) {
482488
if (gba_state[i] == GBA_RETRY && gettime() >= gba_retry_at[i]) {
483489
gba_state[i] = GBA_IDLE;
484490
}
485-
if (gba_state[i] == GBA_IDLE) {
491+
// Skip the first ~30 frames so the UI has time to draw before
492+
// GBA_BootEmbedded's multi-second blocking upload kicks in.
493+
if (gba_state[i] == GBA_IDLE && frame_count > 30) {
486494
int rc = GBA_BootEmbedded(i);
487495
gba_err[i] = (s8)rc;
488496
if (rc == 0) {
@@ -532,16 +540,30 @@ int main(int argc, char **argv) {
532540
// on channel 0 otherwise bounces SI_GetType between cached keyboard and
533541
// BUSY/NORESP, causing the port to flicker between Keyboard and None.
534542
static bool kbd_chan[4] = {false, false, false, false};
543+
// Same sticky-cache treatment for the steering wheel. SI_GC_STEERING
544+
// is the bare TYPE_GC value (0x08000000) — every transient state
545+
// libogc churns through during a disconnect (e.g. status bits
546+
// shedding from 0x09000000) momentarily looks like 0x08000000, so a
547+
// raw-type comparison flickers the port to "Wheel" for empty/
548+
// disconnected slots. Require a NO_RESPONSE-free read to latch.
549+
static bool wheel_chan[4] = {false, false, false, false};
535550
for (int i = 0; i < 4; i++) {
536551
u32 t = SI_GetType(i);
537-
if (((t & ~0xffff) & ~0x001F0000) == SI_GC_KEYBOARD) kbd_chan[i] = true;
552+
u32 hi = (t & ~0xffff) & ~0x001F0000;
553+
if (hi == SI_GC_KEYBOARD) kbd_chan[i] = true;
538554
// Also clear if libogc decisively reports something other than keyboard
539555
// (e.g. NORESP for an empty port, GC_CONTROLLER for a swapped pad).
540556
else if ((t & SI_ERROR_NO_RESPONSE) ||
541-
(((t & ~0xffff) & ~0x001F0000) != 0 &&
542-
((t & SI_TYPE_MASK) == SI_TYPE_GC))) {
557+
(hi != 0 && (t & SI_TYPE_MASK) == SI_TYPE_GC)) {
543558
kbd_chan[i] = false;
544559
}
560+
if (hi == SI_GC_STEERING && !(t & SI_ERROR_NO_RESPONSE)) {
561+
wheel_chan[i] = true;
562+
} else if ((t & SI_ERROR_NO_RESPONSE) ||
563+
(hi != SI_GC_STEERING && hi != 0 &&
564+
(t & SI_TYPE_MASK) == SI_TYPE_GC)) {
565+
wheel_chan[i] = false;
566+
}
545567
}
546568

547569
// Detect any activity — wakes the screensaver and resets the idle timer.
@@ -666,7 +688,7 @@ int main(int argc, char **argv) {
666688
r[4], r[5], r[6], r[0] & 0x0F);
667689
printf(" \n\n");
668690
continue;
669-
} else if ((raw_type & ~0xffff) == SI_GC_STEERING) {
691+
} else if (wheel_chan[i]) {
670692
// GC Steering Wheel (Hori, JP-region racing accessory). Distinct
671693
// SI device type — detection only; per-axis polling for wheel
672694
// angle / pedals is TODO (would mirror the keyboard's bespoke

gba/.gitignore

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Build intermediates — but KEEP the committed payload artifacts
2-
# (build/joypad_payload.c, build/joypad_mb.gba) so consumers of this
3-
# repo as a submodule don't need devkitARM unless they're modifying the
4-
# payload source.
5-
build/*.o
6-
build/*.d
7-
build/*.elf
8-
build/*.map
2+
# (build/<variant>/*_mb.gba, build/<variant>/*_payload.c) so consumers
3+
# of this repo as a submodule don't need devkitARM unless they're
4+
# modifying the payload source.
5+
build/**/*.o
6+
build/**/*.d
7+
build/**/*.elf
8+
build/**/*.map
99
joypad.map
10+
tester.map
1011

1112
.DS_Store
1213
.vscode

gba/Makefile

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
# joypad-gba — GBA multiboot payload that doubles as a USB controller
2-
# (via the GC↔GBA link cable) and renders animated cartoon eyes on the
3-
# GBA screen.
1+
# joypad-tester GBA payloads — two multiboot ROMs built from one source
2+
# tree:
43
#
5-
# The joybus handshake + per-VBlank JOYTR write (in source/main.c) is
6-
# Doridian's gba-as-controller reference (github.com/Doridian/Joybus-PIO)
7-
# verbatim — the cable's level-shifter MCU only stays happy with that
8-
# exact sequence. The eyes overlay runs in the VBlank slot on top.
4+
# joypad_mb.gba (VARIANT=joypad, default)
5+
# Doridian joybus controller + animated eyes overlay. Consumed by
6+
# joypad-os via submodule for its GBA-as-controller mode.
97
#
10-
# TARGET uses the "_mb" suffix so devkitARM's stock %_mb.elf rule picks
11-
# the multiboot specs file (loads at 0x02000000 instead of 0x08000000).
8+
# tester_mb.gba (VARIANT=tester)
9+
# Doridian joybus controller + on-GBA console button display.
10+
# Consumed by joypad-tester's GameCube/Wii host so users see test
11+
# feedback on the GBA itself; also usable standalone via flash cart
12+
# for a pure button tester.
13+
#
14+
# Build both: make (or `make all`)
15+
# Build one: make joypad (or `make tester`)
16+
# Clean: make clean
17+
#
18+
# The TARGET uses the "_mb" suffix so devkitARM's stock %_mb.elf rule
19+
# picks the multiboot specs file (loads at 0x02000000 instead of
20+
# 0x08000000).
1221

1322
ifeq ($(strip $(DEVKITPRO)),)
1423
$(error "Set DEVKITPRO. typically: export DEVKITPRO=/opt/devkitpro")
@@ -22,10 +31,37 @@ include $(DEVKITPRO)/devkitARM/gba_rules
2231
# frontend so these flags pass through.
2332
export LD := $(CC)
2433

25-
TARGET := joypad
26-
BUILD := build
34+
VARIANT ?= joypad
35+
36+
ifeq ($(VARIANT),tester)
37+
TARGET := tester
38+
CFILES := main_tester.c
39+
else
40+
TARGET := joypad
41+
CFILES := main.c display.c eyes_anim.c
42+
endif
43+
2744
SOURCES := source
2845
INCLUDES :=
46+
BUILD := build/$(TARGET)
47+
48+
# SRCROOT is the original source-root dir. Captured before any -C
49+
# recursion so inner-pass paths stay correct.
50+
SRCROOT ?= $(CURDIR)
51+
52+
LIBS := -lgba -lm
53+
LIBDIRS := $(LIBGBA)
54+
55+
# Variant-dependent paths first, so CFLAGS' INCLUDE expansion below picks
56+
# them up.
57+
export OUTPUT := $(SRCROOT)/$(BUILD)/$(TARGET)
58+
export VPATH := $(SRCROOT)/source
59+
export DEPSDIR := $(SRCROOT)/$(BUILD)
60+
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(SRCROOT)/$(dir)) \
61+
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
62+
-I$(SRCROOT)/$(BUILD)
63+
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
64+
export OFILES := $(CFILES:.c=.o)
2965

3066
ARCH := -mthumb-interwork -mthumb
3167
CFLAGS := -g -Wall -O3 \
@@ -36,50 +72,41 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
3672
ASFLAGS := $(ARCH)
3773
LDFLAGS := -g $(ARCH) -Wl,-Map,$(TARGET).map
3874

39-
LIBS := -lgba -lm
40-
LIBDIRS := $(LIBGBA)
41-
42-
ifneq ($(BUILD),$(notdir $(CURDIR)))
75+
ifneq ($(INNER_PASS),1)
4376

44-
export OUTPUT := $(CURDIR)/$(BUILD)/$(TARGET)
45-
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir))
46-
export DEPSDIR := $(CURDIR)/$(BUILD)
77+
# Outer pass: dispatch each requested variant via a recursive inner pass.
78+
.PHONY: all joypad tester clean
4779

48-
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
49-
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
80+
all: joypad tester
5081

51-
export OFILES := $(addsuffix .o,$(BINFILES)) $(CFILES:.c=.o) $(SFILES:.s=.o)
52-
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
53-
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
54-
-I$(CURDIR)/$(BUILD)
55-
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
56-
57-
.PHONY: all clean
82+
joypad:
83+
@mkdir -p build/joypad
84+
@$(MAKE) --no-print-directory -C build/joypad -f $(SRCROOT)/Makefile \
85+
VARIANT=joypad SRCROOT=$(SRCROOT) INNER_PASS=1
5886

59-
all: $(BUILD)
60-
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
61-
62-
$(BUILD):
63-
@mkdir -p $@
87+
tester:
88+
@mkdir -p build/tester
89+
@$(MAKE) --no-print-directory -C build/tester -f $(SRCROOT)/Makefile \
90+
VARIANT=tester SRCROOT=$(SRCROOT) INNER_PASS=1
6491

6592
clean:
6693
@echo cleaning ...
67-
@rm -rf $(BUILD)/*.o $(BUILD)/*.d $(BUILD)/*.elf $(BUILD)/*.map $(TARGET).map
94+
@rm -rf build
6895

6996
else
7097

71-
DEPENDS := $(OFILES:.o=.d)
72-
98+
# Inner pass: actually compile + link this variant. CURDIR is the build
99+
# dir; gba_rules' implicit %.o and %_mb.elf rules find sources via VPATH
100+
# and emit objects/binaries in CURDIR.
73101
all: $(OUTPUT)_payload.c
74102

75103
$(OUTPUT)_payload.c: $(OUTPUT)_mb.gba
76104
@echo embedding $(notdir $<) -^> $(notdir $@)
77-
@python3 $(CURDIR)/../tools/bin2c.py $< $@ \
78-
--source-name $(TARGET)_mb.gba
105+
@python3 $(SRCROOT)/tools/bin2c.py $< $@ --source-name $(TARGET)_mb.gba
79106

80107
$(OUTPUT)_mb.gba: $(OUTPUT)_mb.elf
81108
$(OUTPUT)_mb.elf: $(OFILES)
82109

83-
-include $(DEPENDS)
110+
-include $(OFILES:.o=.d)
84111

85112
endif

0 commit comments

Comments
 (0)