179 lines
5.2 KiB
C++
179 lines
5.2 KiB
C++
#include <bluefruit.h>
|
|
#include <bluefruit.h>
|
|
|
|
// 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();
|
|
}
|
|
}
|