Created
March 24, 2026 11:52
-
-
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...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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