The SDVX controller firmware is a bare-metal STM32 HAL project developed in STM32CubeIDE, targeting the STM32F401 microcontroller on a WeAct BlackPill v3.0 board. It exposes the controller to the host PC as a USB HID joystick device, handling button debouncing via hardware interrupts, quadrature encoder decoding via dedicated timer peripherals, and a three-layer LED compositing system driven entirely by DMA — all within a tight 1 ms main loop.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/MrJefter/sdvx-controller/llms.txt
Use this file to discover all available pages before exploring further.
Main Loop
The firmware’swhile(1) loop executes five functions in strict order every millisecond. Each call is responsible for a distinct subsystem: updating LEDs, reading physical inputs, packaging the USB report, watching for a DFU trigger, and transmitting the report.
| Call | Purpose |
|---|---|
visHandle() | Guards on ws2812b.transferComplete, delegates to visHandle2() at 50 FPS, then fires a new DMA transfer |
Read_Encoders() | Snapshots TIM3 (VOL-L) and TIM4 (VOL-R) counters, derives signed 16-bit deltas, feeds EMA, scales to uint16_t axis values |
Process_Buttons() | Iterates button_stable_state[] and packs one bit per button into joystickReport.buttons |
Check_DFU_Entry() | Compares HAL_GetTick() against dfu_button_press_time; if ≥ 5000 ms, calls GoToBootloader() |
Send_HID_Report() | Calls USBD_CUSTOM_HID_SendReport() only when hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED |
HAL_Delay(1) | 1 ms sleep — determines the effective ~1 kHz polling rate |
USB HID Reporting
The firmware registers as a Custom HID device using the STM32 USB Device Library. The HID report descriptor (inusbd_custom_hid_if.c) defines a joystick application collection containing:
- 7 buttons (1 bit each) mapped to BT-A, BT-B, BT-C, BT-D, FX-L, FX-R, and START
- 1 padding bit to align to a byte boundary
- 2 axes (X and Y, 16 bits each, range 0–65535) for the two rotary encoders (VOL-L and VOL-R)
JOYSTICK_REPORT_SIZE = 5): 1 byte of button flags + 4 bytes of axis data. The report is transmitted every loop iteration when the USB device is in the configured state.
Button Debouncing
All seven buttons are wired with pull-up resistors and trigger EXTI interrupts on both rising and falling edges (GPIO_MODE_IT_RISING_FALLING). The debouncing logic lives entirely in HAL_GPIO_EXTI_Callback():
- On each edge, the callback checks whether
HAL_GetTick() >= button_lockout_timer[i]. - If the lockout has expired, it reads the current raw GPIO state and updates
button_stable_state[i]. - It then sets a new lockout:
button_lockout_timer[i] = current_tick + DEBOUNCE_LOCKOUT_MS(3 ms). - Any further edges within those 3 ms are silently ignored.
Encoder Reading
The two LPD3806 rotary encoders (VOL-L and VOL-R) are decoded entirely in hardware using the STM32’s timer encoder interface:- TIM3 decodes VOL-L (pins PA6/PA7,
KNOBLA/KNOBLB) - TIM4 decodes VOL-R (pins PB6/PB7,
KNOBRA/KNOBRB)
TIM_ENCODERMODE_TI12 (both edges of both channels counted), with an auto-reload period of 0xFFFF (65535) to allow full wrap-around. Each call to Read_Encoders() snapshots the current 16-bit counter and computes the delta via a signed int16_t cast, which correctly handles wrap-around in both directions.
An Exponential Moving Average (EMA) is then applied to smooth axis output:
uint16_t before being placed into the HID report. The raw per-frame deltas are also published in g_encoder_delta_x / g_encoder_delta_y for the LED subsystem to consume.
LED Effect System
The WS2812B LED strip (12 LEDs, connected to PB0) is driven via DMA/PWM using Martin Hubacek’sws2812b library. The effect pipeline runs at a gated ~50 FPS (20 ms interval) and composites three independent layers:
- Fog effect — a sum-of-sines noise field modulated over time produces a slowly drifting purple ambient glow (
FOG_BASE_R/G/B = 150, 0, 255). High-noise regions receive a white brightening mix. - Pulse effect — when any button is pressed, the LEDs mapped to that button flash white for 250 ms with a linear fade-out.
- Encoder glow — rotation on VOL-L illuminates its ring LEDs in cyan; VOL-R illuminates its ring in magenta. Activity decays at 0.955× per frame for a smooth trail effect.
frameBuffer[3 × 12] by visScript(). visHandle() gates execution on ws2812b.transferComplete and fires ws2812b_handle() to start the next DMA transfer when a new frame is ready.
DFU Bootloader Entry
The firmware includes a software DFU entry mechanism to re-flash without physical access to the BOOT0 button. Holding the START button for 5 seconds triggersGoToBootloader():
Building
Import the
HID_SDVX_NEW4 project into STM32CubeIDE and compile the firmware binary.Flashing
Flash the firmware via USB DFU mode with dfu-util or via SWD with an ST-Link adapter.
Configuration
Tune debounce timing, encoder smoothing, LED colors, and DFU hold time.
Pinout
GPIO pin assignments for buttons, encoders, LEDs, and the BOOT0 control line.