Merge pull request #20 from afska/v6.3.0

⏱️ LinkUART, 🖱️ LinkPS2Mouse, ⌨️ LinkPS2Keyboard
This commit is contained in:
Rodrigo Alfonso 2024-04-21 21:54:15 -03:00 committed by GitHub
commit bc9495130a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1921 additions and 24 deletions

View File

@ -12,6 +12,9 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port.
- [🌎](#-LinkUniversal) [LinkUniversal.hpp](lib/LinkUniversal.hpp): Add multiplayer support to your game, both with 👾 *Link Cables* and 📻 *Wireless Adapters*, using the **same API**!
- [🔌](#-LinkGPIO) [LinkGPIO.hpp](lib/LinkGPIO.hpp): Use the Link Port however you want to control **any device** (like LEDs, rumble motors, and that kind of stuff)!
- [🔗](#-LinkSPI) [LinkSPI.hpp](lib/LinkSPI.hpp): Connect with a PC (like a **Raspberry Pi**) or another GBA (with a GBC Link Cable) using this mode. Transfer up to 2Mbit/s!
- [⏱️](#%EF%B8%8F-LinkUART) [LinkUART.hpp](lib/LinkUART.hpp): Easily connect to **any PC** using a USB to UART cable!
- [🖱️](#%EF%B8%8F-LinkPS2Mouse) [LinkPS2Mouse.hpp](lib/LinkPS2Mouse.hpp): Connect a **PS/2 mouse** to the GBA for extended controls!
- [⌨️](#%EF%B8%8F-LinkPS2Keyboard) [LinkPS2Keyboard.hpp](lib/LinkPS2Keyboard.hpp): Connect a **PS/2 keyboard** to the GBA for extended controls!
*(click on the emojis for documentation)*
@ -87,7 +90,7 @@ Name | Return type | Description
# 💻 LinkCableMultiboot
*(aka Multiboot through Multi-Play mode)*
*(aka Multiboot through Multi-Play Mode)*
This tool allows sending Multiboot ROMs (small 256KiB programs that fit in EWRAM) from one GBA to up to 3 slaves, using a single cartridge.
@ -343,3 +346,78 @@ The GBA operates using **SPI mode 3** (`CPOL=1, CPHA=1`). Here's a connection di
</td>
</tr>
</table>
# ⏱️ LinkUART
*(aka UART Mode)*
This is the GBA's implementation of UART. You can use this to interact with a PC using a _USB to UART cable_. You can change the buffer size by changing the compile-time constant `LINK_UART_QUEUE_SIZE`.
![photo](https://github.com/afska/gba-link-connection/assets/1631752/2ca8abb8-1a38-40bb-bf7d-bf29a0f880cd)
## Methods
Name | Return type | Description
--- | --- | ---
`isActive()` | **bool** | Returns whether the library is active or not.
`activate(baudRate, dataSize, parity, useCTS)` | - | Activates the library using a specific UART mode. _Defaults: 9600bps, 8-bit data, no parity bit, no CTS_.
`deactivate()` | - | Deactivates the library.
`sendLine(string)` | - | Takes a null-terminated `string`, and sends it followed by a `'\n'` character. The null character is not sent.
`sendLine(data, cancel)` | - | Like `sendLine(string)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the transfer if it returns `true`.
`readLine(string, [limit])` | **bool** | Reads characters into `string` until finding a `'\n'` character or a character `limit` is reached. A null terminator is added at the end. Returns `false` if the limit has been reached without finding a newline character.
`readLine(string, cancel, [limit])` | - | Like `readLine(string, [limit])` but accepts a `cancel()` function. The library will continuously invoke it, and abort the transfer if it returns `true`.
`send(buffer, size, offset)` | - | Sends `size` bytes from `buffer`, starting at byte `offset`.
`read(buffer, size, offset)` | **u32** | Tries to read `size` bytes into `(u8*)(buffer + offset)`. Returns the number of read bytes.
`canRead()` | **bool** | Returns whether there are bytes to read or not.
`canSend()` | **bool** | Returns whether there is room to send new messages or not.
`availableForRead()` | **u32** | Returns the number of bytes available for read.
`availableForSend()` | **u32** | Returns the number of bytes available for send (buffer size - queued bytes).
`read()` | **u8** | Reads a byte. Returns 0 if nothing is found.
`send(data)` | - | Sends a `data` byte.
## UART Configuration
The GBA operates using 1 stop bit, but everything else can be configured. By default, the library uses `8N1`, which means 8-bit data and no parity bit. RTS/CTS is disabled by default.
![diagram](https://github.com/afska/gba-link-connection/assets/1631752/a6a58f94-da24-4fd9-9603-9c7c9a493f93)
- Black wire (GND) -> GBA GND.
- Green wire (TX) -> GBA SI.
- White wire (RX) -> GBA SO.
# 🖱️ LinkPS2Mouse
A PS/2 mouse driver for the GBA. Use it to add mouse support to your homebrew games.
![photo](https://github.com/afska/gba-link-connection/assets/1631752/6856ff0d-0f06-4a9d-8ded-280052e02b8d)
## Constructor
`new LinkPS2Mouse(timerId)`, where `timerId` is the GBA Timer used for delays.
## Methods
Name | Return type | Description
--- | --- | ---
`isActive()` | **bool** | Returns whether the library is active or not.
`activate()` | - | Activates the library.
`deactivate()` | - | Deactivates the library.
`report(data[3])` | - | Fills the `data` int array with a report. The first int contains _clicks_ that you can check against the bitmasks `LINK_PS2_MOUSE_LEFT_CLICK`, `LINK_PS2_MOUSE_MIDDLE_CLICK`, and `LINK_PS2_MOUSE_RIGHT_CLICK`. The second int is the _X movement_, and the third int is the _Y movement_.
# ⌨️ LinkPS2Keyboard
A PS/2 keyboard driver for the GBA. Use it to add keyboard support to your homebrew games.
![photo](https://github.com/afska/gba-link-connection/assets/1631752/4c5fa3ed-5d96-45fe-ad24-73bc3f71c63f)
## Constructor
`new LinkPS2Keyboard(onEvent)`, where `onEvent` is a function pointer that will receive the scan codes (`u8`). You should check a PS/2 scan code list online, but there are some examples included like `LINK_PS2_KEYBOARD_KEY_ENTER` and `LINK_PS2_KEYBOARD_KEY_RELEASE`.
## Methods
Name | Return type | Description
--- | --- | ---
`isActive()` | **bool** | Returns whether the library is active or not.
`activate()` | - | Activates the library.
`deactivate()` | - | Deactivates the library.

View File

@ -49,7 +49,7 @@ int main() {
// Sender options
if (isSenderMode) {
if (result != LinkCableMultiboot::Result::SUCCESS)
log("LinkCableMultiboot_demo\n (v6.2.3)\n\nPress START to send the "
log("LinkCableMultiboot_demo\n (v6.3.0)\n\nPress START to send the "
"ROM...\nPress B to set client mode...");
if (keys & KEY_START) {

View File

@ -45,7 +45,7 @@ int main() {
u16 keys = ~REG_KEYS & KEY_ANY;
linkCable->send(keys + 1); // (avoid using 0)
std::string output = "LinkCable_basic (v6.2.3)\n\n";
std::string output = "LinkCable_basic (v6.3.0)\n\n";
if (linkCable->isConnected()) {
u8 playerCount = linkCable->playerCount();
u8 currentPlayerId = linkCable->currentPlayerId();

View File

@ -93,10 +93,10 @@ inline void setUpInterrupts() {
void printTutorial() {
#ifndef USE_LINK_UNIVERSAL
DEBULOG("LinkCable_full (v6.2.3)");
DEBULOG("LinkCable_full (v6.3.0)");
#endif
#ifdef USE_LINK_UNIVERSAL
DEBULOG("LinkUniversal_full (v6.2.3)");
DEBULOG("LinkUniversal_full (v6.3.0)");
#endif
DEBULOG("");

View File

@ -89,10 +89,10 @@ int main() {
while (true) {
#ifndef USE_LINK_UNIVERSAL
std::string output = "LinkCable_stress (v6.2.3)\n\n";
std::string output = "LinkCable_stress (v6.3.0)\n\n";
#endif
#ifdef USE_LINK_UNIVERSAL
std::string output = "LinkUniversal_stress (v6.2.3)\n\n";
std::string output = "LinkUniversal_stress (v6.3.0)\n\n";
#endif
linkConnection->deactivate();

View File

@ -27,7 +27,7 @@ int main() {
while (true) {
// (3) Use the pins
std::string output = "LinkGPIO_demo (v6.2.3)\n\n";
std::string output = "LinkGPIO_demo (v6.3.0)\n\n";
// Commands
u16 keys = ~REG_KEYS & KEY_ANY;

View File

@ -0,0 +1,284 @@
#
# Template tonc makefile
#
# Yoinked mostly from DKP's template
#
# === SETUP ===========================================================
# --- No implicit rules ---
.SUFFIXES:
# --- Paths ---
export TONCLIB := ${DEVKITPRO}/libtonc
# === TONC RULES ======================================================
#
# Yes, this is almost, but not quite, completely like to
# DKP's base_rules and gba_rules
#
export PATH := $(DEVKITARM)/bin:$(PATH)
# --- Executable names ---
PREFIX ?= arm-none-eabi-
export CC := $(PREFIX)gcc
export CXX := $(PREFIX)g++
export AS := $(PREFIX)as
export AR := $(PREFIX)ar
export NM := $(PREFIX)nm
export OBJCOPY := $(PREFIX)objcopy
# LD defined in Makefile
# === LINK / TRANSLATE ================================================
%.gba : %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@)
@gbafix $@ -t$(TITLE)
#----------------------------------------------------------------------
%.mb.elf :
@echo Linking multiboot
$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.elf :
@echo Linking cartridge
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.a :
@echo $(notdir $@)
@rm -f $@
$(AR) -crs $@ $^
# === OBJECTIFY =======================================================
%.iwram.o : %.iwram.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.iwram.o : %.iwram.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.s
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.S
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
# canned command sequence for binary data
#----------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
# =====================================================================
# --- Main path ---
export PATH := $(DEVKITARM)/bin:$(PATH)
# === PROJECT DETAILS =================================================
# PROJ : Base project name
# TITLE : Title for ROM header (12 characters)
# LIBS : Libraries to use, formatted as list for linker flags
# BUILD : Directory for build process temporaries. Should NOT be empty!
# SRCDIRS : List of source file directories
# DATADIRS : List of data file directories
# INCDIRS : List of header file directories
# LIBDIRS : List of library directories
# General note: use `.' for the current dir, don't leave the lists empty.
export PROJ ?= $(notdir $(CURDIR))
TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
# --- switches ---
bMB := 0 # Multiboot build
bTEMPS := 0 # Save gcc temporaries (.i and .s files)
bDEBUG2 := 0 # Generate debug info (bDEBUG2? Not a full DEBUG flag. Yet)
# === BUILD FLAGS =====================================================
# This is probably where you can stop editing
# NOTE: I've noticed that -fgcse and -ftree-loop-optimize sometimes muck
# up things (gcse seems fond of building masks inside a loop instead of
# outside them for example). Removing them sometimes helps
# --- Architecture ---
ARCH := -mthumb-interwork -mthumb
RARCH := -mthumb-interwork -mthumb
IARCH := -mthumb-interwork -marm -mlong-calls
# --- Main flags ---
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
CFLAGS += -Wall
CFLAGS += $(INCLUDE)
CFLAGS += -ffast-math -fno-strict-aliasing
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := $(ARCH) $(INCLUDE)
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
# --- switched additions ----------------------------------------------
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
else
TARGET := $(PROJ)
endif
# --- Save temporary files ? ---
ifeq ($(strip $(bTEMPS)), 1)
CFLAGS += -save-temps
CXXFLAGS += -save-temps
endif
# --- Debug info ? ---
ifeq ($(strip $(bDEBUG)), 1)
CFLAGS += -DDEBUG -g
CXXFLAGS += -DDEBUG -g
ASFLAGS += -DDEBUG -g
LDFLAGS += -g
else
CFLAGS += -DNDEBUG
CXXFLAGS += -DNDEBUG
ASFLAGS += -DNDEBUG
endif
# === BUILD PROC ======================================================
ifneq ($(BUILD),$(notdir $(CURDIR)))
# Still in main dir:
# * Define/export some extra variables
# * Invoke this file again from the build dir
# PONDER: what happens if BUILD == "" ?
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := \
$(foreach dir, $(SRCDIRS) , $(CURDIR)/$(dir)) \
$(foreach dir, $(DATADIRS), $(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
# --- List source and data files ---
CFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir, $(DATADIRS), $(notdir $(wildcard $(dir)/*.*)))
# --- Set linker depending on C++ file existence ---
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
# --- Define object file list ---
export OFILES := $(addsuffix .o, $(BINFILES)) \
$(CFILES:.c=.o) $(CPPFILES:.cpp=.o) \
$(SFILES:.s=.o)
# --- Create include and library search paths ---
export INCLUDE := $(foreach dir,$(INCDIRS),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := -L$(CURDIR) $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
# --- Create BUILD if necessary, and run this makefile from there ---
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
all : $(BUILD)
clean:
@echo clean ...
@rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
else # If we're here, we should be in the BUILD dir
DEPENDS := $(OFILES:.o=.d)
# --- Main targets ----
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
-include $(DEPENDS)
endif # End BUILD switch
# --- More targets ----------------------------------------------------
.PHONY: clean rebuild start
rebuild: clean $(BUILD)
start:
start "$(TARGET).gba"
restart: rebuild start
# EOF

View File

@ -0,0 +1,70 @@
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
// (0) Include the header
#include "../../../lib/LinkPS2Keyboard.hpp"
void log(std::string text);
static std::string scanCodes = "";
static std::string output = "";
static u32 irqs = 0;
inline void VBLANK() {}
void SERIAL() {
LINK_PS2_KEYBOARD_ISR_SERIAL();
irqs++;
}
// (1) Create a LinkPS2Keyboard instance
LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 scanCode) {
// (4) Handle events in the callback sent to LinkPS2Keyboard's constructor!
scanCodes += std::to_string(scanCode) + "|";
});
void init() {
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
// (2) Add the interrupt service routines
interrupt_init();
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, SERIAL);
interrupt_enable(INTR_SERIAL);
}
int main() {
init();
while (true) {
std::string output = "LinkPS2Keyboard_demo (v6.3.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkPS2Keyboard->isActive()) {
output += "Press A to read keyboard input";
if (keys & KEY_A) {
// (3) Initialize the library
log("Waiting...");
linkPS2Keyboard->activate();
VBlankIntrWait();
continue;
}
} else {
output += std::to_string(irqs) + " - " + scanCodes;
}
// Print
VBlankIntrWait();
LINK_PS2_KEYBOARD_ISR_VBLANK();
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

View File

@ -0,0 +1,284 @@
#
# Template tonc makefile
#
# Yoinked mostly from DKP's template
#
# === SETUP ===========================================================
# --- No implicit rules ---
.SUFFIXES:
# --- Paths ---
export TONCLIB := ${DEVKITPRO}/libtonc
# === TONC RULES ======================================================
#
# Yes, this is almost, but not quite, completely like to
# DKP's base_rules and gba_rules
#
export PATH := $(DEVKITARM)/bin:$(PATH)
# --- Executable names ---
PREFIX ?= arm-none-eabi-
export CC := $(PREFIX)gcc
export CXX := $(PREFIX)g++
export AS := $(PREFIX)as
export AR := $(PREFIX)ar
export NM := $(PREFIX)nm
export OBJCOPY := $(PREFIX)objcopy
# LD defined in Makefile
# === LINK / TRANSLATE ================================================
%.gba : %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@)
@gbafix $@ -t$(TITLE)
#----------------------------------------------------------------------
%.mb.elf :
@echo Linking multiboot
$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.elf :
@echo Linking cartridge
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.a :
@echo $(notdir $@)
@rm -f $@
$(AR) -crs $@ $^
# === OBJECTIFY =======================================================
%.iwram.o : %.iwram.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.iwram.o : %.iwram.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.s
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.S
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
# canned command sequence for binary data
#----------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
# =====================================================================
# --- Main path ---
export PATH := $(DEVKITARM)/bin:$(PATH)
# === PROJECT DETAILS =================================================
# PROJ : Base project name
# TITLE : Title for ROM header (12 characters)
# LIBS : Libraries to use, formatted as list for linker flags
# BUILD : Directory for build process temporaries. Should NOT be empty!
# SRCDIRS : List of source file directories
# DATADIRS : List of data file directories
# INCDIRS : List of header file directories
# LIBDIRS : List of library directories
# General note: use `.' for the current dir, don't leave the lists empty.
export PROJ ?= $(notdir $(CURDIR))
TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
# --- switches ---
bMB := 0 # Multiboot build
bTEMPS := 0 # Save gcc temporaries (.i and .s files)
bDEBUG2 := 0 # Generate debug info (bDEBUG2? Not a full DEBUG flag. Yet)
# === BUILD FLAGS =====================================================
# This is probably where you can stop editing
# NOTE: I've noticed that -fgcse and -ftree-loop-optimize sometimes muck
# up things (gcse seems fond of building masks inside a loop instead of
# outside them for example). Removing them sometimes helps
# --- Architecture ---
ARCH := -mthumb-interwork -mthumb
RARCH := -mthumb-interwork -mthumb
IARCH := -mthumb-interwork -marm -mlong-calls
# --- Main flags ---
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
CFLAGS += -Wall
CFLAGS += $(INCLUDE)
CFLAGS += -ffast-math -fno-strict-aliasing
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := $(ARCH) $(INCLUDE)
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
# --- switched additions ----------------------------------------------
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
else
TARGET := $(PROJ)
endif
# --- Save temporary files ? ---
ifeq ($(strip $(bTEMPS)), 1)
CFLAGS += -save-temps
CXXFLAGS += -save-temps
endif
# --- Debug info ? ---
ifeq ($(strip $(bDEBUG)), 1)
CFLAGS += -DDEBUG -g
CXXFLAGS += -DDEBUG -g
ASFLAGS += -DDEBUG -g
LDFLAGS += -g
else
CFLAGS += -DNDEBUG
CXXFLAGS += -DNDEBUG
ASFLAGS += -DNDEBUG
endif
# === BUILD PROC ======================================================
ifneq ($(BUILD),$(notdir $(CURDIR)))
# Still in main dir:
# * Define/export some extra variables
# * Invoke this file again from the build dir
# PONDER: what happens if BUILD == "" ?
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := \
$(foreach dir, $(SRCDIRS) , $(CURDIR)/$(dir)) \
$(foreach dir, $(DATADIRS), $(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
# --- List source and data files ---
CFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir, $(DATADIRS), $(notdir $(wildcard $(dir)/*.*)))
# --- Set linker depending on C++ file existence ---
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
# --- Define object file list ---
export OFILES := $(addsuffix .o, $(BINFILES)) \
$(CFILES:.c=.o) $(CPPFILES:.cpp=.o) \
$(SFILES:.s=.o)
# --- Create include and library search paths ---
export INCLUDE := $(foreach dir,$(INCDIRS),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := -L$(CURDIR) $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
# --- Create BUILD if necessary, and run this makefile from there ---
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
all : $(BUILD)
clean:
@echo clean ...
@rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
else # If we're here, we should be in the BUILD dir
DEPENDS := $(OFILES:.o=.d)
# --- Main targets ----
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
-include $(DEPENDS)
endif # End BUILD switch
# --- More targets ----------------------------------------------------
.PHONY: clean rebuild start
rebuild: clean $(BUILD)
start:
start "$(TARGET).gba"
restart: rebuild start
# EOF

View File

@ -0,0 +1,64 @@
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
// (0) Include the header
#include "../../../lib/LinkPS2Mouse.hpp"
void log(std::string text);
inline void VBLANK() {}
inline void TIMER() {}
// (1) Create a LinkPS2Mouse instance
LinkPS2Mouse* linkPS2Mouse = new LinkPS2Mouse(2);
void init() {
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
// (2) Add the interrupt service routines
interrupt_init();
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_TIMER2, TIMER);
interrupt_enable(INTR_TIMER2);
}
int main() {
init();
while (true) {
std::string output = "LinkPS2Mouse_demo (v6.3.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkPS2Mouse->isActive()) {
output += "Press A to read mouse input";
if (keys & KEY_A) {
// (3) Initialize the library
log("Waiting...");
linkPS2Mouse->activate();
VBlankIntrWait();
continue;
}
} else {
// (4) Get a report
int data[3];
linkPS2Mouse->report(data);
output += std::to_string(data[0]) + ": " + "(" + std::to_string(data[1]) +
", " + std::to_string(data[2]) + ")";
}
// Print
VBlankIntrWait();
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

View File

@ -33,7 +33,7 @@ int main() {
u16 prevKeys = 0;
while (true) {
std::string output = "LinkRawCable_demo (v6.2.3)\n\n";
std::string output = "LinkRawCable_demo (v6.3.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkRawCable->isActive()) {

View File

@ -139,7 +139,7 @@ void DebugScene::load() {
log("---");
log("LinkRawWireless demo");
log(" (v6.2.3)");
log(" (v6.3.0)");
log("");
log("START: reset wireless adapter");
log("A: send command");

View File

@ -32,7 +32,7 @@ int main() {
u32 counter = 0;
while (true) {
std::string output = "LinkSPI_demo (v6.2.3)\n\n";
std::string output = "LinkSPI_demo (v6.3.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkSPI->isActive()) {

View File

@ -0,0 +1,284 @@
#
# Template tonc makefile
#
# Yoinked mostly from DKP's template
#
# === SETUP ===========================================================
# --- No implicit rules ---
.SUFFIXES:
# --- Paths ---
export TONCLIB := ${DEVKITPRO}/libtonc
# === TONC RULES ======================================================
#
# Yes, this is almost, but not quite, completely like to
# DKP's base_rules and gba_rules
#
export PATH := $(DEVKITARM)/bin:$(PATH)
# --- Executable names ---
PREFIX ?= arm-none-eabi-
export CC := $(PREFIX)gcc
export CXX := $(PREFIX)g++
export AS := $(PREFIX)as
export AR := $(PREFIX)ar
export NM := $(PREFIX)nm
export OBJCOPY := $(PREFIX)objcopy
# LD defined in Makefile
# === LINK / TRANSLATE ================================================
%.gba : %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@)
@gbafix $@ -t$(TITLE)
#----------------------------------------------------------------------
%.mb.elf :
@echo Linking multiboot
$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.elf :
@echo Linking cartridge
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.a :
@echo $(notdir $@)
@rm -f $@
$(AR) -crs $@ $^
# === OBJECTIFY =======================================================
%.iwram.o : %.iwram.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.iwram.o : %.iwram.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.s
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.S
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
# canned command sequence for binary data
#----------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
# =====================================================================
# --- Main path ---
export PATH := $(DEVKITARM)/bin:$(PATH)
# === PROJECT DETAILS =================================================
# PROJ : Base project name
# TITLE : Title for ROM header (12 characters)
# LIBS : Libraries to use, formatted as list for linker flags
# BUILD : Directory for build process temporaries. Should NOT be empty!
# SRCDIRS : List of source file directories
# DATADIRS : List of data file directories
# INCDIRS : List of header file directories
# LIBDIRS : List of library directories
# General note: use `.' for the current dir, don't leave the lists empty.
export PROJ ?= $(notdir $(CURDIR))
TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
# --- switches ---
bMB := 0 # Multiboot build
bTEMPS := 0 # Save gcc temporaries (.i and .s files)
bDEBUG2 := 0 # Generate debug info (bDEBUG2? Not a full DEBUG flag. Yet)
# === BUILD FLAGS =====================================================
# This is probably where you can stop editing
# NOTE: I've noticed that -fgcse and -ftree-loop-optimize sometimes muck
# up things (gcse seems fond of building masks inside a loop instead of
# outside them for example). Removing them sometimes helps
# --- Architecture ---
ARCH := -mthumb-interwork -mthumb
RARCH := -mthumb-interwork -mthumb
IARCH := -mthumb-interwork -marm -mlong-calls
# --- Main flags ---
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
CFLAGS += -Wall
CFLAGS += $(INCLUDE)
CFLAGS += -ffast-math -fno-strict-aliasing
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := $(ARCH) $(INCLUDE)
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
# --- switched additions ----------------------------------------------
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
else
TARGET := $(PROJ)
endif
# --- Save temporary files ? ---
ifeq ($(strip $(bTEMPS)), 1)
CFLAGS += -save-temps
CXXFLAGS += -save-temps
endif
# --- Debug info ? ---
ifeq ($(strip $(bDEBUG)), 1)
CFLAGS += -DDEBUG -g
CXXFLAGS += -DDEBUG -g
ASFLAGS += -DDEBUG -g
LDFLAGS += -g
else
CFLAGS += -DNDEBUG
CXXFLAGS += -DNDEBUG
ASFLAGS += -DNDEBUG
endif
# === BUILD PROC ======================================================
ifneq ($(BUILD),$(notdir $(CURDIR)))
# Still in main dir:
# * Define/export some extra variables
# * Invoke this file again from the build dir
# PONDER: what happens if BUILD == "" ?
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := \
$(foreach dir, $(SRCDIRS) , $(CURDIR)/$(dir)) \
$(foreach dir, $(DATADIRS), $(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
# --- List source and data files ---
CFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir, $(DATADIRS), $(notdir $(wildcard $(dir)/*.*)))
# --- Set linker depending on C++ file existence ---
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
# --- Define object file list ---
export OFILES := $(addsuffix .o, $(BINFILES)) \
$(CFILES:.c=.o) $(CPPFILES:.cpp=.o) \
$(SFILES:.s=.o)
# --- Create include and library search paths ---
export INCLUDE := $(foreach dir,$(INCDIRS),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := -L$(CURDIR) $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
# --- Create BUILD if necessary, and run this makefile from there ---
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
all : $(BUILD)
clean:
@echo clean ...
@rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
else # If we're here, we should be in the BUILD dir
DEPENDS := $(OFILES:.o=.d)
# --- Main targets ----
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
-include $(DEPENDS)
endif # End BUILD switch
# --- More targets ----------------------------------------------------
.PHONY: clean rebuild start
rebuild: clean $(BUILD)
start:
start "$(TARGET).gba"
restart: rebuild start
# EOF

View File

@ -0,0 +1,13 @@
const { SerialPort, ReadlineParser } = require('serialport') // "^12.0.0"
var serialPort = new SerialPort({
path: "COM9", // (*nix: /dev/ttyACMX, Windows: COMX)
baudRate: 9600
});
const parser = serialPort.pipe(new ReadlineParser());
parser.on('data', (it) => console.log(it))
setInterval(() => {
serialPort.write('<< node\n')
}, 1000);

View File

@ -0,0 +1,96 @@
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
// (0) Include the header
#include "../../../lib/LinkUART.hpp"
void log(std::string text);
inline void VBLANK() {}
std::string received = "";
u32 lines = 0;
// (1) Create a LinkUART instance
LinkUART* linkUART = new LinkUART();
void init() {
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
// (2) Add the interrupt service routines
interrupt_init();
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, LINK_UART_ISR_SERIAL);
interrupt_enable(INTR_SERIAL);
}
int main() {
init();
bool firstTransfer = false;
while (true) {
std::string output = "LinkUART_demo (v6.3.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkUART->isActive()) {
firstTransfer = true;
output += "START: Start listening...\n";
output += "\n(stop: press L+R)\n";
if ((keys & KEY_START) | (keys & KEY_SELECT)) {
// (3) Initialize the library
linkUART->activate();
received = "";
lines = 0;
}
} else {
// Title
if (firstTransfer) {
log(output + "Waiting...");
firstTransfer = false;
}
// (4) Send/read bytes
char buffer[256];
if (linkUART->readLine(buffer, []() {
u16 keys = ~REG_KEYS & KEY_ANY;
return (keys & KEY_L) && (keys & KEY_R);
})) {
lines++;
if (lines >= 18) {
lines = 0;
received = "";
}
received += std::string(buffer);
linkUART->sendLine(">> gba", []() {
u16 keys = ~REG_KEYS & KEY_ANY;
return (keys & KEY_L) && (keys & KEY_R);
});
}
output += received;
// Cancel
if ((keys & KEY_L) && (keys & KEY_R)) {
linkUART->deactivate();
}
}
// Print
VBlankIntrWait();
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

View File

@ -21,7 +21,7 @@ void init() {
int main() {
init();
log("LinkUniversal_basic (v6.2.3)\n\n\nPress A to start\n\n\nhold LEFT on "
log("LinkUniversal_basic (v6.3.0)\n\n\nPress A to start\n\n\nhold LEFT on "
"start:\n -> force cable\n\nhold RIGHT on start:\n -> force "
"wireless\n\nhold UP on start:\n -> force wireless server\n\nhold DOWN "
"on start:\n -> force wireless client\n\nhold B on start:\n -> set 2 "

View File

@ -133,7 +133,7 @@ void MultibootScene::load() {
log("---");
log("LinkWirelessMultiboot demo");
log(" (v6.2.3)");
log(" (v6.3.0)");
log("");
if (fs == NULL) {
log("! GBFS file not found");

View File

@ -47,7 +47,7 @@ int main() {
start:
// Options
log("LinkWireless_demo (v6.2.3)\n\n\n\nPress A to start\n\n\n\n\nhold LEFT "
log("LinkWireless_demo (v6.3.0)\n\n\n\nPress A to start\n\n\n\n\nhold LEFT "
"on start:\n -> disable forwarding\n\nhold UP on start:\n -> disable "
"retransmission\n\nhold B on start:\n -> set 2 players\n\nhold START on "
"start:\n -> async ACK");

View File

@ -25,6 +25,16 @@ make rebuild
cp LinkGPIO_demo.gba ../
cd ..
cd LinkPS2Keyboard_demo/
make rebuild
cp LinkPS2Keyboard_demo.gba ../
cd ..
cd LinkPS2Mouse_demo/
make rebuild
cp LinkPS2Mouse_demo.gba ../
cd ..
cd LinkRawCable_demo/
make rebuild
cp LinkRawCable_demo.gba ../
@ -42,6 +52,11 @@ make rebuild
cp LinkSPI_demo.gba ../
cd ..
cd LinkUART_demo/
make rebuild
cp LinkUART_demo.gba ../
cd ..
cd LinkUniversal_basic/
sed -i -e "s/\/\/ #define LINK_WIRELESS_PUT_ISR_IN_IWRAM/#define LINK_WIRELESS_PUT_ISR_IN_IWRAM/g" ../../lib/LinkWireless.hpp
make rebuild

View File

@ -67,7 +67,7 @@
#define LINK_CABLE_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_CABLE_BARRIER asm volatile("" ::: "memory")
static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.2.3";
static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.3.0";
void LINK_CABLE_ISR_VBLANK();
void LINK_CABLE_ISR_SERIAL();

View File

@ -55,7 +55,7 @@
return error(FAILURE_DURING_HANDSHAKE);
static volatile char LINK_CABLE_MULTIBOOT_VERSION[] =
"LinkCableMultiboot/v6.2.3";
"LinkCableMultiboot/v6.3.0";
const u8 LINK_CABLE_MULTIBOOT_CLIENT_IDS[] = {0b0010, 0b0100, 0b1000};

View File

@ -36,7 +36,7 @@
else \
REG &= ~(1 << BIT);
static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.2.3";
static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.3.0";
const u8 LINK_GPIO_DATA_BITS[] = {2, 3, 1, 0};
const u8 LINK_GPIO_DIRECTION_BITS[] = {6, 7, 5, 4};

144
lib/LinkPS2Keyboard.hpp Normal file
View File

@ -0,0 +1,144 @@
#ifndef LINK_PS2_KEYBOARD_H
#define LINK_PS2_KEYBOARD_H
// --------------------------------------------------------------------------
// A PS/2 Keyboard Adapter for the GBA
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 event) {
// // handle event (check example scan codes below)
// });
// - 2) Add the required interrupt service routines: (*)
// irq_init(NULL);
// irq_add(II_VBLANK, LINK_PS2_KEYBOARD_ISR_VBLANK);
// irq_add(II_SERIAL, LINK_PS2_KEYBOARD_ISR_SERIAL);
// - 3) Initialize the library with:
// linkPS2Keyboard->activate();
// - 4) Handle events in the callback sent to LinkPS2Keyboard's constructor!
// --------------------------------------------------------------------------
// ____________
// | Pinout |
// |PS/2 --- GBA|
// |------------|
// |CLOCK -> SI |
// |DATA --> SO |
// |VCC ---> VCC|
// |GND ---> GND|
// --------------------------------------------------------------------------
#include <tonc_core.h>
#include <functional>
// Example Scan Codes: (Num Lock OFF)
#define LINK_PS2_KEYBOARD_KEY_Z 26 // Z
#define LINK_PS2_KEYBOARD_KEY_Q 21 // Q
#define LINK_PS2_KEYBOARD_KEY_S 27 // S
#define LINK_PS2_KEYBOARD_KEY_E 36 // E
#define LINK_PS2_KEYBOARD_KEY_C 33 // C
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_1 105 // Numpad 1
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_7 108 // Numpad 7
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_5 115 // Numpad 5
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_9 125 // Numpad 9
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_3 122 // Numpad 3
#define LINK_PS2_KEYBOARD_KEY_ENTER 90 // Enter
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_PLUS 121 // Numpad +
#define LINK_PS2_KEYBOARD_KEY_BACKSPACE 102 // Backspace
#define LINK_PS2_KEYBOARD_KEY_NUMPAD_MINUS 123 // Numpad -
#define LINK_PS2_KEYBOARD_KEY_LEFT 107 // Left
#define LINK_PS2_KEYBOARD_KEY_RIGHT 116 // Right
#define LINK_PS2_KEYBOARD_KEY_UP 117 // Up
#define LINK_PS2_KEYBOARD_KEY_ESC 118 // ESC
#define LINK_PS2_KEYBOARD_KEY_SUPR 113 // Supr
// ---
#define LINK_PS2_KEYBOARD_KEY_RELEASE 240 // Triggered before each key release
#define LINK_PS2_KEYBOARD_KEY_SPECIAL 224 // Triggered before special keys
#define LINK_PS2_KEYBOARD_SI_DIRECTION 0b1000000
#define LINK_PS2_KEYBOARD_SO_DIRECTION 0b10000000
#define LINK_PS2_KEYBOARD_SI_DATA 0b100
#define LINK_PS2_KEYBOARD_SO_DATA 0b1000
#define LINK_PS2_KEYBOARD_TIMEOUT_FRAMES 15 // (~250ms)
static volatile char LINK_PS2_KEYBOARD_VERSION[] = "LinkPS2Keyboard/v6.3.0";
class LinkPS2Keyboard {
public:
explicit LinkPS2Keyboard(std::function<void(u8 event)> onEvent) {
this->onEvent = onEvent;
}
bool isActive() { return isEnabled; }
void activate() {
deactivate();
REG_RCNT = 0b1000000100000000; // General Purpose Mode + SI interrupts
REG_SIOCNT = 0; // Unused
bitcount = 0;
incoming = 0;
prevFrame = 0;
frameCounter = 0;
isEnabled = true;
}
void deactivate() {
isEnabled = false;
REG_RCNT = 0b1000000000000000; // General Purpose Mode
REG_SIOCNT = 0; // Unused
}
void _onVBlank() { frameCounter++; }
void _onSerial() {
u8 val = (REG_RCNT & LINK_PS2_KEYBOARD_SO_DATA) != 0;
u32 nowFrame = frameCounter;
if (nowFrame - prevFrame > LINK_PS2_KEYBOARD_TIMEOUT_FRAMES) {
bitcount = 0;
incoming = 0;
}
prevFrame = nowFrame;
u8 n = bitcount - 1;
if (n <= 7)
incoming |= (val << n);
bitcount++;
if (bitcount == 11) {
onEvent(incoming);
bitcount = 0;
incoming = 0;
}
}
private:
bool isEnabled = false;
uint8_t bitcount = 0;
uint8_t incoming = 0;
uint32_t prevFrame = 0;
u32 frameCounter = 0;
std::function<void(u8 event)> onEvent;
};
extern LinkPS2Keyboard* linkPS2Keyboard;
inline void LINK_PS2_KEYBOARD_ISR_VBLANK() {
if (!linkPS2Keyboard->isActive())
return;
linkPS2Keyboard->_onVBlank();
}
inline void LINK_PS2_KEYBOARD_ISR_SERIAL() {
if (!linkPS2Keyboard->isActive())
return;
linkPS2Keyboard->_onSerial();
}
#endif // LINK_PS2_KEYBOARD_H

238
lib/LinkPS2Mouse.hpp Normal file
View File

@ -0,0 +1,238 @@
#ifndef LINK_PS2_MOUSE_H
#define LINK_PS2_MOUSE_H
// --------------------------------------------------------------------------
// A PS/2 Mouse Adapter for the GBA
// (Based on https://github.com/kristopher/PS2-Mouse-Arduino, MIT license)
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkPS2Mouse* linkPS2Mouse = new LinkPS2Mouse();
// - 2) Add the required interrupt service routines: (*)
// irq_init(NULL);
// irq_add(II_TIMER2, NULL);
// - 3) Initialize the library with:
// linkPS2Mouse->activate();
// - 4) Get a report:
// int data[3];
// linkPS2Mouse->report(data);
// if ((data[0] & LINK_PS2_MOUSE_LEFT_CLICK) != 0)
// ; // handle LEFT click
// data[1] // X movement
// data[2] // Y movement
// --------------------------------------------------------------------------
// ____________
// | Pinout |
// |PS/2 --- GBA|
// |------------|
// |CLOCK -> SI |
// |DATA --> SO |
// |VCC ---> VCC|
// |GND ---> GND|
// --------------------------------------------------------------------------
#include <tonc_bios.h>
#include <tonc_core.h>
#define LINK_PS2_MOUSE_LEFT_CLICK 0b001
#define LINK_PS2_MOUSE_RIGHT_CLICK 0b010
#define LINK_PS2_MOUSE_MIDDLE_CLICK 0b100
#define LINK_PS2_MOUSE_SI_DIRECTION 0b1000000
#define LINK_PS2_MOUSE_SO_DIRECTION 0b10000000
#define LINK_PS2_MOUSE_SI_DATA 0b100
#define LINK_PS2_MOUSE_SO_DATA 0b1000
#define LINK_PS2_MOUSE_TO_TICKS 17
const u16 LINK_PS2_MOUSE_IRQ_IDS[] = {IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2,
IRQ_TIMER3};
static volatile char LINK_PS2_MOUSE_VERSION[] = "LinkPS2Mouse/v6.3.0";
class LinkPS2Mouse {
public:
explicit LinkPS2Mouse(u8 waitTimerId) { this->waitTimerId = waitTimerId; }
bool isActive() { return isEnabled; }
void activate() {
deactivate();
setClockHigh();
setDataHigh();
waitMilliseconds(20);
write(0xff); // send reset to the mouse
readByte(); // read ack byte
waitMilliseconds(20); // not sure why this needs the delay
readByte(); // blank
readByte(); // blank
waitMilliseconds(20); // not sure why this needs the delay
enableDataReporting(); // tell the mouse to start sending data
waitMicroseconds(100);
isEnabled = true;
}
void deactivate() {
isEnabled = false;
REG_RCNT = 0b1000000000000000; // General Purpose Mode
REG_SIOCNT = 0; // Unused
}
void report(int (&data)[3]) {
write(0xeb); // send read data
readByte(); // read ack byte
data[0] = readByte(); // status bit
data[1] = readMovementX(data[0]); // X movement packet
data[2] = readMovementY(data[0]); // Y movement packet
}
private:
u8 waitTimerId;
volatile bool isEnabled = false;
void enableDataReporting() {
write(0xf4); // send enable data reporting
readByte(); // read ack byte
}
s16 readMovementX(int status) {
s16 x = readByte();
if ((status & (1 << 4)) != 0)
// negative
for (int i = 8; i < 16; i++)
x |= 1 << i;
return x;
}
s16 readMovementY(int status) {
s16 y = readByte();
if ((status & (1 << 5)) != 0)
// negative
for (int i = 8; i < 16; i++)
y |= 1 << i;
return y;
}
void write(u8 data) {
u8 parity = 1;
setDataHigh();
setClockHigh();
waitMicroseconds(300);
setClockLow();
waitMicroseconds(300);
setDataLow();
waitMicroseconds(10);
setClockHigh(); // (start bit)
while (getClock())
; // wait for mouse to take control of clock
// clock is low, and we are clear to send data
for (u32 i = 0; i < 8; i++) {
if (data & 0x01)
setDataHigh();
else
setDataLow();
// wait for clock cycle
while (!getClock())
;
while (getClock())
;
parity = parity ^ (data & 0x01);
data = data >> 1;
}
// parity
if (parity)
setDataHigh();
else
setDataLow();
while (!getClock())
;
while (getClock())
;
setDataHigh();
waitMicroseconds(50);
while (getClock())
;
while (!getClock() || !getData())
; // wait for mouse to switch modes
setClockLow(); // put a hold on the incoming data.
}
u8 readByte() {
u8 data = 0;
setClockHigh();
setDataHigh();
waitMicroseconds(50);
while (getClock())
;
while (!getClock())
; // eat start bit
for (int i = 0; i < 8; i++) {
data |= readBit() << i;
}
readBit(); // parity bit
readBit(); // stop bit should be 1
setClockLow();
return data;
}
bool readBit() {
while (getClock())
;
bool bit = getData();
while (!getClock())
;
return bit;
}
void waitMilliseconds(u16 milliseconds) {
u16 ticksOf1024Cycles = milliseconds * LINK_PS2_MOUSE_TO_TICKS;
REG_TM[waitTimerId].start = -ticksOf1024Cycles;
REG_TM[waitTimerId].cnt = TM_ENABLE | TM_IRQ | TM_FREQ_1024;
IntrWait(1, LINK_PS2_MOUSE_IRQ_IDS[waitTimerId]);
REG_TM[waitTimerId].cnt = 0;
}
void waitMicroseconds(u16 microseconds) {
u16 cycles = microseconds * LINK_PS2_MOUSE_TO_TICKS;
REG_TM[waitTimerId].start = -cycles;
REG_TM[waitTimerId].cnt = TM_ENABLE | TM_IRQ | TM_FREQ_1;
IntrWait(1, LINK_PS2_MOUSE_IRQ_IDS[waitTimerId]);
REG_TM[waitTimerId].cnt = 0;
}
bool getClock() {
REG_RCNT &= ~LINK_PS2_MOUSE_SI_DIRECTION;
return (REG_RCNT & LINK_PS2_MOUSE_SI_DATA) >> 0;
}
bool getData() {
REG_RCNT &= ~LINK_PS2_MOUSE_SO_DIRECTION;
return (REG_RCNT & LINK_PS2_MOUSE_SO_DATA) >> 1;
}
void setClockHigh() {
REG_RCNT |= LINK_PS2_MOUSE_SI_DIRECTION;
REG_RCNT |= LINK_PS2_MOUSE_SI_DATA;
}
void setClockLow() {
REG_RCNT |= LINK_PS2_MOUSE_SI_DIRECTION;
REG_RCNT &= ~LINK_PS2_MOUSE_SI_DATA;
}
void setDataHigh() {
REG_RCNT |= LINK_PS2_MOUSE_SO_DIRECTION;
REG_RCNT |= LINK_PS2_MOUSE_SO_DATA;
}
void setDataLow() {
REG_RCNT |= LINK_PS2_MOUSE_SO_DIRECTION;
REG_RCNT &= ~LINK_PS2_MOUSE_SO_DATA;
}
};
extern LinkPS2Mouse* linkPS2Mouse;
#endif // LINK_PS2_MOUSE_H

View File

@ -55,7 +55,7 @@
} \
}
static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v6.2.3";
static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v6.3.0";
class LinkRawCable {
public:

View File

@ -66,7 +66,7 @@
#define LINK_RAW_WIRELESS_COMMAND_WAIT 0x27
#define LINK_RAW_WIRELESS_COMMAND_BYE 0x3d
static volatile char LINK_RAW_WIRELESS_VERSION[] = "LinkRawWireless/v6.2.3";
static volatile char LINK_RAW_WIRELESS_VERSION[] = "LinkRawWireless/v6.3.0";
const u16 LINK_RAW_WIRELESS_LOGIN_PARTS[] = {
0x494e, 0x494e, 0x544e, 0x544e, 0x4e45, 0x4e45, 0x4f44, 0x4f44, 0x8001};

View File

@ -76,7 +76,7 @@
#define LINK_SPI_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_SPI_BIT_GENERAL_PURPOSE_HIGH 15
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.2.3";
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.3.0";
const u32 LINK_SPI_MASK_CLEAR_SO_BIT = ~(1 << LINK_SPI_BIT_SO);
const u32 LINK_SPI_MASK_SET_START_BIT = (1 << LINK_SPI_BIT_START);

327
lib/LinkUART.hpp Normal file
View File

@ -0,0 +1,327 @@
#ifndef LINK_UART_H
#define LINK_UART_H
// --------------------------------------------------------------------------
// An UART handler for the Link Port (8N1, 7N1, 8E1, 7E1, 8O1, 7E1).
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkUART* linkUART = new LinkUART();
// - 2) Add the required interrupt service routines: (*)
// irq_init(NULL);
// irq_add(II_SERIAL, LINK_UART_ISR_SERIAL);
// - 3) Initialize the library with:
// linkUART->activate();
// - 4) Send/read data by using:
// linkUART->send(0xFA);
// linkUART->sendLine("hello");
// u8 newByte = linkUART->read();
// char newString[256];
// linkUART->readLine(newString);
// --------------------------------------------------------------------------
// (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug.
// That causes packet loss. You REALLY want to use libugba's instead.
// (see examples)
// --------------------------------------------------------------------------
#include <tonc_core.h>
// Buffer size
#define LINK_UART_QUEUE_SIZE 256
#define LINK_UART_BIT_CTS 2
#define LINK_UART_BIT_PARITY_CONTROL 3
#define LINK_UART_BIT_SEND_DATA_FLAG 4
#define LINK_UART_BIT_RECEIVE_DATA_FLAG 5
#define LINK_UART_BIT_ERROR_FLAG 6
#define LINK_UART_BIT_DATA_LENGTH 7
#define LINK_UART_BIT_FIFO_ENABLE 8
#define LINK_UART_BIT_PARITY_ENABLE 9
#define LINK_UART_BIT_SEND_ENABLE 10
#define LINK_UART_BIT_RECEIVE_ENABLE 11
#define LINK_UART_BIT_UART_1 12
#define LINK_UART_BIT_UART_2 13
#define LINK_UART_BIT_IRQ 14
#define LINK_UART_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_UART_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_UART_BARRIER asm volatile("" ::: "memory")
static volatile char LINK_UART_VERSION[] = "LinkUART/v6.3.0";
void LINK_UART_ISR_SERIAL();
class LinkUART {
public:
enum BaudRate {
BAUD_RATE_0, // 9600 bps
BAUD_RATE_1, // 38400 bps
BAUD_RATE_2, // 57600 bps
BAUD_RATE_3 // 115200 bps
};
enum DataSize { SIZE_7_BITS, SIZE_8_BITS };
enum Parity { NO, EVEN, ODD };
explicit LinkUART() {
this->config.baudRate = BAUD_RATE_0;
this->config.dataSize = SIZE_8_BITS;
this->config.parity = NO;
this->config.useCTS = false;
}
bool isActive() { return isEnabled; }
void activate(BaudRate baudRate = BAUD_RATE_0,
DataSize dataSize = SIZE_8_BITS,
Parity parity = NO,
bool useCTS = false) {
this->config.baudRate = baudRate;
this->config.dataSize = dataSize;
this->config.parity = parity;
this->config.useCTS = false;
LINK_UART_BARRIER;
isEnabled = false;
LINK_UART_BARRIER;
reset();
LINK_UART_BARRIER;
isEnabled = true;
LINK_UART_BARRIER;
}
void deactivate() {
LINK_UART_BARRIER;
isEnabled = false;
LINK_UART_BARRIER;
resetState();
stop();
}
void sendLine(const char* string) {
sendLine(string, []() { return false; });
}
template <typename F>
void sendLine(const char* string, F cancel) {
for (u32 i = 0; string[i] != '\0'; i++) {
while (!canSend())
if (cancel())
return;
send(string[i]);
}
send('\n');
}
bool readLine(char* string, u32 limit = LINK_UART_QUEUE_SIZE) {
return readLine(
string, []() { return false; }, limit);
}
template <typename F>
bool readLine(char* string, F cancel, u32 limit = LINK_UART_QUEUE_SIZE) {
u32 readBytes = 0;
char lastChar = '\0';
bool aborted = false;
while (lastChar != '\n') {
while (!canRead())
if (cancel())
return false;
string[readBytes++] = lastChar = read();
if (readBytes >= limit - 1) {
aborted = true;
break;
}
}
string[readBytes] = '\0';
return !aborted && readBytes > 1;
}
void send(const u8* buffer, u32 size, u32 offset = 0) {
for (u32 i = 0; i < size; i++)
send(buffer[offset + i]);
}
u32 read(u8* buffer, u32 size, u32 offset = 0) {
for (u32 i = 0; i < size; i++) {
if (!canRead())
return i;
buffer[offset + i] = read();
}
return size;
}
bool canRead() { return !incomingQueue.isEmpty(); }
bool canSend() { return !outgoingQueue.isFull(); }
u32 availableForRead() { return incomingQueue.size(); }
u32 availableForSend() { return LINK_UART_QUEUE_SIZE - outgoingQueue.size(); }
u8 read() {
LINK_UART_BARRIER;
isReading = true;
LINK_UART_BARRIER;
u8 data = incomingQueue.pop();
LINK_UART_BARRIER;
isReading = false;
LINK_UART_BARRIER;
return data;
}
void send(u8 data) {
LINK_UART_BARRIER;
isAdding = true;
LINK_UART_BARRIER;
outgoingQueue.push(data);
LINK_UART_BARRIER;
isAdding = false;
LINK_UART_BARRIER;
}
void _onSerial() {
if (!isEnabled || hasError())
return;
if (!isReading && canReceive())
incomingQueue.push((u8)REG_SIODATA8);
if (!isAdding && canTransfer() && needsTransfer())
REG_SIODATA8 = outgoingQueue.pop();
}
private:
class U8Queue {
public:
void push(u8 item) {
if (isFull())
pop();
rear = (rear + 1) % LINK_UART_QUEUE_SIZE;
arr[rear] = item;
count++;
}
u16 pop() {
if (isEmpty())
return 0;
auto x = arr[front];
front = (front + 1) % LINK_UART_QUEUE_SIZE;
count--;
return x;
}
u16 peek() {
if (isEmpty())
return 0;
return arr[front];
}
void clear() {
front = count = 0;
rear = -1;
}
u32 size() { return count; }
bool isEmpty() { return size() == 0; }
bool isFull() { return size() == LINK_UART_QUEUE_SIZE; }
private:
u8 arr[LINK_UART_QUEUE_SIZE];
vs32 front = 0;
vs32 rear = -1;
vu32 count = 0;
};
struct Config {
BaudRate baudRate;
DataSize dataSize;
Parity parity;
bool useCTS;
};
Config config;
U8Queue incomingQueue;
U8Queue outgoingQueue;
volatile bool isEnabled = false;
volatile bool isReading = false;
volatile bool isAdding = false;
bool canReceive() { return !isBitHigh(LINK_UART_BIT_RECEIVE_DATA_FLAG); }
bool canTransfer() { return !isBitHigh(LINK_UART_BIT_SEND_DATA_FLAG); }
bool hasError() { return isBitHigh(LINK_UART_BIT_ERROR_FLAG); }
bool needsTransfer() { return outgoingQueue.size() > 0; }
void reset() {
resetState();
stop();
start();
}
void resetState() {
incomingQueue.clear();
outgoingQueue.clear();
}
void stop() { setGeneralPurposeMode(); }
void start() {
setUARTMode();
if (config.dataSize == SIZE_8_BITS)
set8BitData();
if (config.parity > NO) {
if (config.parity == ODD)
setOddParity();
setParityOn();
}
if (config.useCTS)
setCTSOn();
setFIFOOn();
setInterruptsOn();
setSendOn();
setReceiveOn();
}
void set8BitData() { setBitHigh(LINK_UART_BIT_DATA_LENGTH); }
void setParityOn() { setBitHigh(LINK_UART_BIT_PARITY_ENABLE); }
void setOddParity() { setBitHigh(LINK_UART_BIT_PARITY_CONTROL); }
void setCTSOn() { setBitHigh(LINK_UART_BIT_CTS); }
void setFIFOOn() { setBitHigh(LINK_UART_BIT_FIFO_ENABLE); }
void setInterruptsOn() { setBitHigh(LINK_UART_BIT_IRQ); }
void setSendOn() { setBitHigh(LINK_UART_BIT_SEND_ENABLE); }
void setReceiveOn() { setBitHigh(LINK_UART_BIT_RECEIVE_ENABLE); }
void setUARTMode() {
REG_RCNT = REG_RCNT & ~(1 << LINK_UART_BIT_GENERAL_PURPOSE_HIGH);
REG_SIOCNT = (1 << LINK_UART_BIT_UART_1) | (1 << LINK_UART_BIT_UART_2);
REG_SIOCNT |= config.baudRate;
REG_SIOMLT_SEND = 0;
}
void setGeneralPurposeMode() {
REG_RCNT = (REG_RCNT & ~(1 << LINK_UART_BIT_GENERAL_PURPOSE_LOW)) |
(1 << LINK_UART_BIT_GENERAL_PURPOSE_HIGH);
}
bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { REG_SIOCNT |= 1 << bit; }
void setBitLow(u8 bit) { REG_SIOCNT &= ~(1 << bit); }
};
extern LinkUART* linkUART;
inline void LINK_UART_ISR_SERIAL() {
linkUART->_onSerial();
}
#endif // LINK_UART_H

View File

@ -67,7 +67,7 @@
#define LINK_UNIVERSAL_SERVE_WAIT_FRAMES 60
#define LINK_UNIVERSAL_SERVE_WAIT_FRAMES_RANDOM 30
static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.2.3";
static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.3.0";
void LINK_UNIVERSAL_ISR_VBLANK();
void LINK_UNIVERSAL_ISR_SERIAL();

View File

@ -139,7 +139,7 @@
if (!reset()) \
return false;
static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v6.2.3";
static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v6.3.0";
void LINK_WIRELESS_ISR_VBLANK();
void LINK_WIRELESS_ISR_SERIAL();

View File

@ -73,7 +73,7 @@ const u8 LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_OFFSET = 4;
const u8 LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_SIZE = 12;
static volatile char LINK_WIRELESS_MULTIBOOT_VERSION[] =
"LinkWirelessMultiboot/v6.2.3";
"LinkWirelessMultiboot/v6.3.0";
class LinkWirelessMultiboot {
public:

View File

@ -34,7 +34,7 @@
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT)
static volatile char LINK_WIRELESS_OPEN_SDK_VERSION[] =
"LinkWirelessOpenSDK/v6.2.3";
"LinkWirelessOpenSDK/v6.3.0";
class LinkWirelessOpenSDK {
public: