If you like to build a hustle free wireless, rechargable, BLE controller prototype based on ESP32, it is very helpful to focus on established development boards, that provide proper LIPO recharging chipsets. There is a wide, affordable palette of devices available – over here, we use the ESP32 WROOM setup, you can find in this devboard for textile projects VELLEMANN or a more reduced one by AZ Delivery ESP Lolin.
Also for convenient reasons, i prefer to use LIPO lithium rechargable battery packs – they come with a variety of capacity. I can recomment 100-500 mAh for this ESP32 project – you can go lower when using ESP8266 for sure.
BASIC GAMEPAD TEST SETUP IN CPP
We can use the ESP32 with its integrated Bluetooth to simulate any generic gamepad. With this, we have a pretty standard input device for a wide range of digital environments like Browser, Unity or Touchdesigner without further plugins or extensions. This approach works best with the pretty stable Bluetooth library for ESP32 by lemming – include the lib to your project: https://github.com/lemmingDev/ESP32-BLE-Gamepad
// include all necessary libs #include <Arduino.h> #include <BleGamepad.h> // initalize controller - et custom device name, manufacturer and initial battery level BleGamepad bleGamepad("TURBOCONTROLLER v1", "TURBOFLIP STUDIOS", 99); void setup() { Serial.println("try initalize gamepad"); bleGamepad.begin(); Serial.begin(9600); delay(200); Serial.println("gamepad initalized"); } void loop() { // only operate when connected to host if (bleGamepad.isConnected()){ // ------------------------------------------ // simple test setup for axis and buttons // ------------------------------------------ // Set the duration of a sine wave to simulate axis movement const int waveDuration = 5000; const int sampleRate = 20; // Set the sample rate in milliseconds const int numSamples = waveDuration / sampleRate; const float frequency = 0.1; // Set the frequency of the sine wave // Calculate sine wave values for x and y axes float xValue = 0.5 * sin(2.0 * PI * frequency * millis() / 1000.0) + 0.5; float yValue = 0.5 * sin(2.0 * PI * frequency * millis() / 300.0) + 0.5; // overwrite when working with real analog data from custom input sticks //xValue = float(joy_x)*0.00024414062; //yValue = float(joy_y)*0.00024414062; // hardcoded calibration - when hardware sends uncalibrated data if(xValue > 0.49 && xValue < 0.51 ){xValue = 0.5;} if(yValue > 0.49 && yValue < 0.51 ){yValue = 0.5;} // Convert float values to int16_t (16-bit) int16_t xIntValue = static_cast<int16_t>(xValue * 32767); int16_t yIntValue = static_cast<int16_t>(yValue * 32767); // Set the axes values bleGamepad.setLeftThumb(xIntValue, yIntValue); // Calculate sine wave values for x and y axes float xValue2 = 0.5 * sin(2.0 * PI * frequency * millis() / 100.0) + 0.5; float yValue2 = 0.5 * sin(2.0 * PI * frequency * millis() / 400.0) + 0.5; // Convert float values to int16_t (16-bit) int16_t xIntValue2 = static_cast<int16_t>(xValue2 * 32767); int16_t yIntValue2 = static_cast<int16_t>(yValue2 * 32767); // Set the axes values bleGamepad.setRightThumb(xIntValue2, yIntValue2); } // set a delay to avoid possible data overflow delay(10); }
advanced ESP3 controller
#include <Arduino.h> #include <BleGamepad.h> #include <trxy_btn.h> #define ACTLEDPIN 22 // Pin button is attached to int tick = 0; bool interaction = false; unsigned long lastInteractionTime = 0; BleGamepad bleGamepad("TURBOCONTROLLER v2", "TURBOFLIP STUDIOS", 99); // Set custom device name, manufacturer and initial battery level aBTN mainbtn(34,false,false); aBTN shiftbtn(32,false,false); aBTN btn3(26,true,true); aBTN btn4(25,true,true); // ------------------------------------- void enterDeepSleep() { esp_sleep_enable_ext0_wakeup(GPIO_NUM_25, LOW); // Wake up when BUTTON_1_PIN goes low esp_sleep_enable_ext0_wakeup(GPIO_NUM_26, LOW); // Wake up when BUTTON_2_PIN goes low esp_sleep_enable_ext0_wakeup(GPIO_NUM_32, LOW); // Wake up when BUTTON_3_PIN goes low esp_sleep_enable_ext0_wakeup(GPIO_NUM_34, LOW); // Wake up when BUTTON_4_PIN goes low esp_deep_sleep(300e6); // 300 seconds = 300e6 microseconds } // ------------------------------------- void setup() { pinMode(ACTLEDPIN, OUTPUT); BleGamepadConfiguration bleGamepadConfig; bleGamepadConfig.setAutoReport(false); bleGamepad.begin(&bleGamepadConfig); delay(200); // Serial.println("initalize gampad"); } void blink(){ digitalWrite(ACTLEDPIN, HIGH); delay(20); digitalWrite(ACTLEDPIN, LOW); delay(20); digitalWrite(ACTLEDPIN, HIGH); delay(20); digitalWrite(ACTLEDPIN, LOW); } void loop() { if (bleGamepad.isConnected()){ // indicate connection to host digitalWrite(ACTLEDPIN, LOW); btn3.operateBUTTON(); btn4.operateBUTTON(); mainbtn.operateBUTTON(); shiftbtn.operateBUTTON(); // -------------------------------------------- // Track interaction if (btn3.on_released || btn4.on_released || mainbtn.on_released || shiftbtn.on_released) { lastInteractionTime = millis(); } // Check for inactivity unsigned long currentTime = millis(); if (currentTime - lastInteractionTime >= 300000) { // 300 seconds = 300,000 milliseconds // If no interaction for 300 seconds, enter deep sleep enterDeepSleep(); } // ------------------------------------------ if (btn4.on_released) { digitalWrite(ACTLEDPIN,LOW); bleGamepad.release(BUTTON_4); interaction = false; bleGamepad.sendReport(); blink(); } if (btn4.on_pressed) { digitalWrite(ACTLEDPIN, HIGH ); bleGamepad.press(BUTTON_4); interaction = true; bleGamepad.sendReport(); } // ------------------------------------------ if (btn3.on_released) { bleGamepad.release(BUTTON_3); bleGamepad.sendReport(); delay(10); } if (btn3.on_pressed) { bleGamepad.press(BUTTON_3); bleGamepad.sendReport(); delay(10); blink(); } // ------------------------------------------ if (shiftbtn.on_released) { bleGamepad.release(BUTTON_2); bleGamepad.sendReport(); digitalWrite(ACTLEDPIN,LOW); delay(10); } if (shiftbtn.on_pressed) { bleGamepad.press(BUTTON_2); bleGamepad.sendReport(); digitalWrite(ACTLEDPIN,HIGH); delay(10); } // ------------------------------------------ if (mainbtn.on_released) { // Serial.println("mainbtn OFF"); bleGamepad.release(BUTTON_1); bleGamepad.sendReport(); digitalWrite(ACTLEDPIN,LOW); interaction = false; delay(10); } if (mainbtn.on_pressed) { // Serial.println("mainbtn"); bleGamepad.press(BUTTON_1); bleGamepad.sendReport(); digitalWrite(ACTLEDPIN, HIGH); interaction = true; delay(10); }else{ } tick++; if(tick > 60){ tick = 0; if( !interaction){ digitalWrite(ACTLEDPIN, HIGH); delay(6); digitalWrite(ACTLEDPIN, LOW); } } } delay(10); return; // // { // joy_x = analogRead(JOYSTICK_X_PIN) ; // joy_y = analogRead(JOYSTICK_Y_PIN) ; // Serial.println(joy_x); /* // Set the duration of the sine wave in milliseconds const int waveDuration = 5000; const int sampleRate = 20; // Set the sample rate in milliseconds const int numSamples = waveDuration / sampleRate; const float frequency = 0.1; // Set the frequency of the sine wave // Calculate sine wave values for x and y axes float xValue = 0.5 * sin(2.0 * PI * frequency * millis() / 1000.0) + 0.5; float yValue = 0.5 * sin(2.0 * PI * frequency * millis() / 300.0) + 0.5; xValue = float(joy_x)*0.00024414062; yValue = float(joy_y)*0.00024414062; // hardcoded calib if(xValue > 0.49 && xValue < 0.51 ){xValue = 0.5;} if(yValue > 0.49 && yValue < 0.51 ){yValue = 0.5;} // Convert float values to int16_t (16-bit) int16_t xIntValue = static_cast<int16_t>(xValue * 32767); int16_t yIntValue = static_cast<int16_t>(yValue * 32767); // Set the axes values bleGamepad.setLeftThumb(xIntValue, yIntValue); // Calculate sine wave values for x and y axes float xValue2 = 0.5 * sin(2.0 * PI * frequency * millis() / 100.0) + 0.5; float yValue2 = 0.5 * sin(2.0 * PI * frequency * millis() / 400.0) + 0.5; // Convert float values to int16_t (16-bit) int16_t xIntValue2 = static_cast<int16_t>(xValue2 * 32767); int16_t yIntValue2 = static_cast<int16_t>(yValue2 * 32767); // Set the axes values bleGamepad.setRightThumb(xIntValue2, yIntValue2); } else { digitalWrite(LEDPIN, LOW); } */ delay(10); }
Simply put this helper class in your project to make button interaction more comfortable.
// ------------------------------- // TXY'S button helper class // -------------------------------- // serves you wonderful functionality right on the plate :) // is_pressed = momentary state of button // is_holded = momentary state if button is down for at least 1s // on_pressed = triggered once if button is down // on_pressed = triggered once if button is up // on_holded = triggered once if button is down for at least 1s class aBTN { private: int bpin; long ts = 0; bool previous_state = false; bool prev_is_holded = false; bool flipped_phase = false; public: bool is_pressed = false; bool is_holded = false; bool on_pressed = false; bool on_released = false; bool on_holded = false; bool need_pullup = false; aBTN(int bpin, bool _flipped_phase, bool _pu) { this->bpin = bpin; this->flipped_phase = _flipped_phase; this->need_pullup = _pu; init(); } void init() { if(!need_pullup){ pinMode(bpin, INPUT); }else{ pinMode(bpin, INPUT_PULLUP); } } void operateBUTTON() { this->is_pressed = digitalRead(bpin); // flip phase if button phase is flipped if(this->flipped_phase){this->is_pressed = !this->is_pressed;} // reset press states this->on_pressed = false; this->on_released = false; // if there is any change of state :) if (this->previous_state != this->is_pressed) { if (this->is_pressed) { //button is pressed down ------- this->ts = millis(); // set timestamp this->on_pressed = true; } else { //button is released ------- this->on_released = true; this->is_holded = false; this->on_holded = false; this->prev_is_holded = false; } // buffer prev state to avoid repeat this->previous_state = this->is_pressed; } // reset on hold state first this->on_holded = false; // if button is holded for more than 1s and is still pressed! if (this->ts + 1000 < millis() && this->is_pressed ) { this->is_holded = true; // set is holded state each frame here if (this->prev_is_holded != this->is_holded && this->is_pressed ) { this->on_holded = true; this->prev_is_holded = this->is_holded; } } } };
quick testing
You can evaluate your gamepad functions with this simple browser based tool immediately: https://hardwaretester.com/gamepad – This can give you a clue if all data is send in the right format and boundaries.