pn5180_diag.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #!/usr/bin/env python3
  2. """PN5180 NFC reader diagnostic script.
  3. Connects to a PN5180 over SPI on a Raspberry Pi and reads
  4. hardware status, version info, and register state.
  5. Wiring (from spoolbuddy/README.md):
  6. PN5180 VCC -> Pi Pin 1 (3.3V)
  7. PN5180 GND -> Pi Pin 20 (GND)
  8. PN5180 SCK -> Pi Pin 23 (GPIO11)
  9. PN5180 MISO -> Pi Pin 21 (GPIO9)
  10. PN5180 MOSI -> Pi Pin 19 (GPIO10)
  11. PN5180 NSS -> Pi Pin 24 (GPIO8 / CE0)
  12. PN5180 BUSY -> Pi Pin 22 (GPIO25)
  13. PN5180 RST -> Pi Pin 18 (GPIO24)
  14. """
  15. import sys
  16. import time
  17. import gpiod
  18. import spidev
  19. # ---------------------------------------------------------------------------
  20. # Pin assignments (BCM numbering)
  21. # ---------------------------------------------------------------------------
  22. BUSY_PIN = 25 # Pin 22
  23. RST_PIN = 24 # Pin 18
  24. # ---------------------------------------------------------------------------
  25. # SPI command instruction codes (NXP PN5180 datasheet Table 5)
  26. # ---------------------------------------------------------------------------
  27. CMD_WRITE_REGISTER = 0x00
  28. CMD_WRITE_REGISTER_OR_MASK = 0x01
  29. CMD_WRITE_REGISTER_AND_MASK = 0x02
  30. CMD_READ_REGISTER = 0x04
  31. CMD_READ_REGISTER_MULTIPLE = 0x05
  32. CMD_WRITE_EEPROM = 0x06
  33. CMD_READ_EEPROM = 0x07
  34. CMD_SEND_DATA = 0x09
  35. CMD_READ_DATA = 0x0A
  36. CMD_LOAD_RF_CONFIG = 0x11
  37. CMD_RF_ON = 0x16
  38. CMD_RF_OFF = 0x17
  39. # ---------------------------------------------------------------------------
  40. # Register addresses (32-bit each)
  41. # ---------------------------------------------------------------------------
  42. REG_SYSTEM_CONFIG = 0x00
  43. REG_IRQ_ENABLE = 0x01
  44. REG_IRQ_STATUS = 0x02
  45. REG_IRQ_CLEAR = 0x03
  46. REG_TRANSCEIVE_CONTROL = 0x04
  47. REG_TIMER1_RELOAD = 0x0C
  48. REG_TIMER1_CONFIG = 0x0F
  49. REG_RX_WAIT_CONFIG = 0x11
  50. REG_CRC_RX_CONFIG = 0x12
  51. REG_RX_STATUS = 0x13
  52. REG_CRC_TX_CONFIG = 0x19
  53. REG_RF_STATUS = 0x1D
  54. REG_SYSTEM_STATUS = 0x24
  55. REG_TEMP_CONTROL = 0x25
  56. REGISTER_NAMES = {
  57. REG_SYSTEM_CONFIG: "SYSTEM_CONFIG",
  58. REG_IRQ_ENABLE: "IRQ_ENABLE",
  59. REG_IRQ_STATUS: "IRQ_STATUS",
  60. REG_IRQ_CLEAR: "IRQ_CLEAR",
  61. REG_TRANSCEIVE_CONTROL: "TRANSCEIVE_CONTROL",
  62. REG_TIMER1_RELOAD: "TIMER1_RELOAD",
  63. REG_TIMER1_CONFIG: "TIMER1_CONFIG",
  64. REG_RX_WAIT_CONFIG: "RX_WAIT_CONFIG",
  65. REG_CRC_RX_CONFIG: "CRC_RX_CONFIG",
  66. REG_RX_STATUS: "RX_STATUS",
  67. REG_CRC_TX_CONFIG: "CRC_TX_CONFIG",
  68. REG_RF_STATUS: "RF_STATUS",
  69. REG_SYSTEM_STATUS: "SYSTEM_STATUS",
  70. REG_TEMP_CONTROL: "TEMP_CONTROL",
  71. }
  72. # ---------------------------------------------------------------------------
  73. # EEPROM addresses
  74. # ---------------------------------------------------------------------------
  75. EEPROM_DIE_IDENTIFIER = 0x00 # 16 bytes
  76. EEPROM_PRODUCT_VERSION = 0x10 # 2 bytes
  77. EEPROM_FIRMWARE_VERSION = 0x12 # 2 bytes
  78. EEPROM_EEPROM_VERSION = 0x14 # 2 bytes
  79. EEPROM_IRQ_PIN_CONFIG = 0x1A # 1 byte
  80. def _find_gpio_chip():
  81. """Find the right gpiochip for Raspberry Pi GPIO pins.
  82. RPi 5 uses gpiochip4, RPi 4 uses gpiochip0.
  83. """
  84. for path in ["/dev/gpiochip4", "/dev/gpiochip0"]:
  85. try:
  86. chip = gpiod.Chip(path)
  87. info = chip.get_info()
  88. # RPi 4: pinctrl-bcm2711, RPi 5: pinctrl-rp1
  89. if "pinctrl" in info.label:
  90. return chip
  91. chip.close()
  92. except (FileNotFoundError, PermissionError, OSError):
  93. continue
  94. raise RuntimeError("Could not find Raspberry Pi GPIO chip")
  95. class PN5180:
  96. """Low-level driver for the PN5180 NFC frontend over SPI."""
  97. def __init__(self, spi_bus=0, spi_device=0, spi_speed_hz=1_000_000, busy_pin=BUSY_PIN, rst_pin=RST_PIN):
  98. # GPIO setup via libgpiod
  99. self._chip = _find_gpio_chip()
  100. self._busy_line = self._chip.request_lines(
  101. consumer="pn5180-diag",
  102. config={busy_pin: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT)},
  103. )
  104. self._rst_line = self._chip.request_lines(
  105. consumer="pn5180-diag",
  106. config={
  107. rst_pin: gpiod.LineSettings(
  108. direction=gpiod.line.Direction.OUTPUT,
  109. output_value=gpiod.line.Value.ACTIVE,
  110. )
  111. },
  112. )
  113. self._busy_pin = busy_pin
  114. self._rst_pin = rst_pin
  115. # SPI setup – mode 0 (CPOL=0, CPHA=0), MSB first
  116. self._spi = spidev.SpiDev()
  117. self._spi.open(spi_bus, spi_device)
  118. self._spi.max_speed_hz = spi_speed_hz
  119. self._spi.mode = 0b00
  120. self._spi.bits_per_word = 8
  121. def close(self):
  122. self._spi.close()
  123. self._busy_line.release()
  124. self._rst_line.release()
  125. self._chip.close()
  126. # -- low-level helpers --------------------------------------------------
  127. def _busy_is_high(self):
  128. return self._busy_line.get_value(self._busy_pin) == gpiod.line.Value.ACTIVE
  129. def _wait_busy(self, timeout_s=1.0):
  130. """Block until BUSY goes LOW (PN5180 ready)."""
  131. deadline = time.monotonic() + timeout_s
  132. while self._busy_is_high():
  133. if time.monotonic() > deadline:
  134. raise TimeoutError("PN5180 BUSY line did not go low")
  135. time.sleep(0.001)
  136. def _send_command(self, tx_data, rx_len=0):
  137. """Send an SPI command frame and optionally read a response frame.
  138. The PN5180 SPI protocol is half-duplex:
  139. 1. Send command frame (NSS held low for entire frame).
  140. 2. Wait for BUSY high then low (command processed).
  141. 3. If a response is expected, clock out rx_len bytes in a second frame.
  142. """
  143. self._wait_busy()
  144. # Transmit command
  145. self._spi.xfer2(list(tx_data))
  146. if rx_len == 0:
  147. # Write-only command – wait for processing
  148. time.sleep(0.001)
  149. self._wait_busy()
  150. return None
  151. # Wait for PN5180 to process command (BUSY goes high then low)
  152. time.sleep(0.001)
  153. self._wait_busy()
  154. # Read response
  155. rx = self._spi.xfer2([0xFF] * rx_len)
  156. time.sleep(0.001)
  157. self._wait_busy()
  158. return bytes(rx)
  159. # -- register operations ------------------------------------------------
  160. def read_register(self, addr):
  161. """Read a 32-bit register. Returns int."""
  162. resp = self._send_command([CMD_READ_REGISTER, addr], rx_len=4)
  163. return int.from_bytes(resp, "little")
  164. def write_register(self, addr, value):
  165. """Write a 32-bit value to a register."""
  166. self._send_command(
  167. [
  168. CMD_WRITE_REGISTER,
  169. addr,
  170. value & 0xFF,
  171. (value >> 8) & 0xFF,
  172. (value >> 16) & 0xFF,
  173. (value >> 24) & 0xFF,
  174. ]
  175. )
  176. def write_register_or_mask(self, addr, mask):
  177. self._send_command(
  178. [
  179. CMD_WRITE_REGISTER_OR_MASK,
  180. addr,
  181. mask & 0xFF,
  182. (mask >> 8) & 0xFF,
  183. (mask >> 16) & 0xFF,
  184. (mask >> 24) & 0xFF,
  185. ]
  186. )
  187. def write_register_and_mask(self, addr, mask):
  188. self._send_command(
  189. [
  190. CMD_WRITE_REGISTER_AND_MASK,
  191. addr,
  192. mask & 0xFF,
  193. (mask >> 8) & 0xFF,
  194. (mask >> 16) & 0xFF,
  195. (mask >> 24) & 0xFF,
  196. ]
  197. )
  198. # -- EEPROM operations --------------------------------------------------
  199. def read_eeprom(self, addr, length):
  200. """Read `length` bytes from EEPROM starting at `addr`."""
  201. return self._send_command([CMD_READ_EEPROM, addr, length], rx_len=length)
  202. # -- reset --------------------------------------------------------------
  203. def reset(self):
  204. """Hardware-reset the PN5180 via the RST pin."""
  205. self._rst_line.set_value(self._rst_pin, gpiod.line.Value.INACTIVE)
  206. time.sleep(0.01)
  207. self._rst_line.set_value(self._rst_pin, gpiod.line.Value.ACTIVE)
  208. time.sleep(0.05)
  209. self._wait_busy(timeout_s=2.0)
  210. # Clear all IRQ flags
  211. self.write_register(REG_IRQ_CLEAR, 0xFFFFFFFF)
  212. # -- version / identity -------------------------------------------------
  213. def get_product_version(self):
  214. data = self.read_eeprom(EEPROM_PRODUCT_VERSION, 2)
  215. return f"{data[1]}.{data[0]}"
  216. def get_firmware_version(self):
  217. data = self.read_eeprom(EEPROM_FIRMWARE_VERSION, 2)
  218. return f"{data[1]}.{data[0]}"
  219. def get_eeprom_version(self):
  220. data = self.read_eeprom(EEPROM_EEPROM_VERSION, 2)
  221. return f"{data[1]}.{data[0]}"
  222. def get_die_identifier(self):
  223. data = self.read_eeprom(EEPROM_DIE_IDENTIFIER, 16)
  224. return data.hex()
  225. def run_diagnostics():
  226. print("=" * 60)
  227. print("PN5180 NFC Reader Diagnostics")
  228. print("=" * 60)
  229. nfc = PN5180()
  230. try:
  231. # Reset
  232. print("\n[1] Hardware reset...")
  233. nfc.reset()
  234. print(" Reset OK")
  235. # Version info
  236. print("\n[2] Version info (EEPROM)")
  237. print(f" Product version : {nfc.get_product_version()}")
  238. print(f" Firmware version : {nfc.get_firmware_version()}")
  239. print(f" EEPROM version : {nfc.get_eeprom_version()}")
  240. print(f" Die identifier : {nfc.get_die_identifier()}")
  241. # Register dump
  242. print("\n[3] Register dump")
  243. for addr, name in sorted(REGISTER_NAMES.items()):
  244. val = nfc.read_register(addr)
  245. print(f" 0x{addr:02X} {name:<24s} = 0x{val:08X}")
  246. # IRQ status breakdown
  247. irq = nfc.read_register(REG_IRQ_STATUS)
  248. print(f"\n[4] IRQ status flags (0x{irq:08X})")
  249. irq_flags = [
  250. (0, "RX_IRQ"),
  251. (1, "TX_IRQ"),
  252. (2, "IDLE_IRQ"),
  253. (3, "MODE_DETECTED_IRQ"),
  254. (4, "CARD_ACTIVATED_IRQ"),
  255. (5, "STATE_CHANGE_IRQ"),
  256. (6, "RFOFF_DET_IRQ"),
  257. (7, "RFON_DET_IRQ"),
  258. (8, "TX_RFOFF_IRQ"),
  259. (9, "TX_RFON_IRQ"),
  260. (10, "RF_ACTIVE_ERROR_IRQ"),
  261. (14, "LPCD_IRQ"),
  262. ]
  263. for bit, name in irq_flags:
  264. state = "SET" if irq & (1 << bit) else "---"
  265. print(f" bit {bit:2d}: {name:<28s} [{state}]")
  266. # RF status
  267. rf = nfc.read_register(REG_RF_STATUS)
  268. print(f"\n[5] RF status (0x{rf:08X})")
  269. tx_rf_on = bool(rf & (1 << 0))
  270. rx_en = bool(rf & (1 << 1))
  271. print(f" TX RF active : {tx_rf_on}")
  272. print(f" RX enabled : {rx_en}")
  273. # System status
  274. sys_stat = nfc.read_register(REG_SYSTEM_STATUS)
  275. print(f"\n[6] System status (0x{sys_stat:08X})")
  276. # Temperature
  277. temp_ctrl = nfc.read_register(REG_TEMP_CONTROL)
  278. print(f"\n[7] Temp control register (0x{temp_ctrl:08X})")
  279. print("\n" + "=" * 60)
  280. print("Diagnostics complete - PN5180 is responding over SPI.")
  281. print("=" * 60)
  282. except TimeoutError as e:
  283. print(f"\nERROR: {e}")
  284. print("Check wiring and ensure SPI is enabled (dtparam=spi=on in /boot/firmware/config.txt)")
  285. sys.exit(1)
  286. except Exception as e:
  287. print(f"\nERROR: {e}")
  288. sys.exit(1)
  289. finally:
  290. nfc.close()
  291. if __name__ == "__main__":
  292. run_diagnostics()