Add read/write for felica
felica types refactor
This commit is contained in:
+1
-1
@@ -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"] }
|
||||
|
||||
+5
-1
@@ -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"]
|
||||
@@ -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`).
|
||||
+35
-4
@@ -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.
|
||||
|
||||
+30
-23
@@ -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<u16>,
|
||||
}
|
||||
|
||||
/// Driver for the NXP PN532 NFC controller.
|
||||
pub struct Pn532<B: Interface> {
|
||||
pub struct Pn532<B: Interface, const N: usize = 64> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// 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<B: Interface> Pn532<B> {
|
||||
self.interface.read_response(&mut self.buffer, 1000)
|
||||
}
|
||||
|
||||
pub(crate) fn read_timeout(&mut self, timeout_ms: u16) -> Result<usize, Error<B::TransportError>> {
|
||||
self.interface.read_response(&mut self.buffer, timeout_ms)
|
||||
pub(crate) fn read_timeout(&mut self, timeout: Duration) -> Result<usize, Error<B::TransportError>> {
|
||||
self.interface.read_response(&mut self.buffer, timeout.as_millis() as u16)
|
||||
}
|
||||
|
||||
// -- generic PN532 functions -------------------------------------------
|
||||
@@ -210,4 +195,26 @@ impl<B: Interface> Pn532<B> {
|
||||
self.read()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// return error if current buffer contains an error code
|
||||
pub(crate) fn validate_buffer(&self, len: usize) -> Result<(), Error<B::TransportError>> {
|
||||
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<B::TransportError>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
+8
-10
@@ -1,3 +1,4 @@
|
||||
use core::fmt::Debug;
|
||||
use defmt::Debug2Format;
|
||||
use strum::FromRepr;
|
||||
|
||||
@@ -25,20 +26,17 @@ pub enum Error<E> {
|
||||
/// 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<E: defmt::Format> defmt::Format for Error<E> {
|
||||
impl<E: Debug> defmt::Format for Error<E> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+121
-87
@@ -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<B: Interface> Pn532<B> {
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// Poll for a FeliCa card.
|
||||
pub fn felica_polling(
|
||||
&mut self,
|
||||
system_code: u16,
|
||||
request_code: u8,
|
||||
timeout_ms: u16,
|
||||
) -> Result<Option<FelicaPollingResponse>, Error<B::TransportError>> {
|
||||
system_code: Option<u16>,
|
||||
baud_rate: BaudRate,
|
||||
request_code: PollingRequestCode,
|
||||
timeout: Duration,
|
||||
) -> Result<Option<PollingResponse>, Error<B::TransportError>> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
|
||||
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<B: Interface> Pn532<B> {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Some(FelicaPollingResponse {
|
||||
Ok(Some(PollingResponse {
|
||||
idm,
|
||||
pmm,
|
||||
system_code_response,
|
||||
@@ -75,6 +84,7 @@ impl<B: Interface> Pn532<B> {
|
||||
&mut self,
|
||||
command: &[u8],
|
||||
response: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<usize, Error<B::TransportError>> {
|
||||
if command.len() > 0xFE {
|
||||
return Err(Error::InvalidParam);
|
||||
@@ -84,10 +94,8 @@ impl<B: Interface> Pn532<B> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
/// 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<B::TransportError>> {
|
||||
@@ -115,7 +124,7 @@ impl<B: Interface> Pn532<B> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
}
|
||||
|
||||
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<B: Interface> Pn532<B> {
|
||||
}
|
||||
|
||||
/// Send a FeliCa "Request Response" command, returning the card's mode.
|
||||
pub fn felica_request_response(&mut self) -> Result<u8, Error<B::TransportError>> {
|
||||
pub fn felica_request_response(&mut self, card: &PollingResponse) -> Result<u8, Error<B::TransportError>> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
/// 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<B::TransportError>> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
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::<u8, COMMAND_SIZE>::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<B: Interface> Pn532<B> {
|
||||
/// 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<B::TransportError>> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
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::<u8, COMMAND_SIZE>::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<B: Interface> Pn532<B> {
|
||||
/// 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<usize, Error<B::TransportError>> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
}
|
||||
Ok(num_system_code)
|
||||
}
|
||||
|
||||
/// Release the FeliCa card.
|
||||
pub fn felica_release(&mut self) -> Result<(), Error<B::TransportError>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>) -> 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>) -> 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)
|
||||
}
|
||||
}
|
||||
@@ -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<u16>,
|
||||
}
|
||||
+14
-32
@@ -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<P: OutputPin> 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<I2C, D> I2cInterface<I2C, D, NoReset, NoIrq> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST: ResetPin> I2cInterface<I2C, D, RST, NoIrq> {
|
||||
impl<I2C, D, RST: OutputPin> I2cInterface<I2C, D, RST, NoIrq> {
|
||||
/// 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<I2C, D, IRQ: InputPin> I2cInterface<I2C, D, NoReset, IRQ> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I2C, D, RST: ResetPin, IRQ: InputPin> I2cInterface<I2C, D, RST, IRQ> {
|
||||
impl<I2C, D, RST: OutputPin, IRQ: InputPin> I2cInterface<I2C, D, RST, IRQ> {
|
||||
/// 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<I2C, D, RST, IRQ> Interface for I2cInterface<I2C, D, RST, IRQ>
|
||||
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<Self::TransportError>> {
|
||||
// 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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<B: Interface> Pn532<B> {
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// Wait for an ISO14443A target and read its UID.
|
||||
///
|
||||
/// Returns `Ok(None)` when no card appears within `timeout_ms` milliseconds.
|
||||
|
||||
+39
-5
@@ -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)+) => {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<B: Interface> Pn532<B> {
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// Whether the block number is the first block of a sector.
|
||||
pub fn mifare_classic_is_first_block(block: u32) -> bool {
|
||||
if block < 128 {
|
||||
|
||||
+5
-4
@@ -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<B: Interface> Pn532<B> {
|
||||
impl<B: Interface, const N: usize> Pn532<B, N> {
|
||||
/// Initialize the PN532 as a target using a raw command frame.
|
||||
pub fn tg_init_as_target(
|
||||
&mut self,
|
||||
@@ -36,7 +37,7 @@ impl<B: Interface> Pn532<B> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
header: &[u8],
|
||||
body: &[u8],
|
||||
) -> Result<(), Error<B::TransportError>> {
|
||||
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<B: Interface> Pn532<B> {
|
||||
.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()));
|
||||
}
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
@@ -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<heapless::Vec<u8, 16>> = OnceLock::new();
|
||||
|
||||
@@ -21,17 +21,18 @@ fn get_card_key() -> &'static heapless::Vec<u8, 16> {
|
||||
}
|
||||
|
||||
pub struct CardReader<'a> {
|
||||
driver: Pn532<pn532::I2cInterface<SharedI2c<'a>, Delay, pn532::NoReset, gpio::Input<'a>>>
|
||||
driver: Pn532<pn532::I2cInterface<SharedI2c<'a>, 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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -43,7 +43,7 @@ async fn main(spawner: Spawner) -> () {
|
||||
}
|
||||
};
|
||||
defmt::info!("Board initialized");
|
||||
|
||||
|
||||
if let Err(e) = board.start().await {
|
||||
defmt::error!("Error: {}", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user