mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-04-25 16:23:41 -05:00
Merge pull request #20 from afska/v6.3.0
⏱️ LinkUART, 🖱️ LinkPS2Mouse, ⌨️ LinkPS2Keyboard
This commit is contained in:
commit
bc9495130a
80
README.md
80
README.md
|
|
@ -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`.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

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

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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("");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
284
examples/LinkPS2Keyboard_demo/Makefile
Normal file
284
examples/LinkPS2Keyboard_demo/Makefile
Normal 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
|
||||
70
examples/LinkPS2Keyboard_demo/src/main.cpp
Normal file
70
examples/LinkPS2Keyboard_demo/src/main.cpp
Normal 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());
|
||||
}
|
||||
284
examples/LinkPS2Mouse_demo/Makefile
Normal file
284
examples/LinkPS2Mouse_demo/Makefile
Normal 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
|
||||
64
examples/LinkPS2Mouse_demo/src/main.cpp
Normal file
64
examples/LinkPS2Mouse_demo/src/main.cpp
Normal 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());
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
284
examples/LinkUART_demo/Makefile
Normal file
284
examples/LinkUART_demo/Makefile
Normal 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
|
||||
13
examples/LinkUART_demo/Test.js
Normal file
13
examples/LinkUART_demo/Test.js
Normal 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);
|
||||
96
examples/LinkUART_demo/src/main.cpp
Normal file
96
examples/LinkUART_demo/src/main.cpp
Normal 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());
|
||||
}
|
||||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
144
lib/LinkPS2Keyboard.hpp
Normal 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
238
lib/LinkPS2Mouse.hpp
Normal 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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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
327
lib/LinkUART.hpp
Normal 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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user