A flexible and expandable wireless Bluetooth (BLE) joystick built with an ESP32 and the Arduino IDE. This project emulates a Bluetooth keyboard or gamepad, making it compatible with most PCs and mobile devices out-of-the-box.
The code is designed to be easily modified, allowing you to add and map new buttons, switches, or other inputs as your project grows.
- Wireless Control: Uses Bluetooth Low Energy (BLE) to connect to any compatible device as a keyboard/gamepad.
- Highly Configurable: Easily remap buttons and axes by changing a few lines of code.
- Scalable: The default code supports 4 buttons and 2 joystick axes, but it's trivial to add more.
- Low Cost: Built with common and inexpensive components.
- An ESP32 development board (e.g., ESP32-WROOM-32, ESP32-C3, ESP32-S3)
- 2 x 2-axis Analog Joystick Modules
- 4 x Push Buttons (or any other digital input)
- Breadboard and Jumper Wires
This project relies on the BLEKeyboard library by T-vK.
Repository Link: https://github.com/T-vK/ESP32-BLE-Keyboard
You must install this library manually, as the version on the Arduino Library Manager may be outdated.
- Go to the
BLEKeyboardReleases Page. - Download the
.zipfile for the latest release (Not Pre-release) (e.g.,BLEKeyboard-1.0.3.zip). - DO NOT download the
mainbranch or the "Source code" zip from the main repository page. This development branch may be unstable. - In the Arduino IDE, go to Sketch > Include Library > Add .ZIP Library...
- Select the
.zipfile you just downloaded.
You will also need the ESP32 board definitions installed in your Arduino IDE.
- Wire Hardware: Connect your joysticks and buttons to the ESP32. See the Default Pinout section below for the default configuration.
- Install Libraries: Make sure you have installed the
BLEKeyboardlibrary as described above. - Open & Upload: Open the
.inofile in the Arduino IDE, select your ESP32 board, and upload the code. - Pair: On your computer, phone, or tablet, go to Bluetooth settings. You will see a new device named "ESP32-BLE-Joystick". Pair with it.
- Play! The ESP32 will now send keystrokes to your device when you move the joysticks or press the buttons.
The included code is pre-configured for the setup described below. You can easily change any of these pins by editing their definitions at the top of the .ino file.
- Joystick 1: Controls the Y-Axis (e.g., W/S keys)
- Joystick 2: Controls the X-Axis (e.g., A/D keys)
- 4 Buttons:
- One buttons associated with Joystick 1
- One buttons associated with Joystick 2
- Two independent external buttons
| Component | Function | ESP32 Pin (Example) | Default Key Mapped |
|---|---|---|---|
| Joystick 1 (Vertical) | Y-Axis | GPIO 4 (ADC) |
w (up), s (down) |
| Joystick 2 (Horizontal) | X-Axis | GPIO 12 (ADC) |
a (left), d (right) |
| --- | --- | --- | --- |
| Joystick 1 Button 1 | Button 1 | GPIO 6 |
e.g., 'KEY_DOWN_ARROW' |
| Joystick 2 Button 1 | Button 2 | GPIO 13 |
e.g., 'KEY_UP_ARROW' |
| External Button 1 | Button 3 | GPIO 7 |
e.g., KEY_LEFT_ARROW |
| External Button 2 | Button 4 | GPIO 10 |
e.g., KEY_RIGHT_ARROW |
Note: The GPIO pins used for joysticks must be ADC-capable (Analog-to-Digital Converter). On most ESP32s, these are pins GPIO 32-39 (ADC1) and GPIO 0, 2, 4, 6, 7, 10, 12-15, 25-27 (ADC2).
To change the keys, find the main loop() function. The logic for button presses and joystick movement is located there.
Change the key inside the bleKeyboard.press() and bleKeyboard.release() functions.
Example: Changing Button 2 from KEY_UP_ARROW to the letter 'g'
// Change this:
bleKeyboard.press(KEY_DOWN_ARROW);
delay(100);
bleKeyboard.release(KEY_DOWN_ARROW);
// To this:
bleKeyboard.press('g');
delay(100);
bleKeyboard.release('g');For special keys (like Ctrl, Shift, F1, Arrow Keys, etc.), you must use the special key codes defined by the library (e.g., KEY_F1, KEY_RIGHT_ARROW).
- Connect your new button to a free GPIO pin on the ESP32 (e.g.,
GPIO 23). - Define the pin at the top of the file:
const int NEW_BUTTON_PIN = 23;
- Set its
pinModein thesetup()function. We useINPUT_PULLUPso we don't need an external resistor.pinMode(NEW_BUTTON_PIN, INPUT_PULLDOWN); - Set its
pinModein thesetup()function. We useINPUT_PULLUPso we don't need an external resistor.int buttonNewValue = digitalRead(NEW_BUTTON_PIN); - Add the logic to read it in the
loop()function.// Check new button if (buttonNewValue == HIGH) { bleKeyboard.press('z'); delay(100); bleKeyboard.release('z'); }
This project is open-source and available under the MIT License.
- Big thanks to T-vK for the excellent BLEKeyboard library that makes this all possible.