From mcu-skills
This skill should be used when the user asks to flash, program, or upload firmware to an STM32 device over UART, when testing STM32 firmware autonomously, when asked to "flash and verify" or "flash and test" an STM32 board, or when working with STM32WL targets (rak3172, russell) in a meshtastic-firmware PlatformIO project.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mcu-skills:stm32-flashThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Autonomous workflow for flashing STM32WL firmware over the UART bootloader and confirming the
Autonomous workflow for flashing STM32WL firmware over the UART bootloader and confirming the device boots correctly. Covers the full sequence from environment checks through boot verification.
macOS only. This skill has only been tested on macOS. The port naming conventions (cu./tty.
prefixes), lock file paths (/private/tmp/LCK..), and lsof usage are macOS-specific. It is not
expected to work on Windows without significant adaptation.
wio-e5 not supported for UART flashing. The wio-e5 dev board's built-in USB-serial is wired to a UART that is not the STM32WL system UART bootloader port. The UART bootloader cannot be reached over the dev board USB connector. wio-e5 requires ST-Link/SWD for programming.
| Variable | Purpose | Auto-discovered if unset? |
|---|---|---|
STM32_PROGRAMMER_CLI | Full path to STM32CubeProgrammer CLI binary | Yes — searched in standard macOS locations |
MESHTASTIC_PORT | Serial port (tty. form), e.g. /dev/tty.usbserial-1234 | Yes — probed automatically (see Step 0) |
macOS port rule: all actual commands must substitute cu. for tty.:
${MESHTASTIC_PORT/tty./cu.}. STM32CubeProgrammer requires cu.; the meshtastic CLI returns
EBUSY on tty. immediately after the programmer releases the port.
# 1. Find STM32CubeProgrammer if not set
if [ -z "$STM32_PROGRAMMER_CLI" ]; then
STM32_PROGRAMMER_CLI=$(find /Applications -name "STM32_Programmer_CLI" -path "*/Resources/bin/*" 2>/dev/null | head -1)
fi
[ -z "$STM32_PROGRAMMER_CLI" ] && echo "ERROR: STM32_Programmer_CLI not found" && exit 1
echo "Programmer: $STM32_PROGRAMMER_CLI"
# 2. Find and probe serial port if not set
if [ -z "$MESHTASTIC_PORT" ]; then
MESHTASTIC_PORT=$(find_meshtastic_port) # see port-probe logic below
fi
[ -z "$MESHTASTIC_PORT" ] && echo "ERROR: no device found" && exit 1
echo "Port: $MESHTASTIC_PORT"
When MESHTASTIC_PORT is not set, enumerate all USB serial ports and probe each one with a 10-
second timeout, trying two tests in order:
meshtastic --port <cu.port> and check for
Connected to radio in the output within 10 s. If found, this port has running firmware.-info within 10 s and check for
Activating device: OK. If found, this port has a device in bootloader mode.Accept the first port that passes either check. If no port passes either check, report all ports tried and their outcomes, then abort.
# Enumerate candidates
CANDIDATES=$(ls /dev/tty.usbserial-* /dev/tty.usbmodem* 2>/dev/null)
if [ -z "$CANDIDATES" ]; then echo "ERROR: no USB serial ports found"; exit 1; fi
MESHTASTIC_PORT=""
for PORT in $CANDIDATES; do
CU_PORT="${PORT/tty./cu.}"
echo "Probing $PORT ..."
# Test 1: meshtastic firmware
if timeout 10 meshtastic --port "$CU_PORT" 2>&1 | grep -q "Connected to radio"; then
echo " → meshtastic firmware detected"
MESHTASTIC_PORT="$PORT"; break
fi
# Test 2: UART bootloader
if "$STM32_PROGRAMMER_CLI" -c port="$CU_PORT" br=115200 -info 2>&1 | grep -q "Activating device: OK"; then
echo " → STM32 UART bootloader detected"
MESHTASTIC_PORT="$PORT"; break
fi
echo " → no response (not a target device, or device off)"
done
Do this before every flash attempt. A previous meshtastic or STM32_Programmer_CLI
invocation left running in the background will silently hold the port; STM32CubeProgrammer will
hang after printing its header banner with no error.
PIDS=$(lsof 2>/dev/null | grep "$(basename $MESHTASTIC_PORT | sed 's/tty\./cu./')" | awk '{print $2}' | sort -u)
[ -n "$PIDS" ] && kill $PIDS 2>/dev/null
rm -f /private/tmp/LCK..cu.usbserial-* /private/tmp/LCK..tty.usbserial-*
Diagnosis: if STM32CubeProgrammer connects successfully it will print
Serial Port /dev/cu.usbserial-... is successfully opened. within ~1 second. If you see only the
banner header and nothing else, there is still a process or lock file blocking the port.
Two paths — try software first; fall back to hardware if the device has no running firmware.
meshtastic --port "${MESHTASTIC_PORT/tty./cu.}" --enter-dfu
Wait for the CLI to print a success message before proceeding. If it hangs, the device is not running meshtastic firmware — use the hardware path.
Confirm bootloader is active before flashing:
"$STM32_PROGRAMMER_CLI" -c port="${MESHTASTIC_PORT/tty./cu.}" br=115200 -info 2>&1 | grep -E "Activating|Chip|Device name"
Activating device: OK → proceedActivating device: KO → device is not in bootloader mode; repeat Step 2Cannot open port → repeat Step 1 (stale lock)PlatformIO writes .hex files to .pio/build/<env>/. Locate the latest and flash over UART.
-v verifies; -g 0x08000000 issues the UART bootloader GO command to jump to the application
without requiring a power cycle.
ENV=russell # or rak3172
HEX=$(ls -t .pio/build/${ENV}/firmware-${ENV}-*.hex 2>/dev/null | head -1)
if [ -z "$HEX" ]; then echo "ERROR: no hex found for env $ENV"; exit 1; fi
echo "Flashing: $HEX"
"$STM32_PROGRAMMER_CLI" -c port="${MESHTASTIC_PORT/tty./cu.}" br=115200 -w "$HEX" -v -g 0x08000000 2>&1
Expected successful completion output:
Download verified successfully
RUNNING Program ...
Address: : 0x8000000
Start operation achieved successfully
If Start operation achieved successfully is not present, the flash or verify failed — do not
proceed to Step 4.
Allow ~3 seconds for the firmware to initialise, then query the device:
meshtastic --port "${MESHTASTIC_PORT/tty./cu.}" --info 2>&1
A healthy device responds with JSON node info. Key fields to confirm:
| Field | Expected value |
|---|---|
pioEnv | Matches $ENV (e.g. "russell") |
firmwareVersion | Matches the build (e.g. "2.7.23.abc") |
rebootCount | 0 on first clean boot |
If the command hangs, the device has not booted — check for a crash loop on the raw UART:
python -m serial.tools.miniterm "${MESHTASTIC_PORT/tty./cu.}" 115200
# exit: Ctrl-A Ctrl-\
When block_size or block_count changed (as in feat/stm32-lfs-cleanup), the on-disk
superblock will not match and LittleFS will reformat on first mount. This is normal.
Look for on the raw UART:
[LittleFS] Formatting... then [LittleFS] Mounted — expected, not an error[NodeDB] Loading... followed by node lines — config rebuilt from scratch| Symptom | Cause | Fix |
|---|---|---|
| No ports found in Step 0 | No USB serial adapters connected | Connect the device |
| All ports time out in Step 0 | Device off, wrong board, or wio-e5 (unsupported) | Power on device; check it is rak3172 or russell |
| Programmer hangs after header banner | Stale process/lock | Step 1 |
Activating device: KO | Device not in bootloader | Repeat Step 2 |
Cannot open port | Lock file from previous run | rm -f /private/tmp/LCK..cu.usbserial-* |
meshtastic --info EBUSY | Using tty. instead of cu. | Use ${MESHTASTIC_PORT/tty./cu.} |
meshtastic --info hangs | Device crashed or not booted | Check raw UART for panic |
| Flash succeeds but GO fails | Should not happen with -g 0x08000000; if it does, power-cycle |
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub mesh-malaysia/mcu-skills --plugin mcu-skills