From eaf930e71b846813a69b60447907783a798ba478 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Tue, 6 Aug 2024 22:29:38 -0300 Subject: [PATCH] Dropping tonc dependency in LinkCable --- README.md | 1 + examples/LinkCable_basic/src/main.cpp | 8 +- examples/LinkCable_full/src/main.cpp | 6 +- examples/LinkCable_full/src/main.h | 13 ++- examples/LinkCable_stress/src/main.cpp | 8 +- examples/LinkCable_stress/src/main.h | 13 ++- lib/LinkCable.hpp | 111 +++++++++++++------------ lib/LinkCableMultiboot.hpp | 6 +- lib/LinkSPI.hpp | 4 +- lib/_link_common.hpp | 27 ++++++ 10 files changed, 118 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 63d095f..b6bb9c1 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port. - Copy the `lib/` folder into your project's `include` directory and include the library you need, such as [LinkCable.hpp](lib/LinkCable.hpp). For initial instructions and setup details, refer to the big comment block at the beginning of each file, the documentation included here, and the provided examples. No external dependencies are required. - Check out the [examples](examples) folder. + * The example code uses [libtonc](https://github.com/gbadev-org/libtonc) (and [libugba](https://github.com/AntonioND/libugba) for interrupts), but any library can be used. * Builds are available in [Releases](https://github.com/afska/gba-link-connection/releases). * They can be tested on real GBAs or using emulators. * For `LinkCable`/`LinkWireless`/`LinkUniversal` there are stress tests that you can use to tweak your configuration. diff --git a/examples/LinkCable_basic/src/main.cpp b/examples/LinkCable_basic/src/main.cpp index 0c4578c..2f92560 100644 --- a/examples/LinkCable_basic/src/main.cpp +++ b/examples/LinkCable_basic/src/main.cpp @@ -1,13 +1,13 @@ -#include -#include -#include "../../_lib/interrupt.h" - // BASIC: // This example sends the pressed buttons to other players. // (0) Include the header #include "../../../lib/LinkCable.hpp" +#include +#include +#include "../../_lib/interrupt.h" + void log(std::string text); // (1) Create a LinkCable instance diff --git a/examples/LinkCable_full/src/main.cpp b/examples/LinkCable_full/src/main.cpp index 3670c45..ecdcae5 100644 --- a/examples/LinkCable_full/src/main.cpp +++ b/examples/LinkCable_full/src/main.cpp @@ -1,12 +1,12 @@ +// FULL: +// This example has a menu and lets the user send data in different ways. + #include "main.h" #include #include "../../_lib/interrupt.h" #include "scenes/TestScene.h" #include "utils/SceneUtils.h" -// FULL: -// This example has a menu and lets the user send data in different ways. - void setUpInterrupts(); void printTutorial(); static std::shared_ptr engine{new GBAEngine()}; diff --git a/examples/LinkCable_full/src/main.h b/examples/LinkCable_full/src/main.h index b7001b1..07ba353 100644 --- a/examples/LinkCable_full/src/main.h +++ b/examples/LinkCable_full/src/main.h @@ -1,12 +1,17 @@ #ifndef MAIN_H #define MAIN_H -#include -#include "../../../lib/LinkCable.hpp" -#include "../../../lib/LinkUniversal.hpp" - // #define USE_LINK_UNIVERSAL +#ifndef USE_LINK_UNIVERSAL +#include "../../../lib/LinkCable.hpp" +#endif +#ifdef USE_LINK_UNIVERSAL +#include "../../../lib/LinkUniversal.hpp" +#endif + +#include + #ifndef USE_LINK_UNIVERSAL extern LinkCable* linkConnection; #endif diff --git a/examples/LinkCable_stress/src/main.cpp b/examples/LinkCable_stress/src/main.cpp index 7df0236..861cc21 100644 --- a/examples/LinkCable_stress/src/main.cpp +++ b/examples/LinkCable_stress/src/main.cpp @@ -1,7 +1,3 @@ -#include "main.h" -#include -#include "../../_lib/interrupt.h" - // STRESS: // This example can perform multiple stress tests. // A) Packet loss test: @@ -18,6 +14,10 @@ // R) Measure ping-pong latency: // - Like (L), but adding a validation response and adding that time. +#include "main.h" +#include +#include "../../_lib/interrupt.h" + #define FINAL_VALUE 65534 void test(bool withSync); diff --git a/examples/LinkCable_stress/src/main.h b/examples/LinkCable_stress/src/main.h index b7001b1..07ba353 100644 --- a/examples/LinkCable_stress/src/main.h +++ b/examples/LinkCable_stress/src/main.h @@ -1,12 +1,17 @@ #ifndef MAIN_H #define MAIN_H -#include -#include "../../../lib/LinkCable.hpp" -#include "../../../lib/LinkUniversal.hpp" - // #define USE_LINK_UNIVERSAL +#ifndef USE_LINK_UNIVERSAL +#include "../../../lib/LinkCable.hpp" +#endif +#ifdef USE_LINK_UNIVERSAL +#include "../../../lib/LinkUniversal.hpp" +#endif + +#include + #ifndef USE_LINK_UNIVERSAL extern LinkCable* linkConnection; #endif diff --git a/lib/LinkCable.hpp b/lib/LinkCable.hpp index 64b215f..56fe458 100644 --- a/lib/LinkCable.hpp +++ b/lib/LinkCable.hpp @@ -42,41 +42,42 @@ // (they mean 'disconnected' and 'no data' respectively) // -------------------------------------------------------------------------- -#include -#include +#include "_link_common.hpp" // Buffer size #define LINK_CABLE_QUEUE_SIZE 15 +static volatile char LINK_CABLE_VERSION[] = "LinkCable/v7.0.0"; + #define LINK_CABLE_MAX_PLAYERS 4 -#define LINK_CABLE_DISCONNECTED 0xffff -#define LINK_CABLE_NO_DATA 0x0 #define LINK_CABLE_DEFAULT_TIMEOUT 3 #define LINK_CABLE_DEFAULT_REMOTE_TIMEOUT 5 #define LINK_CABLE_DEFAULT_INTERVAL 50 #define LINK_CABLE_DEFAULT_SEND_TIMER_ID 3 -#define LINK_CABLE_BASE_FREQUENCY TM_FREQ_1024 -#define LINK_CABLE_REMOTE_TIMEOUT_OFFLINE -1 -#define LINK_CABLE_BIT_SLAVE 2 -#define LINK_CABLE_BIT_READY 3 -#define LINK_CABLE_BITS_PLAYER_ID 4 -#define LINK_CABLE_BIT_ERROR 6 -#define LINK_CABLE_BIT_START 7 -#define LINK_CABLE_BIT_MULTIPLAYER 13 -#define LINK_CABLE_BIT_IRQ 14 -#define LINK_CABLE_BIT_GENERAL_PURPOSE_LOW 14 -#define LINK_CABLE_BIT_GENERAL_PURPOSE_HIGH 15 #define LINK_CABLE_BARRIER asm volatile("" ::: "memory") -static volatile char LINK_CABLE_VERSION[] = "LinkCable/v7.0.0"; - -void LINK_CABLE_ISR_VBLANK(); -void LINK_CABLE_ISR_SERIAL(); -void LINK_CABLE_ISR_TIMER(); -const u16 LINK_CABLE_TIMER_IRQ_IDS[] = {IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2, - IRQ_TIMER3}; - class LinkCable { + private: + using u32 = unsigned int; + using u16 = unsigned short; + using u8 = unsigned char; + using vs32 = volatile signed int; + using vu32 = volatile unsigned int; + + static constexpr u16 DISCONNECTED = 0xffff; + static constexpr u16 NO_DATA = 0x0; + static constexpr u16 BASE_FREQUENCY = Link::_TM_FREQ_1024; + static constexpr int REMOTE_TIMEOUT_OFFLINE = -1; + static constexpr int BIT_SLAVE = 2; + static constexpr int BIT_READY = 3; + static constexpr int BITS_PLAYER_ID = 4; + static constexpr int BIT_ERROR = 6; + static constexpr int BIT_START = 7; + static constexpr int BIT_MULTIPLAYER = 13; + static constexpr int BIT_IRQ = 14; + static constexpr int BIT_GENERAL_PURPOSE_LOW = 14; + static constexpr int BIT_GENERAL_PURPOSE_HIGH = 15; + public: enum BaudRate { BAUD_RATE_0, // 9600 bps @@ -98,7 +99,7 @@ class LinkCable { u16 pop() { if (isEmpty()) - return LINK_CABLE_NO_DATA; + return NO_DATA; auto x = arr[front]; front = (front + 1) % LINK_CABLE_QUEUE_SIZE; @@ -109,7 +110,7 @@ class LinkCable { u16 peek() { if (isEmpty()) - return LINK_CABLE_NO_DATA; + return NO_DATA; return arr[front]; } @@ -202,7 +203,8 @@ class LinkCable { sync(); while (isConnected() && !canRead(playerId) && !cancel()) { - IntrWait(1, IRQ_SERIAL | LINK_CABLE_TIMER_IRQ_IDS[config.sendTimerId]); + Link::_IntrWait( + 1, Link::_IRQ_SERIAL | Link::_TIMER_IRQ_IDS[config.sendTimerId]); sync(); } @@ -218,7 +220,7 @@ class LinkCable { u16 peek(u8 playerId) { return state.incomingMessages[playerId].peek(); } void send(u16 data) { - if (data == LINK_CABLE_DISCONNECTED || data == LINK_CABLE_NO_DATA) + if (data == DISCONNECTED || data == NO_DATA) return; LINK_CABLE_BARRIER; @@ -263,10 +265,10 @@ class LinkCable { u8 newPlayerCount = 0; for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++) { - u16 data = REG_SIOMULTI[i]; + u16 data = Link::_REG_SIOMULTI[i]; - if (data != LINK_CABLE_DISCONNECTED) { - if (data != LINK_CABLE_NO_DATA && i != state.currentPlayerId) + if (data != DISCONNECTED) { + if (data != NO_DATA && i != state.currentPlayerId) _state.newMessages[i].push(data); newPlayerCount++; setOnline(i); @@ -284,8 +286,7 @@ class LinkCable { state.playerCount = newPlayerCount; state.currentPlayerId = - (REG_SIOCNT & (0b11 << LINK_CABLE_BITS_PLAYER_ID)) >> - LINK_CABLE_BITS_PLAYER_ID; + (Link::_REG_SIOCNT & (0b11 << BITS_PLAYER_ID)) >> BITS_PLAYER_ID; if (!isMaster()) sendPendingData(); @@ -341,10 +342,10 @@ class LinkCable { volatile bool isAddingMessage = false; volatile bool isAddingWhileResetting = false; - bool isMaster() { return !isBitHigh(LINK_CABLE_BIT_SLAVE); } - bool isReady() { return isBitHigh(LINK_CABLE_BIT_READY); } - bool hasError() { return isBitHigh(LINK_CABLE_BIT_ERROR); } - bool isSending() { return isBitHigh(LINK_CABLE_BIT_START); } + bool isMaster() { return !isBitHigh(BIT_SLAVE); } + bool isReady() { return isBitHigh(BIT_READY); } + bool hasError() { return isBitHigh(BIT_ERROR); } + bool isSending() { return isBitHigh(BIT_START); } bool didTimeout() { return _state.IRQTimeout >= config.timeout; } void sendPendingData() { @@ -357,10 +358,10 @@ class LinkCable { } void transfer(u16 data) { - REG_SIOMLT_SEND = data; + Link::_REG_SIOMLT_SEND = data; if (isMaster()) - setBitHigh(LINK_CABLE_BIT_START); + setBitHigh(BIT_START); } void reset() { @@ -401,14 +402,14 @@ class LinkCable { } void stopTimer() { - REG_TM[config.sendTimerId].cnt = - REG_TM[config.sendTimerId].cnt & (~TM_ENABLE); + Link::_REG_TM[config.sendTimerId].cnt = + Link::_REG_TM[config.sendTimerId].cnt & (~Link::_TM_ENABLE); } void startTimer() { - REG_TM[config.sendTimerId].start = -config.interval; - REG_TM[config.sendTimerId].cnt = - TM_ENABLE | TM_IRQ | LINK_CABLE_BASE_FREQUENCY; + Link::_REG_TM[config.sendTimerId].start = -config.interval; + Link::_REG_TM[config.sendTimerId].cnt = + Link::_TM_ENABLE | Link::_TM_IRQ | BASE_FREQUENCY; } void clearIncomingMessages() { @@ -434,30 +435,30 @@ class LinkCable { } bool isOnline(u8 playerId) { - return _state.timeouts[playerId] != LINK_CABLE_REMOTE_TIMEOUT_OFFLINE; + return _state.timeouts[playerId] != REMOTE_TIMEOUT_OFFLINE; } void setOnline(u8 playerId) { _state.timeouts[playerId] = 0; } void setOffline(u8 playerId) { - _state.timeouts[playerId] = LINK_CABLE_REMOTE_TIMEOUT_OFFLINE; + _state.timeouts[playerId] = REMOTE_TIMEOUT_OFFLINE; } - void setInterruptsOn() { setBitHigh(LINK_CABLE_BIT_IRQ); } + void setInterruptsOn() { setBitHigh(BIT_IRQ); } void setMultiPlayMode() { - REG_RCNT = REG_RCNT & ~(1 << LINK_CABLE_BIT_GENERAL_PURPOSE_HIGH); - REG_SIOCNT = (1 << LINK_CABLE_BIT_MULTIPLAYER); - REG_SIOCNT |= config.baudRate; - REG_SIOMLT_SEND = 0; + Link::_REG_RCNT = Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_HIGH); + Link::_REG_SIOCNT = (1 << BIT_MULTIPLAYER); + Link::_REG_SIOCNT |= config.baudRate; + Link::_REG_SIOMLT_SEND = 0; } void setGeneralPurposeMode() { - REG_RCNT = (REG_RCNT & ~(1 << LINK_CABLE_BIT_GENERAL_PURPOSE_LOW)) | - (1 << LINK_CABLE_BIT_GENERAL_PURPOSE_HIGH); + Link::_REG_RCNT = (Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_LOW)) | + (1 << 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); } + bool isBitHigh(u8 bit) { return (Link::_REG_SIOCNT >> bit) & 1; } + void setBitHigh(u8 bit) { Link::_REG_SIOCNT |= 1 << bit; } + void setBitLow(u8 bit) { Link::_REG_SIOCNT &= ~(1 << bit); } }; extern LinkCable* linkCable; diff --git a/lib/LinkCableMultiboot.hpp b/lib/LinkCableMultiboot.hpp index 3fa9954..ef85085 100644 --- a/lib/LinkCableMultiboot.hpp +++ b/lib/LinkCableMultiboot.hpp @@ -26,6 +26,9 @@ #include "LinkRawCable.hpp" +static volatile char LINK_CABLE_MULTIBOOT_VERSION[] = + "LinkCableMultiboot/v7.0.0"; + #define LINK_CABLE_MULTIBOOT_TRY(CALL) \ do { \ partialResult = CALL; \ @@ -35,9 +38,6 @@ else if (partialResult == ERROR) \ return error(FAILURE_DURING_HANDSHAKE); -static volatile char LINK_CABLE_MULTIBOOT_VERSION[] = - "LinkCableMultiboot/v7.0.0"; - /** * @brief A Multiboot tool to send small programs from one GBA to up to 3 * slaves. diff --git a/lib/LinkSPI.hpp b/lib/LinkSPI.hpp index 944c22a..ec945cd 100644 --- a/lib/LinkSPI.hpp +++ b/lib/LinkSPI.hpp @@ -45,6 +45,8 @@ // 8-bit mode (uncomment to enable) // #define LINK_SPI_8BIT_MODE +static volatile char LINK_SPI_VERSION[] = "LinkSPI/v7.0.0"; + #ifdef LINK_SPI_8BIT_MODE #define LINK_SPI_DATA_TYPE u8 #endif @@ -66,8 +68,6 @@ #define LINK_SPI_NO_DATA 0xffffffff #endif -static volatile char LINK_SPI_VERSION[] = "LinkSPI/v7.0.0"; - /** * @brief An SPI handler for the Link Port (Normal Mode, either 32 or 8 bits). * 32-bit transfers by default. Set `LINK_SPI_8BIT_MODE` for 8-bit transfers. diff --git a/lib/_link_common.hpp b/lib/_link_common.hpp index ad45a82..7b81fca 100644 --- a/lib/_link_common.hpp +++ b/lib/_link_common.hpp @@ -45,6 +45,33 @@ inline volatile u16& _REG_VCOUNT = inline volatile _TMR_REC* const _REG_TM = reinterpret_cast(_REG_BASE + 0x0100); +static constexpr u16 _TM_FREQ_1 = 0; // 1 cycle/tick (16.7 MHz) +static constexpr u16 _TM_FREQ_64 = 0x0001; // 64 cycles/tick (262 kHz) +static constexpr u16 _TM_FREQ_256 = 0x0002; // 256 cycles/tick (66 kHz) +static constexpr u16 _TM_FREQ_1024 = 0x0003; // 1024 cycles/tick (16 kHz) +static constexpr u16 _TM_IRQ = 0x0040; +static constexpr u16 _TM_ENABLE = 0x0080; + +static constexpr u16 _IRQ_VBLANK = 0x0001; //!< Catch VBlank irq +static constexpr u16 _IRQ_TIMER0 = 0x0008; //!< Catch timer 0 irq +static constexpr u16 _IRQ_TIMER1 = 0x0010; //!< Catch timer 1 irq +static constexpr u16 _IRQ_TIMER2 = 0x0020; //!< Catch timer 2 irq +static constexpr u16 _IRQ_TIMER3 = 0x0040; //!< Catch timer 3 irq +static constexpr u16 _IRQ_SERIAL = 0x0080; //!< Catch serial comm irq +static constexpr u16 _TIMER_IRQ_IDS[] = {_IRQ_TIMER0, _IRQ_TIMER1, _IRQ_TIMER2, + _IRQ_TIMER3}; + +inline void _IntrWait(u32 flagClear, u32 irq) { + asm volatile( + "mov r0, %0\n" // flagClear => r0 + "mov r1, %1\n" // irq => r1 + "swi 0x04\n" // call 0x04 + : // outputs + : "r"(flagClear), "r"(irq) // inputs + : "r0", "r1" // clobbered registers + ); +} + typedef struct { u32 reserved1[5]; u8 handshake_data;