The thumb joystick module is two potentiometers (X and Y) plus a push switch under the stick. It is the usual controller for pan-tilt servos, robot cars and simple games on an Uno.
Both axes are analog, so they go to A0 and A1. The Uno’s 10-bit ADC reads them from 0 to 1023, with about 512 at rest. The button closes to GND when you press the stick down. It goes to D2 with the Uno’s internal pull-up turned on, so no resistor is needed.
The sketch calibrates the centre at start-up, ignores small wobble, and prints each axis as −100 % to +100 % with a direction word, ready to map onto servo angles or motor speeds.
Wiring
| Arduino Uno R3 | Dual-axis joystick module (KY-023) | Wire | Notes |
|---|---|---|---|
| GND | GND | black | |
| 5V | +5V | red | The pots sit across this supply, so 5 V gives the full 0–1023 range on the Uno. |
| A0 | VRx | yellow | |
| A1 | VRy | green | |
| D2 | SW | blue | Push switch to GND. Most modules have no pull-up, so the sketch turns on the Uno’s internal one. |
3.3–5 V part, 5 V board. VRx and VRy swing between 0 V and the module’s supply. On ESP32 and Pico, power the joystick from 3.3 V (the pin labelled +5V is just the supply input) so the analog outputs never exceed 3.3 V.
Board quirks
- The centre is rarely exactly 512 — 490–530 is typical. The sketch reads it at start-up, so leave the stick alone while the Uno resets.
- Each half of the travel is scaled separately, and a small deadzone stops the reading flickering around zero.
- Which way is “up” depends on how you hold the module. If a direction is reversed, swap the sign of
xoryrather than rewiring. - Readings are ratiometric: the pots and the Uno’s ADC both use the same 5 V, so a USB supply that is really 4.8 V doesn’t shift the result.
Code
// Analog joystick module (KY-023 style) on an Arduino Uno R3.
// GND → GND, +5V → 5V, VRx → A0, VRy → A1, SW → D2 (internal pull-up; pressed = LOW).
const uint8_t X_PIN = A0, Y_PIN = A1, SW_PIN = 2;
const int DEADZONE = 40; // ignore small wobble around the centre
int centreX = 512, centreY = 512;
int axis(int raw, int centre) {
int v = raw - centre;
if (abs(v) < DEADZONE) return 0;
// Scale each half separately: the centre is rarely exactly 512.
return v > 0 ? map(v, DEADZONE, 1023 - centre, 0, 100) : map(v, -centre, -DEADZONE, -100, 0);
}
void setup() {
pinMode(SW_PIN, INPUT_PULLUP);
Serial.begin(9600);
centreX = analogRead(X_PIN); // don't touch the stick during reset
centreY = analogRead(Y_PIN);
Serial.print(F("Joystick centre X "));
Serial.print(centreX);
Serial.print(F(" Y "));
Serial.println(centreY);
}
void loop() {
int x = constrain(axis(analogRead(X_PIN), centreX), -100, 100);
int y = constrain(axis(analogRead(Y_PIN), centreY), -100, 100);
bool pressed = digitalRead(SW_PIN) == LOW;
Serial.print(F("X "));
Serial.print(x);
Serial.print(F("% Y "));
Serial.print(y);
Serial.print(F("% "));
if (pressed) Serial.print(F("[button] "));
if (y > 50) Serial.print(F("UP"));
else if (y < -50) Serial.print(F("DOWN"));
if (x > 50) Serial.print(F(" RIGHT"));
else if (x < -50) Serial.print(F(" LEFT"));
Serial.println();
delay(200);
}// Analog joystick module (KY-023 style) on an Arduino Uno R3.
// GND → GND, +5V → 5V, VRx → A0, VRy → A1, SW → D2 (internal pull-up; pressed = LOW).
const uint8_t X_PIN = A0, Y_PIN = A1, SW_PIN = 2;
const int DEADZONE = 40; // ignore small wobble around the centre
int centreX = 512, centreY = 512;
int axis(int raw, int centre) {
int v = raw - centre;
if (abs(v) < DEADZONE) return 0;
// Scale each half separately: the centre is rarely exactly 512.
return v > 0 ? map(v, DEADZONE, 1023 - centre, 0, 100) : map(v, -centre, -DEADZONE, -100, 0);
}
void setup() {
pinMode(SW_PIN, INPUT_PULLUP);
Serial.begin(9600);
centreX = analogRead(X_PIN); // don't touch the stick during reset
centreY = analogRead(Y_PIN);
Serial.print(F("Joystick centre X "));
Serial.print(centreX);
Serial.print(F(" Y "));
Serial.println(centreY);
}
void loop() {
int x = constrain(axis(analogRead(X_PIN), centreX), -100, 100);
int y = constrain(axis(analogRead(Y_PIN), centreY), -100, 100);
bool pressed = digitalRead(SW_PIN) == LOW;
Serial.print(F("X "));
Serial.print(x);
Serial.print(F("% Y "));
Serial.print(y);
Serial.print(F("% "));
if (pressed) Serial.print(F("[button] "));
if (y > 50) Serial.print(F("UP"));
else if (y < -50) Serial.print(F("DOWN"));
if (x > 50) Serial.print(F(" RIGHT"));
else if (x < -50) Serial.print(F(" LEFT"));
Serial.println();
delay(200);
}Expected serial output
Joystick centre X 508 Y 517
X 0% Y 0%
X 0% Y 100% UP
X -100% Y 0% LEFT
X 0% Y 0% [button]Joystick centre X 508 Y 517 X 0% Y 0% X 0% Y 100% UP X -100% Y 0% LEFT X 0% Y 0% [button]
Common errors
The button reads pressed all the time, or flickers
INPUT_PULLUP is missing, or SW is on a pin other than D2. Without the pull-up the pin floats.
X and Y are swapped
Module silkscreens differ. Swap A0 and A1 in the sketch.
Values never reach 0 or 1023
Normal for cheap sticks — they often stop around 5 and 1018. The scaling still reaches ±100 % thanks to constrain().