Building & flashing Klipper firmware
Running Klipper means compiling a tiny firmware for your printer's microcontroller and flashing it onto the board. It sounds intimidating and it is genuinely simple — three commands and a copy — but one wrong setting in the menu silently bricks the boot. This is the HOW-TO, written from a real build.
You configure the build for your exact MCU with make menuconfig, run make to produce out/klipper.bin, and copy that file onto the board. The single biggest gotcha is the bootloader offset: most Creality and BigTreeTech boards ship with a bootloader that expects the application at 28KiB (0x7000), not Klipper's default 8KiB (0x2000). Pick the wrong offset and the flash succeeds but the board never boots.
What you're building
Klipper has two halves and you only compile one of them. The host half — the motion planner — already lives on your Raspberry Pi or other SBC; you installed it once and you update it with git. The MCU half is a small program that runs on the printer's own chip and does nothing but execute precisely timed step commands sent from the host. That MCU firmware is hardware-specific, so there is no universal binary: you build it for your board, then flash it.
Everything below happens on the host (the Pi), inside the Klipper source tree — usually ~/klipper. You compile there, then move the resulting binary to the printer board. The host and the MCU firmware are versioned together, which matters later (see The traps).
Step 1 — make menuconfig
From the Klipper directory, run make menuconfig. This opens a text menu where you describe the target board. Three choices matter:
- Micro-controller architecture. Pick the family your board actually uses — STM32 (covering F0/F1/F4/G0 parts), GD32, RP2040, AVR and others all appear here. For an STM32 you then choose the specific processor:
STM32F103is the classic 8-bit-era replacement chip on older Creality boards, while newer boards useSTM32F4xxorSTM32G0xx. Getting the chip wrong produces a binary the board can't run. - Bootloader offset. This is the one that bites. The board's existing bootloader occupies the start of flash and hands control to your application at a fixed address. Klipper defaults to an 8KiB offset (
0x2000), but most Creality and many BigTreeTech boards expect 28KiB (0x7000). Set this to match the bootloader on the board, not what feels normal. - Communication interface. Choose how the host will talk to the MCU. Boards with a dedicated USB port often use USB; boards that talk over the original serial pins use a USART — commonly USART1 on PA9/PA10 on Creality 8-bit-replacement boards. This choice has to match the cable you'll actually use.
Save and exit. menuconfig writes a .config file that drives the build. On our Creality 4.2.2 board the working answers were STM32F103, 28KiB offset (0x7000), and serial on USART1.
Step 2 — make
Run make. It compiles against the .config from the previous step and, on success, writes the firmware to out/klipper.bin (some targets produce out/klipper.uf2 or out/klipper.elf instead — RP2040 in particular emits UF2). If you change anything in menuconfig, run make clean before make to avoid a stale build. That out/klipper.bin is the entire deliverable; the next step is just getting it onto the board.
Step 3 — flash it
How you flash depends entirely on the board's bootloader. There are three paths you'll meet in practice, plus UF2 for RP2040.
- SD card (Creality / BigTreeTech). The common case. Copy
out/klipper.binonto a microSD card, rename it to something the board's bootloader will pick up — frequentlyfirmware.bin, though Creality boards usually accept any unique name and look for a changed file — insert it, and power-cycle the printer. The bootloader copies the new firmware and, on success, renames the file on the card to.CUR. If the file is still.binafter a reboot, nothing flashed (see The traps). - DFU / USB (native-USB STM32). Boards that expose the MCU's USB directly can be flashed over the wire with
dfu-util, often via Klipper'smake flash FLASH_DEVICE=.... You typically put the chip into DFU mode first — a jumper or a button — then writeklipper.binto the application offset. This is the fastest loop once it's set up because there's no card-shuffling. - BOOT0 / ROM bootloader (recovery). Every STM32 has a built-in serial ROM bootloader you reach by tying the BOOT0 pin high at power-on. This is the rescue path when the board won't boot at all — you flash over UART with
stm32flash(or re-install a proper bootloader). Slow and fiddly, but it gets a dead board back. - UF2 (RP2040). RP2040 boards mount as a USB mass-storage device when held in BOOTSEL; you drag
out/klipper.uf2onto the drive and it reboots into firmware. No tools needed.
The traps
- Wrong bootloader offset = a board that won't boot. The flash reports success, the file is even renamed to
.CUR, and the printer is dead. This is almost always Klipper's default0x2000offset on a board that wants0x7000. The application landed at an address the bootloader never jumps to. Rebuild with the correct 28KiB offset and re-flash — it is recoverable, you just have to get the number right. - Host and MCU versions must match. Klipper checks that the firmware running on the MCU was built from the same version as the host. Update Klipper on the Pi (
git pull) and the host will refuse to connect until you rebuild and re-flash the MCU. Treat a host update and an MCU re-flash as one operation. - SD-card pickiness. The Creality bootloader is fussy about the card itself, and this wastes more time than any compile error. It wants a small SDHC card (a few GB, not a 64GB+ SDXC), formatted FAT32 with a small allocation unit — around 4KB clusters. Big cards and large cluster sizes give a silent failure: the screen shows something like "No SD/TF-Card or error", or it just ignores the file, and the
.binnever becomes.CUR. When a flash mysteriously does nothing, reformat a small card to FAT32 / 4KB before you touch anything else. - The
.CURrename is your receipt. A successful flash renamesfirmware.bintofirmware.bin.CUR(or similar) on the card. If you re-flash, the bootloader may ignore an unchanged filename — give the new build a different name or delete the old.CURso it's seen as new.
Find the serial
Once the MCU is flashed and connected, the host needs to know where to reach it. The stable way is by-id, which survives reboots and port swaps:
ls /dev/serial/by-id/*
That prints a long, device-specific path. Copy it into printer.cfg under the [mcu] section as the serial: value, then restart Klipper. If by-id shows nothing, the board either didn't enumerate (wrong interface choice in Step 1) or didn't boot (wrong offset — back to The traps).
Links
This guide is distilled from a real 3d.2nth.ai build: ender-pi (github.com/2nth-ai/ender-pi), where we flash Klipper onto the stock Creality 4.2.2 board of a Creality Ender 3 V2 from a Raspberry Pi 4. The 28KiB-offset trap and the SD-card pickiness above are both lived experience from that build, not theory.
- Official build and flash docs: klipper3d.org — Installation and Config checks.
- Per-MCU notes: klipper3d.org — Bootloaders (the authoritative reference for offsets).
- Klipper source and releases: github.com/Klipper3d/klipper.
- More on the firmware itself: Klipper.