Batch STM32 SWD flasher. Wraps STM32_Programmer_CLI.exe so you can flash many
boards back-to-back without clicking through the STM32CubeProgrammer GUI.
Built for production runs: connect a board, press Enter, watch for OK. It keeps
a running pass/fail tally, writes a CSV log of the session, and refuses to flash
if the firmware file changes under it mid-run.
- Python 3.8+ (3.14 confirmed working)
- STM32CubeProgrammer installed at the default location:
C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\STM32_Programmer_CLI.exe - ST-Link probe (SWD)
If your install path differs, pass --cli <path> or set the AXXFLASHER_CLI
environment variable. AxxFlasher also checks the Program Files (x86) location
and your PATH, and it validates the programmer before the first board
rather than failing part way through a batch.
Three ways to start it:
- Drag and drop firmware onto
axxflasher.bat. - Double-click
axxflasher.bat— a file picker opens. - Command line:
python axxflasher.py path\to\firmware.bin
Then for each board: connect it, press Enter, wait for OK (or FAIL).
While a board flashes, a live progress bar tracks the programmer through its
phases (erasing → writing → verifying) with elapsed time; it clears when
the flash finishes, leaving the one-line OK/FAIL result. The bar is purely
cosmetic — the programmer is invoked exactly as before, so per-board time is
unchanged — and it is suppressed when output is redirected to a file.
A short high beep means OK, a two-tone low buzz means FAIL, so you can watch
the bench instead of the screen (--no-beep to silence).
Ctrl+C to quit. A summary of pass/fail counts prints on exit, listing which
attempt numbers failed, plus a lifetime counter kept across sessions (stored
under %LOCALAPPDATA%\AxxFlasher, never in the working directory).
--auto removes the Enter press entirely: insert a board and it flashes as
soon as it is detected, then AxxFlasher waits for you to remove it before
arming the next cycle. Empty-fixture polls are silent and are not counted as
attempts. Removal is detected with non-intrusive HOTPLUG probes so the fresh
board is not reset while it sits in the fixture; if a firmware hides from
HOTPLUG (SWD pins reused), AxxFlasher notices via the UID and switches to the
flashing mode automatically. Note: continuous polling means many short
connect attempts, which flaky ST-Link clones tolerate worse than the
one-press-one-flash flow.
Exit code is 0 if every attempt passed, 1 if any failed — so it slots into a
scripted test rig.
| Flag | Default | Notes |
|---|---|---|
-a, --address ADDR |
0x08000000 |
Flash address for .bin files. Ignored for .elf and .hex. |
--no-verify |
off | Skip post-flash verify (faster but no integrity check). |
-v, --verbose |
off | Stream full programmer output on every flash. |
--cli PATH |
auto | Path to STM32_Programmer_CLI.exe. |
--sn SERIAL |
any | Pin to one ST-Link by serial number. |
--mode {UR,HOTPLUG,NORMAL} |
UR |
Connect mode. UR (under reset) is the most reliable on fresh parts. |
--timeout SEC |
90 |
Per-flash timeout. |
--no-retry |
off | Disable the automatic single retry on a transient probe error. |
--no-log |
off | Don't write the CSV run log. |
--list-probes |
— | List connected ST-Link probes and exit. |
--auto |
off | Hands-free: flash on board detection, wait for removal, repeat. |
--lock |
off | Set readout protection (RDP level 1) after each successful flash. |
--no-beep |
off | Disable the pass/fail sounds. |
--no-uid |
off | Don't read/log the device unique ID. |
--uid-addr ADDR |
auto | UID base address for families not in the built-in table. |
Each flash is an independent STM32_Programmer_CLI invocation, so the fastest
way to get through a large batch is more probes, not a faster script — the bulk
of the ~2 s per board is USB enumeration and connect, which can't be shortened
from here.
With two or more ST-Links, pin each instance to a probe and run them in separate terminals:
python axxflasher.py --list-probes
python axxflasher.py fw.bin --sn 0671FF535155878281151625
python axxflasher.py fw.bin --sn 066BFF343339424D43165223
--no-verify cuts roughly the read-back half off each flash if you are willing
to trade the integrity check for speed.
.bin— written to--address(default0x08000000).elf— addresses come from the ELF, no--addressneeded.hex— addresses come from the HEX, no--addressneeded
- Firmware fingerprint. The SHA-256 of the image is taken at startup and re-checked before every flash. If you rebuild the firmware part way through a run, AxxFlasher stops rather than silently shipping two different images in one batch.
- Wrong-file check. For
.binimages the Cortex-M vector table is sanity checked before the run (stack pointer in RAM, reset vector inside flash at the target address). A file that fails the check needs an explicit confirm — this catches picking the wrong file or flashing a bootloader-offset image to the wrong address. - Automatic retry. A failure that looks transient (no probe, no target, timeout) is retried once before being called a failure. A verify mismatch is never retried — you want to see that one.
- Wedged-probe hint. The ST-Link failure state that only a physical USB
replug can fix (
DEV_USB_COMM_ERR) is recognized and reported as exactly that, instead of a generic failure that invites useless retries. - UID traceability. Each board's 96-bit unique ID is read in the same
programmer invocation (a milliseconds-level chained read, no reconnect) and
written to the CSV log. The address is auto-detected from the device ID for
common STM32 families (F0–F7, G0/G4, L0–L5, H7, WB/WL, U5); pass
--uid-addrfor anything else,--no-uidto turn it off. The first board of a session pays one extra read (~1 s) because the family isn't known until the first connect. - Double-flash detection. If a board's UID was already seen this session, AxxFlasher says so — catching the classic "did I already do this one?" slip.
- Run log. Every attempt is appended to
axxflasher-<firmware>-<timestamp>.csvin the working directory (gitignored): timestamp, attempt number, result, duration, probe serial, device UID, and failure reason. - AxxSolder reminder. When the firmware file name contains "AxxSolder", a reminder to disconnect the iron handle is printed at startup.
With --lock, each successful flash is followed by a separate programmer
invocation that sets readout protection to RDP level 1. A lock failure cannot
turn a good flash into a reported FAIL — it is reported on its own line
(and gets the failure sound). Note that reflashing an RDP1 board later
requires a mass erase (-ob RDP=0xAA). Adds ~1–2 s per board, only when
enabled. The lock step runs after the UID read, so UIDs still land in the log.
[#1] press Enter to flash >
OK 2.3s 1 ok / 0 fail
[#2] (1 flashed, 1 ok) press Enter to flash >
FAIL 1.1s 1 ok / 1 fail - no ST-Link detected - is the probe plugged in?
--- programmer output ---
| (last 15 lines of CLI output)
-------------------------
On failure, common causes are decoded into a one-line hint (no probe, target not connected, verify mismatch, read protection, etc.). Full CLI output is shown below the hint so you can dig in if needed.
A flash counts as successful only when the programmer exits 0 and its
output contains no line beginning with Error:. Both checks are needed:
STM32_Programmer_CLI can exit 0 while printing an error (-l does exactly
that), and matching Error anywhere in the output would fail every flash whose
firmware path happens to contain that word — the programmer echoes the path back.