Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

PN532 NFC with ESP32 DevKit V1 — I2C at 3.3 V and a saved card list

Wire a PN532 V3 NFC module to an ESP32 DevKit V1 in I2C mode at 3.3 V, read card UIDs, and enrol allowed cards with the BOOT button into flash.

Arduino C++Verified — core esp32 3.3.12, Adafruit PN532 1.3.4, 3 Nov 2026Compile-only

Community draft — report a mistake

The PN532 is the NFC reader that also talks to NTAG stickers and phones, not just MIFARE cards. The red V3 board can run over I2C, SPI or UART, chosen with two small DIP switches.

On the ESP32 DevKit V1 use I2C on D21/D22 with the module powered from 3V3, so its pull-ups keep the bus at the ESP32’s level. Two more wires — IRQ on D33 and reset on D32 — let the library know when a card arrives. The sketch prints each card’s UID and keeps a list of allowed cards in flash: hold the BOOT button while tapping a card to enrol it.

Wiring

ESP32 DevKit V1Only the pins used are shown · 3.3 V logicPN532 NFC / RFID module (13.56 MHz, V3)D32D33GNDD22D213V3GNDVCCSDASCLSCK (not connected)MISO (not connected)MOSI (not connected)SS (not connected)IRQRSTO
Also needed: NFC cards or tags (13.56 MHz: MIFARE Classic, NTAG213/215/216).
Every connection
ESP32 DevKit V1PN532 NFC / RFID module (13.56 MHz, V3)WireNotes
3V3VCCredAt 3.3 V the module’s I2C pull-ups go to 3.3 V too, which the ESP32 needs. Read range drops only slightly.
GNDGNDblack
D21SDAblue
D22SCLyellow
D33IRQpurpleLOW when a card is in the field. The library uses it to know when to read.
D32RSTOorangeReset. The library pulses it at start-up.

3.3–5.5 V part, 3.3 V board. The V3 board has a regulator and level shifting, and its I2C pull-ups go to VCC. On ESP32 and Pico power it from 3.3 V so SDA/SCL stay at 3.3 V (read range drops only slightly); on Uno use 5 V.

Board quirks

  • Set the DIP switches to I2C (switch 1 ON, switch 2 OFF) before powering up. The mode is read only at power-on.
  • Power the PN532 from 3V3, not VIN. Its I2C pull-ups go to its own VCC, so on 5 V they would push 5 V into GPIO21/22. At 3.3 V everything is in spec.
  • The sketch remembers up to 10 allowed cards in flash with Preferences, so the list survives power cuts. Hold BOOT (GPIO0) while tapping a card to add it. GPIO0 is only a strapping pin at reset — reading it later is fine.
  • A UID alone is not security: cheap “magic” cards can copy a MIFARE Classic UID. It is fine for a hostel-room light or attendance demo, not for a real lock.
  • The RC522 is cheaper; the PN532 adds NTAG/phone NFC support, more range and a choice of I2C, SPI or UART. On the ESP32 the I2C version needs only two bus wires plus IRQ and reset.

Code

pn532-esp32.inoArduino C++Open in PlaygroundOpen in PlaygroundFlash
// PN532 NFC module (V3, I2C mode) on an ESP32 DevKit V1 — card UIDs with a remembered "allowed" list.
// DIP switches: 1 ON, 2 OFF (I2C). VCC → 3V3, GND → GND, SDA → D21, SCL → D22, IRQ → D33, RSTO → D32.
// Hold BOOT while you present a card to add it to the list (kept in flash across power cuts).
#include <Wire.h>
#include <Adafruit_PN532.h>
#include <Preferences.h>

constexpr uint8_t PN532_IRQ = 33, PN532_RST = 32, BOOT_BTN = 0, LED_PIN = 2;
Adafruit_PN532 nfc(PN532_IRQ, PN532_RST, &Wire);
Preferences prefs;
constexpr uint8_t MAX_CARDS = 10;

String uidHex(const uint8_t *uid, uint8_t len) {
  String s;
  for (uint8_t i = 0; i < len; i++) {
    if (uid[i] < 0x10) s += '0';
    s += String(uid[i], HEX);
  }
  s.toUpperCase();
  return s;
}

bool isAllowed(const String &id) {
  uint8_t n = prefs.getUChar("n", 0);
  for (uint8_t i = 0; i < n; i++)
    if (prefs.getString(("c" + String(i)).c_str(), "") == id) return true;
  return false;
}

void setup() {
  Serial.begin(115200);
  pinMode(BOOT_BTN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Wire.begin(21, 22);
  nfc.begin();
  uint32_t ver = nfc.getFirmwareVersion();
  if (!ver) {
    Serial.println("PN532 not found - check the DIP switches (I2C = 1 ON, 2 OFF), SDA/SCL and 3V3");
    while (true) delay(1000);
  }
  Serial.printf("PN532 firmware %u.%u\n", (unsigned)(ver >> 16 & 0xFF), (unsigned)(ver >> 8 & 0xFF));
  nfc.SAMConfig();
  prefs.begin("nfc", false);
  Serial.printf("%u card(s) allowed. Hold BOOT while tapping a card to add it.\n", prefs.getUChar("n", 0));
}

void loop() {
  uint8_t uid[7];
  uint8_t len = 0;
  if (!nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &len, 300)) return;
  String id = uidHex(uid, len);
  bool enrol = digitalRead(BOOT_BTN) == LOW;
  if (enrol && !isAllowed(id)) {
    uint8_t n = prefs.getUChar("n", 0);
    if (n < MAX_CARDS) {
      prefs.putString(("c" + String(n)).c_str(), id);
      prefs.putUChar("n", n + 1);
      Serial.printf("Added %s (%u-byte UID)\n", id.c_str(), len);
    } else {
      Serial.println("List full");
    }
  } else {
    bool ok = isAllowed(id);
    Serial.printf("Card %s (%u-byte UID)  %s\n", id.c_str(), len, ok ? "ALLOWED" : "unknown");
    digitalWrite(LED_PIN, ok);
    delay(1000);
    digitalWrite(LED_PIN, LOW);
  }
  delay(500);  // don't read the same tap twice
}

Library: Adafruit PN532 (Adafruit) — compiled with Adafruit PN532 1.3.4.

Expected serial output

Serial Monitor · 115200Output
PN532 firmware 1.6
0 card(s) allowed. Hold BOOT while tapping a card to add it.
Card 04A23CB2C51290 (7-byte UID)  unknown
Added 7A3F1C9E (4-byte UID)
Card 7A3F1C9E (4-byte UID)  ALLOWED

Common errors

“PN532 not found”

DIP switches not in I2C mode (1 ON, 2 OFF), or set after power-up — power-cycle the module. Then check SDA on D21 and SCL on D22.

Found, but cards are never read

IRQ not reaching D33. The library waits for IRQ to go LOW. Also hold the card flat on the antenna side, not the chip side.

Phone NFC isn’t detected

Phones emulate cards only in specific apps (payments), and many randomise their ID. Use real tags for testing.

↑↓ to move · Enter to open