diff --git a/Cargo.toml b/Cargo.toml index c53c2b7..e9ec0c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ postcard = { version = "1.1.3", default-features = false, features = ["defmt", " serde = { version = "1.0.229", default-features = false, features = ["derive"] } atomic = "0.6.1" bytemuck = { version = "1.25.2", features = ["derive", "alloc_uninit", "extern_crate_alloc", "zeroable_atomics", "zeroable_maybe_uninit"] } -pn532 = { path = "pn532", features = ["defmt", "alloc"] } +pn532 = { path = "pn532", features = ["defmt", "alloc", "felica-lite-s"] } once_cell = { version = "1.21.4", default-features = false, features = ["alloc"] } heapless = { version = "0.9.3", features = ["alloc", "defmt", "serde"] } crossbeam = { version = "0.8.4", default-features = false, features = ["alloc"] } diff --git a/pn532/Cargo.toml b/pn532/Cargo.toml index fa44c89..52852c1 100644 --- a/pn532/Cargo.toml +++ b/pn532/Cargo.toml @@ -22,9 +22,13 @@ des = { version = "~0.9", default-features = false } cbc = { version = "~0.2", default-features = false, features = ["block-padding"] } strum = { version = "0.28.0", default-features = false, features = ["derive", "strum_macros"] } heapless = "~0.9" -thiserror = { version = "2.0.20", default-features = false } +thiserror = { version = "~2", default-features = false } [features] default = [] defmt = ["dep:defmt", "heapless/defmt"] alloc = ["defmt/alloc", "hex/alloc", "heapless/alloc"] +mifare-classic = [] +iso14443a = [] +felica = [] +felica-lite-s = ["felica"] \ No newline at end of file diff --git a/pn532/PROTOCOL.md b/pn532/PROTOCOL.md new file mode 100644 index 0000000..85557cc --- /dev/null +++ b/pn532/PROTOCOL.md @@ -0,0 +1,446 @@ +# 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 +``` + +- `LEN` and `LCS` are fixed to `0xFF`. +- Real length: `LENGTH = LENM × 256 + LENL` = number of bytes in TFI + payload. +- `LCS` satisfies `LENM + 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..n` bytes; the + value has no impact on frame processing. The PN532 only synchronises on the + `0x00 0xFF` start code. +- **Host → PN532** (SPI): preamble and postamble **must** be exactly one `0x00` byte. +- **PN532 → Host**: always a single `0x00` byte. This can be disabled entirely with + `SetParameters` flag `fRemovePrePostAmble` (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 FF` from incoming frames. + +### 4.2 I²C + +- **Address**: 7-bit `0x24`; 8-bit write `0x48`, read `0x49` (`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 follows + ``` + + Read sequence: START → read 1 status byte → if `RDY == 0`, STOP and retry; if + `RDY == 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 `0x02` until `RDY == 1`, then read + with `0x03`. +- SPI preamble/postamble **must** be exactly one `0x00` byte. +- Optionally use the `P70_IRQ` pin (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 + +```text +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 + +1. **Reset** — pulse `RSTPD_N` (high → low → wait ~400 ms → high), let the PN532 boot. +2. **GetFirmwareVersion** (`0x02`) — sanity-check the link and chip. +3. **SAMConfiguration** (`0x14`, mode `0x01`) — enable the SAM in normal (reader) + mode; required before any RF command. +4. Poll with **InListPassiveTarget** (`0x4A`), then transact with **InDataExchange** (`0x40`). diff --git a/pn532/src/commands.rs b/pn532/src/commands.rs index 6551524..5eb6642 100644 --- a/pn532/src/commands.rs +++ b/pn532/src/commands.rs @@ -108,10 +108,41 @@ pub const PN532_GPIO_P34: u8 = 4; pub const PN532_GPIO_P35: u8 = 5; // FeliCa limits. -pub const FELICA_READ_MAX_SERVICE_NUM: usize = 16; -pub const FELICA_READ_MAX_BLOCK_NUM: usize = 12; -pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = 16; -pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = 10; +pub const fn felica_read_max_service_num() -> usize { + if cfg!(feature = "felica-lite-s") { + 8 + } else { + 16 + } +} +pub const fn felica_read_max_block_num() -> usize { + if cfg!(feature = "felica-lite-s") { + 4 + } else { + 12 + } +} +pub const fn felica_write_max_service_num() -> usize { + if cfg!(feature = "felica-lite-s") { + 4 + } else { + 16 + } +} +pub const fn felica_write_max_block_num() -> usize { + if cfg!(feature = "felica-lite-s") { + 2 + } else { + 10 + } +} + +pub const FELICA_READ_MAX_SERVICE_NUM: usize = felica_read_max_service_num(); +pub const FELICA_READ_MAX_BLOCK_NUM: usize = felica_read_max_block_num(); + +pub const FELICA_WRITE_MAX_SERVICE_NUM: usize = felica_write_max_service_num(); +pub const FELICA_WRITE_MAX_BLOCK_NUM: usize = felica_write_max_block_num(); + pub const FELICA_REQ_SERVICE_MAX_NODE_NUM: usize = 32; // Frame protocol constants. diff --git a/pn532/src/driver.rs b/pn532/src/driver.rs index bd86c4a..a2b9f0f 100644 --- a/pn532/src/driver.rs +++ b/pn532/src/driver.rs @@ -3,8 +3,8 @@ use crate::commands::*; use crate::error::Error; use crate::interface::Interface; - -pub(crate) const PACKET_BUFFER_SIZE: usize = 64; +use crate::StatusCode; +use core::time::Duration; /// A card UID read by [`Pn532::read_passive_target_id`]. #[derive(Debug, Clone, PartialEq, Eq)] @@ -46,35 +46,20 @@ pub enum TargetInitStatus { Failed, } -/// Result of a successful FeliCa polling request. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct FelicaPollingResponse { - /// The card's IDm (NFCID2). - pub idm: [u8; 8], - /// The card's PMm (PAD). - pub pmm: [u8; 8], - /// The card's system code, when returned. - pub system_code_response: Option, -} - /// Driver for the NXP PN532 NFC controller. -pub struct Pn532 { +pub struct Pn532 { pub(crate) interface: B, pub(crate) in_listed_tag: u8, - pub(crate) felica_idm: [u8; 8], - pub(crate) felica_pmm: [u8; 8], - pub(crate) buffer: [u8; PACKET_BUFFER_SIZE], + pub(crate) buffer: [u8; N], } -impl Pn532 { +impl Pn532 { /// Create a driver over the given transport interface. pub fn new(interface: B) -> Self { Self { interface, in_listed_tag: 0, - felica_idm: [0; 8], - felica_pmm: [0; 8], - buffer: [0; PACKET_BUFFER_SIZE], + buffer: [0; N], } } @@ -103,8 +88,8 @@ impl Pn532 { self.interface.read_response(&mut self.buffer, 1000) } - pub(crate) fn read_timeout(&mut self, timeout_ms: u16) -> Result> { - self.interface.read_response(&mut self.buffer, timeout_ms) + pub(crate) fn read_timeout(&mut self, timeout: Duration) -> Result> { + self.interface.read_response(&mut self.buffer, timeout.as_millis() as u16) } // -- generic PN532 functions ------------------------------------------- @@ -210,4 +195,26 @@ impl Pn532 { self.read()?; Ok(()) } + + /// return error if current buffer contains an error code + pub(crate) fn validate_buffer(&self, len: usize) -> Result<(), Error> { + if len == 0 || (self.buffer[0] & 0x3F) != 0 { + Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap())) + } else { + Ok(()) + } + } + + + + /// Release the card. + pub fn release(&mut self) -> Result<(), Error> { + self.buffer[0] = PN532_COMMAND_INRELEASE; + self.buffer[1] = 0x00; + self.send(2)?; + + let len = self.read_timeout(Duration::from_secs(1))?; + self.validate_buffer(len)?; + Ok(()) + } } diff --git a/pn532/src/error.rs b/pn532/src/error.rs index d4ad258..a52ba0a 100644 --- a/pn532/src/error.rs +++ b/pn532/src/error.rs @@ -1,3 +1,4 @@ +use core::fmt::Debug; use defmt::Debug2Format; use strum::FromRepr; @@ -25,20 +26,17 @@ pub enum Error { /// The device returned a non-zero status code. #[error("Received non zero status code: {0:?}")] Status(StatusCode), + #[error("Invalid baud rate")] + InvalidBaudRate, + #[cfg(feature = "felica")] + #[error("Felica error")] + FelicaError } #[cfg(feature = "defmt")] -impl defmt::Format for Error { +impl defmt::Format for Error { fn format(&self, fmt: defmt::Formatter) { - match self { - Error::Transport(e) => defmt::write!(fmt, "transport error: {}", e), - Error::Timeout => defmt::write!(fmt, "timeout"), - Error::InvalidAck => defmt::write!(fmt, "invalid ACK frame"), - Error::InvalidFrame => defmt::write!(fmt, "invalid frame"), - Error::NoSpace => defmt::write!(fmt, "not enough space in buffer"), - Error::InvalidParam => defmt::write!(fmt, "invalid parameter"), - Error::Status(code) => defmt::write!(fmt, "status error {:?}", code), - } + defmt::write!(fmt, "{:?}", Debug2Format(self)) } } diff --git a/pn532/src/felica/mod.rs b/pn532/src/felica/mod.rs index 5775c2c..869ed4c 100644 --- a/pn532/src/felica/mod.rs +++ b/pn532/src/felica/mod.rs @@ -1,34 +1,43 @@ mod types; +pub(crate) use types::AsBlock; +pub use types::Block as FelicaBlock; +pub use types::PollingRequestCode as FelicaPollingRequestCode; +pub use types::PollingResponse as FelicaPollingResponse; +pub use types::ServiceCode as FelicaServiceCode; + +use crate::commands::*; use crate::driver::Pn532; use crate::error::{Error, StatusCode}; use crate::interface::Interface; - -use crate::commands::{FELICA_CMD_POLLING, FELICA_CMD_READ_WITHOUT_ENCRYPTION, FELICA_CMD_REQUEST_RESPONSE, FELICA_CMD_REQUEST_SERVICE, FELICA_CMD_REQUEST_SYSTEM_CODE, FELICA_CMD_WRITE_WITHOUT_ENCRYPTION, FELICA_READ_MAX_BLOCK_NUM, FELICA_READ_MAX_SERVICE_NUM, FELICA_REQ_SERVICE_MAX_NODE_NUM, FELICA_WRITE_MAX_BLOCK_NUM, FELICA_WRITE_MAX_SERVICE_NUM, PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INLISTPASSIVETARGET, PN532_COMMAND_INRELEASE}; -use crate::{BaudRate, FelicaPollingResponse}; -pub(crate) use types::AsBlock; -pub use types::Block as FelicaBlock; +use crate::BaudRate; +use core::time::Duration; use types::*; -impl Pn532 { +impl Pn532 { /// Poll for a FeliCa card. pub fn felica_polling( &mut self, - system_code: u16, - request_code: u8, - timeout_ms: u16, - ) -> Result, Error> { + system_code: Option, + baud_rate: BaudRate, + request_code: PollingRequestCode, + timeout: Duration, + ) -> Result, Error> { + if !matches!(baud_rate, BaudRate::Felica212kbps | BaudRate::Felica424kbps) { + return Err(Error::InvalidBaudRate); + } + let system_code = system_code.unwrap_or(0xFFFF); self.buffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; self.buffer[1] = 1; - self.buffer[2] = BaudRate::Felica212kbps.into(); + self.buffer[2] = baud_rate.into(); self.buffer[3] = FELICA_CMD_POLLING; self.buffer[4] = (system_code >> 8) as u8; self.buffer[5] = system_code as u8; - self.buffer[6] = request_code; + self.buffer[6] = request_code as u8; self.buffer[7] = 0; self.send(8)?; - match self.read_timeout(timeout_ms) { + match self.read_timeout(timeout) { Err(Error::Timeout) => return Ok(None), Err(e) => return Err(e), Ok(_) => {} @@ -49,11 +58,11 @@ impl Pn532 { let mut idm = [0u8; 8]; idm.copy_from_slice(&self.buffer[4..12]); - self.felica_idm = idm; + let idm = IDm::from(idm); let mut pmm = [0u8; 8]; pmm.copy_from_slice(&self.buffer[12..20]); - self.felica_pmm = pmm; + let pmm = PMm::from(pmm); let system_code_response = if response_length == 20 { Some(u16::from_be_bytes([self.buffer[20], self.buffer[21]])) @@ -61,7 +70,7 @@ impl Pn532 { None }; - Ok(Some(FelicaPollingResponse { + Ok(Some(PollingResponse { idm, pmm, system_code_response, @@ -75,6 +84,7 @@ impl Pn532 { &mut self, command: &[u8], response: &mut [u8], + timeout: Duration, ) -> Result> { if command.len() > 0xFE { return Err(Error::InvalidParam); @@ -84,10 +94,8 @@ impl Pn532 { self.buffer[2] = (command.len() + 1) as u8; self.send_with_body(3, command)?; - let len = self.read_timeout(200)?; - if len == 0 || (self.buffer[0] & 0x3F) != 0 { - return Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap())); - } + let len = self.read_timeout(timeout)?; + self.validate_buffer(len)?; let response_len = self.buffer[1] as usize - 1; if len - 2 != response_len { @@ -103,6 +111,7 @@ impl Pn532 { /// Send a FeliCa "Request Service" command. pub fn felica_request_service( &mut self, + card: &IDm, node_code_list: &[u16], key_versions: &mut [u16], ) -> Result<(), Error> { @@ -115,7 +124,7 @@ impl Pn532 { let mut j = 0; cmd[j] = FELICA_CMD_REQUEST_SERVICE; j += 1; - cmd[j..j + 8].copy_from_slice(&self.felica_idm); + cmd[j..j + 8].copy_from_slice(&card.to_bytes()); j += 8; cmd[j] = num_node as u8; j += 1; @@ -126,7 +135,11 @@ impl Pn532 { } let mut response = [0u8; 10 + 2 * FELICA_REQ_SERVICE_MAX_NODE_NUM]; - let response_len = self.felica_send_command(&cmd[..j], &mut response)?; + let response_len = self.felica_send_command( + &cmd[..j], + &mut response, + Duration::from_millis(200) + )?; if response_len != 10 + 2 * num_node { return Err(Error::InvalidFrame); } @@ -137,13 +150,13 @@ impl Pn532 { } /// Send a FeliCa "Request Response" command, returning the card's mode. - pub fn felica_request_response(&mut self) -> Result> { + pub fn felica_request_response(&mut self, card: &PollingResponse) -> Result> { let mut cmd = [0u8; 9]; cmd[0] = FELICA_CMD_REQUEST_RESPONSE; - cmd[1..9].copy_from_slice(&self.felica_idm); + cmd[1..9].copy_from_slice(&card.idm.to_bytes()); let mut response = [0u8; 10]; - let response_len = self.felica_send_command(&cmd, &mut response)?; + let response_len = self.felica_send_command(&cmd, &mut response, Duration::from_millis(200))?; if response_len != 10 { return Err(Error::InvalidFrame); } @@ -155,10 +168,28 @@ impl Pn532 { /// Note: the number of blocks is limited by the 64-byte response buffer. pub fn felica_read_without_encryption( &mut self, - service_code_list: &[u16], - block_list: &[u16], + card: &IDm, + pmm: &PMm, + service_code_list: &[impl AsServiceCode], + block_list: &[impl AsBlock], block_data: &mut [[u8; 16]], ) -> Result<(), Error> { + const COMMAND_SIZE: usize = + 1 // command code (0x06) + + 8 // idm + + 1 // service length + + 2 * FELICA_READ_MAX_SERVICE_NUM // service code list (2 * service length) + + 1 // block length (1<=n<=4) + + 2 * FELICA_READ_MAX_BLOCK_NUM; // block list (2n<=N<=3n), hardcoded to use 2 byte block list only + const RESPONSE_SIZE: usize = + 1 // response code (0x07) + + 8 // IDm + + 1 // status[0] + + 1 // status[1] + + 1 // block length + + 16 * FELICA_READ_MAX_BLOCK_NUM; // block data + + // validate let num_service = service_code_list.len(); let num_block = block_list.len(); if num_service > FELICA_READ_MAX_SERVICE_NUM @@ -168,29 +199,31 @@ impl Pn532 { return Err(Error::InvalidParam); } - let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_READ_MAX_SERVICE_NUM + 1 + 2 * FELICA_READ_MAX_BLOCK_NUM]; - let mut j = 0; - cmd[j] = FELICA_CMD_READ_WITHOUT_ENCRYPTION; - j += 1; - cmd[j..j + 8].copy_from_slice(&self.felica_idm); - j += 8; - cmd[j] = num_service as u8; - j += 1; - for &sc in service_code_list { - cmd[j] = sc as u8; - cmd[j + 1] = (sc >> 8) as u8; - j += 2; + // command + let mut cmd = heapless::Vec::::new(); + cmd.push(FELICA_CMD_READ_WITHOUT_ENCRYPTION).unwrap(); + cmd.extend_from_slice(&card.to_bytes()).unwrap(); + cmd.push(num_service as u8).unwrap(); + for sc in service_code_list { + let sc: [u8; 2] = sc.as_service_code().to_le_bytes(); + cmd.extend_from_slice(&sc).unwrap(); } - cmd[j] = num_block as u8; - j += 1; - for &bl in block_list { - cmd[j] = (bl >> 8) as u8; - cmd[j + 1] = bl as u8; - j += 2; + cmd.push(num_block as u8).unwrap(); + for bl in block_list { + let bl: [u8; 2] = bl.as_block().to_be_bytes(); + cmd.extend_from_slice(&bl).unwrap(); } - let mut response = [0u8; 12 + 16 * FELICA_READ_MAX_BLOCK_NUM]; - let response_len = self.felica_send_command(&cmd[..j], &mut response)?; + // response + let mut response = [0u8; RESPONSE_SIZE]; + // PMm gives the card's *processing* time only; add RF TX/RX + PN532 + // overhead margin so we never abandon a command mid-flight (which would + // desync the host/PN532 framing). + let response_len = self.felica_send_command( + &cmd, + &mut response, + pmm.get_read_timeout(num_block, Some(Duration::from_millis(50))) + )?; if response_len != 12 + 16 * num_block { return Err(Error::InvalidFrame); } @@ -209,10 +242,27 @@ impl Pn532 { /// Send a FeliCa "Write Without Encryption" command. pub fn felica_write_without_encryption( &mut self, - service_code_list: &[u16], + card: &IDm, + pmm: &PMm, + service_code_list: &[impl AsServiceCode], block_list: &[impl AsBlock], block_data: &[[u8; 16]], ) -> Result<(), Error> { + const COMMAND_SIZE: usize = + 1 // command code + + 8 // IDm + + 1 // service len (m) + + 2 * FELICA_WRITE_MAX_SERVICE_NUM // service code (2 * m) + + 1 // block len (n) + + 2 * FELICA_WRITE_MAX_BLOCK_NUM // block list (2n<=N<=3n) (hardcoded to 2 bytes only) + + 16 * FELICA_WRITE_MAX_BLOCK_NUM; // block data (16 * n) + const RESPONSE_SIZE: usize = + 1 // command code + + 8 // IDm + + 1 // status[0] + + 1; // status[1] + + // validate let num_service = service_code_list.len(); let num_block = block_list.len(); if num_service > FELICA_WRITE_MAX_SERVICE_NUM @@ -222,36 +272,32 @@ impl Pn532 { return Err(Error::InvalidParam); } - let mut cmd = [0u8; 1 + 8 + 1 + 2 * FELICA_WRITE_MAX_SERVICE_NUM + 1 + 2 * FELICA_WRITE_MAX_BLOCK_NUM - + 16 * FELICA_WRITE_MAX_BLOCK_NUM]; - let mut j = 0; - cmd[j] = FELICA_CMD_WRITE_WITHOUT_ENCRYPTION; - j += 1; - cmd[j..j + 8].copy_from_slice(&self.felica_idm); - j += 8; - cmd[j] = num_service as u8; - j += 1; - for &sc in service_code_list { - cmd[j] = sc as u8; - cmd[j + 1] = (sc >> 8) as u8; - j += 2; + // command + let mut cmd = heapless::Vec::::new(); + cmd.push(FELICA_CMD_WRITE_WITHOUT_ENCRYPTION).unwrap(); + cmd.extend_from_slice(&card.to_bytes()).unwrap(); + cmd.push(num_service as u8).unwrap(); + for sc in service_code_list { + let sc = sc.as_service_code().to_le_bytes(); + cmd.extend_from_slice(&sc).unwrap(); } - cmd[j] = num_block as u8; - j += 1; + cmd.push(num_block as u8).unwrap(); for block in block_list { - let block = block.as_block(); - cmd[j] = (block >> 8) as u8; - cmd[j + 1] = block as u8; - j += 2; + let block = block.as_block().to_be_bytes(); + cmd.extend_from_slice(&block).unwrap(); } for block in block_data.iter().take(num_block) { - cmd[j..j + 16].copy_from_slice(block); - j += 16; + cmd.extend_from_slice(block).unwrap(); } - let mut response = [0u8; 11]; - let response_len = self.felica_send_command(&cmd[..j], &mut response)?; - if response_len != 11 { + // response + let mut response = [0u8; RESPONSE_SIZE]; + let response_len = self.felica_send_command( + &cmd, + &mut response, + pmm.get_write_timeout(num_block, Some(Duration::from_millis(50))) + )?; + if response_len != RESPONSE_SIZE { return Err(Error::InvalidFrame); } if response[9] != 0 || response[10] != 0 { @@ -265,14 +311,15 @@ impl Pn532 { /// Returns the number of system codes written to `system_code_list`. pub fn felica_request_system_code( &mut self, + card: &IDm, system_code_list: &mut [impl AsServiceCode], ) -> Result> { let mut cmd = [0u8; 9]; cmd[0] = FELICA_CMD_REQUEST_SYSTEM_CODE; - cmd[1..9].copy_from_slice(&self.felica_idm); + cmd[1..9].copy_from_slice(&card.to_bytes()); let mut response = [0u8; 10 + 2 * 16]; - let response_len = self.felica_send_command(&cmd, &mut response)?; + let response_len = self.felica_send_command(&cmd, &mut response, Duration::from_millis(200))?; if response_len < 10 { return Err(Error::InvalidFrame); } @@ -290,17 +337,4 @@ impl Pn532 { } Ok(num_system_code) } - - /// Release the FeliCa card. - pub fn felica_release(&mut self) -> Result<(), Error> { - self.buffer[0] = PN532_COMMAND_INRELEASE; - self.buffer[1] = 0x00; - self.send(2)?; - - let len = self.read_timeout(1000)?; - if len == 0 || (self.buffer[0] & 0x3F) != 0 { - return Err(Error::Status(StatusCode::from_repr(self.buffer[0] & 0x3F).unwrap())); - } - Ok(()) - } } diff --git a/pn532/src/felica/types/idm.rs b/pn532/src/felica/types/idm.rs new file mode 100644 index 0000000..5a62929 --- /dev/null +++ b/pn532/src/felica/types/idm.rs @@ -0,0 +1,30 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IDm { + pub manufacturer: u8, + pub card_id: [u8; 7] +} + +impl IDm { + /// The raw 8 bytes in wire order (manufacturer, ic_type, card_id). + pub fn to_bytes(&self) -> [u8; 8] { + let mut data = [0u8; 8]; + data[0] = self.manufacturer; + data[1..].copy_from_slice(&self.card_id); + data + } +} + +impl From<[u8; 8]> for IDm { + fn from(value: [u8; 8]) -> Self { + Self { + manufacturer: value[0], + card_id: value[1..].try_into().unwrap() + } + } +} + +impl Into<[u8; 8]> for IDm { + fn into(self) -> [u8; 8] { + self.to_bytes() + } +} \ No newline at end of file diff --git a/pn532/src/felica/types.rs b/pn532/src/felica/types/mod.rs similarity index 84% rename from pn532/src/felica/types.rs rename to pn532/src/felica/types/mod.rs index 84934ec..2e59c40 100644 --- a/pn532/src/felica/types.rs +++ b/pn532/src/felica/types/mod.rs @@ -1,3 +1,11 @@ +mod polling; +mod pmm; +mod idm; + +pub use idm::*; +pub use pmm::*; +pub use polling::*; + use strum::FromRepr; pub trait AsServiceCode { @@ -68,7 +76,8 @@ impl AsBlock for Block { impl AsBlock for u16 { fn as_block(&self) -> u16 { - *self + // A 2-byte block list element is `0x80 | block_number`: the MSB marks a + // 2-byte element (access mode 0 = standard area, service list order 0). + *self | 0x8000 } -} - +} \ No newline at end of file diff --git a/pn532/src/felica/types/pmm.rs b/pn532/src/felica/types/pmm.rs new file mode 100644 index 0000000..4ef2fd5 --- /dev/null +++ b/pn532/src/felica/types/pmm.rs @@ -0,0 +1,54 @@ +use core::time::Duration; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PMm { + pub rom_type: u8, + pub ic_type: u8, + pub read_timeout: u8, + pub write_timeout: u8, +} + +impl From<[u8; 8]> for PMm { + fn from(value: [u8; 8]) -> Self { + Self { + rom_type: value[0], + ic_type: value[1], + read_timeout: value[5], + write_timeout: value[6], + } + } +} + +impl PMm { + pub fn to_bytes(&self) -> [u8; 8] { + let mut data = [0u8; 8]; + data[0] = self.rom_type; + data[1] = self.ic_type; + data[5] = self.read_timeout; + data[6] = self.write_timeout; + data + } + + /// Maximum response time for a Read command over `block_len` blocks: + pub fn get_read_timeout(&self, block_len: usize, margin: Option) -> Duration { + Self::response_time(self.read_timeout, block_len) + margin.unwrap_or(Duration::default()) + } + + /// Maximum response time for a Write command over `block_len` blocks. + pub fn get_write_timeout(&self, block_len: usize, margin: Option) -> Duration { + Self::response_time(self.write_timeout, block_len) + margin.unwrap_or(Duration::default()) + } + + /// `T x [(B+1)*n + (A+1)] x 4^E`, with `T = 256*16/fc ~= 302.06 us`. + fn response_time(param: u8, block_len: usize) -> Duration { + let e = ((param & 0b1100_0000) >> 6) as u32; + let a = ((param & 0b0011_1000) >> 3) as f64; + let b = (param & 0b0000_0111) as f64; + // T = 256 * 16 / fc; fc = 13.56 MHz => 4096 / 13.56 us. + let t_us = 256.0 * 16.0 / 13.56; + let mult = (1u32 << (2 * e)) as f64; // 4^E = 1, 4, 16, 64 + let us = t_us * ((b + 1.0) * block_len as f64 + (a + 1.0)) * mult; + // Round up: a timeout must never underestimate the max response time. + Duration::from_micros(us as u64 + 1) + } +} \ No newline at end of file diff --git a/pn532/src/felica/types/polling.rs b/pn532/src/felica/types/polling.rs new file mode 100644 index 0000000..821cdee --- /dev/null +++ b/pn532/src/felica/types/polling.rs @@ -0,0 +1,34 @@ +use super::{IDm, PMm}; +use strum::FromRepr; + +#[derive(FromRepr, Copy, Clone, Debug, PartialEq, Eq, Default)] +#[repr(u8)] +pub enum PollingRequestCode { + NoRequest, + #[default] + SystemCode, + CommunicationPerformance +} + +#[derive(FromRepr, Copy, Clone, Debug, PartialEq, Eq, Default)] +#[repr(u8)] +pub enum PollingTimeSlot { + #[default] + _1 = 0x00, + _2 = 0x01, + _4 = 0x03, + _8 = 0x07, + _16 = 0x0F, +} + + +/// Result of a successful FeliCa polling request. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PollingResponse { + /// The card's IDm (NFCID2). + pub idm: IDm, + /// The card's PMm (PAD). + pub pmm: PMm, + /// The card's system code, when returned. + pub system_code_response: Option, +} \ No newline at end of file diff --git a/pn532/src/interface.rs b/pn532/src/interface.rs index b357c86..9d9b149 100644 --- a/pn532/src/interface.rs +++ b/pn532/src/interface.rs @@ -27,31 +27,15 @@ const WRITE_FRAME_CAPACITY: usize = 64; #[derive(Clone, Copy, Debug, Default)] pub struct NoReset; -/// A PN532 reset pin (`RSTPD_N`, active-low). -/// -/// Implemented for any [`OutputPin`] as well as for [`NoReset`] (which does -/// nothing). A reset pin is optional but strongly recommended: without a reset -/// pulse the PN532 can end up in an uninitialised state and NACK subsequent -/// commands (surfacing as `Error::Transport(AcknowledgeCheckFailed(Data))`). -pub trait ResetPin { - /// Assert reset (drive the pin low). - fn assert(&mut self); - /// Deassert reset (drive the pin high). - fn deassert(&mut self); -} +impl ErrorType for NoReset { type Error = Infallible; } -impl ResetPin for NoReset { - fn assert(&mut self) {} - fn deassert(&mut self) {} -} - -impl ResetPin for P { - fn assert(&mut self) { - let _ = self.set_low(); +impl OutputPin for NoReset { + fn set_low(&mut self) -> Result<(), Self::Error> { + panic!("NoReset should not be used") } - fn deassert(&mut self) { - let _ = self.set_high(); + fn set_high(&mut self) -> Result<(), Self::Error> { + panic!("NoReset should not be used") } } @@ -126,7 +110,7 @@ impl I2cInterface { } } -impl I2cInterface { +impl I2cInterface { /// Create an interface with a reset pin (`RSTPD_N`). pub fn with_reset(i2c: I2C, delay: D, reset: RST) -> Self { Self { @@ -152,7 +136,7 @@ impl I2cInterface { } } -impl I2cInterface { +impl I2cInterface { /// Create an interface with both a reset pin and an IRQ pin. pub fn with_reset_irq(i2c: I2C, delay: D, reset: RST, irq: IRQ) -> Self { Self { @@ -176,7 +160,7 @@ impl Interface for I2cInterface where I2C: I2c, D: DelayNs, - RST: ResetPin, + RST: OutputPin, IRQ: InputPin, { type TransportError = I2C::Error; @@ -184,10 +168,10 @@ where fn begin(&mut self) -> Result<(), Error> { // Pulse RSTPD_N: high -> low -> wait -> high -> wait. This mirrors the // Adafruit library's begin() reset sequence. - self.reset.deassert(); - self.reset.assert(); + self.reset.set_high().unwrap(); + self.reset.set_low().unwrap(); self.delay.delay_ms(400); - self.reset.deassert(); + self.reset.set_high().unwrap(); // Let the PN532 boot after the reset is released. The Adafruit library // waits ~10 ms + a 500 ms wakeup here; give it a full 500 ms. self.delay.delay_ms(500); @@ -230,13 +214,11 @@ where frame[idx] = (!sum).wrapping_add(1); frame[idx + 1] = PN532_POSTAMBLE; - #[cfg(feature = "defmt")] - defmt::debug!("pn532: write cmd=0x{:02X} len={}", header[0], frame_len); + crate::debug!("pn532: write cmd=0x{:02X} len={}", header[0], frame_len); self.i2c .write(PN532_I2C_ADDRESS, &frame[..frame_len]) .map_err(Error::Transport)?; - #[cfg(feature = "defmt")] - defmt::debug!("pn532: write ACKed, reading ACK frame"); + crate::debug!("pn532: write ACKed, reading ACK frame"); self.read_ack_frame() } diff --git a/pn532/src/iso14443a/mod.rs b/pn532/src/iso14443a/mod.rs index 97b40b9..66aba4b 100644 --- a/pn532/src/iso14443a/mod.rs +++ b/pn532/src/iso14443a/mod.rs @@ -2,7 +2,7 @@ use crate::commands::{PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INLISTPASSIVET use crate::error::StatusCode; use crate::{BaudRate, Error, Interface, Pn532, Uid}; -impl Pn532 { +impl Pn532 { /// Wait for an ISO14443A target and read its UID. /// /// Returns `Ok(None)` when no card appears within `timeout_ms` milliseconds. diff --git a/pn532/src/lib.rs b/pn532/src/lib.rs index 4a1dcb2..bfc1197 100644 --- a/pn532/src/lib.rs +++ b/pn532/src/lib.rs @@ -43,17 +43,51 @@ //! * The I2C interface is blocking and uses busy-wait polling for the PN532's //! "ready" flag, mirroring the original Arduino implementation. +extern crate alloc; + pub mod commands; pub mod error; +#[cfg(feature = "felica")] pub mod felica; pub mod interface; - pub(crate) mod driver; pub mod tg; +#[cfg(feature = "mifare-classic")] pub mod mifare; +#[cfg(feature = "iso14443a")] pub mod iso14443a; -pub use commands::BaudRate; -pub use driver::{FelicaPollingResponse, Pn532, TargetInitStatus, Uid}; -pub use error::Error; -pub use interface::{I2cInterface, Interface, NoIrq, NoReset}; +pub use commands::*; +pub use driver::*; +pub use error::*; +pub use interface::*; + +#[cfg(feature = "defmt")] +use defmt::debug; + +#[cfg(not(feature = "defmt"))] +#[macro_export] +macro_rules! error { + ($($arg:tt)+) => {}; +} +#[cfg(not(feature = "defmt"))] +#[macro_export] +macro_rules! warn { + ($($arg:tt)+) => {}; +} +#[cfg(not(feature = "defmt"))] +#[macro_export] +macro_rules! info { + ($($arg:tt)+) => {}; +} +#[cfg(not(feature = "defmt"))] +#[macro_export] +macro_rules! debug { + ($($arg:tt)+) => {}; +} +#[cfg(not(feature = "defmt"))] +#[macro_export] +macro_rules! trace { + ($($arg:tt)+) => {}; +} + diff --git a/pn532/src/mifare/mod.rs b/pn532/src/mifare/mod.rs index 3f6481b..421840b 100644 --- a/pn532/src/mifare/mod.rs +++ b/pn532/src/mifare/mod.rs @@ -2,7 +2,7 @@ use crate::commands::{MIFARE_CMD_AUTH_A, MIFARE_CMD_AUTH_B, MIFARE_CMD_READ, MIF use crate::error::StatusCode; use crate::{Error, Interface, Pn532}; -impl Pn532 { +impl Pn532 { /// Whether the block number is the first block of a sector. pub fn mifare_classic_is_first_block(block: u32) -> bool { if block < 128 { diff --git a/pn532/src/tg/mod.rs b/pn532/src/tg/mod.rs index 1577bee..d52e3d9 100644 --- a/pn532/src/tg/mod.rs +++ b/pn532/src/tg/mod.rs @@ -1,8 +1,9 @@ use crate::commands::{PN532_COMMAND_TGGETDATA, PN532_COMMAND_TGINITASTARGET, PN532_COMMAND_TGSETDATA}; use crate::error::StatusCode; use crate::{Error, Interface, Pn532, TargetInitStatus}; +use core::time::Duration; -impl Pn532 { +impl Pn532 { /// Initialize the PN532 as a target using a raw command frame. pub fn tg_init_as_target( &mut self, @@ -36,7 +37,7 @@ impl Pn532 { self.buffer[0] = PN532_COMMAND_TGGETDATA; self.send(1)?; - let len = self.read_timeout(3000)?; + let len = self.read_timeout(Duration::from_millis(3000))?; if len == 0 { return Ok(0); } @@ -55,7 +56,7 @@ impl Pn532 { header: &[u8], body: &[u8], ) -> Result<(), Error> { - if header.len() > crate::driver::PACKET_BUFFER_SIZE - 1 { + if header.len() > N - 1 { self.buffer[0] = PN532_COMMAND_TGSETDATA; self.interface.write_command(&self.buffer[..1], header)?; } else { @@ -65,7 +66,7 @@ impl Pn532 { .write_command(&self.buffer[..1 + header.len()], body)?; } - let len = self.read_timeout(3000)?; + let len = self.read_timeout(Duration::from_millis(3000))?; if len == 0 || self.buffer[0] != 0 { return Err(Error::Status(StatusCode::from_repr(self.buffer[0]).unwrap())); } diff --git a/src/board.rs b/src/board.rs index 8a82f08..d16d327 100644 --- a/src/board.rs +++ b/src/board.rs @@ -56,7 +56,8 @@ impl Board { let i2c_bus = I2C_BUS.init(BlockingMutex::new(RefCell::new(async_i2c))); let irq = gpio::Input::new(peripherals.GPIO6, InputConfig::default().with_pull(Pull::Up)); - let card_reader = CardReader::new(SharedI2c::new(i2c_bus), irq); + let reset = gpio::Output::new(peripherals.GPIO5, Level::High, Default::default()); + let card_reader = CardReader::new(SharedI2c::new(i2c_bus), irq, reset); let vext = gpio::Output::new(peripherals.GPIO36, Level::Low, Default::default()); let lora = { @@ -93,6 +94,7 @@ impl Board { } pub async fn start(mut self) -> Result<(), crate::error::Error> { + self.card_reader.init()?; self.lora.try_join().await?; defmt::info!("Board started"); LED_STATE.store(true, Ordering::Relaxed); diff --git a/src/components/card_reader.rs b/src/components/card_reader.rs index 0bdb5ea..9f104b2 100644 --- a/src/components/card_reader.rs +++ b/src/components/card_reader.rs @@ -6,8 +6,8 @@ use embassy_time::{Delay, Timer}; use esp_hal::gpio; use esp_hal::rng::Trng; use lorawan_device::RngCore; -use pn532::felica::FelicaBlock; -use pn532::Pn532; +use pn532::felica::{FelicaBlock, FelicaPollingRequestCode, FelicaServiceCode}; +use pn532::{BaudRate, Pn532}; static CARD_KEY: OnceLock> = OnceLock::new(); @@ -21,17 +21,18 @@ fn get_card_key() -> &'static heapless::Vec { } pub struct CardReader<'a> { - driver: Pn532, Delay, pn532::NoReset, gpio::Input<'a>>> + driver: Pn532, Delay, gpio::Output<'a>, gpio::Input<'a>>> } impl<'a> CardReader<'a> { - pub fn new(i2c: SharedI2c<'a>, irq: gpio::Input<'a>) -> Self { + pub fn new(i2c: SharedI2c<'a>, irq: gpio::Input<'a>, reset: gpio::Output<'a>) -> Self { Self { - driver: Pn532::new(pn532::I2cInterface::with_irq(i2c, Delay, irq)) + driver: Pn532::new(pn532::I2cInterface::with_reset_irq(i2c, Delay, reset, irq)) } } pub fn init(&mut self) -> Result<(), crate::error::Error> { + self.driver.begin()?; self.driver.sam_config()?; Ok(()) } @@ -50,20 +51,43 @@ pub async fn start_card_reader( loop { Timer::after_millis(50).await; - let poll_result = match card_reader.driver.felica_polling(0xFFFF, 0x01, 1000) { + let Some(response) = (match card_reader.driver.felica_polling( + None, + BaudRate::Felica424kbps, + FelicaPollingRequestCode::SystemCode, + core::time::Duration::from_secs(1), + ) { Ok(v) => v, Err(e) => { defmt::error!("card_reader driver error: {}", e); continue; } - }; - let Some(res) = poll_result else { + }) else { defmt::trace!("No card detected"); continue; }; - defmt::info!("Polling card: {}", Debug2Format(&res)); + defmt::info!("Polling card: {}", Debug2Format(&response)); + + let mut data = [[0u8; 16]; 3]; + // user blocks 0x0000..0x000D via the raw u16 AsBlock impl; a single read + // is capped at 3 blocks by the 64-byte packet buffer (response = 14 + 16n). + let blocks: [u16; 3] = [0x0000, 0x0001, 0x0002]; + match card_reader.driver.felica_read_without_encryption( + &response.idm, + &response.pmm, + &[FelicaServiceCode::Read], + &blocks, + &mut data, + ) { + Ok(_) => {}, + Err(e) => { + defmt::error!("unable to read blocks: {}", e); + continue; + } + } + defmt::debug!("Blocks: {:?}", data); - match card_reader.driver.felica_release() { + match card_reader.driver.release() { Ok(_) => {}, Err(e) => { defmt::error!("card_reader release error: {}", e); @@ -76,5 +100,6 @@ pub async fn start_card_reader( if let Err(e) = card_reader.driver.set_rf_field(0x00, 0x01) { defmt::error!("card_reader RF field error: {}", e); } + Timer::after_millis(200).await; } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index bf8ce48..4e1e908 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,7 @@ async fn main(spawner: Spawner) -> () { } }; defmt::info!("Board initialized"); - + if let Err(e) = board.start().await { defmt::error!("Error: {}", e); }