A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port. Its main purpose is to provide multiplayer support to homebrew games. C bindings are also included for compatibility.
- 👾 LinkCable.hpp: The classic 16-bit Multi-Play mode (up to 4 players) using a GBA Link Cable!
- 💻 LinkCableMultiboot.hpp: Send Multiboot software (small 256KiB ROMs) to other GBAs with no cartridge!
- 🔧👾 LinkRawCable.hpp: A minimal low-level API for the 16-bit Multi-Play mode.
- 📻 LinkWireless.hpp: Connect up to 5 consoles with the Wireless Adapter!
- 📡 LinkWirelessMultiboot.hpp: Send Multiboot software (small 256KiB ROMs) to other GBAs over the air!
- 🔧📻 LinkRawWireless.hpp: A minimal low-level API for the Wireless Adapter.
- 🔧🏛️ LinkWirelessOpenSDK.hpp: An abstraction of the official software level protocol of the Wireless Adapter.
- 🌎 LinkUniversal.hpp: Add multiplayer support to your game, both with 👾 Link Cables and 📻 Wireless Adapters, using the same API!
- 🔌 LinkGPIO.hpp: Use the Link Port however you want to control any device (like LEDs, rumble motors, and that kind of stuff)!
- 🔗 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.hpp: Easily connect to any PC using a USB to UART cable!
- 🟪 LinkCube.hpp: Exchange data with a Wii or a GameCube using the classic Joybus protocol!
- 💳 LinkCard.hpp: Receive DLCs in the form of e-Reader cards!
- 📱 LinkMobile.hpp: Connect to the internet using the Mobile Adapter GB, brought back to life thanks to the REON project!
- 📺 LinkIR.hpp: Turn down the volume of your neighbor's TV using the Infrared Adapter!
- 🖱️ LinkPS2Mouse.hpp: Connect a PS/2 mouse to the GBA for extended controls!
- ⌨️ LinkPS2Keyboard.hpp: Connect a PS/2 keyboard to the GBA for extended controls!
(click on the emojis for documentation)
Created by [r]labs.
💬 Check out my other GBA projects: piuGBA, beat-beast, gba-remote-play, gba-flashcartio.
- Copy the contents of the lib/ folder into a directory that is part of your project's include path. Then,
#include
the library you need, such as LinkCable.hpp, in your project. No external dependencies are required. - For initial instructions and setup details, refer to the large comment block at the beginning of each file, the documentation included here, and the provided examples.
- Check out the examples/ folder.
- Compiled ROMs are available in Releases.
- The example code uses libtonc (and libugba for interrupts), but any library can be used.
- The examples can be tested on real GBAs or using emulators.
- The LinkUniversal_real ROM tests a more real scenario using an audio player, a background video, text and sprites.
- The
LinkCableMultiboot_demo
andLinkWirelessMultiboot_demo
examples can bootstrap all other examples, allowing you to test with multiple units even if you only have one flashcart.
- Check out the FAQ.
The files use some compiler extensions, so using GCC is required.
The example ROMs were compiled with devkitARM, using GCC
14.1.0
with-std=c++17
as the standard and-Ofast
as the optimization level.
To learn implementation details, you might also want to check out the docs/ folder, which contains important documentation.
Running ./compile.sh
builds all the examples with the right configuration.
The project must be in a path without spaces; devkitARM and some *nix commands are required.
All the projects understand these Makefile actions:
make [ clean | build | start | rebuild | restart ]
- To use the libraries in a C project, include the files from the lib/c_bindings/ directory.
- For documentation, use this
README.md
file or comments inside the main C++ files. - Some libraries may not be available in C.
- Some methods/overloads may not be available in the C implementations.
- Unlike the main libraries, C bindings depend on libtonc.
// Instantiating
LinkSomething* linkSomething = new LinkSomething(a, b); // C++
LinkSomethingHandle cLinkSomething = C_LinkSomething_create(a, b); // C
// Calling methods
linkSomething->method(a, b); // C++
C_LinkSomething_method(cLinkSomething, a, b); // C
// Destroying
delete linkSomething; // C++
C_LinkSomething_destroy(cLinkSomething); // C
(aka Multi-Play Mode)
⬆️ This is the Link Port mode that games use for multiplayer.
The library uses message queues to send/receive data and transmits when it's possible. As it uses CPU interrupts, the connection is alive even if a console drops a frame or gets stuck in a long iteration loop. After such an event, all nodes end up receiving all the pending messages.
new LinkCable(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
baudRate |
BaudRate | BaudRate::BAUD_RATE_1 |
Sets a specific baud rate. |
timeout |
u32 | 3 |
Maximum number of frames without receiving data from other player before marking them as disconnected or resetting the connection. |
interval |
u16 | 50 |
Number of 1024-cycle ticks (61.04μs) between transfers (50 = 3.052ms). It's the interval of Timer #sendTimerId . Lower values will transfer faster but also consume more CPU. You can use Link::perFrame(...) to convert from transfers per frame to interval values. |
sendTimerId |
u8 (0~3) | 3 |
GBA Timer to use for sending. |
You can update these values at any time without creating a new instance:
- Call
deactivate()
. - Mutate the
config
property. - Call
activate()
.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate() |
- | Activates the library. |
deactivate() |
- | Deactivates the library. |
isConnected() |
bool | Returns true if there are at least 2 connected players. |
playerCount() |
u8 (1~4) | Returns the number of connected players. |
currentPlayerId() |
u8 (0~3) | Returns the current player ID. |
sync() |
- | Collects available messages from interrupts for later processing with read(...) . Call this method whenever you need to fetch new data, and always process all the messages before calling it again. |
waitFor(playerId) |
bool | Waits for data from player #playerId . Returns true on success, or false on disconnection. |
waitFor(playerId, cancel) |
bool | Like waitFor(playerId) , but accepts a cancel() function. The library will continuously invoke it, and abort the wait if it returns true . |
canRead(playerId) |
bool | Returns true if there are pending messages from player #playerId . Keep in mind that if this returns false , it will keep doing so until you fetch new data with sync() . |
read(playerId) |
u16 | Dequeues and returns the next message from player #playerId . If there's no data from that player, a 0 will be returned. |
peek(playerId) |
u16 | Returns the next message from player #playerId without dequeuing it. If there's no data from that player, a 0 will be returned. |
canSend() |
bool | Returns whether a send(...) call would fail due to the queue being full or not. |
send(data) |
bool | Sends data to all connected players. If data is invalid or the send queue is full, a false will be returned. |
didQueueOverflow([clear]) |
bool | Returns whether the internal queue lost messages at some point due to being full. This can happen if your queue size is too low, if you receive too much data without calling sync(...) enough times, or if you don't read(...) enough messages before the next sync() call. After this call, the overflow flag is cleared if clear is true (default behavior). |
resetTimeout() |
- | Resets other players' timeout count to 0 . Call this before reducing config.timeout . |
resetTimer() |
- | Restarts the send timer without disconnecting. Call this if you changed config.interval |
0xFFFF
and 0x0
are reserved values, so don't send them!
LINK_CABLE_QUEUE_SIZE
: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max per player). The default value is15
, which seems fine for most games.- This affects how much memory is allocated. With the default value, it's around
390
bytes. There's a double-buffered pending queue (to avoid data races),1
incoming queue and1
outgoing queue. - You can approximate the memory usage with:
(LINK_CABLE_QUEUE_SIZE * sizeof(u16) * LINK_CABLE_MAX_PLAYERS) * 3 + LINK_CABLE_QUEUE_SIZE * sizeof(u16)
<=>LINK_CABLE_QUEUE_SIZE * 26
- This affects how much memory is allocated. With the default value, it's around
(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.
Its demo (LinkCableMultiboot_demo
) has all the other gba-link-connection ROMs bundled with it, so it can be used to quickly test the library.
This version is simpler and blocks the system thread until completion. It doesn't require interrupt service routines.
Name | Return type | Description |
---|---|---|
sendRom(rom, romSize, cancel, [mode]) |
Result | Sends the rom (must be 4-byte aligned). During the handshake process, the library will continuously invoke cancel , and abort the transfer if it returns true . The romSize must be a number between 448 and 262144 , and a multiple of 16 . The mode can be either LinkCableMultiboot::TransferMode::MULTI_PLAY for GBA cable (default value) or LinkCableMultiboot::TransferMode::SPI for GBC cable. Once completed, the return value should be LinkCableMultiboot::Result::SUCCESS . |
LINK_CABLE_MULTIBOOT_PALETTE_DATA
: to control how the logo is displayed.- Format:
0b1CCCDSS1
, whereC
=color,D
=direction,S
=speed. - Default:
0b10010011
.
- Format:
This version (LinkCableMultiboot::Async
) allows more advanced use cases like playing animations and/or audio during the transfers, displaying the number of connected players and send percentage, and marking the transfer as 'ready' to start. It requires adding the provided interrupt service routines. The class is polymorphic with LinkWirelessMultiboot::Async
.
new LinkCableMultiboot::Async(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
waitForReadySignal |
bool | false |
Whether the code should wait for a markReady() call to start the actual transfer. |
mode |
TransferMode | TransferMode::MULTI_PLAY |
Either LinkCableMultiboot::TransferMode::MULTI_PLAY for GBA cable (default value) or LinkCableMultiboot::TransferMode::SPI for GBC cable. |
You can update these values at any time without creating a new instance by mutating the config
property. Keep in mind that the changes won't be applied after the next sendRom(...)
call.
Name | Return type | Description |
---|---|---|
sendRom(rom, romSize) |
bool | Sends the rom (must be 4-byte aligned). The romSize must be a number between 448 and 262144 , and a multiple of 16 . Once completed, getState() should return LinkCableMultiboot::Async::State::STOPPED and getResult() should return LinkCableMultiboot::Async::GeneralResult::SUCCESS . Returns false if there's a pending transfer or the data is invalid. |
reset() |
bool | Deactivates the library, canceling the in-progress transfer, if any. |
isSending() |
bool | Returns whether there's an active transfer or not. |
getState() |
State | Returns the current state. |
getResult([clear]) |
GeneralResult | Returns the result of the last operation. After this call, the result is cleared if clear is true (default behavior). |
getDetailedResult([clear]) |
Result | Returns the detailed result of the last operation. After this call, the result is cleared if clear is true (default behavior). |
playerCount() |
u8 (1~4) | Returns the number of connected players. |
getPercentage() |
u32 (0~100) | Returns the completion percentage. |
isReady() |
bool | Returns whether the ready mark is active or not. This is only useful when using the waitForReadySignal parameter. |
markReady() |
bool | Marks the transfer as ready. This is only useful when using the waitForReadySignal parameter. |
reset()
inside an interrupt handler!
LINK_CABLE_MULTIBOOT_ASYNC_DISABLE_NESTED_IRQ
: to disable nested IRQs. In the async version, SERIAL IRQs can be interrupted (once they clear their time-critical needs) by default, which helps prevent issues with audio engines. However, if something goes wrong, you can disable this behavior.
- This is a minimal hardware wrapper designed for the Multi-Play mode.
- It doesn't include any of the features of 👾 LinkCable, so it's not well suited for games.
- Its demo (
LinkRawCable_demo
) can help emulator developers in enhancing accuracy.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate(baudRate = BAUD_RATE_3) |
- | Activates the library in a specific baudRate (LinkRawCable::BaudRate ). |
deactivate() |
- | Deactivates the library. |
transfer(data) |
Response | Exchanges data with the connected consoles. Returns the received data, including the assigned player ID. |
transfer(data, cancel) |
Response | Like transfer(data) , but accepts a cancel() function. The library will continuously invoke it, and abort the transfer if it returns true . |
transferAsync(data) |
- | 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! |
getAsyncState() |
AsyncState | Returns the state of the last async transfer (one of LinkRawCable::AsyncState::IDLE , LinkRawCable::AsyncState::WAITING , or LinkRawCable::AsyncState::READY ). |
getAsyncData() |
Response | If the async state is READY , returns the remote data and switches the state back to IDLE . If not, returns an empty response. |
getBaudRate() |
BaudRate | Returns the current baudRate . |
isMaster() |
bool | Returns whether the console is connected as master or not. Returns garbage when the cable is not properly connected. |
isReady() |
bool | Returns whether all connected consoles have entered the multiplayer mode. Returns garbage when the cable is not properly connected. |
LinkCable
!
0xFFFF
, it's a reserved value that means disconnected client!
transfer(...)
if isReady()
!
(aka GBA Wireless Adapter)
⬆️ This is a driver for an accessory that enables wireless games up to 5 players. The inner workings of the adapter are highly unknown, but this blog post is very helpful. I've updated it to add more details about the things I learned by means of reverse engineering brute force and trial&error.
The library, by default, implements a lightweight protocol (on top of the adapter's message system) that sends packet IDs and checksums. This allows detecting disconnections, forwarding messages to all nodes, and retransmitting to prevent packet loss.
demo.mp4
new LinkWireless(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
forwarding |
bool | true |
If true , the server forwards all messages to the clients. Otherwise, clients only see messages sent from the server (ignoring other peers). |
retransmission |
bool | true |
If true , the library handles retransmission for you, so there should be no packet loss. |
maxPlayers |
u8 (2~5) | 5 |
Maximum number of allowed players. |
timeout |
u32 | 10 |
Maximum number of frames without receiving data from other player before resetting the connection. |
interval |
u16 | 75 |
Number of 1024-cycle ticks (61.04μs) between transfers (75 = 4.578ms). It's the interval of Timer #sendTimerId . Lower values will transfer faster but also consume more CPU. You can use Link::perFrame(...) to convert from transfers per frame to interval values. |
sendTimerId |
u8 (0~3) | 3 |
GBA Timer to use for sending. |
You can update these values at any time without creating a new instance:
- Call
deactivate()
. - Mutate the
config
property. - Call
activate()
.
- Most of these methods return a boolean, indicating if the action was successful. If not, you can call
getLastError()
to know the reason. Usually, unless it's a trivial error (like buffers being full), the connection with the adapter is reset and the game needs to start again. - You can check the connection state at any time with
getState()
. - Until a session starts, all actions are synchronous.
- During sessions (when the state is
SERVING
orCONNECTED
), the message transfers are IRQ-driven, sosend(...)
andreceive(...)
won't waste extra cycles. Though there are some synchronous methods that can be called during a session:serve(...)
, to update the broadcast data.closeServer()
, to make it the room unavailable for new players.getSignalLevel(...)
, to retrieve signal levels.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate() |
bool | Activates the library. When an adapter is connected, it changes the state to AUTHENTICATED . It can also be used to disconnect or reset the adapter. |
restoreExistingConnection() |
bool | Restores the state from an existing connection on the Wireless Adapter hardware. This is useful, for example, after a fresh launch of a Multiboot game, to synchronize the library with the current state and avoid a reconnection. Returns whether the restoration was successful. On success, the state should be either SERVING or CONNECTED . This should be used as a replacement for activate() . |
deactivate([turnOff]) |
bool | Puts the adapter into a low consumption mode and then deactivates the library. It returns a boolean indicating whether the transition to low consumption mode was successful. You can disable the transition and deactivate directly by setting turnOff to true . |
serve([gameName], [userName], [gameId]) |
bool | Starts broadcasting a server and changes the state to SERVING . You can, optionally, provide a gameName (max 14 characters), a userName (max 8 characters), and a gameId (0 ~ 0x7FFF) that games will be able to read. The strings must be null-terminated character arrays. If the adapter is already serving, this method only updates the broadcast data. Updating broadcast data while serving can fail if the adapter is busy. In that case, this will return false and getLastError() will be BUSY_TRY_AGAIN . |
closeServer() |
bool | Closes the server while keeping the session active, to prevent new users from joining the room. This action can fail if the adapter is busy. In that case, this will return false and getLastError() will be BUSY_TRY_AGAIN . |
getSignalLevel(response) |
bool | Retrieves the signal level of each player (0-255), filling the response struct. For hosts, the array will contain the signal level of each client in indexes 1-4. For clients, it will only include the index corresponding to the currentPlayerId() . For clients, this action can fail if the adapter is busy. In that case, this will return false and getLastError() will be BUSY_TRY_AGAIN . For hosts, you already have this data, so it's free! |
getServers(servers, serverCount, [onWait]) |
bool | Fills the servers array with all the currently broadcasting servers. This action takes 1 second to complete, but you can optionally provide an onWait() function which will be invoked each time VBlank starts. |
getServersAsyncStart() |
bool | Starts looking for broadcasting servers and changes the state to SEARCHING . After this, call getServersAsyncEnd(...) 1 second later. |
getServersAsyncEnd(servers, serverCount) |
bool | Fills the servers array with all the currently broadcasting servers. Changes the state to AUTHENTICATED again. |
connect(serverId) |
bool | Starts a connection with serverId and changes the state to CONNECTING . |
keepConnecting() |
bool | When connecting, this needs to be called until the state is CONNECTED . It assigns a player ID. Keep in mind that isConnected() and playerCount() won't be updated until the first message from the server arrives. |
canSend() |
bool | Returns whether a send(...) call would fail due to the queue being full or not. |
send(data) |
bool | Enqueues data to be sent to other nodes. |
receive(messages, receivedCount) |
bool | Fills the messages array with incoming messages. |
getState() |
State | Returns the current state (one of LinkWireless::State::NEEDS_RESET , LinkWireless::State::AUTHENTICATED , LinkWireless::State::SEARCHING , LinkWireless::State::SERVING , LinkWireless::State::CONNECTING , or LinkWireless::State::CONNECTED ). |
isConnected() |
bool | Returns true if the player count is higher than 1 . |
isSessionActive() |
bool | Returns true if the state is SERVING or CONNECTED . |
isServerClosed() |
bool | Returns true if the server was closed with closeServer() . |
playerCount() |
u8 (1~5) | Returns the number of connected players. |
currentPlayerId() |
u8 (0~4) | Returns the current player ID. |
didQueueOverflow([clear]) |
bool | Returns whether the internal queue lost messages at some point due to being full. This can happen if your queue size is too low, if you receive too much data without calling receive(...) enough times, or if excessive receive(...) calls prevent the ISR from copying data. After this call, the overflow flag is cleared if clear is true (default behavior). |
resetTimeout() |
- | Resets other players' timeout count to 0 . Call this before reducing config.timeout . |
resetTimer() |
- | Restarts the send timer without disconnecting. Call this if you changed config.interval . |
getLastError([clear]) |
Error | If one of the other methods returns false , you can inspect this to know the cause. After this call, the last error is cleared if clear is true (default behavior). |
LINK_WIRELESS_QUEUE_SIZE
: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max). The default value is30
, which seems fine for most games.- This affects how much memory is allocated. With the default value, it's around
480
bytes. There's a double-buffered incoming queue and a double-buffered outgoing queue (to avoid data races). - You can approximate the memory usage with:
LINK_WIRELESS_QUEUE_SIZE * sizeof(Message) * 4
<=>LINK_WIRELESS_QUEUE_SIZE * 16
- This affects how much memory is allocated. With the default value, it's around
LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH
andLINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH
: to set the biggest allowed transfer per timer tick. Higher values will use the bandwidth more efficiently but also consume more CPU! These values must be in the range[6;21]
for servers and[2;4]
for clients. The default values are11
and4
, but you might want to set them a bit lower to reduce CPU usage.- This is measured in words (1 message = 1 halfword). One word is used as a header, so a max transfer length of 11 could transfer up to 20 messages.
LINK_WIRELESS_PUT_ISR_IN_IWRAM
: to put critical functions in IWRAM, which can significantly improve performance due to its faster access. This is disabled by default to conserve IWRAM space, which is limited, but it's enabled in demos to showcase its performance benefits.- If you enable this, make sure that
lib/iwram_code/LinkWireless.cpp
gets compiled! For example, in a Makefile-based project, verify that the directory is in yourSRCDIRS
list. - Depending on how much IWRAM you have available, you might want to tweak these knobs:
LINK_WIRELESS_PUT_ISR_IN_IWRAM_SERIAL
: (default:1
) Put the SERIAL ISR in IWRAM (recommended, since this handler runs ~20 times per frame)LINK_WIRELESS_PUT_ISR_IN_IWRAM_TIMER
: (default:1
) Put the TIMER ISR in IWRAM (not that necessary)LINK_WIRELESS_PUT_ISR_IN_IWRAM_SERIAL_LEVEL
: (default:"-Ofast"
) Optimization level for the SERIAL ISRLINK_WIRELESS_PUT_ISR_IN_IWRAM_TIMER_LEVEL
: (default:"-Ofast"
) Optimization level for the TIMER ISR
- If you enable this, make sure that
LINK_WIRELESS_ENABLE_NESTED_IRQ
: to allowLINK_WIRELESS_ISR_*
functions to be interrupted. This can be useful, for example, if your audio engine requires calling a VBlank handler with precise timing.
(aka Multiboot through Wireless Adapter)
⬆️ This tool allows sending Multiboot ROMs (small 256KiB programs that fit in EWRAM) from one GBA to up to 4 slaves, wirelessly, using a single cartridge.
Its demo (LinkWirelessMultiboot_demo
) has all the other gba-link-connection ROMs bundled with it, so it can be used to quickly test the library.
multiboot.mp4
This version is simpler and blocks the system thread until completion. It doesn't require interrupt service routines.
Name | Return type | Description |
---|---|---|
sendRom(rom, romSize, gameName, userName, gameId, players, listener, [keepConnectionAlive]) |
Result | Sends the rom . The players must be the number of consoles that will download the ROM. Once this number of players is reached, the code will start transmitting the ROM bytes. During the process, the library will continuously invoke listener (passing a LinkWirelessMultiboot::MultibootProgress object as argument), and abort the transfer if it returns true . The romSize must be a number between 448 and 262144 . It's recommended to use a ROM size that is a multiple of 16 , since this also ensures compatibility with Multiboot via Link Cable. Once completed, the return value should be LinkWirelessMultiboot::Result::SUCCESS . You can start the transfer before the player count is reached by running *progress.ready = true; in the listener callback. If keepConnectionAlive is true , the adapter won't be reset after a successful transfer, so users can continue the session using LinkWireless::restoreExistingConnection() . |
reset() |
bool | Turns off the adapter and deactivates the library. It returns a boolean indicating whether the transition to low consumption mode was successful. |
LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
: to enable logging. SetlinkWirelessMultiboot->logger
and it will be called to report the detailed state of the library. Note that this option#include
sstd::string
!
This version (LinkWirelessMultiboot::Async
) allows more advanced use cases like playing animations and/or audio during the transfers, displaying the number of connected players and send percentage, and marking the transfer as 'ready' to start. It requires adding the provided interrupt service routines. The class is polymorphic with LinkCableMultiboot::Async
.
new LinkWirelessMultiboot::Async(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
gameName |
const char* | "" |
Game name. Maximum 14 characters + null terminator. |
userName |
const char* | "" |
User name. Maximum 8 characters + null terminator. |
gameId |
u16 (0 ~ 0x7FFF) | 0x7FFF |
The Game ID to be broadcasted. |
players |
u32 (2~5) | 5 |
The number of consoles that will download the ROM. Once this number of players is reached, the code will start transmitting the ROM bytes, unless waitForReadySignal is true . |
waitForReadySignal |
bool | false |
Whether the code should wait for a markReady() call to start the actual transfer. |
keepConnectionAlive |
bool | false |
If true , the adapter won't be reset after a successful transfer, so users can continue the session using LinkWireless::restoreExistingConnection() . |
interval |
u16 | 50 |
Number of 1024-cycle ticks (61.04μs) between transfers (50 = 3.052ms). It's the interval of Timer #timerId . Lower values will transfer faster but also consume more CPU. Some audio players require precise interrupt timing to avoid crashes! Use a minimum of 30. |
timerId |
u8 (0~3) | 3 |
GBA Timer to use for sending. |
You can update these values at any time without creating a new instance by mutating the config
property. Keep in mind that the changes won't be applied after the next sendRom(...)
call.
Name | Return type | Description |
---|---|---|
sendRom(rom, romSize) |
bool | Sends the rom . The romSize must be a number between 448 and 262144 . It's recommended to use a ROM size that is a multiple of 16 , since this also ensures compatibility with Multiboot via Link Cable. Once completed, isSending() should return false and getResult() should return LinkWirelessMultiboot::Async::GeneralResult::SUCCESS . Returns false if there's a pending transfer or the data is invalid. |
reset() |
bool | Turns off the adapter and deactivates the library, canceling the in-progress transfer, if any. It returns a boolean indicating whether the transition to low consumption mode was successful. |
isSending() |
bool | Returns whether there's an active transfer or not. |
getState() |
State | Returns the current state. |
getResult([clear]) |
GeneralResult | Returns the result of the last operation. After this call, the result is cleared if clear is true (default behavior). |
getDetailedResult([clear]) |
Result | Returns the detailed result of the last operation. After this call, the result is cleared if clear is true (default behavior). |
playerCount() |
u8 (1~5) | Returns the number of connected players. |
getPercentage() |
u32 (0~100) | Returns the completion percentage. |
isReady() |
bool | Returns whether the ready mark is active or not. |
markReady() |
bool | Marks the transfer as ready. |
reset()
inside an interrupt handler!
LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
: to enable logging. SetlinkWirelessMultibootAsync->logger
and it will be called to report the detailed state of the library. Note that this option#include
sstd::string
!LINK_WIRELESS_MULTIBOOT_ASYNC_DISABLE_NESTED_IRQ
: to disable nested IRQs. In the async version, SERIAL IRQs can be interrupted (once they clear their time-critical needs) by default, which helps prevent issues with audio engines. However, if something goes wrong, you can disable this behavior.
- This is a minimal hardware wrapper designed for the Wireless Adapter.
- It doesn't include any of the features of 📻 LinkWireless, so it's not well suited for games.
- Its demo (
LinkRawWireless_demo
) can help emulator developers in enhancing accuracy.
- There's one method for every supported Wireless Adapter command:
setup
=0x17
getSystemStatus
=0x13
broadcast
=0x16
startHost
=0x19
getSignalLevel
=0x11
getSlotStatus
=0x14
pollConnections
=0x1A
endHost
=0x1B
broadcastReadStart
=0x1C
broadcastReadPoll
=0x1D
broadcastReadEnd
=0x1E
connect
=0x1F
keepConnecting
=0x20
finishConnection
=0x21
sendData
=0x24
sendDataAndWait
=0x25
receiveData
=0x26
wait
=0x27
disconnectClient
=0x30
bye
=0x3D
- Use
sendCommand(...)
to send arbitrary commands. - Use
sendCommandAsync(...)
to send arbitrary commands asynchronously.- This requires setting
LINK_RAW_WIRELESS_ISR_SERIAL
as theSERIAL
interrupt handler. - After calling this method, call
getAsyncState()
andgetAsyncCommandResult()
. - Do not call any other methods until the async state is
IDLE
again, or the adapter will desync!
- This requires setting
- When sending arbitrary commands, the responses are not parsed. The exceptions are
SendData
andReceiveData
, which have these helpers:getSendDataHeaderFor(...)
getReceiveDataResponse(...)
LinkWireless
!
LINK_RAW_WIRELESS_ENABLE_LOGGING
: to enable logging. SetlinkRawWireless->logger
and it will be called to report the detailed state of the library. Note that this option#include
sstd::string
!
⬆️ All first-party games, including the Multiboot 'bootloader' sent by the adapter, use an official software-level protocol. This class provides methods for creating and reading packets that adhere to this protocol. It's supposed to be used in conjunction with 🔧📻 LinkRawWireless.
Additionally, there's a LinkWirelessOpenSDK::MultiTransfer
class for file transfers, used by multiboot.
Name | Return type | Description |
---|---|---|
getChildrenData(response) |
ChildrenData | Parses the response and returns a struct containing all the received packets from the connected clients. |
getParentData(response) |
ParentData | Parses the response and returns a struct containing all the received packets from the host. |
createServerBuffer(fullPayload, fullPayloadSize, sequence, [targetSlots], [offset]) |
SendBuffer | Creates a buffer for the host to send a fullPayload with a valid header. If fullPayloadSize is higher than 84 (the maximum payload size), the buffer will only contain the first 84 bytes (unless an offset > 0 is used). A sequence number must be created by using LinkWirelessOpenSDK::SequenceNumber::fromPacketId(...) . Optionally, a targetSlots bit array can be used to exclude some clients from the transmissions (the default is 0b1111 ). |
createServerACKBuffer(clientHeader, clientNumber) |
SendBuffer | Creates a buffer for the host to acknowledge a header received from a certain clientNumber . |
createClientBuffer(fullPayload, fullPayloadSize, sequence, [offset]) |
SendBuffer | Creates a buffer for the client to send a fullPayload with a valid header. If fullPayloadSize is higher than 14 (the maximum payload size), the buffer will only contain the first 14 bytes (unless an offset > 0 is used). A sequence number must be created by using LinkWirelessOpenSDK::SequenceNumber::fromPacketId(...) . |
createClientACKBuffer(serverHeader) |
SendBuffer | Creates a buffer for the client to acknowledge a header received from the host. |
⬆️ A multiuse library that doesn't care whether you plug a Link Cable or a Wireless Adapter. It continuously switches between both and tries to connect to other peers, supporting the hot swapping of cables and adapters and all the features from 👾 LinkCable and 📻 LinkWireless.
demo.mp4
new LinkUniversal(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
protocol |
Protocol | AUTODETECT |
Specifies what protocol should be used (one of LinkUniversal::Protocol::AUTODETECT , LinkUniversal::Protocol::CABLE , LinkUniversal::Protocol::WIRELESS_AUTO , LinkUniversal::Protocol::WIRELESS_SERVER , LinkUniversal::Protocol::WIRELESS_CLIENT , or LinkUniversal::Protocol::WIRELESS_RESTORE_EXISTING ). |
gameName |
const char* | "" |
The game name that will be broadcasted in wireless sessions (max 14 characters). The string must be a null-terminated character array. The library uses this to only connect to servers from the same game. |
cableOptions |
CableOptions | same as LinkCable | All the 👾 LinkCable constructor parameters in one struct. |
wirelessOptions |
WirelessOptions | same as LinkWireless | All the 📻 LinkWireless constructor parameters in one struct. |
The interface is the same as 👾 LinkCable. Additionally, it supports these methods:
Name | Return type | Description |
---|---|---|
getState() |
State | Returns the current state (one of LinkUniversal::State::INITIALIZING , LinkUniversal::State::WAITING , or LinkUniversal::State::CONNECTED ). |
getMode() |
Mode | Returns the active mode (one of LinkUniversal::Mode::LINK_CABLE , or LinkUniversal::Mode::LINK_WIRELESS ). |
getProtocol() |
Protocol | Returns the active protocol (one of LinkUniversal::Protocol::AUTODETECT , LinkUniversal::Protocol::CABLE , LinkUniversal::Protocol::WIRELESS_AUTO , LinkUniversal::Protocol::WIRELESS_SERVER , LinkUniversal::Protocol::WIRELESS_CLIENT , or LinkUniversal::Protocol::WIRELESS_RESTORE_EXISTING ). |
setProtocol(protocol) |
- | Sets the active protocol . |
getWirelessState() |
LinkWireless::State | Returns the wireless state (same as 📻 LinkWireless's getState() ). |
isConnectedNow() |
bool | Like isConnected() , but returns whether there's an active connection right now, meaning that it can change between sync() calls. |
getLinkCable() |
LinkCable* | Returns the internal LinkCable instance (for advanced usage). |
getLinkWireless() |
LinkWireless* | Returns the internal LinkWireless instance (for advanced usage). |
LINK_UNIVERSAL_MAX_PLAYERS
: to set a maximum number of players. The default value is5
, but since LinkCable's limit is4
, you might want to decrease it.LINK_UNIVERSAL_GAME_ID_FILTER
: to restrict wireless connections to rooms with a specific game ID (0x0000
~0x7FFF
). The default value (0
) connects to any game ID and uses0x7FFF
when serving.
(aka General Purpose Mode)
⬆️ This is the default Link Port mode, and it allows users to manipulate pins SI
, SO
, SD
and SC
directly.
Name | Return type | Description |
---|---|---|
reset() |
- | Resets communication mode to General Purpose (same as Link::reset() ). Required to initialize the library! |
setMode(pin, direction) |
- | Configures a pin to use a direction (input or output). |
getMode(pin) |
Direction | Returns the direction set at pin . |
readPin(pin) |
bool | Returns whether a pin is HIGH or not (when set as an input). |
writePin(pin, isHigh) |
- | Sets a pin to be high or not (when set as an output). |
setSIInterrupts(isEnabled) |
- | If it isEnabled , an IRQ will be generated when SI changes from HIGH to LOW. |
getSIInterrupts() |
bool | Returns whether SI-falling interrupts are enabled or not. |
SI
terminal to an input!
reset()
when you finish doing GPIO stuff! (for compatibility with the other libraries)
(aka Normal Mode)
⬆️ This is the GBA's implementation of SPI. You can use this to interact with other GBAs or computers that know SPI.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate(mode, [dataSize]) |
- | Activates the library in a specific mode (one of LinkSPI::Mode::SLAVE , LinkSPI::Mode::MASTER_256KBPS , or LinkSPI::Mode::MASTER_2MBPS ). By default, the dataSize is 32-bit, but can be changed to LinkSPI::DataSize::SIZE_8BIT . |
deactivate() |
- | Deactivates the library. |
transfer(data) |
u32 | Exchanges data with the other end. Returns the received data. |
transfer(data, cancel) |
u32 | Like transfer(data) , but accepts a cancel() function. The library will continuously invoke it, and abort the transfer if it returns true . |
transferAsync(data, [cancel]) |
- | 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! |
getAsyncState() |
AsyncState | Returns the state of the last async transfer (one of LinkSPI::AsyncState::IDLE , LinkSPI::AsyncState::WAITING , or LinkSPI::AsyncState::READY ). |
getAsyncData() |
u32 | If the async state is READY , returns the remote data and switches the state back to IDLE . If not, returns an empty response. |
getMode() |
Mode | Returns the current mode . |
getDataSize() |
DataSize | Returns the current dataSize . |
setWaitModeActive(isActive) |
- | Enables or disables waitMode (*). |
isWaitModeActive() |
bool | Returns whether waitMode (*) is active or not. |
(*)
waitMode
: The GBA adds an extra feature over SPI. When working as master, it can check whether the other terminal is ready to receive (ready:MISO=LOW
), and wait if it's not (not ready:MISO=HIGH
). That makes the connection more reliable, but it's not always supported on other hardware units (e.g. the Wireless Adapter), so it must be disabled in those cases.
waitMode
is disabled by default.
MISO
meansSO
on the slave side andSI
on the master side.
0xFFFFFFFF
(or 0xFF
) on misuse or cancelled transfers!
The GBA operates using SPI mode 3 (CPOL=1, CPHA=1
). Here's a connection diagram that illustrates how to connect a Link Cable to a Raspberry Pi 3's SPI pins:
(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.
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) |
- | 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]) |
bool | 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. |
LINK_UART_QUEUE_SIZE
: to set the buffer size.
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.
- Black wire (
GND
) -> GBAGND
. - Green wire (
TX
) -> GBASI
. - White wire (
RX
) -> GBASO
.
(aka JOYBUS Mode)
⬆️ This is the GBA's implementation of JOYBUS, in which users connect the console to a GameCube (or Wii with GC ports) using an official adapter. The library can be tested using Dolphin/mGBA and gba-joybus-tester.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate() |
- | Activates the library. |
deactivate() |
- | Deactivates the library. |
wait() |
bool | Waits for data. Returns true on success, or false on JOYBUS reset. |
wait(cancel) |
bool | Like wait() , but accepts a cancel() function. The library will invoke it after every SERIAL interrupt, and abort the wait if it returns true . |
canRead() |
bool | Returns true if there are pending received values to read. |
read() |
u32 | Dequeues and returns the next received value. If there's no received data, a 0 will be returned. |
peek() |
u32 | Returns the next received value without dequeuing it. If there's no received data, a 0 will be returned. |
send(data) |
- | Sends 32-bit data . If the other end asks for data at the same time you call this method, a 0x00000000 will be sent. |
pendingCount() |
u32 | Returns the number of pending outgoing transfers. |
didQueueOverflow([clear]) |
bool | Returns whether the internal queue lost messages at some point due to being full. This can happen if your queue size is too low, if you receive too much data without calling read(...) enough times, or if excessive read(...) calls prevent the ISR from copying data. After this call, the overflow flag is cleared if clear is true (default behavior). |
didReset([clear]) |
bool | Returns whether a JOYBUS reset was requested or not. After this call, the reset flag is cleared if clear is true (default behavior). |
-
LINK_CUBE_QUEUE_SIZE
: to set a custom buffer size (how many incoming and outgoing values the queues can store at max). The default value is10
, which seems fine for most games.-
This affects how much memory is allocated. With the default value, it's around
120
bytes. There's a double-buffered pending queue (to avoid data races), and 1 outgoing queue. -
You can approximate the memory usage with:
LINK_CUBE_QUEUE_SIZE * sizeof(u32) * 3
<=>LINK_CUBE_QUEUE_SIZE * 12
-
(aka e-Reader)
⬆️ The e-Reader accessory enables games to receive DLC cards from a second GBA via Link Cable. It's region-locked, but both USA and JAP adapters are supported.
Check out the #testcards folder and this README to learn how to create your cards.
Name | Return type | Description |
---|---|---|
getConnectedDevice() |
ConnectedDevice | Returns the connected device. If it returns E_READER_USA or E_READER_JAP , you should send the loader with sendLoader(...) . If it returns DLC_LOADER , you should receive scanned cards with receiveCard(...) . |
getConnectedDevice(cancel) |
ConnectedDevice | Like getConnectedDevice() , but accepts a cancel() function. The library will continuously invoke it, and abort the detection if it returns true . |
sendLoader(loader, loaderSize, cancel) |
SendResult | Sends the loader card (loaderSize bytes, must be a multiple of 32 ) and returns a SendResult . The cancel function will be continuously invoked. If it returns true , the transfer will be aborted. |
receiveCard(card, cancel) |
ReceiveResult | Receives a 1998-byte card (a byte array) from the DLC Loader and returns a ReceiveResult . The cancel function will be continuously invoked. If it returns true , the transfer will be aborted. |
(aka Mobile Adapter GB)
⬆️ This is a driver for an accessory that enables online connectivity on the GB and GBA. The protocol was reverse-engineered by the REON Team.
The original accessory was sold in Japan only and using it nowadays is hard since it relies on old tech, but REON has created an open-source implementation called libmobile, as well as support for emulators and microcontrollers.
It has two modes of operation:
- Direct call (P2P): Calling someone directly for a 2-player session, using the other person's IP address or the phone number provided by the relay server.
- ISP call (PPP): Calling an ISP number for internet access. In this mode, the adapter can open up to 2 TCP/UDP sockets and transfer arbitrary data.
new LinkMobile(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
timeout |
u32 | 600 |
Number of frames without completing a request to reset a connection. |
timerId |
u8 (0~3) | 3 |
GBA Timer to use for sending. |
You can update these values at any time without creating a new instance:
- Call
deactivate()
. - Mutate the
config
property. - Call
activate()
.
- All actions are asynchronous/nonblocking. That means, they will return
true
if nothing is awfully wrong, but the actual consequence will occur some frames later. You can callgetState()
at any time to know what it's doing. - On fatal errors, the library will transition to a
NEEDS_RESET
state. In that case, you can callgetError()
to know more details on what happened, and thenactivate()
to restart. - When calling
deactivate()
, the adapter automatically turns itself off after3
seconds of inactivity. However, to gracefully turn it off, it's recommended to callshutdown()
first, wait until the state isSHUTDOWN
, and thendeactivate()
.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate() |
- | Activates the library. After some time, if an adapter is connected, the state will be changed to SESSION_ACTIVE . If not, the state will be NEEDS_RESET , and you can retrieve the error with getError() . |
deactivate() |
- | Deactivates the library, resetting the serial mode to GPIO. Calling shutdown() first is recommended, but the adapter will put itself in sleep mode after 3 seconds anyway. |
shutdown() |
bool | Gracefully shuts down the adapter, closing all connections. After some time, the state will be changed to SHUTDOWN , and only then it's safe to call deactivate() . |
call(phoneNumber) |
bool | Initiates a P2P connection with a phoneNumber . After some time, the state will be CALL_ESTABLISHED (or ACTIVE_SESSION if the connection fails or ends). In REON/libmobile the phone number can be a number assigned by the relay server, or a 12-digit IPv4 address (for example, "127000000001" would be 127.0.0.1 ). |
callISP(password, loginId) |
bool | Calls the ISP number registered in the adapter configuration, or a default number if the adapter hasn't been configured. Then, performs a login operation using the provided password and loginId . After some time, the state will be PPP_ACTIVE . If loginId is empty and the adapter has been configured, it will use the one stored in the configuration. Both parameters are null-terminated strings (max 32 characters). |
dnsQuery(domainName, result) |
bool | Looks up the IPv4 address for a domainName (a null-terminated string, max 253 characters). It also accepts an ASCII IPv4 address, converting it into a 4-byte address instead of querying the DNS server. The result is a pointer to a LinkMobile::DNSQuery struct that will be filled with the result. When the request is completed, the completed field will be true . If an IP address was found, the success field will be true and the ipv4 field can be read as a 4-byte address. |
openConnection(ip, port, type, result) |
bool | Opens a TCP/UDP (type ) connection at the given ip (4-byte address) on the given port . The result is a pointer to a LinkMobile::OpenConn struct that will be filled with the result. When the request is completed, the completed field will be true . If the connection was successful, the success field will be true and the connectionId field can be used when calling the transfer(...) method. Only 2 connections can be opened at the same time. |
closeConnection(connectionId, type, result) |
bool | Closes an active TCP/UDP (type ) connection. The result is a pointer to a LinkMobile::CloseConn struct that will be filled with the result. When the request is completed, the completed field will be true . If the connection was closed correctly, the success field will be true . |
transfer(dataToSend, result, [connectionId]) |
bool | Requests a data transfer (up to 254 bytes) and responds the received data. The transfer can be done with the other node in a P2P connection, or with any open TCP/UDP connection if a PPP session is active. In the case of a TCP/UDP connection, the connectionId must be provided. The result is a pointer to a LinkMobile::DataTransfer struct that will be filled with the received data. It can also point to dataToSend to reuse the struct. When the request is completed, the completed field will be true . If the transfer was successful, the success field will be true . If not, you can assume that the connection was closed. |
waitFor(asyncRequest) |
bool | Waits for asyncRequest to be completed. Returns true if the request was completed && successful, and the adapter session is still alive. Otherwise, it returns false . The asyncRequest is a pointer to a LinkMobile::DNSQuery , LinkMobile::OpenConn , LinkMobile::CloseConn , or LinkMobile::DataTransfer . |
hangUp() |
bool | Hangs up the current P2P or PPP call. Closes all connections. |
readConfiguration(configurationData) |
bool | Retrieves the adapter configuration, and puts it in the configurationData struct. If the adapter has an active session, the data is already loaded, so it's instantaneous. |
getState() |
State | Returns the current state (one of LinkMobile::State::NEEDS_RESET , LinkMobile::State::PINGING , LinkMobile::State::WAITING_TO_START , LinkMobile::State::STARTING_SESSION , LinkMobile::State::ACTIVATING_SIO32 , LinkMobile::State::WAITING_32BIT_SWITCH , LinkMobile::State::READING_CONFIGURATION , LinkMobile::State::SESSION_ACTIVE , LinkMobile::State::CALL_REQUESTED , LinkMobile::State::CALLING , LinkMobile::State::CALL_ESTABLISHED , LinkMobile::State::ISP_CALL_REQUESTED , LinkMobile::State::ISP_CALLING , LinkMobile::State::PPP_LOGIN , LinkMobile::State::PPP_ACTIVE , LinkMobile::State::SHUTDOWN_REQUESTED , LinkMobile::State::ENDING_SESSION , LinkMobile::State::WAITING_8BIT_SWITCH , or LinkMobile::State::SHUTDOWN ). |
getRole() |
Role | Returns the current role in the P2P connection (one of LinkMobile::Role::NO_P2P_CONNECTION , LinkMobile::Role::CALLER , or LinkMobile::Role::RECEIVER ). |
isConfigurationValid() |
int | Returns whether the adapter has been configured or not. Returns 1 = yes, 0 = no, -1 = unknown (no session active). |
isConnectedP2P() |
bool | Returns true if a P2P call is established (the state is CALL_ESTABLISHED ). |
isConnectedPPP() |
bool | Returns true if a PPP session is active (the state is PPP_ACTIVE ). |
isSessionActive() |
bool | Returns true if the session is active. |
canShutdown() |
bool | Returns true if there's an active session and there's no previous shutdown requests. |
getDataSize() |
LinkSPI::DataSize | Returns the current operation mode (LinkSPI::DataSize ). |
getError() |
Error | Returns details about the last error that caused the connection to be aborted. |
LINK_MOBILE_QUEUE_SIZE
: to set a custom request queue size (how many commands can be queued at the same time). The default value is10
, which seems fine for most games.- This affects how much memory is allocated. With the default value, it's around
3
KB.
- This affects how much memory is allocated. With the default value, it's around
(aka Infrared Adapter)
⬆️ The Infrared Adapter was only used in one commercial game: Cyber Drive Zoids: Kiju no Senshi Hyuu, but we can now give it a better use with homebrew!
This library lets you control the IR LED directly via bitbanging, send/receive modulated 38kHz signals, and send/receive pulses in the standard NEC protocol.
To use this library, make sure that lib/iwram_code/LinkIR.cpp
gets compiled! For example, in a Makefile-based project, verify that the directory is in your SRCDIRS
list.
new LinkIR(...)
accepts these optional parameters:
Name | Type | Default | Description |
---|---|---|---|
primaryTimerId |
u8 (0~3) | 2 |
GBA Timer to use for measuring time (1/2). |
secondaryTimerId |
u8 (0~3) | 3 |
GBA Timer to use for measuring time (2/2). |
You can update these values at any time without creating a new instance:
- Call
deactivate()
. - Mutate the
config
property. - Call
activate()
.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate() |
- | Activates the library. Returns whether the adapter is connected or not. |
deactivate() |
- | Deactivates the library. |
sendNEC(address, command) |
- | Sends a NEC signal, with an 8-bit address and an 8-bit command . |
receiveNEC(address, command, [startTimeout]) |
bool | Receives a signal and returns whether it's a NEC signal or not. If it is, the address and command will be filled. Returns true on success. If a startTimeout is provided, the reception will be canceled after that number of microseconds if no signal is detected. |
parseNEC(pulses, address, command) |
bool | Tries to interpret an already received array of pulses as a NEC signal. On success, returns true and fills the address and command parameters. |
send(pulses) |
- | Sends a generic IR signal, modulating at standard 38kHz. The pulses are u16 numbers describing the signal. Even indices are marks (IR on), odd indices are spaces (IR off), and 0 ends the signal. |
receive(pulses, maxEntries, [startTimeout], [signalTimeout]) |
bool | Receives a generic IR signal modulated at standard 38kHz, up to a certain number of pulses (maxEntries ). Returns whether something was received or not. The pulses are u16 numbers describing the signal. Even indices are marks (IR on), odd indices are spaces (IR off), and 0 ends the signal. If a startTimeout is provided, the reception will be canceled after that number of microseconds if no signal is detected. If a signalTimeout is provided, the reception will be terminated after a space longer than that number of microseconds (default: 15000 ). |
setLight(on) |
- | Turns the output IR LED ON/OFF through the SO pin (HIGH = ON). Add some pauses after every 10µs! |
isEmittingLight() |
bool | Returns whether the output IR LED is ON or OFF. |
isDetectingLight() |
bool | Returns whether a remote light signal is detected through the SI pin (LOW = DETECTED) or not. |
send(...)
and receive(...)
calls!
⬆️ A PS/2 mouse driver for the GBA. Use it to add mouse support to your homebrew games.
new LinkPS2Mouse(timerId)
, where timerId
is the GBA Timer used for delays.
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. |
activate()
or report(...)
could freeze the system if nothing is connected: detecting timeouts using interrupts is the user's responsibility!
____________
|PS/2 --- GBA|
|------------|
|CLOCK -> SI |
|DATA --> SO |
|VCC ---> VCC|
|GND ---> GND|
⬆️ A PS/2 keyboard driver for the GBA. Use it to add keyboard support to your homebrew games.
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 most common keys/events are included in enums like LINK_PS2_KEYBOARD_KEY::ENTER
and LINK_PS2_KEYBOARD_EVENT::RELEASE
.
Name | Return type | Description |
---|---|---|
isActive() |
bool | Returns whether the library is active or not. |
activate() |
- | Activates the library. |
deactivate() |
- | Deactivates the library. |
____________
|PS/2 --- GBA|
|------------|
|CLOCK -> SI |
|DATA --> SO |
|VCC ---> VCC|
|GND ---> GND|
- gba-hpp: C++ header-only library for GBA development. The
_link_common.hpp
file uses part of it. - gba_mem_viewer: Memory viewer for the GBA, with manual Multiboot handling without the SWI call.
LinkCableMultiboot::Async
is based on it. - 4-e: Tool to send e-Card bins to Super Mario Advance 4 over a Link Cable.
LinkCard
is based on it. - PS2-Mouse-Arduino: PS/2 mouse driver for Arduino.
LinkPS2Mouse
is based on it.
- libtonc: Low level library for GBA hardware access. All the examples here use it.
- libugba: Low level library to develop GBA games that can also be built for PC. All the examples here work thanks to its interrupt handler.
- gba-sprite-engine: An object-oriented GBA sprite engine concept. Some examples here use a fork of it.
- butano: Modern C++ high level GBA engine. The
LinkUniversal_real
example uses it.