#include #include // Pin definitions for the 9 buttons const int buttonPins[] = {1, 2, 3, 4, 5, 6, 8, 9, 10}; const int numButtons = sizeof(buttonPins) / sizeof(buttonPins[0]); // Button state tracking struct Button { int pin; bool lastState; unsigned long lastDebounceTime; unsigned long pressTime; int clickCount; bool isPressed; bool longPressHandled; }; Button buttons[numButtons]; // Debounce and timing constants const unsigned long debounceDelay = 50; const unsigned long doubleClickDelay = 400; const unsigned long longPressDelay = 1000; // BLE Keyboard object BLEDis bledis; BLEHidAdafruit blehid; // Character mapping for each event // 9 buttons * 3 events/button = 27 characters const char eventChars[numButtons][3] = { {'a', 'b', 'c'}, // Button D1: single, double, long {'d', 'e', 'f'}, // Button D2: single, double, long {'g', 'h', 'i'}, // Button D3: single, double, long {'j', 'k', 'l'}, // Button D4: single, double, long {'m', 'n', 'o'}, // Button D5: single, double, long {'p', 'q', 'r'}, // Button D6: single, double, long {'s', 't', 'u'}, // Button D8: single, double, long {'v', 'w', 'x'}, // Button D9: single, double, long {'y', 'z', '1'} // Button D10: single, double, long }; void setup() { Serial.begin(115200); //while ( !Serial ) delay(10); // Wait for serial port to connect. Serial.println("Nexus Game Controller Starting..."); // Initialize buttons for (int i = 0; i < numButtons; i++) { buttons[i].pin = buttonPins[i]; pinMode(buttons[i].pin, INPUT_PULLUP); buttons[i].lastState = HIGH; buttons[i].lastDebounceTime = 0; buttons[i].pressTime = 0; buttons[i].clickCount = 0; buttons[i].isPressed = false; buttons[i].longPressHandled = false; } // Setup Bluetooth Bluefruit.begin(); Bluefruit.setName("Nexus Game Controller"); Bluefruit.setTxPower(4); // Configure and start BLE services bledis.setManufacturer("Seeed Studio"); bledis.setModel("XIAO nRF52840 Sense"); bledis.begin(); blehid.begin(); // Start advertising Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower(); Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD); Bluefruit.Advertising.addService(blehid); Bluefruit.Advertising.addName(); Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast advertising mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds Serial.println("Advertising..."); // Set up connection/disconnection callbacks Bluefruit.Periph.setConnectCallback(connect_callback); Bluefruit.Periph.setDisconnectCallback(disconnect_callback); } void connect_callback(uint16_t conn_handle) { (void) conn_handle; Serial.println("Bluetooth connected"); } void disconnect_callback(uint16_t conn_handle, uint8_t reason) { (void) conn_handle; (void) reason; Serial.println("Bluetooth disconnected"); } void loop() { for (int i = 0; i < numButtons; i++) { handleButton(&buttons[i], i); } } void handleButton(Button* b, int buttonIndex) { bool reading = digitalRead(b->pin); // Debounce logic if (reading != b->lastState) { b->lastDebounceTime = millis(); } if ((millis() - b->lastDebounceTime) > debounceDelay) { // If the button state has been stable if (reading == LOW && !b->isPressed) { // Button just pressed b->isPressed = true; b->pressTime = millis(); b->clickCount++; b->longPressHandled = false; } else if (reading == HIGH && b->isPressed) { // Button just released b->isPressed = false; if (!b->longPressHandled) { // It's a click, but we need to wait for a potential double click } else { // This was a long press, so we do nothing on release b->longPressHandled = false; // Reset for next time } } } // Check for long press if (b->isPressed && !b->longPressHandled && (millis() - b->pressTime > longPressDelay)) { triggerEvent(buttonIndex, 2); // Long Press b->longPressHandled = true; b->clickCount = 0; // Reset click count after a long press } // Check for single/double click timeout if (b->clickCount > 0 && !b->isPressed && (millis() - b->pressTime > doubleClickDelay)) { if (!b->longPressHandled) { if (b->clickCount == 1) { triggerEvent(buttonIndex, 0); // Single Click } else if (b->clickCount == 2) { triggerEvent(buttonIndex, 1); // Double Click } } b->clickCount = 0; } b->lastState = reading; } void triggerEvent(int buttonIndex, int eventType) { // eventType: 0 = single, 1 = double, 2 = long char key = eventChars[buttonIndex][eventType]; const char* eventStr[] = {"single click", "double click", "long press"}; Serial.print("Button D"); Serial.print(buttonPins[buttonIndex]); Serial.print(" triggered "); Serial.print(eventStr[eventType]); Serial.print(" event. Character '"); Serial.print(key); Serial.println("' has been sent."); if (Bluefruit.connected()) { blehid.keyPress(key); delay(10); // a small delay to prevent flooding blehid.keyRelease(); } }