mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-30 18:56:16 +00:00
Lean: update Semantics modules, add new numerics/physics data files Hardware: update FPGA bitstreams (tangnano9k_uart_loopback) Infra: k3s-flake tests, netcup-vps configuration, VCN compute substrate Docs: ARCHITECTURE, specs, citation updates
249 lines
8.1 KiB
Python
Executable file
249 lines
8.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
||
"""Probe all serial ports simultaneously for Tang Nano 9K UART output.
|
||
|
||
Targets the 0xAA test byte from the uart_test design.
|
||
Handles both BL702 and FT2232H board variants by scanning every
|
||
available /dev/ttyUSB* and /dev/ttyACM* device.
|
||
|
||
Usage:
|
||
python3 probe_serial_ports.py # auto-detect all ports
|
||
python3 probe_serial_ports.py --baud 9600 # override baud rate
|
||
python3 probe_serial_ports.py --ports /dev/ttyUSB0 /dev/ttyUSB1
|
||
"""
|
||
|
||
import argparse
|
||
import glob
|
||
import os
|
||
import select
|
||
import sys
|
||
import termios
|
||
import time
|
||
import tty
|
||
|
||
BAUD_FLAGS = {
|
||
9600: termios.B9600,
|
||
19200: termios.B19200,
|
||
38400: termios.B38400,
|
||
57600: termios.B57600,
|
||
115200: termios.B115200,
|
||
}
|
||
|
||
# ANSI colours for terminal output
|
||
GREEN = "\033[32m"
|
||
YELLOW = "\033[33m"
|
||
RED = "\033[31m"
|
||
CYAN = "\033[36m"
|
||
DIM = "\033[2m"
|
||
BOLD = "\033[1m"
|
||
RESET = "\033[0m"
|
||
|
||
|
||
def discover_ports() -> list[str]:
|
||
"""Find all candidate serial device nodes."""
|
||
patterns = ["/dev/ttyUSB*", "/dev/ttyACM*"]
|
||
ports = []
|
||
for pat in patterns:
|
||
ports.extend(sorted(glob.glob(pat)))
|
||
return ports
|
||
|
||
|
||
def configure_raw(fd: int, baud: int) -> list[int]:
|
||
"""Set fd to raw 8N1 at the given baud rate. Returns old attrs."""
|
||
old = termios.tcgetattr(fd)
|
||
tty.setraw(fd)
|
||
attrs = termios.tcgetattr(fd)
|
||
baud_flag = BAUD_FLAGS[baud]
|
||
attrs[4] = baud_flag # ispeed
|
||
attrs[5] = baud_flag # ospeed
|
||
attrs[2] |= termios.CLOCAL | termios.CREAD
|
||
attrs[2] &= ~termios.CSTOPB
|
||
attrs[2] &= ~termios.PARENB
|
||
attrs[2] &= ~termios.CSIZE
|
||
attrs[2] |= termios.CS8
|
||
attrs[6][termios.VMIN] = 0
|
||
attrs[6][termios.VTIME] = 0
|
||
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
||
return old
|
||
|
||
|
||
def decode_byte(b: int) -> str:
|
||
"""Human-readable decode of one byte in context of the UART test."""
|
||
if b == 0xAA:
|
||
return f"{GREEN}0xAA ✓ UART test byte{RESET}"
|
||
if b == 0x55:
|
||
return f"{YELLOW}0x55 (complement — TX/RX might be swapped){RESET}"
|
||
if b == 0x00:
|
||
return f"{DIM}0x00 (break / noise){RESET}"
|
||
if b == 0xFF:
|
||
return f"{DIM}0xFF (idle line / noise){RESET}"
|
||
return f"0x{b:02X}"
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(
|
||
description="Probe all serial ports for Tang Nano 9K UART test output (0xAA)."
|
||
)
|
||
parser.add_argument(
|
||
"--ports",
|
||
nargs="*",
|
||
help="Explicit port list; default: auto-discover /dev/ttyUSB* /dev/ttyACM*",
|
||
)
|
||
parser.add_argument(
|
||
"--baud",
|
||
type=int,
|
||
default=115200,
|
||
choices=sorted(BAUD_FLAGS),
|
||
help="Baud rate (default: 115200)",
|
||
)
|
||
parser.add_argument(
|
||
"--seconds",
|
||
type=float,
|
||
default=10.0,
|
||
help="How long to listen (default: 10s)",
|
||
)
|
||
parser.add_argument(
|
||
"--all-bauds",
|
||
action="store_true",
|
||
help="Cycle through all supported baud rates (9600, 19200, 38400, 57600, 115200)",
|
||
)
|
||
args = parser.parse_args()
|
||
|
||
ports = args.ports or discover_ports()
|
||
if not ports:
|
||
print(f"{RED}No serial ports found.{RESET}")
|
||
print("Check: ls -la /dev/ttyUSB* /dev/ttyACM*")
|
||
print("Check: dmesg | grep -i 'tty\\|usb.*serial'")
|
||
return 1
|
||
|
||
baud_rates = sorted(BAUD_FLAGS) if args.all_bauds else [args.baud]
|
||
|
||
# Header
|
||
print(f"{BOLD}{'='*60}{RESET}")
|
||
print(f"{BOLD}Tang Nano 9K UART Serial Port Probe{RESET}")
|
||
print(f"{BOLD}{'='*60}{RESET}")
|
||
print(f" Ports: {', '.join(ports)}")
|
||
print(f" Baud rates: {', '.join(str(b) for b in baud_rates)}")
|
||
print(f" Duration: {args.seconds}s per baud rate")
|
||
print(f" Looking for: 0xAA (the UART test byte)")
|
||
print(f"{BOLD}{'='*60}{RESET}")
|
||
print()
|
||
|
||
found_port = None
|
||
found_baud = None
|
||
|
||
for baud in baud_rates:
|
||
print(f"{CYAN}── Probing at {baud} baud ──{RESET}")
|
||
|
||
# Open all ports
|
||
open_fds: dict[str, tuple[int, list[int]]] = {}
|
||
for port in ports:
|
||
try:
|
||
fd = os.open(port, os.O_RDONLY | os.O_NOCTTY | os.O_NONBLOCK)
|
||
old_attrs = configure_raw(fd, baud)
|
||
open_fds[port] = (fd, old_attrs)
|
||
print(f" {GREEN}✓{RESET} Opened {port}")
|
||
except OSError as e:
|
||
print(f" {RED}✗{RESET} Cannot open {port}: {e}")
|
||
|
||
if not open_fds:
|
||
print(f" {RED}No ports could be opened at {baud} baud.{RESET}")
|
||
continue
|
||
|
||
# Flush any stale data
|
||
time.sleep(0.1)
|
||
for port, (fd, _) in open_fds.items():
|
||
try:
|
||
while os.read(fd, 4096):
|
||
pass
|
||
except BlockingIOError:
|
||
pass
|
||
|
||
print(f" Listening for {args.seconds}s ...")
|
||
deadline = time.monotonic() + args.seconds
|
||
port_stats: dict[str, dict] = {
|
||
port: {"total": 0, "aa_count": 0, "bytes": bytearray()}
|
||
for port in open_fds
|
||
}
|
||
|
||
try:
|
||
while time.monotonic() < deadline:
|
||
fds = [fd for fd, _ in open_fds.values()]
|
||
ready, _, _ = select.select(fds, [], [], 0.25)
|
||
if not ready:
|
||
continue
|
||
for port, (fd, _) in open_fds.items():
|
||
if fd not in ready:
|
||
continue
|
||
try:
|
||
data = os.read(fd, 256)
|
||
except BlockingIOError:
|
||
continue
|
||
if not data:
|
||
continue
|
||
stats = port_stats[port]
|
||
stats["total"] += len(data)
|
||
stats["bytes"].extend(data)
|
||
for byte in data:
|
||
if byte == 0xAA:
|
||
stats["aa_count"] += 1
|
||
ts = time.strftime("%H:%M:%S")
|
||
print(
|
||
f" [{ts}] {BOLD}{port}{RESET}: {decode_byte(byte)}"
|
||
)
|
||
finally:
|
||
for port, (fd, old_attrs) in open_fds.items():
|
||
try:
|
||
termios.tcsetattr(fd, termios.TCSANOW, old_attrs)
|
||
except Exception:
|
||
pass
|
||
os.close(fd)
|
||
|
||
# Summary for this baud rate
|
||
print()
|
||
print(f" {BOLD}Summary at {baud} baud:{RESET}")
|
||
for port, stats in port_stats.items():
|
||
total = stats["total"]
|
||
aa = stats["aa_count"]
|
||
if total == 0:
|
||
print(f" {port}: {DIM}no data received{RESET}")
|
||
else:
|
||
raw_hex = " ".join(f"{b:02X}" for b in stats["bytes"][:32])
|
||
if len(stats["bytes"]) > 32:
|
||
raw_hex += " ..."
|
||
status = f"{GREEN}✓ MATCH{RESET}" if aa > 0 else f"{YELLOW}no 0xAA{RESET}"
|
||
print(f" {port}: {total} bytes, {aa} × 0xAA {status}")
|
||
print(f" raw: {raw_hex}")
|
||
if aa > 0 and found_port is None:
|
||
found_port = port
|
||
found_baud = baud
|
||
print()
|
||
|
||
# Final verdict
|
||
print(f"{BOLD}{'='*60}{RESET}")
|
||
if found_port:
|
||
print(f"{GREEN}{BOLD}UART FOUND!{RESET}")
|
||
print(f" Port: {found_port}")
|
||
print(f" Baud: {found_baud}")
|
||
print()
|
||
print(f" To monitor continuously:")
|
||
print(f" python3 monitor_uart.py {found_port} --baud {found_baud}")
|
||
print(f" Or with pyserial:")
|
||
print(f" python3 -m serial.tools.miniterm {found_port} {found_baud}")
|
||
else:
|
||
print(f"{RED}{BOLD}No UART data detected on any port.{RESET}")
|
||
print()
|
||
print(f" Troubleshooting checklist:")
|
||
print(f" 1. Is the bitstream flashed? (check LEDs toggling)")
|
||
print(f" 2. Is uart_tx mapped to pin 17? (check uart_test.cst)")
|
||
print(f" 3. Try --all-bauds to scan all baud rates")
|
||
print(f" 4. Check dmesg for USB device enumeration:")
|
||
print(f" dmesg | grep -i 'tty\\|usb.*serial\\|bl702'")
|
||
print(f" 5. On BL702 boards, the UART may need the BL702")
|
||
print(f" firmware to be in passthrough mode")
|
||
print(f"{BOLD}{'='*60}{RESET}")
|
||
|
||
return 0 if found_port else 2
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|