Robust joystick-to-command transmitter for ESP32.
Vector normalization, hysteresis, spike rejection, and ESP-NOW send — fixes asymmetric joystick ranges and eliminates direction chatter.
- Overview
- Features
- Hardware
- Wiring / Pinout
- Quick start
- Calibration & tuning
- Usage examples
- Troubleshooting
- License
This project reads an analog joystick on an ESP32, filters and normalizes the X/Y inputs asymmetrically (to account for differing travel to 0 and to ADC max), computes a 2D vector, and maps that vector to one of five commands: F (forward), B (back), L (left), R (right) or S (stop). Commands are sent over ESP-NOW to a receiver MAC you configure.
The approach solves these problems you likely saw earlier:
- unequal axis travel (center→0 vs center→4095)
- noisy ADC samples and single-sample saturations
- rapid toggling when joystick near boundaries
- blocked loops causing sluggish stop behavior
- Asymmetric normalization so left/right and forward/back map evenly
- Median + moving average smoothing
- Spike rejection for 0/4095 saturations
- Vector magnitude + angle decision (angle sector mapping)
- Magnitude hysteresis (enter/exit thresholds) and angular hysteresis
- Emergency stop via button (flushes buffers, forces
S) - ESP-NOW support (single-char command send)
Minimum:
- 1 × ESP32 development board
- 1 × Analog joystick (or two if you have separate sticks)
- Jumper wires
- Optional: motor controller + motors on receiver side
Pins used (default):
PIN_X(joystick X) → GPIO 35 (VRX)PIN_Y(joystick Y) → GPIO 34 (VRY)PIN_SW(button) → GPIO 12 (INPUT_PULLUP)
Receiver MAC (set on transmitter):
uint8_t peerMac[] = { 0x20, 0xE7, 0xC8, 0x68, 0xB8, 0x30 };- Joystick module: VCC → 3.3V, GND → GND
- Joystick VRx → GPIO 35
- Joystick VRy → GPIO 34
- Joystick SW (button) → GPIO 12 (with internal pull-up in code)
Tip: on many joysticks the Y axis may be inverted relative to your expectations. The code normalizes and maps angles so inversion just flips direction labels; if needed swap axis wiring or invert sign.
- Clone repository
git clone https://github.com/PasinduPabasaraSL/esp32-rc-car-controller.git
cd esp32-rc-car-controller- Open the sketch in Arduino IDE or PlatformIO:
- Arduino: open
vectormap_joystick_with_espnow.ino - PlatformIO: import the project
-
Update
peerMacin the top of the transmitter sketch to the receiver ESP32 MAC. -
Compile and flash to the ESP32:
# PlatformIO example
pio run -t upload
# or using esptool / Arduino IDE upload- Open serial monitor (115200) and keep joystick idle while the sketch calibrates center. You’ll see:
Calibrating center - keep joystick idle...
CenterX=2980 CenterY=2925
Ready (vector normalization + hysteresis mode)
- Move the joystick and watch
R,L,F,B,Sprinted and sent over ESP-NOW.
At startup the sketch samples the stick for a short time to determine centerX and centerY. Keep the stick idle during that period.
ENTER_MAG(default0.35) — normalized magnitude required to start moving. Increase to require larger push.EXIT_MAG(default0.25) — magnitude below which the code returns toS. Lower gives more sensitivity; higher gives more stability.ANGLE_HYST(degrees, default15) — prevents flipping near sector borders. Increase to bias chosen direction longer.AVG_SAMPLES/MEDIAN_WINDOW— smoothing window sizes. Larger windows = smoother but more latency.SPIKE_DELTA— spike rejection sensitivity for sudden 0/4095 jumps.
- Increase
ENTER_MAGif you get false moves. - Increase
EXIT_MAGa little if stop bounces back to a direction. - For responsiveness reduce
AVG_SAMPLESandMEDIAN_WINDOWbut watch noise. - Re-run and test full-range pushes (0 and 4095) to verify normalization.
- Normal idle:
x=3025 y=2875 dx=0 dy=0 dxn=0.000 dyn=0.000 mag=0.000 ang=0.0 -> S
- Push right:
x=3892 y=2999 dx=913 dy=0 dxn=0.818 dyn=0.000 mag=0.818 ang=0.0 -> R
- Push forward:
x=2143 y=0 dx=-834 dy=-2911 dxn=-0.743 dyn=-1.000 mag=1.255 ang=90.0 -> F
Problem: stop didn’t work — car moved shortly after pressing button
Solution: ensure you have the version that flushes buffers on button press. That forces filters to center and keeps the S command until release.
Problem: always sends L or biased direction
- Check wiring (swap X wires), ensure calibration run with joystick idle.
- Verify center values printed at startup; if center looks very off, tighten connections or re-run.
- Adjust
ENTER_MAG/EXIT_MAG.
Problem: jitter or flicker at neutral
- Increase
EXIT_MAGor median window; increaseDELAYslightly. - If flicker persists at exact diagonal boundaries increase
ANGLE_HYST.
Suggested PR template:
### Summary
What changes and why.
### How tested
Steps to reproduce + expected logs.
### Notes
Any tuning recommendations or breaking changes.This project is released under the MIT License. See LICENSE.
- Original algorithm: asymmetric normalization + vector mapping idea.
- Inspired by iterative debugging of joystick edge/zero saturation problems.