Your config is the same as mine working on regular sims, I just tried the card from the adapter and it fails on getting the ATR as well.
This is the script in python (AI-generated, manually tested). The output is ugly, but it basically captures the card’s ATR (same method you were using) and then sends a “SELECT MF” and looks at the card’s response. If the T=0 exchange works, the byte sent by the card should be a valid procedure/status byte (like 0xA4 ACK), and if it doesn’t work you get a 0xFF (or nothing). The veredict on successes may be wrong since the “WORKS” path is untested on a working card, but the output is always shown.
This is basically what tools like pysim do to read the files of a sim, and I have seen articles like thisusing it on the BP5. (If I use pysim on my setup with BP6 the script does not read/detect the card).
Thank you for taking time trying to help 
Since I’m new I can´t upload files so here it goes xD:
from __future__ import annotations
import argparse
import re
import sys
import time
from pathlib import Path
import serial
DEFAULT_PORT = "/dev/ttyACM0"
HEX_RE = re.compile(rb"0x([0-9a-fA-F]{2})")
PROMPT_RE = re.compile(rb"(HiZ|HDUART|HDPLXUART)>")
ANSI_RE = re.compile(rb"\x1b\[[0-9;:?]*[ -/]*[@-~]|\x1b[78]")
RX_LINE_RE = re.compile(rb"RX:(.*?)(?:(?:\r?\n)+(?:\S+>)|$)", re.DOTALL)
NODATA_PLACEHOLDER_RE = re.compile(rb"0x00\s*\(\s*No\s+data\s+to\s+read\s*\)", re.DOTALL)
def read_for(ser: serial.Serial, seconds: float) -> bytes:
end = time.monotonic() + seconds
chunks: list[bytes] = []
while time.monotonic() < end:
waiting = ser.in_waiting
if waiting:
chunks.append(ser.read(waiting))
else:
time.sleep(0.005)
waiting = ser.in_waiting
if waiting:
chunks.append(ser.read(waiting))
return b"".join(chunks)
def read_until_quiet(ser: serial.Serial, timeout: float, quiet: float = 0.2, minimum: float = 0.1) -> bytes:
end = time.monotonic() + timeout
min_end = time.monotonic() + minimum
last_data = time.monotonic()
data = bytearray()
while time.monotonic() < end:
waiting = ser.in_waiting
if waiting:
data.extend(ser.read(waiting))
last_data = time.monotonic()
elif time.monotonic() >= min_end and time.monotonic() - last_data >= quiet:
break
else:
time.sleep(0.005)
waiting = ser.in_waiting
if waiting:
data.extend(ser.read(waiting))
return bytes(data)
def read_until_prompt(ser: serial.Serial, timeout: float) -> bytes:
end = time.monotonic() + timeout
data = bytearray()
while time.monotonic() < end:
waiting = ser.in_waiting
if waiting:
data.extend(ser.read(waiting))
if PROMPT_RE.search(strip_ansi(bytes(data))):
break
else:
time.sleep(0.005)
waiting = ser.in_waiting
if waiting:
data.extend(ser.read(waiting))
return bytes(data)
def send_line(ser: serial.Serial, line: str) -> None:
ser.write(line.encode("ascii") + b"\r")
ser.flush()
def emit(data: bytes, log_file: Path) -> None:
with log_file.open("ab") as f:
f.write(data)
text = data.decode("utf-8", errors="replace")
if text:
print(text, end="")
def note(log_file: Path, text: str) -> None:
emit(f"\n>>> {text}\n".encode("ascii"), log_file)
def strip_ansi(data: bytes) -> bytes:
return ANSI_RE.sub(b"", data).replace(b"\r", b"\n")
def command_and_log(ser: serial.Serial, log_file: Path, line: str, timeout: float = 1.0,
quiet: float = 0.2, minimum: float = 0.1) -> bytes:
note(log_file, f"cmd: {line}")
send_line(ser, line)
data = read_until_quiet(ser, timeout, quiet=quiet, minimum=minimum)
emit(data, log_file)
return data
def bus_command_and_log(ser: serial.Serial, log_file: Path, line: str, read_seconds: float) -> bytes:
note(log_file, f"cmd: {line}")
send_line(ser, line)
data = read_for(ser, read_seconds)
emit(data, log_file)
return data
def configure_clock(ser: serial.Serial, log_file: Path, pin: str, clock: str, duty: str) -> None:
note(log_file, f"enable clock: IO{pin} {clock} {duty}")
send_line(ser, "G")
emit(read_until_quiet(ser, 1.0), log_file)
send_line(ser, pin)
emit(read_until_quiet(ser, 1.0), log_file)
send_line(ser, clock)
emit(read_until_quiet(ser, 1.0), log_file)
send_line(ser, duty)
emit(read_until_quiet(ser, 2.0, quiet=0.25, minimum=0.3), log_file)
def configure_hduart_mode(ser: serial.Serial, log_file: Path, bus: str,
parity: str = "even", stops: int = 2, baud: int = 9600) -> None:
note(log_file, f"configure HDUART mode: {baud} 8-{parity}-{stops} {bus}")
command_and_log(ser, log_file, f"m hduart -b {baud} -d 8 -p {parity} -s {stops} -l {bus}",
timeout=2.0, quiet=0.25, minimum=0.4)
def extract_hex_bytes(data: bytes) -> list[int]:
return [int(m.group(1), 16) for m in HEX_RE.finditer(data)]
def parse_atr_from(values: list[int], start: int) -> list[int] | None:
if start + 2 > len(values) or values[start] not in (0x3B, 0x3F):
return None
pos = start + 1
t0 = values[pos]
pos += 1
y = t0 >> 4
historical_len = t0 & 0x0F
tck_present = False
while y:
td = None
for bit in range(4):
if y & (1 << bit):
if pos >= len(values):
return None
value = values[pos]
pos += 1
if bit == 3:
td = value
if value & 0x0F:
tck_present = True
y = (td >> 4) if td is not None else 0
pos += historical_len
if tck_present:
pos += 1
if pos > len(values):
return None
atr = values[start:pos]
if tck_present: # reject a garbled/doubled capture: TCK must
x = 0 # make the XOR of T0..TCK zero
for b in atr[1:]:
x ^= b
if x != 0:
return None
return atr
def find_atr(values: list[int]) -> list[int] | None:
for idx, value in enumerate(values):
if value in (0x3B, 0x3F):
atr = parse_atr_from(values, idx)
if atr:
return atr
return None
def extract_rx_text(data: bytes) -> list[str]:
"""Return the raw text of each `RX:` line, ANSI stripped, for exact reading."""
data = strip_ansi(data)
out: list[str] = []
for m in RX_LINE_RE.finditer(data):
chunk = m.group(1)
text = chunk.decode("utf-8", errors="replace")
out.append(" ".join(text.split()))
return out
def extract_rx_real_bytes(data: bytes) -> list[int]:
"""Parse only real received bytes from RX: lines, dropping 'No data' 0x00s.
Bus Pirate prints `0x00 (No data to read)` for empty reads. We drop those so
the first *real* byte the card produced is not hidden behind placeholders.
"""
data = strip_ansi(data)
values: list[int] = []
for m in RX_LINE_RE.finditer(data):
chunk = m.group(1)
# Bus Pirate can wrap the placeholder over two terminal lines; remove
# the whole marker before extracting hex bytes.
chunk = NODATA_PLACEHOLDER_RE.sub(b"", chunk)
values.extend(int(x.group(1), 16) for x in HEX_RE.finditer(chunk))
return values
def fmt(values: list[int] | None) -> str:
if not values:
return "(none)"
return " ".join(f"{v:02X}" for v in values)
def cold_start(ser: serial.Serial, log_file: Path, args: argparse.Namespace, voltage: str) -> None:
if args.configure_mode:
configure_hduart_mode(ser, log_file, args.hduart_bus, args.parity, args.stops, args.sim_baud)
note(log_file, "cold-start cleanup")
for cmd in ("logic stop", "]", "a 2", "w", "p", "g", "l"):
command_and_log(ser, log_file, cmd, timeout=1.0)
note(log_file, f"wait with Vout off: {args.power_off_delay:.2f}s")
time.sleep(args.power_off_delay)
command_and_log(ser, log_file, f"W {voltage}", timeout=2.0)
command_and_log(ser, log_file, "P", timeout=1.0)
configure_clock(ser, log_file, args.clock_pin, args.clock, args.duty)
def read_atr_after_reset(ser: serial.Serial, log_file: Path, args: argparse.Namespace) -> list[int] | None:
command_and_log(ser, log_file, "[", timeout=1.0)
command_and_log(ser, log_file, "a 2", timeout=1.0)
time.sleep(args.reset_low)
note(log_file, "cmd: @ 2 ; read ATR window")
send_line(ser, "@ 2")
data = read_for(ser, args.atr_window)
emit(data, log_file)
atr = find_atr(extract_hex_bytes(data))
note(log_file, f"ATR parsed: {fmt(atr)}")
command_and_log(ser, log_file, "]", timeout=1.0)
return atr
def read_atr_with_retries(
ser: serial.Serial,
log_file: Path,
args: argparse.Namespace,
context: str,
) -> list[int] | None:
for attempt in range(1, args.atr_retries + 1):
if args.atr_retries > 1:
note(log_file, f"{context}: ATR attempt {attempt}/{args.atr_retries}")
atr = read_atr_after_reset(ser, log_file, args)
if atr:
return atr
return None
def sum_delays_ms(command: str) -> int:
return sum(int(m) for m in re.findall(r"D:(\d+)", command))
def run_variation(ser: serial.Serial, log_file: Path, args: argparse.Namespace,
label: str, command: str) -> tuple[list[int] | None, list[int], list[str]]:
note(log_file, f"variation: {label}")
atr = read_atr_with_retries(ser, log_file, args, label)
if not atr:
return None, [], []
read_seconds = max(1.0, sum_delays_ms(command) / 1000.0 + 1.0)
raw = bus_command_and_log(ser, log_file, command, read_seconds=read_seconds)
real = extract_rx_real_bytes(raw)
rx_lines = extract_rx_text(raw)
note(log_file, f"{label} RX lines: {rx_lines}")
note(log_file, f"{label} first real card byte(s): {fmt(real)}")
return atr, real, rx_lines
def classify_first_byte(b: int, ins: int) -> tuple[str, bool]:
"""Interpret the first byte the card returns after a T=0 command header.
Returns (human-readable meaning, works?)."""
if b == ins:
return (f"ACK (procedure byte = INS {ins:02X}): card accepts, wants command data", True)
if b == 0x60:
return ("NULL (0x60): card requests more time - valid T=0 procedure byte", True)
if 0x61 <= b <= 0x6F or 0x90 <= b <= 0x9F:
return (f"status word start SW1={b:02X}: card returned a T=0 status", True)
if b == 0xFF:
return ("0xFF: not a valid T=0 procedure/status byte (idle line / NAK)", False)
return (f"{b:02X}: not a standard T=0 procedure byte", False)
def run_verdict_test(ser: serial.Serial, log_file: Path, args: argparse.Namespace,
label: str, header: list[int], data: list[int]) -> str:
"""Reset for a fresh ATR, send a SELECT header, read the T=0 procedure byte,
and if the card ACKs, send the command data and read the status word. Returns
one human-readable RESULT line (WORKS / FAIL / INCONCLUSIVE)."""
note(log_file, f"verdict test: {label}")
atr = read_atr_with_retries(ser, log_file, args, label)
if not atr:
return f"RESULT [{label}]: INCONCLUSIVE - no ATR (check card/socket/voltage)"
ins = header[1]
hdr_cmd = ">" + " ".join(f"0x{b:02x}" for b in header) + " D:20 r:8"
raw = bus_command_and_log(ser, log_file, hdr_cmd, read_seconds=1.5)
real = extract_rx_real_bytes(raw)
note(log_file, f"{label} header response bytes: {fmt(real)}")
if not real:
return (f"RESULT [{label}]: FAIL - no procedure byte after the SELECT header "
f"(the BP6 issue: turnaround produces nothing)")
b = real[0]
meaning, works = classify_first_byte(b, ins)
if not works:
return f"RESULT [{label}]: FAIL - first byte {meaning}"
detail = f"procedure byte {meaning}"
if b == ins and data:
data_cmd = ">" + " ".join(f"0x{x:02x}" for x in data) + " D:20 r:8"
raw2 = bus_command_and_log(ser, log_file, data_cmd, read_seconds=1.5)
sw = extract_rx_real_bytes(raw2)
note(log_file, f"{label} after-data bytes: {fmt(sw)}")
detail += f"; after data {' '.join(f'{x:02X}' for x in data)} -> {fmt(sw)}"
return f"RESULT [{label}]: WORKS - T=0 turnaround functional. {detail}"
# (label, command). UICC SELECT MF header 00 A4 00 04 02 (+data 3F 00), and
# the classic GSM header A0 A4 00 00 02, sent with varied guard time / windows.
VARIATIONS = [
("uicc_baseline_D20", ">0x00 0xA4 0x00 0x04 0x02 D:20 r:8"),
("uicc_immediate", ">0x00 0xA4 0x00 0x04 0x02 r:8"),
("uicc_guard_1ms", ">0x00 D:1 0xA4 D:1 0x00 D:1 0x04 D:1 0x02 D:20 r:8"),
("uicc_guard_5ms", ">0x00 D:5 0xA4 D:5 0x00 D:5 0x04 D:5 0x02 D:50 r:8"),
("uicc_longwait_D1000", ">0x00 0xA4 0x00 0x04 0x02 D:1000 r:8"),
("gsm_baseline_D20", ">0xA0 0xA4 0x00 0x00 0x02 D:20 r:8"),
("gsm_guard_5ms", ">0xA0 D:5 0xA4 D:5 0x00 D:5 0x00 D:5 0x02 D:50 r:8"),
]
def prepare_prompt(ser: serial.Serial, log_file: Path) -> None:
note(log_file, "wake prompt")
emit(read_for(ser, 0.3), log_file)
send_line(ser, "")
emit(read_until_quiet(ser, 1.0), log_file)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--port", default=DEFAULT_PORT)
parser.add_argument("--baud", type=int, default=115200)
parser.add_argument("--voltage", default="3.3")
parser.add_argument("--auto-voltage", action="store_true",
help="try 3.3V then 1.8V and keep whichever yields an ATR")
parser.add_argument("--sim-baud", type=int, default=9600,
help="HDUART baud toward the SIM; keep = clock/372")
parser.add_argument("--clock-pin", default="1")
parser.add_argument("--clock", default="3.5712mhz")
parser.add_argument("--duty", default="50%")
parser.add_argument("--hduart-bus", choices=["listen", "master"], default="listen")
parser.add_argument("--parity", choices=["even", "odd", "none"], default="even")
parser.add_argument("--stops", type=int, choices=[1, 2], default=2)
parser.add_argument("--quick", action="store_true",
help="run only baseline + guard_5ms variations")
parser.add_argument("--configure-mode", action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--power-off-delay", type=float, default=1.0)
parser.add_argument("--reset-low", type=float, default=0.1)
parser.add_argument("--atr-window", type=float, default=1.0)
parser.add_argument("--atr-retries", type=int, default=3)
parser.add_argument("--inspect-only", action="store_true",
help="just report i/v and one ATR, then exit")
parser.add_argument("--rst-low-control", action="store_true",
help="control: power the card but hold RST low (muted), then "
"transmit the SELECT header and read. Expect NO return byte, "
"proving the 0xFF only appears with a live card.")
parser.add_argument("--log", default="bp6_hduart_sim_t0_repro.log")
args = parser.parse_args()
log_file = Path(args.log)
log_file.parent.mkdir(parents=True, exist_ok=True)
log_file.write_bytes(b"")
voltages = ["3.3", "1.8"] if args.auto_voltage else [args.voltage]
try:
with serial.Serial(args.port, args.baud, timeout=0.02, write_timeout=1, exclusive=True) as ser:
note(log_file, f"opened {args.port} at {args.baud}")
prepare_prompt(ser, log_file)
summaries: list[str] = []
if args.rst_low_control:
voltage = voltages[0]
note(log_file, f"=== RST-low control at {voltage}V ===")
cold_start(ser, log_file, args, voltage)
command_and_log(ser, log_file, "v", timeout=2.0, quiet=0.25, minimum=0.3)
note(log_file, "hold RST low: a 2 (card powered+clocked but muted)")
command_and_log(ser, log_file, "a 2", timeout=1.0)
cmd = ">0x00 0xA4 0x00 0x04 0x02 D:20 r:8"
raw = bus_command_and_log(ser, log_file, cmd, read_seconds=1.5)
real = extract_rx_real_bytes(raw)
rx_lines = extract_rx_text(raw)
summaries.append(f"RST-low control: first_real={fmt(real)} | RX={rx_lines}")
summaries.append("(expected: no card byte; proves 0xFF needs a live card)")
command_and_log(ser, log_file, "@ 2", timeout=1.0)
note(log_file, "summary")
for line in summaries:
note(log_file, line)
print("\n==== SUMMARY ====")
for line in summaries:
print(f" {line}")
print(f"Raw log: {log_file}")
return 0
atr = None
used_voltage = None
for voltage in voltages:
note(log_file, f"=== try voltage {voltage}V ===")
cold_start(ser, log_file, args, voltage)
command_and_log(ser, log_file, "i", timeout=3.0, quiet=0.25, minimum=0.5)
command_and_log(ser, log_file, "v", timeout=2.0, quiet=0.25, minimum=0.3)
atr = read_atr_with_retries(ser, log_file, args, f"voltage {voltage}V")
if atr:
used_voltage = voltage
break
verdicts: list[str] = []
if not atr:
summaries.append("no ATR at any tried voltage; check inserted card / socket")
verdicts.append("RESULT: INCONCLUSIVE - no ATR (card not answering; check "
"card/socket/voltage before judging T=0)")
else:
summaries.append(f"ATR at {used_voltage}V: {fmt(atr)}")
if not args.inspect_only:
# Headline: self-verifying pass/fail T=0 verdict.
verdicts.append(run_verdict_test(
ser, log_file, args, "UICC SELECT MF",
[0x00, 0xA4, 0x00, 0x04, 0x02], [0x3F, 0x00]))
verdicts.append(run_verdict_test(
ser, log_file, args, "GSM SELECT MF",
[0xA0, 0xA4, 0x00, 0x00, 0x02], [0x3F, 0x00]))
# Supporting evidence: guard-time / parity / speed sweeps.
variations = VARIATIONS
if args.quick:
variations = [v for v in VARIATIONS
if v[0] in ("uicc_baseline_D20", "uicc_guard_5ms")]
for label, command in variations:
_atr, real, rx_lines = run_variation(ser, log_file, args, label, command)
if _atr is None:
summaries.append(f"{label}: LOST ATR before send")
continue
summaries.append(f"{label}: first_real={fmt(real)} | RX={rx_lines}")
note(log_file, "verdict")
for line in verdicts:
note(log_file, line)
note(log_file, "summary")
for line in summaries:
note(log_file, line)
if verdicts:
print("\n==== RESULT (does T=0 work on this setup?) ====")
for line in verdicts:
print(f" {line}")
print("\n==== DETAIL ====")
for line in summaries:
print(f" {line}")
print(f"Raw log: {log_file}")
except serial.SerialException as exc:
print(f"serial error: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())