Using wireless Bluetooth gamepads and mice on an Amiga or C64

While I am very fond of the good old joysticks like the TAC-2 and Competition Pro, they do have one drawback – a cable. Sometimes I’d just want to sit down on my couch and play some Amiga games on actual oldskool hardware without having to untangle some extra-long cables and replug everything just to get the computer to sit on my coffee table. Trip up on those cables on your way to the fridge and the Amiga goes flying on the floor.

Modern game consoles these days use wireless gamepads with a fantastically long battery life and range, but usually require either Bluetooth or some proprietary hardware to use them on a computer. Adding a full Bluetooth stack into AmigaOS 3.x doesn’t sound like a very appetising idea for a fun project, nor would it be much use on most games anyway. However – what if we used some additional hardware that already has all that, is fairly cheap and hacker friendly – the ubiquitous Raspberry Pi!

I’ll admit – it does feel a bit backwards to use a tiny 1GHz/512MB computer just to enable the use of wireless gamepads on a 30-year old 8MHz/1MB computer. However, the flexibility of the RPi is just unbeatable when you can have a full Linux system with a development environment right on that tiny little circuit board.

My basic idea was to use the built-in Bluetooth hardware of a Raspberry Pi Zero W along with the Bluetooth stack on the Linux kernel to read input from my gamepads and mice, then use the GPIO pins to emulate two Atari-style DB9 joysticks or an Amiga-style mouse with rotary encoders. The nice thing about this is that it works with any computer that has DB9 joystick ports: C64, Atari ST, MSX, ZX Spectrum – the list goes on.

Pull-ups on an A500 (thanks to Amigawiki.de)

While doing some research prior to starting the project, a slight show-stopper was brought to my attention. The GPIO pins on the RPi use 3.3V logic levels and are not safe to use with +5V logic levels of the DB9 joystick port. Because the pins on the port are active-high, at least Amiga A500 and A1200 models use 4.7KΩ resistors as pull-ups to +5V. Without any level conversion, connecting the DB9 pins directly to a RPi would probably kill the GPIO pins or worse.

Rather than slap a level converting buffer on the GPIO pins, I opted to go for a separate I/O extender board that I can neatly sandwich on top of the RPi. To emulate two joysticks with 1 or 2 fire buttons, a minimum of 12 I/O pins are required. The IO Pi Zero from AB Electronics in the UK seemed like a perfect solution – inexpensive, simple I2C protocol, has 16 I/O pins and stacks perfectly on top of a RPi Zero W.

Raspberry Pi Zero W and 16-channel IO Pi Zero

Getting Bluetooth LE and PS3 controllers to work on Raspbian

Before writing any code, I first needed to test that I can actually pair and connect the Raspberry Pi with the two PS3 Sixaxis controllers and a Microsoft Bluetooth Mouse 3600 I have. This was actually a fairly necessary step, because absolutely nothing seemed to work out-of-the-box. The bluez package bundled with the latest stable Raspbian is hopelessly out of date and has very buggy support for both Bluetooth LE and simplified pairing protocols. So the first order of business was to download the latest version (5.46 at the time), compile it and replace the old one.

Rather than install the new version of bluez, I chose to just overwrite all the files of the older version. This way I can have the required firmware and driver packages for the Raspberry Pi installed (bluez-firmware and pi-bluetooth), along with all the udev configuration and startup scripts from the main bluez package.

I started by downloading the latest source package from the Linux kernel archive:

To build the binaries from the source, Debian packages libglib2.0-dev, libdbus-1-dev, libudev1-dev, libreadline6-dev and libical-dev are required. The Makefiles for building can be generated using autoconf with the following command line:

Once the configuration has completed successfully, run make followed by make install.

Note that bluetoothd seems to be installed into /usr/libexec/bluetooth/ rather than /usr/lib/bluetooth which the deb-package uses. Stop the bluetooth service and overwrite the old binary with the newly compiled one:

At this point, it’s easiest to just reboot Linux on the Raspberry Pi and then see if the Bluetooth on the RPi is more receptive to the devices I have.

While pairing with the Microsoft mouse just failed every time on the older version, the latest one works without any trouble. All that’s needed is to do is to start bluetoothctl and pair up with the mouse:

On a few occasions I did have bluetoothd crashing while attempting to pair, requiring a sudo service bluetooth restart to get it back up. This also seemed to sometimes help when the kernel was throwing a stream of Bluetooth: SMP security requested but not available errors.

The PS3 Sixaxis controllers are a bit more difficult because they don’t support wireless pairing at all. Instead, they must be paired over USB before they even acknowledge the RPi. The procedure that works for me is as follows:

If the controller is on, power off the Sixaxis controller by holding the PS button down for 10 seconds.

Connect the controller to the USB port on the Raspberry Pi with a suitable cable and adapter combo.

Make note that the four LEDs on the controller will do the “slow blink” to indicate charging. Then press the PS button once. The LEDs should now change to indicate controller number – usually ‘1’.

Disconnect the controller from the USB cable. The LEDs will now change to the “fast blink” indicating that it is searching for the host.

Start bluetoothctl on the Raspberry Pi, type scan on, wait for the address of the controller to be discovered, then scan off.

Note that bluetoothctl will show “Paired: no” for the controller even though it is connected and the HID devices are available.

Check the LEDs on the controller to see if it now displays its number. If it is still doing the “fast blink” and the HID device isn’t registered (runcat /proc/bus/input/devices to see if the controller is shown), it may help to run again:

sudo hciconfig hci0 down
sudo hciconfig hci0 up

The PS3 controller will probably power off after the interface goes down, so press the PS button again after the interface is back up. After this, you should both see the LEDs show correct number and the HID device appear.

And we’re done – hooray!

I wouldn’t call that a particularly user-friendly way of doing it, really. Also note that if you even briefly recharge the controller by plugging it to a PC, Mac or a Playstation, it will immediately pair with that host and you’ll have to pair it again with the Raspberry Pi over USB. Fortunately, it is usually sufficient to just plug it back in once to the RPi and cycle the hci0 interface down and back up again to reconnect.

Before getting into writing some actual code, it’s a good idea to install the packages libevdev2, libevdev-tools and libevdev-dev at this point. Among other stuff, these packages will install the utility evtest, which can be used to list the connected input devices and list of supported event types and codes from any of them:

Note how the names for the button event codes on the Sixaxis controller make absolutely no sense – more on that next.

Querying and polling Linux evdev input devices

With the Bluetooth stuff taken care of, the next step is to start actually start writing some code and reading input from the event devices. Note that I’ll be writing everything in plain C to keep things simple.

As is customary in Linux, the input devices are presented as files named /dev/input/event[0-9]+. Both wired USB and wireless HID devices present the same interface, so we actually get support for wired devices as a bonus! Instead of doing ioctls directly to the devices, I’m making things a bit easier for me and using libevdev to access the devices. Including the headers libevdev/libevdev.h and libevdev/libevdev-uinput.h allows the use the required library functions.

To both query the capabilities and poll events from a device, it’s simply sufficient to get a read-only file descriptor to it and :

The above example checks if the device is able to send button press events and then if it is able to send a dpad up button press event. Seems pretty logical, right? Hah – you’d wish!

Although it may seem that the event devices have a nice and standardised set of event codes, at least the gamepads I tested with have their own absolutely non-standard sets of event codes. Here’s a table with the dpad and face buttons for PS3 and XBOX360 controllers:

Button

PS3

XBOX360

Dpad up

EV_KEY / 292

EV_ABS / ABS_HAT0X / -1

Dpad right

EV_KEY / 293

EV_ABS / ABS_HAT0Y / 1

Dpad down

EV_KEY / 294

EV_ABS / ABS_HAT0X / 0

Dpad left

EV_KEY / 295

EV_ABS / ABS_HAT0Y / -1

Button top

(△) EV_KEY / 300

(Y) EV_KEY / BTN_NORTH

Button right

(◯) EV_KEY / 301

(B) EV_KEY / BTN_EAST

Button bottom

(X) EV_KEY / 302

(A) EV_KEY / BTN_SOUTH

Button left

(□) EV_KEY / 303

(X) EV_KEY / BTN_WEST

Mice tend to stick to a more coherent set of events, with the movement being sent as EV_REL events and buttons as EV_KEY events with codes BTN_LEFT and BTN_RIGHT. The value returned with the event is negative if the movement is left or up and positive otherwise.

The event polling loop runs the following (non-blocking) call repeatedly to get new events from a device:

rc will contain 0 if no event was available, 1 if a new event was read into ev. The struct input_event is defined in the Linux kernel header file linux/input.h. Pretty simple once the various quirks of the actual hardware are known and taken into account when parsing the event.

Interfacing with the DB9 joystick ports

Now that we know what the gamepads and mouse are doing, we can finally get to the hardware side of things and start to actually control the pins. To keep things tidy, I’m emulating the actual 9-pin port states in two variables and updating them according to the events read from HID devices.

uint16_t port1_pins=0x016f, port2_pins=0x016f;

I won’t go into detail over how the pin states are changed to gamepad mouse input as it’s just some rather boring bit clearing and setting. If you’re interested in looking at the code a bit further, read the source for ports.c on the GitHub page for the project. Note that the mouse encoder and quadrature pulse generation is more or less written so that the mouse I’m using will generate roughly similar DPI to an actual Amiga mouse.

The library provided for the IO Pi Zero board is written in Python, so I needed to write my own code for controlling the MCP23017 chip over I2C. Looking at the datasheet, it appeared to be fairly simple to hack up some code to just run all GPIO pins as outputs and control their state.

Writing I2C code becomes a bit easier by installing the package libi2c-dev, which contains a modifed header file linux/i2c-dev.h, allowing the kernel I2C functions to be called also from userland. Using these calls saves you from manually writing register numbers before reading or writing data.

On the Pi Zero W, the I2C bus on the 40-pin header has bus number 1, so the device can be opened with:

int fd=open("/dev/i2c-1", O_RDWR);

By default, the IO Pi Zero board is configured to address 0x20, so after successfully opening the device, slave access needs to be acquired to that address:

int rc=ioctl(fd, I2C_SLAVE, 0x20);

If both calls were successful, it is now possible to write bytes to registers on the MCP23017 using the aforementioned kernel I2C functions:

int rc=i2c_smbus_write_byte_data(fd, 0x01, 0x00);

To initialize the chip, it is sufficient to write zero to IOCON, IODIRA and IODIRB to assign all 16 pins as outputs. After that, you can just update the pin states by writing into GPIOA (0x12) and GPIOB (0x13). I’ve set up the registers so that each GPIO register controls one joystick port and the pin assignments are the same for each:

Pin

GPIOA/GPIOB bit

DB9 port 1/2

Up

0

1

Down

1

2

Left

2

3

Right

3

4

Button 1

4

6

Button 2

5

9

A thread running alongside the event processing thread looks constantly at the emulated port variables and upon detecting a change, copies the relevant bits into an 8-bit variable and writes to the corresponding GPIO register:

With all the required code in place, all that’s needed is to build a cable to connect the 2x8pin header from the IO Pi Zero into two female DB9 connectors that can be plugged into the joystick ports of Amiga. Getting single wires from two regular round data cables into a ribbon cable connector was a bit fiddly but eventually it turned out fine. I double checked all the pins with a multimeter just in case, though.

Mouse/joystick ports on an Amiga A1200

One thing to remember is that the ground plane on the joystick ports (pin 8) must be connected to the Raspberry Pi’s ground. You could do this through the ground pins on the 40-pin header, but I used the solder points for external power on the IO Pi Zero. The +5V input pin is not being used, although it would be cool to power the Raspberry Pi from the joystick ports. Unfortunately it’s not possible as they only provide 50mA of current each – the Pi Zero W with the IO Pi Zero can draw up to ~350mA so we need an external power supply.

Connected to an A1200 for testing

With both the hardware and software taken care of, it’s time for some testing!

Using joyemu

The finished “product” is being called joyemu, and it offers some configurability instead of hardcoding everything and relying on defaults:

Upon startup, joyemu detects which input devices are capable of behaving like a mouse and gamepads, then assigns them to joystick ports in the order of detection. As the above usage help shows, the device numbers and mapping can be forced on the command line although in this example, we’re going with the defaults:

By default the mouse pulses are sent on the pins assigned by Commodore for the Amiga series of computers but a command line switch is available to use Atari ST pin assignments instead. Note that Atari support is untested as I don’t have the hardware to try it on. The mouse emulation also does not work with a Commodore C64 due to the 1351 mouse’s signalling protocol being very different to Amiga’s.

Here’s a rather noisy video of running joyemu on the Pi Zero W connected to an Amiga A1200 and using the Bluetooth mouse to start a game and briefly play it using a PS3 gamepad. The inlay image shows the console output with some added verbosity.

joyemu is open source and BSD licensed, so you’re welcome to fork it and hack it in any way you want. If you add something cool, please do submit a pull request with your changes! The source code can be found on GitHub. Thanks for reading!