16 KiB
PN532 — Byte-Level Communication Protocol
The PN532 is a "dumb" NFC front-end: the host drives it by writing command frames and reading back response frames. This document describes those bytes — the framing, checksums, acknowledgements, error codes and command set — independent of the physical transport (HSU/UART, I²C or SPI). The transport only changes how the bytes are clocked in/out; the frame content is identical everywhere.
Sources: NXP PN532 User Manual UM0701-02 (the host protocol) and NXP
PN532_C1.pdf(the datasheet, for transport framing and the low-level CIU command set).
1. The Information Frame
Every command and every response is one information frame of bytes:
| Preamble | Start code | LEN | LCS | TFI | DATA ... | DCS | Postamble |
| 00 | 00 FF | | | | | | 00 |
| Field | Size | Value / meaning |
|---|---|---|
| Preamble | 1 | 0x00 (see §1.4 for length rules) |
| Start code | 2 | 0x00 0xFF |
| LEN | 1 | Length of the data field = 1 (TFI) + N (payload bytes). 0x01..0xFF. |
| LCS | 1 | Length Checksum — (0x100 − LEN) & 0xFF, i.e. LEN + LCS == 0 (mod 256). |
| TFI | 1 | Frame identifier: 0xD4 host→PN532, 0xD5 PN532→host. |
| DATA | N | Command code (PD0) followed by parameters (PD1…PDn). |
| DCS | 1 | Data Checksum — (0x100 − (TFI + Σ DATA)) & 0xFF, i.e. TFI + Σ DATA + DCS == 0 (mod 256). |
| Postamble | 1 | 0x00 (see §1.4) |
Checksums in one line (two's complement):
LCS = (!LEN) + 1
DCS = (!(TFI + DATA0 + DATA1 + ...)) + 1
A frame is rejected (no ACK is returned) if LEN + LCS != 0 or the data checksum
does not sum to zero.
1.1 Extended information frame (payload > 255 bytes)
The firmware supports up to 264 data bytes (265 including TFI). Beyond the normal frame's 255-byte limit it uses an extended frame:
00 00 FF FF FF LENM LENL LCS TFI PD0 ... PDn DCS 00
└─┬─┘ └──┬──┘ │
LEN = FF LCS = FF │ ← both fixed to 0xFF (normally an "error" LEN/LCS pair)
LCS ← lower byte of [LENM + LENL + LCS] = 0x00
LENandLCSare fixed to0xFF.- Real length:
LENGTH = LENM × 256 + LENL= number of bytes in TFI + payload. LCSsatisfiesLENM + LENL + LCS == 0(mod 256).- The host may use the extended frame for short frames too; the PN532 always picks the right form (normal ≤ 255, extended > 255).
1.2 Frame Identifier (TFI)
| TFI | Direction |
|---|---|
0xD4 |
Host → PN532 |
0xD5 |
PN532 → Host |
0x7F |
Error frame (PN532 → host, §2) |
1.3 Response command byte
The response echoes the command code + 1:
| Command (host→PN532) | Response (PN532→host) |
|---|---|
GetFirmwareVersion 0x02 |
0x03 |
SAMConfiguration 0x14 |
0x15 |
InListPassiveTarget 0x4A |
0x4B |
InDataExchange 0x40 |
0x41 |
| … | command + 1 |
1.4 Preamble / Postamble length rules
They are not always a single 0x00 byte:
- Host → PN532 (HSU and I²C): preamble and postamble may be
0..nbytes; the value has no impact on frame processing. The PN532 only synchronises on the0x00 0xFFstart code. - Host → PN532 (SPI): preamble and postamble must be exactly one
0x00byte. - PN532 → Host: always a single
0x00byte. This can be disabled entirely withSetParametersflagfRemovePrePostAmble(bit 6) to save 2 bytes per frame.
2. Acknowledge / NACK / Error Frames
ACK : 00 00 FF 00 FF 00 ← PN532 ↔ host: "frame received OK"
NACK : 00 00 FF FF 00 00 ← host → PN532 only: "resend your last response"
Error: 00 00 FF 01 FF 7F 81 00 ← PN532 → host: "syntax error at application level"
- The ACK has two roles: acknowledging a received frame, and (when sent by the host during command processing) aborting the current process.
- The NACK is used only by the host, to ask the PN532 to retransmit its last response (after a corrupt/absent response). The PN532 never sends NACK — it just stays silent on a data-link error.
- The Error frame is returned when the PN532 sees an unknown command code or incorrect parameters in an otherwise valid frame.
2.1 Dialog structure
The host is always the master:
host ── command frame ──▶ PN532
host ◀── ACK ──────────── PN532 (must arrive within 15 ms; else host resends)
... PN532 executes ...
host ◀── response frame ─ PN532
host ── (optional ACK) ─▶ PN532
- 15 ms rule (HSU): the ACK must follow the command within 15 ms. If the host sees no ACK, it resends the command.
- Abort: a new command, or a bare ACK, aborts the current process; the PN532 then answers only the last command received.
- Data-link errors that silence the PN532: LCS error, DCS error, framing error (HSU stop bit = 0), HSU timeout (frame not fully received within ~4× a 256-byte frame; e.g. 89 ms at 115200 baud, 44 ms at 230400, 8 ms at 1.288 M).
3. Status Byte and Error Codes
RF commands (InDataExchange, TgGetData, InListPassiveTarget, …) return a
status byte as the first payload byte:
7 6 5 .. 0
NADPresent MI Error code
- bit 7
NADPresent— payload contains a NAD byte (DEP / ISO14443-4 PCD). - bit 6
MI— More Information (chaining) in progress. - bits 0–5 — error code (
0x00= success).
Error code list:
| Code | Cause |
|---|---|
0x00 |
OK |
0x01 |
Time out — target did not answer |
0x02 |
CRC error detected by the CIU |
0x03 |
Parity error detected by the CIU |
0x04 |
Erroneous bit count during anticollision (14443-3 Type A / 18092 106 k) |
0x05 |
Framing error during MIFARE operation |
0x06 |
Abnormal bit-collision during bitwise anticollision at 106 k |
0x07 |
Communication buffer size insufficient |
0x09 |
RF buffer overflow (CIU_Error BufferOvfl) |
0x0A |
RF field not switched on in time by counterpart (active mode) |
0x0B |
RF protocol error |
0x0D |
Temperature error — antenna drivers switched off |
0x0E |
Internal buffer overflow |
0x10 |
Invalid parameter (range / format) |
0x12 |
DEP: unsupported command received from initiator |
0x13 |
DEP / MIFARE / 14443-4: data format does not match spec |
0x14 |
MIFARE: authentication error |
0x23 |
ISO14443-3: UID check byte wrong |
0x25 |
DEP: invalid device state |
0x26 |
Operation not allowed in this configuration |
0x27 |
Command not acceptable in current context (unknown target number, …) |
0x29 |
Target released by its initiator |
0x2A |
14443-3B: card ID mismatch (wrong card) |
0x2B |
14443-3B: previously activated card disappeared |
0x2C |
NFCID3 mismatch (initiator vs target) in DEP 212/424 passive |
0x2D |
Over-current detected |
0x2E |
NAD missing in DEP frame |
4. Transport-Specific Bytes
4.1 HSU / UART
- Full-duplex, up to 1.288 Mbaud (default 115200), 8 data bits, LSB first, 1 stop bit.
- Frames are sent as-is (see §1), preamble/postamble may be 0..n bytes.
- Hardware preamble filter strips
00 00 FFfrom incoming frames.
4.2 I²C
-
Address: 7-bit
0x24; 8-bit write0x48, read0x49(SLV+W = 0x48,SLV+R = 0x49). -
Fast mode up to 400 kHz.
-
The frame is "slightly modified": on reads, a status byte is prepended. On writes (commands) the frame is written verbatim (no status byte).
RDY status byte: bit 0 = RDY (bits 7..1 reserved) RDY = 0 → no frame available RDY = 1 → frame followsRead sequence: START → read 1 status byte → if
RDY == 0, STOP and retry; ifRDY == 1, keep reading the whole frame before STOP. A STOP before the full frame discards the remaining bytes.RDY ... RDY RDY frame
4.3 SPI
-
Slave, SCK up to 5 MHz.
-
Every transfer starts with a direction byte (2 LSBs):
First byte Operation xxxx xx01(0x01)Data write (host → PN532) xxxx xx10(0x02)Status read (PN532 → host) xxxx xx11(0x03)Data read (PN532 → host) -
Status register (1 byte): bit 0 = RDY. Poll
0x02untilRDY == 1, then read with0x03. -
SPI preamble/postamble must be exactly one
0x00byte. -
Optionally use the
P70_IRQpin (handshake) to skip status polling.
5. Host Command Set
Each command is the first payload byte (PD0) after the 0xD4 TFI. In = initiator,
Tg = target.
| Command | Code | Parameters |
|---|---|---|
Diagnose |
0x00 |
NumTst (1), [InParam…] |
GetFirmwareVersion |
0x02 |
— |
GetGeneralStatus |
0x04 |
— |
ReadRegister |
0x06 |
Address (2, big-endian) |
WriteRegister |
0x08 |
Address (2) + Value (1…n) |
ReadGPIO |
0x0C |
— |
WriteGPIO |
0x0E |
P3, P7 |
SetSerialBaudRate |
0x10 |
Baud rate (1) |
SetParameters |
0x12 |
Flags (1) |
SAMConfiguration |
0x14 |
Mode (1), Timeout (1), [IRQ (1)] |
PowerDown |
0x16 |
WakeUpEnable (1), GenerateIRQ (1) |
RFConfiguration |
0x32 |
CfgItem (1), … |
RFRegulationTest |
0x58 |
TxMode (1) |
InJumpForPSL |
0x46 |
ActPSL (1), … |
InJumpForDEP |
0x56 |
ActPass (1), … |
InListPassiveTarget |
0x4A |
MaxTg (1), BrTy (1), [InitiatorData] |
InATR |
0x50 |
— |
InPSL |
0x4E |
Tg (1) |
InDataExchange |
0x40 |
Tg (1), [DataOut…] |
InCommunicateThru |
0x42 |
DataOut… |
InDeselect |
0x44 |
Tg (1) |
InRelease |
0x52 |
Tg (1) |
InSelect |
0x54 |
Tg (1) |
InAutoPoll |
0x60 |
PollNr (1), … |
TgInitAsTarget |
0x8C |
Mode (1), … |
TgGetData |
0x86 |
— |
TgSetData |
0x8E |
DataIn… |
TgGetInitiatorCommand |
0x88 |
— |
TgResponseToInitiator |
0x90 |
Data… |
TgGetTargetStatus |
0x8A |
— |
TgSetGeneralBytes |
0x92 |
Data… |
TgSetMetaData |
0x94 |
Data… |
6. Worked Examples
6.1 GetFirmwareVersion (0x02)
Request:
00 00 FF 02 FE D4 02 2A 00
└─preamble+start─┘ │ │ │ │ └ postamble
LEN=02 ───────┘ │ │ └ DCS=0x2A
LCS=FE ──────────┘ └ command 0x02
TFI=0xD4 ───────────┘
Response (IC=0x32 PN532, firmware 1.6, rev 0x06, support 0x07):
00 00 FF 06 FA D5 03 32 01 06 07 E8 00
│ │ │ │ │ │ └ DCS
│ │ │ │ │ └ support (0x07)
│ │ │ │ └ rev
│ │ │ └ firmware version (0x01 = 1.6)
│ │ └ IC = 0x32 (PN532)
│ └ cmd+1 = 0x03
└ TFI = 0xD5
support bitmask: 0x01 ISO/IEC 14443A, 0x02 ISO/IEC 14443B, 0x04 ISO/IEC 18092 (NFCIP-1).
6.2 SAMConfiguration (0x14)
Params: Mode=0x01 (normal), Timeout=0x14 (20 × 50 ms = 1 s), IRQ=0x01 (drive P70_IRQ).
00 00 FF 05 FB D4 14 01 14 01 02 00
│ │ │ │ │ └ DCS
│ │ │ └ IRQ
│ │ └ Timeout (LSB 50 ms; 0x00 = no timeout)
│ └ Mode (0x01 normal, 0x02 virtual card, 0x03 wired card, 0x04 dual card)
└ command 0x14
Response:
00 00 FF 02 FE D5 15 16 00
6.3 InListPassiveTarget (0x4A)
Params: MaxTg=0x01, BrTy=0x00 (106 kbps ISO/IEC 14443A):
00 00 FF 04 FC D4 4A 01 00 E1 00
│ │ │ └ BrTy (0x00 A, 0x01/0x02 FeliCa 212/424, 0x03 B, 0x04 Jewel)
│ │ └ MaxTg (max 2)
│ └ command 0x4A
Response (0x4B) for a MIFARE card with 4-byte UID DE AD BE EF:
00 00 FF 0C F4 D5 4B 01 01 04 00 08 04 DE AD BE EF 96 00
│ │ │ │ │ │ │ │ └── NFCID1 ──┘ └ DCS
│ │ │ │ │ │ │ └ NFCID length (4)
│ │ │ │ │ │ └ SEL_RES
│ │ │ │ └── SENS_RES (04 00)
│ │ └ target number (Tg = 0x01)
│ └ NbTg = 0x01
└ TFI 0xD5
6.4 InDataExchange (0x40)
Params: Tg=0x01, then the raw card command — MIFARE Classic READ block 4 (0x30 0x04):
00 00 FF 05 FB D4 40 01 30 04 B7 00
│ │ │ └─────┘ └ DCS
│ │ └ MIFARE READ (0x30) block 04
│ └ target Tg = 0x01 (bit 6 = MI for DEP chaining)
└ command 0x40
Success response (0x41): Status=0x00, then 16 data bytes (00..0F):
00 00 FF 13 ED D5 41 00 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 72 00
│ │ └ status 0x00 = success
│ └ cmd+1 = 0x41
└ TFI 0xD5
6.5 ReadRegister (0x06)
Params: register address (2 bytes, big-endian). Reading CIU_RxSel (0x6307):
00 00 FF 04 FC D4 06 63 07 BC 00
Response (0x07) with value 0x84:
00 00 FF 03 FD D5 07 84 A0 00
7. MIFARE / ISO/IEC 14443A Card Bytes
These are the payload bytes passed inside InDataExchange / InCommunicateThru.
| Operation | Card command |
|---|---|
| Authenticate key A / B | 0x60 / 0x61 + block + UID + key (6) |
| Read block | 0x30 + block |
| Write block | 0xA0 + block + 16 data bytes |
| Write (Ultralight) | 0xA2 + page + 4 data bytes |
| Transfer (write commit) | 0xB0 + block |
| Decrement | 0xC0 + block + 4-byte value |
| Increment | 0xC1 + block + 4-byte value |
| Restore | 0xC2 + block |
FeliCa payload codes: Polling 0x00, Request Service 0x02, Request Response 0x04,
Read Without Encryption 0x06, Write Without Encryption 0x08, Request System Code 0x0C.
8. SetParameters (0x12) flags
D4 12 Flags
| Bit | Flag | Meaning |
|---|---|---|
| 0 | fNADUsed |
Use NAD in DEP / 14443-4 PCD |
| 1 | fDIDUsed |
Use DID (DEP) / CID (14443-4 PCD) |
| 2 | fAutomaticATR_RES |
Auto-generate ATR_RES in target mode |
| 4 | fAutomaticRATS |
Auto-send RATS after selecting 14443-4 card |
| 5 | fISO14443-4_PICC |
Emulate ISO14443-4 PICC |
| 6 | fRemovePrePostAmble |
Omit preamble + postamble in frames sent to host |
9. CIU Command Set (low-level, from datasheet §8.6.20)
The firmware drives a Contactless Interface Unit (CIU) whose commands live in the
CIU_Command register. These are not host commands — they surface only via
WriteRegister/ReadRegister. Listed for completeness:
| Command | Code | Action |
|---|---|---|
| Idle | 0000 |
No action; cancel current command |
| Config | 0001 |
Configure CIU for FeliCa / MIFARE / NFCIP-1 |
| GenerateRandomID | 0010 |
Generate 10-byte random ID |
| CalcCRC | 0011 |
Run CRC coprocessor (or self-test) |
| Transmit | 0100 |
Transmit data from FIFO |
| NoCmdChange | 0111 |
Modify CIU_Command bits without changing command |
| Receive | 1000 |
Activate receiver |
| SelfTest | 1001 |
Activate self-test |
| Transceive | 1100 |
Transmit then auto-receive (initiator) or vice-versa |
| AutoColl | 1101 |
FeliCa polling / MIFARE anticollision (card mode) |
| MFAuthent | 1110 |
MIFARE Classic authentication |
| SoftReset | 1111 |
Reset the CIU |
10. Minimum Startup Sequence
- Reset — pulse
RSTPD_N(high → low → wait ~400 ms → high), let the PN532 boot. - GetFirmwareVersion (
0x02) — sanity-check the link and chip. - SAMConfiguration (
0x14, mode0x01) — enable the SAM in normal (reader) mode; required before any RF command. - Poll with InListPassiveTarget (
0x4A), then transact with InDataExchange (0x40).