THE BLOG · LAUNCH POST

OpenMicroKbd: an open-source build of OpenAI's $230 Codex Micro macropad

The finished OpenMicroKbd held in one hand: black PCB in a white printed case, icon keycaps, a knob, a joystick and a yellow accent key
The finished board: 13 keys, a knob, an analog joystick, a capacitive touch pad, and a white 3D-printed case.

In July 2026, OpenAI shipped its first branded hardware: the Codex Micro, a $230 macropad built with Work Louder. Thirteen keys, a dial, a joystick, a touch sensor, RGB lighting, Bluetooth, and a CNC aluminium and polycarbonate case.

OpenMicroKbd is an open-source board that recreates that control layout. MIT licensed, wired USB-C, and about $37 in parts and assembly for one board. The schematic, firmware, companion app, routed board, and the full manufacturing package are all published — you can send the Gerbers to a fab today.

This is an independent open-source project, not affiliated with, endorsed by, or sponsored by OpenAI. "Codex Micro" refers to OpenAI's product; all trademarks belong to their respective owners. I will not sell OpenMicroKbd — the point is that you can make it without me.

Project snapshot

It began as a recreation of the Codex Micro's layout, but it is not limited to Codex. It targets AI-assisted coding, terminal workflows, video editing, color grading, and other work built around shortcuts and scrubbing.

Every input's emitted code is configurable and stored in the board's own flash. Out of the box the keys send F13–F20 and Shift+F13–F17 — deliberately chosen because those are interceptable on every OS and collide with nothing — but you can reassign any key to a different keycode, a modifier-qualified combination, or a media usage, save it to the device, and it keeps that mapping on any machine you plug it into.

The assembled board in its white printed case, beside the embossed Open Micro Kbd packaging box and two printed trays of alternate keycaps
All of it printable: the case the board sits in, the packaging behind it, and trays of interchangeable keycaps.

Why build one at all

The Codex Micro is genuinely interesting hardware. Its six frosted Agent Keys display thread state: idle, thinking, complete, needs human, error.

That status integration is tied to the ChatGPT desktop app, which bridges thread state to the LEDs. Use Codex CLI, a browser, VS Code, or JetBrains and those six keys go static or dark. There is no public device SDK.

Reactions converged on that mismatch. Ars Technica asked why OpenAI's first branded hardware was a light-up keyboard. The New Stack asked whether anyone needed one. TechCrunch called it fun for some coders and mystifying to others. An HN reader put the engineering objection more bluntly: "This is just a Macropad, right? All of the smarts are on the PC side. So why is it so expensive?"

Price is the hook, but it is not really the argument.

The problem worth solving is not that the macropad costs $230. It is that a general-purpose input device keeps its most interesting behavior behind one application.

So the goal here was a board with the same physical vocabulary and none of that boundary: open schematic, open firmware, open host app, and a manufacturing package anyone can send to a fab.

There was a second motive, and it is the reason this particular board exists rather than some easier one. I have been building CoHDL, a hardware description language for PCBs, and I needed to prove it on something real with awkward requirements: a key matrix, a rotary encoder, an analog joystick, a capacitive touch pad, two LED chains, and a USB power budget to respect. Small enough to finish. Complicated enough to be a fair test.

A complete device here has four layers — PCB, enclosure, firmware, host application — and the sections below follow that boundary.

Part one: a PCB complicated enough to be useful

The board uses an STM32F072CBT6 Cortex-M0 with 128 KiB of flash and 16 KiB of SRAM. It includes an 8 MHz HSE crystal and a 2×3 SWD debug socket. It measures 95 × 95 mm on a 4-layer stackup: signal, two ground planes, signal.

Controls occupy a square 4×4 grid at 19.05 mm spacing:

Lighting uses two WS2812-compatible chains, 21 LEDs total: 13 reverse-mount SK6812MINI-E LEDs beneath the keys, and 8 more around the perimeter as underglow. USBLC6 provides USB ESD protection, and an AP2112 regulates 3.3 V.

Close-up of the finished board showing per-key lighting, perimeter underglow, the knob, the joystick and the capacitive touch pad
The finished control surface: two LED chains, the knob and joystick along the top, and the capacitive touch pad at lower left.
Macro view of the board's underside during hand assembly: a soldering iron and solder wire at one joint, surrounded by the teal reverse-mount LED packages silkscreened LED17 through LED29
The key LEDs are reverse-mount: they sit on the underside and fire up through cutouts toward the keycaps.

What one board actually costs

These are real quoted prices for a single prototype run. The SMT line is a turnkey quote: it covers the components and machine assembly, so the component detail below is nested inside it rather than added on top.

LineCost
PCB (4-layer, 95 × 95 mm)$6.93
SMT assembly — turnkey, parts + placement$18.14
13 × Kailh Choc switches$2.97
Keycap set$8.85
Total, one assembled board$36.90

Inside that SMT quote, the component spend breaks down as follows — $7.67 of parts, with the rest of the line being the fab's assembly service:

ComponentQtyCost
STM32F072CBT61$2.88
EC11 encoder1$1.64
RKJXV joystick1$0.97
Passives1 set$0.84
21 × SK6812MINI-E LEDs21$0.90
8 MHz crystal1$0.13
AP2112 LDO1$0.10
USBLC6 ESD protection1$0.05
USB-C port1$0.15

Two things worth saying plainly about that number.

First, it is a one-off prototype quote, with hand-populated through-hole parts and no shipping, spare boards, or failed attempts included. Different fabs, finishes, and shipping choices move it; add a spare board and a courier and it climbs. My labour is priced at zero throughout.

Second — and more importantly:

$37 and $230 are not the same kind of number, and this is not a like-for-like comparison.

The commercial device runs wireless over Bluetooth, and carries the power management and cell to do it — Work Louder's equivalent Creator Micro 2 PRO lists a 2100 mAh battery. This board is wired-only: no radio, no antenna, no RF layout, no wireless stack, no charging circuit, and no radio certification. The commercial unit also arrives in a CNC aluminium and polycarbonate case; this one gets a 3D print. And the $230 buys assembly at volume, testing, packaging, distribution, warranty, support, and margin — none of which appear in a prototype quote.

The honest claim is not "six times cheaper." It is that an open board with the same control surface costs tens of dollars to make, and you can inspect and change every layer of it.

One assembled board at $36.90 against the commercial product at $230 A stacked bar shows the $36.90 board: $6.93 PCB, $18.14 turnkey SMT, $2.97 switches, $8.85 keycaps. A second bar shows the Codex Micro at $230. The roughly $193 difference is bracketed and labelled with what it pays for: wireless operation and its battery, a machined case, assembly at volume, testing, packaging, distribution, warranty, support and margin. The same figures appear in the two tables above. OPENMICROKBD $36.90 CODEX MICRO $230 $0 $100 $200 the remaining ~$193 buys things this board does not have wireless operation and its battery · a machined case assembly at volume · testing · packaging · distribution · warranty · support · margin PCB $6.93 Turnkey SMT $18.14 Switches $2.97 Keycaps $8.85
Same length scale for both, because that is the honest picture — but the gap is not markup. The bracket is what the difference actually pays for.

From requirements to routed copper

The workflow started with concrete requirements given to an agent: control layout, matrix, power rails, USB, LEDs, MCU resources, mechanical constraints. That conversation became CoHDL source for the schematic-level design.

CoHDL emitted the netlist, BOM, footprints, and constraints. The design then went to Quilter.ai for physical layout. The resulting boards were ordered from a fab and populated.

The Quilter.ai layout screen showing the routed 95 by 95 millimetre board, its switch and LED footprints, layer list and a design review panel
Physical layout is Quilter's work, not CoHDL's: 88 components and 337 pins placed and routed, returned as ten candidate boards in about twelve minutes.

An agent also assisted with firmware and host-app development. It did not produce a finished system from one prompt. Pin assignments, component choices, generated outputs, layout, soldering, bring-up, behavior, and failure modes all required review and debugging.

Hand-soldering the board at an electronics workbench, with magnifier, iron and board vise in frame
The through-hole parts are still hand work: switches, encoder and joystick are not in the pick-and-place file.

The PCB is written, not drawn

The schematic for this board is not a drawing. It is source code:

inst sw: [SW_KEY; 13]
inst d: [diode::D_1N4148W; 13]

net ROW1: mcu.PA10, sw[2..=5].A
net COL1: mcu.PB5, d[0, 3, 7, 10].Cathode

Power intent can be attached to a net:

#[intent("AP2112K enable tied to VIN — always on when USB is present")]
#[high_current(500mA)]
net V3V3 [3.3V]: ldo.VOUT, mcu.VDD, mcu.VDDA, mcu.VBAT, mcu.VDDIO2, joy.X_END_B, joy.Y_END_B,
                 c_vdda.A, c_vdd_a.A, c_vdd_b.A, c_vdda_hf.A

[3.3V] is a unit annotation. #[intent(...)] makes design intent a checkable attribute rather than an unverified comment. The compiler checks units, pin-connection obligations, and power-integrity rules, then writes netlist, BOM, footprint, and constraint artifacts under hw/v1/out/. It also generates the firmware pin map from the hardware source.

Two honest boundaries. CoHDL produced the schematic and its generated outputs; Quilter.ai did the physical layout — the routing is not written. And the compiler is not public yet, which matters: source hardware is most useful when someone else can run the whole toolchain. For now CoHDL is a demonstrated but unreproducible part of this project.

Part two: the enclosure

A bare PCB is not a satisfactory desktop device. It rocks on components or standoffs, exposes solder joints, and makes a side-mounted USB connector awkward to reach.

So the repository ships a printable enclosure under hw/v1/mechanical/, as both editable Fusion sources and ready-to-slice meshes: the case, the encoder knob, the joystick cap, the packaging, and the soldering jig used to hold the board while the through-hole parts go in. The board outline is there as DXF too, for anyone modelling their own.

Securing the PCB into the white 3D-printed enclosure with a corner screw
The board mounts at the four corners, with a side opening for USB-C.

The enclosure is also the layer most worth remixing, and the easiest to change safely. Tilt, feet, material, encoder height, and USB-cable clearance can all vary without touching the electronics. If you design a variant, publish the editable source beside the printable output, and leave the SWD socket accessible — it is the recovery path if a firmware update goes wrong.

Part three: firmware and its failure modes

Firmware lives under fw/ and uses Rust with Embassy for thumbv6m. The current build occupies about 22 KiB of 128 KiB flash and roughly 5 KiB of 16 KiB static RAM, with LTO and opt-level="s".

There is no separate RTOS, and the crate's own unsafe is confined to a handful of narrow, commented blocks: the WS2812 inline-asm bit timing, the DFU no-init-RAM handoff into the ROM bootloader, and one memory-mapped flash read.

The COL2ROW matrix scans at 1 kHz with 5 ms debounce. The exact GPIO assignments come from the generated pin map — more on that below.

USB exposes a composite HID device — keyboard, consumer control, and a mouse interface that carries the joystick's pointer modes — plus a vendor interface on usage page 0xFF60 with 32-byte reports. bcdDevice carries the Cargo semantic version, letting the updater decide whether an update is needed without opening the vendor interface.

Deep dive one: the keymap lives on the device

Every input is a slot, and there are 24 of them: 13 keys, three for the encoder, five for the joystick, three for the touch pad. Each slot is four bytes:

pub struct Slot {
    pub kind: u8,   // none / keyboard / consumer
    pub mods: u8,   // HID modifier bitmask — ctrl, shift, alt, gui
    pub code: u16,  // keyboard usage, or consumer usage
}

That mods byte is what makes the board more than an F-key pad. A slot can emit a plain keycode, a modifier-qualified combination, or a consumer usage like play/pause. The factory defaults use F13–F20 plain and Shift+F13–F17 for the remaining five keys, so nothing collides with an existing OS shortcut out of the box — but none of that is baked in.

The whole table is persisted in the last 2 KiB flash page, at offset 0x1F800. The blob is 120 bytes in today's layout: an "OMK1" magic, a layout version, analog tuning, the 24 slots, lighting settings, and a checksum — and the loader still accepts every earlier layout version. Vendor HID commands manage it — GET_KEYMAP, SET_KEYMAP, SAVE, FACTORY_RESET.

The save path is ordered deliberately. The body is written first and the magic word is written last, as its own operation. If power dies mid-save, the header stays erased, the magic check fails on next boot, and the board comes up on factory defaults rather than a half-written keymap. The checksum catches the subtler tear where power drops between body and magic during a re-save.

Corrupt slots degrade rather than propagate: a bad kind, or a keyboard code above 0xFF, becomes a disabled slot instead of a wrong keypress.

The board in front of a laptop running the config app, showing key, rotary and joystick assignments
Remapping on the device: the app reads and writes the assignments for keys, encoder and joystick, then saves the table into flash.

Deep dive two: DFU without a resident bootloader

The app requests DFU with a vendor HID command. The device acknowledges it, writes a magic word into no-init RAM, and reboots.

Early startup checks and clears that marker before peripheral initialization, then jumps to STM32 ROM system memory at 0x1FFF_C800, as documented by ST AN2606.

The chip re-enumerates as ST DFU device 0483:df11 on the same USB-C port, and the app speaks DfuSe (AN3156) over libusb directly: erase the covered 2 KiB pages, program in wTransferSize blocks, set the address pointer, manifest. No custom resident bootloader consumes flash, requires versioning, or has to coordinate with the application image.

Before any of that happens, the app sanity-checks the image — it validates the Cortex-M0 vector table against 128 K flash and 16 K RAM, so pointing it at the wrong .bin is refused before a single page is erased.

The failure mode still matters. BOOT0 is held low, so ROM entry is software-only. If power fails mid-programming, the application may be gone and the board will not automatically return to DFU. Recovery is the SWD socket.

That trade is acceptable for a board with an exposed debug socket. It would need reconsideration in a sealed consumer product.

The companion app's Firmware dialog mid-install, showing version 0.6.0 available, the raw .bin image selected, and a progress bar at 26 percent
The whole update is one dialog and no held buttons. The keymap sits in a flash page the programming pass never covers, so profiles survive the install.

Deep dive three: fitting 21 LEDs inside the USB budget

Twenty-one addressable LEDs can still exceed the USB 2.0 500 mA budget at full white. Firmware limits every channel using scaled(n / 64), across both the 13 key LEDs and the 8 perimeter LEDs sharing VBUS.

This is a static ceiling, not a dynamic current model. It neither estimates draw from the current color mix nor negotiates higher USB-C current. The worst case is predictable, but the LEDs never reach their standalone rated brightness.

The protocol is bit-banged, and its cycle counts assume a 48 MHz core. Clock configuration is therefore part of driver correctness, not merely performance tuning.

Deep dive four: USB disciplines the core clock

Although the board includes an 8 MHz HSE crystal, the current firmware does not depend on it. HSI48 drives the CPU and USB peripheral at 48 MHz.

The STM32 Clock Recovery System disciplines HSI48 against USB Start-of-Frame packets. USB becomes the timing reference, keeping the core at the frequency the LED driver assumes while leaving the crystal available for future firmware and debugging.

How the USB host ends up setting the core clock The USB host sends start-of-frame packets once per millisecond. The Clock Recovery System compares them against the HSI48 on-chip oscillator and trims it, so HSI48 holds 48 MHz. HSI48 clocks both the CPU core and the USB full-speed peripheral. The WS2812 bit-bang driver runs on the CPU and its cycle counts assume exactly 48 MHz, which is why the clock choice is part of driver correctness. The 8 MHz HSE crystal is fitted on the board but the current firmware does not use it. USB HOST SOF every 1 ms CRS compares & trims HSE 8 MHz fitted, unused HSI48 48 MHz on-chip RC USB FS PERIPHERAL the same clock it disciplines CPU CORE 48 MHz WS2812 BIT-BANG cycle counts assume 48 MHz USB is the timing reference — lose the host and the LED driver loses its yardstick.
The crystal is on the board, but the host is what keeps the core at the frequency the LED driver counts on.

The boundary is explicit: this design assumes a USB-connected device. It would be a poor clock strategy for a wireless, battery-powered variant running for long periods without SOF packets — one concrete example of how far the two designs diverge once a radio is involved.

Deep dive five: the pin map is generated, not copied

Firmware does not choose its pins independently. CoHDL generates the pin map from the hardware source.

That removes a common two-file failure: changing a schematic pin while forgetting the corresponding firmware constant. Generated files are not automatically correct; they preserve bad choices as reliably as good ones. The narrower benefit still holds — electrical intent and firmware assumptions cannot silently drift apart.

Part four: the companion app

The Rust and GPUI app under app/ does three things.

It finds the pad over the vendor HID interface (1209:0001, usage page 0xFF60) and shows live connection state, the running firmware version via an in-band query, and the serial.

It updates firmware over the DFU path described above — pick a .bin, press Install, and it validates, reboots to ROM DFU, programs, waits for re-enumeration, and confirms the new version.

And it edits the keymap, reading the current table off the device, letting you reassign any slot, and writing it back into flash. Because the mapping lives on the board, it travels with the hardware: configure it once, plug it into a different machine, and the keys behave the same with no software installed there.

Separately, the app can bind a key to a host-side action — running a shell command (sh -c, or cmd /C on Windows) or opening a URL, file, or app through the OS default handler. Those bindings are host-side by nature and live in the app's own configuration; a keyboard cannot launch your build script by itself. So there are two layers available, and the trade-off between them is explicit: device-resident keymaps are portable, host-side actions are more powerful.

The board plugged into a MacBook over USB-C with its key lighting and underglow active
In daily use the keymap lives on the board — it keeps working with the app closed, and travels to whatever machine you plug into.

Scope, and what you are choosing

A different device with different priorities, not a feature-for-feature substitute:

The commercial product arrives assembled, enclosed, tested, and supported. This project asks you to supply assembly and time, in exchange for a board you can modify.

Build one

The repository is github.com/conol-ai/openmicrokbd:

Prebuilt, notarized macOS builds of the companion app ship with every GitHub release, alongside the firmware binaries. Or build both from source:

git clone https://github.com/conol-ai/openmicrokbd
cd openmicrokbd
scripts/build-firmware.sh dist        # production binary + debug ELF
cd app && cargo run --release --locked

To order boards, upload hw/v1/fab/openmicro-gerbers.zip with the assembly BOM and the SMT placement CSV. The board is 4-layer, 95 × 95 mm, 1.6 mm FR-4; DRC reports zero unconnected items, and the remaining clearance and silkscreen flags are documented and accepted in hw/v1/fab/README.md. Two ordering notes that matter: specify ENIG so the capacitive touch pad works properly, and note that the through-hole parts — switches, encoder, joystick — are hand-populated, since the pick-and-place file covers SMD only.

If you build one, keep the SWD socket reachable. Because BOOT0 is held low, that socket is the recovery path after a power loss during a firmware update.

The manufacturing files are in the repository. The point was never to show you a board — it was to let you make one.

Thanks to Quilter.ai for the physical layout, and to the readers who challenged the original product's closed feedback path. The useful criticism was never that a macropad is silly. It was that a general-purpose input device should stay general-purpose.

One last thing, for anyone who scrolled past the code. That schematic was never drawn in an editor — it was written, type-checked, and compiled. The language is called CoHDL, and it is the part of this project I am most interested in. Its compiler is not public yet, so the generated files in hw/v1/out/ are the only evidence I can offer today. It gets its own write-up when it opens.

Until then the keyboard is the argument. The PCB is written, not drawn — and you can read every line of it today.

★ Get the Blueprint on GitHub ← All Posts