GameBoy Advance Link Cable woes

Felix Jones
6 min readMay 13, 2021

Update: 32-bit Normal transfer works on mGBA! Only 8-bit Normal transfer is broken as of this update.

Update2: mGBA has been updated to support 8-bit Normal transfers. Nice.

Quickly writing up my findings because this has really annoyed me and I want to get something written out there for the next person who attempts this. This article almost certainly has errors and mistaken assumptions, take it as a thought-dump rather than ground truth.

The GBA homebrew community tends to stay away from touching multiplayer/link-cable related code and I’ve identified two reasons:

  • Normal8/Normal32 mode doesn’t work on emulators
  • GBATek’s description of communication protocols is utter, utter garbage

GBATek is the only public place where the GBA link protocols are described, and the description is just awful (and I suspect totally incorrect). The first communication section the reader encounters in GBATek is Normal8/32 mode, and quite unfortunately as of this writing this mode doesn’t work on emulators.

I’ll cut to the chase and say: Normal8/32 behaviour depends on the type of cable you have (so no wonder emulators don’t have it working!). CGB cables (Color Game Boy) support bidirectional Normal transfers, but AGB cables (Advance Game Boy) is one directional only. GBATek doesn’t mention this and it is somewhat alluded to in the other (archived) place where link cable documentation is described that I will not mention here (oh and that other place also has mistakes where clearly the registers used to be two 8 bit registers from OG Game Boy but were consolidated into a single 16 bit reg).

I’ll try to describe the protocol that I have working (it can likely be optimised, an exercise for the reader because I’m lazy). Registers are 0 indexed (so 16-bit reg has bit indices 0..15).

Note: As of this writing, this only works on hardware. This particular transfer mode does not work on mGBA and VBA.

Normal Serial Communication Flow

The lazy/crappy Xilefian protocol

At the start of every individual transfer:

  1. Clear REG_RCNT to zero (this register is unused).
  2. For 8-bit: Set bit 12 in REG_SIOCNT to 0 for 8-bit and set bit 13 in REG_SIOCNT to 0 because you have to. Notice they’re both zero, so you can clear the entire register if you want 8-bit.
    For 32-bit: Set bit 12 in REG_SIOCNT to 1 for 32-bit and set bit 13 in REG_SIOCNT to 0 because you have to.*
  3. For 8-bit: Write the data you want to send to REG_SIODATA8.
    For 32-bit:
    Write the data you want to send to REG_SIODATA32.**
  4. Without changing the other bits: Set bit 3 in REG_SIOCNT to 1. This is the “output state” and by writing 1 to it you’re saying “I’m actually not ready for transfers”. In my code I first read back REG_SIOCNT then modified bit 3 and then wrote it back.

And now you’re at the cross-road of different behaviour for Player1/Player2 (master/slave phrasing tends to be used, I’ll be using Player1/Player2 for a very good reason you’ll see later).

* I actually cheated in my code and said “the 8-bit/32-bit flag at bit 12 is actually a 2-bit enum across bits 12 & 13 with two possible values: 00 and 01”. Also, if anyone tells you bits 4 or 5 don’t believe them. It’s definitely bits 12 and 13.
** Or
REG_SIODATA32_L and REG_SIODATA32_H if you’re all about that 16-bit life. Oh yes, for reasons you’ll see later this stage is entirely pointless for Player2 unless you’re using a CGB link cable.

Player1 Start

Remember! We’re still at the start of the individual transfer at this point!

  1. Without changing the other bits: Set bit 0 in REG_SIOCNT to 1 to use the internal serial clock.*
  2. Without changing the other bits: Set bit 1 in REG_SIOCNT to 0 to use the 256 KHz clock.*
  3. At this point your state-machine is going to switch into the “Player1 Waiting” state (did I mention you should be using a state machine?).

* Like my other 2-bit enum cheat, in my code I combined these two steps into one with a 2-bit enum across bits 0 & 1 with the values: 00, 01, 11 for “external clock, internal 256KHz, internal 2MHz”.

Player2 Start

Still at the start of an individual transfer.

  1. Without changing the other bits: Set bit 0 in REG_SIOCNT to 0 to use the external serial clock (basically clocked by the cable, so player1 is controlling it). Bit 1 is ignored in this mode, so no need to touch it.
  2. Without changing the other bits: Set bit 3 in REG_SIOCNT to 0. If you remember from before, this is the output state. 0 here means “I’m ready to transfer now”. Player1 in its Waiting state (coming up next) should detect this (I say should because mGBA as of this writing fails at this).
  3. Without changing the other bits: Set bit 7 in REG_SIOCNT to 1. Bit 7 is the “start” flag (or “transferring data” flag), setting it to 1 should begin a transfer.
  4. At this point, switch your state-machine into a “Player2 Waiting” state, because now everything is in the hands of Player1.

Player1 Waiting

  1. Read REG_SIOCNT and test bit 2. This is the input state bit, when Player2 set its output state in the section above this bit here should change. We want to test if bit 2 is 0, if it isn’t then, uh, try again soon? Maybe implement some kind of timeout here, if the timeout happens you can reset to the beginning state of the protocol.
  2. If bit 2 is 0 and without changing the other bits: Set bit 7 in REG_SIOCNT to 1. As this point we know Player2 is ready, so Player1 can start the transfer.
  3. At this point the state-machine should change to a “Player1 Sending” state.

Player2 Waiting

  1. Read REG_SIOCNT and test bit 7. Remember, this is the “start” or “transferring” flag, it will automatically switch to 0 when the transfer is complete, so we’re testing it until it changes from 1 to 0. Implement a time-out here because if there’s no Player1 then Player2 could be waiting forever.
    Keep reading bit 7 until it is 0. That’s how we know we’ve received all the data.
  2. You can now read back REG_SIODATA8 or REG_SIODATA32 to retrieve whatever it was Player1 sent.

Player1 Sending

  1. Read REG_SIOCNT and test bit 7. Perhaps a time-out can be added here, I’m not 100% sure, I had a timeout implemented anyway because I had all sorts of bugs I was trying to figure out.
    Keep reading bit 7 until it is 0. That’s how we know sending has completed.
  2. Hey, sending has completed! Now this is the crazy part: If you are using a CGB cable, that is a Color GameBoy cable, you can now read back REG_SIODATA8 or REG_SIODATA32 to retrieve whatever it was Player2 sent.
    If you are using an AGB cable, then sucks to be you because only 1-way transfers are supported. I’m lucky enough to have a 3rd party link cable with a neat little switch on it that changes it to CGB mode.

And now both Player1 and Player2 can go back to that initial state at the very beginning for the next transfer.

This is the protocol that eventually worked for me, it can be streamlined for sure, but again that’s an exercise for the reader.

About that “Player1” and “Player2” naming

I did say we’d get back to this. Take a look at your AGB link cable, notice one end should be skinnier than the other, this is so it can link up with other cables. The wider end is Player2 and the thinner end is Player1, Normal mode data can only be sent from Player1 to Player2.

If you want two-way data transfer, use a different mode or use a CGB cable.

Aha but there is one more weird case: the protocol above can be swapped around, so Player2 runs Player1’s code and vice-versa, however on an AGB cable the transfer is still one directional from the thinner Player1 to the thicker Player2. There’s no way to send data the other direction with an AGB cable, which really sucks and is why Normal mode should probably be avoided anyway.

Emulator Issues

Here’s my mGBA issue about Normal mode not working: https://github.com/mgba-emu/mgba/issues/2172
The inferior Visual Boy Advance freaks out when attempting Normal mode serial transfers, I had Player2 reading back what it’s sending and Player1 locked up similar to mGBA.

--

--

Felix Jones

Minecraft Java Edition game developer at Mojang Studios. Vulkan, OpenGL, C/C++, Java and a million other things. [Envision. Create. Share.]