From 7bafeedd30bcfc03503ad64cfa6799a4d5b98909 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sat, 20 Apr 2024 09:45:25 -0300 Subject: [PATCH 01/10] Adding LinkUART --- examples/LinkUART_demo/Makefile | 284 ++++++++++++++++++++++++++++ examples/LinkUART_demo/src/main.cpp | 85 +++++++++ lib/LinkUART.hpp | 240 +++++++++++++++++++++++ 3 files changed, 609 insertions(+) create mode 100644 examples/LinkUART_demo/Makefile create mode 100644 examples/LinkUART_demo/src/main.cpp create mode 100644 lib/LinkUART.hpp diff --git a/examples/LinkUART_demo/Makefile b/examples/LinkUART_demo/Makefile new file mode 100644 index 00000000..7cff4240 --- /dev/null +++ b/examples/LinkUART_demo/Makefile @@ -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 $( `(echo $(> `(echo $(> `(echo $( $(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 diff --git a/examples/LinkUART_demo/src/main.cpp b/examples/LinkUART_demo/src/main.cpp new file mode 100644 index 00000000..652dc2f9 --- /dev/null +++ b/examples/LinkUART_demo/src/main.cpp @@ -0,0 +1,85 @@ +#include +#include +#include "../../_lib/interrupt.h" + +// (0) Include the header +#include "../../../lib/LinkUART.hpp" + +void log(std::string text); +inline void VBLANK() {} + +std::string buffer = ""; + +// (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.2.3)\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(); + buffer = ""; + } + } else { + // Title + output += "[uart]\n"; + if (firstTransfer) { + log(output + "Waiting..."); + firstTransfer = false; + } + + // (4) Send/read bytes + if (linkUART->canRead()) { + u8 newByte = linkUART->read(); + while (!linkUART->canSend()) + ; + linkUART->send('z'); + buffer += (char)newByte; + if (buffer.size() > 250) + buffer = ""; + } + output += buffer; + + // 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()); +} diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp new file mode 100644 index 00000000..822caee2 --- /dev/null +++ b/lib/LinkUART.hpp @@ -0,0 +1,240 @@ +#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 bytes by using: +// if (linkUART->canSend()) +// linkUART->send(0xFA); +// if (linkUART->canRead()) +// u8 newByte = linkUART->read(); +// -------------------------------------------------------------------------- +// (*) 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 +#include + +// 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.2.3"; + +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(); + } + + bool canRead() { return !queue.isEmpty(); } + u8 read() { return queue.pop(); } + + bool canSend() { return !isBitHigh(LINK_UART_BIT_SEND_DATA_FLAG); } + void send(u8 data) { REG_SIODATA8 = data; } + + void _onSerial() { + if (!isEnabled) + return; + + if (hasError()) { + reset(); + return; + } + + if (canReceive()) + queue.push((u8)REG_SIODATA8); + } + + 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 queue; + volatile bool isEnabled = false; + + bool hasError() { return isBitHigh(LINK_UART_BIT_ERROR_FLAG); } + bool canReceive() { return !isBitHigh(LINK_UART_BIT_RECEIVE_DATA_FLAG); } + + void reset() { + resetState(); + stop(); + start(); + } + + void resetState() { queue.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 From d0ed54e7d47dedf8d4634b091c9088f127d11a53 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 06:52:50 -0300 Subject: [PATCH 02/10] Adding locks for concurrency --- lib/LinkUART.hpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp index 822caee2..dd236de1 100644 --- a/lib/LinkUART.hpp +++ b/lib/LinkUART.hpp @@ -100,21 +100,30 @@ class LinkUART { } bool canRead() { return !queue.isEmpty(); } - u8 read() { return queue.pop(); } - bool canSend() { return !isBitHigh(LINK_UART_BIT_SEND_DATA_FLAG); } + bool hasError() { return isBitHigh(LINK_UART_BIT_ERROR_FLAG); } + + u8 read() { + LINK_UART_BARRIER; + isReading = true; + LINK_UART_BARRIER; + + u8 data = queue.pop(); + + LINK_UART_BARRIER; + isReading = true; + LINK_UART_BARRIER; + + return data; + } + void send(u8 data) { REG_SIODATA8 = data; } void _onSerial() { - if (!isEnabled) + if (!isEnabled || hasError()) return; - if (hasError()) { - reset(); - return; - } - - if (canReceive()) + if (canReceive() && !isReading) queue.push((u8)REG_SIODATA8); } @@ -174,8 +183,9 @@ class LinkUART { Config config; U8Queue queue; volatile bool isEnabled = false; + volatile bool isReading = false; + // TODO: Make it similar to LinkCable (timer + outgoingQueue) - bool hasError() { return isBitHigh(LINK_UART_BIT_ERROR_FLAG); } bool canReceive() { return !isBitHigh(LINK_UART_BIT_RECEIVE_DATA_FLAG); } void reset() { From f6e1ff3cc35e96f94376dd3802499b17af58b62a Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 17:58:41 -0300 Subject: [PATCH 03/10] Updating UART demo --- examples/LinkUART_demo/Test.js | 13 ++++ examples/LinkUART_demo/src/main.cpp | 35 +++++---- lib/LinkUART.hpp | 108 ++++++++++++++++++++++++---- 3 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 examples/LinkUART_demo/Test.js diff --git a/examples/LinkUART_demo/Test.js b/examples/LinkUART_demo/Test.js new file mode 100644 index 00000000..d9b7d1af --- /dev/null +++ b/examples/LinkUART_demo/Test.js @@ -0,0 +1,13 @@ +const { SerialPort, ReadlineParser } = require('serialport') + +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); \ No newline at end of file diff --git a/examples/LinkUART_demo/src/main.cpp b/examples/LinkUART_demo/src/main.cpp index 652dc2f9..724c3805 100644 --- a/examples/LinkUART_demo/src/main.cpp +++ b/examples/LinkUART_demo/src/main.cpp @@ -8,7 +8,8 @@ void log(std::string text); inline void VBLANK() {} -std::string buffer = ""; +std::string received = ""; +u32 lines = 0; // (1) Create a LinkUART instance LinkUART* linkUART = new LinkUART(); @@ -42,27 +43,37 @@ int main() { if ((keys & KEY_START) | (keys & KEY_SELECT)) { // (3) Initialize the library linkUART->activate(); - buffer = ""; + received = ""; + lines = 0; } } else { // Title - output += "[uart]\n"; if (firstTransfer) { log(output + "Waiting..."); firstTransfer = false; } // (4) Send/read bytes - if (linkUART->canRead()) { - u8 newByte = linkUART->read(); - while (!linkUART->canSend()) - ; - linkUART->send('z'); - buffer += (char)newByte; - if (buffer.size() > 250) - buffer = ""; + 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 += buffer; + + output += received; // Cancel if ((keys & KEY_L) && (keys & KEY_R)) { diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp index dd236de1..436054f1 100644 --- a/lib/LinkUART.hpp +++ b/lib/LinkUART.hpp @@ -13,10 +13,11 @@ // - 3) Initialize the library with: // linkUART->activate(); // - 4) Send/read bytes by using: -// if (linkUART->canSend()) -// linkUART->send(0xFA); -// if (linkUART->canRead()) -// u8 newByte = linkUART->read(); +// 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. @@ -99,32 +100,102 @@ class LinkUART { stop(); } - bool canRead() { return !queue.isEmpty(); } - bool canSend() { return !isBitHigh(LINK_UART_BIT_SEND_DATA_FLAG); } - bool hasError() { return isBitHigh(LINK_UART_BIT_ERROR_FLAG); } + void sendLine(const char* string) { + sendLine(string, []() { return false; }); + } + + template + 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 + 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 outgoingQueue.size(); } u8 read() { LINK_UART_BARRIER; isReading = true; LINK_UART_BARRIER; - u8 data = queue.pop(); + u8 data = incomingQueue.pop(); LINK_UART_BARRIER; - isReading = true; + isReading = false; LINK_UART_BARRIER; return data; } - void send(u8 data) { REG_SIODATA8 = 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 (canReceive() && !isReading) - queue.push((u8)REG_SIODATA8); + if (!isReading && canReceive()) + incomingQueue.push((u8)REG_SIODATA8); + + if (!isAdding && canTransfer() && needsTransfer()) + REG_SIODATA8 = outgoingQueue.pop(); } private: @@ -181,12 +252,16 @@ class LinkUART { }; Config config; - U8Queue queue; + U8Queue incomingQueue; + U8Queue outgoingQueue; volatile bool isEnabled = false; volatile bool isReading = false; - // TODO: Make it similar to LinkCable (timer + outgoingQueue) + 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(); @@ -194,7 +269,10 @@ class LinkUART { start(); } - void resetState() { queue.clear(); } + void resetState() { + incomingQueue.clear(); + outgoingQueue.clear(); + } void stop() { setGeneralPurposeMode(); } From 4aa43dfcb6e3a002daf523482490ac382966ef34 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 18:55:44 -0300 Subject: [PATCH 04/10] Adding UART documentation --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- lib/LinkUART.hpp | 4 ++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 790bb609..5d6f3107 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ 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! +- [⏱️](#-LinkUART) [LinkUART.hpp](lib/LinkUART.hpp): Easily connect to **any PC** using a USB to UART cable! *(click on the emojis for documentation)* @@ -87,7 +88,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 +344,41 @@ The GBA operates using **SPI mode 3** (`CPOL=1, CPHA=1`). Here's a connection di + +# ⏱️ 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)` | - | Receives a null-terminated `string`, and sends the string 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. \ No newline at end of file diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp index 436054f1..5fded3b3 100644 --- a/lib/LinkUART.hpp +++ b/lib/LinkUART.hpp @@ -12,7 +12,7 @@ // irq_add(II_SERIAL, LINK_UART_ISR_SERIAL); // - 3) Initialize the library with: // linkUART->activate(); -// - 4) Send/read bytes by using: +// - 4) Send/read data by using: // linkUART->send(0xFA); // linkUART->sendLine("hello"); // u8 newByte = linkUART->read(); @@ -159,7 +159,7 @@ class LinkUART { bool canRead() { return !incomingQueue.isEmpty(); } bool canSend() { return !outgoingQueue.isFull(); } u32 availableForRead() { return incomingQueue.size(); } - u32 availableForSend() { return outgoingQueue.size(); } + u32 availableForSend() { return LINK_UART_QUEUE_SIZE - outgoingQueue.size(); } u8 read() { LINK_UART_BARRIER; From 33f0d2ca4ebfc674e8eb37633e2de8a04721c055 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 18:56:55 -0300 Subject: [PATCH 05/10] Version => v6.3.0 --- README.md | 2 +- examples/LinkCableMultiboot_demo/src/main.cpp | 2 +- examples/LinkCable_basic/src/main.cpp | 2 +- examples/LinkCable_full/src/main.cpp | 4 ++-- examples/LinkCable_stress/src/main.cpp | 4 ++-- examples/LinkGPIO_demo/src/main.cpp | 2 +- examples/LinkRawCable_demo/src/main.cpp | 2 +- examples/LinkRawWireless_demo/src/scenes/DebugScene.cpp | 2 +- examples/LinkSPI_demo/src/main.cpp | 2 +- examples/LinkUART_demo/src/main.cpp | 2 +- examples/LinkUniversal_basic/src/main.cpp | 2 +- .../LinkWirelessMultiboot_demo/src/scenes/MultibootScene.cpp | 2 +- examples/LinkWireless_demo/src/main.cpp | 2 +- lib/LinkCable.hpp | 2 +- lib/LinkCableMultiboot.hpp | 2 +- lib/LinkGPIO.hpp | 2 +- lib/LinkRawCable.hpp | 2 +- lib/LinkRawWireless.hpp | 2 +- lib/LinkSPI.hpp | 2 +- lib/LinkUART.hpp | 2 +- lib/LinkUniversal.hpp | 2 +- lib/LinkWireless.hpp | 2 +- lib/LinkWirelessMultiboot.hpp | 2 +- lib/LinkWirelessOpenSDK.hpp | 2 +- 24 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 5d6f3107..0dda131e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ 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! -- [⏱️](#-LinkUART) [LinkUART.hpp](lib/LinkUART.hpp): Easily connect to **any PC** using a USB to UART cable! +- [⏱️](#%EF%B8%8F-LinkUART) [LinkUART.hpp](lib/LinkUART.hpp): Easily connect to **any PC** using a USB to UART cable! *(click on the emojis for documentation)* diff --git a/examples/LinkCableMultiboot_demo/src/main.cpp b/examples/LinkCableMultiboot_demo/src/main.cpp index 48e04c09..9ed5b9ab 100644 --- a/examples/LinkCableMultiboot_demo/src/main.cpp +++ b/examples/LinkCableMultiboot_demo/src/main.cpp @@ -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) { diff --git a/examples/LinkCable_basic/src/main.cpp b/examples/LinkCable_basic/src/main.cpp index 8c96325e..9aecd151 100644 --- a/examples/LinkCable_basic/src/main.cpp +++ b/examples/LinkCable_basic/src/main.cpp @@ -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(); diff --git a/examples/LinkCable_full/src/main.cpp b/examples/LinkCable_full/src/main.cpp index 4781fade..a4e47c15 100644 --- a/examples/LinkCable_full/src/main.cpp +++ b/examples/LinkCable_full/src/main.cpp @@ -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(""); diff --git a/examples/LinkCable_stress/src/main.cpp b/examples/LinkCable_stress/src/main.cpp index 6a900bb7..c6ce2412 100644 --- a/examples/LinkCable_stress/src/main.cpp +++ b/examples/LinkCable_stress/src/main.cpp @@ -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(); diff --git a/examples/LinkGPIO_demo/src/main.cpp b/examples/LinkGPIO_demo/src/main.cpp index b1265dfc..1f9cb633 100644 --- a/examples/LinkGPIO_demo/src/main.cpp +++ b/examples/LinkGPIO_demo/src/main.cpp @@ -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; diff --git a/examples/LinkRawCable_demo/src/main.cpp b/examples/LinkRawCable_demo/src/main.cpp index f810d5ab..8afe7eb4 100644 --- a/examples/LinkRawCable_demo/src/main.cpp +++ b/examples/LinkRawCable_demo/src/main.cpp @@ -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()) { diff --git a/examples/LinkRawWireless_demo/src/scenes/DebugScene.cpp b/examples/LinkRawWireless_demo/src/scenes/DebugScene.cpp index 67f1882d..34651d86 100644 --- a/examples/LinkRawWireless_demo/src/scenes/DebugScene.cpp +++ b/examples/LinkRawWireless_demo/src/scenes/DebugScene.cpp @@ -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"); diff --git a/examples/LinkSPI_demo/src/main.cpp b/examples/LinkSPI_demo/src/main.cpp index f77b3593..82312fce 100644 --- a/examples/LinkSPI_demo/src/main.cpp +++ b/examples/LinkSPI_demo/src/main.cpp @@ -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()) { diff --git a/examples/LinkUART_demo/src/main.cpp b/examples/LinkUART_demo/src/main.cpp index 724c3805..cef56a66 100644 --- a/examples/LinkUART_demo/src/main.cpp +++ b/examples/LinkUART_demo/src/main.cpp @@ -32,7 +32,7 @@ int main() { bool firstTransfer = false; while (true) { - std::string output = "LinkUART_demo (v6.2.3)\n\n"; + std::string output = "LinkUART_demo (v6.3.0)\n\n"; u16 keys = ~REG_KEYS & KEY_ANY; if (!linkUART->isActive()) { diff --git a/examples/LinkUniversal_basic/src/main.cpp b/examples/LinkUniversal_basic/src/main.cpp index 375ae776..62049bfd 100644 --- a/examples/LinkUniversal_basic/src/main.cpp +++ b/examples/LinkUniversal_basic/src/main.cpp @@ -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 " diff --git a/examples/LinkWirelessMultiboot_demo/src/scenes/MultibootScene.cpp b/examples/LinkWirelessMultiboot_demo/src/scenes/MultibootScene.cpp index 5b506a70..c9c22d0c 100644 --- a/examples/LinkWirelessMultiboot_demo/src/scenes/MultibootScene.cpp +++ b/examples/LinkWirelessMultiboot_demo/src/scenes/MultibootScene.cpp @@ -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"); diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 5ea0f3a5..40dc9bbb 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -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"); diff --git a/lib/LinkCable.hpp b/lib/LinkCable.hpp index 2aff2030..74a705d2 100644 --- a/lib/LinkCable.hpp +++ b/lib/LinkCable.hpp @@ -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(); diff --git a/lib/LinkCableMultiboot.hpp b/lib/LinkCableMultiboot.hpp index fd01a33d..6702b668 100644 --- a/lib/LinkCableMultiboot.hpp +++ b/lib/LinkCableMultiboot.hpp @@ -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}; diff --git a/lib/LinkGPIO.hpp b/lib/LinkGPIO.hpp index 081fd8fa..79662666 100644 --- a/lib/LinkGPIO.hpp +++ b/lib/LinkGPIO.hpp @@ -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}; diff --git a/lib/LinkRawCable.hpp b/lib/LinkRawCable.hpp index c2210819..646cf7bf 100644 --- a/lib/LinkRawCable.hpp +++ b/lib/LinkRawCable.hpp @@ -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: diff --git a/lib/LinkRawWireless.hpp b/lib/LinkRawWireless.hpp index 410c21d9..40432cf0 100644 --- a/lib/LinkRawWireless.hpp +++ b/lib/LinkRawWireless.hpp @@ -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}; diff --git a/lib/LinkSPI.hpp b/lib/LinkSPI.hpp index b099aa5e..05fb5f90 100644 --- a/lib/LinkSPI.hpp +++ b/lib/LinkSPI.hpp @@ -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); diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp index 5fded3b3..f3303f9d 100644 --- a/lib/LinkUART.hpp +++ b/lib/LinkUART.hpp @@ -47,7 +47,7 @@ #define LINK_UART_BIT_GENERAL_PURPOSE_HIGH 15 #define LINK_UART_BARRIER asm volatile("" ::: "memory") -static volatile char LINK_UART_VERSION[] = "LinkUART/v6.2.3"; +static volatile char LINK_UART_VERSION[] = "LinkUART/v6.3.0"; void LINK_UART_ISR_SERIAL(); diff --git a/lib/LinkUniversal.hpp b/lib/LinkUniversal.hpp index b3fbffbb..a2c13710 100644 --- a/lib/LinkUniversal.hpp +++ b/lib/LinkUniversal.hpp @@ -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(); diff --git a/lib/LinkWireless.hpp b/lib/LinkWireless.hpp index 7a06073a..c3c620b2 100644 --- a/lib/LinkWireless.hpp +++ b/lib/LinkWireless.hpp @@ -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(); diff --git a/lib/LinkWirelessMultiboot.hpp b/lib/LinkWirelessMultiboot.hpp index 992354d5..66491096 100644 --- a/lib/LinkWirelessMultiboot.hpp +++ b/lib/LinkWirelessMultiboot.hpp @@ -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: diff --git a/lib/LinkWirelessOpenSDK.hpp b/lib/LinkWirelessOpenSDK.hpp index 4f8dd1fb..955243bb 100644 --- a/lib/LinkWirelessOpenSDK.hpp +++ b/lib/LinkWirelessOpenSDK.hpp @@ -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: From 37e01daed07990b4b042ccd7b756335362ecc296 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 19:00:32 -0300 Subject: [PATCH 06/10] Updating UART docs --- README.md | 2 +- lib/LinkUART.hpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 0dda131e..79302298 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ 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)` | - | Receives a null-terminated `string`, and sends the string followed by a `'\n'` character. The null character is not sent. +`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`. diff --git a/lib/LinkUART.hpp b/lib/LinkUART.hpp index f3303f9d..94231953 100644 --- a/lib/LinkUART.hpp +++ b/lib/LinkUART.hpp @@ -24,7 +24,6 @@ // (see examples) // -------------------------------------------------------------------------- -#include #include // Buffer size From 1ed8195154716a5914e8b95a678a1d4a551ad444 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 20:09:54 -0300 Subject: [PATCH 07/10] Adding PS2 libraries --- examples/LinkPS2Keyboard_demo/Makefile | 284 +++++++++++++++++++++ examples/LinkPS2Keyboard_demo/src/main.cpp | 67 +++++ examples/LinkPS2Mouse_demo/Makefile | 284 +++++++++++++++++++++ examples/LinkPS2Mouse_demo/src/main.cpp | 64 +++++ lib/LinkPS2Keyboard.hpp | 120 +++++++++ lib/LinkPS2Mouse.hpp | 238 +++++++++++++++++ 6 files changed, 1057 insertions(+) create mode 100644 examples/LinkPS2Keyboard_demo/Makefile create mode 100644 examples/LinkPS2Keyboard_demo/src/main.cpp create mode 100644 examples/LinkPS2Mouse_demo/Makefile create mode 100644 examples/LinkPS2Mouse_demo/src/main.cpp create mode 100644 lib/LinkPS2Keyboard.hpp create mode 100644 lib/LinkPS2Mouse.hpp diff --git a/examples/LinkPS2Keyboard_demo/Makefile b/examples/LinkPS2Keyboard_demo/Makefile new file mode 100644 index 00000000..7cff4240 --- /dev/null +++ b/examples/LinkPS2Keyboard_demo/Makefile @@ -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 $( `(echo $(> `(echo $(> `(echo $( $(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 diff --git a/examples/LinkPS2Keyboard_demo/src/main.cpp b/examples/LinkPS2Keyboard_demo/src/main.cpp new file mode 100644 index 00000000..fa28a151 --- /dev/null +++ b/examples/LinkPS2Keyboard_demo/src/main.cpp @@ -0,0 +1,67 @@ +#include +#include +#include "../../_lib/interrupt.h" + +// (0) Include the header +#include "../../../lib/LinkPS2Keyboard.hpp" + +void log(std::string text); +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 event) { + // (4) Handle events in the callback sent to LinkPS2Keyboard's constructor! + output += std::to_string(event) + "|"; +}); + +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; + } + } + + // 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()); +} diff --git a/examples/LinkPS2Mouse_demo/Makefile b/examples/LinkPS2Mouse_demo/Makefile new file mode 100644 index 00000000..7cff4240 --- /dev/null +++ b/examples/LinkPS2Mouse_demo/Makefile @@ -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 $( `(echo $(> `(echo $(> `(echo $( $(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 diff --git a/examples/LinkPS2Mouse_demo/src/main.cpp b/examples/LinkPS2Mouse_demo/src/main.cpp new file mode 100644 index 00000000..3b16bc9e --- /dev/null +++ b/examples/LinkPS2Mouse_demo/src/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#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, VBLANK); + 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); + log(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()); +} diff --git a/lib/LinkPS2Keyboard.hpp b/lib/LinkPS2Keyboard.hpp new file mode 100644 index 00000000..42cd3946 --- /dev/null +++ b/lib/LinkPS2Keyboard.hpp @@ -0,0 +1,120 @@ +#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 +// }); +// - 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 +#include + +#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 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 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 \ No newline at end of file diff --git a/lib/LinkPS2Mouse.hpp b/lib/LinkPS2Mouse.hpp new file mode 100644 index 00000000..6bd64e5a --- /dev/null +++ b/lib/LinkPS2Mouse.hpp @@ -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 +#include + +#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 \ No newline at end of file From c23056e51adaefa74be20f589fc827fdbe0b1f07 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 20:44:28 -0300 Subject: [PATCH 08/10] Updating compile script --- examples/LinkPS2Mouse_demo/src/main.cpp | 2 +- examples/compile.sh | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/examples/LinkPS2Mouse_demo/src/main.cpp b/examples/LinkPS2Mouse_demo/src/main.cpp index 3b16bc9e..b06a1f64 100644 --- a/examples/LinkPS2Mouse_demo/src/main.cpp +++ b/examples/LinkPS2Mouse_demo/src/main.cpp @@ -20,7 +20,7 @@ void init() { interrupt_init(); interrupt_set_handler(INTR_VBLANK, VBLANK); interrupt_enable(INTR_VBLANK); - interrupt_set_handler(INTR_TIMER2, VBLANK); + interrupt_set_handler(INTR_TIMER2, TIMER); interrupt_enable(INTR_TIMER2); } diff --git a/examples/compile.sh b/examples/compile.sh index 4f765791..debd85d9 100644 --- a/examples/compile.sh +++ b/examples/compile.sh @@ -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 From 232a834c3bdcad0ed2b4e333f6b44b37c156963c Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 20:57:59 -0300 Subject: [PATCH 09/10] Fixing PS2 examples --- examples/LinkPS2Keyboard_demo/src/main.cpp | 7 +++++-- examples/LinkPS2Mouse_demo/src/main.cpp | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/LinkPS2Keyboard_demo/src/main.cpp b/examples/LinkPS2Keyboard_demo/src/main.cpp index fa28a151..3822a07e 100644 --- a/examples/LinkPS2Keyboard_demo/src/main.cpp +++ b/examples/LinkPS2Keyboard_demo/src/main.cpp @@ -6,6 +6,7 @@ #include "../../../lib/LinkPS2Keyboard.hpp" void log(std::string text); +static std::string scanCodes = ""; static std::string output = ""; static u32 irqs = 0; inline void VBLANK() {} @@ -15,9 +16,9 @@ void SERIAL() { } // (1) Create a LinkPS2Keyboard instance -LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 event) { +LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 scanCode) { // (4) Handle events in the callback sent to LinkPS2Keyboard's constructor! - output += std::to_string(event) + "|"; + scanCodes += std::to_string(scanCode) + "|"; }); void init() { @@ -49,6 +50,8 @@ int main() { VBlankIntrWait(); continue; } + } else { + output += std::to_string(irqs) + " - " + scanCodes; } // Print diff --git a/examples/LinkPS2Mouse_demo/src/main.cpp b/examples/LinkPS2Mouse_demo/src/main.cpp index b06a1f64..bf84ea5c 100644 --- a/examples/LinkPS2Mouse_demo/src/main.cpp +++ b/examples/LinkPS2Mouse_demo/src/main.cpp @@ -45,8 +45,8 @@ int main() { // (4) Get a report int data[3]; linkPS2Mouse->report(data); - log(std::to_string(data[0]) + ": " + "(" + std::to_string(data[1]) + - ", " + std::to_string(data[2]) + ")"); + output += std::to_string(data[0]) + ": " + "(" + std::to_string(data[1]) + + ", " + std::to_string(data[2]) + ")"; } // Print From 6245d8f73e92e7b1eccb00f0819139eaed9b541a Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Sun, 21 Apr 2024 21:20:35 -0300 Subject: [PATCH 10/10] Adding PS/2 documentation --- README.md | 41 +++++++++++++++++++++++++++++++++- examples/LinkUART_demo/Test.js | 2 +- lib/LinkPS2Keyboard.hpp | 26 ++++++++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 79302298..39df2079 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port. - [🔌](#-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)* @@ -381,4 +383,41 @@ The GBA operates using 1 stop bit, but everything else can be configured. By def - Black wire (GND) -> GBA GND. - Green wire (TX) -> GBA SI. -- White wire (RX) -> GBA SO. \ No newline at end of file +- 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. \ No newline at end of file diff --git a/examples/LinkUART_demo/Test.js b/examples/LinkUART_demo/Test.js index d9b7d1af..1c2ed823 100644 --- a/examples/LinkUART_demo/Test.js +++ b/examples/LinkUART_demo/Test.js @@ -1,4 +1,4 @@ -const { SerialPort, ReadlineParser } = require('serialport') +const { SerialPort, ReadlineParser } = require('serialport') // "^12.0.0" var serialPort = new SerialPort({ path: "COM9", // (*nix: /dev/ttyACMX, Windows: COMX) diff --git a/lib/LinkPS2Keyboard.hpp b/lib/LinkPS2Keyboard.hpp index 42cd3946..d93e23d1 100644 --- a/lib/LinkPS2Keyboard.hpp +++ b/lib/LinkPS2Keyboard.hpp @@ -7,7 +7,7 @@ // Usage: // - 1) Include this header in your main.cpp file and add: // LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 event) { -// // handle event +// // handle event (check example scan codes below) // }); // - 2) Add the required interrupt service routines: (*) // irq_init(NULL); @@ -30,6 +30,30 @@ #include #include +// 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