NFC Reader Firmware Development
Firmware for NFC readers must handle RF state machines, protocol timing, NDEF parsing, error recovery, and host communication — often on resource-constrained MCUs. This guide covers the firmware architecture, critical timing requirements, and common pitfalls for embedded NFC development.
Firmware Architecture Layers
A well-structured NFC firmware stack follows a layered model:
┌─────────────────────────────────┐
│ Application Layer │ Business logic, cloud sync
├─────────────────────────────────┤
│ NDEF / NFC Forum Layer │ NDEF parse, tag type dispatch
├─────────────────────────────────┤
│ NFC Protocol Layer │ ISO 14443 T=CL, ISO 15693, FeliCa
├─────────────────────────────────┤
│ RF Abstraction Layer (RFAL) │ IC-independent RF commands
├─────────────────────────────────┤
│ NFC IC Driver │ SPI/I2C register access (PN532, ST25R)
├─────────────────────────────────┤
│ MCU HAL │ GPIO, SPI, I2C, Timer, UART
└─────────────────────────────────┘
Each layer should only depend on the layer below. This allows swapping the NFC IC (e.g., PN532 → ST25R3916) by only replacing the IC driver layer.
Critical Timing Requirements
NFC protocols specify tight timing windows that cannot be violated. Missing these in firmware causes intermittent failures that are hard to reproduce:
| Protocol Event | Timing Requirement | Consequence of Violation |
|---|---|---|
| ISO 14443ISO 14443Standard for contactless smart cards at 13.56 MHz (Types A and B)View full → Frame Delay Time (FDT) | 1172/128 × bit time ≈ 1.2 ms min | Reader sends before tag ready → NAK |
| ISO 14443 Guard Time | ≥ 5 ms between field off and on | Tag loses power, resets state |
| T=CL WTX (Waiting Time eXtension) | Must respond within FWTI window | Transaction timeout |
| ISO 15693ISO 15693Standard for vicinity-range smart cards, 1+ meter read rangeView full → t1 (SOF to EOF response) | 320.9 µs | iso-15693 tag ignores command |
| NTAG PWD_AUTH timeout | ≤ 2 s from activation | Tag locks out after N failed attempts |
Use hardware timers, not software delay loops, for all NFC timing. On ARM Cortex-M, use SysTick or a dedicated TIM peripheral. Interrupt-driven SPI with DMA is essential — blocking SPI transfers cannot guarantee timing.
Discovery Loop State Machine
The polling discovery loop is the heart of NFC readerNFC readerActive device generating RF field to initiate communication with tagsView full → firmware:
typedef enum {
NFC_STATE_IDLE,
NFC_STATE_POLLING_A,
NFC_STATE_POLLING_B,
NFC_STATE_POLLING_V,
NFC_STATE_TAG_ACTIVE,
NFC_STATE_TAG_LOST,
NFC_STATE_ERROR
} NfcReaderState;
void nfc_discovery_loop(void) {
switch (state) {
case NFC_STATE_IDLE:
rf_field_on();
state = NFC_STATE_POLLING_A;
break;
case NFC_STATE_POLLING_A:
if (iso14443a_poll(&tag_a) == ERR_NONE) {
state = NFC_STATE_TAG_ACTIVE;
} else {
state = NFC_STATE_POLLING_B;
}
break;
// ... additional states
case NFC_STATE_TAG_ACTIVE:
ndef_read_message(&tag_a, &ndef_buf);
on_tag_detected_callback(&ndef_buf);
state = NFC_STATE_TAG_LOST;
break;
}
}
The loop should be called from a periodic timer ISR or a dedicated RTOS task, not from the main loop with blocking delays.
NDEF Parsing in C
For resource-constrained MCUs, implement a minimal NDEF parser that avoids dynamic memory allocation:
typedef struct {
uint8_t tnf;
uint8_t type_len;
uint32_t payload_len;
uint8_t id_len;
const uint8_t *type;
const uint8_t *payload;
bool is_mb; // Message Begin
bool is_me; // Message End
bool is_sr; // Short Record
} NdefRecord;
NfcStatus ndef_parse_record(const uint8_t *buf, size_t buf_len,
NdefRecord *record, size_t *consumed) {
if (buf_len < 3) return NFC_ERR_BUFFER_TOO_SMALL;
uint8_t flags = buf[0];
record->tnf = flags & 0x07;
record->is_mb = (flags >> 7) & 1;
record->is_me = (flags >> 6) & 1;
record->is_sr = (flags >> 4) & 1;
record->type_len = buf[1];
record->payload_len = record->is_sr ? buf[2] :
((uint32_t)buf[2]<<24|(uint32_t)buf[3]<<16|
(uint32_t)buf[4]<<8|buf[5]);
// ... offset calculation and pointer assignment
return NFC_OK;
}
Avoid using malloc — pre-allocate a fixed-size NDEF buffer (typically 256–1024 bytes depending on your tag memory) and parse in-place.
Anti-Collision Handling
When multiple tags are in the RF field, the anti-collision loop must arbitrate cleanly. For ISO 14443-3A (NFC-A):
- Reader sends REQA (0x26, 7-bit frame)
- All tags respond simultaneously — a collision occurs if UIDs differ
- Reader sends SELECT with a bit string covering only non-colliding prefix bits
- Colliding tags that don't match the prefix go silent
- Repeat until single uid resolved
Most NFC IC drivers handle this automatically. The firmware only needs to handle the case where a second tag appears mid-transaction (causing the active tag to lose power in some configurations).
Power Management
NFC readers consume ~100 mA with RF on. In battery-powered devices, duty-cycle the RF field:
| Mode | RF Field | Current | Use Case |
|---|---|---|---|
| Continuous poll | Always on | ~100 mA | Mains-powered reader |
| Low-power poll | 100 ms on / 900 ms off | ~12 mA avg | Battery, ≤ 1 s latency |
| Wake-on-approach | Low-power sensor triggers RF | ~2 mA avg | Long battery life |
For wake-on-approach, some ICs (ST25R3916) offer a dedicated low-power card detection mode that draws < 1 mA and asserts an interrupt when a tag enters the field.
Error Handling and Recovery
| Error | Recovery Action | Retry Limit |
|---|---|---|
| RF transmission error | Retry command once | 2 retries |
| No response (timeout) | Cycle RF field off/on | 3 cycles |
| Protocol error (wrong response) | De-select and re-select tag | 2 retries |
| FIFO overflow | Increase buffer; log warning | No retry |
| I2C/SPI bus error | Reinitialise IC | 3 attempts, then alarm |
Log all errors with structlog or a minimal UART debug stream in development. In production, report error counts to your backend via the application layer.
Use the Chip Selector to evaluate which nfc-controller IC fits your firmware environment, and the Read Range Estimator to predict field strength from your chosen nfc-antenna design. The Compatibility Checker verifies that your firmware-controlled reader handles all required passive-tag types.