ESP32 NFC Development
Embedded NFC with ESP-IDF and Arduino
Integrating NFC reader modules with ESP32 microcontrollers using ESP-IDF and Arduino frameworks. SPI/I2C wiring and NDEF examples.
ESP32 NFC Development: ESP-IDF and Arduino
The ESP32 has no built-in NFC hardware, but its SPI and I2C peripherals make it a natural host for external NFC reader ICs. The PN532 over SPI is the most common choice for prototyping; the PN7150 is preferred for production designs needing NCI compliance. This guide covers both ESP-IDF (C) and Arduino (C++) workflows.
Hardware Options
| IC | Interface | Library | Best For |
|---|---|---|---|
| PN532 | SPI / I2C / HSU | Adafruit PN532, Elechouse | Prototyping, NTAG, MIFARE |
| PN7150 | I2C + IRQ + VEN | nxp-nci-esp32 | Production, NCI stack |
| ST25R3916 | SPI | ST25R Arduino | ISO 15693, long range |
| RC663 | SPI / I2C | NXP CLRC663 SDK | High-throughput multi-protocol |
For IC-level trade-offs see NFC Reader Modules Compared.
PN532 Wiring (SPI)
ESP32 GPIO PN532 Pin
----------- ---------
GPIO 18 (SCLK) → SCK
GPIO 23 (MOSI) → MOSI
GPIO 19 (MISO) → MISO
GPIO 5 (SS) → NSS/CS
3.3V → VCC (3.3V version) or 5V with level shifter
GND → GND
Set the DIP switches on the PN532 breakout to SPI mode: switch 1 OFF, switch 2 ON.
Arduino: Reading NDEF with Adafruit PN532
#include <Wire.h>
#include <Adafruit_PN532.h>
#define PN532_SS 5
#define PN532_SCK 18
#define PN532_MOSI 23
#define PN532_MISO 19
Adafruit_PN532 nfc(PN532_SS);
void setup() {
Serial.begin(115200);
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
Serial.println("PN532 not found. Check wiring.");
while (1) delay(10);
}
Serial.printf("PN532 v%d.%d firmware\n",
(versiondata >> 16) & 0xFF, (versiondata >> 8) & 0xFF);
nfc.SAMConfig();
Serial.println("Waiting for NFC tag...");
}
void loop() {
uint8_t uid[7];
uint8_t uidLength;
if (nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength)) {
Serial.print("UID: ");
for (uint8_t i = 0; i < uidLength; i++) {
Serial.printf("%02X ", uid[i]);
}
Serial.println();
delay(500);
}
}
Arduino: Writing NDEF URL to NTAG213
#include <Adafruit_PN532.h>
// NTAG213 user memory starts at page 4
// Each page is 4 bytes
// NDEF TLV 0x03 + length byte + NDEF record + 0xFE terminator
void writeNdefUrl(Adafruit_PN532 &nfc, const char* url) {
// Build minimal NDEF URI record: https://
// URI abbreviation 0x04 = "https://"
uint8_t urlLen = strlen(url);
uint8_t payloadLen = 1 + urlLen; // abbrev byte + url bytes
// Record: MB=1 ME=1 SR=1 TNF=0x01, Type='U', PayloadLen, 0x04, url
uint8_t ndef[] = {
0xD1, // MB | ME | SR | TNF=WellKnown
0x01, // Type length = 1
(uint8_t)payloadLen,
0x55, // Type = 'U'
0x04 // Abbreviation: https://
};
// TLV wrapper: 0x03, length, ...ndef..., 0xFE
// Write pages starting at page 4
// (Full implementation: pack into 4-byte pages and call nfc.ntag2xx_WritePage)
Serial.println("Write complete");
}
For production use, consider the NDEF library by Don Coleman (arduino-libraries/NDEF) which handles TLV packing and page alignment automatically.
ESP-IDF: PN532 via SPI (C)
#include "driver/spi_master.h"
#include "pn532.h" // community driver
static spi_device_handle_t pn532_spi;
void pn532_spi_init(void) {
spi_bus_config_t buscfg = {
.miso_io_num = 19, .mosi_io_num = 23,
.sclk_io_num = 18, .quadwp_io_num = -1, .quadhd_io_num = -1,
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 1 * 1000 * 1000, // 1 MHz
.mode = 0, .spics_io_num = 5,
.queue_size = 7,
};
spi_bus_initialize(HSPI_HOST, &buscfg, 1);
spi_bus_add_device(HSPI_HOST, &devcfg, &pn532_spi);
}
The PN532 SPI protocol wraps commands in a TFI (Transport Frame Indicator) envelope. Most community drivers for ESP-IDF abstract this, but understanding the raw framing is essential for debugging with a logic analyser.
PN7150 with NCI Stack (Production)
The PN7150 implements the NCI (NFC Controller Interface) standard, making it the correct choice for production firmware that must pass nfc-forum certification:
// NCI reset and init sequence
uint8_t core_reset[] = {0x20, 0x00, 0x01, 0x00};
uint8_t core_init[] = {0x20, 0x01, 0x00};
// Send via I2C to PN7150 (I2C addr 0x28)
i2c_master_write_to_device(I2C_NUM_0, 0x28,
core_reset, sizeof(core_reset), pdMS_TO_TICKS(100));
NXP provides the NFC Reader Library (NXP NFC LIB) with a full NCI stack for ESP-IDF. Link against libnfc_nci_linux or use the ESP-IDF component registry port.
Power Consumption Tips
NFC reader ICs are the single largest power draw in battery-operated designs:
| Mode | PN532 Current | PN7150 Current |
|---|---|---|
| Standby | 1 mA | 0.5 mA |
| RF polling | 55 mA | 40 mA |
| Tag present | 65 mA | 50 mA |
| Hard power-off | 0 mA | 0 mA |
Use a GPIO-controlled load switch to power off the NFC IC between scans. For event-driven designs, wire the PN7150's IRQ pin to an ESP32 GPIO with ext0 wake-up to detect field presence without continuous polling.
FreeRTOS Task Pattern
void nfc_task(void *pvParameters) {
nfc_init();
while (1) {
nfc_tag_t tag;
if (nfc_wait_for_tag(&tag, pdMS_TO_TICKS(5000)) == ESP_OK) {
ESP_LOGI("NFC", "Tag UID: %s", tag.uid_hex);
xQueueSend(nfc_event_queue, &tag, 0);
}
}
}
Keep NFC operations in their own FreeRTOS task with a dedicated stack of at least 4 KB to avoid stack overflows during SPI DMA transfers.
See also: Python NFC Programming Guide | NFC Arduino and Raspberry Pi | NFC Reader Modules Compared | NFC Chip Comparison Guide
Frequently Asked Questions
Our guides cover a range of experience levels. Getting Started guides are written for beginners with no prior NFC knowledge. Programming guides target developers integrating NFC into mobile apps or embedded systems. Security guides are for engineers designing secure NFC deployments for payments, access control, or authentication.
Most guides require only an NFC-enabled smartphone (iPhone 7+ or any modern Android device) and a few NFC tags (NTAG213 or NTAG215 recommended for beginners, available for under $1 each). Advanced guides may reference USB NFC readers like the ACR122U or Proxmark3 for development and testing.
Yes. Programming guides include code examples for Android (Kotlin/Java with the Android NFC API), iOS (Swift with Core NFC), and web-based tools (Web NFC API for Chrome on Android). All code samples are tested and include inline comments explaining each step.