Skip to content

Instantly share code, notes, and snippets.

@GameDragon2k
Last active February 15, 2023 08:47
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save GameDragon2k/90709fbe6b15e66c1b274dde3e1b2439 to your computer and use it in GitHub Desktop.
Save GameDragon2k/90709fbe6b15e66c1b274dde3e1b2439 to your computer and use it in GitHub Desktop.
NodeMCU-32S Game Pad
/*
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