This installment of our micro:bit peripherals in Python series is an overview of analog joysticks, which are popular controller inputs for games, simulations, and more.
Check out the demo video at: https://youtu.be/CaD89i2Naoc
WHAT’S UP WITH “ANALOG”? WHY NOT JUST SAY “JOYSTICK”?
Many early gaming systems had directional controls (UP/DOWN/LEFT/RIGHT) but they were digital in nature. For example, you could push the UP button and go upwards at full speed, or not push any direction and stand still, but there was no way to “go up slowly”.
These are technically called Digital Joysticks, and you can read more about using these sorts of digital inputs in the buttons and switches installment of this blog series.
An Analog Joystick is sensitive to how far you move the stick in any particular direction, and so you can go “a little up”, “a lot up”, and many other settings between those two extremes.
WHAT’S INSIDE AN ANALOG JOYSTICK?
If you read our Potentiometers article in this series, you already know that an analog joystick uses potentiometers internally. In fact, it uses two of them, one for left/right, and one for up/down.
The left/right direction is often referred to as the horizontal or X axis. The up/down direction is often referred to as the vertical or Y axis.
Possibly confusing matters, many of these joysticks include a “bonus” digital input – if you push “inward” on the joystick, you are actually pressing a tiny button.
If you are having trouble spotting the bonus button, look at Button A and Button B on your micro:bit, and then look at the base of your joystick… you’ll spot it!
WHERE TO GET ONE OF THESE?
These little joysticks are readily available from multiple vendors, so it is hard to pick a single definitive source.
Probably the best thing to do is a web search, for example:
https://www.google.com/search?q=KY-023+joystick
This will bring up a results page with multiple sources you can choose from. With a little searching you can find them for under $5 each.
QUICK REFRESHER - READING ANALOG AND DIGITAL SIGNALS ON A MICRO:BIT:
In our previous articles on Potentiometers and Buttons and Switches we learned how to read both of these types of inputs. Here is a quick refresher:
Any pin on the micro:bit that can take analog readings supports the function read_analog(), for example:
pin0.read_analog()
This function gives us a value between 0 and 1023. The value will be 0 if the voltage read on the pin is the same as GND. The value will be 1023 if the voltage read on the pin is 3.3 Volts. Any voltage in between GND and 3.3 Volts will return a value between 0 and 1023.
For example, if the voltage on pin0 was equal to 1.1 volts then you would expect pin0.read_analog() to return 341 ADC counts.
Any pin on the micro:bit that can take digital readings supports the function read_digital(), for example:
pin0.read_digital()
This function gives us a value of True or False, with True corresponding to a high voltage level, and False corresponding to a low voltage level. Which voltage level corresponds to “button is pressed” depends on how you wire the button up, but typically the button is connected so that pressing the button connects the pin to GND. This makes it so False corresponds to “pressed”, and True corresponds to “not pressed”.
SETTING UP THE DEVICE:
Assuming you want to use all of your joysticks capabilities, you will need to make 5 connections between the joystick and the micro:bit.
First, to use any potentiometer you must connect its two power pins to the micro:bit – the joystick controller is no exception. The joystick pin labelled GND will be connected to the micro:bit’s GND pin, and the joystick pin labelled +5V (or some might be labeled +3V, 3.3V, or even VCC) must be connected to the micro:bit 3V pin.
The joystick pin labelled VRx needs to go to a micro:bit pin capable of using read_analog(). In the example code provided with this blog post, we are assuming the VRx pin is connected to micro:bit pin0.
The joystick pin labelled VRy needs a similar analog input pin. We have chosen pin1 for this example.
The last connection is for that “bonus” switch we mentioned earlier. The joystick pin labelled SW (short for SWitch) needs to be connected to any micro:bit pin that supports read_digital(). We have chosen to connect to pin2 because it is easier to connect to than the other remaining pins, thanks to those big round holes on the micro:bit’s edge connector.
MAKING SENSE OF THE JOYSTICK READINGS:
The easiest way to understand how the read_analog() values correspond to different joystick positions is to simply print them from inside a while loop. Here is an example for examining horizontal movement:
Joysticks can vary between manufacturers, but typically you will see readings close to 0 when the joystick is moved all the way over to the left, and numbers very close to 1023 when the joystick is moved all the way to the right. When the joystick is in the middle (centered), the values will also be near the middle of the 0-1023 range, or around 511.
You can do similar experiments with vertical movement, by changing the code to use pin1.read_analog() instead of pin0.read_analog().
Typically you will see readings close to 0 when the joystick is moved all the way up (or “away from you” if the joystick is laying flat), and readings close to 1023 when the joystick is moved all the way down (or “towards you”). Once again, middle positions will correspond to middle values.
Of three joysticks tested at Firia Labs, two followed the “up gives you low values, down gives you high values” pattern and one was reversed: “down was low values, and up was high values”.
You will have to adapt your code to match the joystick you have.
MAKING THE READINGS USEFUL:
This is somewhat dependent on how you want the controls in your game or simulation to work, but there are some common “recipes” that are typically used:
RECIPE 1 – EMULATION OF A DIGITAL JOYSTICK
Sometimes simple UP/DOWN/LEFT/RIGHT control is all you need. To accomplish this, it is usually sufficient to divide a given joystick axis into 3 intervals. Take a look at the following source code to see what I mean:
You can see in the code that the 0-1023 range is split up into three regions: 0-299, 300-600, and 601-1023.
If you examine the code closely, you will notice that directions are assigned to values from 0-299, and 601-1023, but not to the range 600-900. This middle region is referred to as a deadband or deadzone (it’s like a region where the “controls are dead”), and it is used to reduce the need for calibration, and to make the controls seem less “twitchy”. Consequently, if you want the controls to feel more “quick and responsive”, you might try reducing this deadband. Experiment with these thresholds and see which values give you the “feel” you want.
RECIPE 2 – FINER-GRAINED CONTROL
Of course, the whole point of using an analog joystick instead of a digital one is to be able to use more finesse in crushing your opponent moving your game character, so here is an example that divides the joystick’s range of motion into as many regions as there are rows and columns on the micro:bit’s 5x5 display.
Here we are mapping joystick inputs directly into on-screen positions.
If you were controlling something like rocket thrusters you might want to divide the movement range into even more segments.
HOW TO USE THAT “BONUS” BUTTON:
Earlier we mentioned that when you pressed “inward” on the joystick, a small digital button got pressed.
Here is an expanded version of the earlier example, that shows reading all 3 inputs (2 analog inputs, 1 digital input).