|
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | 15 | import sys |
| 16 | +import subprocess |
| 17 | +import json |
| 18 | +import os |
| 19 | +import shutil |
| 20 | +import site |
16 | 21 | from platform import system |
17 | 22 | from os import makedirs |
18 | 23 | from os.path import isdir, join, basename |
@@ -52,6 +57,37 @@ def BeforeUpload(target, source, env): # pylint: disable=W0613,W0621 |
52 | 57 | board = env.BoardConfig() |
53 | 58 | variant = board.get("build.variant", "") |
54 | 59 |
|
| 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 | + |
55 | 91 | env.Replace( |
56 | 92 | AR="arm-none-eabi-ar", |
57 | 93 | AS="arm-none-eabi-as", |
@@ -395,6 +431,49 @@ def _jlink_cmd_script(env, source): |
395 | 431 | ) |
396 | 432 | upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")] |
397 | 433 |
|
| 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 | + |
398 | 477 | elif upload_protocol in debug_tools: |
399 | 478 | openocd_args = [ |
400 | 479 | "-d%d" % (2 if int(ARGUMENTS.get("PIOVERBOSE", 0)) else 1) |
|
0 commit comments