Remember that F-Zero project I was working on for the Virtual Fall event, but had to cancel because the Virtual Boy couldn’t handle it? The plan was to implement multiplayer in the game… bigmak and MineStorm were kind enough to get a second FlashBoy into my hands, and DogP sent me one of his prototype link cables. And then I had to cancel the proejct. |-:
Well, I’m gonna put ’em to good use anyway. At some point (but not for the Virtual Fall event) I’ll bring multiplayer to the community. But for now, information on link port programming!
__________
Abstract
Communications on Virtual Boy happen in discrete units: “a communication” consists of both systems exchanging 1 byte of information, then the transaction is closed. An interrupt can be configured to fire upon completion of a communication.
An additional auxiliary control bit is transferred between systems, and sampled after a communication completes. Interrupts can be configured for this as well.
__________
Communications
Data to be transmitted is written to CPU address 0x02000008, and data received is written by the hardware to 0x0200000C. Attempting to read or write this data during communication may produce junk.
Communications are controlled via a register at CPU address 0x02000000, where the bits have the following significance:
Bit 7 (RW) = Interrupt Disable (0 on reset)
Bit 6 = Unused (1 always)
Bit 5 = Unused (1 always)
Bit 4 (RW) = Clock Select: 0 = Internal (master), 1 = External (slave) (0 on reset)
Bit 3 = Unused (1 always)
Bit 2 (W) = Initiate (if master), Listen (if slave)
Bit 1 (R) = Communication Status (0 on reset)
When communicating, one system needs to configure the port as master and the other as slave. The slave must first listen by setting bit 2, then the master initiates the transaction by setting bit 2. When the communication completes, the received data register of one system will have the same value as the transmitted data register of the other system.
If the slave system is not in listen state when the master initiates a communication, it simply will not respond to the master’s request for communication. The result is that the master’s received data register gets reset to 0x00 (since the slave didn’t send anything on the wire).
If Interrupt Disable is not set, the link port will request a CPU interrupt at address 0xFFFFFE30 after the communication completes.
__________
Auxiliary Bit
CPU bus address 0x02000004 is the aux control register, with the following bits:
Bit 7 (RW) = Interrupt Disable (1 on reset)
Bit 6 = Unused (1 always)
Bit 5 = Unused (1 always)
Bit 4 (RW) = Interrupt Compare Value (1 on reset)
Bit 3 (RW) = Aux Output Value
Bit 2 (R) = Aux Input Value
Bit 1 (RW) = Sample Aux Value
Bit 0 (R) = Aux Status
After a communication completes, this auxiliary control bit is processed. If Interrupt Disable is not set and the value of the aux bit matches Interrupt Compare Value, the link port will request a CPU interrupt at address 0xFFFFFE30. This is the same vector as the normal communication interrupt.
Aux Input Value will only be 1 if Aux Output Value is 1 on both systems. If either or both systems sets Aux Output Value to 0, then Aux Input Value on both systems will be processed as 0. The implication is that the aux bit will be set high until one of the systems pulls it down.
If Sample Aux Value is not set, Aux Input Value will not be updated.
I’m not sure when the Aux Status changes. I imagine it briefly goes up after Communication Status goes down, but I haven’t verified this.
__________
Handshake
When connecting to systems for link play, you can automatically configure the master/slave setting (such as when trading in Pokémon) with the following handshake:
1. Connect the cable
2. ???
3. Profit!
I’m really not sure how to best handle this. My first thought was to use the aux bit as a control flag, except that bit isn’t updated until after a communication occurs, meaning the master and slave settings are already configured. And if that’s the case, the software can then decide what to do based on the actual data exchanged…
There are two ways I can think of to decide who gets to be master and who gets to be slave:
1) Let the user choose. By having a simple menu like “host game” versus “join game”, you can easily have the user decide which setting to use.
2) Randomly switch between master and slave, attempting to send and receive a control value (depending on which mode is in use). The amount of time spent in each mode is random, meaning both systems won’t constantly be in the same state. If one system is master when the other is slave, the slave can receive the control value and send one back. When both systems see the control value(s), they proceed with their current configurations.
I’m open to ideas, though. If there’s a better way to establish an automatic configuration when the link cable is plugged in, speak up!
According to the official docs, the status of the COMCNT pin can be read and written at any time. Writing a 0 to bit 1 of the CCSR (0x0200_0004) will cause the COMCNT pin on the port to sink current. Reading bit 0 returns the status of the pin.
To implement negotiation, just have the game check bit 0 of the CCSR; if it’s low, it will become slave. If it’s high, it will become master and pull COMCNT low.
You could perform this on first boot, in which case the order in which you power on the connected VBs determines the master/slave relationship, or you could do it each time the “start 2-player game” menu item is chosen.
Yeah, COMCNT can be read/written at any time. Attached is the test app I threw together to verify the link cable (wiggled the connectors while it ran to make sure it didn’t drop bytes). It allows you to select master/slave and start/stop a continuous stream of data, as well as asynchronously read/write the COMCNT pin.
I quickly modified an old test app I had written, so yes, the code is ugly… but works.
To pick master/slave, I’ve done a few things. One was to simply have you select 1 or 2, which corresponded to master and slave. IIRC, on TicTacToe and 3D Battlesnake, I set them both to receive, and when one player pressed start it set itself to master and sent a start byte, so the other side would receive that and know it was slave (assuming both didn’t press start at EXACTLY the same time). I’ve also done the same thing with COMCNT, where you leave it high until someone presses start, and then pull it low to signal game start and who’s master. There are probably some other ways to do it as well.
DogP
Attachments:
DogP wrote:
I’ve also done the same thing with COMCNT, where you leave it high until someone presses start, and then pull it low to signal game start and who’s master.
Could you describe how to do this without using the phrase “source code”? (-:
Hrm… I can try.
At startup, both systems write a 1 to the CC-Wr bit (bit 1) of CCSR (0x02000004). While waiting for the game to start, keep checking for a 0 on the CC-Rd bit (bit 0) of CCSR. When someone presses start, they set the CC-Wr bit to 0, which signals to the other system that the other player has pressed start (by seeing 0 on the CC-Rd bit).
There are a lot of ways to handle it from there, but I think the safest way is to have the system that writes CC-Wr to 0 automatically set itself to remote, and wait for the other system to detect the CC-Rd bit, set itself to master, and send a handshake message.
You could also write CC-Wr to 0 and simply delay for a short amount of time, and then set yourself to master and send the handshake message… assuming that the other system will detect it and go into remote mode fairly quickly.
Of course you need to have a timeout and handshake word/sequence, in case the cable isn’t plugged in, or the other system isn’t ready, or whatever. source code.
DOH! I tried. 😉
DogP