Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ The controller features:
- **4 face buttons** (A, B, X, Y)
- **D-Pad** (4 directions)
- **Shoulder buttons** (L1, R1)
- **System buttons** (Start, Select, Home)
- **Vibration motor** for haptic feedback, PWM-controlled
- USB-C powered — no batteries needed
- Fully recognized by **Steam** (Steam Input) — all axes, buttons, and triggers work correctly

I initially planned a two-microcontroller architecture (STM32 + ESP32-C3) communicating over Bluetooth LE, but decided against it — coordinating BLE HID between two microcontrollers turned out to be quite complex. The final design uses a **single STM32 Nucleo-U545RE-Q** that reads all inputs and connects directly to the host as a USB HID gamepad.

Expand All @@ -41,14 +41,14 @@ As an avid gamer, I wanted to understand how a controller works from the ground
┌─────────────────────────────────────────────────────────────┐
│ INPUT LAYER │
│ [Joystick L] [Joystick R] [Trigger L] [Trigger R] │
│ [Buttons x18] [Vibration Motor] │
│ [Buttons x12] [Vibration Motor] │
└───────────────────────┬─────────────────────────────────────┘
│ ADC / GPIO / PWM
┌─────────────────────────────────────────────────────────────┐
│ STM32 Nucleo-U545RE-Q │
│ - Reads ADC: 4 joystick axes + 2 triggers │
│ - Reads GPIO: 18 buttons │
│ - Reads GPIO: 12 buttons │
│ - Controls vibration motor via PWM │
│ - Acts as USB HID Gamepad │
└───────────────────────┬─────────────────────────────────────┘
Expand Down Expand Up @@ -77,6 +77,7 @@ As an avid gamer, I wanted to understand how a controller works from the ground
- Wired the potentiometers for the analog triggers.
- Wired the vibration motor and tested PWM control.
- Full integration test.
- Added the controller to Steam and configured it using Steam Input. Steam correctly detects all axes, buttons, and triggers. The controller is fully functional in Steam.

---

Expand All @@ -88,9 +89,9 @@ As an avid gamer, I wanted to understand how a controller works from the ground
The only microcontroller in the project. Reads all inputs, controls the vibration motor, and communicates with the host via USB HID.

- 6 ADC channels: Joystick L (X, Y), Joystick R (X, Y), Trigger L, Trigger R
- 18 GPIO pins for buttons
- 1 PWM output for the vibration motor
- USB Full Speed via USB-C (CN3) to the host
- 12 GPIO pins for buttons
- 1 PWM output for the vibration motor (TIM3 CH1)
- USB Full Speed via USB-C (PA11/PA12) to the host
- Powered via USB-C; provides 3.3V on Morpho pins (CN7) for peripherals

#### 2. Analog Joysticks PS2 Module (×2)
Expand All @@ -99,8 +100,8 @@ Standard XY joystick modules with integrated push-button (L3/R3). Each module: V
- VRx/VRy → 2 ADC channels per joystick
- SW → GPIO with internal pull-up

#### 3. Tactile Buttons 12×12mm (×20)
A, B, X, Y, L1, R1, Start, Select, Home, D-Pad ×4, L3, R3 + 2 spare.
#### 3. Tactile Buttons 12×12mm (×12)
A, B, X, Y, L1, R1, D-Pad ×4, L3, R3.

- Wired between GPIO and GND, internal pull-up enabled in firmware

Expand Down Expand Up @@ -135,32 +136,45 @@ The schematic covers the following connections:
**Joysticks (×2)**
| Signal | STM32 Pin |
|--------|-----------|
| Joystick L — VRx | ADC channel (TBD) |
| Joystick L — VRy | ADC channel (TBD) |
| Joystick L — SW | GPIO (TBD) |
| Joystick R — VRx | ADC channel (TBD) |
| Joystick R — VRy | ADC channel (TBD) |
| Joystick R — SW | GPIO (TBD) |
| Joystick L — VRx | PA0 (ADC1) |
| Joystick L — VRy | PA1 (ADC1) |
| Joystick L — SW (L3) | PB13 |
| Joystick R — VRx | PA4 (ADC1) |
| Joystick R — VRy | PB0 (ADC1) |
| Joystick R — SW (R3) | PB14 |

**Triggers (×2)**
| Signal | STM32 Pin |
|--------|-----------|
| Trigger L — wiper | ADC channel (TBD) |
| Trigger R — wiper | ADC channel (TBD) |
| Trigger L2 — wiper | PC1 (ADC1) |
| Trigger R2 — wiper | PC0 (ADC1) |

**Buttons (×18)**
**Buttons (×12)**
| Button | STM32 Pin |
|--------|-----------|
| A, B, X, Y | GPIO (TBD) |
| L1, R1 | GPIO (TBD) |
| Start, Select, Home | GPIO (TBD) |
| D-Pad ×4 | GPIO (TBD) |
| L3, R3 | GPIO (TBD) |
| A | PA3 |
| B | PA2 |
| X | PC6 |
| Y | PB10 |
| L1 | PC9 |
| R1 | PC10 |
| D-Up | PB8 |
| D-Down | PA8 |
| D-Left | PC8 |
| D-Right | PC7 |
| L3 | PB13 |
| R3 | PB14 |

**Vibration Motor**
| Signal | STM32 Pin |
|--------|-----------|
| PWM control | TIM channel (TBD) |
| PWM control | PB4 (TIM3 CH1) |

**USB**
| Signal | STM32 Pin |
|--------|-----------|
| USB D- | PA11 |
| USB D+ | PA12 |

---

Expand All @@ -170,26 +184,47 @@ The schematic covers the following connections:
|--------|-------|-------|
| STM32 Nucleo-U545RE-Q | Main microcontroller + USB HID | ~110 RON *(owned)* |
| Joystick Module 2-Axis XY ×2 | Left + right joystick with L3/R3 | 5.45 RON × 2 |
| Tactile Button PCB 12×12×7.3mm ×20 | A, B, X, Y, D-Pad, L1, R1, Start, Select, Home | 1.33 RON × 20 |
| Tactile Button PCB 12×12×7.3mm ×12 | A, B, X, Y, D-Pad ×4, L1, R1, L3, R3 | 1.33 RON × 12 |
| Linear Potentiometer Module 10K ×2 | Analog triggers L2 and R2 | 12.52 RON × 2 |
| Vibration Motor Module DC PWM 3–5V | Haptic feedback | 5.36 RON |
| Breadboard 400p ×3 | Prototyping base | 6.62 RON × 3 |
| Breadboard 800p ×1 | Prototyping base | ~14 RON |
| Dupont Wire 30cm M-M ×2 (40pcs/set) | Breadboard connections | 8.39 RON × 2 |
| Dupont Wire 30cm M-F ×1 (40pcs/set) | Module connections | 7.59 RON |
| Ceramic Capacitor Set 300pcs | VCC / ADC decoupling | 12.55 RON |
| Resistor Kit 30 values 600pcs | Pull-up / protection | 15.16 RON |
| | Subtotal (components) | 139.84 RON |
| | Subtotal (components) | 123.34 RON |
| | Delivery | 15.15 RON |
| | **Total (cart incl. VAT)** | **154.99 RON** |
| | **Grand Total (incl. Nucleo)** | **~265 RON** |
| | **Total (cart incl. VAT)** | **138.49 RON** |
| | **Grand Total (incl. Nucleo)** | **~248 RON** |

---

## Software

*(To be developed)*
Firmware is written in **Rust** using the **Embassy** async framework for STM32.

### Key components

- **USB HID** — `embassy-usb` with a custom 8-byte gamepad report descriptor (12 buttons + 4 axes + 2 triggers). The device presents itself as a Game Pad (Usage Page 0x01, Usage 0x05) for maximum OS compatibility.
- **ADC** — `embassy-stm32` ADC1, blocking reads at ~19.5 cycles sample time. Raw 14-bit values mapped to signed 8-bit axis values via `adc_to_axis()`.
- **GPIO** — 12 buttons, active-low with internal pull-up. Press events detected on falling edge (bit was 0, now 1).
- **PWM** — TIM3 CH1 on PB4 drives the vibration motor. Duty cycle: 100% when R2 trigger is maxed out, 70% for 500 ms every 10 seconds otherwise.
- **Clocks** — HSI @ 16 MHz for the CPU; HSI48 (synced from USB SOF) for the USB peripheral; SYS clock routed to ADC.

### HID report layout (8 bytes)

| Byte | Content |
|------|---------|
| 0 | Buttons 0–7 (A, B, X, Y, L1, R1, D-Up, D-Down) |
| 1 | Buttons 8–11 (D-Left, D-Right, L3, R3) + 4-bit padding |
| 2 | LX axis (i8) |
| 3 | LY axis (i8) |
| 4 | RX axis (i8) |
| 5 | RY axis (i8) |
| 6 | L2 trigger (u8, 0–255) |
| 7 | R2 trigger (u8, 0–255) |

Firmware will be written in **Rust** using the **Embassy** async framework for STM32. Implementation details will be added as development progresses.
The main loop runs at ~200 Hz (5 ms `Timer::after`). USB HID and USB stack are joined as two concurrent async tasks via `embassy-futures::join`.

---

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading