25

I've been looking into implementing Gameboy Link Cable compatibility into Bizhawk (which does not support it yet) using Lua. The problem is that I can't find any mention of how it works online.

I want to know how I can modify the gameboy's memory in order to emulate link cable behavior. Nothing on the Serial Data Transfer (Link Cable) page was helpful.

How does the Gameboy know that it's being linked? What memory addresses does it utilize? What specific part of memory tells the Gameboy that no link cable is connected? IE where does the Gameboy check before say "no link cable is connected"

24

You'll need to manipulate the memory address 0xFF02, and shift the data (MSB first) into 0xFF01, as stated below. If there is no cable - ergo, no gameboy connected - then 0xFF is received, in 0xFF01.

From Serial Data Transfer (Link Cable)

Communication between two Gameboys happens one byte at a time. One Gameboy acts as the master, uses its internal clock, and thus controls when the exchange happens. The other one uses an external clock (i.e., the one inside the other Gameboy) and has no control over when the transfer happens. If it hasn't gotten around to loading up the next data byte at the time the transfer begins, the last one will go out again. Alternately, if it's ready to send the next byte but the last one hasn't gone out yet, it has no choice but to wait.

FF01 - SB - Serial transfer data (R/W)

Before a transfer, it holds the next byte that will go out. During a transfer, it has a blend of the outgoing and incoming bytes. Each cycle, the leftmost bit is shifted out (and over the wire) and the incoming bit is shifted in from the other side:

o7 o6 o5 o4 o3 o2 o1 o0
o6 o5 o4 o3 o2 o1 o0 i7
o5 o4 o3 o2 o1 o0 i7 i6
o4 o3 o2 o1 o0 i7 i6 i5
o3 o2 o1 o0 i7 i6 i5 i4
o2 o1 o0 i7 i6 i5 i4 i3
o1 o0 i7 i6 i5 i4 i3 i2
o0 i7 i6 i5 i4 i3 i2 i1
i7 i6 i5 i4 i3 i2 i1 i0

FF02 - SC - Serial Transfer Control (R/W)

 Bit 7 - Transfer Start Flag (0=No transfer is in progress or requested, 1=Transfer in progress, or requested)
 Bit 1 - Clock Speed (0=Normal, 1=Fast) ** CGB Mode Only **
 Bit 0 - Shift Clock (0=External Clock, 1=Internal Clock)

The gameboy acting as master will load up a data byte in SB and then set SC to 0x81 (Transfer requested, use internal clock). It will be notified that the transfer is complete in two ways: SC's Bit 7 will be cleared (i.e., SC will be set up 0x01), and also the Serial Interrupt handler will be called (i.e., the CPU will jump to 0x0058). The other gameboy will load up a data byte and can optionally set SC's Bit 7 (i.e., SC=0x80). Regardless of whether or not it has done this, if and when the master gameboy wants to conduct a transfer, it will happen (pulling whatever happens to be in SB at that time). The passive gameboy will have its serial interrupt handler called at the end of the transfer, and if it bothered to set SC's Bit 7, it will be cleared.

...

During a transfer, a byte is shifted in at the same time that a byte is shifted out. The rate of the shift is determined by whether the clock source is internal or external. The most significant bit is shifted in and out first. When the internal clock is selected, it drives the clock pin on the game link port and it stays high when not used. During a transfer it will go low eight times to clock in/out each bit.

The state of the last bit shifted out determines the state of the output line until another transfer takes place.

If a serial transfer with internal clock is performed and no external GameBoy is present, a value of $FF will be received in the transfer. The following code initiates the process of shifting $75 out the serial port and a byte to be shifted into $FF01:

ld   a,$75
ld  ($FF01),a
ld   a,$81
ld  ($FF02),a

The Game Boy does not support wake-on-LAN. Completion of an externally clocked serial transfer does not exit STOP mode.

This link, Everything You Always Wanted To Know About GAMEBOY, also shows the memory map (indicating the interrupt locations and the comms bytes) which will clarify the data from the first link:

GB General Memory Map*

---------------------

  Interrupt Enable Register

 --------------------------- FFFF

  Internal RAM

 --------------------------- FF80

  Empty but unusable for I/O

 --------------------------- FF4C

  I/O ports

 --------------------------- FF00

  Empty but unusable for I/O

 --------------------------- FEA0

  Sprite Attrib Memory (OAM)

 --------------------------- FE00

  Echo of 8kB Internal RAM

 --------------------------- E000

  8kB Internal RAM

 --------------------------- C000

  8kB switchable RAM bank

 --------------------------- A000

  8kB Video RAM

 --------------------------- 8000

  32kB Cartridge

 --------------------------- 0000





 * NOTE: b = bit, B = byte
  • 4
    This looks like SPI basically. – Melebius Oct 21 at 6:32
  • 2
    @Melebius Oh, it probably is just a vendor-specific implementation of it. A lot of communication protocols look alike, are based on one another, developed concurrently, or any other reason to make them similar to others out there. – Mast Oct 21 at 14:41
  • 1
    @hobbs OK, my comment intended to say: If you have an SPI library available, you’ll be probably able to utilize it here. – Melebius Oct 22 at 5:49
  • 2
    FWIW I noticed that the diagram in the SPI article @Melebius linked to exactly matches the Gameboy protocol. (i.e a distributed ring buffer) – Alex Hajnal Oct 22 at 13:49
13

Note, this is a supplement to Greenonline's answer.

Instead of emulating the bitwise transfer I would just emulate the bytewise transfer.

If some game depends on the transmission time, you need to emulate this period for the reset of bit 7 of SC. When exactly in this period you transfer the bytes doesn't matter.

If some game depends on the bitwise transfer, think about if it's worth to support it. But I'll be right astonished if such a game exists.

A shortly described (and perhaps erroneous) abstract algorithm might be:

forever
    if SC.7 becomes 1 then
        if SC.0 = 1 then
            # You are a master device.
            send(SB)
            SB = receive() # clears "received"
            SC.7 = 0
    if received() then
        # You are a slave device.
        send(SB)
        SB = receive() # fetch received byte
        SC.7 = 0

You still have to think about what happens if two masters are connected. I have no idea what happens on the hardware.

Connecting two slaves is no problem. Bytes will not be exchanged and SC.7 will remain set.

BTW, it's all in the quoted description! It "just" needs to be translated in behaviour.

  • "Instead of emulating the bitwise transfer I would just emulate the bytewise transfer." ? – DrakaSAN Oct 21 at 12:20
  • 4
    The bitwise transfer is timed by the internal or external shift clock. The transfer happens bit by bit one at a clock cycle. But presumably all games watch only SC.7 which will be reset after the full byte was transferred. So it will save a lot of work and emulation time by not emulating bitwise transfers. – the busybee Oct 21 at 13:12
  • Oh! I read too quickly and didn't noticed that it was bitwise vs bytewise, and thought there was a typo. My bad. – DrakaSAN Oct 21 at 13:27
  • I'd be more tempted to calculate it just-in-time, which collapses into the whole-byte case if software doesn't read or write while a shift is ongoing, but could also give the correct result if it does. E.g. a read is bits = (now - start) / period bits in, so the read result is (local << bits) | (remote >> (8 - bits)). There's no difference to the need to count time properly to trigger an interrupt, and you could treat writes correctly with just an extra piece of storage and a subtraction, i.e. (local << (bits - write_time)) | .... – Tommy Oct 21 at 13:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.