fix: use xsct (not Vivado) for JTAG check + normalize COM>=10 ports on Windows
**JTAG check**: Switch from Vivado to xsct - xsct is already proven to work (JTAG reboot succeeds) - Much lighter than Vivado (no GUI infrastructure / open_hw_manager) - Uses -eval inline TCL: connect; targets; disconnect; quit - Removes dead tempfile/Path/os imports **Windows COM port**: Add normalize_port() to serial_monitor.py - COM ports >= 10 require NT namespace prefix (\.\COM11) - Without it: PermissionError(13, '拒绝访问。') - Applied at all 7 call sites: serial_monitor (5), reboot_manager, widgets
This commit is contained in:
+2
-1
@@ -1075,7 +1075,8 @@ class UartMonitorWindow(ctk.CTkToplevel):
|
|||||||
def _bg_open() -> None:
|
def _bg_open() -> None:
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
ser = serial.Serial(self._port, self._baudrate, timeout=1)
|
from serial_monitor import normalize_port
|
||||||
|
ser = serial.Serial(normalize_port(self._port), self._baudrate, timeout=1)
|
||||||
ser.close()
|
ser.close()
|
||||||
self.after(0, self._on_open_success)
|
self.after(0, self._on_open_success)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -125,9 +125,10 @@ def reboot_via_serial(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import serial
|
import serial
|
||||||
|
from serial_monitor import normalize_port
|
||||||
|
|
||||||
with serial.Serial(
|
with serial.Serial(
|
||||||
config.serial_port,
|
normalize_port(config.serial_port),
|
||||||
config.serial_baudrate,
|
config.serial_baudrate,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
) as ser:
|
) as ser:
|
||||||
|
|||||||
+27
-6
@@ -7,8 +7,10 @@ Provides UartMonitor for continuous background monitoring.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
@@ -16,6 +18,26 @@ import serial
|
|||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_port(port: str) -> str:
|
||||||
|
"""Normalize a serial port name for the current platform.
|
||||||
|
|
||||||
|
On Windows, COM ports numbered >= 10 require the NT namespace
|
||||||
|
prefix (\\\\.\\COM11). Without it, CreateFile fails with
|
||||||
|
'Access denied' or 'file not found'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Raw port name from config or detection.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Normalized port name suitable for serial.Serial().
|
||||||
|
"""
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
match = re.match(r"^COM(\d+)$", port, re.IGNORECASE)
|
||||||
|
if match and int(match.group(1)) >= 10:
|
||||||
|
return rf"\\.\{port}"
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BootInfo:
|
class BootInfo:
|
||||||
"""Parsed information extracted from Zynq boot output."""
|
"""Parsed information extracted from Zynq boot output."""
|
||||||
@@ -168,7 +190,7 @@ def check_uart_available(
|
|||||||
|
|
||||||
# 3. Try to open and read
|
# 3. Try to open and read
|
||||||
try:
|
try:
|
||||||
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
with serial.Serial(normalize_port(port), baudrate, timeout=timeout) as ser:
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
while ser.in_waiting:
|
while ser.in_waiting:
|
||||||
@@ -206,7 +228,7 @@ def read_serial_stream(
|
|||||||
serial.SerialException: If the port cannot be opened.
|
serial.SerialException: If the port cannot be opened.
|
||||||
"""
|
"""
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
with serial.Serial(normalize_port(port), baudrate, timeout=timeout) as ser:
|
||||||
# Clear any pending data
|
# Clear any pending data
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
while ser.in_waiting:
|
while ser.in_waiting:
|
||||||
@@ -242,8 +264,7 @@ def test_serial_version(
|
|||||||
import serial
|
import serial
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with serial.Serial(port, baudrate, timeout=timeout) as ser:
|
with serial.Serial(normalize_port(port), baudrate, timeout=timeout) as ser:
|
||||||
# Clear buffers
|
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
ser.reset_output_buffer()
|
ser.reset_output_buffer()
|
||||||
|
|
||||||
@@ -384,7 +405,7 @@ class UartMonitor:
|
|||||||
|
|
||||||
# Quick port check before starting thread
|
# Quick port check before starting thread
|
||||||
try:
|
try:
|
||||||
test_ser = serial.Serial(self._port, self._baudrate, timeout=1)
|
test_ser = serial.Serial(normalize_port(self._port), self._baudrate, timeout=1)
|
||||||
test_ser.close()
|
test_ser.close()
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
if self.on_error:
|
if self.on_error:
|
||||||
@@ -411,7 +432,7 @@ class UartMonitor:
|
|||||||
"""Main read loop running in background thread."""
|
"""Main read loop running in background thread."""
|
||||||
try:
|
try:
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
self._port,
|
normalize_port(self._port),
|
||||||
self._baudrate,
|
self._baudrate,
|
||||||
timeout=self._timeout,
|
timeout=self._timeout,
|
||||||
)
|
)
|
||||||
|
|||||||
+25
-30
@@ -8,17 +8,14 @@ recognition on the JTAG chain.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from config_manager import Config
|
from config_manager import Config
|
||||||
from vitis_checker import get_vivado_path, get_hw_server_path, build_tool_command
|
from vitis_checker import get_xsct_path, get_hw_server_path, build_tool_command
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -102,35 +99,32 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
|
|
||||||
hw_proc: subprocess.Popen | None = None
|
hw_proc: subprocess.Popen | None = None
|
||||||
try:
|
try:
|
||||||
# Start hw_server in the background (it daemonises own I/O)
|
# Start hw_server in the background (needed for xsct connect)
|
||||||
hw_proc = subprocess.Popen(
|
hw_proc = subprocess.Popen(
|
||||||
build_tool_command(hw_server_path),
|
build_tool_command(hw_server_path),
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
# Give hw_server a moment to bind its port
|
time.sleep(2) # let hw_server bind its port
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# ── 2. Find vivado ────────────────────────────────────
|
# ── 2. Find xsct (much lighter than Vivado for JTAG ops) ──
|
||||||
vivado_path = get_vivado_path(xilinx_root=xilinx_root)
|
xsct_path = get_xsct_path(xilinx_root=xilinx_root)
|
||||||
if not vivado_path:
|
if not xsct_path:
|
||||||
return ZynqCheckResult(
|
return ZynqCheckResult(
|
||||||
devices=[], jtag_chain=[], is_present=False,
|
devices=[], jtag_chain=[], is_present=False,
|
||||||
ps_detected=False, pl_detected=False,
|
ps_detected=False, pl_detected=False,
|
||||||
hw_server_url=hw_server_url,
|
hw_server_url=hw_server_url,
|
||||||
error="Vivado not found",
|
error="xsct not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── 3. Create and run JTAG query TCL ──────────────────
|
# ── 3. Run JTAG query TCL via xsct ────────────────────
|
||||||
tcl_content = _build_jtag_query_tcl()
|
tcl_content = _build_jtag_query_tcl()
|
||||||
tcl_path = Path(tempfile.gettempdir()) / f"zynq_jtag_check_{os.getpid()}.tcl"
|
|
||||||
tcl_path.write_text(tcl_content)
|
|
||||||
|
|
||||||
if callback:
|
if callback:
|
||||||
callback("progress", "Scanning JTAG chain...")
|
callback("progress", "Scanning JTAG chain...")
|
||||||
|
|
||||||
cmd = build_tool_command(
|
cmd = build_tool_command(
|
||||||
vivado_path, "-mode", "batch", "-source", str(tcl_path),
|
xsct_path, "-eval", tcl_content,
|
||||||
)
|
)
|
||||||
python_timeout = config.step_timeouts.get("check_env", 120)
|
python_timeout = config.step_timeouts.get("check_env", 120)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
@@ -139,7 +133,6 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
text=True,
|
text=True,
|
||||||
timeout=python_timeout,
|
timeout=python_timeout,
|
||||||
)
|
)
|
||||||
tcl_path.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
output = result.stdout + result.stderr
|
||||||
jtag_chain = _parse_jtag_chain(output)
|
jtag_chain = _parse_jtag_chain(output)
|
||||||
@@ -193,23 +186,25 @@ def check_zynq_jtag(config: Config, callback: Callable | None = None) -> ZynqChe
|
|||||||
|
|
||||||
|
|
||||||
def _build_jtag_query_tcl() -> str:
|
def _build_jtag_query_tcl() -> str:
|
||||||
"""Build TCL script for JTAG device query.
|
"""Build TCL script for xsct JTAG device query.
|
||||||
|
|
||||||
|
Uses xsct (not Vivado) because:
|
||||||
|
- xsct is much lighter (no GUI infrastructure)
|
||||||
|
- xsct JTAG reboot already works on this system
|
||||||
|
- connect + targets lists all devices on the JTAG chain
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
TCL script content as string.
|
TCL script content as string (semicolon-delimited for -eval mode).
|
||||||
"""
|
"""
|
||||||
return """
|
return (
|
||||||
open_hw_manager
|
"connect; "
|
||||||
connect_hw_server
|
'puts "===JTAG_START==="; '
|
||||||
open_hw_target
|
"set all [targets]; "
|
||||||
puts "===JTAG_START==="
|
'foreach t $all { puts "DEVICE:$t" }; '
|
||||||
foreach dev [get_hw_devices] {
|
'puts "===JTAG_END==="; '
|
||||||
set name [get_property NAME $dev]
|
"disconnect; "
|
||||||
puts "DEVICE:$name"
|
"quit"
|
||||||
}
|
)
|
||||||
puts "===JTAG_END==="
|
|
||||||
quit
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
def _parse_jtag_chain(output: str) -> list[JtagDevice]:
|
||||||
|
|||||||
Reference in New Issue
Block a user