├── README.md └── ESP32_GAMEPAD └── ESP32_GAMEPAD.ino /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_GAMEPAD 2 | ESP32 Gamepad/Joystick interface using HID over BLE. 3 | 4 | Features: 5 | * Code can be compiled to either create and HID joystick or gamepad. Currently the configuration is fixed with 2 axes and 16 buttons. 6 | * Easy to configure the mapping of GPIO pins to specific buttons. It is possible to skip buttons - for example to map GPIOs to buttons 1, 2, 5, 6 and skip buttons 3 and 4. 7 | * Debounce of input to avoid accdental button presses. Button pressed as immediately reported, for a release to be registered the button has to remain in a released state for 10ms. 8 | * Suports creating an additional keyboard device in order to report key presses. Disabled by default. 9 | 10 | Default pin mapping: 11 | * Up/Down/Left/Right: GPIO 27/26/25/33 12 | * Button 1-8: GPIO 23, 22, 21, 19, 18, 17, 16, 4 13 | -------------------------------------------------------------------------------- /ESP32_GAMEPAD/ESP32_GAMEPAD.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "BLE2902.h" 6 | #include "BLEHIDDevice.h" 7 | #include "HIDTypes.h" 8 | 9 | #define JOYSTICK_MODE // Define to report device as a Joystick, undefine to report device as Gamepad 10 | #undef ADD_KEYBOARD // Define to add a 2nd keyboard device to the gamepad/joystick (for special keys etc.) 11 | 12 | #define DEBOUNCE_MS 10 // Will must see 10 ms of continuous not-pressed input before we will report it. 13 | #define CONNECT_LED_PIN 2 // Use built-in LED to dislay connection state 14 | 15 | // Map GPIO pins to buttons and axis inputs. Bit 0 = button 1, bit 1 = button 2, etc. Bit 31, 30, 29, 28= Up, Down, Left, Right 16 | #define GPIO39_MAP 0x00000000 // . 17 | #define GPIO36_MAP 0x00000000 // . 18 | #define GPIO35_MAP 0x00000000 // . 19 | #define GPIO34_MAP 0x00000000 // . 20 | #define GPIO33_MAP 0x00010000 // Right 21 | #define GPIO32_MAP 0x00000000 // . 22 | #define GPIO27_MAP 0x00080000 // Up 23 | #define GPIO26_MAP 0x00040000 // Down 24 | #define GPIO25_MAP 0x00020000 // Left 25 | #define GPIO23_MAP 0x00000001 // button 1 26 | #define GPIO22_MAP 0x00000002 // button 2 27 | #define GPIO21_MAP 0x00000004 // button 3 28 | #define GPIO19_MAP 0x00000008 // button 4 29 | #define GPIO18_MAP 0x00000010 // button 5 30 | #define GPIO17_MAP 0x00000020 // button 6 31 | #define GPIO16_MAP 0x00000040 // button 7 32 | #define GPIO13_MAP 0x00000000 // . 33 | #define GPIO4_MAP 0x00000080 // button 8 34 | 35 | BLEHIDDevice* hid; 36 | BLECharacteristic* input; 37 | #ifdef ADD_KEYBOARD 38 | BLECharacteristic* input2; 39 | BLECharacteristic* output2; 40 | #endif 41 | 42 | bool connected = false; 43 | uint32_t current_state = 0; // current (debounced) mapped input state 44 | int next_zero_ms[32]; // next millisec timestamp each bit is allowed to be zeroed 45 | 46 | // MAME4Droid 0.139 sees buttons as A, B, C, X, Y, Z, L1, R1, L2, R2, SELECT, START, MODE, THUMBL, THUMBR, unknown 47 | const uint8_t report[] = { 48 | USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls 49 | #ifdef JOYSTICK_MODE 50 | USAGE(1), 0x04, // Joystick 51 | #else 52 | USAGE(1), 0x05, // Gamepad 53 | #endif 54 | COLLECTION(1), 0x01, // Application 55 | REPORT_ID(1), 0x01, // Report ID (1) 56 | USAGE_PAGE(1), 0x09, // Buttons... 57 | USAGE_MINIMUM(1), 0x01, // Minimum - button 1 58 | USAGE_MAXIMUM(1), 0x10, // Maximum - button 16 59 | LOGICAL_MINIMUM(1), 0x00, // Logical minimum 0 (not pressed) 60 | LOGICAL_MAXIMUM(1), 0x01, // Logical maximum 1 (pressed) 61 | REPORT_SIZE(1), 0x01, // 1 bit per button 62 | REPORT_COUNT(1), 0x10, // 16 buttons to report on (two bytes in total) 63 | HIDINPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position 64 | USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls 65 | USAGE(1), 0x30, // Usage (x-axis) 66 | USAGE(1), 0x31, // Usage (y-axis) 67 | LOGICAL_MINIMUM(1), 0x81, // Logical minimum -127 68 | LOGICAL_MAXIMUM(1), 0x7F, // Logical maximum 127 69 | REPORT_SIZE(1), 0x08, // 1 byte per axis 70 | REPORT_COUNT(1), 0x02, // 2 axis in the report (2 bytes in total) 71 | HIDINPUT(1), 0x02, // Data,Var,Abs,X & Y positions 72 | END_COLLECTION(0), 73 | #ifdef ADD_KEYBOARD 74 | // composite keyboard device 75 | USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls 76 | USAGE(1), 0x06, // Keyboard 77 | COLLECTION(1), 0x01, // Application 78 | REPORT_ID(1), 0x02, // Report ID (2) 79 | USAGE_PAGE(1), 0x07, // Usage Page (Key Codes) 80 | USAGE_MINIMUM(1), 0xE0, 81 | USAGE_MAXIMUM(1), 0xE7, 82 | LOGICAL_MINIMUM(1), 0x00, 83 | LOGICAL_MAXIMUM(1), 0x01, 84 | REPORT_SIZE(1), 0x01, // 1 byte (Modifiers) 85 | REPORT_COUNT(1), 0x08, 86 | HIDINPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position 87 | REPORT_COUNT(1), 0x01, // 1 byte (Reserved) 88 | REPORT_SIZE(1), 0x08, 89 | HIDINPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position 90 | REPORT_COUNT(1), 0x06, // 6 bytes (Keys) 91 | REPORT_SIZE(1), 0x08, 92 | LOGICAL_MINIMUM(1), 0x00, 93 | LOGICAL_MAXIMUM(1), 0x65, // 101 keys 94 | USAGE_MINIMUM(1), 0x00, 95 | USAGE_MAXIMUM(1), 0x65, 96 | HIDINPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position 97 | REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) 98 | REPORT_SIZE(1), 0x01, 99 | USAGE_PAGE(1), 0x08, // LEDs 100 | USAGE_MINIMUM(1), 0x01, // Num Lock 101 | USAGE_MAXIMUM(1), 0x05, // Kana 102 | HIDOUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile 103 | REPORT_COUNT(1), 0x01, // 3 bits (Padding) 104 | REPORT_SIZE(1), 0x03, 105 | HIDOUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile 106 | END_COLLECTION(0) 107 | #endif 108 | }; 109 | 110 | 111 | class ServerCallbacks: public BLEServerCallbacks 112 | { 113 | void onConnect(BLEServer* pServer) 114 | { 115 | connected = true; 116 | digitalWrite(CONNECT_LED_PIN, HIGH); 117 | BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 118 | desc->setNotifications(true); 119 | } 120 | 121 | void onDisconnect(BLEServer* pServer) 122 | { 123 | connected = false; 124 | digitalWrite(CONNECT_LED_PIN, LOW); 125 | BLE2902* desc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); 126 | desc->setNotifications(false); 127 | } 128 | }; 129 | 130 | #ifdef ADD_KEYBOARD 131 | class OutputCallbacks : public BLECharacteristicCallbacks 132 | { 133 | void onWrite(BLECharacteristic* me) 134 | { 135 | // uint8_t* value = (uint8_t*)(me->getValue().c_str()); 136 | // printf("special keys: %d", *value); 137 | } 138 | }; 139 | #endif 140 | 141 | void setup() 142 | { 143 | Serial.begin(115200); 144 | printf("Starting BLE Gamepad device\n!"); 145 | 146 | BLEDevice::init("ESP32-Gamepad"); 147 | BLEServer *pServer = BLEDevice::createServer(); 148 | pServer->setCallbacks(new ServerCallbacks()); 149 | 150 | hid = new BLEHIDDevice(pServer); 151 | input = hid->inputReport(1); // <-- input REPORTID 1 from report map 152 | 153 | #ifdef ADD_KEYBOARD 154 | input2 = hid->inputReport(2); // <-- input REPORTID 2 from report map 155 | output2 = hid->outputReport(2); // <-- output REPORTID 2 from report map 156 | output2->setCallbacks(new OutputCallbacks()); 157 | #endif 158 | 159 | BLESecurity *pSecurity = new BLESecurity(); 160 | pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); 161 | 162 | std::string name = "DeonVanDerWesthuysen"; 163 | hid->manufacturer()->setValue(name); 164 | hid->pnp(0x02, 0xADDE, 0xEFBE, 0x0100); // High and low bytes of words get swapped 165 | hid->hidInfo(0x00, 0x02); 166 | hid->reportMap((uint8_t*)report, sizeof(report)); 167 | hid->startServices(); 168 | 169 | BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); 170 | #ifdef JOYSTICK_MODE 171 | pAdvertising->setAppearance(HID_JOYSTICK); 172 | #else 173 | pAdvertising->setAppearance(HID_GAMEPAD); 174 | #endif 175 | pAdvertising->addServiceUUID(hid->hidService()->getUUID()); 176 | pAdvertising->start(); 177 | hid->setBatteryLevel(100); 178 | 179 | // Initialise next zero-able time for each bit to 0 ms. 180 | memset (next_zero_ms, 0, sizeof(next_zero_ms)); 181 | 182 | // Setup joystick input pins. 183 | pinMode(39, INPUT_PULLUP); // no internal pull-up available 184 | pinMode(36, INPUT_PULLUP); // no internal pull-up available 185 | pinMode(35, INPUT_PULLUP); // no internal pull-up available 186 | pinMode(34, INPUT_PULLUP); // no internal pull-up available 187 | pinMode(33, INPUT_PULLUP); // 188 | pinMode(32, INPUT_PULLUP); // 189 | pinMode(27, INPUT_PULLUP); // 190 | pinMode(26, INPUT_PULLUP); // 191 | pinMode(25, INPUT_PULLUP); // 192 | pinMode(23, INPUT_PULLUP); // 193 | pinMode(22, INPUT_PULLUP); // 194 | pinMode(21, INPUT_PULLUP); // 195 | pinMode(19, INPUT_PULLUP); // 196 | pinMode(18, INPUT_PULLUP); // 197 | pinMode(17, INPUT_PULLUP); // 198 | pinMode(16, INPUT_PULLUP); // 199 | pinMode(13, INPUT_PULLUP); // 200 | pinMode(4, INPUT_PULLUP); // 201 | 202 | pinMode(CONNECT_LED_PIN, OUTPUT); 203 | printf ("Init complete!\n"); 204 | } 205 | 206 | void loop() 207 | { 208 | uint32_t input_state = 0; // Will contain a 1 bit for each mapped button press. 209 | uint32_t next_state = current_state; // Update current inputs with debounced input 210 | uint32_t changed; // Bits that changed since update 211 | int count; 212 | int mask = 1; 213 | int now = millis(); 214 | 215 | // Add value of each mapped button/input 216 | if (digitalRead(39)==LOW) input_state |= GPIO39_MAP; 217 | if (digitalRead(36)==LOW) input_state |= GPIO36_MAP; 218 | if (digitalRead(35)==LOW) input_state |= GPIO35_MAP; 219 | if (digitalRead(34)==LOW) input_state |= GPIO34_MAP; 220 | if (digitalRead(33)==LOW) input_state |= GPIO33_MAP; 221 | if (digitalRead(32)==LOW) input_state |= GPIO32_MAP; 222 | if (digitalRead(27)==LOW) input_state |= GPIO27_MAP; 223 | if (digitalRead(26)==LOW) input_state |= GPIO26_MAP; 224 | if (digitalRead(25)==LOW) input_state |= GPIO25_MAP; 225 | 226 | if (digitalRead(23)==LOW) input_state |= GPIO23_MAP; 227 | if (digitalRead(22)==LOW) input_state |= GPIO22_MAP; 228 | if (digitalRead(21)==LOW) input_state |= GPIO21_MAP; 229 | if (digitalRead(19)==LOW) input_state |= GPIO19_MAP; 230 | if (digitalRead(18)==LOW) input_state |= GPIO18_MAP; 231 | if (digitalRead(17)==LOW) input_state |= GPIO17_MAP; 232 | if (digitalRead(16)==LOW) input_state |= GPIO16_MAP; 233 | if (digitalRead(13)==LOW) input_state |= GPIO13_MAP; 234 | if (digitalRead(4)==LOW) input_state |= GPIO4_MAP; 235 | 236 | for (count = 0; count < 32; count++) 237 | { 238 | if (input_state & mask) 239 | { 240 | // Button pressed - set button bit and update the timestamp when input may be reported as zero 241 | next_state |= mask; 242 | next_zero_ms[count] = now + DEBOUNCE_MS; 243 | } 244 | else if (now > next_zero_ms[count]) 245 | { 246 | // Button was not pressed and we reached the end of the debounce period. 247 | // If button was pressed during debounce period the timestamp would have been updated. 248 | // This logic fails for the first DEBOUNCE_MS after timer roll-over but auto recovers. 249 | next_state &= (~mask); 250 | } 251 | mask<<= 1; 252 | } 253 | 254 | // Send a HID report if there is a change in the current debounced state 255 | changed= current_state ^ next_state; 256 | if (changed) 257 | { 258 | uint8_t hidreport[] = {0x0, 0x0, 0x0, 0x0}; 259 | #ifdef ADD_KEYBOARD 260 | uint8_t hidreport2[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; // Modifier, Reserved, 6 key bytes 261 | int idx; 262 | #endif 263 | 264 | hidreport[0]= next_state & 0xFF; 265 | hidreport[1]= (next_state>>8) & 0xFF; 266 | if (next_state&0x00020000) hidreport[2]-= 127; // Left button 267 | if (next_state&0x00010000) hidreport[2]+= 127; // Right button 268 | if (next_state&0x00080000) hidreport[3]-= 127; // Up button 269 | if (next_state&0x00040000) hidreport[3]+= 127; // Down button 270 | //printf ("report values %02X %02X %02X %02X ", hidreport[0], hidreport[1], hidreport[2], hidreport[3]); 271 | 272 | #ifdef ADD_KEYBOARD 273 | idx= 2; // First index of keyboard hid report 274 | // sample key mapping - update to match your requirements 275 | if ((idxsetValue(hidreport, sizeof(hidreport)); 291 | input->notify(); 292 | } 293 | #ifdef ADD_KEYBOARD 294 | if (changed & 0xFFF00000) // If bits mapped into Keyboard changed, send keyboard report 295 | { 296 | input2->setValue(hidreport2,sizeof(hidreport2)); 297 | input2->notify(); 298 | } 299 | #endif 300 | } 301 | current_state= next_state; 302 | } 303 | } 304 | 305 | --------------------------------------------------------------------------------