To interface a KY-023 joystick with an Arduino, connect the VRx and VRy analog outputs to the Arduino's ADC pins (e.g., A0 and A1), wire VCC to 5V and GND to GND, and read the values using analogRead(). The raw 0-1023 values require deadzone calibration in software to prevent servo jitter or character drift at the center position. This guide walks through the exact hardware setup, provides production-ready C++ code with serial error handling, and details how to troubleshoot the most common analog drifting issues.
The KY-023 Joystick Arduino Setup: What You Need
Estimated Time: 30 minutes
Target Board Variant: Arduino Uno R3 (ATmega328P) or Nano V3 (ATmega328P). Note: The code uses 5V logic and 10-bit ADC resolution. If using an ESP32 or Arduino R4 Minima, ADC mapping and voltage references must be adjusted.
Before you start stripping wires, gather these exact components. Prices reflect typical 2026 market rates for hobbyist-grade parts.
- Microcontroller: Arduino Uno R3 (ATmega328P) or compatible clone (~$12-$15).
- Joystick Module: KY-023 Dual-Axis Joystick Breakout. This specific module uses two 10kΩ B103 linear taper potentiometers and a 4-pin tactile switch (~$2-$4).
- Actuator (for testing): SG90 9g Micro Servo (5V compatible) (~$3).
- Wiring: Male-to-Male and Male-to-Female Dupont jumper wires, 400-tie-point solderless breadboard.
- Tools: Digital multimeter (for verifying 5V rail), small Phillips screwdriver (for servo horn).
Pin Mapping and Wiring the Joystick Module
The KY-023 has 5 pins: GND, +5V (VCC), VRx (X-axis analog), VRy (Y-axis analog), and SW (button switch). The analog pins output a voltage between 0V and 5V depending on the wiper position of the internal 10kΩ potentiometers. The SW pin is an open-drain output that connects to GND when the joystick is pressed down.
| KY-023 Pin | Arduino Uno R3 Pin | Wire Color (Suggested) | Function |
|---|---|---|---|
| GND | GND | Black | Circuit Common / 0V Reference |
| +5V (VCC) | 5V | Red | Power Supply (Do NOT use 3.3V) |
| VRx | A0 | Yellow | X-Axis Analog Input (0-1023) |
| VRy | A1 | Green | Y-Axis Analog Input (0-1023) |
| SW | D2 | Blue | Button Digital Input (Active LOW) |
Wiring Steps:
- Insert the Arduino Uno and KY-023 module into the breadboard. Ensure the joystick pins are not shorted across the center trench.
- Connect the GND and 5V rails. Safety Check: Use your multimeter to verify exactly 5.0V (±0.2V) across the red and blue breadboard rails before connecting the module.
- Wire the VRx and VRy pins to A0 and A1. Keep these analog wires away from the servo power lines to minimize electromagnetic interference (EMI).
- Connect the SW pin to Digital Pin 2.
- Wire the SG90 Servo: Brown to GND, Red to 5V, and Orange (Signal) to Digital Pin 9.
Complete Arduino Code with Deadzone Calibration
Raw analog reads from cheap B103 potentiometers rarely sit perfectly at 512 when centered. They usually fluctuate between 490 and 530 due to mechanical tolerances and ADC quantization noise. If you map 0-1023 directly to a servo's 0-180 degrees, your servo will jitter constantly at rest.
The code below targets the Arduino Uno R3 (ATmega328P). It implements a software deadzone, maps the values to a usable -100 to +100 range, and includes serial error handling to catch wiring faults on boot.
#include <Servo.h>
// --- PIN DEFINITIONS ---
#define VRX_PIN A0
#define VRY_PIN A1
#define SW_PIN 2
#define SERVO_PIN 9
// --- CALIBRATION CONSTANTS ---
#define ADC_MAX 1023
#define CENTER_VAL 512
#define DEADZONE 25 // Ignore readings within +/- 25 of center
#define SERVO_MIN 10 // Prevent servo stalling at extreme ends
#define SERVO_MAX 170
Servo myServo;
void setup() {
Serial.begin(115200);
// Configure button pin with internal pull-up resistor (no external resistor needed)
pinMode(SW_PIN, INPUT_PULLUP);
myServo.attach(SERVO_PIN, 500, 2400); // Standard SG90 pulse widths
myServo.write(90); // Center servo on boot
// --- BOOT ERROR HANDLING ---
// Check for floating pins or shorted VCC/GND on analog reads
int initialX = analogRead(VRX_PIN);
int initialY = analogRead(VRY_PIN);
if (initialX >= 1020 || initialY >= 1020) {
Serial.println("[ERR] Analog read stuck at 1023. Verify A0/A1 wiring and 5V rail.");
} else if (initialX <= 5 || initialY <= 5) {
Serial.println("[ERR] Analog read stuck at 0. Check GND connection to KY-023.");
} else {
Serial.println("Joystick initialized successfully.");
}
}
void loop() {
int rawX = analogRead(VRX_PIN);
int rawY = analogRead(VRY_PIN);
bool buttonPressed = (digitalRead(SW_PIN) == LOW);
int mappedX = applyDeadzone(rawX);
int mappedY = applyDeadzone(rawY);
// Map X-axis to Servo angle (0-180)
int servoAngle = map(mappedX, -100, 100, SERVO_MIN, SERVO_MAX);
myServo.write(servoAngle);
// Serial output for debugging (view in Serial Plotter)
Serial.print("X:"); Serial.print(mappedX);
Serial.print("\tY:"); Serial.print(mappedY);
Serial.print("\tBtn:"); Serial.println(buttonPressed ? "PRESSED" : "RELEASED");
delay(20); // 50Hz update rate, matches standard RC servo refresh rate
}
// Function to apply center deadzone and normalize to -100 to +100
int applyDeadzone(int rawVal) {
int offset = rawVal - CENTER_VAL;
if (abs(offset) < DEADZONE) {
return 0; // Inside deadzone, return exact center
}
// Map the remaining range to -100 to +100
if (offset > 0) {
return map(offset, DEADZONE, CENTER_VAL, 0, 100);
} else {
return map(offset, -CENTER_VAL, -DEADZONE, -100, 0);
}
}
Troubleshooting: Drifting, Jitter, and Stuck Values
When a joystick Arduino project fails, it is almost always an analog reference or mechanical wear issue, not a broken microcontroller. If your build isn't working, here are the first three things to check:
- VCC/GND Polarity Swap: If you accidentally wire 5V to GND and GND to 5V on the KY-023, you will forward-bias the internal ESD diodes and send 5V directly into the Arduino's ground plane. You will smell burning plastic. Unplug immediately; the module is destroyed, and the Arduino's 5V linear regulator may be fried.
- Analog Pin Mapping Mismatch: Verify that the physical wire on A0 matches
#define VRX_PIN A0in the code. Plugging into A2 while the code reads A0 will result in floating, random values (0-1023 jumping wildly). - USB Brownout: The SG90 servo can draw up to 700mA under stall conditions. The Arduino Uno's onboard 5V regulator and USB polyfuse are typically limited to 500mA-800mA. If the Arduino resets randomly when you move the servo, power the servo from a separate 5V buck converter, sharing only the GND with the Arduino.
Specific Error Strings and Ranked Causes
Symptom: Serial Monitor outputs: [ERR] Analog read stuck at 1023. Verify A0/A1 wiring and 5V rail. OR the servo is pinned hard to 180 degrees and won't move.
Ranked Causes:
- Floating Analog Pin (Most Likely): The wire between the KY-023 VRx pin and Arduino A0 is loose or broken. The ADC is reading ambient electrical noise and latching high. Reseat the Dupont connector.
- Wiper Track Oxidation: The internal B103 potentiometer wiper has lost contact with the carbon track due to dirt or physical wear. Spray a micro-drop of DeoxIT or isopropyl alcohol into the pot housing and work the joystick in circles for 30 seconds.
- Short to VCC: A stray strand of copper from the VRx wire is touching the 5V rail on the breadboard, forcing 5V directly into the A0 pin.
Symptom: Servo jitters continuously at the center position, even when you aren't touching the joystick.
Fix: This is ADC noise. Increase the DEADZONE constant in the code from 25 to 40. Alternatively, add a 0.1µF (100nF) ceramic capacitor between the VRx pin and GND on the breadboard to create a low-pass hardware filter, smoothing out high-frequency voltage ripples. See Arduino Serial Communication Docs for more on parsing clean data.
Extending and Simplifying the Build
If your goal is to use the joystick as a PC game controller, the Arduino Uno R3 cannot natively act as a USB HID device. To simplify this build for gaming, swap the Uno R3 for an Arduino Leonardo or Pro Micro (ATmega32U4). These boards feature native USB and can use the standard
Joystick.h library to appear as an Xbox/PlayStation controller to Windows without custom serial bridges.
How to Extend the Build:
- Add I2C Telemetry: Wire an SSD1306 128x64 OLED display to the I2C pins (A4/A5). Print the mapped X/Y coordinates in real-time. This is highly useful when debugging RC car steering endpoints.
- Upgrade to Bluetooth RC: Replace the Uno R3 with an ESP32 DevKit V1. The ESP32 has a 12-bit ADC (0-4095) and built-in Bluetooth. You can map the joystick values and transmit them via BLE to an L298N motor driver on a robot chassis. Note: The ESP32 ADC is non-linear at the extremes; you will need to implement a software lookup table to correct the 0-4095 curve.
- Dual-Joystick Tank Steering: Add a second KY-023 module to A2 and A3. Use differential steering logic (Left Motor = Y + X, Right Motor = Y - X) to control a tracked robot.
Frequently Asked Questions
Why is my joystick Arduino analog read jumping around?
Analog reads jumping between values (e.g., 510, 515, 508) is normal behavior caused by the Arduino's 10-bit ADC quantization and minor electrical noise on the 5V rail. The USB power from a PC is rarely perfectly clean. To fix this in software, implement the deadzone logic shown in the code above, or take 10 rapid readings and average them (oversampling) before mapping the value.
Can I use a KY-023 joystick with an ESP32 or Raspberry Pi Pico?
Yes, but you must adjust the voltage and code. The Raspberry Pi Pico and ESP32 are 3.3V logic devices. You must wire the KY-023 VCC pin to the 3.3V output of the microcontroller, not 5V. If you feed 5V into the analog pins of an ESP32, you will permanently damage the GPIO matrix. Additionally, update the ADC_MAX in the code to 4095 for the ESP32 (12-bit) or 65535 for the Pico (16-bit default mapping).
How do I use the joystick button (SW pin) without a resistor?
The KY-023 SW pin does not have an onboard pull-up resistor. When the button is released, the pin is left "floating" and will read random HIGH/LOW states. You solve this in software by setting the pin mode to INPUT_PULLUP in the setup() function. This activates the ATmega328P's internal 20kΩ-50kΩ pull-up resistor, holding the pin HIGH until the joystick is pressed, which shorts it to GND (reading LOW).
What is the difference between the KY-023 and a PS2 joystick module?
Electrically, they are nearly identical; both use dual 10kΩ potentiometers and a tactile switch. The difference is purely mechanical and dimensional. The PS2 module (often labeled as the Thumb Joystick Shield or bare PS2 stick) uses a shorter, thumb-operated stalk with a tighter gimbal mechanism, offering slightly better centering spring tension. The KY-023 uses a longer, full-hand arcade-style stalk. For Arduino code and wiring purposes, they are 100% interchangeable.






