fightstick

Firmware

Table of contents

The fightstick firmware is a bare-metal C application targeting the STM32F042K4 microcontroller on the stm32f0-usbd-devboard. It implements a USB HID gamepad with a 2-axis digital joystick and 12 buttons. The firmware uses no HAL or RTOS -- it directly accesses peripheral registers via CMSIS headers and relies on the usbd-fs-stm32 library for USB device functionality.

Building from source

Prerequisites

  • CMake 3.25 or later
  • ARM GNU Toolchain (arm-none-eabi)
  • Ninja (recommended) or Make

The following dependencies are fetched automatically via CMake FetchContent:

Configure and build

cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja
cmake --build build

Output artifacts

The build produces the following in the build/ directory:

FileDescription
fightstick.elfELF binary
fightstick.elf.mapLinker map
fightstick.binRaw binary
fightstick.hexIntel HEX
fightstick.dfuDFU with suffix (for dfu-util)

Memory layout

RegionStart addressSize
Flash0x0800000016 KB
RAM0x200000006 KB

Flashing

Using USB DFU

Automated builds from the latest source are available from the rolling release.

The STM32F042K4 has a built-in USB DFU bootloader in system memory. There are two ways to enter DFU mode:

  • Empty MCU -- a new, unprogrammed STM32F042K4 boots directly into its system memory DFU bootloader, so it is ready to flash immediately
  • Button combination -- hold BTN09 (pin 18, B0) and BTN10 (pin 19, B1) simultaneously while powering on or resetting the device

When the firmware detects both BTN09 and BTN10 held at startup, it writes a magic value (0xdeadbeef) to an RTC backup register and triggers a software reset. On the subsequent boot, the firmware reads this value and jumps to the system bootloader at 0x1FFFC400.

See the Hardware Build Manual for general DFU flashing instructions using dfu-util.

Flash via the provided CMake target:

cmake --build build --target fightstick-stlink-write

See the Hardware Build Manual for general ST-Link setup and flashing instructions.

Architecture overview

Clock configuration

The firmware configures the system clock to use the HSI48 internal oscillator directly at 48 MHz with no PLL. Both AHB and APB buses run at 48 MHz (prescaler /1). One flash wait state is enabled as required for 48 MHz operation.

Peripheral map

PeripheralFunction
GPIOA PA0--PA9Button inputs (BTN01--BTN08, BTN11, BTN12) with internal pull-ups
GPIOA PA15Status LED output (active high)
GPIOB PB0--PB1Button inputs (BTN09, BTN10) with internal pull-ups
GPIOB PB3--PB6Joystick direction inputs (LEFT, RIGHT, DOWN, UP) with internal pull-ups
TIM17HID idle rate timer (one-pulse mode)
USBFull-speed USB device peripheral
RTC BKP0RBootloader entry flag register

See Assembly for physical wiring details and pin assignments.

Main loop

The main() function initializes GPIO ports with pull-ups, checks for the DFU button combination, then configures the clock, idle timer, and USB peripheral. The main loop calls usbd_task() continuously to process USB events.

On each USB IN token for endpoint 1, the usbd_in_cb() callback reads all 16 GPIO inputs and constructs the HID report. The report is only sent when the input state changes or when the HID idle timer expires, reducing unnecessary USB traffic.

Source files

FilePurpose
main.cGPIO initialization, clock setup, USB callbacks, input polling, and main loop
descriptors.cUSB device, configuration, interface, HID, and endpoint descriptors; HID report descriptor; string descriptors
bootloader.cDFU bootloader entry logic using RTC backup register and system memory jump
idle.cHID idle rate implementation using TIM17 in one-pulse mode

USB HID protocol

Device identity

FieldValue
USB version2.0 Full-Speed
Device classHID (interface-level)
VID0x1d50
PID0x619a
Manufacturerrgm.io
Productfightstick
Serial numberSTM32 internal unique ID
Max power40 mA (bus-powered)
HID version1.11

Endpoints

DirectionTypeMax packet sizeInterval
EP1 INInterrupt64 bytes1 ms

HID report descriptor

The device uses the Generic Desktop / Gamepad usage page with a single input report.

Report IDKindSizeDescription
1Input2 bytes (+ 1 byte report ID)Joystick axes and buttons

Report layout (3 bytes total):

ByteBitsFieldValues
07:0Report IDAlways 0x01
11:0X axis-1 (left), 0 (center), 1 (right)
13:2Y axis-1 (up), 0 (center), 1 (down)
14Button 10 = released, 1 = pressed
15Button 20 = released, 1 = pressed
16Button 30 = released, 1 = pressed
17Button 40 = released, 1 = pressed
20Button 50 = released, 1 = pressed
21Button 60 = released, 1 = pressed
22Button 70 = released, 1 = pressed
23Button 80 = released, 1 = pressed
24Button 90 = released, 1 = pressed
25Button 100 = released, 1 = pressed
26Button 110 = released, 1 = pressed
27Button 120 = released, 1 = pressed

Idle rate

The firmware implements HID Set_Idle and Get_Idle requests. The default idle rate is 500 ms (idle value 125). When the idle rate is non-zero, the firmware sends the current report periodically even if no input state has changed. When set to 0, reports are only sent on input changes.