I buy a lot of gamepads and joysticks. Most recently, I bought a fairly rare Gravis Mousestick II, mainly because it has adjustable tension. This is a useful accessibility feature, so I wanted to examine how they did that and think about how I might make something similar.
Although I haven’t finished making an ADB to USB converter so I can actually use the joystick yet, I did read up on what it was supposed to do. There weren’t joysticks for the Macintosh when the Gravis unit came out, so it had a driver to make it act like a mouse.
This got me thinking: why not do that with a modern USB joystick or gamepad? There are plenty of applications you can install on a computer to do this. There are also gamepads like the original Steam Controller that do this, but only when Steam is running. Something based on a microcontroller would work on any platform that supports a USB mouse, and without drivers or an application.
At this point I’ve made a lot of TinyUSB projects, especially MIDI projects, and have a working familiarity with both the device and host stack. It seemed like a good time to give it a go.
So I thought up a project. The thing is, gamepads have two sticks. I was thinking about the old game Battlezone, which uses two sticks to control a tank. Why not combine the two ideas and make a “battle mouse”, where:
- Moving both sticks up moves the pointer up.
- Moving both sticks down moves the pointer down.
- Moving one stick up and moving the other down moves the pointer left and right.
- Moving one stick up and leaving the other neutral moves more slowly left and right.
- The left trigger acts as the left mouse button.
- The right trigger acts as the right mouse button.
This is completely impractical and no one (including myself) is really going to use it, but it’s goofy enough to be fun to play with, and I can learn to:
- Read gamepad inputs from a microcontroller’s USB Host port.
- Send mouse input to a USB Host via a microcontroller’s device port.
Round One: The Adafruit Feather RP2040 with USB Type A Host
I wrote an initial prototype for the Adafruit Feather RP2040 with USB Type A Host using the Raspberry Pi Pico SDK, and then promptly got stuck. I could read gamepad inputs from the host port, but nothing I did on the client side resulted in the mouse pointer moving. I spent a lot of time looking for examples, but eventually I discovered that even the example shipped with TinyUSB wouldn’t move the mouse pointer.
This led to a bit of soul searching. The last time I hit something like this, it was MIDI system exclusive messages, which don’t work in the same way on the host functions in TinyUSB as they do on the client side.
This seemed like another area of the code that was maybe not as heavily exercised. I mean, there are so many keyboard projects that I’m sure they’d hear if there were problems there, but I just didn’t find very many mouse projects, at least written in C.
What I did find were examples written in Python. Many of these use TinyUSB under the hood, and if they work, I’d have a known good to compare my work to and could maybe figure out a solution on the C side.
Round Two: The Pimoroni Explorer
By happy coincidence, I have a Pimoroni Explorer, which is a RP2350-based unit with a screen and a few other niceties built in. I’ve never quite managed to get it working with C and the Pico SDK, but it comes with Micropython preinstalled, so it seemed like the right tool for the job.
Micropython
I started working with the built-in Micropython, and figured out basics like installing packages, running code, and using the REPL. This was helpful in understanding some of the basics (like using the display), but their TinyUSB stack doesn’t even have wrappers the USB host code I need (yet), so I kept looking around.
Circuit Python
Some of the examples I’d seen were written for Circuit Python, which has a USB stack that includes host support. It also has definitions and examples for dual-USB boards like the ones I tend to use. They even had a binary for the Pimoroni Explorer, which I installed.
Immediately, I liked it a lot better. Instead of a colourful and somewhat childish menu system, the screen is a console. Log messages printed in your code display on the screen. The console also displays commands and results triggered using the remote REPL tool.
This may seem small, but logging error messages is kind of a bugbear with USB projects. A lot of the time you end up disabling communications with the board via the device port, which means your only way to observe log messages is with an external debugger physically wired up to the microcontroller. It’s so refreshing just to see the error and log messages.
The other thing I love is the REPL console itself. Since it’s a scripted environment, you can improve your understand in real time. Instead of writing test code, compiling, installing it and observing the results, you can just write the code you’d like to use on the console and see what happens, including seeing error messages. Not bad.
The First Working Mouse Example
So, within a few minutes, I had my mouse example working. It runs the pointer in
a circle using sin and cos functions, which doesn’t sound exciting unless
you’ve been trying to make it work for a while.
Adding a USB Host Port
With that I moved on to the next challenge. Dual-USB boards supported by Circuit Python have definitions and convenience code, but since the Pimoroni Explorer doesn’t come with a host port, you have to wire something up yourself and then figure out the software side.
I hooked up a USB A breakout board to the breadboard built into the Explorer. The gamepad had power immediately, which was a good sign.
I drilled down into the board definition for the Adafruit Feather RP2040 with USB Host, and from that, I figured out how to manually activate the host port. Thankfully, this is something you can do in real time, you don’t need to manage any code that’s part of the device’s “boot” process.
I initially used this module, but it was written for particular gamepads. I was able to use it with a Playstation 4 controller, but it didn’t work with a lot of the gamepads I have
What I really want to learn is how to work with a controller of my choosing, so I looked at more examples until I was able to come up with a basic approach using the core USB modules and some Adafruit helper modules.
I used these to make a quick tool to print what was being read from the gamepad. I could see which bytes were updated when I changed things, and used that to come up with a strategy to parse the gamepad’s state.
The Battle Mouse
With that, I was finally able to create the Circuit Python version of the Battle Mouse. Here’s a demo:
Off Piste Learning Adventures
So, with the gamepad, the whole setup is borderline usable, but of course I wanted to take it further. I specifically wanted to use it with the Xbox Adaptive Controller, which supports using standalone joysticks to generate thumbstick input. With seemingly very little effort, I could control my tank-like mouse with tank-sized joysticks.
The first thing I learned is that the Xbox Adaptive Controller (and apparently any Xbox One controller) doesn’t act like a normal USB HID Gamepad. There are workarounds like this one, but thankfully I don’t need to go that far just to try it.
The excellent OGX Mini Project lets you program a dual-USB microcontroller to allow any gamepad (including the Xbox Adaptive Controller) to pretend to be another device.
Long term, I can learn from their approach to supporting Xbox controllers. Short term, use OGX Mini to make the XAC pretend to be something I can work with more easily.
So, I hooked up the XAC and big honking joysticks via the OGX Min, and updated my code to work with it. It was not usable enough to make for a good demo, mostly because one of the joysticks is really muddy, i.e. it can’t reach the full range of values and “jitters” off centre when not in use. I have so many joysticks at this point that I probably need to raise my standards a bit and offer this one to the thrift store.
But, I did at least come up with an approach to using the XAC in Circuit Python projects. I also learned that I can “chain” the OGX Mini, i.e. connect it to the host port of my setup, and connect another controller to the OGX Mini. This is something I haven’t managed yet with my own dual-USB projects, and is yet another area where OGX Mini sets the standard to learn from.
What’s Next
Now that I know I can get the mouse working in Circuit Python, I plan to closely compare the USB descriptors and their use of TinyUSB to what I’ve done in C. Hopefully I’ll figure out a way to get the C version working.
Once I get that done, I may make a slightly more serious gamepad to mouse/keyboard adapter that supports configuration options. The OGX Mini has set the standard here as well, it has a mode where you can reprogram its behaviour with a web browser. With that and some way to persist settings, I could replace most of what I’ve done with the Gamepad Navigator with a platform-independent device that works across all applications.
Anyway, that’s it for now. Stay tuned for whatever’s next.