octokeyz
Firmware
Single bare-metal C firmware for the STM32F042K6/K4 (ARM Cortex-M0). No HAL, no RTOS -- direct register access throughout. A single binary runs on both hardware variants: the firmware probes for an SSD1306 display at startup and silently disables display features if none is found. See the hardware pages for octokeyz and octokeyz-mega.
The firmware implements a USB HID device with a custom vendor protocol. Host-side interaction is handled by the Go client library or any custom HID implementation that speaks the protocol described below.
Building from Source
Prerequisites
- CMake 3.25 or later
- Ninja build system (optional, but recommended)
- ARM GNU Toolchain (
arm-none-eabi) - Git
The two library dependencies -- cmake-cmsis-stm32 (build framework) and usbd-fs-stm32 (USB device stack) -- are fetched automatically via CMake FetchContent during configuration.
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/firmware/ directory:
| File | Description |
|---|---|
octokeyz.elf |
ELF binary |
octokeyz.bin |
Raw binary |
octokeyz.hex |
Intel HEX |
octokeyz.dfu |
DFU with suffix (for dfu-util) |
octokeyz.map |
Linker map |
The firmware version is derived from git tags matching the v[0-9]* pattern. Between tagged releases, the version includes the commit count and abbreviated hash (e.g. 0.0.72-1b14).
Memory Layout
The linker script (firmware/STM32F042KxTx_FLASH.ld) targets the smaller K4 memory to ensure compatibility with both STM32F042K4 and K6 variants:
| Region | Start Address | Size |
|---|---|---|
| Flash | 0x08000000 |
16 KB |
| RAM | 0x20000000 |
6 KB |
Flashing
Using ST-Link
With st-flash (from https://github.com/stlink-org/stlink) installed, run:
cmake --build build --target octokeyz-stlink-write
Using USB DFU
Firmware flashing can be done over USB using the STM32's built-in DFU bootloader. There are several ways to enter DFU mode:
Empty Microcontroller: An empty microcontroller boots directly into the DFU bootloader.
Button Combo: With an octokeyz firmware already running on the microcontroller, hold buttons 1 and 5 simultaneously while plugging in the USB cable. The microcontroller boots directly into the DFU bootloader.
Bootloader Pin Header: Connect a jumper to JP1 (Boot to DFU) pin header and plug the USB cable. The microcontroller boots directly into the DFU bootloader.
Once in DFU mode, follow the instructions from my generic Hardware Build Manual's "STM32 (USB DFU)" section.
Linux udev Rules
Linux users may have issues connecting to the macropad as a normal user.
The file share/udev/60-octokeyz.rules grants device access to users in the plugdev group and to logged-in users via the uaccess tag:
SUBSYSTEMS=="usb", DRIVERS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="6184", GROUP="plugdev", MODE="0660", TAG+="uaccess"
Install it:
sudo cp share/udev/60-octokeyz.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
Architecture Overview
The firmware runs on HSI48 at 48 MHz (internal oscillator, no external crystal). Flash wait state is set to 1 cycle as required at this frequency. AHB and APB buses run undivided.
Peripheral Map
| Peripheral | Function |
|---|---|
| GPIOA PA0-PA7 | Button inputs (internal pull-up) |
| GPIOB PB0 | LED output (TIM3 CH3, alternate function) |
| GPIOA PA9 / PA10 | I2C1 SCL / SDA (display, open-drain with pull-up) |
| USB FS | USB device |
| I2C1 | SSD1306 display communication (address 0x3c) |
| DMA1 Channel 2 | I2C1 TX -- display data transfer |
| TIM3 | LED PWM generation |
| TIM16 | Display clear delay (one-pulse mode) |
| TIM17 | HID idle rate timer (one-pulse mode) |
| RTC BKP0R | Bootloader magic value handling |
Source Files
| File | Purpose |
|---|---|
main.c |
Clock init, GPIO setup, USB and display init, main loop (usbd_task() + display_task()), USB callback implementations |
descriptors.c |
USB device/config/HID descriptors, string descriptors, HID report descriptor |
display.c |
SSD1306 driver: I2C + DMA init, double-buffered line rendering, font rasterization, delayed clear |
led.c |
TIM3 PWM setup, 5-state LED control (on, flash, slow blink, fast blink, off) |
idle.c |
HID idle rate tracking via TIM17, SET_IDLE/GET_IDLE request handling |
bootloader.c |
DFU entry detection (RTC backup register check), system memory remap and jump, reset trigger |
USB HID Protocol
Device Identity
| Field | Value |
|---|---|
| USB version | 2.0 Full-Speed |
| Device class | HID (interface-level) |
| VID | 0x1d50 (generously provided by OpenMoko) |
| PID | 0x6184 |
| Manufacturer | rgm.io |
| Product | octokeyz |
| Serial number | STM32 unique device ID |
| Max power | 100 mA (bus-powered) |
| HID version | 1.11 |
Endpoints
The firmware only defines endpoint 1 in both directions:
| Direction | Type | Max Packet Size | Interval |
|---|---|---|---|
| IN | Interrupt | 64 bytes | 10 ms |
| OUT | Interrupt | 64 bytes | 10 ms |
HID Reports
| ID | Kind | Size (bytes) | Description |
|---|---|---|---|
| 1 | Input | 1 | Button states -- bits 0-7 map to buttons 1-8, 1 = pressed |
| 1 | Output | 1 | LED control -- bits 0-2 encode state: 1=on, 2=flash, 3=slow blink, 4=fast blink, 5=off |
| 1 | Feature | 1 | Capabilities -- bit 0: device has a display |
| 2 | Output | 23 | Display line data -- byte 0: line number (5 bits), byte 1: alignment (2 bits, 1=left, 2=right, 3=center), bytes 2-22: ASCII text (21 chars max) |
| 2 | Feature | 3 | Display capabilities -- byte 0: number of lines, byte 1: characters per line, byte 2 bit 0: supports clear |
| 3 | Output | 2 | Display clear with delay -- 16-bit little-endian milliseconds |
Report sizes listed above do not include the report ID byte.
Vendor Usage Pages
| Usage Page | Name | Contains |
|---|---|---|
0xFF00 |
octokeyz | Application collection, capabilities feature |
0xFF01 |
octokeyz Key | Button states (keys 1-8) |
0xFF02 |
octokeyz LED | LED control (states 1-5) |
0xFF03 |
octokeyz Display | Display capabilities, line data, clear command |
Note
This is a custom vendor HID protocol, not a standard keyboard or consumer device. Interacting with it requires the Go client library or a custom implementation that understands the report structure and vendor usage pages described above.
HID Idle Rate
The default idle rate is 500 ms (value 125 in 4 ms units), configurable via standard HID SET_IDLE and GET_IDLE requests. When idle rate is non-zero, button state reports are sent periodically even if no state change has occurred. Setting idle rate to 0 disables periodic reports -- the device only sends a report when button state actually changes.