Analog joystick: reading the X and Y axes and the button
How the joystick module with two potentiometers and a button works, the reading ranges on the Arduino Nano and the ESP32, why you should calibrate the center and use a dead zone, and how to wire the button without a resistor.

The joystick module in robotics kits is very simple on the inside: two potentiometers and one button. One potentiometer measures side-to-side movement (X axis), the other measures forward and back (Y axis). Pressing the stick down closes the button.
The five pins
- GND and VCC (power).
- VRx and VRy: the analog outputs of the two axes.
- SW: the button.
Each potentiometer works as a voltage divider. With the stick at one end, the output sits near 0 V; at the other end, near the supply voltage; in the middle, roughly halfway.
What the board reads
| Board | Resolution | Reading range |
|---|---|---|
| Arduino Nano | 10 bits | 0 to 1023 |
| ESP32 | 12 bits | 0 to 4095 |
On the Nano, the joystick is powered at 5 V and the axes go to the analog inputs, A0 to A7. A6 and A7 are a good choice for axes, because they can only do analog reads, which leaves the other pins free.
On the ESP32, power the joystick at 3.3 V, never 5 V: the axis output must not exceed the pin voltage. And use the ADC1 pins (GPIO 32 to 39), because ADC2 doesn't work while Wi-Fi is on. In MicroPython, 11 dB attenuation lets you read the full range. The ESP32's converter is less linear near the ends: for a joystick that doesn't matter, but don't expect instrument-grade accuracy.
The center is never exactly the middle
With the stick released, the Nano rarely reads 512. It reads 498, 521, 507, and the value changes from one joystick to the next. If the program treats "anything other than 512" as movement, the arm moves on its own.
Two measures fix this:
- Calibrate the center at power-up. In the first moments, with the stick released, the program reads the axis a few times and stores the average as that joystick's center.
- Dead zone. Any reading within a set margin of the center (for example, 40 units on the Nano) counts as "still".
The joystick cards in the block coding IDE already do both.
The button without a resistor
The module's button connects the SW pin to GND when pressed. Released, it's disconnected, and a "floating" input pin reads random values. The fix is the board's internal pull-up:
- On Arduino, INPUT_PULLUP mode.
- In MicroPython, Pin.PULL_UP.
With the pull-up, the pin reads 1 with the button released and 0 with the button pressed: the logic is inverted, and the code has to account for that.
On the ESP32, watch out: GPIO 34 to 39 have no internal pull-up. The button goes on another pin.
Debounce
A mechanical button doesn't close cleanly: the contact "bounces" for a few milliseconds and the board may read several presses. Waiting 20 ms to 50 ms after the first change and confirming the reading keeps a single press from recording three positions on the arm.
In the block coding IDE
In the block coding IDE, the joystick card asks for the X, Y and button pins and gives you the axis already centered, from -100 to 100. For what to do with that value on the arm, see the two ways of controlling it in robot arm with four mini servos.