Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

Joystick module with ESP32 DevKit V1 — 3.3 V wiring and centre calibration

Wire a KY-023 joystick to an ESP32 DevKit V1 with its +5V pin on 3V3, axes on ADC1 pins D34/D35, the button on D32, and auto-calibrate the off-centre rest point.

Arduino C++Verified — core esp32 3.3.12, 3 Nov 2026Compile-only

Community draft — report a mistake

The two-axis joystick module is a pair of 10 kΩ potentiometers and a push button under one thumb stick — the input for robot-car remotes and menu navigation.

On the ESP32 DevKit V1 there is one wiring surprise: the pin labelled +5V goes to 3V3. The axes go to the ADC1 input-only pins D34 and D35, and the button to D32, which has the internal pull-up those two lack. The sketch calibrates the centre at start-up and prints each axis as −100 … +100 % plus a direction.

Wiring

ESP32 DevKit V1Only the pins used are shown · 3.3 V logicDual-axis joystick module (KY-023)D34D35D32GND3V3GND+5VVRxVRySW
Every connection
ESP32 DevKit V1Dual-axis joystick module (KY-023)WireNotes
3V3+5VredYes, 3V3 into the pin labelled +5V: it is only the supply for the two pots. At 5 V the axes would swing to 5 V.
GNDGNDblack
D34VRxyellowADC1, input-only.
D35VRyblueADC1, input-only.
D32SWgreenButton to GND when pressed. D32 has an internal pull-up; D34/D35 don’t.

3.3–5 V part, 3.3 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 pin printed +5V is just the top of both potentiometers. On a 3.3 V board it must go to 3V3, otherwise VRx/VRy reach 5 V.
  • Both axes go on input-only ADC1 pins (34 and 35), which keep working when you add Wi-Fi or ESP-NOW for a remote control. The button can’t share them: 34–39 have no internal pull-ups, so SW goes on D32 with INPUT_PULLUP.
  • The resting position is rarely 2048. The cheap pots sit off-centre and the ESP32 ADC is non-linear, so the sketch measures the centre at boot and uses a dead zone of ±150 counts.
  • Near both ends the ESP32 ADC flattens (below about 0.15 V and above about 3.1 V with the default attenuation), so the stick hits ±100 % a little before its mechanical limit. That’s normal.

Code

joystick-esp32.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// Analog joystick module (KY-023 style) on an ESP32 DevKit V1.
// +5V pin → 3V3 (!), GND → GND, VRx → D34, VRy → D35 (ADC1, input-only), SW → D32 (internal pull-up).
// Calibrates the centre at start-up — don't touch the stick while it boots.

constexpr uint8_t X_PIN = 34;
constexpr uint8_t Y_PIN = 35;
constexpr uint8_t SW_PIN = 32;     // 34/35 have no pull-ups, so the button goes on a full GPIO
constexpr int DEADZONE = 150;      // counts around the centre treated as "no movement"

int cx, cy;

int axis(uint8_t pin, int centre) {
  int v = analogRead(pin) - centre;      // −centre … +(4095 − centre)
  if (abs(v) < DEADZONE) return 0;
  int span = v > 0 ? 4095 - centre : centre;
  return constrain(v * 100 / span, -100, 100);  // −100 … +100 %
}

void setup() {
  Serial.begin(115200);
  pinMode(SW_PIN, INPUT_PULLUP);
  long sx = 0, sy = 0;
  for (int i = 0; i < 32; i++) {
    sx += analogRead(X_PIN);
    sy += analogRead(Y_PIN);
    delay(5);
  }
  cx = sx / 32;
  cy = sy / 32;
  Serial.printf("Joystick centre: X %d  Y %d (ideal 2048 — ESP32 ADC and pots vary)\n", cx, cy);
}

void loop() {
  int x = axis(X_PIN, cx);
  int y = axis(Y_PIN, cy);
  bool pressed = digitalRead(SW_PIN) == LOW;
  const char *dir = (x == 0 && y == 0) ? "centre"
                  : abs(x) > abs(y)    ? (x > 0 ? "right" : "left")
                                       : (y > 0 ? "down" : "up");
  Serial.printf("X %4d %%  Y %4d %%  %-6s %s\n", x, y, dir, pressed ? "[pressed]" : "");
  delay(100);
}

Expected serial output

Serial Monitor · 115200Output
Joystick centre: X 1873  Y 1912 (ideal 2048 — ESP32 ADC and pots vary)
X    0 %  Y    0 %  centre
X  100 %  Y    3 %  right
X    0 %  Y  -87 %  up     [pressed]

Common errors

One direction reaches 100 % at half travel

The module is on 5 V (VIN) instead of 3V3, so the axis passes 3.3 V halfway. Move +5V to 3V3.

Button reads pressed all the time

SW is on D34/D35/VP/VN, which have no pull-up and float. Move it to D32 (or any output-capable pin) and keep INPUT_PULLUP.

Stick drifts when centred

You touched it during start-up calibration, or the dead zone is too small for your module. Press EN with the stick released, or raise DEADZONE.

↑↓ to move · Enter to open