Serial Communication (Link Cable) Tutorial

From GbdevWiki
Jump to: navigation, search

Hi, this tutorial aims to explain how you may add 2 players feature to your Game Boy game.

Game Boy 2 players mode

The Game Boy serial communication capability makes it possible for game developers to make 2 players mode for their games, you needed as a player to have two Game Boy consoles, two cartridges of a same game with 2 players mode and a link cable and you were ready to play with another player, in this tutorial I present a complete Game Boy assembly code example that highlights the concept.

The demo example

Every player first sees an 8-bit binary representation of the number zero, if one of the two players presses the Start button, the value would change to represent the value of the $FF00 register (Direction keys state), for that to be illustrating 2 players capability, every player is not seeing the value of his Game Boy's register but rather the register of the other player's Game Boy. So if a player presses the direction keys of its Game Boy he would see the value displayed in the other Game Boy changing accordingly.
The ROM can be downloaded following this link (the ROM is hosted in FreeHostia, didn't find a way to host it directly in the Wiki), you may test it using a Game Boy emulator that supports link emulation I suggest that you use No$gmb, it does link emulation in the same machine, you just have to setup controls for player 1 and 2, right click after you run the ROM and click on Link, this would split the screen into two parts every part representing a Game Boy unit, either press Start on player 1 or player 2 controls and start testing.

Exchanging input state

In the examples that I have seen in video games programming, the games are constructed using the main loop, this loop that is part of the game code and is the most important part is executed approximately 60 times a second. At every iteration, according to the input (keys or buttons pressed), the code changes the values of the memory (RAM variables, console registers, etc.) which would be reflected by the output (LCD screen) using sprites, tiles, positions in the screen, etc., this is the way Game Boy games are constructed too. On the other hand, Game Boy offers the possibility to exchange data with another Game Boy unit using a link cable, at a frequency of one byte (probably more) per main loop iteration. When a game code is being executed on a Game boy, the information that is necessary at every iteration is the input keys/buttons state, in order to achieve a 2 players mode, we need to know the input keys/buttons state of the other Game Boy unit too, and since we can fit that state in one byte (even in 6-bit), we can exchange that state so every Game Boy unit knows the state of the other in each iteration.

The registers

The way offered by the Game Boy to do a data exchange in the assembly code is by loading two registers, the $FF01 and $FF02 registers, the first register purpose is to be loaded with the byte that needs to be transferred, the second register purpose is more difficult to grasp, at least in my case, it can only be loaded with two values, $80 (10000000 in binary) or $81 (10000001 in binary), the first value means that the Game Boy will use an external clock and the second value an internal one, I'm not able to understand the meaning of this because of my limits in the electronics domain, but I was able to understand it in a practical way, it is important to know that when a Game Boy needs to transfer a byte of data to another Game Boy, the other one needs to have a byte to transfer too, it is not like one Game Boy sends a byte and the other one receives it, and in another transfer the latter sends a byte and the former receives it, it needs to be an exchange, the two Game Boy's need to send and receive in the same transfer, returning to the register if you load the register with $80, you are like initializing a transfer, no transfer is done in this moment and will never be done, unless, when in the other connected Game Boy the same register there is loaded with $81 in which moment the transfer is started and data present in the $FF01 register is exchanged between the two Game Boy's, every transfer needs to be done in this manner, one Game Boy first initializes the transfer ($FF02 = $80) and the other Game Boy starts it ($FF02 = $81), the initialization needs to be before the start.

The interrupts

Interrupt is an important concept in Game Boy programming, interrupt is a hardware signal sent to the CPU whereupon the CPU stops the execution of the program it was executing and jumps to another program (called the interrupt subroutine) and starts its execution, upon its finish it returns to continue the execution of the program it was on before the interrupt.
Usually every Game Boy game uses at least one interrupt, the V-Blank interrupt which occurs approximately 60 times a second so it is used to construct the game code main loop. The CPU jumps to the address $0040 upon the V-Blank interrupt.

For our example another interrupt will be used and it is the Serial interrupt, under a communication between two Game Boy units, when a transfer is started, around a hundred of instructions later every unit receives data sent by the other, upon this event the Serial interrupt occurs and the CPU jumps to the address $0058 and executes its code.

It is worth noting that it is possible to disable interrupts using the DI instruction and reenabling them using the EI instruction, when interrupts are disabled CPU ignores them when they occur and continues program execution normally, but these ignored interrupts are not lost but saved until interrupts are reenabled again whereupon the CPU jumps to execute every pending interrupt before returning to continue normal program execution. It is also possible to disable or enable specific interrupts by setting appropriate values in dedicated Game Boy registers, finally when a CPU enters the execution of an interrupt subroutine, interrupts are disabled automatically so you need to add the EI instruction to enable them either inside the subroutine code (if needed) or usually at the end before the RET instruction (you may directly use the RETI instruction which is equivalent to EI followed by RET).

A solution to the problem

The goal is to:
transfer a byte of data between two Game Boy's every main loop iteration, the byte contains the state of the direction keys, so if we are in one Game Boy, the state of the other Game Boy is present before a main loop code is executed so we can display the object controlled by the other player correctly.
One important reminder is that it is the same game executed in every Game Boy, there is no difference between two copies of the game, this is also the case for the Game Boy, all Game Boy's are identical, there is no such thing a Game Boy for player 1 and a Game boy for player 2.

Achieving the first transfer:
To succeed a transfer one Game boy needs to initialize it before the second one starts it. When a Game boy is turned on with a game, it first executes the initialization code, then the first iteration of the main loop, then the second and so on until it is turned off.
In the initialization code, We add the code that initializes a transfer, the value that we will transfer is 1, which we will label as SLAVECODE, let's ignore the main loop and try to visualize what will be happening in a two connected Game Boy units:
if we turn on one unit, it will initialize a transfer, then when the other unit is turned on, it will initialize a transfer too, but no transfer will be done, because no unit implied a code that start a transfer.
In the main loop code, we start by checking if a variable called SIOTYPE is different from 0:
if yes (at least one transfer has been done), we jump to execute the game code
if no (no transfer has been done yet), we check if the player presses the Start button:
if no, we skip game code and wait for the next iteration
if yes, we start a transfer this time the value will be 2 (MASTERCODE), note that in this case the starting code will overwrite the initializing code and we will effectively start a transfer.
Let's visualize our code execution again:
one Game Boy unit is turned on and the Start button isn't pressed at any moment, this results in a transfer being initialized but the main loop is without any notable effect, a couple of seconds later a second connected Game Boy is turned on, where the Start button will be pressed, in the iteration code of that moment a transfer will be started, since the condition for a successful transfer is met (initialization before start), time for a hundred instructions later first unit will receive MASTERCODE and second unit will receive SLAVECODE, Serial interrupt subroutine will be executed in every unit and in this subroutine we will change the SIOTYPE variable of the the first unit from 0 to SLAVECODE (receiving MASTERCODE means the other unit will be the master), and change the SIOTYPE variable of the second unit from 0 to MASTERCODE (receiving SLAVECODE means the other unit will be the slave).

Achieving the next transfers:
Every unit is executing the Serial interrupt upon data arrival, the moment when this is executed is very important here.
We will call the Game Boy unit that started the first transfer the master unit, and the other unit will be called the slave unit.
The master unit started the first transfer, the time that takes the data to arrive is as we said the time needed for a hundred of instructions to execute, which is equivalent approximately to 0.0002 second, while the time needed for the next main loop iteration to start which is equivalent to the time needed for the next V-Blank interrupt to occur is approximately 0.01 second, this is largely above the transport duration, we will use this fact to succeed our next transfer and the following ones, so in the Serial interrupt subroutine, we will initialize the second transfer in the slave unit (now we can execute conditional code in one machine and not the other by checking the SIOTYPE value), the value that we will be transferring is the direction keys state, which will be prepared in the main loop code and not in the serial interrupt subroutine, then when the next main loop iteration will come in the master unit, we start the transfer there with the direction keys state as the value to transfer as well, finally every upcoming transfer will be done in a same manner, initialized on the slave unit in the serial interrupt subroutine and started on the master unit in the main loop iteration.

Note: It is stated in the Game Boy Programming Manual that in serial communication depending on the method used there may be a delay of one iteration concerning the "freshness" of the data being transferred, for example if a main loop code is being executed in one Game Boy, the data that is present for that code isn't coming from the current main loop code being executed at the same time in the other Game Boy but the previous one, this is the case for the method used in our example but I find that this is not a problem, the important is to get the state of the other unit, being delayed by one loop iteration would not impact in fluidity or gameplay of the game.

Code explanation

In this final part explanations are given for the code that is implemented for our example, though only code in relation with serial communication is considered.
Before we start you can download the complete source code, it is contained in a zip file that may be downloaded following this link, you need to also download WLA DX assembler if you want to compile the source code into a Game Boy ROM, to simplify this process a batch file is given in the zip file, open the file on an editor, point to the second line and change the value C:\wladx to the path of the folder where you installed WLA DX, save and close the editor and run the batch file, if all goes well a ROM will be compiled from the source and placed in the same folder containing the batch file, now you may change the source code and run the batch file again to produce a new ROM with new changes taken in consideration.

Assembler directives

.DEFINE SIOTYPE $C000+1
.DEFINE TD $C000+2
.DEFINE RD $C000+3

We define SIOTYPE, TD and TR as three variables equivalent to the memory addresses $C001, $C002 and $C003 respectively.

.DEFINE SLAVECODE  1
.DEFINE MASTERCODE 2
.DEFINE SLAVEINIT  %10000000
.DEFINE MASTERINIT %10000001

We label the two decimal values 1 and 2 and two binary values %10000000 and %10000001 as SLAVECODE, MASTERCODE, SLAVEINIT and MASTERINIT respectively.

Initialization code

XOR A
LD (SIOTYPE), A
LD (TD), A
LD (RD), A

We initialize the three variables to 0.

CALL INITFIRSTTR

We call INITFIRSTTR subroutine.

INITFIRSTTR:    LD A, SLAVECODE
                LDH ($01), A
                LD A, SLAVEINIT
                LDH ($02), A
                RET

The subroutine initializes the first transfer, by loading the $FF02 register with %10000000 (SLAVEINIT), the value to transfer here is 1 (SLAVECODE).

The main loop

MAINLOOP:       LD A, (SIOTYPE)
                OR A
                JR NZ, GAMESTARTED
                LD A, %00010000
                LDH ($00), A
                LDH A, ($00)
                BIT 3, A
                CALL Z, STARTFIRSTTR
                JP SKIPGAMECODE
GAMESTARTED:

We check the value of SIOTYPE variable, if it is different from 0 we jump to the label GAMESTARTED, if it is equal to 0, we verify the state of the Start button, we call STARTFIRSTTR subroutine only if Start is being pressed, finally we jump to SKIPGAMECODE label.

STARTFIRSTTR:   LD A, MASTERCODE
                LDH ($01), A
                LD A, MASTERINIT
                LDH ($02), A
                RET

The subroutine overwrites the values set by the initialization subroutine which will effectively start the first transfer, $FF02 is loaded with %10000001 (MASTERINIT), $FF01 is loaded with 2 (MASTERCODE).

Serial interrupt subroutine

SERIAL:         PUSH AF
                LD A, (SIOTYPE)
                OR A
                JR NZ, LINKISUP
                LDH A, ($01)
                CP SLAVECODE
                JR Z, SETTYPE
                CP MASTERCODE
                JR Z, SETTYPE
                CALL INITFIRSTTR
                JR EXITSERIAL

To avoid a malfunction in the main loop code execution flow, we need to first save the CPU registers pairs that will be altered inside the subroutine, after that we check if SIOTYPE is different from 0 in which case we jump to the label LINKISUP, if no (meaning the subroutine being called upon the first transfer), we load the received byte into the A register, and we only go to the SETTYPE label if the value is equal to either SLAVECODE or MASTERCODE, if it is different, we recall INITFIRSTTR subroutine and exit the Serial subroutine.
Checking the value received from the transfer here is very important, because one may press the Start button while no other Game Boy is connected to its Game Boy, this will in fact start a transfer and result in the Serial subroutine being called with bogus data received, either 0 or 255 so we need to check the correctness of the value received.
If the value is not correct we restore the state where the first transfer is being initialized by calling INITFIRSTTR subroutine.

SETTYPE:        XOR 3
                LD (SIOTYPE), A
                CP SLAVECODE
                CALL Z, SLAVEINITSTR
                JR EXITSERIAL

Register A contains the received data from the first transfer, XOR 3 instruction is a technique to change the value in the A register so it becomes 2 (MASTERCODE) if it was 1 (SLAVECODE), or else 1 if it was 2, then we set the SIOTYPE variable with this value, this makes for us a differentiation between the two connected Game Boy units, the unit that started the transfer will receive SLAVECODE and store MASTERCODE in its SIOTYPE variable, it can be called the master, and the other unit will receive MASTERCODE and store SLAVECODE in its SIOTYPE variable, it can be called the slave, after that we compare the stored value so we only call the SLAVEINITSTR subroutine on the slave unit, then we exit the Serial subroutine.

SLAVEINITSTR:   LD A, (TD)
                LDH ($01), A
                LD A, SLAVEINIT
                LDH ($02), A
                RET

This subroutine is called on the slave machine to initialize the second transfer, and will be called to initialize every following transfer, the data to be transferred is the state of the direction keys and should be prepared in the main loop iteration that precedes the Serial subroutine.

LINKISUP:       LDH A, ($01)
                LD (RD), A
                LD A, (SIOTYPE)
                CP SLAVECODE
                CALL Z, SLAVEINITSTR

EXITSERIAL:     POP AF
                RETI

Entering the Serial interrupt subroutine upon the transfers that follow the first one makes the execution jump to the LINKISUP label (SIOTYPE different from 0), where we store the received data into the RD variable, call the SLAVEINITSTR subroutine on the slave machine, restore altered registers pairs values, and exit the Serial subroutine.

The main loop (continuation)

GAMESTARTED:

                LD A, %00100000
                LDH ($00), A
                LDH A, ($00)
                LD (TD), A

                LD A, (SIOTYPE)
                CP MASTERCODE
                CALL Z, MASTERSTARTSTR

SKIPGAMECODE:   LD A, (RD)
                CALL DUMPBINARY

VBLANKNOTDONE:  LD A, (VBLANKDONE)
                OR A
                JR Z, VBLANKNOTDONE

                XOR A
                LD (VBLANKDONE), A

                JP MAINLOOP

Every main loop iteration that follows the first execution of the Serial interrupt subroutine will induce a jump to the GAMESTARTED label, since SIOTYPE will be set in every machine, there we prepare the data that will be transferred which is corresponding to the direction keys state and store it in the TD variable, then we start the transfer on the master machine by calling the MASTERSTARTSTR subroutine, after that we display a binary representation of the data received from the last transfer, by loading it from RD variable into A register and calling DUMPBINARY subroutine, finally we wait for the V-Blank interrupt to occur before jumping to the next main loop iteration.

References

The Pan Docs, the Game boy Programming Manual, Link Demo code example by Jeff Frohwein (www.devrs.com) and many others.