Skip to content

Commit 32b832f

Browse files
Add pyOCD upload support
1 parent 3a83161 commit 32b832f

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

boards/seeed-xiao-nrf54l15.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
"maximum_ram_size": 262144,
2727
"maximum_size": 1572864,
2828
"protocol": "cmsis-dap",
29+
"pyocd_target": "nrf54l",
30+
"pyocd_frequency": 4000000,
2931
"protocols": [
3032
"cmsis-dap",
33+
"pyocd",
3134
"jlink"
3235
]
3336
},

builder/board_build/nrf/nrf_build.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
# limitations under the License.
1414

1515
import sys
16+
import subprocess
17+
import json
18+
import os
19+
import shutil
20+
import site
1621
from platform import system
1722
from os import makedirs
1823
from os.path import isdir, join, basename
@@ -52,6 +57,37 @@ def BeforeUpload(target, source, env): # pylint: disable=W0613,W0621
5257
board = env.BoardConfig()
5358
variant = board.get("build.variant", "")
5459

60+
61+
def _ensure_pyocd_installed():
62+
# Always use the forked pyOCD with nRF54LM20A support, regardless of MCU.
63+
pyocd_spec = "pyocd @ git+https://github.com/StarSphere-1024/pyOCD.git@nrf54lm20a"
64+
expected_url_substring = "github.com/StarSphere-1024/pyOCD"
65+
66+
def _installed_pyocd_is_expected() -> bool:
67+
try:
68+
import subprocess
69+
python_exe = sys.executable # Use the current Python executable
70+
output = subprocess.check_output([python_exe, "-m", "pyocd", "list", "--targets"]).decode("utf-8")
71+
return "nrf54lm20a" in output.lower()
72+
except (ImportError, subprocess.CalledProcessError, Exception):
73+
return False
74+
75+
if _installed_pyocd_is_expected():
76+
return
77+
78+
python_exe = env.subst("$PYTHONEXE")
79+
print("[INFO] Installing pyOCD from fork...")
80+
subprocess.check_call([python_exe, "-m", "pip", "install", "--upgrade", "pip"])
81+
subprocess.check_call([
82+
python_exe,
83+
"-m",
84+
"pip",
85+
"install",
86+
"--upgrade",
87+
pyocd_spec,
88+
"libusb",
89+
])
90+
5591
env.Replace(
5692
AR="arm-none-eabi-ar",
5793
AS="arm-none-eabi-as",
@@ -395,6 +431,49 @@ def _jlink_cmd_script(env, source):
395431
)
396432
upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")]
397433

434+
elif upload_protocol == "pyocd":
435+
_ensure_pyocd_installed()
436+
437+
pyocd_target = board.get("upload.pyocd_target")
438+
if not pyocd_target:
439+
mcu = (board.get("build.mcu") or "").strip()
440+
441+
# Best-effort mapping from board MCU name to pyOCD target name.
442+
# Boards can override this via `upload.pyocd_target`.
443+
mcu_to_pyocd_target = {
444+
"nrf54lm20a": "nrf54lm20a",
445+
# nRF54L15 uses the generic nRF54L family target in pyOCD.
446+
"nrf54l15": "nrf54l",
447+
# Most nRF52 boards use the generic nRF52 family target.
448+
"nrf52840": "nrf52",
449+
}
450+
451+
if mcu in mcu_to_pyocd_target:
452+
pyocd_target = mcu_to_pyocd_target[mcu]
453+
else:
454+
pyocd_target = "nrf54l"
455+
print(
456+
"Warning! Unknown MCU '%s' for pyOCD; defaulting to '%s'. "
457+
"Set 'upload.pyocd_target' in the board JSON if needed." % (mcu, pyocd_target)
458+
)
459+
460+
pyocd_frequency = str(board.get("upload.pyocd_frequency", 4_000_000))
461+
462+
env.Replace(
463+
UPLOADER="$PYTHONEXE",
464+
UPLOADERFLAGS=[
465+
"-m",
466+
"pyocd",
467+
"flash",
468+
"--target",
469+
pyocd_target,
470+
"--frequency",
471+
pyocd_frequency,
472+
],
473+
UPLOADCMD='"$UPLOADER" $UPLOADERFLAGS "$SOURCE"',
474+
)
475+
upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")]
476+
398477
elif upload_protocol in debug_tools:
399478
openocd_args = [
400479
"-d%d" % (2 if int(ARGUMENTS.get("PIOVERBOSE", 0)) else 1)

0 commit comments

Comments
 (0)