Objective and use case
What you’ll build: This hands-on practical case implements NFC badge–based access control that uses the Arduino Nano 33 IoT’s Wi-Fi to call a backend when authorized tags are presented.
Why it matters / Use cases
- Implement secure access control for facilities using NFC badges, enhancing security measures.
- Integrate IoT devices with existing infrastructure to streamline user authentication processes.
- Provide real-time status updates on access attempts via the SSD1306 OLED display.
- Enable remote monitoring of access logs by posting events to a server.
Expected outcome
- Successful NFC tag reads with a 95% accuracy rate in identifying authorized users.
- Event POST requests to the server with less than 200 ms latency.
- Real-time feedback displayed on the OLED screen within 1 second of tag presentation.
- Ability to handle up to 50 access attempts per minute without performance degradation.
Audience: Advanced users; Level: Advanced
Architecture/flow: Arduino Nano 33 IoT communicates with PN532 for NFC reads, displays status on SSD1306 OLED, and connects to Wi-Fi for backend communication.
NFC Wi‑Fi Access Control on Arduino Nano 33 IoT + PN532 NFC + SSD1306 OLED (Advanced)
This hands‑on practical case implements NFC badge–based access control that uses the board’s Wi‑Fi to call a backend when authorized tags are presented. The PN532 reads NFC tags, the SSD1306 OLED shows status, and the Nano 33 IoT connects to Wi‑Fi to POST an event to a server. You will build, flash, validate, troubleshoot, and extend this project using PlatformIO from the command line.
Target device: Arduino Nano 33 IoT + PN532 NFC + SSD1306 OLED
Objective: nfc‑wifi‑access‑control
Note on toolchain: Because the chosen board is not the default Arduino UNO (AVR), this guide uses PlatformIO Core (CLI) instead of Arduino CLI, and includes driver/port notes and exact pio commands.
Prerequisites
- Skill level: Advanced (comfortable with C++ for Arduino, PlatformIO, wiring I2C devices, and working with HTTP backends).
- Host OS:
- Windows 10/11, macOS 12+, or Linux (Ubuntu 20.04+).
- Software:
- Python 3.8+ (for installing PlatformIO Core).
- PlatformIO Core 6.1+ (CLI).
- Node.js 18+ (for the optional minimal validation server).
- Hardware:
- Arduino Nano 33 IoT (official, 3.3 V logic).
- PN532 NFC/RFID Controller Breakout in I2C mode.
- SSD1306 OLED 128×64 I2C (address 0x3C).
- Breadboard and 3.3 V wiring jumpers.
Driver notes:
– Arduino Nano 33 IoT has native USB CDC ACM. On macOS/Linux, no driver required. On Windows 10/11, drivers are automatic. For Windows 7, install Arduino SAMD drivers (installing Arduino IDE once is sufficient to obtain the driver, even if you use PlatformIO to build).
Materials (exact model)
- Microcontroller: Arduino Nano 33 IoT (ABX00027).
- NFC reader: Adafruit PN532 NFC/RFID Controller Breakout Board (v1.6 or later; configured to I2C mode).
- OLED display: SSD1306 128×64 I2C (typical address 0x3C; e.g., Adafruit 938 or compatible).
- NFC media: MIFARE Classic/Ultralight cards or key fobs (ISO14443A).
- USB cable: Micro USB (for the Nano 33 IoT).
- Breadboard and male‑to‑female dupont wires (3.3 V logic compatible).
Power/logic caution:
– Arduino Nano 33 IoT is a 3.3 V device. Do NOT feed 5 V logic to I/O. PN532 and SSD1306 should be powered at 3.3 V and use 3.3 V logic.
Setup/Connection
We will run both PN532 and SSD1306 over I2C. The PN532 in I2C mode additionally uses IRQ and RST pins for the Adafruit library’s handshake. The OLED shares the I2C bus.
- Configure the PN532 breakout to I2C mode:
-
Set the interface selection jumpers/switches to I2C per your board’s silkscreen (commonly “I2C” selection documented on the breakout). Power‑cycle after changing switches/jumpers.
-
Wire the modules to the Arduino Nano 33 IoT as follows:
| Module | Signal | Nano 33 IoT Pin | Notes |
|---|---|---|---|
| PN532 | VIN | 3V3 | 3.3 V only |
| PN532 | GND | GND | Common ground |
| PN532 | SDA | A4 (SDA) | I2C data |
| PN532 | SCL | A5 (SCL) | I2C clock |
| PN532 | IRQ | D2 | Used by Adafruit PN532 I2C mode |
| PN532 | RST | D3 | Reset line |
| SSD1306 | VCC | 3V3 | 3.3 V only |
| SSD1306 | GND | GND | Common ground |
| SSD1306 | SDA | A4 (SDA) | I2C data (shared bus) |
| SSD1306 | SCL | A5 (SCL) | I2C clock (shared bus) |
- Addresses:
- SSD1306 I2C address: 0x3C (typical).
- PN532 I2C 7‑bit address: 0x24 (8‑bit 0x48) (handled by library).
Double‑check:
– No 5 V lines connected to modules.
– A4/A5 are the Nano 33 IoT’s I2C pins (SDA/SCL).
– PN532 interface truly set to I2C (if not, the library will not detect it).
Full Code
We will use PlatformIO with the Arduino framework and pin an exact set of library versions for reproducibility.
Project tree (relative paths):
– platformio.ini
– src/main.cpp
– include/secrets.h
First create platformio.ini:
; platformio.ini
[env:nano_33_iot]
platform = atmelsam
board = nano_33_iot
framework = arduino
monitor_speed = 115200
lib_deps =
adafruit/Adafruit PN532 @ 1.3.0
adafruit/Adafruit SSD1306 @ 2.5.9
adafruit/Adafruit GFX Library @ 1.11.9
arduino-libraries/WiFiNINA @ 1.8.14
bblanchon/ArduinoJson @ 7.0.4
rweather/Crypto @ 0.4.0
Then add include/secrets.h (fill in your Wi‑Fi SSID/password and HMAC key):
// include/secrets.h
#pragma once
// Wi-Fi credentials
#define SECRET_SSID "YourWiFiSSID"
#define SECRET_PASS "YourWiFiPassword"
// 32-byte HMAC key (hex or ascii). Keep secret in production.
// For demo purposes, use a simple ASCII key; replace with a strong one.
#define SECRET_HMAC_KEY "this_is_a_demo_hmac_key_32bytes!!"
Finally, add src/main.cpp:
// src/main.cpp
#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <WiFiNINA.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_PN532.h>
#include <ArduinoJson.h>
#include <Crypto.h>
#include <SHA256.h>
#include <HMAC.h>
#include "secrets.h"
// OLED config
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET_PIN -1
#define OLED_I2C_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET_PIN);
// PN532 (I2C mode) pins
#define PN532_IRQ 2
#define PN532_RESET 3
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);
// Wi-Fi / server settings
const char* WIFI_SSID = SECRET_SSID;
const char* WIFI_PASS = SECRET_PASS;
const char* SERVER_HOST = "192.168.1.50";
const uint16_t SERVER_PORT = 8080;
const char* SERVER_PATH = "/api/access";
// Authorization list: Allowed NFC UIDs (hex). Replace with your tags.
static const uint8_t AUTH_UIDS[][7] = {
// Example UIDs (lengths vary, common: 4 or 7 bytes). Fill with your real tag UIDs.
{ 0x04, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6 }, // 7-byte sample
{ 0xDE, 0xAD, 0xBE, 0xEF } // 4-byte sample
};
static const size_t AUTH_UIDS_LEN[] = {
7,
4
};
static const size_t AUTH_COUNT = sizeof(AUTH_UIDS_LEN)/sizeof(AUTH_UIDS_LEN[0]);
// Forward declarations
void oledMsg(const String& l1, const String& l2 = "", const String& l3 = "");
String uidToHex(const uint8_t* uid, uint8_t uidLength);
bool isAuthorized(const uint8_t* uid, uint8_t uidLength);
bool ensureWiFi();
bool httpPostAccess(const uint8_t* uid, uint8_t uidLength);
String hmacOfUID(const uint8_t* uid, uint8_t uidLength);
// Utility: Print WiFi firmware version (helps troubleshooting)
void printWiFiFirmware() {
String fv = WiFi.firmwareVersion();
Serial.print(F("WiFiNINA firmware: "));
Serial.println(fv);
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 4000) { /* wait for USB */ }
// OLED init
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
// Hard fail: cannot proceed visually, but continue via Serial
}
display.clearDisplay();
display.display();
oledMsg("NFC WiFi Access", "Nano 33 IoT", "Booting...");
// NFC init
Wire.begin();
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
Serial.println(F("Didn't find PN532. Check I2C & I/F switches."));
oledMsg("PN532 not found", "Check I2C & pins", "");
delay(3000);
} else {
Serial.print(F("PN532 found. Chip: 0x"));
Serial.println((versiondata >> 24) & 0xFF, HEX);
Serial.print(F("Firmware: "));
Serial.print((versiondata >> 16) & 0xFF, DEC);
Serial.print('.');
Serial.println((versiondata >> 8) & 0xFF, DEC);
nfc.setPassiveActivationRetries(0xFF);
nfc.SAMConfig(); // configure board to read RFID tags
}
// Wi-Fi init
if (WiFi.status() == WL_NO_MODULE) {
Serial.println(F("Communication with WiFi module failed!"));
oledMsg("WiFiNINA module", "not found", "");
} else {
printWiFiFirmware();
}
oledMsg("Scan your NFC", "Authorized -> WiFi", "Unauthorized -> Deny");
}
void loop() {
boolean success;
uint8_t uid[7];
uint8_t uidLength = 0;
// Try to read a tag with a short timeout (100 ms) to keep loop responsive
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 100);
if (success) {
String uidHex = uidToHex(uid, uidLength);
Serial.print(F("Tag detected UID: "));
Serial.println(uidHex);
oledMsg("Tag detected:", uidHex, "");
if (isAuthorized(uid, uidLength)) {
Serial.println(F("Authorized tag."));
oledMsg("Authorized", "Connecting WiFi...", "");
if (ensureWiFi()) {
Serial.println(F("WiFi connected."));
oledMsg("WiFi OK", "Contacting server", "");
if (httpPostAccess(uid, uidLength)) {
Serial.println(F("Server accepted access."));
oledMsg("ACCESS GRANTED", uidHex, "Server OK");
} else {
Serial.println(F("Server rejected or error."));
oledMsg("ACCESS PENDING", "Server error", "");
}
} else {
Serial.println(F("WiFi connection failed."));
oledMsg("WiFi failed", "Check SSID/PASS", "");
}
} else {
Serial.println(F("Unauthorized tag."));
oledMsg("ACCESS DENIED", uidHex, "");
}
delay(1500);
oledMsg("Scan your NFC", "", "");
}
// Small delay to avoid I2C flooding
delay(20);
}
void oledMsg(const String& l1, const String& l2, const String& l3) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(l1);
if (l2.length()) display.println(l2);
if (l3.length()) display.println(l3);
display.display();
}
String uidToHex(const uint8_t* uid, uint8_t uidLength) {
char buf[2 * 7 + 1];
size_t idx = 0;
for (uint8_t i = 0; i < uidLength; i++) {
sprintf(&buf[idx], "%02X", uid[i]);
idx += 2;
}
buf[idx] = 0;
return String(buf);
}
bool isAuthorized(const uint8_t* uid, uint8_t uidLength) {
for (size_t i = 0; i < AUTH_COUNT; i++) {
if (AUTH_UIDS_LEN[i] != uidLength) continue;
if (memcmp(AUTH_UIDS[i], uid, uidLength) == 0) {
return true;
}
}
return false;
}
bool ensureWiFi() {
if (WiFi.status() == WL_CONNECTED) return true;
int status = WL_IDLE_STATUS;
unsigned long start = millis();
const unsigned long timeout = 20000; // 20 seconds
Serial.print(F("Connecting to WiFi SSID: "));
Serial.println(WIFI_SSID);
WiFi.disconnect();
delay(300);
while (millis() - start < timeout) {
status = WiFi.begin(WIFI_SSID, WIFI_PASS);
// Wait a bit to establish
for (int i = 0; i < 20; i++) {
if (WiFi.status() == WL_CONNECTED) break;
delay(250);
}
if (WiFi.status() == WL_CONNECTED) break;
Serial.print(F("."));
}
if (WiFi.status() == WL_CONNECTED) {
Serial.print(F("Connected, IP: "));
Serial.println(WiFi.localIP());
return true;
}
return false;
}
String hmacOfUID(const uint8_t* uid, uint8_t uidLength) {
// HMAC-SHA256 over raw UID bytes using the secret key
HMAC<SHA256> hmac;
const uint8_t* key = reinterpret_cast<const uint8_t*>(SECRET_HMAC_KEY);
size_t keyLen = strlen(SECRET_HMAC_KEY);
hmac.reset(key, keyLen);
hmac.update(uid, uidLength);
uint8_t mac[SHA256::HASH_SIZE];
hmac.finalize(mac, sizeof(mac));
// Convert to hex string
char hex[2 * sizeof(mac) + 1];
for (size_t i = 0; i < sizeof(mac); i++) {
sprintf(&hex[2 * i], "%02X", mac[i]);
}
hex[2 * sizeof(mac)] = 0;
return String(hex);
}
bool httpPostAccess(const uint8_t* uid, uint8_t uidLength) {
WiFiClient client;
if (!client.connect(SERVER_HOST, SERVER_PORT)) {
Serial.println(F("HTTP connect failed."));
return false;
}
String uidHex = uidToHex(uid, uidLength);
String hmacHex = hmacOfUID(uid, uidLength);
// Build JSON payload
StaticJsonDocument<200> doc;
doc["uid"] = uidHex;
doc["hmac"] = hmacHex;
String payload;
serializeJson(doc, payload);
// Build HTTP/1.1 request
String request =
String("POST ") + SERVER_PATH + " HTTP/1.1\r\n" +
"Host: " + SERVER_HOST + ":" + String(SERVER_PORT) + "\r\n" +
"User-Agent: Nano33IoT-PN532/1.0\r\n" +
"Content-Type: application/json\r\n" +
"Content-Length: " + String(payload.length()) + "\r\n" +
"Connection: close\r\n" +
"X-Auth: " + hmacHex + "\r\n" +
"\r\n" +
payload;
client.print(request);
// Wait for response status line
unsigned long start = millis();
while (client.connected() && !client.available() && millis() - start < 5000) {
delay(10);
}
if (!client.available()) {
Serial.println(F("No response from server."));
client.stop();
return false;
}
// Read first line of response
String statusLine = client.readStringUntil('\n');
statusLine.trim();
Serial.print(F("HTTP status: "));
Serial.println(statusLine);
bool ok = statusLine.startsWith("HTTP/1.1 200");
// Drain and close
while (client.available()) client.read();
client.stop();
return ok;
}
Notes:
– Replace AUTH_UIDS entries with the UIDs of your authorized tags.
– Fill include/secrets.h with your network credentials and a secret HMAC key.
– The server endpoint is assumed to be reachable at http://192.168.1.50:8080/api/access. You can change SERVER_HOST, SERVER_PORT, and SERVER_PATH.
Optional minimal validation server (Node.js):
// server.js - minimal HTTP server for validation
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/access') {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
if (body.length > 1e6) req.socket.destroy(); // guard
});
req.on('end', () => {
try {
const data = JSON.parse(body);
console.log('Access event:', data);
// In a real system, recompute HMAC and compare with data.hmac
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
} catch (e) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Bad Request');
}
});
} else {
res.writeHead(404);
res.end();
}
});
server.listen(8080, '0.0.0.0', () => {
console.log('Validation server listening on http://0.0.0.0:8080');
});
Build/Flash/Run commands
PlatformIO Core install (choose one):
pipx install platformio
# Option B: pip
python3 -m pip install --upgrade platformio
# Verify
pio --version
Project initialization and build:
# Create and enter project directory
mkdir nfc-wifi-access-nano33iot
cd nfc-wifi-access-nano33iot
# Create files:
# - platformio.ini (as provided)
# - include/secrets.h (fill credentials)
# - src/main.cpp
# Install libraries declared in lib_deps (auto on first build)
pio run
# List serial devices to find the Nano 33 IoT port
pio device list
# Examples: Windows -> COM5 ; Linux -> /dev/ttyACM0 ; macOS -> /dev/cu.usbmodem14101
# Upload firmware (replace with your port)
pio run -t upload -e nano_33_iot --upload-port /dev/ttyACM0
# Open serial monitor at 115200 bps
pio device monitor --baud 115200
Run the optional validation server:
# In a separate terminal on the server host (192.168.1.50 in the example)
node server.js
Adjust SERVER_HOST in src/main.cpp if your server host is different.
Step‑by‑step Validation
- USB/port check
- Connect the Nano 33 IoT via USB.
- Run:
bash
pio device list -
Confirm a device like /dev/ttyACM0 (Linux), /dev/cu.usbmodemXXXX (macOS), or COMx (Windows) appears.
-
Wire inspection
- Verify the connections from the Setup/Connection table.
-
Re‑confirm PN532 is in I2C mode.
-
First flash
- Build and upload:
bash
pio run -t upload -e nano_33_iot --upload-port <your-port> - Open the monitor:
bash
pio device monitor --baud 115200 -
Expected boot logs: PN532 firmware version, WiFiNINA firmware version, and “Scan your NFC”.
-
OLED sanity check
- The OLED should show:
- “NFC WiFi Access”
- Then “Scan your NFC”
-
If blank: recheck VCC/GND/SDA/SCL and I2C address (0x3C default).
-
PN532 detection
- Present a tag near the PN532 antenna.
- Serial monitor should print “Tag detected UID: …”.
-
If nothing happens, swap another tag (ISO14443A), verify IRQ (D2) and RST (D3) wiring.
-
Authorize at least one tag
- Edit AUTH_UIDS in src/main.cpp with an actual UID from your tag (observe the hex reported in Serial).
- Rebuild/upload.
-
Present that tag; you should see:
- OLED: “Authorized” then “WiFi OK” and “Server OK”.
- Serial: “Authorized tag.” then “WiFi connected.” then “Server accepted access.”
-
Server response validation
- Start the validation server:
bash
node server.js - Present the authorized tag again.
- The server terminal should print the JSON payload, e.g.:
- Access event: { uid: ’04A1B2C3D4E5F6′, hmac: ‘…’ }
-
Verify Arduino’s serial shows an HTTP 200 OK.
-
Unauthorized tag test
- Present a tag not in AUTH_UIDS.
- OLED should display “ACCESS DENIED”; no Wi‑Fi or server call attempted.
-
Serial should log “Unauthorized tag.”
-
Wi‑Fi failure path
- Temporarily change your SSID in include/secrets.h to a wrong value; rebuild/upload.
- Present an authorized tag.
-
Expected:
- OLED: “WiFi failed”
- Serial: “WiFi connection failed.”
-
Server failure path
- Stop the validation server or change SERVER_HOST to an unreachable IP.
- Present authorized tag.
- Expected:
- Serial: “HTTP connect failed.” or “No response from server.”
- OLED: “ACCESS PENDING / Server error”
Troubleshooting
- PN532 not detected
- Symptom: “Didn’t find PN532.” in Serial/OLED.
-
Checks:
- Confirm PN532 set to I2C mode (switch/jumper config).
- Verify wiring: SDA->A4, SCL->A5, IRQ->D2, RST->D3, VIN->3V3, GND->GND.
- Ensure 3.3 V power; avoid 5 V power or level shifters that clamp.
- Ensure only one module uses those IRQ/RST pins.
- Try power‑cycling after changing interface switches.
-
OLED shows nothing
- Confirm 3.3 V and GND.
- Check I2C lines, correct address (0x3C). Some displays are at 0x3D—change OLED_I2C_ADDR accordingly.
-
If using long wires, lower I2C speed or shorten wires.
-
Tags not read
- Ensure ISO14443A tags (MIFARE Classic/Ultralight). The PN532 example code targets PN532_MIFARE_ISO14443A.
- Keep the tag within a few centimeters and centered over the PN532 antenna.
-
Avoid metal surfaces under antenna.
-
Wi‑Fi issues
- Serial shows “Communication with WiFi module failed!”
- Rare, but indicates module not responding. Try unplug/replug, different USB cable/port.
- Wrong SSID/PASS: adjust include/secrets.h.
- Weak Wi‑Fi signal: place closer to AP, ensure 2.4 GHz network.
-
Firmware mismatch: Some WiFiNINA features require recent firmware. Use a separate maintenance step to update NINA‑W102 firmware (can be done with Arduino IDE’s firmware updater if necessary).
-
HTTP server unreachable
- Confirm server host/IP and port are correct and reachable from the device’s Wi‑Fi network.
- Open firewall for TCP/8080.
-
Use ping from a PC on the same network and verify the route.
-
HMAC validation fails on backend
- Ensure the backend computes HMAC‑SHA256 over the same bytes (raw UID) with the exact same shared secret.
- Check hex case (upper vs lower) and stripping issues.
-
Consider including UID length in the HMAC input if you have mixed 4/7‑byte tags.
-
PlatformIO upload errors (port)
- Use:
bash
pio device list
and choose the correct port. - Close any Serial Monitor before uploading.
-
On Windows, if no COM port appears, try a different cable/port or install Arduino SAMD drivers.
-
Power stability
- If you see intermittent resets, use a powered USB hub or ensure your PC/laptop can supply sufficient current.
Improvements
- Transport security (TLS):
- Replace WiFiClient with WiFiSSLClient and use HTTPS on your server.
- Validate the server certificate (fingerprint or full chain) to prevent MITM attacks.
-
Note: This increases flash/RAM use; ensure WiFiNINA firmware is up to date.
-
Stronger request semantics:
- Include a timestamp and nonce in the payload and the HMAC computation.
-
Backend should reject stale or replayed tokens.
-
Access decisions on backend:
- Send tag UIDs to the backend and let it decide (centralized ACL).
-
Cache allow/deny locally for offline operation with a short TTL.
-
Persistent authorized list:
- Store authorized UIDs in nonvolatile memory (e.g., emulated EEPROM or flash).
-
Add a serial command to enroll/revoke badges.
-
Non‑blocking architecture:
- Convert the loop into a state machine to avoid blocking delays.
-
Debounce repeated reads: wait for tag removal before accepting the next read.
-
Visual and audible feedback:
-
Add a status LED or buzzer for distinct grant/deny signals.
-
PN532 interface alternatives:
- SPI can reduce I2C bus contention and often improves throughput.
-
If you switch to SPI, update wiring and use the SPI constructor for Adafruit_PN532.
-
Backend integration:
- Replace the validation server with your production access controller (e.g., a REST API that drives a relay/door strike).
-
Implement logging, rate limiting, and alerting for failed attempts.
-
Hardening secrets:
- Avoid storing secrets in plaintext in firmware. Consider a secure element or at least obfuscation.
-
Rotate secrets periodically.
-
OTA updates:
-
Implement an update mechanism to deploy new firmware without physical access.
-
Monitoring:
- Expose a local HTTP status endpoint or simple mDNS service with uptime and last access logs.
Final Checklist
- Hardware
- Arduino Nano 33 IoT connected via USB.
- PN532 wired to A4/A5 (I2C), IRQ->D2, RST->D3; powered at 3.3 V.
- SSD1306 wired to A4/A5 (I2C); powered at 3.3 V.
-
Common ground verified.
-
Software
- PlatformIO Core installed; pio commands available.
- platformio.ini created with exact lib versions.
- include/secrets.h filled with SSID, PASS, and strong SECRET_HMAC_KEY.
-
src/main.cpp compiled without errors.
-
Build/Flash
- Board port identified via “pio device list”.
- Firmware uploaded via:
- pio run -t upload -e nano_33_iot –upload-port
- pio run -t upload -e nano_33_iot –upload-port
-
Serial monitor open at 115200 baud.
-
Validation
- OLED displays “Scan your NFC”.
- PN532 reports firmware in Serial; tags detected produce UID log lines.
- Authorized tag triggers Wi‑Fi connect and HTTP 200 OK from your server.
-
Unauthorized tag shows “ACCESS DENIED”.
-
Troubleshooting resolved
- No I2C address conflicts; OLED and PN532 are visible and functional.
- Wi‑Fi credentials verified; WiFiNINA firmware acceptable.
- Server host reachable and receiving JSON payloads.
With this build, the Arduino Nano 33 IoT + PN532 NFC + SSD1306 OLED acts as a compact NFC‑driven access control client over Wi‑Fi. You can now evolve it into a production‑grade system with TLS, centralized authorization, and robust device management.
Find this product and/or books on this topic on Amazon
As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.



