The joystick proposed here is much known between makers. It provides two axis and a key button and every axis is actually a potentiometer: that means axis data is analogue and we need to use ADC to read its positioning.
Potentiometers are provided of springs so, without forcing, wipers are approximately positioned in the centre of the two resistive elements. As this device is very simple to use, it is not easy find a related datasheet. Indeed, for the most of the applications, it would be useless. Anyway, joystick used in this demo is a very cheap one marked as “Keyes_SJoys” (See Fig.1).
Schematic and pin out
Our device has 5 pins:
- GND, connection to ground;
- +5V, should be meant as connection to VCC;
- VRx, X-axis wiper ;
- VRy, Y-axis wiper;
- SW, switch terminal.
Note that pins are almost the same for every 2-axis Joystick you can buy online. There are some variant which have two or no switch.
Referring to Fig.1 we can see that the pins of resistive elements are connected to +5V and GND, and switch is connected between SW and GND. As the ADC on STM32 samples voltages from 0 to 3.0V, +5V should be connected to DC 3V. Since the switch has no pull up resistor we must provide it: we should connect a resistor across VCC(with a resistance greater of that exhibited by switch, as example 20kΩ). The GPIO from STM32 could be configured to provide an internal pull-up or pull-down so we will just act on software.
Proposed demo has three threads. One just makes a LED blinking, another samples voltage on VRx and VRyand read SW status and the last one (the main()) prints data on a sequential stream.
Notes on sampled data handling
We want to spent some words on sampled data as this case is a little bit more complicated than that faced in main ADC tutorial or slider potentiometer article. Indeed, here we are sampling data from two different channel pretending to handle data from each channel in separately. Let’s take a look to ADCConversionGroup:
Our conversion group contemplates a non-circular sampling on a sequence of two channel (ADC_CHANNEL_IN0, ADC_CHANNEL_IN1). Looking at adcConver() calling we will see that we sample that group for 10 times. As our sample buffer has been declared as:
when conversion is done, we have a buffer of 20 samples organized in this way:
For convenience of use a good idea is declaring sample buffer in this way:
this way our buffer is organizes as:
In the first column we have ten samples from IN0 (i.e. PA0 and in my case X-axis), in the second from IN1 (PA1, Y-axis). At that point there are some self question that should be answered:
- What kind of value we should expect?
- Why we have sampled ten times each axis?
- What exactly means “the wipers are approximately positioned in the centre of resistive element”?
STM32 has 12-bit ADC that means sampled values are in the range 0 and 212 – 1 (i.e. 4095). We sampled data many times to find means and remove noise. If we are not applying any force on the cursor then wipers are in the rest position and we expect to read 0 from both axis. Smarter reader should figure out that this in not what happens: without noise nor mechanical inaccuracies (i.e. inaccuracies due to the springs that do not place the cursor exactly in the middle of the resistive element) we will read 2047 (2047.5 if it were possible). Indeed since the absolute ratings are [0, 4095] we are placed exactly in the center. To obtain something more “pleasant” we should subtract 2047 to read data. In this case absolute ratings will become [-2047, 2048]. In this case we could say that negative and positive available value ranges are balanced.
Unfortunately, since there are mechanical inaccuracies even subtracting 2047 we still have an offset. If we want make a good calibration we should get this offset before to start using joystick. We can define the offset like a difference between real and expected value. The offset is a systematic error and that means it is predictable and typically constant to the true value. That means we have to calculate offset just for once and than we will compensate it during the following measurements.
To make things more clear let’s do an example. We are reading 2029 from x-axis when cursor is in the rest position (but we was expecting 2047): that means offset is -18, indeed subtracting this offset and 2047 to our value we will obtain 0. After this operation absolute ratings are not anymore [-2047, 2048] but they become [-2029, 2066]. We can balance negative and positive ranges choosing two different multiplying factors. As example if we would to have a range like [-10000,+10000] we can achieve this goal multiplying negative values by (10000/2029) and positive values by (10000/2066). If everything has been explained well, next code should be clear:
The attached demo has been tested under ChibiOS 17.6.x. Note that you can find more recent version of this project int the Download Page. Note also that starting from the version 20 all the demos from PLAY Embedded will be distributed together with ChibiStudio.