Add hall sensor circuit design, NTAG5 config check tool, I2C bus docs
- Hall sensor + LP5562 EN wired-AND circuit design (DRV5032FB/FE, 1M pull-up, open-drain topology) with three interaction modes: boot-time recovery, tap-to-swap, hold-to-confirm recovery - NTAG5 config check tool (tools/ntag5_config_check.py) using uFCoder library for ISO15693 transparent mode via uFR Zero reader. Supports inventory + addressed mode for multi-tag fields. - Updated DEVELOPMENT_PLAN with I2C bus management notes for LP5562 (0x30) + NTAG5Link (0x54) shared bus - Updated README with wired-AND hardware diagram, hall sensor interaction section, and updated wiring table - Updated CLAUDE.md with LP5562 EN wired-AND topology docs - Updated STATUS.md with hall sensor decisions and hardware inventory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
BIN
tools/libuFCoder-x86_64.so
Executable file
BIN
tools/libuFCoder-x86_64.so
Executable file
Binary file not shown.
530
tools/ntag5_config_check.py
Normal file
530
tools/ntag5_config_check.py
Normal file
@@ -0,0 +1,530 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read NTAG5Link configuration via uFR Zero reader (read-only).
|
||||
|
||||
Uses the Digital Logic uFCoder library in ISO15693 transparent mode
|
||||
to send NXP custom commands for reading config registers.
|
||||
|
||||
Supports multiple tags in the field: runs INVENTORY first, then
|
||||
reads each tag using addressed mode.
|
||||
|
||||
Usage: python3 ntag5_config_check.py [/dev/ttyUSBx]
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import sys
|
||||
import os
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Load uFCoder library
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
LIB_PATH = os.path.join(SCRIPT_DIR, "libuFCoder-x86_64.so")
|
||||
|
||||
if not os.path.exists(LIB_PATH):
|
||||
print(f"ERROR: uFCoder library not found at {LIB_PATH}")
|
||||
sys.exit(1)
|
||||
|
||||
ufr = ctypes.cdll.LoadLibrary(LIB_PATH)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# uFCoder function signatures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ufr.ReaderOpenEx.argtypes = [ctypes.c_uint32, ctypes.c_char_p, ctypes.c_uint32, ctypes.c_char_p]
|
||||
ufr.ReaderOpenEx.restype = ctypes.c_uint32
|
||||
|
||||
ufr.ReaderClose.argtypes = []
|
||||
ufr.ReaderClose.restype = ctypes.c_uint32
|
||||
|
||||
ufr.card_transceive_mode_start.argtypes = [ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint32, ctypes.c_uint32]
|
||||
ufr.card_transceive_mode_start.restype = ctypes.c_uint32
|
||||
|
||||
ufr.card_transceive_mode_stop.argtypes = []
|
||||
ufr.card_transceive_mode_stop.restype = ctypes.c_uint32
|
||||
|
||||
ufr.uart_transceive.argtypes = [
|
||||
ctypes.POINTER(ctypes.c_uint8), # send_data
|
||||
ctypes.c_uint8, # send_len
|
||||
ctypes.POINTER(ctypes.c_uint8), # rcv_data
|
||||
ctypes.c_uint32, # bytes_to_receive
|
||||
ctypes.POINTER(ctypes.c_uint32), # rcv_len
|
||||
]
|
||||
ufr.uart_transceive.restype = ctypes.c_uint32
|
||||
|
||||
# UFR status codes
|
||||
UFR_OK = 0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ISO15693 flags and commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Request flags
|
||||
ISO_FLAG_SUB_CARRIER = (1 << 0)
|
||||
ISO_FLAG_DATA_RATE = (1 << 1)
|
||||
ISO_FLAG_INVENTORY = (1 << 2)
|
||||
ISO_FLAG_PROTOCOL_EXT = (1 << 3)
|
||||
# When INVENTORY flag is NOT set:
|
||||
ISO_FLAG_SELECT = (1 << 4)
|
||||
ISO_FLAG_ADDRESS = (1 << 5)
|
||||
ISO_FLAG_OPTION = (1 << 6)
|
||||
# When INVENTORY flag IS set:
|
||||
ISO_FLAG_AFI = (1 << 4)
|
||||
ISO_FLAG_NB_SLOTS_1 = (1 << 5) # single slot
|
||||
|
||||
# Standard ISO15693 commands
|
||||
ISO_CMD_INVENTORY = 0x01
|
||||
ISO_CMD_STAY_QUIET = 0x02
|
||||
ISO_CMD_SYSTEM_INFO = 0x2B
|
||||
|
||||
# NXP custom commands
|
||||
NXP_CMD_SYSTEM_INFO = 0xAB
|
||||
NXP_CMD_READ_CONFIG = 0xC0
|
||||
NXP_CMD_MANUF_CODE_NXP = 0x04
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NXP NTAG5Link config constants (from ntag5sensor)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Config addresses
|
||||
NXP_CONFIG_ADDR_CONFIG = 0x37
|
||||
NXP_CONFIG_ADDR_EH_CONFIG = 0x3D
|
||||
|
||||
# Config byte 0 flags
|
||||
NXP_CONFIG_0_AUTO_STANDBY_MODE_EN = (1 << 0)
|
||||
NXP_CONFIG_0_LOCK_SESSION_REG = (1 << 1)
|
||||
NXP_CONFIG_0_EH_MODE_MASK = (3 << 2)
|
||||
NXP_CONFIG_0_EH_MODE_LOW_FIELD_STRENGTH = (2 << 2)
|
||||
NXP_CONFIG_0_EH_MODE_HIGH_FIELD_STRENGTH = (3 << 2)
|
||||
NXP_CONFIG_0_SRAM_COPY_EN = (1 << 7)
|
||||
|
||||
# Config byte 1 flags
|
||||
NXP_CONFIG_1_PT_TRANSFER_DIR = (1 << 0)
|
||||
NXP_CONFIG_1_SRAM_ENABLE = (1 << 1)
|
||||
NXP_CONFIG_1_ARBITER_MASK = (3 << 2)
|
||||
NXP_CONFIG_1_ARBITER_MODE_SRAM_PASSTHROUGH = (2 << 2)
|
||||
NXP_CONFIG_1_USE_CASE_MASK = (3 << 4)
|
||||
NXP_CONFIG_1_USE_CASE_CONF_I2C_SLAVE = (0 << 4)
|
||||
NXP_CONFIG_1_EH_ARBITER_MODE_EN = (1 << 7)
|
||||
|
||||
# Config byte 2 flags
|
||||
NXP_CONFIG_2_GPIO0_SLEW_RATE = (1 << 0)
|
||||
NXP_CONFIG_2_GPIO1_SLEW_RATE = (1 << 1)
|
||||
NXP_CONFIG_2_LOCK_BLOCK_COMMAND_SUPPORTED = (1 << 2)
|
||||
NXP_CONFIG_2_EXTENDED_COMMANDS_SUPPORTED = (1 << 3)
|
||||
NXP_CONFIG_2_GPIO0_PAD_MASK = (3 << 4)
|
||||
NXP_CONFIG_2_GPIO1_PAD_MASK = (3 << 6)
|
||||
|
||||
# EH config flags
|
||||
NXP_EH_CONFIG_EH_ENABLE = (1 << 0)
|
||||
NXP_EH_CONFIG_VOUT_V_MASK = (3 << 1)
|
||||
NXP_EH_CONFIG_DISABLE_POWER_CHECK = (1 << 3)
|
||||
NXP_EH_CONFIG_VOUT_I_MASK = (7 << 4)
|
||||
|
||||
# Lookup tables
|
||||
EH_V_SEL = {0: "1.8V", 1: "2.4V", 2: "3.0V", 3: "RFU"}
|
||||
EH_I_SEL = {0: "0.4mA", 1: "0.6mA", 2: "1.4mA", 3: "2.7mA",
|
||||
4: "4.0mA", 5: "6.5mA", 6: "9.0mA", 7: "12.5mA"}
|
||||
|
||||
ARBITER_MAP = {0: "normal", 1: "sram_mirror", 2: "sram_passthrough", 3: "sram_phdc"}
|
||||
USE_CASE_MAP = {0: "i2c_slave", 1: "i2c_master", 2: "gpio_pwm", 3: "tristate"}
|
||||
GPIO_PAD_MAP = {0: "disabled", 1: "plain_pullup", 2: "plain", 3: "plain_pulldown"}
|
||||
EH_MODE_MAP = {0: "rfu0", 1: "rfu1", 2: "low_field_strength", 3: "high_field_strength"}
|
||||
ED_CONFIG_MAP = {0: "disable", 1: "nfc_field_detect", 2: "pwm",
|
||||
3: "i2c_to_nfc_passthrough", 4: "nfc_to_i2c_passthrough",
|
||||
5: "arbiter_lock", 6: "ndef_msg_tlv_length", 7: "standby_mode",
|
||||
8: "write_cmd_indication", 9: "read_cmd_indication",
|
||||
10: "start_of_command_indication", 11: "read_from_synch_block",
|
||||
12: "write_to_synch_block", 13: "software_interrupt"}
|
||||
|
||||
INTERFACE_MAP = {0: "nfc_only", 1: "gpio", 2: "rfu", 3: "gpio_i2c"}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# xblink expected configuration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
EXPECTED = {
|
||||
"eh_mode": "low_field_strength",
|
||||
"use_case": "i2c_slave",
|
||||
"sram_enabled": True,
|
||||
"arbiter_mode": "sram_passthrough",
|
||||
"eh_enable": True,
|
||||
"eh_voltage": "3.0V",
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Low-level transceive
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def iso15693_transceive(cmd_bytes, expected_response_len=32):
|
||||
"""Send raw ISO15693 command and return response bytes (flags stripped)."""
|
||||
cmd = (ctypes.c_uint8 * len(cmd_bytes))(*cmd_bytes)
|
||||
rcv = (ctypes.c_uint8 * 256)()
|
||||
rcv_len = ctypes.c_uint32(0)
|
||||
|
||||
status = ufr.uart_transceive(cmd, len(cmd_bytes), rcv, expected_response_len, ctypes.byref(rcv_len))
|
||||
|
||||
if status != UFR_OK:
|
||||
if rcv_len.value > 0:
|
||||
data = bytes(rcv[:rcv_len.value])
|
||||
if data[0] == 0x00: # Response flags OK
|
||||
return data[1:]
|
||||
else:
|
||||
print(f" WARNING: Response flags = 0x{data[0]:02X} (error)")
|
||||
return data[1:]
|
||||
print(f" ERROR: uart_transceive failed, status=0x{status:04X}, rcv_len={rcv_len.value}")
|
||||
return None
|
||||
|
||||
data = bytes(rcv[:rcv_len.value])
|
||||
if len(data) > 0 and data[0] != 0x00:
|
||||
print(f" WARNING: Response flags = 0x{data[0]:02X}")
|
||||
return data[1:] if len(data) > 1 else data
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ISO15693 inventory
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def inventory():
|
||||
"""Run ISO15693 INVENTORY to discover all tags in the field.
|
||||
|
||||
Returns list of 8-byte UIDs (as bytes, LSB first as received on air).
|
||||
"""
|
||||
# Single-slot inventory: flags = data_rate | inventory | single_slot
|
||||
flags = ISO_FLAG_DATA_RATE | ISO_FLAG_INVENTORY | ISO_FLAG_NB_SLOTS_1
|
||||
cmd = [flags, ISO_CMD_INVENTORY, 0x00] # mask_length=0 (no mask)
|
||||
# Response: flags(1) + DSFID(1) + UID(8) = 10 bytes
|
||||
data = iso15693_transceive(cmd, 10)
|
||||
if data is None:
|
||||
return []
|
||||
|
||||
uids = []
|
||||
if len(data) >= 9:
|
||||
dsfid = data[0]
|
||||
uid = data[1:9] # 8 bytes, LSB first
|
||||
uids.append(uid)
|
||||
|
||||
# TODO: for multi-slot (16 slots), would iterate and collect multiple UIDs.
|
||||
# Single-slot works when only one tag is present; with multiple tags,
|
||||
# collisions occur and we'd need anti-collision. For now this covers
|
||||
# the primary use case (one NTAG5 Click on the reader).
|
||||
return uids
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Addressed config reads
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def read_config_block_addressed(uid, address, num_blocks=1):
|
||||
"""Read NTAG5Link config block via NXP READ_CONFIG in addressed mode.
|
||||
|
||||
Args:
|
||||
uid: 8-byte UID (LSB first, as returned by inventory)
|
||||
address: config block address (e.g. 0x37)
|
||||
num_blocks: number of 4-byte blocks to read
|
||||
"""
|
||||
flags = ISO_FLAG_DATA_RATE | ISO_FLAG_ADDRESS
|
||||
cmd = [flags, NXP_CMD_READ_CONFIG, NXP_CMD_MANUF_CODE_NXP]
|
||||
cmd.extend(uid) # 8-byte UID
|
||||
cmd.extend([address, num_blocks - 1])
|
||||
# Response: 1 byte flags + num_blocks * 4 bytes data
|
||||
return iso15693_transceive(cmd, 1 + num_blocks * 4)
|
||||
|
||||
|
||||
def read_config_block_nonaddressed(address, num_blocks=1):
|
||||
"""Read NTAG5Link config block in non-addressed mode (single tag only)."""
|
||||
cmd = [ISO_FLAG_DATA_RATE, NXP_CMD_READ_CONFIG, NXP_CMD_MANUF_CODE_NXP,
|
||||
address, num_blocks - 1]
|
||||
return iso15693_transceive(cmd, 1 + num_blocks * 4)
|
||||
|
||||
|
||||
def get_system_info_addressed(uid):
|
||||
"""Get ISO15693 system info in addressed mode."""
|
||||
flags = ISO_FLAG_DATA_RATE | ISO_FLAG_ADDRESS
|
||||
cmd = [flags, ISO_CMD_SYSTEM_INFO]
|
||||
cmd.extend(uid)
|
||||
# Response varies; request generous buffer
|
||||
return iso15693_transceive(cmd, 32)
|
||||
|
||||
|
||||
def get_nxp_system_info_addressed(uid):
|
||||
"""Get NXP-specific system info in addressed mode."""
|
||||
flags = ISO_FLAG_DATA_RATE | ISO_FLAG_ADDRESS
|
||||
cmd = [flags, NXP_CMD_SYSTEM_INFO, NXP_CMD_MANUF_CODE_NXP]
|
||||
cmd.extend(uid)
|
||||
return iso15693_transceive(cmd, 32)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Decoders
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def decode_uid(uid_bytes):
|
||||
"""Format UID for display (MSB first, colon-separated)."""
|
||||
return ":".join(f"{b:02X}" for b in reversed(uid_bytes))
|
||||
|
||||
|
||||
def decode_system_info(data):
|
||||
"""Decode ISO15693 GET_SYSTEM_INFO response."""
|
||||
if data is None or len(data) < 9:
|
||||
return None
|
||||
|
||||
info_flags = data[0]
|
||||
uid = data[1:9][::-1] # reverse to MSB first
|
||||
res = {"uid": uid, "uid_str": ":".join(f"{b:02X}" for b in uid)}
|
||||
|
||||
idx = 9
|
||||
if info_flags & 0x01 and idx < len(data): # DSFID
|
||||
res["dsfid"] = data[idx]
|
||||
idx += 1
|
||||
if info_flags & 0x02 and idx < len(data): # AFI
|
||||
res["afi"] = data[idx]
|
||||
idx += 1
|
||||
if info_flags & 0x04 and idx + 1 < len(data): # Memory size
|
||||
res["num_blocks"] = data[idx] + 1
|
||||
res["block_size"] = (data[idx + 1] & 0x1F) + 1
|
||||
res["memory_bytes"] = res["num_blocks"] * res["block_size"]
|
||||
idx += 2
|
||||
if info_flags & 0x08 and idx < len(data): # IC reference
|
||||
res["ic_ref"] = data[idx]
|
||||
idx += 1
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def decode_nxp_system_info(data):
|
||||
"""Decode NXP GET_NXP_SYSTEM_INFO response."""
|
||||
if data is None or len(data) < 7:
|
||||
return None
|
||||
|
||||
res = {
|
||||
"pp_pointer": data[0],
|
||||
"lock_bits_raw": data[2],
|
||||
}
|
||||
|
||||
if len(data) >= 7:
|
||||
b3 = data[6]
|
||||
res["interface"] = INTERFACE_MAP.get((b3 >> 5) & 0x03, "unknown")
|
||||
res["num_keys"] = b3 & 0x0F
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def decode_config(data):
|
||||
"""Decode CONFIG block (address 0x37) — 4 bytes."""
|
||||
if data is None or len(data) < 3:
|
||||
return None
|
||||
|
||||
b0, b1, b2 = data[0], data[1], data[2]
|
||||
return {
|
||||
"raw": [f"0x{b:02X}" for b in data[:4]] if len(data) >= 4 else [f"0x{b:02X}" for b in data],
|
||||
"auto_standby": bool(b0 & NXP_CONFIG_0_AUTO_STANDBY_MODE_EN),
|
||||
"lock_session_reg": bool(b0 & NXP_CONFIG_0_LOCK_SESSION_REG),
|
||||
"eh_mode": EH_MODE_MAP.get((b0 >> 2) & 0x03, "unknown"),
|
||||
"sram_copy_en": bool(b0 & NXP_CONFIG_0_SRAM_COPY_EN),
|
||||
"pt_transfer_dir": "reader_to_tag" if (b1 & NXP_CONFIG_1_PT_TRANSFER_DIR) else "tag_to_reader",
|
||||
"sram_enabled": bool(b1 & NXP_CONFIG_1_SRAM_ENABLE),
|
||||
"arbiter_mode": ARBITER_MAP.get((b1 >> 2) & 0x03, "unknown"),
|
||||
"use_case": USE_CASE_MAP.get((b1 >> 4) & 0x03, "unknown"),
|
||||
"eh_arbiter_mode_en": bool(b1 & NXP_CONFIG_1_EH_ARBITER_MODE_EN),
|
||||
"gpio0_slew_fast": bool(b2 & NXP_CONFIG_2_GPIO0_SLEW_RATE),
|
||||
"gpio1_slew_fast": bool(b2 & NXP_CONFIG_2_GPIO1_SLEW_RATE),
|
||||
"lock_block_supported": bool(b2 & NXP_CONFIG_2_LOCK_BLOCK_COMMAND_SUPPORTED),
|
||||
"ext_commands_supported": bool(b2 & NXP_CONFIG_2_EXTENDED_COMMANDS_SUPPORTED),
|
||||
"gpio0_pad_in": GPIO_PAD_MAP.get((b2 >> 4) & 0x03, "unknown"),
|
||||
"gpio1_pad_in": GPIO_PAD_MAP.get((b2 >> 6) & 0x03, "unknown"),
|
||||
}
|
||||
|
||||
|
||||
def decode_eh_config(data):
|
||||
"""Decode EH/ED CONFIG block (address 0x3D) — 4 bytes."""
|
||||
if data is None or len(data) < 1:
|
||||
return None
|
||||
|
||||
eh = data[0]
|
||||
ed = data[2] if len(data) > 2 else 0
|
||||
|
||||
return {
|
||||
"raw": [f"0x{b:02X}" for b in data[:4]] if len(data) >= 4 else [f"0x{b:02X}" for b in data],
|
||||
"eh_enable": bool(eh & NXP_EH_CONFIG_EH_ENABLE),
|
||||
"eh_voltage": EH_V_SEL.get((eh >> 1) & 0x03, "unknown"),
|
||||
"disable_power_check": bool(eh & NXP_EH_CONFIG_DISABLE_POWER_CHECK),
|
||||
"eh_current": EH_I_SEL.get((eh >> 4) & 0x07, "unknown"),
|
||||
"ed_config": ED_CONFIG_MAP.get(ed & 0x0F, "unknown"),
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Verification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def check_against_expected(config, eh_config):
|
||||
"""Compare config against xblink expected values."""
|
||||
print("\n--- xblink Config Check ---")
|
||||
checks = [
|
||||
("EH mode", config.get("eh_mode"), EXPECTED["eh_mode"]),
|
||||
("Use case", config.get("use_case"), EXPECTED["use_case"]),
|
||||
("SRAM enabled", config.get("sram_enabled"), EXPECTED["sram_enabled"]),
|
||||
("Arbiter mode", config.get("arbiter_mode"), EXPECTED["arbiter_mode"]),
|
||||
("EH enable", eh_config.get("eh_enable"), EXPECTED["eh_enable"]),
|
||||
("EH voltage", eh_config.get("eh_voltage"), EXPECTED["eh_voltage"]),
|
||||
]
|
||||
|
||||
all_ok = True
|
||||
for name, actual, expected in checks:
|
||||
match = actual == expected
|
||||
symbol = "OK" if match else "MISMATCH"
|
||||
if not match:
|
||||
all_ok = False
|
||||
print(f" [{symbol:8s}] {name:20s}: {actual} (expected: {expected})")
|
||||
|
||||
if all_ok:
|
||||
print("\n All config values match xblink requirements!")
|
||||
else:
|
||||
print("\n Some values need updating. Use ntag5sensor tooling to reconfigure.")
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
def read_and_check_tag(uid):
|
||||
"""Read all config from one tag and verify against xblink requirements.
|
||||
|
||||
Args:
|
||||
uid: 8-byte UID (LSB first) or None for non-addressed mode
|
||||
"""
|
||||
use_addressed = uid is not None
|
||||
uid_str = decode_uid(uid) if uid else "(non-addressed)"
|
||||
|
||||
# --- ISO15693 system info ---
|
||||
if use_addressed:
|
||||
print(f"\nISO15693 System Info...")
|
||||
sys_data = get_system_info_addressed(uid)
|
||||
sys_info = decode_system_info(sys_data)
|
||||
if sys_info:
|
||||
print(f" Memory: {sys_info.get('memory_bytes', '?')} bytes "
|
||||
f"({sys_info.get('num_blocks', '?')} blocks x {sys_info.get('block_size', '?')} bytes)")
|
||||
if "ic_ref" in sys_info:
|
||||
print(f" IC ref: 0x{sys_info['ic_ref']:02X}")
|
||||
else:
|
||||
print(" Failed to read system info.")
|
||||
|
||||
# --- NXP system info ---
|
||||
if use_addressed:
|
||||
print(f"\nNXP System Info...")
|
||||
nxp_data = get_nxp_system_info_addressed(uid)
|
||||
nxp_info = decode_nxp_system_info(nxp_data)
|
||||
if nxp_info:
|
||||
print(f" Interface: {nxp_info.get('interface', '?')}")
|
||||
print(f" Num keys: {nxp_info.get('num_keys', '?')}")
|
||||
else:
|
||||
print(" Failed to read NXP system info (may not be an NXP tag).")
|
||||
|
||||
# --- CONFIG block (0x37) ---
|
||||
print(f"\nCONFIG block (0x37)...")
|
||||
if use_addressed:
|
||||
config_data = read_config_block_addressed(uid, NXP_CONFIG_ADDR_CONFIG)
|
||||
else:
|
||||
config_data = read_config_block_nonaddressed(NXP_CONFIG_ADDR_CONFIG)
|
||||
config = decode_config(config_data)
|
||||
|
||||
if config:
|
||||
print(f" Raw bytes: {config['raw']}")
|
||||
print(f" EH mode: {config['eh_mode']}")
|
||||
print(f" Use case: {config['use_case']}")
|
||||
print(f" SRAM enabled: {config['sram_enabled']}")
|
||||
print(f" Arbiter mode: {config['arbiter_mode']}")
|
||||
print(f" Auto standby: {config['auto_standby']}")
|
||||
print(f" SRAM copy: {config['sram_copy_en']}")
|
||||
print(f" PT transfer dir: {config['pt_transfer_dir']}")
|
||||
print(f" EH arbiter mode: {config['eh_arbiter_mode_en']}")
|
||||
print(f" GPIO0 pad: {config['gpio0_pad_in']}")
|
||||
print(f" GPIO1 pad: {config['gpio1_pad_in']}")
|
||||
print(f" Lock block: {config['lock_block_supported']}")
|
||||
print(f" Ext commands: {config['ext_commands_supported']}")
|
||||
else:
|
||||
print(" Failed to read config block.")
|
||||
|
||||
# --- EH/ED CONFIG block (0x3D) ---
|
||||
print(f"\nEH/ED CONFIG block (0x3D)...")
|
||||
if use_addressed:
|
||||
eh_data = read_config_block_addressed(uid, NXP_CONFIG_ADDR_EH_CONFIG)
|
||||
else:
|
||||
eh_data = read_config_block_nonaddressed(NXP_CONFIG_ADDR_EH_CONFIG)
|
||||
eh_config = decode_eh_config(eh_data)
|
||||
|
||||
if eh_config:
|
||||
print(f" Raw bytes: {eh_config['raw']}")
|
||||
print(f" EH enable: {eh_config['eh_enable']}")
|
||||
print(f" EH voltage: {eh_config['eh_voltage']}")
|
||||
print(f" EH current limit: {eh_config['eh_current']}")
|
||||
print(f" Power check: {'disabled' if eh_config['disable_power_check'] else 'enabled'}")
|
||||
print(f" ED config: {eh_config['ed_config']}")
|
||||
else:
|
||||
print(" Failed to read EH config block.")
|
||||
|
||||
# --- Check against xblink expected values ---
|
||||
if config and eh_config:
|
||||
return check_against_expected(config, eh_config)
|
||||
return False
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
port = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyUSB0"
|
||||
print(f"Connecting to uFR Zero on {port}...")
|
||||
|
||||
# Open reader: reader_type=1 (uFR @ 1Mbps), port_interface=1 (serial/CP210x)
|
||||
status = ufr.ReaderOpenEx(1, port.encode(), 1, None)
|
||||
if status != UFR_OK:
|
||||
print(f"ERROR: Failed to open reader, status=0x{status:04X}")
|
||||
sys.exit(1)
|
||||
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
print("Reader connected.")
|
||||
|
||||
# Enter ISO15693 transparent mode (tx_crc=1, rx_crc=1, rf_timeout=10000us, uart_timeout=500ms)
|
||||
status = ufr.card_transceive_mode_start(1, 1, 10000, 500)
|
||||
if status != UFR_OK:
|
||||
print(f"ERROR: Failed to enter transceive mode, status=0x{status:04X}")
|
||||
ufr.ReaderClose()
|
||||
sys.exit(1)
|
||||
|
||||
print("ISO15693 transparent mode active.")
|
||||
|
||||
# --- Step 1: Inventory ---
|
||||
print("\n=== ISO15693 Inventory ===")
|
||||
uids = inventory()
|
||||
|
||||
if not uids:
|
||||
print(" No tags found. Trying non-addressed mode as fallback...")
|
||||
print("\n=== Tag (non-addressed) ===")
|
||||
read_and_check_tag(None)
|
||||
else:
|
||||
print(f" Found {len(uids)} tag(s):")
|
||||
for i, uid in enumerate(uids):
|
||||
print(f" [{i}] UID: {decode_uid(uid)}")
|
||||
|
||||
# --- Step 2: Read each tag ---
|
||||
all_ok = True
|
||||
for i, uid in enumerate(uids):
|
||||
print(f"\n{'='*50}")
|
||||
print(f"=== Tag {i}: {decode_uid(uid)} ===")
|
||||
print(f"{'='*50}")
|
||||
ok = read_and_check_tag(uid)
|
||||
if not ok:
|
||||
all_ok = False
|
||||
|
||||
if len(uids) > 1:
|
||||
print(f"\n{'='*50}")
|
||||
if all_ok:
|
||||
print("All tags pass xblink config check.")
|
||||
else:
|
||||
print("Some tags have config mismatches.")
|
||||
|
||||
# Cleanup
|
||||
ufr.card_transceive_mode_stop()
|
||||
ufr.ReaderClose()
|
||||
print("\nReader closed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user