Nostalgia-Tron, Part 8: Over-engineered joystick rotation

Posted in Articles, Raspberry Pi, Tutorials

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.

Restrictor Plates
On the ServoStik, the diamond shape prevents the joystick from engaging two microswitches at once.

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.

I am great at Fritzing.

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:

  1. I’d launch a game that obviously required 4‐way control, like Pac‐Man, but I’d forget to switch the joystick’s mode.
  2. 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.
  3. 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:

  1. I desoldered the ends of the white and green wires from the hardware toggle switch.
  2. I trimmed the ends that went toward my power source and covered them with heat‐shrink tubing; I wouldn’t need them anymore.
  3. 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.
The Pi takes over communication with the ServoStik control board.

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:

  1. We’ll grab the restructuredControls.json file from that repo and place it inside the arcade config directory (/home/pi/controls/arcade).
  2. We’ll make a command‐line script called joystick-type that takes a ROM name as input and outputs either 4, 8, or 0 for that game’s joystick mode. (0 means “I don’t know”; you’ll understand why we need that in a minute.)
  3. 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.
  4. 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.
  5. When a game launches, we’ll put something in runcommand-onstart.sh that takes the output of joystick-type and gives it to set-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:

  1. We assume that we’re already in 8‐way mode (because runcommand-onend.sh will ensure that we are when it exits a game).
  2. We call the joystick-type script and capture its output into a variable. It ought to output a simple 4 or 8, but we strip whitespace just in case.
  3. When the return value is 8, we do nothing, because we’re already in 8‐way mode.
  4. When the return value is 4, we leverage a tool called dialog to show some text on the screen in a prettified manner. (RetroPie uses dialog extensively.) We explain that the joysticks are switching to 4‐way mode and then pause for a moment so the user can read it.
  5. We call the set-joystick script that we wrote earlier to trigger the switch. (I could’ve solved the TODO 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.

Once Pac‐Man launches, the joysticks switch to 4‐way control.

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

Leave a comment

(Huh?)
What's allowed? Markdown syntax, with these caveats…
GitHub-Flavored Markdown

A couple aspects of GFM are supported:

  • Three backticks (```) above and below a section of code mark a code block. Begin a JavaScript code block with ```js, or a Ruby code block with ```ruby, or an HTML code block with ```html.
  • Underscores in the middle of words (cool_method_name) will not be interpreted as emphasis.
HTML tags/attributes allowed

Whether you're writing HTML or Markdown, the resulting HTML will be sanitized to remove anything not on this list.

<a href="" title="" class=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class=""> <del datetime=""> <em> <i> <li> <ol> <pre> <q cite=""> <s> <strike> <strong> <ul>

Now what? Subscribe to this entry's comment feed to follow the discussion.