added hardware

This commit is contained in:
cpu
2025-07-22 01:12:34 +02:00
parent e8ebb23678
commit 980549c36c
6 changed files with 269 additions and 39 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

50
arduino/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Nexus Game Controller
A game controller using a Seeed Studio XIAO nRF52840 Sense board that emulates a Bluetooth keyboard.
## Hardware
- [Seeed Studio XIAO nRF52840 Sense](#https://www.seeedstudio.com/Seeed-XIAO-BLE-Sense-nRF52840-p-5253.html)
- 9 [push buttons](https://rpishop.cz/komponenty/6128-pimoroni-cerne-arkadove-tlacitko.html) connected to pins D1-D6 and D8-D10.
## Features
- **Bluetooth LE Keyboard:** Connects to any computer or mobile device as a standard keyboard.
- **Multi-Event Buttons:** Each of the 9 buttons supports three types of interactions:
- Single Click
- Double Click
- Long Press
- **Character Mapping:** Each button event sends a unique character.
- **Serial Debugging:** Outputs button events and Bluetooth connection status to the serial monitor.
## Button Mappings
| Button | Single Click | Double Click | Long Press |
|--------|--------------|--------------|------------|
| D1 | `a` | `b` | `c` |
| D2 | `d` | `e` | `f` |
| D3 | `g` | `h` | `i` |
| D4 | `j` | `k` | `l` |
| D5 | `m` | `n` | `o` |
| D6 | `p` | `q` | `r` |
| D8 | `s` | `t` | `u` |
| D9 | `v` | `w` | `x` |
| D10 | `y` | `z` | `1` |
## How to Use
1. **Setup Arduino IDE:**
- Install the "Seeed nRF52 Boards" package.
- Select "Seeed XIAO BLE Sense - nRF52840" as the board.
- Install the "Adafruit Bluefruit nRF52" library.
2. **Upload:** Compile and upload the [sketch](/arduino/sketch.ino) to your XIAO board.
3. **Connect:** Scan for Bluetooth devices on your computer or mobile device and connect to "Nexus Game Controller".
4. **Play:** Press the buttons to send keystrokes. Open the Serial Monitor at 115200 baud to see debug information.
## 3D Print
![Hexagon](/arduino/Krabicka_hexagon.png)
You can import the [3mf file](/arduino/Krabicka_hexagon.3mf) to your slicer for printing.
## Customize your design
Open the [3D model](/arduino/Krabicka_hexagon.FCStd) in FreeCAD to model your changes.

178
arduino/sketch.ino Normal file
View File

@@ -0,0 +1,178 @@
#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();
}
}