mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-10-02 05:47:11 -05:00
Merge pull request #2 from rodri042/frame-count
Fix speed / stability issues
This commit is contained in:
13
README.md
13
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.
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -9,7 +9,7 @@ void setUpInterrupts();
|
||||
void printTutorial();
|
||||
static std::shared_ptr<GBAEngine> engine{new GBAEngine()};
|
||||
static std::unique_ptr<TestScene> 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("");
|
||||
|
||||
@@ -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) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef LINK_CONNECTION_H
|
||||
#define LINK_CONNECTION_H
|
||||
|
||||
#include <tonc_bios.h>
|
||||
#include <tonc_core.h>
|
||||
#include <tonc_memdef.h>
|
||||
#include <tonc_memmap.h>
|
||||
@@ -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<u16>& q);
|
||||
void LINK_QUEUE_CLEAR(std::queue<u16>& q);
|
||||
const u16 LINK_TIMER_IRQ_IDS[] = {IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2,
|
||||
IRQ_TIMER3};
|
||||
|
||||
struct LinkState {
|
||||
u8 playerCount;
|
||||
u8 currentPlayerId;
|
||||
std::queue<u16> _incomingMessages[LINK_MAX_PLAYERS];
|
||||
std::queue<u16> _outgoingMessages;
|
||||
int _timeouts[LINK_MAX_PLAYERS];
|
||||
bool _IRQFlag;
|
||||
u32 _IRQTimeout;
|
||||
|
||||
@@ -81,18 +96,22 @@ class LinkConnection {
|
||||
};
|
||||
std::unique_ptr<struct LinkState> 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<u16>& 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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user