diff --git a/README.md b/README.md index a2a5e20..36f0dbd 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ All the complexity is abstracted in a single header file that exposes an easy-to - Include [LinkConnection.h](lib/LinkConnection.h) in your game code, and read its comment with instructions. - Check out the [examples](examples) folder * Builds are available in *Releases*. - * They can be tested on real GBAs or with emulators (*NO$GBA*, *mGBA*, or *VBA-M*). + * They can be tested on real GBAs or with emulators (*NO$GBA* or *VBA-M*). ## Constructor options @@ -21,10 +21,13 @@ All the complexity is abstracted in a single header file that exposes an easy-to Name | Type | Default | Description --- | --- | --- | --- -`startNow` | **bool** | `true` | Automatically starts serial communication. Otherwise, you'll need to call `linkConnection->activate()`. `baudRate` | **BaudRate** | `BaudRate::BAUD_RATE_3` | Sets a specific baud rate. -`timeout` | **u32** | `3` | Number of frames without an `II_SERIAL` IRQ to reset the connection. -`bufferSize` | **u32** | `60` | Number of messages that the queues will be able to store. +`timeout` | **u32** | `3` | Number of *frames* without an `II_SERIAL` IRQ to reset the connection. +`remoteTimeout` | **u32** | `5` | Number of *messages* with `0xFFFF` to mark a player as disconnected. +`bufferSize` | **u32** | `10` | Number of *messages* that the queues will be able to store. +`speed` | **u16** | `100` | Number of *1024cycles* (61.04μs) ticks between messages *(100 = 6,104ms)*. It's the interval of Timer #`sendTimerId`. +`sendTimerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending. +`waitTimerId` | **u8** *(0~3)* | `2` | GBA Timer to use for waiting. ## Makefile actions @@ -140,4 +143,4 @@ Above is not counting the additional CPU time that must be spent on initiating a > Fast One-Way Transmission Beside for the actual SIO Multiplayer mode, you can also use SIO Normal mode for fast one-way data transfer from Master unit to all Child unit(s). See chapter about SIO Normal mode for details. -``` \ No newline at end of file +``` diff --git a/examples/basic/src/main.cpp b/examples/basic/src/main.cpp index c158aca..2e99a7e 100644 --- a/examples/basic/src/main.cpp +++ b/examples/basic/src/main.cpp @@ -19,6 +19,11 @@ void init() { // (2) Add the interrupt service routines irq_add(II_VBLANK, LINK_ISR_VBLANK); irq_add(II_SERIAL, LINK_ISR_SERIAL); + irq_add(II_TIMER3, LINK_ISR_TIMER); + irq_add(II_TIMER2, NULL); + + // (3) Initialize the library + linkConnection->activate(); } int main() { @@ -27,9 +32,9 @@ int main() { u16 data[LINK_MAX_PLAYERS]; while (1) { - // (3) Send/read messages messages + // (4) Send/read messages messages u16 keys = ~REG_KEYS & KEY_ANY; - u16 message = (keys << 1) | 1; + u16 message = keys + 1; linkConnection->send(message); auto linkState = linkConnection->linkState.get(); @@ -39,7 +44,7 @@ int main() { for (u32 i = 0; i < linkState->playerCount; i++) { while (linkState->hasMessage(i)) - data[i] = linkState->readMessage(i) >> 1; + data[i] = linkState->readMessage(i) - 1; output += "Player " + std::to_string(i) + ": " + std::to_string(data[i]) + "\n"; diff --git a/examples/full/src/main.cpp b/examples/full/src/main.cpp index 5d6ccac..d651433 100644 --- a/examples/full/src/main.cpp +++ b/examples/full/src/main.cpp @@ -9,7 +9,7 @@ void setUpInterrupts(); void printTutorial(); static std::shared_ptr engine{new GBAEngine()}; static std::unique_ptr testScene{new TestScene(engine)}; -LinkConnection* linkConnection = new LinkConnection(false); +LinkConnection* linkConnection = new LinkConnection(); int main() { setUpInterrupts(); @@ -57,11 +57,11 @@ inline void ISR_reset() { inline void setUpInterrupts() { irq_init(NULL); - // VBlank + // LinkConnection irq_add(II_VBLANK, LINK_ISR_VBLANK); - - // Link connection irq_add(II_SERIAL, LINK_ISR_SERIAL); + irq_add(II_TIMER3, LINK_ISR_TIMER); + irq_add(II_TIMER2, NULL); // A+B+START+SELECT REG_KEYCNT = 0b1100000000001111; @@ -74,10 +74,10 @@ void printTutorial() { DEBULOG("START: turn on connection"); DEBULOG("(on connection, p1 sends 999)"); DEBULOG(""); - DEBULOG("A: send 555 once per frame"); - DEBULOG("B: send counter once"); - DEBULOG("L: send 1, then 2"); - DEBULOG("R: send 43981, then 257"); + DEBULOG("A: send counter++ (once)"); + DEBULOG("B: send counter++ (cont)"); + DEBULOG("L: send counter++ twice (once)"); + DEBULOG("R: send counter++ twice (cont)"); DEBULOG("SELECT: force lag (9k lines)"); DEBULOG("DOWN: turn off connection"); DEBULOG(""); diff --git a/examples/full/src/scenes/TestScene.cpp b/examples/full/src/scenes/TestScene.cpp index aa6eec8..8029943 100644 --- a/examples/full/src/scenes/TestScene.cpp +++ b/examples/full/src/scenes/TestScene.cpp @@ -42,6 +42,8 @@ void TestScene::tick(u16 keys) { if (engine->isTransitioning()) return; + frameCounter++; + // check keys aHandler->setIsPressed(keys & KEY_A); bHandler->setIsPressed(keys & KEY_B); @@ -67,24 +69,21 @@ void TestScene::tick(u16 keys) { // determine which value should be sent u16 value = LINK_NO_DATA; - if (!initialized && linkConnection->linkState->currentPlayerId == 1) { + if (!initialized && linkState->currentPlayerId == 1) { initialized = true; value = 999; } - if (aHandler->getIsPressed()) - value = 555; - if (bHandler->hasBeenPressedNow()) { + if (aHandler->getIsPressed() || bHandler->hasBeenPressedNow()) { counter++; value = counter; } // send data - if (lHandler->hasBeenPressedNow()) { - send(1); - send(2); - } else if (rHandler->hasBeenPressedNow()) { - send(43981); - send(257); + if (rHandler->getIsPressed() || lHandler->hasBeenPressedNow()) { + counter++; + send(counter); + counter++; + send(counter); } else if (value != LINK_NO_DATA) send(value); @@ -93,7 +92,8 @@ void TestScene::tick(u16 keys) { for (u32 i = 0; i < linkState->playerCount; i++) while (linkState->hasMessage(i)) { u16 message = linkState->readMessage(i); - if (i != linkState->currentPlayerId) - DEBULOG("<-p" + asStr(i) + ": " + asStr(message)); + if (i != linkState->currentPlayerId && message != 10000) + DEBULOG("<-p" + asStr(i) + ": " + asStr(message) + " (frame " + + asStr(frameCounter) + ")"); } } diff --git a/examples/full/src/scenes/TestScene.h b/examples/full/src/scenes/TestScene.h index 90b4067..a2c5a5f 100644 --- a/examples/full/src/scenes/TestScene.h +++ b/examples/full/src/scenes/TestScene.h @@ -23,6 +23,7 @@ class TestScene : public Scene { u32 counter = 0; bool isConnected = false; bool initialized = false; + u32 frameCounter = 0; void log(std::string text); }; diff --git a/lib/LinkConnection.h b/lib/LinkConnection.h index d1dd372..3f0c6cf 100644 --- a/lib/LinkConnection.h +++ b/lib/LinkConnection.h @@ -1,6 +1,7 @@ #ifndef LINK_CONNECTION_H #define LINK_CONNECTION_H +#include #include #include #include @@ -11,9 +12,14 @@ #define LINK_MAX_PLAYERS 4 #define LINK_DISCONNECTED 0xFFFF #define LINK_NO_DATA 0x0 -#define LINK_TRANSFER_VCOUNT_WAIT 2 #define LINK_DEFAULT_TIMEOUT 3 -#define LINK_DEFAULT_BUFFER_SIZE 60 +#define LINK_DEFAULT_REMOTE_TIMEOUT 5 +#define LINK_DEFAULT_BUFFER_SIZE 10 +#define LINK_DEFAULT_SPEED 100 +#define LINK_DEFAULT_SEND_TIMER_ID 3 +#define LINK_DEFAULT_WAIT_TIMER_ID 2 +#define LINK_TRANSFER_WAIT_CYCLES 1000 +#define LINK_BASE_FREQUENCY TM_FREQ_1024 #define LINK_BIT_SLAVE 2 #define LINK_BIT_READY 3 #define LINK_BITS_PLAYER_ID 4 @@ -31,10 +37,15 @@ // Usage: // - 1) Include this header in your main.cpp file and add: // LinkConnection* linkConnection = new LinkConnection(); -// - 2) Add the interrupt service routines: +// - 2) Add the required interrupt service routines: +// irq_init(NULL); // irq_add(II_VBLANK, LINK_ISR_VBLANK); // irq_add(II_SERIAL, LINK_ISR_SERIAL); -// - 3) Send/read messages by using: +// irq_add(II_TIMER3, LINK_ISR_TIMER); +// irq_add(II_TIMER2, NULL); +// - 3) Initialize the library with: +// linkConnection->activate(); +// - 4) Send/read messages by using: // linkConnection->send(...); // linkConnection->linkState @@ -43,15 +54,19 @@ // (they mean 'disconnected' and 'no data' respectively) void LINK_ISR_VBLANK(); +void LINK_ISR_TIMER(); void LINK_ISR_SERIAL(); u16 LINK_QUEUE_POP(std::queue& q); void LINK_QUEUE_CLEAR(std::queue& q); +const u16 LINK_TIMER_IRQ_IDS[] = {IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2, + IRQ_TIMER3}; struct LinkState { u8 playerCount; u8 currentPlayerId; std::queue _incomingMessages[LINK_MAX_PLAYERS]; std::queue _outgoingMessages; + int _timeouts[LINK_MAX_PLAYERS]; bool _IRQFlag; u32 _IRQTimeout; @@ -81,18 +96,22 @@ class LinkConnection { }; std::unique_ptr linkState{new LinkState()}; - explicit LinkConnection(bool startNow = true, - BaudRate baudRate = BAUD_RATE_3, + explicit LinkConnection(BaudRate baudRate = BAUD_RATE_3, u32 timeout = LINK_DEFAULT_TIMEOUT, - u32 bufferSize = LINK_DEFAULT_BUFFER_SIZE) { + u32 remoteTimeout = LINK_DEFAULT_REMOTE_TIMEOUT, + u32 bufferSize = LINK_DEFAULT_BUFFER_SIZE, + u16 speed = LINK_DEFAULT_SPEED, + u8 sendTimerId = LINK_DEFAULT_SEND_TIMER_ID, + u8 waitTimerId = LINK_DEFAULT_WAIT_TIMER_ID) { this->baudRate = baudRate; this->timeout = timeout; + this->remoteTimeout = remoteTimeout; this->bufferSize = bufferSize; + this->speed = speed; + this->sendTimerId = sendTimerId; + this->waitTimerId = waitTimerId; - if (startNow) - activate(); - else - stop(); + stop(); } bool isActive() { return isEnabled; } @@ -115,77 +134,100 @@ class LinkConnection { push(linkState->_outgoingMessages, data); } - bool isReady() { - return isBitHigh(LINK_BIT_READY) && !isBitHigh(LINK_BIT_ERROR); - } - void _onVBlank() { - if (!isEnabled || resetIfNeeded()) + if (!isEnabled) return; - if (!linkState->_IRQFlag) { + if (!linkState->_IRQFlag) linkState->_IRQTimeout++; - if (linkState->_IRQTimeout >= timeout) - reset(); - else if (isMaster()) - transfer(LINK_NO_DATA, true); - } - linkState->_IRQFlag = false; } + void _onTimer() { + if (!isEnabled) + return; + + if (didTimeout()) { + reset(); + return; + } + + if (isMaster() && isReady() && !isSending()) + sendPendingData(); + } + void _onSerial() { - if (!isEnabled || resetIfNeeded()) + if (!isEnabled) + return; + + wait(); + + if (resetIfNeeded()) return; linkState->_IRQFlag = true; linkState->_IRQTimeout = 0; - linkState->playerCount = 0; - linkState->currentPlayerId = - (REG_SIOCNT & (0b11 << LINK_BITS_PLAYER_ID)) >> LINK_BITS_PLAYER_ID; - + u8 newPlayerCount = 0; for (u32 i = 0; i < LINK_MAX_PLAYERS; i++) { u16 data = REG_SIOMULTI[i]; if (data != LINK_DISCONNECTED) { if (data != LINK_NO_DATA) push(linkState->_incomingMessages[i], data); - linkState->playerCount++; - } else - LINK_QUEUE_CLEAR(linkState->_incomingMessages[i]); + newPlayerCount++; + linkState->_timeouts[i] = 0; + } else if (linkState->_timeouts[i] > 0) { + linkState->_timeouts[i]++; + + if (linkState->_timeouts[i] >= (int)remoteTimeout) { + LINK_QUEUE_CLEAR(linkState->_incomingMessages[i]); + linkState->_timeouts[i] = -1; + } else + newPlayerCount++; + } } - if (linkState->isConnected()) + linkState->playerCount = newPlayerCount; + linkState->currentPlayerId = + (REG_SIOCNT & (0b11 << LINK_BITS_PLAYER_ID)) >> LINK_BITS_PLAYER_ID; + + if (!isMaster()) sendPendingData(); } private: BaudRate baudRate; u32 timeout; + u32 remoteTimeout; u32 bufferSize; + u32 speed; + u8 sendTimerId; + u8 waitTimerId; bool isEnabled = false; + bool isReady() { return isBitHigh(LINK_BIT_READY); } + bool hasError() { return isBitHigh(LINK_BIT_ERROR); } + bool isMaster() { return !isBitHigh(LINK_BIT_SLAVE); } + bool isSending() { return isBitHigh(LINK_BIT_START); } + bool didTimeout() { return linkState->_IRQTimeout >= timeout; } + void sendPendingData() { transfer(LINK_QUEUE_POP(linkState->_outgoingMessages)); } - void transfer(u16 data, bool force = false) { - bool shouldNotify = isMaster() && (data != LINK_NO_DATA || force); - - if (shouldNotify) - setBitLow(LINK_BIT_START); - - wait(LINK_TRANSFER_VCOUNT_WAIT); + void transfer(u16 data) { REG_SIOMLT_SEND = data; - if (shouldNotify) + if (isMaster()) { + wait(); setBitHigh(LINK_BIT_START); + } } bool resetIfNeeded() { - if (!isReady()) { + if (!isReady() || hasError()) { reset(); return true; } @@ -202,19 +244,25 @@ class LinkConnection { void resetState() { linkState->playerCount = 0; linkState->currentPlayerId = 0; - for (u32 i = 0; i < LINK_MAX_PLAYERS; i++) + for (u32 i = 0; i < LINK_MAX_PLAYERS; i++) { LINK_QUEUE_CLEAR(linkState->_incomingMessages[i]); + linkState->_timeouts[i] = -1; + } LINK_QUEUE_CLEAR(linkState->_outgoingMessages); linkState->_IRQFlag = false; linkState->_IRQTimeout = 0; } void stop() { + stopTimer(); + LINK_SET_LOW(REG_RCNT, LINK_BIT_GENERAL_PURPOSE_LOW); LINK_SET_HIGH(REG_RCNT, LINK_BIT_GENERAL_PURPOSE_HIGH); } void start() { + startTimer(); + LINK_SET_LOW(REG_RCNT, LINK_BIT_GENERAL_PURPOSE_HIGH); REG_SIOCNT = baudRate; REG_SIOMLT_SEND = 0; @@ -222,6 +270,15 @@ class LinkConnection { setBitHigh(LINK_BIT_IRQ); } + void stopTimer() { + REG_TM[sendTimerId].cnt = REG_TM[sendTimerId].cnt & (~TM_ENABLE); + } + + void startTimer() { + REG_TM[sendTimerId].start = -speed; + REG_TM[sendTimerId].cnt = TM_ENABLE | TM_IRQ | LINK_BASE_FREQUENCY; + } + void push(std::queue& q, u16 value) { if (q.size() >= bufferSize) LINK_QUEUE_POP(q); @@ -229,19 +286,13 @@ class LinkConnection { q.push(value); } - void wait(u32 verticalLines) { - u32 lines = 0; - u32 vCount = REG_VCOUNT; - - while (lines < verticalLines) { - if (REG_VCOUNT != vCount) { - lines++; - vCount = REG_VCOUNT; - } - }; + void wait() { + REG_TM[waitTimerId].start = -LINK_TRANSFER_WAIT_CYCLES; + REG_TM[waitTimerId].cnt = TM_ENABLE | TM_IRQ | TM_FREQ_1; + IntrWait(1, LINK_TIMER_IRQ_IDS[waitTimerId]); + REG_TM[waitTimerId].cnt = 0; } - bool isMaster() { return !isBitHigh(LINK_BIT_SLAVE); } bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; } void setBitHigh(u8 bit) { LINK_SET_HIGH(REG_SIOCNT, bit); } void setBitLow(u8 bit) { LINK_SET_LOW(REG_SIOCNT, bit); } @@ -253,6 +304,10 @@ inline void LINK_ISR_VBLANK() { linkConnection->_onVBlank(); } +inline void LINK_ISR_TIMER() { + linkConnection->_onTimer(); +} + inline void LINK_ISR_SERIAL() { linkConnection->_onSerial(); }