What is a GameLink!?
If you grew up in the 80s and 90s like me, you might remember the tech wonder that Nintendo Game Boy Game Link Cable was, a cable that allowed you to connect two Game Boy systems for multiplayer gaming, way before online gaming was a thing. As a kid, it was like magic, so as soon as I played a little with TinyGo Keeb zero-kb02, I decided I needed to make one for them.
TinyGo, TinyGo Keeb and zero-kb02
So the logical next question is, what does it mean any of those words?
- TinyGo is a project to bring the Go programming language to microcontrollers and modern web browsers by creating a new compiler based on LLVM
- TinyGo Keeb is how I referred to customs keyboards that have a TinyGo firmware, the series of workshops called TinyGo Keeb Tour and the most popular model zero-kb02. (it should be noted that the official repository is called tinygo-keyboard)
- zero-kb02 is probably the most popular of such keyboards (or keebs as called in jargon). Both TinyGo Keeb and zero-kb02 is the work of @sago35, none of this would be possible without him
- TinyGo Keeb Tour 2024 & 2025 is a series of workshops held in several cities of Japan where the attendees solder and build the keyboard zero-kb02 and runs a few example programs on them
TinyGo Keeb zero-kb02
Connecting two devices
We are going to use the I²C interface of the zero-kb02 to talk to each other. To make everything easier we are going to use a Waveshare zero RP2040 (it could be another board, but those are very cheap and I have many) to handle all the messages from one device to another. Connect one I²C port to each different device, like shown in the image below.
*the 5V/VCC>red wire is not needed
shown in detail the I²C connector of zero-kb02 below the USB-C
The firmware of the GameLink is very simple too. Two ring buffers that can holds several "messages", device A writes a message in buffer A, then device B read the message from buffer A and viceversa. The magic of it resides in the I2C.Listen(port) func, which configures to port to listen to incomming I²C messages. Then it's a matter to reply with an appropriate response:
for {
evt, n, err := ports[port].WaitForEvent(buf)
if err != nil {
}
switch evt {
case machine.I2CReceive: // store received message
if n > dataSize {
n = dataSize
}
stacks[port].writePtr = (stacks[port].writePtr + 1) % stackSize
for o := 0; o < n; o++ {
stacks[port].stack[stacks[port].writePtr].data[o] = buf[o]
}
for o := n; o < dataSize; o++ {
stacks[port].stack[stacks[port].writePtr].data[o] = 0
}
stacks[port].stack[stacks[port].writePtr].read = false
case machine.I2CRequest: // return the oldest unread message
portClient := (port + 1) % 2
ptr := (stacks[portClient].readPtr + 1) % stackSize
if stacks[portClient].stack[ptr].read {
ports[port].Reply([]byte{0})
continue
}
stacks[portClient].readPtr = ptr
ports[port].Reply(stacks[portClient].stack[ptr].data[:])
stacks[portClient].stack[ptr].read = true
case machine.I2CFinish:
default:
}
}
GameLink driver
And finally our TinyGo Keebs need a driver to talk to the GameLink. The driver itself will be very simple for this Proof of Concept
// Write sends a message to the GameLink to be read later by the other device connected to
func (d *Device) Write(data []uint8) error {
return d.bus.Tx(d.Address, data, nil)
}
// Read returns a message from the GameLink sent by the other device connected to
func (d *Device) Read() ([]uint8, error) {
data := make([]uint8, 10)
err := d.bus.Tx(d.Address, nil, data)
return data, err
}
two zero-kb02 connected via a GameLink
Tic-tac-four?
To showcase the GameLink I created a small Proof of Concept game of tic-tac-toe played on a 4x3 matrix, you can see and download the code in the official github repository. Please, be aware this is a PoC and as any software in the early stages, it contains multiple errors, lacks features and a lot of wrong decissions were made during its making.
demo of a tic-tac-four gameplay
This article is part of TinyGo - Qiita Advent Calendar 2024