Created
August 28, 2025 17:25
-
-
Save instance-id/bdf16a4d3bcdbc292dedbaee62f7385b to your computer and use it in GitHub Desktop.
Unlock and flash candleLight firmware to WeAct USB2CANFDV1 device via ST-Link v2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
#--------------------------------------------------------------------------------------------------------------- | |
# Flash candleLight firmware onto the WeAct USB2CANFDV1 (https://github.com/WeActStudio/WeActStudio.USB2CANFDV1) | |
# auto-handling leftover write-protection / PCROP and (optionally) lowering RDP. | |
#--------------------------------------------------------------------------------------------------------------- | |
# This script assumes you are using ST-Link V2 and it is connected to the device. | |
# Device used when creating/testing (https://www.aliexpress.us/item/3256805117494436.html - STLink-STM32 variant) | |
# This script came about from the following firmware port | |
# https://github.com/WeActStudio/WeActStudio.USB2CANFDV1/issues/4 | |
# Follow steps in above issue post to build WeActStudio_USB2CANFDV1_fw | |
# Firmware location: | |
# https://github.com/romainreignier/candleLight_fw/tree/add_weactstudio_usb2canfdv1 | |
# Once you have WeActStudio_USB2CANFDV1_fw.bin file: | |
# run this script to erase the device and flash new firmware. | |
# Usage: | |
# ./firmware_flash.sh # normal (will flash the specified firmware) | |
# ./firmware_flash.sh --unlock # clears WRP/PCROP if still set (requires RDP=0xAA) | |
# ./firmware_flash.sh --force-unlock # skip prompt when clearing protections | |
# ./firmware_flash.sh --auto-rdp # if RDP != 0xAA, lower it to 0xAA (TRIGGERS MASS ERASE) | |
# ./firmware_flash.sh --auto-rdp --unlock # perform full sequence automatically (ideal for new unmodified device) | |
# | |
# Notes: | |
# * Lowering RDP (Level 1 -> 0xAA) causes an automatic full chip erase by the MCU. | |
# * After lowering RDP, a separate mass erase is redundant; script detects this. | |
# ENV overrides: FIRMWARE_BUILD, FIRMWARE_NAME, CLI (STM32_Programmer_CLI path) | |
# github.com/instance-id | |
set -euo pipefail | |
CLI=${CLI:-STM32_Programmer_CLI} | |
FIRMWARE_BUILD=${FIRMWARE_BUILD:-/mnt/x/GitHub/romainreignier/candleLight_fw/build} | |
FIRMWARE_NAME=${FIRMWARE_NAME:-WeActStudio_USB2CANFDV1_fw.bin} | |
FIRMWARE_PATH="${FIRMWARE_BUILD}/${FIRMWARE_NAME}" | |
ADDR=0x08000000 | |
DO_UNLOCK=0 | |
FORCE=0 | |
AUTO_RDP=0 | |
RDP_LOWERED=0 | |
while [[ $# -gt 0 ]]; do | |
case "$1" in | |
--unlock) DO_UNLOCK=1; shift;; | |
--force-unlock) DO_UNLOCK=1; FORCE=1; shift;; | |
--auto-rdp) AUTO_RDP=1; shift;; | |
-h|--help) | |
grep '^#' "$0" | sed 's/^# //;s/^#$//' ; exit 0 ;; | |
*) echo "Unknown arg: $1" >&2; exit 1;; | |
esac | |
done | |
if [[ ! -f "$FIRMWARE_PATH" ]]; then | |
echo "ERROR: Firmware file not found: $FIRMWARE_PATH" >&2 | |
exit 1 | |
fi | |
echo "Firmware: $FIRMWARE_PATH" | |
echo "Reading option bytes..." | |
OB_DUMP="$($CLI -c port=SWD -ob displ 2>/dev/null || true)" | |
extract_field() { | |
# Grep field like WRP1A_STRT and pull hex value after ':' | |
printf '%s\n' "$OB_DUMP" | awk -v k="$1" '$1==k{for(i=1;i<=NF;i++){if($i==":") {print $(i+1); exit}}}' | |
} | |
RDP_VAL=$(printf '%s\n' "$OB_DUMP" | grep -E '^[[:space:]]*RDP[[:space:]]*:' | grep -o '0x[0-9A-Fa-f]*' | head -n1 || true) | |
WRP1A_STRT=$(printf '%s' "$OB_DUMP" | grep -E 'WRP1A_STRT' | grep -o '0x[0-9A-Fa-f]+' | head -n1 || true) | |
WRP1A_END=$(printf '%s' "$OB_DUMP" | grep -E 'WRP1A_END' | grep -o '0x[0-9A-Fa-f]+' | head -n1 || true) | |
PCROP1A_STRT=$(printf '%s' "$OB_DUMP" | grep -E 'PCROP1A_STRT' | grep -o '0x[0-9A-Fa-f]+' | head -n1 || true) | |
PCROP1A_END=$(printf '%s' "$OB_DUMP" | grep -E 'PCROP1A_END ' | grep -o '0x[0-9A-Fa-f]+' | head -n1 || true) | |
echo "RDP=$RDP_VAL WRP1A_STRT=$WRP1A_STRT WRP1A_END=$WRP1A_END PCROP1A_STRT=$PCROP1A_STRT PCROP1A_END=$PCROP1A_END" | |
# --- Handle RDP lowering if requested --- | |
if [[ "$RDP_VAL" != "0xAA" ]]; then | |
echo "Device RDP is not 0xAA (current: $RDP_VAL). Flash/erase operations will be blocked or limited." >&2 | |
if (( AUTO_RDP )); then | |
if (( ! FORCE )); then | |
read -r -p "Lower RDP to 0xAA now (WILL MASS ERASE)? [y/N] " ans | |
[[ "$ans" =~ ^[Yy]$ ]] || { echo "Aborting per user choice"; exit 10; } | |
fi | |
echo "Lowering RDP to 0xAA (this triggers automatic mass erase)..." | |
$CLI -c port=SWD -ob RDP=0xAA || { echo "Failed to lower RDP" >&2; exit 11; } | |
RDP_LOWERED=1 | |
echo "Re-reading option bytes after RDP change..." | |
OB_DUMP="$($CLI -c port=SWD -ob displ 2>/dev/null || true)" | |
RDP_VAL=$(printf '%s\n' "$OB_DUMP" | grep -E '^[[:space:]]*RDP[[:space:]]*:' | grep -o '0x[0-9A-Fa-f]*' | head -n1 || true) | |
echo "New RDP=$RDP_VAL" | |
if [[ "$RDP_VAL" != "0xAA" ]]; then | |
echo "ERROR: RDP did not change to 0xAA; aborting." >&2 | |
exit 12 | |
fi | |
# After RDP lowering, other protections may still exist; continue normal path. | |
else | |
echo "RDP not lowered. Re-run with --auto-rdp (optionally --force-unlock) or manually:" | |
echo " $CLI -c port=SWD -ob RDP=0xAA" | |
exit 9 | |
fi | |
fi | |
NEEDS_UNLOCK=0 | |
if [[ "$WRP1A_STRT" == "0x0" && "$WRP1A_END" == "0x1" ]]; then | |
NEEDS_UNLOCK=1 | |
fi | |
if [[ "$PCROP1A_STRT" != "0x1FF" || "$PCROP1A_END" != "0x0" ]]; then | |
NEEDS_UNLOCK=1 | |
fi | |
if (( NEEDS_UNLOCK )); then | |
echo "Detected active write/PCROP protection over initial flash pages." | |
if (( DO_UNLOCK )); then | |
if (( ! FORCE )); then | |
read -r -p "Proceed to clear WRP1A/PCROP1A (destructive if data remained)? [y/N] " ans | |
[[ "$ans" =~ ^[Yy]$ ]] || { echo "Aborting per user choice"; exit 2; } | |
fi | |
echo "Clearing WRP1A + PCROP1A..." | |
# Disable by setting to empty ranges (start high, end low pattern used by vendor) | |
$CLI -c port=SWD -ob WRP1A_STRT=0x7F WRP1A_END=0x0 PCROP1A_STRT=0x1FF PCROP1A_END=0x0 || { | |
echo "Failed to clear protections" >&2; exit 1; } | |
echo "Re-reading option bytes..." | |
$CLI -c port=SWD -ob displ | grep -E 'WRP1A_|PCROP1A_' || true | |
else | |
echo "Protection present. Re-run with --unlock (and optionally --force-unlock) to clear, then flash." >&2 | |
exit 3 | |
fi | |
else | |
echo "No blocking protection detected." | |
fi | |
if (( RDP_LOWERED )); then | |
echo "Mass erase already performed implicitly by RDP lowering. Skipping explicit mass erase." | |
else | |
echo "Performing mass erase (safest before flashing replacement firmware)..." | |
$CLI -c port=SWD -e all || { echo "Mass erase failed" >&2; exit 1; } | |
fi | |
echo "Flashing firmware..." | |
$CLI -c port=SWD -w "$FIRMWARE_PATH" $ADDR -v -rst || { echo "Flash command failed" >&2; exit 1; } | |
echo "Verifying a short readback of vector table..." | |
$CLI -c port=SWD -r8 $ADDR 0x100 /tmp/_vt_check.bin || echo "Readback check failed (non-fatal if protection re-enabled)." | |
hexdump -C /tmp/_vt_check.bin | head -n5 || true | |
echo "Done. Device should enumerate under candleLight / gs_usb now." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment