I’m experimenting with some NFC devices for a top secret project for silly hackers. Here I’ve got this red PN532 NFC breakout board wired to an ESP32 C3 Super Mini for SPI communication. I can’t tell you why I’m doing this project, or even what it is (yet), but I will tell you how it all started…
Blame HackerBoxes
As I’ve written about before, I am an enthusiastic HackerBoxes subscriber. This is the thing where every month they send you a neat box to tinker with (usually $59 per box, but subscribers usually only pay $44; no I’m not being compensated, I just like them). I’ve been a subscriber since I first learned about them at DEF CON 32, and while I am (as most tinkerers) in arrears on projects, I love doing them when I find the time.
Anyway, HackerBox #0117 is a kit for doing RFID and NFC. I got this kit assembled, and I played with the code for a little bit. It inspired me to build a bunch of [REDACTED] that I could bring with me next year to [REDACTED]. So I contacted [REDACTED] and they thought that this was a great idea! I started thinking through which components I want to use, and how I’d want to build my [REDACTED], and I made a couple of decisions right away:
- UART appears to suck for NFC applications
- The 7941W that came with the HackerBox was too limited for more than just the HackerBox
My favorite thing about HackerBoxes (last plug, I promise) is that they always encourage you to go where your curiosity takes you. It is in that spirit that I set off looking to answer three questions:
- Which microcontroller is best for this application?
- Cost
- Power consumption
- Ease of use
- Functionality and features
- Which NFC chipset is going work best?
- Which protocol will be best for the task?
Things Decided
I’m using an ESP32 C3 Super Mini for the brains of the [REDACTED]. This is for two main reasons: 1) I’ve already got a bunch of them lying around, and 2) they’re going to do a much better job of fitting into the small spaces I want. Look at all of this room for activities!

This takes me to my second decision: the protocol. I got lucky early on in my experimentation, and quickly settled on SPI for this project. The main factor was reliability and range. UART interfaces, even HSU, have been really difficult to even prototype with. I also tried I2C, but ran into problems with it being a bit slow. I don’t need the world’s fastest response times, but I get bored holding the card in front of the antenna for that long. The only decision left is the NFC module.
I spent a good amount of time prototyping with other modules, and I’ve settled on the PN532, which is the red one you see in the photo above. It’s not the least expensive module available, but the one I bought was USD $3.10 for just one on AliExpress. There are cheaper variants, but I bought this as a four-pack of other modules, including the MFRC522. The MFRC522 is a lot less expensive, and it uses less power, but as I was experimenting I found that the software support was pretty limited. There are good libraries out there, but I’m making production-grade [REDACTED]s and I need to make sure that they work.
Putting It All Together
With protocol, NFC module, and microcontroller all selected, I’m putting it all together. Luckily our friends at ESPBoards.dev put together this delightful document on using this specific NFC module with ESP32-based devices, and what’s more it validates my choice of SPI for the protocol!

The diagram is for a different ESP32 kit, but that’s no problem, I just reference the pinout diagram I have above. This leaves me in a spot where I have my hardware, it’s all wired up, and now I get to writing some code!

Adafruit has a lovely library written in C++ which is compatible with both my chosen microcontroller and my chosen module. They have sample code which makes it easy to set up which just reads and dumps compatible devices. I set this code snippet up, adjusted the pinout for my configuration, and it just works. Check it out!
Here’s the code!
// Thank you, Adafruit!
// https://github.com/adafruit/Adafruit-PN532/blob/bf9f3f31961474fdbca23cb57d604c7f27269baf/examples/mifareclassic_memdump/mifareclassic_memdump.ino#L17
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
#define PN532_SCK (4)
#define PN532_MOSI (6)
#define PN532_SS (7)
#define PN532_MISO (5)
Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);
void setup() {
Serial.begin(115200);
Serial.println("Starting NFC init...");
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (! versiondata) {
Serial.print("Didn't find PN532 board");
while (1); // halt
}
// Got ok data, print it out!
Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
Serial.println("Waiting for an ISO14443A Card ...");
}
void loop() {
uint8_t success; // Flag to check if there was an error with the PN532
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
uint8_t currentblock; // Counter to keep track of which block we're on
bool authenticated = false; // Flag to indicate if the sector is authenticated
uint8_t data[16]; // Array to store block data during reads
// Keyb on NDEF and Mifare Classic should be the same
uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
// Wait for an ISO14443A type cards (Mifare, etc.). When one is found
// 'uid' will be populated with the UID, and uidLength will indicate
// if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
if (success) {
// Display some basic information about the card
Serial.println("Found an ISO14443A card");
Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
Serial.print(" UID Value: ");
nfc.PrintHex(uid, uidLength);
Serial.println("");
if (uidLength == 4)
{
// We probably have a Mifare Classic card ...
Serial.println("Seems to be a Mifare Classic card (4 byte UID)");
// Now we try to go through all 16 sectors (each having 4 blocks)
// authenticating each sector, and then dumping the blocks
for (currentblock = 0; currentblock < 64; currentblock++)
{
// Check if this is a new block so that we can reauthenticate
if (nfc.mifareclassic_IsFirstBlock(currentblock)) authenticated = false;
// If the sector hasn't been authenticated, do so first
if (!authenticated)
{
// Starting of a new sector ... try to to authenticate
Serial.print("------------------------Sector ");Serial.print(currentblock/4, DEC);Serial.println("-------------------------");
if (currentblock == 0)
{
// This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!)
// or 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 for NDEF formatted cards using key a,
// but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal);
}
else
{
// This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!)
// or 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 for NDEF formatted cards using key a,
// but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal);
}
if (success)
{
authenticated = true;
}
else
{
Serial.println("Authentication error");
}
}
// If we're still not authenticated just skip the block
if (!authenticated)
{
Serial.print("Block ");Serial.print(currentblock, DEC);Serial.println(" unable to authenticate");
}
else
{
// Authenticated ... we should be able to read the block now
// Dump the data into the 'data' array
success = nfc.mifareclassic_ReadDataBlock(currentblock, data);
if (success)
{
// Read successful
Serial.print("Block ");Serial.print(currentblock, DEC);
if (currentblock < 10)
{
Serial.print(" ");
}
else
{
Serial.print(" ");
}
// Dump the raw data
nfc.PrintHexChar(data, 16);
}
else
{
// Oops ... something happened
Serial.print("Block ");Serial.print(currentblock, DEC);
Serial.println(" unable to read this block");
}
}
}
}
else
{
Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!");
}
}
// Wait a bit before trying again
Serial.println("\n\nSend a character to run the mem dumper again!");
Serial.flush();
while (!Serial.available());
while (Serial.available()) {
Serial.read();
}
Serial.flush();
}
Code language: Arduino (arduino)
To be clear, I didn’t write any of the code above, I simply modified the pinout configuration and went with it. Adafruit does excellent work! I flashed the code to the ESP32 on my breadboard, and I tested it with a number of the NFC devices which came with my reader and I get something that looks like this:
Found an ISO14443A card
UID Length: 4 bytes
UID Value: 0xB3 0x24 0x50 0x2D
Seems to be a Mifare Classic card (4 byte UID)
------------------------Sector 0-------------------------
Block 0 B3 24 50 2D EA 08 04 00 62 63 64 65 66 67 68 69 �$P-�...bcdefghi
Block 1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 3 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 1-------------------------
Block 4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 7 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 2-------------------------
Block 8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 11 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 3-------------------------
Block 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 15 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 4-------------------------
Block 16 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 17 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 19 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 5-------------------------
Block 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 23 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 6-------------------------
Block 24 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 25 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 26 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 27 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 7-------------------------
Block 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 31 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 8-------------------------
Block 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 35 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 9-------------------------
Block 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 39 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 10-------------------------
Block 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 43 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 11-------------------------
Block 44 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 45 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 46 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 47 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 12-------------------------
Block 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 51 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 13-------------------------
Block 52 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 53 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 55 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 14-------------------------
Block 56 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 59 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
------------------------Sector 15-------------------------
Block 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 62 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
Block 63 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF ......�.�i������
Send a character to run the mem dumper again!Code language: PHP (php)
Where From Here?
So I have a hardware configuration, I have some prototype software, and I have some more to research. I have other hardware features I need to support in these [REDACTED]s, too. Obviously we’ll need some NeoPixels, and I’m going to need to spend more time learning KiCad. I’m going to want to miniaturize this build a bit more, mostly because I want to make it fit in a fun enclosure. I’ve never made my own PCB before, so there’s a lot of learning to do.
Acknowledgements
- DeepSeek, provided by Kagi Assistant, was used briefly early-on as I was brainstorming
- All of the code, less my itty-bitty tweaks, was from this example by Adafruit
- Here is the PN532 breakout board I used, and you can see the other variants I tried on this page as well
- Here are the ESP32-C3 Super Minis that I used
- While I typically use terminal-based tools for these types of projects, I used the Arduino IDE for this one since it was all sample code
Thanks, y’all.