-
-
Save brunosxs/01c9e1cbf6118eb00e4a0f522fcb9b95 to your computer and use it in GitHub Desktop.
NodeMCU-32S Game Pad
This file contains 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
/* | |
Video: https://www.youtube.com/watch?v=oCMOYS71NIU | |
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp | |
Ported to Arduino ESP32 by Evandro Copercini | |
Gamepad coding by Game Dragon | |
*/ | |
#include <BLEDevice.h> | |
#include <BLEServer.h> | |
#include <BLEUtils.h> | |
#include <BLEHIDDevice.h> | |
#include <BLE2902.h> | |
static BLEHIDDevice *pHID; | |
BLEServer *pServer; | |
BLECharacteristic *input; | |
bool deviceConnected = false; | |
bool oldDeviceConnected = false; | |
int buttonCount = 10; // IMPORTANT TO CHANGE | |
uint8_t *GPIO_axis; | |
uint8_t *GPIO_buttons; | |
struct gamepad_report_t | |
{ | |
int8_t left_x; | |
int8_t left_y; | |
uint16_t buttons; | |
}; | |
bool operator!=(const gamepad_report_t& lhs, const gamepad_report_t& rhs) | |
{ | |
return (lhs.left_x != rhs.left_x) | |
|| (lhs.left_y != rhs.left_y) | |
|| (lhs.buttons != rhs.buttons); | |
} | |
// ESP32S Gamepad Pinout | |
static const uint8_t pinMapping[] = { | |
36, // UP | |
39, // RIGHT (32 safe) | |
34, // DOWN (33 safe) | |
35, // LEFT (25 safe) | |
32, // Buttons 1-11 | |
33, | |
25, | |
26, | |
27, | |
14, | |
12, | |
13, | |
9, | |
10 | |
}; | |
class MyServerCallbacks: public BLEServerCallbacks { | |
void onConnect(BLEServer* pServer) { | |
deviceConnected = true; | |
}; | |
void onDisconnect(BLEServer* pServer) { | |
deviceConnected = false; | |
} | |
}; | |
void setup() { | |
Serial.begin(115200); | |
// Create the BLE Device | |
BLEDevice::init("BLE Arcade"); // Give it a name | |
// Create the BLE Server | |
pServer = BLEDevice::createServer(); | |
pServer->setCallbacks(new MyServerCallbacks()); | |
// Instantiate HID Device | |
pHID = new BLEHIDDevice(pServer); | |
input = pHID->inputReport(1); | |
pHID->manufacturer()->setValue("ESP32"); | |
pHID->pnp(0x01,0x02e5,0xabcd,0x0110); | |
pHID->hidInfo(0x00,0x01); | |
BLESecurity *pSecurity = new BLESecurity(); | |
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND); | |
// Set Report Map | |
const uint8_t reportMap[] = { | |
0x05, 0x01, // USAGE_PAGE (Generic Desktop) | |
0x09, 0x05, // USAGE (Game Pad) | |
0xa1, 0x01, // COLLECTION (Application) | |
0xa1, 0x02, // COLLECTION (Logical) | |
0x85, 0x01, // REPORT_ID (1) | |
0x75, 0x08, // REPORT_SIZE (8) | |
0x95, 0x02, // REPORT_COUNT (2) | |
0x05, 0x01, // USAGE_PAGE (Generic Desktop) | |
0x09, 0x30, // USAGE (X) | |
0x09, 0x31, // USAGE (Y) | |
0x15, 0x81, // LOGICAL_MINIMUM (-127) | |
0x25, 0x7f, // LOGICAL_MAXIMUM (127) | |
0x81, 0x02, // INPUT (Data, Var, Abs) | |
0x75, 0x01, // REPORT_SIZE (1) | |
0x95, 0x0b, // REPORT_COUNT (11) | |
0x15, 0x00, // LOGICAL_MINIMUM (0) | |
0x25, 0x01, // LOGICAL_MAXIMUM (1) | |
0x05, 0x09, // USAGE_PAGE (Button) | |
0x19, 0x01, // USAGE_MINIMUM (Button 1) | |
0x29, 0x0b, // USAGE_MAXIMUM (Button 11) | |
0x81, 0x02, // INPUT (Data, Var, Abs) | |
// PADDING for byte alignment | |
0x75, 0x01, // REPORT_SIZE (1) | |
0x95, 0x05, // REPORT_COUNT (5) | |
0x81, 0x03, // INPUT (Constant, Var, Abs) | |
0xc0, // END_COLLECTION | |
0xc0 // END_COLLECTION | |
}; | |
pHID->reportMap((uint8_t*)reportMap, sizeof(reportMap)); | |
int numReport = sizeof(reportMap); | |
Serial.println(numReport); | |
SetupGamepad(); | |
// Start the service | |
pHID->startServices(); | |
// Start advertising | |
BLEAdvertising *pAdvertising = pServer->getAdvertising(); | |
pAdvertising->setAppearance(HID_GAMEPAD); | |
pAdvertising->addServiceUUID(pHID->hidService()->getUUID()); | |
pAdvertising->start(); | |
Serial.println("Waiting a client connection to notify..."); | |
} | |
void SetupGamepad() { | |
uint8_t index = 0; | |
Serial.println("///// GPIO SETUP /////"); | |
if (sizeof(pinMapping) == 0 ) | |
{ | |
Serial.print("///// FAILED - No GPIO pins to map. /////"); | |
} | |
else if (sizeof(pinMapping) < buttonCount + 4) | |
{ | |
Serial.print("///// FAILED - Not enough GPIO pins mapped. /////"); | |
} | |
else | |
{ | |
// Setup Axis Pins | |
GPIO_axis = new uint8_t[4]; | |
for (int i=0; i < 4; i++) | |
{ | |
GPIO_axis[i] = pinMapping[index]; | |
pinMode(GPIO_axis[i], INPUT_PULLUP); | |
digitalWrite(pinMapping[index], HIGH); | |
Serial.print("Axis"); | |
Serial.print(i); | |
Serial.print(" set to GPIO"); | |
Serial.println(pinMapping[index]); | |
if(index < sizeof(pinMapping)-1) index++; | |
} | |
// Setup Button Pins | |
if (buttonCount > 0) | |
{ | |
GPIO_buttons = new uint8_t[buttonCount]; | |
for (int i=0; i < buttonCount; i++) | |
{ | |
GPIO_buttons[i] = pinMapping[index]; | |
pinMode(GPIO_buttons[i], INPUT_PULLUP); | |
digitalWrite(pinMapping[index], HIGH); | |
Serial.print("Button"); | |
Serial.print(i+1); | |
Serial.print(" set to GPIO"); | |
Serial.println(pinMapping[index]); | |
if(index < sizeof(pinMapping)-1) index++; | |
} | |
} | |
Serial.println("///// GAMEPAD READY! /////"); | |
} | |
} | |
gamepad_report_t oldValue, newValue; | |
bool pressed(uint8_t pin) { | |
return (digitalRead(pin) == LOW); | |
} | |
void loop() { | |
if (deviceConnected) { | |
// AXIS | |
{ | |
newValue.left_y = 0; | |
newValue.left_x = 0; | |
if (pressed(GPIO_axis[0])) newValue.left_y -= 127; | |
if (pressed(GPIO_axis[1])) newValue.left_x += 127; | |
if (pressed(GPIO_axis[2])) newValue.left_y += 127; | |
if (pressed(GPIO_axis[3])) newValue.left_x -= 127; | |
} | |
// BUTTONS | |
{ | |
newValue.buttons = 0; | |
for (int i=0; i < buttonCount; i++) | |
{ | |
if (pressed(GPIO_buttons[i])) | |
newValue.buttons |= (1 << i); | |
} | |
} | |
if (newValue != oldValue) | |
{ | |
Serial.println("---"); | |
if (newValue.left_x != oldValue.left_x ) | |
{ | |
Serial.print("X Axis: "); | |
Serial.println(newValue.left_x, HEX); | |
} | |
if (newValue.left_y != oldValue.left_y ) | |
{ | |
Serial.print("Y Axis: "); | |
Serial.println(newValue.left_y, HEX); | |
} | |
if (newValue.buttons != oldValue.buttons) | |
{ | |
Serial.print("Button Input: "); | |
Serial.println(newValue.buttons, BIN); | |
} | |
uint8_t a[] = {newValue.left_x, newValue.left_y, newValue.buttons, (newValue.buttons >> 8)}; | |
input->setValue(a, sizeof(a)); | |
input->notify(); | |
oldValue = newValue; | |
} | |
delay(5); | |
} | |
// Connecting | |
if (deviceConnected && !oldDeviceConnected) | |
{ | |
oldDeviceConnected = deviceConnected; | |
} | |
// Disconnecting | |
if (!deviceConnected && oldDeviceConnected) | |
{ | |
delay(500); // give the bluetooth stack the chance to get things ready | |
pServer->startAdvertising(); | |
Serial.println("restart advertising"); | |
oldDeviceConnected = deviceConnected; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment