Skip to content

Instantly share code, notes, and snippets.

@Robotto
Created March 24, 2026 11:52
Show Gist options
  • Select an option

  • Save Robotto/70a5ca63b3faa3b21995607e9b3c2ff0 to your computer and use it in GitHub Desktop.

Select an option

Save Robotto/70a5ca63b3faa3b21995607e9b3c2ff0 to your computer and use it in GitHub Desktop.
Use an ESP32 as a bluetooth joypad... maybe you need to calibrate it on first use...
#include <Arduino.h>
#include <BleGamepad.h> //http://librarymanager/All#ESP32-BLE-Gamepad
//BleGamepad bleGamepad;
BleGamepad bleGamepad("BLEstick", "orksat.me", 100); // Set custom device name, manufacturer and initial battery level
BleGamepadConfiguration bleGamepadConfig; // Create a BleGamepadConfiguration object to store all of the options
const int delayBetweenHIDReports = 5; // Additional delay in milliseconds between HID reports
const int buttonPin = 33; //Looks like the button is active low
const int xAxisPin = 36; //pin name SVP = ADC2
const int yAxisPin = 39; //pin name SVN = ADC3
void setup()
{
Serial.begin(115200);
Serial.println("Starting BLE work!");
//bleGamepad.begin();
bleGamepadConfig.setAutoReport(true);
bleGamepadConfig.setControllerType(CONTROLLER_TYPE_GAMEPAD); // CONTROLLER_TYPE_JOYSTICK, CONTROLLER_TYPE_GAMEPAD (DEFAULT), CONTROLLER_TYPE_MULTI_AXIS
bleGamepadConfig.setButtonCount(1);
bleGamepadConfig.setHatSwitchCount(0);
bleGamepadConfig.setWhichSpecialButtons(false, false, false, false, false, false, false, false);
bleGamepadConfig.setWhichAxes(true, true, false, false, false, false, false, false);
bleGamepadConfig.setIncludeThrottle(false);
bleGamepadConfig.setVid(0xdead);
bleGamepadConfig.setPid(0xbeef);
bleGamepadConfig.setModelNumber("1.0");
bleGamepadConfig.setSoftwareRevision("Software Rev 1");
bleGamepadConfig.setSerialNumber("24404972");
bleGamepadConfig.setFirmwareRevision("2.0");
bleGamepadConfig.setHardwareRevision("0.1");
// Some non-Windows operating systems and web based gamepad testers don't like min axis set below 0, so 0 is set by default
//bleGamepadConfig.setAxesMin(0x8001); // -32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
bleGamepadConfig.setAxesMin(0x0000); // 0 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
bleGamepadConfig.setAxesMax(0x7FFF); // 32767 --> int16_t - 16 bit signed integer - Can be in decimal or hexadecimal
bleGamepad.begin(&bleGamepadConfig); // Begin gamepad with configuration options
bleGamepad.setBatteryPowerInformation(POWER_STATE_NOT_PRESENT);
pinMode(buttonPin, INPUT);
}
//Debouncing the button is kinda tricky:
unsigned long buttonChangedAtTime=0; //store millis at change so we can debounce the button
bool lastButtonState=false; //Store last button reading, so we know if it changed
bool buttonHasChanged=false;
const unsigned long buttonDebounceTime = 20; //time before any button change is valid
const int numberOfAnalogReadingsToAverage=16;
void loop()
{
int X,Y;
bool BTN;
bool currentButtonState=digitalRead(buttonPin);
unsigned long xAvg=0;
unsigned long yAvg=0;
//Do a bunch of analog readings and calculate the average, to filter out noise.
for(int i=0;i<numberOfAnalogReadingsToAverage;i++){
xAvg+=analogRead(xAxisPin);
yAvg+=analogRead(yAxisPin);
}
X = xAvg/numberOfAnalogReadingsToAverage;
Y = yAvg/numberOfAnalogReadingsToAverage;
if(lastButtonState!=currentButtonState)
{
buttonChangedAtTime=millis();
lastButtonState=currentButtonState;
buttonHasChanged=true;
}
if(buttonHasChanged && millis()>buttonChangedAtTime+buttonDebounceTime)
{
BTN=currentButtonState;
buttonHasChanged=false;
}
/*
Serial.print("X:");
Serial.print(X);
Serial.print("\tY:");
Serial.print(Y);
Serial.print("\tBTN:");
Serial.print(BTN);
Serial.println();
*/
int xMinimum = 0;
int xMaximum = 4095;
int yMinimum = 0;
int yMaximum = 4095;
//Constrain values between max and min to avoid weird behaviour if values overshoot
int adjustedX = constrain(X, xMinimum, xMaximum);
int adjustedY = constrain(Y, yMinimum, yMaximum);
// Map adjusted X and Y to 0x7FFF ~ 0x8001 for use as an axis reading
adjustedX = map(adjustedX, xMinimum, xMaximum, 0, 0x7FFF);
adjustedY = map(adjustedY, yMinimum, yMaximum, 0x7FFF, 0);
if (bleGamepad.isConnected())
{
// Update throttle axis and auto-send report
bleGamepad.setAxes(adjustedX,adjustedY);
if(!BTN) bleGamepad.press(BUTTON_1); //Button is active low, so we press the gamepad button if reads (!)not true.
else bleGamepad.release(BUTTON_1);
delay(delayBetweenHIDReports);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment