-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathLinkRawCable.hpp
286 lines (240 loc) · 8.47 KB
/
LinkRawCable.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#ifndef LINK_RAW_CABLE_H
#define LINK_RAW_CABLE_H
// --------------------------------------------------------------------------
// A low level handler for the Link Port (Multi-Play Mode).
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkRawCable* linkRawCable = new LinkRawCable();
// - 2) (Optional) Add the interrupt service routines: (*)
// irq_init(NULL);
// irq_add(II_SERIAL, LINK_RAW_CABLE_ISR_SERIAL);
// // (this is only required for `transferAsync`)
// - 3) Initialize the library with:
// linkRawCable->activate();
// - 4) Exchange 16-bit data with the connected consoles:
// LinkRawCable::Response data = linkRawCable->transfer(0x1234);
// // (this blocks the console indefinitely)
// - 5) Exchange data with a cancellation callback:
// LinkRawCable::Response data = linkRawCable->transfer(0x1234, []() {
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// });
// - 6) Exchange data asynchronously:
// linkRawCable->transferAsync(0x1234);
// // ...
// if (linkRawCable->getAsyncState() == LinkRawCable::AsyncState::READY) {
// LinkRawCable::Response data = linkRawCable->getAsyncData();
// // ...
// }
// --------------------------------------------------------------------------
// (*) 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)
// --------------------------------------------------------------------------
// considerations:
// - don't send 0xFFFF, it's a reserved value that means <disconnected client>
// - only transfer(...) if isReady()
// --------------------------------------------------------------------------
#ifndef LINK_DEVELOPMENT
#pragma GCC system_header
#endif
#include "_link_common.hpp"
static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v7.0.2";
#define LINK_RAW_CABLE_MAX_PLAYERS 4
#define LINK_RAW_CABLE_DISCONNECTED 0xffff
/**
* @brief A low level handler for the Link Port (Multi-Play Mode).
*/
class LinkRawCable {
private:
using u32 = unsigned int;
using u16 = unsigned short;
using u8 = unsigned char;
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
BAUD_RATE_1, // 38400 bps
BAUD_RATE_2, // 57600 bps
BAUD_RATE_3 // 115200 bps
};
struct Response {
u16 data[LINK_RAW_CABLE_MAX_PLAYERS];
int playerId = -1; // (-1 = unknown)
};
enum AsyncState { IDLE, WAITING, READY };
private:
static constexpr Response EMPTY_RESPONSE = {
{LINK_RAW_CABLE_DISCONNECTED, LINK_RAW_CABLE_DISCONNECTED,
LINK_RAW_CABLE_DISCONNECTED, LINK_RAW_CABLE_DISCONNECTED},
-1};
public:
/**
* @brief Returns whether the library is active or not.
*/
[[nodiscard]] bool isActive() { return isEnabled; }
/**
* @brief Activates the library in a specific `baudRate`.
* @param baudRate One of the enum values from `LinkRawCable::BaudRate`.
* Defaults to `LinkRawCable::BaudRate::BAUD_RATE_1` (38400 bps).
*/
void activate(BaudRate baudRate = BaudRate::BAUD_RATE_1) {
this->baudRate = baudRate;
this->asyncState = IDLE;
this->asyncData = EMPTY_RESPONSE;
setMultiPlayMode();
isEnabled = true;
}
/**
* @brief Deactivates the library.
*/
void deactivate() {
isEnabled = false;
setGeneralPurposeMode();
baudRate = BaudRate::BAUD_RATE_1;
asyncState = IDLE;
asyncData = EMPTY_RESPONSE;
}
/**
* @brief Exchanges `data` with the connected consoles. Returns the received
* data from each player, including the assigned player ID.
* @param data The value to be sent.
* \warning Blocks the system until completion.
*/
Response transfer(u16 data) {
return transfer(data, []() { return false; });
}
/**
* @brief Exchanges `data` with the connected consoles. Returns the received
* data from each player, including the assigned player ID.
* @param data The value to be sent.
* @param cancel A function that will be continuously invoked. If it returns
* `true`, the transfer will be aborted and the response will be empty.
* \warning Blocks the system until completion or cancellation.
*/
template <typename F>
Response transfer(u16 data, F cancel, bool _async = false) {
if (asyncState != IDLE)
return EMPTY_RESPONSE;
setData(data);
if (_async) {
asyncState = WAITING;
setInterruptsOn();
} else {
setInterruptsOff();
}
startTransfer();
if (_async)
return EMPTY_RESPONSE;
while (isSending())
if (cancel()) {
stopTransfer();
return EMPTY_RESPONSE;
}
if (isReady() && !hasError())
return getData();
return EMPTY_RESPONSE;
}
/**
* @brief Schedules a `data` transfer and returns. After this, call
* `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the
* async data, normal `transfer(...)`s won't do anything!
* @param data The value to be sent.
*/
void transferAsync(u16 data) {
transfer(data, []() { return false; }, true);
}
/**
* @brief Returns the state of the last async transfer
* @return One of the enum values from `LinkRawCable::AsyncState`.
*/
[[nodiscard]] AsyncState getAsyncState() { return asyncState; }
/**
* @brief If the async state is `READY`, returns the remote data and switches
* the state back to `IDLE`. If not, returns an empty response.
*/
[[nodiscard]] Response getAsyncData() {
if (asyncState != READY)
return EMPTY_RESPONSE;
Response data = asyncData;
asyncState = IDLE;
return data;
}
/**
* @brief Returns the current `baudRate`.
*/
[[nodiscard]] BaudRate getBaudRate() { return baudRate; }
/**
* @brief Returns whether the console is connected as master or not. Returns
* garbage when the cable is not properly connected.
*/
[[nodiscard]] bool isMaster() { return !isBitHigh(BIT_SLAVE); }
/**
* @brief Returns whether all connected consoles have entered the multiplayer
* mode. Returns garbage when the cable is not properly connected.
*/
[[nodiscard]] bool isReady() { return isBitHigh(BIT_READY); }
/**
* @brief This method is called by the SERIAL interrupt handler.
* \warning This is internal API!
*/
void _onSerial() {
if (!isEnabled || asyncState != WAITING)
return;
setInterruptsOff();
asyncState = READY;
asyncData = EMPTY_RESPONSE;
if (isReady() && !hasError())
asyncData = getData();
}
private:
BaudRate baudRate = BaudRate::BAUD_RATE_1;
AsyncState asyncState = IDLE;
Response asyncData = EMPTY_RESPONSE;
volatile bool isEnabled = false;
void setMultiPlayMode() {
Link::_REG_RCNT = Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_HIGH);
Link::_REG_SIOCNT = (1 << BIT_MULTIPLAYER);
Link::_REG_SIOCNT |= baudRate;
Link::_REG_SIOMLT_SEND = 0;
}
void setGeneralPurposeMode() {
Link::_REG_RCNT = (Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_LOW)) |
(1 << BIT_GENERAL_PURPOSE_HIGH);
}
void setData(u16 data) { Link::_REG_SIOMLT_SEND = data; }
Response getData() {
Response response = EMPTY_RESPONSE;
for (u32 i = 0; i < LINK_RAW_CABLE_MAX_PLAYERS; i++)
response.data[i] = Link::_REG_SIOMULTI[i];
response.playerId =
(Link::_REG_SIOCNT & (0b11 << BITS_PLAYER_ID)) >> BITS_PLAYER_ID;
return response;
}
bool hasError() { return isBitHigh(BIT_ERROR); }
bool isSending() { return isBitHigh(BIT_START); }
void startTransfer() { setBitHigh(BIT_START); }
void stopTransfer() { setBitLow(BIT_START); }
void setInterruptsOn() { setBitHigh(BIT_IRQ); }
void setInterruptsOff() { setBitLow(BIT_IRQ); }
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 LinkRawCable* linkRawCable;
/**
* @brief SERIAL interrupt handler.
*/
inline void LINK_RAW_CABLE_ISR_SERIAL() {
linkRawCable->_onSerial();
}
#endif // LINK_RAW_CABLE_H