- tools/python-embed/: embedded Python 3.12.10 runtime with bleak/pyserial preinstalled (git-ignored, fetched from the huaweicloud mirror); bats resolve Python in order: python-embed -> .venv-ble -> system - NSpyocd.exe + DFP pack copied into tools/ (git-ignored); flash_package.py prefers the in-tools copies - make_release.bat/ps1: one-click zip to out/mothercup_tools_<ts>.zip (staged in TEMP to avoid self-inclusion; zip excluded from git) - all bats converted to CRLF (LF-only breaks cmd block parsing and %~dp0) - verified: zip extracted outside the repo runs ble_ota --uart end-to-end (PASS, 27 kB/s) and flash_package via the bundled NSpyocd - docs: dev log section 49, AGENTS.md tools section + heap 18KB fix
120 lines
4.4 KiB
Python
120 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
flash_package.py - erase the chip and program the merged package via
|
|
NS-LINK (NSpyocd). Streams the vendor tool's output with the vendor
|
|
banner replaced by the CAIIC one (the banner is baked into the
|
|
PyInstaller-packed NSpyocd.exe and cannot be edited).
|
|
|
|
usage: flash_package.bat [image]
|
|
[image] optional, default: tools/out/mothercup_ble_prod.hex
|
|
|
|
Connect NS-LINK to SWD (PA4/PA5) and the reset pin first, and CLOSE
|
|
Keil (it holds the probe).
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
# NSpyocd is a PyInstaller-packed pyocd: its banner is inside the
|
|
# compressed bundle, so we rebrand it on the fly. The console encoding
|
|
# varies (UTF-8 vs GBK), match the byte pattern in both.
|
|
BANNER_PATTERNS = [
|
|
("国民技术NSLINK上位机".encode("utf-8"), b"CAIIC NSLINK UMP"),
|
|
("国民技术NSLINK上位机".encode("gbk"), b"CAIIC NSLINK UMP"),
|
|
]
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
TOOLS = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
# Prefer the copies shipped inside tools/ (portable production package);
|
|
# fall back to the repo's nations-tec/ SDK layout (developer machine).
|
|
_local_pyocd = os.path.join(TOOLS, "NSpyocd", "NSpyocd.exe")
|
|
_local_pack = os.path.join(TOOLS, "N32WB03x_DFP.1.4.0.pack")
|
|
PYOCD = _local_pyocd if os.path.isfile(_local_pyocd) else \
|
|
os.path.join(ROOT, "nations-tec", "N32WB03x_SDK_V2.0.0",
|
|
"utilities", "dfu", "NSpyocd", "NSpyocd.exe")
|
|
# The builtin "n32wb031" target only maps 256KB of flash (fails at
|
|
# 0x01040000 on the dual-bank package); the DFP pack target
|
|
# "n32wb031keq6_2" maps the full 512KB of the KEQ6-2 we actually use.
|
|
PACK = _local_pack if os.path.isfile(_local_pack) else \
|
|
os.path.join(ROOT, "nations-tec", "N32WB03x_DFP.1.4.0.pack")
|
|
TARGET = "n32wb031keq6_2"
|
|
|
|
|
|
def run_step(args):
|
|
"""Run one NSpyocd step, streaming output with the banner replaced.
|
|
Reads in small chunks (not line-buffered) so the tool's \\r-based
|
|
progress bar redraws live. Returns the process exit code."""
|
|
proc = subprocess.Popen([PYOCD] + args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
out = sys.stdout.buffer
|
|
keep = max(len(p) for p, _ in BANNER_PATTERNS) - 1
|
|
tail = b""
|
|
while True:
|
|
chunk = proc.stdout.read(256)
|
|
if not chunk:
|
|
break
|
|
buf = tail + chunk
|
|
# replace on the COMBINED buffer first (a pattern straddling the
|
|
# chunk boundary only exists contiguously here), then hold back
|
|
# the last maxpat-1 bytes so a pattern split by the next chunk
|
|
# is still seen whole
|
|
for old, new in BANNER_PATTERNS:
|
|
buf = buf.replace(old, new)
|
|
emit, tail = buf[:-keep], buf[-keep:]
|
|
out.write(emit)
|
|
out.flush()
|
|
for old, new in BANNER_PATTERNS:
|
|
tail = tail.replace(old, new)
|
|
out.write(tail)
|
|
out.flush()
|
|
return proc.wait()
|
|
|
|
|
|
def main():
|
|
image = sys.argv[1] if len(sys.argv) > 1 else \
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"out", "mothercup_ble_prod.hex")
|
|
if not os.path.isfile(image):
|
|
print("%s not found - run tools\\make_package.bat first" % image)
|
|
return 1
|
|
|
|
# -M under-reset: connect while holding the chip in reset
|
|
# -f 1000000: 1 MHz SWD clock (slow but tolerant of wiring)
|
|
print("Erasing chip...", flush=True)
|
|
rc = run_step(["erase", "--chip", "--pack", PACK, "-M", "under-reset",
|
|
"-f", "1000000", "-t", TARGET])
|
|
if rc != 0:
|
|
return fail()
|
|
|
|
print("Programming %s ..." % image, flush=True)
|
|
# -O smart_flash=false: with the pack target the pre-program diff pass
|
|
# faults reading 0x01000000; programming+verify itself is fine.
|
|
rc = run_step(["load", "--pack", PACK, "-M", "under-reset",
|
|
"-f", "1000000", "-t", TARGET,
|
|
"-O", "smart_flash=false", image])
|
|
if rc != 0:
|
|
return fail()
|
|
|
|
print("Program Finish!")
|
|
return 0
|
|
|
|
|
|
def fail():
|
|
print()
|
|
print("*** FLASH FAILED ***")
|
|
print("Troubleshooting:")
|
|
print(" 1. Close Keil / any tool holding the probe, replug NS-LINK USB")
|
|
print(" 2. List probes: \"%s\" list" % PYOCD)
|
|
print(" 3. Check wiring: SWDIO=PA5 SWCLK=PA4 GND and the RESET pin")
|
|
print(" 4. If several probes are attached, add -u <probe_uid> "
|
|
"to the commands above")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|