Skip to content

Commit a6c1eba

Browse files
committed
tooling: Add make deploy-usb for DAPLink mass-storage flashing.
1 parent 1f060e6 commit a6c1eba

3 files changed

Lines changed: 160 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ make firmware # Clone micropython-steami (if needed), link local drivers,
195195
make firmware-update # Refresh the MicroPython clone and board-specific submodules
196196
make deploy # Flash firmware via pyOCD (default)
197197
make deploy-openocd # Flash firmware via OpenOCD (alternative)
198+
make deploy-usb # Flash firmware via DAPLink USB mass-storage (alternative)
198199
make run SCRIPT=lib/steami_config/examples/show_config.py # Run with live output
199200
make deploy-script SCRIPT=lib/.../calibrate_magnetometer.py # Deploy as main.py for autonomous use
200201
make run-main # Re-execute the deployed main.py

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ deploy-pyocd: $(MPY_DIR) ## Flash firmware via pyOCD (CMSIS-DAP)
134134
deploy-openocd: $(MPY_DIR) ## Flash firmware via OpenOCD
135135
$(MAKE) -C $(STM32_DIR) BOARD=$(BOARD) deploy-openocd
136136

137+
.PHONY: deploy-usb
138+
deploy-usb: $(MPY_DIR) ## Flash firmware via DAPLink USB mass-storage
139+
@$(PYTHON) scripts/deploy_usb.py $(STM32_DIR)/build-$(BOARD)/firmware.bin
140+
137141
.PHONY: run
138142
run: ## Run a script on the board with live output (SCRIPT=path/to/file.py)
139143
@if [ -z "$(SCRIPT)" ]; then \

scripts/deploy_usb.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""Deploy MicroPython firmware to a STeaMi board via DAPLink USB mass-storage.
2+
3+
Detects the STeaMi volume by its label across Linux, macOS, and Windows,
4+
copies the firmware .bin to it, and lets DAPLink auto-reset the target.
5+
6+
Usage:
7+
python scripts/deploy_usb.py path/to/firmware.bin
8+
"""
9+
10+
import os
11+
import platform
12+
import shutil
13+
import subprocess
14+
import sys
15+
16+
VOLUME_LABEL = "STeaMi"
17+
18+
19+
def find_steami_linux():
20+
"""Find STeaMi mount point on Linux via findmnt."""
21+
try:
22+
result = subprocess.run(
23+
["findmnt", "-n", "-o", "TARGET", "-S", "LABEL=" + VOLUME_LABEL],
24+
capture_output=True,
25+
text=True,
26+
check=False,
27+
)
28+
if result.returncode == 0:
29+
mount = result.stdout.strip().split("\n")[0]
30+
return mount or None
31+
except FileNotFoundError:
32+
pass
33+
return None
34+
35+
36+
def find_steami_macos():
37+
"""Find STeaMi mount point on macOS (always /Volumes/<label>)."""
38+
path = "/Volumes/" + VOLUME_LABEL
39+
if os.path.isdir(path):
40+
return path
41+
return None
42+
43+
44+
def _find_steami_windows_powershell():
45+
"""Find STeaMi drive letter via PowerShell Get-Volume (preferred)."""
46+
ps_cmd = (
47+
"Get-Volume | Where-Object FileSystemLabel -eq '"
48+
+ VOLUME_LABEL
49+
+ "' | Select-Object -First 1 -ExpandProperty DriveLetter"
50+
)
51+
try:
52+
result = subprocess.run(
53+
["powershell", "-NoProfile", "-Command", ps_cmd],
54+
capture_output=True,
55+
text=True,
56+
check=False,
57+
)
58+
except FileNotFoundError:
59+
return None
60+
if result.returncode == 0:
61+
letter = result.stdout.strip()
62+
if letter:
63+
return letter + ":\\"
64+
return None
65+
66+
67+
def _find_steami_windows_wmic():
68+
"""Find STeaMi drive letter via legacy wmic (fallback for older Windows)."""
69+
try:
70+
result = subprocess.run(
71+
[
72+
"wmic",
73+
"logicaldisk",
74+
"where",
75+
"VolumeName='" + VOLUME_LABEL + "'",
76+
"get",
77+
"DeviceID",
78+
"/value",
79+
],
80+
capture_output=True,
81+
text=True,
82+
check=False,
83+
)
84+
except FileNotFoundError:
85+
return None
86+
if result.returncode == 0:
87+
for line in result.stdout.splitlines():
88+
if line.startswith("DeviceID="):
89+
drive = line.split("=", 1)[1].strip()
90+
if drive:
91+
return drive + "\\"
92+
return None
93+
94+
95+
def find_steami_windows():
96+
"""Find STeaMi drive letter on Windows.
97+
98+
Tries PowerShell Get-Volume first (works on all modern Windows),
99+
falls back to wmic for older systems where PowerShell is unavailable.
100+
"""
101+
return _find_steami_windows_powershell() or _find_steami_windows_wmic()
102+
103+
104+
def find_steami():
105+
"""Detect the STeaMi USB volume across platforms."""
106+
system = platform.system()
107+
if system == "Linux":
108+
return find_steami_linux()
109+
if system == "Darwin":
110+
return find_steami_macos()
111+
if system == "Windows":
112+
return find_steami_windows()
113+
print("Error: unsupported OS: " + system, file=sys.stderr)
114+
sys.exit(1)
115+
116+
117+
def main():
118+
if len(sys.argv) != 2:
119+
print("Usage: deploy_usb.py <firmware.bin>", file=sys.stderr)
120+
sys.exit(1)
121+
122+
firmware = sys.argv[1]
123+
if not os.path.isfile(firmware):
124+
print("Error: firmware binary not found: " + firmware, file=sys.stderr)
125+
print("Run 'make firmware' first.", file=sys.stderr)
126+
sys.exit(1)
127+
128+
mount = find_steami()
129+
if not mount or not os.path.isdir(mount):
130+
print(
131+
"Error: STeaMi board not found (no volume with label '"
132+
+ VOLUME_LABEL
133+
+ "').",
134+
file=sys.stderr,
135+
)
136+
print("Check that the board is connected and mounted.", file=sys.stderr)
137+
if platform.system() == "Windows":
138+
print(
139+
"On Windows, this requires PowerShell (Get-Volume) or wmic.",
140+
file=sys.stderr,
141+
)
142+
sys.exit(1)
143+
144+
print("Copying firmware to " + mount + "...")
145+
shutil.copy(firmware, mount)
146+
147+
# Best-effort flush on Unix (no-op on Windows)
148+
if hasattr(os, "sync"):
149+
os.sync()
150+
151+
print("Firmware deployed via USB. Board will reset automatically.")
152+
153+
154+
if __name__ == "__main__":
155+
main()

0 commit comments

Comments
 (0)