Skip to content

✨ Katapult bootloader entry (M997) and upload - #28535

Open
thisiskeithb wants to merge 3 commits into
MarlinFirmware:bugfix-2.1.xfrom
thisiskeithb:pr/add-katapult-m997
Open

✨ Katapult bootloader entry (M997) and upload#28535
thisiskeithb wants to merge 3 commits into
MarlinFirmware:bugfix-2.1.xfrom
thisiskeithb:pr/add-katapult-m997

Conversation

@thisiskeithb

@thisiskeithb thisiskeithb commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Description

Add opt-in Katapult support for STM32: bootloader entry from M997, plus a PlatformIO upload target that flashes through Katapult from VSCode or pio run -t upload.

Katapult is the standard bootloader in the Klipper ecosystem, and several boards Marlin supports ship with it or have vendor instructions to install it (BTT Manta and Octopus, LDO Leviathan). Marlin already honors its flash offset through the board_build.offset envs, but getting into the bootloader still takes a BOOT/RESET press, and boards without an SD socket have no card flashing to fall back on.

M997

With BOOTLOADER_KATAPULT defined, the STM32 HAL's flashFirmware() requests the bootloader the way Klipper's armcm_reset.c does: check Katapult's "CanBoot!" signature, stored 8 bytes before its reset handler and located through the vector table at FLASH_BASE, write the request key 0x5984E3FA6CA1589B to the 8 bytes at the bootloader's initial stack pointer, then reset. Katapult sees the key and stays resident, so firmware can be flashed with no button presses. On Cortex-M7 the write is followed by SCB_CleanDCache_by_Addr() so it lands in SRAM before the reset.

If the signature check fails, because no bootloader is installed or something else lives at FLASH_BASE, M997 falls back to hal.reboot(), so the define is harmless on a bare board.

Boards opt in per env with -DBOOTLOADER_KATAPULT on the env whose board_build.offset matches the installed Katapult's offset, and build_unflags it in no_bootloader variants. Any stock Katapult build works.

Upload

buildroot/share/scripts/katapult_upload.py registers an upload callback, same pattern as the _xfer envs' upload.py:

  1. If a Katapult device (USB ID 1d50:6177) is already enumerated, from a first install or an interrupted flash, flash it directly.
  2. Otherwise autodetect the running Marlin's port, send M997, and wait for the board to come back as Katapult.
  3. Run Katapult's flashtool.py to upload and SHA-verify, after which Katapult jumps to the new firmware.

flashtool.py is downloaded once from a pinned Katapult commit, SHA-256 verified (Katapult is GPLv3 like Marlin), and cached under .pio/. Set KATAPULT_FLASHTOOL to use a local checkout instead.

Three envs are included for the boards most often shipped with Katapult, each keeping its parent's existing board_build.offset:

Env Board Offset
STM32F446ZE_btt_katapult Octopus V1.0/1.1, Octopus Pro V1.0 (F446ZE) 0x8000
STM32F429ZG_btt_katapult Octopus V1.1, Octopus Pro V1.0 (F429ZG) 0x8000
STM32H723ZE_btt_katapult Octopus Pro V1.0.1/1.1, Octopus Max EZ, Manta M8P V2.0 (H723ZE) 0x20000

Other boards need the same three lines: -DBOOTLOADER_KATAPULT, the pre: script, and upload_protocol = custom.

Notes

  • flashtool.py imports termios/fcntl, so uploads need POSIX (Linux / macOS / WSL). The script errors out early on native Windows.
  • The H723 envs build with -DD_CACHE_DISABLED, so SCB_CleanDCache_by_Addr() is a no-op there. It's in for anyone running with the cache on.

Tested on a handful of boards with stock Katapult at 0x0 and Marlin at 0x8000:

Board MCU Env
BTT Octopus V1.0 STM32F446ZET6 STM32F446ZE_btt_katapult
BTT Octopus Pro V1.0 STM32F446ZET6 STM32F446ZE_btt_katapult
BTT Octopus Pro V1.0 STM32F429ZGT6 STM32F429ZG_btt_katapult
BTT Octopus Pro V1.1 STM32H723ZET6 STM32H723ZE_btt_katapult
LDO Leviathan V1.2 STM32F446ZET6 (custom env)

On the four Octopus boards with Katapult built for a matching offset (32KiB on F4, 128KiB on H7) and USB on PA11/PA12:

  • M997 over USB CDC drops the board from 0483:5740 to Katapult's 1d50:6177 in ~0.3s on every board, no button presses.
  • pio run -t upload with the board already in Katapult flashes and SHA-verifies in ~6-11s.
  • pio run -t upload with Marlin running does the M997 handshake and completes in ~7-9s. Katapult reports Application Start: 0x8008000 on F4 and 0x8020000 on H7, matching each env's board_build.offset.
  • Marlin boots after each upload and answers M115 with the timestamp of the build just flashed.

Tip

The crystal differs per board (12MHz on F446, 8MHz on F429, 25MHz on H723), so Katapult has to be configured to match.

For anyone testing over SWD: Marlin uses PA13/PA14 (SWDIO/SWCLK) for LED_PIN and E3_DIR_PIN on the Octopus family, so an ST-Link has to connect under reset once firmware is running.

Requirements

An STM32 board with Katapult installed at an offset matching the env's board_build.offset.

Benefits

Reflash a Katapult board over USB without opening the case to press BOOT / RESET.

Configurations

Stock configs with two changes, built against env:STM32F446ZE_btt_katapult (swap the board for the other two envs):

#define MOTHERBOARD BOARD_BTT_OCTOPUS_PRO_V1_0
#define SERIAL_PORT -1  // USB CDC, for M997 and the upload script

Bootloader Backups

These exist elsewhere, but I dumped BTT's stock SD card bootloaders just in case:

Related Issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants