As we open Part 8 of this series, it will have become clear to most readers that I swung for the fences on this project. The two ServoStiks I purchased prove the point: they can switch between 4‐way control and 8‐way control. Did I need this? No. Was I going to take advantage of it once I bought them? Of course.
Today you’ll learn about how I stole another feature from LEDBlinky: the ability to switch the ServoStiks between 4‐way control and 8‐way control automatically as a game requires.
The idea
I can’t say anything about a joystick that hasn’t already been explained better on slagcoin, so I’ll summarize instead:
- A joystick has four microswitches on it, each of them nearly identical to the switches on arcade buttons.
- Very old arcade games that used joysticks featured 4‐way control — usually the cardinal directions. The hardware wouldn’t allow the joystick to hit the corners, so the programming didn’t consider what would happen if two of the microswitches were engaged at once.
- Later games began to feature 8‐way control — they anticipated these in‐between directions and interpreted them as diagonals.
- On many joysticks, a restrictor plate of a certain shape defines how the joystick can travel — for example, whether it’s allowed to hit the diagonals or not.
To over‐simplify: an 8‐way joystick has a round restrictor plate, and a 4‐way joystick has a diamond‐shaped restrictor plate. The ServoStik is based on a joystick called the Sanwa JLW which allows for the mode to be determined solely by rotation: with the diamond shape it can’t reach the diagonals, but rotated 45 degrees it can reach all eight directions.
Out of the box, the JLW’s restrictor plate needs to be adjusted by hand to switch modes. The ServoStik adds a motor so that the rotation can happen without having to crack the cabinet open. Rube Goldberg would be proud.
Plan A
Ordinarily, the ServoStik’s control board plugs into a computer via USB and is treated like an HID device just like the Pac‐Drive. But my original plan was to use a hardware toggle switch to select joystick mode. With this in mind, I followed the instructions on Ultimarc’s site to perform a one‐way conversion into “hardware mode.” This mode eschews HID protocols and allows the joystick to respond to a voltage signal.
Here’s how it works: take an ordinary USB cable. Strip the insulation somewhere in the middle and you’ll see four wires. Keep the red and black wires intact; those deliver power to the joystick. But cut the white and green wires, strip some of their insulation, and wire each end into a hardware switch — or into two pushbuttons.
When the green wire’s circuit is closed, the ServoStik will switch to (or remain in) 4‐way mode. When the white wire’s circuit is closed, the ServoStik will switch to (or remain in) 8‐way mode.
This worked — and was really quite easy. But only a couple days after installation I went back to the drawing board. One of three things would inevitably happen:
- I’d launch a game that obviously required 4‐way control, like Pac‐Man, but I’d forget to switch the joystick’s mode.
- I’d launch a game like Pac‐Man, remember to switch the joystick into 4‐way mode, but forget to switch the mode back to 8‐way when launching the next game.
- I’d launch a game but be unsure of whether it wanted 4‐way or 8‐way input, and hence not know which way to flip the switch.
As I should’ve realized at the time, any plan that requires me to remember to do something is fatally flawed.
Plan B
Once I changed my mind and decided to trigger the mode‐switching via software, I felt a bit stupid for having so boldly made the irreversible “hardware mode” change. But then I remembered: a Pi is more versatile than your ordinary PC! Anything a simple toggle switch can do mechanically is something I can do in software via GPIO. This series has already made us into GPIO experts.
Hooking up the ServoStik via GPIO
In fact, I think I even like the simplicity of this approach better than the alternative. Here’s what I did:
- I desoldered the ends of the white and green wires from the hardware toggle switch.
- I trimmed the ends that went toward my power source and covered them with heat‐shrink tubing; I wouldn’t need them anymore.
- The ends that went toward the ServoStik’s control board got soldered to extension wires. Those extension wires were connected to two spare GPIO pins on my Pi.
And that’s it. Hell, I had to strain to make three steps out of these directions.
The rest can be handled in software. I put the green wire on pin 5 and the white wire on pin 6. So to trigger 4‐way mode, I set pin 5 high and pin 6 low; to trigger 8‐way mode, I set pin 5 low and pin 6 high. That’s it.
The software
Setting mode from the command line
The most straightforward way to talk to GPIO (unless you’re the sort of masochist who likes Bash scripting) is via Python. Here’s the entire script (or grab it from the gist with all this post’s code samples):
#!/usr/bin/env python3
"""
A script for setting the ServoStik to 4-way mode or 8-way mode.
"""
usage = """
Usage: set-joystick [4|8|0]
Takes a single argument which should be either "4" (four-way), "8" (eight-way),
or "0" (which means "I have no clue what to do and am willing to leave it up to
this script to decide.")
"""
import sys
from RPi import GPIO
GPIO.setmode(GPIO.BCM)
MODE_8 = 6 # white
MODE_4 = 5 # green
GPIO.setup(MODE_4, GPIO.OUT)
GPIO.setup(MODE_8, GPIO.OUT)
def set_joystick_mode(mode):
target_pin = MODE_8
other_pin = MODE_4
if mode == 4:
target_pin = MODE_4
other_pin = MODE_8
GPIO.output(other_pin, GPIO.LOW)
GPIO.output(target_pin, GPIO.HIGH)
mode = sys.argv[1]
if mode not in ("0", "4", "8", ""):
print("Invalid argument!", file=sys.stderr)
print(usage)
GPIO.cleanup()
sys.exit(1)
if mode == "0" or mode == "":
# Zero equals a shrug. Put the joystick in 8-way mode.
set_joystick_mode(8)
elif mode == "4":
set_joystick_mode(4)
elif mode == "8":
set_joystick_mode(8)
GPIO.cleanup()
sys.exit()
Save this script as /home/pi/bin/set-joystick
. Now you should be able to do set-joystick 4
to trigger 4‐way mode and set-joystick 8
to trigger 8‐way mode.
This might be the simplest thing we’ve done via GPIO so far. All the control board needs is a momentary signal change to trigger the joysticks, so we don’t have to run a daemon like we did for other tasks. We can just run this script on demand.
Knowing which mode is which
So we’ve solved how to control the switch via software. But we still need a system to keep track of which mode goes with which game.
That sort of information would be easy to look up on the spot. I can create /home/pi/controls/
, populate it with one subdirectory per system, and then have one config file per game — just like I did in Part 7 for button configs. So RetroPie could consult /home/pi/controls/arcade/pacman.cfg
, read a 4
as its only contents, and know that the game needs 4‐way mode.
And, again, with only 100 games, I could create each config file by hand. It’s not that much work, and I’d have to do it only once per game. But why do 30 minutes of gruntwork when you can take 60 minutes to write a script to automate the task?
The controls.dat project, now dormant, aimed to provide metadata about the control scheme of each game that MAME supported: how many buttons it used, how each button was labeled, whether it used a joystick or a driving wheel or a trackball or something even weirder than that, and so on.
This sort of information was of no use to me in Part 7; knowing which buttons to light up required me to document how I had chosen to map a game’s controls onto my own buttons. But all I need to know here is 4‐way versus 8‐way. That’s an immutable fact of the game itself.
The actual controls.dat
file is in INI format and it’s weird and gigantic. A kind soul maintained (at least for a while) a companion project that represented that same data as JSON. The data file would still be gigantic, but not nearly as weird.
So here’s the plan:
- We’ll grab the
restructuredControls.json
file from that repo and place it inside the arcade config directory (/home/pi/controls/arcade
). - We’ll make a command‐line script called
joystick-type
that takes a ROM name as input and outputs either4
,8
, or0
for that game’s joystick mode. (0
means “I don’t know”; you’ll understand why we need that in a minute.) - The
joystick-type
script will first try to look up the game’s config file. If that file exists, we’ll use it as the single source of truth for a joystick’s mode. - If that config file does not exist, and we’re launching an arcade game, we’ll dig through our gigantic JSON file to see if it has any information about that game’s control scheme. If so, we use its verdict to create a config file for that game. That way we never have to do that bookkeeping work again for that particular game.
- When a game launches, we’ll put something in
runcommand-onstart.sh
that takes the output ofjoystick-type
and gives it toset-joystick
.
Writing the script
The script isn’t fascinating enough to dump into this article; you can read the joystick-type
file in the gist if you’re curious. The usage is simple: joystick-type arcade pacman
, for instance, should print 4
to STDOUT, and joystick-type arcade sf2
should print 8
.
Hooking into game launch
Last time we were able to take the complex task of per‐game button illumination and condense it to one line of code in runcommand-onstart.sh
. I was not as elegant this time around.
I assume you’ve read Part 7, so I won’t re‐explain runcommand-onstart.sh
and runcommand-onend.sh
.
The code below should go in /opt/retropie/configs/all/runcommand-onstart.sh
— create it if it doesn’t exist.
#!/usr/bin/bash
# ARGUMENTS, IN ORDER:
# 1. System (e.g., "arcade")
# 2. Emulator (e.g. "lr-fba-next")
# 3. Full path to game (e.g., /home/pi/RetroPie/roms/arcade/wjammers.zip)
if [ -z "$3" ]; then
exit 0
fi
system=$1
emulator=$2
# Gets the basename of the game (e.g., "wjammers")
game=$(basename $3)
game=${game%.*}
###
# Figure out the joystick mode of the game.
joystick_mode=`/home/pi/bin/joystick-type "$system" "$game"`
# Strip all spaces. Replace "8" with an empty string so it fails the
# conditional below.
joystick_mode=${joystick_mode// /}
joystick_mode=${joystick_mode//8/}
# If the joystick type needs to be switched to 4-way mode, notify the user.
# We don't care about 8-way mode because that's the default. We don't care
# about when `joystick-mode` returns nothing, because in that case we take
# no action.
if [[ ! -z "$joystick_mode" ]]; then
DIALOGRC="/opt/retropie/configs/all/runcommand-launch-dialog.cfg" dialog --infobox "\nSetting joystick to ${joystick_mode}-way mode." 5 60
sleep 0.5
fi
# TODO: I don't know why this needs `sudo`. It seems to work fine without it
# in an interactive shell. Investigate.
sudo /home/pi/bin/set-joystick "$joystick_mode"
Here’s what we’re doing:
- We assume that we’re already in 8‐way mode (because
runcommand-onend.sh
will ensure that we are when it exits a game). - We call the
joystick-type
script and capture its output into a variable. It ought to output a simple4
or8
, but we strip whitespace just in case. - When the return value is
8
, we do nothing, because we’re already in 8‐way mode. - When the return value is
4
, we leverage a tool calleddialog
to show some text on the screen in a prettified manner. (RetroPie usesdialog
extensively.) We explain that the joysticks are switching to 4‐way mode and then pause for a moment so the user can read it. - We call the
set-joystick
script that we wrote earlier to trigger the switch. (I could’ve solved theTODO
in this script before I published this article, but what’s the fun in that?)
Hooking into game exit
We have a small custodial task to perform on game exit: because life is easier when we treat 8‐way mode as the default, we should ensure that the joysticks are in 8‐way mode when a game exits. So here’s what /opt/retropie/configs/all/runcommand-onend.sh
should look like:
# ARGUMENTS, IN ORDER:
# 1. System (e.g., "arcade")
# 2. Emulator (e.g. "lr-fba-next")
# 3. Full path to game (e.g., /home/pi/RetroPie/roms/arcade/wjammers.zip)
if [ -z "$3" ]; then
exit 0
fi
system=$1
emulator=$2
# Gets the basename of the game (e.g., "wjammers")
game=$(basename $3)
game=${game%.*}
###
# Return the joystick to 8-way mode no matter what.
# TODO: I don't know why this needs `sudo`. It seems to work fine without it
# in an interactive shell. Investigate.
sudo /home/pi/bin/set-joystick 8
Once again, here’s a gist with all the code samples.
The proof
This video won’t be as compelling as the last one, but it at least demonstrates that I really did do everything I just described.
Was this worth it?
Not really. I don’t doubt that Pac‐Man is somewhat vexing to play with an 8‐way joystick, but I do doubt that it would’ve caused me more distress than this project did.
A ServoStik needs a lot of feeding and watering. The restrictor plate has to be held against the bottom of the joystick tightly enough to make the stick feel sturdy, but loosely enough that it can be rotated ninety degrees by a rather small motor. To pull this off it employs some machine screws, springs, and nylon washers, but it won’t work perfectly out of the box. Invest in some WD‐40.
Another option would be to install two ordinary 8‐way joysticks and chill out about the whole thing.
What’s next?
We’re nearly out of hardware‐related topics, so I’ll talk about the scripts I wrote for miscellaneous tasks like checking game metadata and testing my controls. Pedants will enjoy Part 9!
Comments
I accomplished the same thing by building my own servo controlled joystick. Modified the joystick back plastic plate to attach a servo to and auto change the joystick mode. Plugged it into the GPIO. Wrote a python program to activate the servo. That then changed the joystick back and forth from 4-Way to 8-Way. Then I used the runcommand-onstart.sh. Stored argument $3 to a variable. Striped the path and file extension leaving just the rom or game name. Did a case statement to determine which game needed which joystick setting then called one of two python files to change the joystick before each game. It turned out awesome. My wife is an expert from the retro arcade ms pacman times and could not play the game in 8-way joystick mode. Once set to 4-way she said its just like the old school cabinet.
Great articles, but I see a couple of problems that perhaps you could comment on:
I’ll report again as I continue to sift through your code.
Thanks.
Further to my first comment, re: GPIO pins: Upon further investigation, I believe the two pins you used are/were UART0 Tx and UART0 Rx. Is this your intention, and if so, why?
Thanks.
Ian, you’re right about both mistakes. I edited the gist to correct the ordering of those two lines. By sheer luck, I’d saved the original Fritzing diagram, so I fixed that as well. Thanks!
On pinout.xyz, the UART pins are listed as BCM 14 and 15. I chose BCM 5 and 6 for no particular reason; they’re adjacent to pins that I’m using for my volume knob.
You, sir, are AWESOME!!! The PC in my barrel arcade died coincidentally just after I bought a Raspberry Pi Zero to play around with. Since I was only running 8bit games, I thought I’d use it to replace the PC. I tried to install and use RGBCommander but it doesn’t work on the Pi Zero. I then tried to install the linux drivers from Katie Snow (pointed to from the Ultimarc website) but couldn’t get that going either. With your instructions and scripts, my servostiks are now working perfectly! Thanks!!!
I am having a problem getting this to work for me consistently. I have everything connected correctly, I can get the servo to change positions but it does not work unless I give the set-joystick command many time in a row in terminal. It will change but almost never on the first attempt. I have tried changing gpio pins with the same result. Being that I have a very limited knowledge of the code is there a way to send the command for a longer interval? or many times from one command.
Joshua, you can duplicate the calls to
set_joystick_mode
at the bottom of the Python script, so that on each code path it calls that function multiple times — perhaps with a call to time.sleep in between each one, though you’d have toimport time
to use that function.But my instinct is that the root cause is elsewhere — for instance, a flaky connection between the ServoStik control board and the GPIO. If you have a multimeter, try doing a continuity test to ensure that the connections are solid.
Hi, i am new to programming my pi but have been into retro gaming for a long time now. I currently have a vertical cab with a Rasp Pi 3 and servo stick and board. I’ve followed the instructions but am struggling to get it working. Based n the comment from Ian, should i be using pin 5 & 6 or 3 & 6. I copied the files from the zip folder into the stated folders. I also added a copy of restructuredControls.json . I created 2 .cfg files to test, one for dkong (4) and one for scramble (8). What am I missing? Help would be much appreciated.
Cheers