jef-sure/pn5180

0.0.3

Latest
uploaded 15 hours ago
ESP-IDF component for NXP PN5180 NFC/RFID reader with ISO14443A, ISO15693, MIFARE, and NDEF support

readme

# PN5180 ESP32 Component (ESP-IDF)

## Overview

ESP-IDF component for the NXP PN5180 NFC/RFID reader. This implementation provides a robust multi-protocol NFC reader with comprehensive support for ISO14443A, ISO15693, MIFARE Classic/Ultralight, and NDEF message parsing.

### Features
- ✅ **ISO14443A** - Anticollision, multi-cascade UID enumeration, card selection
- ✅ **ISO15693** - Vicinity tag support with configurable modulation (ASK 100%/10%)
- ✅ **MIFARE Classic 1K/4K** - Authentication (Key A/B), block read/write
- ✅ **MIFARE Ultralight** - Read-only support
- ✅ **NDEF** - Complete message parsing with TLV decoding, Text RTD, URI RTD support
- ✅ **Multi-card** - Enumerate up to 14 cards in field
- ✅ **Error detection** - RX/CRC/collision error handling with clean recovery
- ✅ **SPI** - Tested at 7 MHz with BUSY line synchronization

## Hardware

Default wiring used in the sample app:

| Signal | ESP32 GPIO | Description |
| ------ | ---------- | ----------- |
| RST    | 12         | Hardware reset (active low) |
| SCK    | 18         | SPI clock |
| MOSI   | 23         | SPI data to PN5180 |
| MISO   | 19         | SPI data from PN5180 |
| NSS    | 5          | SPI chip select (active low) |
| BUSY   | 21         | PN5180 busy indicator |

Adjust the GPIO assignments or the SPI host (`VSPI_HOST` by default) to match your board.

## Requirements

- ESP-IDF v5.x (tested) with an ESP32 target.
- 3.3 V PN5180 breakout wired for SPI and BUSY/RST lines.
- Enough DMA-capable heap for the driver buffers (two 512-byte buffers are allocated).

## Getting Started

1. Place this repository under your project's `components/` directory (or add it as a git submodule).
2. Include the headers you need:
   - `pn5180.h` - Core driver and shared types
   - `pn5180-14443.h` - ISO14443A protocol (MIFARE, NTAG, etc.)
   - `pn5180-15693.h` - ISO15693 protocol (vicinity tags)
   - `pn5180-ndef.h` - NDEF message reading and parsing
   - `pn5180-mifare.h` - MIFARE Classic authentication helpers
3. Build and flash with `idf.py build flash monitor`.

**Note**: The `main/` directory contains an example application. The `examples/` directory has standalone examples you can reference.

## API Reference

### Core Types

```c
// Card type enumeration (detected via SAK analysis)
typedef enum {
    NFC_TYPE_UNKNOWN,           // Unknown or unsupported
    NFC_TYPE_MIFARE_CLASSIC_1K, // MIFARE Classic 1K (SAK 0x08)
    NFC_TYPE_MIFARE_CLASSIC_4K, // MIFARE Classic 4K (SAK 0x18)
    NFC_TYPE_MIFARE_ULTRALIGHT, // MIFARE Ultralight/NTAG (SAK 0x00)
    NFC_TYPE_NTAG,              // NTAG21x series
    NFC_TYPE_ISO14443_4,        // ISO14443-4 compliant (DESFire, etc.)
    // ... see pn5180.h for complete list
} nfc_type_t;

// Protocol interface - all protocols implement this
typedef struct {
    func_setup_rf_t        setup_rf;        // Configure RF field
    funct_get_all_uids_t   get_all_uids;    // Enumerate all cards
    func_select_by_uid_t   select_by_uid;   // Select specific card
    func_authenticate_t    authenticate;    // Auth (MIFARE Classic)
    func_block_read_t      block_read;      // Read block
    func_block_write_t     block_write;     // Write block
    func_halt_t            halt;            // HALT selected card
    // ...
} pn5180_proto_t;
```

## Usage Examples

### Basic ISO14443A Card Enumeration

```c
#include "pn5180.h"
#include "pn5180-14443.h"

enum {
    PN5180_RST  = GPIO_NUM_12,
    PN5180_SCK  = GPIO_NUM_18,
    PN5180_MOSI = GPIO_NUM_23,
    PN5180_MISO = GPIO_NUM_19,
    PN5180_NSS  = GPIO_NUM_5,
    PN5180_BUSY = GPIO_NUM_21,
    PN5180_FREQ = 7000000,
};

void app_main(void)
{
    pn5180_spi_t *spi = pn5180_spi_init(VSPI_HOST, PN5180_SCK, PN5180_MISO, PN5180_MOSI, PN5180_FREQ);
    pn5180_t *pn5180  = pn5180_init(spi, PN5180_NSS, PN5180_BUSY, PN5180_RST);

    pn5180_proto_t *iso14443 = pn5180_14443_init(pn5180);
    iso14443->setup_rf(iso14443);

    nfc_uids_array_t *uids = iso14443->get_all_uids(iso14443);
    if (uids) {
        for (int i = 0; i < uids->uids_count; i++) {
            nfc_uid_t *uid = &uids->uids[i];
            printf("Card %d: Type=%d, UID len=%d\n", i, uid->type, uid->uid_len);
        }
        free(uids);
    }

    free(iso14443);
    pn5180_deinit(pn5180, true);
}
```

### MIFARE Classic Authentication and Read

```c
#include "pn5180.h"
#include "pn5180-14443.h"
#include "pn5180-mifare.h"

void read_mifare_classic(pn5180_proto_t *proto, nfc_uid_t *uid)
{
    // Select the card first
    if (!proto->select_by_uid(proto, uid)) {
        ESP_LOGE(TAG, "Failed to select card");
        return;
    }

    // Authenticate sector 1 (blocks 4-7) with Key A
    uint8_t key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};  // Default key
    int block = 4;  // First block of sector 1

    if (!proto->authenticate(proto, block, 0, key)) {  // 0 = Key A
        ESP_LOGE(TAG, "Authentication failed");
        return;
    }

    // Read authenticated block
    uint8_t data[16];
    if (proto->block_read(proto, block, data, sizeof(data)) > 0) {
        ESP_LOGI(TAG, "Block %d data:", block);
        ESP_LOG_BUFFER_HEX(TAG, data, 16);
    }
}
```

### ISO15693 Vicinity Tag Reading

```c
#include "pn5180.h"
#include "pn5180-15693.h"

void read_iso15693_tags(pn5180_t *pn5180)
{
    // Initialize with ASK 100% modulation
    pn5180_proto_t *iso15693 = pn5180_15693_init(pn5180, PN5180_15693_26KASK100);
    iso15693->setup_rf(iso15693);

    // Enumerate tags
    nfc_uids_array_t *uids = iso15693->get_all_uids(iso15693);
    if (uids && uids->uids_count > 0) {
        // Select first tag
        if (iso15693->select_by_uid(iso15693, &uids->uids[0])) {
            // Read block 0
            uint8_t data[4];
            if (iso15693->block_read(iso15693, 0, data, sizeof(data)) > 0) {
                ESP_LOG_BUFFER_HEX(TAG, data, 4);
            }
        }
        free(uids);
    }

    free(iso15693);
}
```

### NDEF Message Reading

```c
#include "pn5180-ndef.h"

void read_ndef_message(pn5180_proto_t *proto)
{
    // For MIFARE Classic: start_block=4, block_size=16
    // For ISO15693 Type 5: start_block=1, block_size=4

    int start_block = 4;   // Adjust based on card type
    int block_size  = 16;  // 16 for MIFARE, 4 for ISO15693

    ndef_message_parsed_t *msg = NULL;
    // auth_cb + sector_cb are optional (NULL if not needed)
    ndef_result_t result = ndef_read_from_selected_card(proto, start_block, block_size, 0,
                                                        NULL, NULL, NULL, &msg);

    if (result != NDEF_OK || !msg) {
        ESP_LOGE(TAG, "NDEF read failed: %s", ndef_result_to_string(result));
        return;
    }

    ESP_LOGI(TAG, "Found %zu NDEF records", msg->record_count);

    for (size_t i = 0; i < msg->record_count; i++) {
        ndef_record_t *rec = &msg->records[i];
        ESP_LOGI(TAG, "Record %zu: TNF=0x%02X, Type len=%u, Payload len=%u",
                 i, rec->tnf, rec->type_len, rec->payload_len);

        // Check for URI record
        if (rec->tnf == NDEF_TNF_WELL_KNOWN && rec->type_len == 1 && rec->type[0] == 'U') {
            // Decode URI - first byte is prefix code
            ESP_LOGI(TAG, "  URI record found");
        }
        // Check for Text record
        else if (rec->tnf == NDEF_TNF_WELL_KNOWN && rec->type_len == 1 && rec->type[0] == 'T') {
            ESP_LOGI(TAG, "  Text record found");
        }
    }

    ndef_free_parsed_message(msg);
}
```

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│                    Application Layer                    │
│   (Your code - card detection, business logic, etc.)    │
└─────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│                     NDEF Layer                          │
│   pn5180-ndef.h - Message parsing, TLV, record types    │
└─────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│                   Protocol Layer                        │
│   pn5180-14443.h (MIFARE)  │  pn5180-15693.h (Vicinity) │
│   pn5180-mifare.h (Auth)   │                            │
└─────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│                     Core Driver                         │
│   pn5180.h - SPI, RF control, low-level commands        │
└─────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│                      Hardware                           │
│            ESP32 SPI <-> PN5180 NFC Reader              │
└─────────────────────────────────────────────────────────┘
```

## Notes

- **Blocking calls & timeouts**: All APIs are synchronous and wait for hardware completion using `BUSY`, IRQ, and transceiver-state polling. Operations respect internal timeouts and return promptly on error.

- **Error handling**: The component detects RX errors (protocol/CRC/collision) and returns failure without automatic retries. Implement retries in your application.

- **MIFARE Classic authentication**:
    - Authentication is per-sector; re-authenticate when crossing sector boundaries
    - After auth failure, re-select the card before retrying
    - Some tags/readers may require a HALT before re-select; apply only on failure if needed

- **CRC policy (ISO14443A)**: Anticollision runs with CRC disabled; SELECT uses CRC enabled. After SELECT, CRC remains enabled.

- **RF field control**: Toggle RF off/on between scans (`pn5180_rf_off()` / `pn5180_rf_on()`) and allow ~5 ms for tags to return to IDLE.

- **UID enumeration**: `get_all_uids()` returns a heap-allocated array (max 14 cards). Always free after use; returns NULL if no cards detected.

- **Runtime configuration**: Pins and SPI frequency are set in your app; there are no Kconfig options.

## Troubleshooting

| Issue | Solution |
|-------|----------|
| No cards detected | Check wiring, ensure 3.3V supply, verify RF field is on |
| Auth timeout after first block | Re-authenticate on sector change; re-select on auth failure |
| WUPA timeout | Try HALT then re-select (only if selection fails) |
| Corrupted reads | Check SPI wiring, reduce frequency, add decoupling capacitors |
| WDT reset during multi-sector read | Add `vTaskDelay(pdMS_TO_TICKS(10))` between operations |

## License

MIT License

Links

Supports all targets

Maintainer

  • Anton Petrusevich <anton.petrusevich.mobile@gmail.com>

License: MIT

To add this component to your project, run:

idf.py add-dependency "jef-sure/pn5180^0.0.3"

download archive

Stats

  • Archive size
    Archive size ~ 152.94 KB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

jef-sure/pn5180 version: 0.0.3
|