Wiring a USB gamepad to the Sharp X68000 through the serial port

The Sharp X68000 remains one of the most distinctive Japanese home computers ever built, and keeping one alive in a Sydney workshop or a Brisbane garage in 2025 means dealing with parts that have not rolled off a production line for two decades. Original joysticks for the platform have become genuinely scarce, and even the well-made reproductions fetch silly money on the secondary market. Most enthusiasts eventually land on the same solution: a modern USB gamepad routed through a small bridge board and presented to the machine as if it were a native peripheral. This article walks through the practical work of writing a Human68k driver that talks to a USB gamepad via the RS-232C serial port, with no soldering iron left cold and no joystick port left untouched.

The X68000 was originally designed around a dedicated joystick connector, but the serial port offers something a hardware-only joystick cannot: software control over polling, button mapping, and debounce behaviour. A microcontroller board acts as the translator, reading USB HID reports from the gamepad and shipping them over RS-232C as compact binary packets. The Human68k side needs a driver that loads at boot, claims a free logical device name, opens the UART, and turns incoming bytes into events the rest of the OS can consume. The result feels closer to a proper device than a hack, which matters when you are wiring together a system that deserves the same respect we give to a freshly recapped synth board.

Building the hardware bridge

The first decision is what runs the bridge. A Teensy 4.0 or an RP2040-based board both have USB host capability and enough serial bandwidth to keep up with a gamepad's report rate. The RP2040 is the easier entry point because the TinyUSB library already exposes HID parser support, and the chip is plentiful at Jaycar counters across the country for under twenty bucks AUD. Wiring is straightforward: USB host lines go to a female USB-A breakout, the UART pins go through a MAX3232 level shifter to bring the 3.3-volt logic up to the X68000's true RS-232 levels, and a short DB9 cable ties it all back to the machine.

Power is rarely an issue because the X68000 supplies enough current through the serial port's RTS and DTR lines to feed a small microcontroller once you add a regulator. In practice most builders tap 5 V from a free molex connector inside the case rather than relying on the serial handshake. A status LED tied to a spare GPIO pin is a quiet luxury: it flickers on each successful HID report and gives an immediate visual cue when the gamepad goes to sleep, which Xbox and PlayStation pads do with frustrating regularity if you forget to disable their idle timers.

The mechanical side matters too. A 3D-printed bracket that bolts the bridge board to the back of the X68000 keeps the cabling tidy, and a stick-on ferrite bead on the serial cable suppresses the high-frequency hash the USB host stack tends to radiate. None of this is glamorous, but it is the difference between a setup that works on a quiet Sunday arvo and one that glitches every time the fridge kicks in next door.

Choosing a serial protocol

The X68000's MPSC tops out at 38400 baud through the standard system driver, which sounds slow until you remember how small a gamepad report actually is. A typical 8-button D-pad-and-stick pad fits into a 9-byte payload: a header byte, two bytes per stick axis, and one packed byte for the buttons. At 38400 baud that is roughly 425 reports per second, far above what any human hand can produce and well clear of the polling rates the original joystick drivers ever expected.

The packet format I settled on is dead simple. Byte zero is 0xA5, used as a sync marker so the driver can recover from a missed byte. Bytes one and two carry the X and Y stick position as signed 16-bit little-endian values, byte three holds the trigger axes if present, and byte four is a bitfield of buttons with a reserved high bit so the framing stays unambiguous. A trailing 0x5A byte lets the driver verify the end of the packet and discard anything that looks corrupted.

Flow control deserves a paragraph of its own because getting it wrong causes intermittent lockups that look identical to a bug in the Human68k code. RTS/CTS handshaking is reliable but adds two wires, while XON/XOFF works fine for gamepad traffic because the data rate is so predictable. I run XON/XOFF in development because it makes debugging easier with a logic analyser, and switch to hardware handshaking once the driver is stable.

Writing the Human68k driver

Human68k drivers are conventionally shipped as .SYS files that load from CONFIG.SYS at boot, and the registration interface is refreshingly direct. The driver stub receives a request packet from the kernel, fills in a few fields describing its device name and entry points, and returns. There is no plug-and-play framework to wrestle with, which is part of the charm of the platform. Pick a name like JOYUSB, declare a logical device, and from that point on applications can open the name just like they would open COM1.

The main loop of the driver runs as a polled task rather than an interrupt service routine. The X68000's serial interrupt is notoriously fiddly because Human68k does not expose a clean way to chain custom ISRs onto the MPSC vector, and stuffing a C function into the middle of the chain usually ends in tears. Polling at a 60 Hz tick driven by the system's 16-bit timer is far more forgiving. The timer interrupt already has a well-known handler hook, the workload per poll is tiny, and the latency budget stays comfortably under one display frame.

The buffer design is the part where most first attempts go astray. A circular buffer of 64 packets is a reasonable starting point, but you have to guard the head and tail pointers against being read and written simultaneously. The cleanest solution is to disable interrupts around the buffer update, copy the new packet in, and re-enable. On Human68k the relevant primitives are di() and ei() from the standard headers, and they behave exactly the way you would expect from a mature 68000 environment. Once the buffer is in place the rest of the driver is straightforward: an IOCS call returns the latest packet, a second call clears the "new data" flag, and that is the entire surface area.

Polling rate, latency and what it feels like in-game

The interesting question is whether 60 Hz polling feels any different from the 120 Hz modern controllers claim. For almost every X68000 title the answer is no, because the games themselves were designed around slower joysticks and slower CRT response. A side-by-side test with Gradius II and Akumajou Dracula shows identical input feel between a genuine joystick and the USB bridge, which is a result that took me by surprise the first time I tried it. The bottleneck turns out to be the human, not the hardware.

That said, there are edge cases. Rhythm games, lightgun titles, and anything that depends on frame-perfect inputs can reveal the extra millisecond of latency the serial bridge introduces. The fix is to bump the polling rate by tightening the timer tick from 16 ms to 8 ms, which costs almost nothing on the 68000 and pushes the effective rate above 100 Hz. The microcontroller side needs to be ready for that, of course, so the TinyUSB host task must be prioritised above the UART transmit routine. Reordering those two tasks dropped the worst-case latency on my bench from 14 ms to 6 ms, which is the kind of detail that separates a usable rig from a perfect one.

For the audio crowd the parallel with the recapping the monitor synth module work is striking. Both projects are about restoring responsiveness to a vintage system that has drifted over the years, and both reward careful attention to small timing margins. A fresh electrolytic in the audio path and a well-tuned interrupt loop are essentially the same craft applied to different parts of the machine.

Button mapping and game-by-game behaviour

The X68000's original joystick driver only knew about two axes and two fire buttons, which left a lot of headroom for the eight or ten buttons a modern gamepad exposes. The driver carries a small mapping table in its data segment that translates the incoming bitfield into whatever the running program asks for. The default maps shoulder buttons to "fire 3" and "fire 4", stick clicks to a configurable action, and reserves the start and select buttons for a system-level menu that brings up a tiny TSR-driven configuration screen.

For shoot-em-ups the mapping is essentially one-to-one and the experience is clean. For driving games the analog triggers need attention because some titles read them as digital buttons while others interpret them as analog brake and accelerator. The driver exposes an IOCS call that lets the application declare which interpretation it wants, and the game reads the relevant bytes back. It is a small concession to modernity, but it is the kind of thing Australian retro enthusiasts have been quietly adding to their setups for years because we tend to use the same X68000 for both arcade classics and the occasional imported sim.

Storage matters here too, because mapping tables need to survive a reboot. The driver writes its configuration to a small file on the boot drive, and that file can be backed up the same way you would back up anything else on the platform. The site's SCSI zip drive backup guide covers the practical steps for keeping driver state safe across machines, and the same approach works for a stack of CF cards if that is more your style.

Real-world behaviour and common gotchas

The first thing anyone notices when they install the driver is that some commercial games still hard-code the joystick port directly through IOCS calls and ignore the COM-port device. There is no clean workaround for this from driver code alone; the only practical solution is to load a small TSR that intercepts the joystick IOCS vector and redirects the read to the new buffer. It is a 40-line assembly patch and it lives in the public domain because every X68000 coder I know has needed it at least once.

The second gotcha is cable length. RS-232C is forgiving compared to USB, but a three-metre cable is enough to start losing packets at 38400 baud if it runs alongside a CRT's flyback transformer. Keeping the cable short and well away from the monitor's yoke windings fixes it. The third gotcha is USB suspend: many controllers drop out of the bus a few seconds after the last input, and the host stack then tears down the HID parser. The bridge code re-enumerates automatically, but there is a 200 ms gap that the Human68k driver smooths over by repeating the last known packet until a new one arrives.

Finally, there is the matter of documentation. The driver source lives in a public repository along with the bridge firmware, the protocol spec, and the assembly stub for the joystick redirect. Anyone with a soldering iron, a spare Pico, and a free weekend should be able to replicate the whole stack from the README alone. That is the spirit the X68000 community has always run on, and it is the reason machines like mine still get a workout most arvos instead of gathering dust.

Approaches at a glance

Approach Hardware cost (AUD) Latency Build effort Best for
Direct joystick port mod $5–$15 ~1 ms Soldering only Purists with original pads
Serial bridge at 38400 baud $25–$40 6–8 ms Firmware + driver Most X68000 games
Serial bridge at 115200 baud with custom IOCS $40–$60 3–4 ms Firmware + driver + TSR Rhythm and sim titles
Parallel-port adapter $60–$100 <1 ms Significant code Edge cases only

If you have an X68000 sitting under the desk and a drawer full of USB pads from the last console generation, the next step is to grab a Pico, wire up a MAX3232, and start hacking. The serial port has been waiting for a use case like this since the machine was new, and the driver fits in a CONFIG.SYS line shorter than most people's boot menus. Once it is running, drop into your favourite shooter, hold down fire, and watch the screen respond exactly the way it did in 1992.

Nereid-X Expansion Board

A personally-produced LAN+USB+Memory expansion board for Sharp X68000 series computers. Multiple production runs were offered, including a final batch and a later revival reproduction run.

Power Supply Repair

X68 power supply repair and modification services were offered by the site owner, with documentation shared through diary entries spanning 2001–2006.

Server & Networking

Notes on FreeBSD administration, ISP changes, server migration, and networking topics. The site itself ran on FreeBSD with the hns diary system and Namazu search integration.

A two-ink risograph print in muted slate-blue and charcoal on off-white paper, showing a stylized desktop computer monitor beside a circuit board with soft geometric trace lines, conveying a calm retro-computing workshop atmosphere. A two-ink risograph print in deep purple and dark grey on cream stock, depicting a compact expansion card with connector ports and subtle Japanese technical annotations, evoking a hobbyist electronics bench. A two-ink risograph print in teal and charcoal on warm white paper, showing a server rack silhouette with soft network-line motifs and a small weather icon, suggesting a personal server room corner.

Get in touch

X68K.NET connects Sharp X68000 enthusiasts through community links and shared projects. Reach out with questions about the Nereid project or X68 resources.