#!/usr/bin/env python3
"""
J-Link flash utility for AddVantage PPG firmware.
Cross-platform (Windows/Linux/macOS)
"""
import subprocess
import sys
import os
import tempfile
import platform
from pathlib import Path
# J-Link settings for SKEAZ128MLK4
DEVICE = "SKEAZ128XXX4"
INTERFACE = "SWD"
SPEED = "4000"
JLINK_SERIAL = "50114603" # Force specific J-Link to avoid GUI picker
[docs]
def find_jlink() -> str:
"""Find J-Link Commander executable."""
if platform.system() == "Windows":
paths = [
r"C:\Program Files\SEGGER\JLink\JLink.exe",
r"C:\Program Files (x86)\SEGGER\JLink\JLink.exe",
]
for p in paths:
if os.path.exists(p):
return p
# Try PATH
return "JLink.exe"
elif platform.system() == "Darwin":
paths = [
"/Applications/SEGGER/JLink/JLinkExe",
"/usr/local/bin/JLinkExe",
]
for p in paths:
if os.path.exists(p):
return p
return "JLinkExe"
else: # Linux
paths = [
"/opt/SEGGER/JLink/JLinkExe",
"/usr/bin/JLinkExe",
]
for p in paths:
if os.path.exists(p):
return p
return "JLinkExe"
[docs]
def flash_firmware(hex_path: str, verify: bool = True) -> bool:
"""
Flash firmware using J-Link Commander.
Args:
hex_path: Path to .hex file
verify: Whether to verify after flashing
Returns:
True if successful, False otherwise
"""
jlink = find_jlink()
hex_path = str(Path(hex_path).resolve())
# Normalize path for J-Link (use forward slashes)
hex_path_normalized = hex_path.replace("\\", "/")
# Create command script
commands = [
"r", # Reset target
"unlock kinetis", # Unlock flash (if locked)
"erase", # Erase flash
"unlock kinetis", # Unlock again after erase
f"loadfile {hex_path_normalized}", # Load hex file
]
if verify:
commands.append(f"verifybin {hex_path_normalized} 0")
commands.extend([
"r", # Reset
"g", # Go (start execution)
"qc", # Quit with close
])
# Write command file
with tempfile.NamedTemporaryFile(mode='w', suffix='.jlink', delete=False) as f:
f.write('\n'.join(commands))
cmd_file = f.name
try:
# Run J-Link Commander
result = subprocess.run(
[
jlink,
"-device", DEVICE,
"-if", INTERFACE,
"-speed", SPEED,
"-autoconnect", "1",
"-SelectEmuBySN", JLINK_SERIAL,
"-CommanderScript", cmd_file,
],
capture_output=True,
text=True,
timeout=60
)
print(result.stdout)
if result.returncode != 0:
print(f"J-Link error: {result.stderr}", file=sys.stderr)
return False
# Check for success indicators
if "O.K." in result.stdout or "Verify successful" in result.stdout:
return True
if "Error" in result.stdout or "Failed" in result.stdout:
return False
return True
except subprocess.TimeoutExpired:
print("J-Link timeout", file=sys.stderr)
return False
except FileNotFoundError:
print(f"J-Link not found at: {jlink}", file=sys.stderr)
return False
finally:
os.unlink(cmd_file)
[docs]
def reset_target() -> bool:
"""Reset the target MCU."""
jlink = find_jlink()
commands = ["r", "g", "qc"]
with tempfile.NamedTemporaryFile(mode='w', suffix='.jlink', delete=False) as f:
f.write('\n'.join(commands))
cmd_file = f.name
try:
result = subprocess.run(
[jlink, "-device", DEVICE, "-if", INTERFACE, "-speed", SPEED,
"-autoconnect", "1", "-SelectEmuBySN", JLINK_SERIAL, "-CommanderScript", cmd_file],
capture_output=True,
text=True,
timeout=30
)
return result.returncode == 0
except Exception as e:
print(f"Reset failed: {e}", file=sys.stderr)
return False
finally:
os.unlink(cmd_file)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='Flash firmware via J-Link')
parser.add_argument('hex_file', help='Path to .hex file')
parser.add_argument('--no-verify', action='store_true', help='Skip verification')
parser.add_argument('--reset-only', action='store_true', help='Just reset the target')
args = parser.parse_args()
if args.reset_only:
success = reset_target()
else:
if not Path(args.hex_file).exists():
print(f"Error: File not found: {args.hex_file}")
sys.exit(1)
success = flash_firmware(args.hex_file, verify=not args.no_verify)
sys.exit(0 if success else 1)