NFC Unit

This library drives Unit NFC (ST25R3916 on I2C). It discovers ISO14443 Type A tags, resolves chip type (Classic, Ultralight / NTAG family, DESFire, Plus, etc.), and exposes high-level read/write helpers for supported tag kinds.

Support the following products:

NFCUnit

UiFlow2 Example

Detect card

Open the cores3_unit_nfc_example.m5f2 project in UiFlow2.

This example polls the reader, shows UID, type name, and user memory size on the screen, and plays a short tone when a tag is present.

UiFlow2 Code Block:

cores3_unit_nfc_example.png

Example output:

None

MicroPython Example

Detect card

This example polls the reader, shows UID, type name, and user memory size on the screen, and plays a short tone when a tag is present.

MicroPython Code Block:

  1# SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
  2#
  3# SPDX-License-Identifier: MIT
  4
  5import os, sys, io
  6import M5
  7from M5 import *
  8import m5ui
  9import lvgl as lv
 10from hardware import Pin
 11from hardware import I2C
 12from unit import NFCUnit
 13import time
 14
 15
 16page0 = None
 17label_title = None
 18label_uid = None
 19label_type = None
 20label_size = None
 21i2c0 = None
 22nfc_0 = None
 23card_0 = None
 24card_uid = None
 25new = None
 26card_type = None
 27card_memory = None
 28write_buf = None
 29count = None
 30last_time = None
 31
 32
 33def setup():
 34    global \
 35        page0, \
 36        label_title, \
 37        label_uid, \
 38        label_type, \
 39        label_size, \
 40        i2c0, \
 41        nfc_0, \
 42        card_0, \
 43        card_uid, \
 44        new, \
 45        card_type, \
 46        card_memory, \
 47        write_buf, \
 48        count, \
 49        last_time
 50
 51    M5.begin()
 52    Widgets.setRotation(1)
 53    m5ui.init()
 54    page0 = m5ui.M5Page(bg_c=0x000000)
 55    label_title = m5ui.M5Label(
 56        "NFC Card detect",
 57        x=58,
 58        y=5,
 59        text_c=0x14ACDB,
 60        bg_c=0x000000,
 61        bg_opa=0,
 62        font=lv.font_montserrat_24,
 63        parent=page0,
 64    )
 65    label_uid = m5ui.M5Label(
 66        "UID:",
 67        x=18,
 68        y=70,
 69        text_c=0xFFFFFF,
 70        bg_c=0x000000,
 71        bg_opa=0,
 72        font=lv.font_montserrat_16,
 73        parent=page0,
 74    )
 75    label_type = m5ui.M5Label(
 76        "Type:",
 77        x=10,
 78        y=100,
 79        text_c=0xFFFFFF,
 80        bg_c=0x000000,
 81        bg_opa=0,
 82        font=lv.font_montserrat_16,
 83        parent=page0,
 84    )
 85    label_size = m5ui.M5Label(
 86        "Size:",
 87        x=16,
 88        y=130,
 89        text_c=0xFFFFFF,
 90        bg_c=0x000000,
 91        bg_opa=0,
 92        font=lv.font_montserrat_16,
 93        parent=page0,
 94    )
 95
 96    i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=400000)
 97    nfc_0 = NFCUnit(i2c0)
 98    page0.screen_load()
 99    Speaker.begin()
100    Speaker.setVolumePercentage(0.5)
101    new = True
102    write_buf = bytearray(16)
103    write_buf[0] = 0x12
104    count = 0
105
106
107def loop():
108    global \
109        page0, \
110        label_title, \
111        label_uid, \
112        label_type, \
113        label_size, \
114        i2c0, \
115        nfc_0, \
116        card_0, \
117        card_uid, \
118        new, \
119        card_type, \
120        card_memory, \
121        write_buf, \
122        count, \
123        last_time
124    M5.update()
125    card_0 = nfc_0.detect()
126    if card_0:
127        card_uid = card_0.uid_str
128        card_type = card_0.type_name
129        card_memory = card_0.user_memory
130        label_uid.set_text(str((str("UID: ") + str(card_uid))))
131        label_type.set_text(str((str("Type: ") + str(card_type))))
132        label_size.set_text(str((str("Size: ") + str(card_memory))))
133        if (time.ticks_diff((time.ticks_ms()), last_time)) >= 3000 or new:
134            last_time = time.ticks_ms()
135            Speaker.tone(900, 100)
136            print((str("read data befor write ") + str((nfc_0.read(card_0, 1)))))
137            count = (count if isinstance(count, (int, float)) else 0) + 1
138            write_buf[-1] = 0x12 + count
139            time.sleep_ms(100)
140            if nfc_0.write(card_0, 1, write_buf):
141                print("write success")
142                time.sleep_ms(100)
143                print((str("read data after write ") + str((nfc_0.read(card_0, 1)))))
144        new = False
145    else:
146        new = True
147
148
149if __name__ == "__main__":
150    try:
151        setup()
152        while True:
153            loop()
154    except (Exception, KeyboardInterrupt) as e:
155        try:
156            m5ui.deinit()
157            from utility import print_error_msg
158
159            print_error_msg(e)
160        except ImportError:
161            print("please update to latest firmware")

Example output:

None

API

NFCUnit

class unit.nfc.NFCUnit

Driver for Unit NFC. Pass a configured I2C bus instance (for example from hardware.I2C).

Parameters:

i2c – I2C bus used to talk to the ST25R3916.

UiFlow2 Code Block:

init.png

MicroPython Code Block:

from hardware import Pin, I2C
from unit import NFCUnit

i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=400000)
nfc = NFCUnit(i2c0)
detect()

Poll for a Type A tag in the field.

Returns:

A unit.nfc.Card instance if a tag was found and identified, otherwise None.

UiFlow2 Code Block:

detect.png

MicroPython Code Block:

card = nfc.detect()
if card:
    print(card.uid_str, card.type_name)
write(card, index, data)

Write one address unit.

Note

Only MIFARE Classic is supported for now.

data must be 16 bytes; index is the block number. Sector trailers and block 0 require valid keys/access rules from the card.

Parameters:
Returns:

True on success, False otherwise.

UiFlow2 Code Block:

write.png

MicroPython Code Block:

ok = nfc.write(card, index, data)
read(card, index)

Read one address unit for the current tag.

  • MIFARE Classic: index is the global block number (0-based). Returns 16 bytes on success, or None.

  • Type 2 family (Ultralight / NTAG / ST25TA / ISO18092 where applicable): index is the page number. Returns 4 bytes on success, or None.

  • Other chip types: None (use chip-specific flows outside this helper).

Parameters:
Returns:

bytes or None.

UiFlow2 Code Block:

read.png

MicroPython Code Block:

data = nfc.read(card, index)
halt()

Send HLTA to put the Type A PICC into HALT state.

UiFlow2 Code Block:

halt.png

MicroPython Code Block:

nfc.halt()
rf_off()

Turn the reader RF field off.

UiFlow2 Code Block:

rf_off.png

MicroPython Code Block:

nfc.rf_off()
rf_on()

Turn the reader RF field on.

UiFlow2 Code Block:

rf_on.png

MicroPython Code Block:

nfc.rf_on()

Card

class unit.nfc.Card

Object returned by NFCUnit.detect() when a tag is present. Holds the anti-collision result and the resolved type metadata from the stack (SAK/ATQA/version/ATS paths as implemented in firmware).

Attributes

UiFlow2 Code Block:

attribute.png

uid

bytes — UID bytes (length uid_len).

type_id

int — Internal type id used by this driver (aligned with the TYPE_NAMES table in unit/nfc.py).

type_name

str — Resolved chip label from the identification logic in firmware; same strings as TYPE_NAMES in unit/nfc.py. type_id selects the row below (unknown or unclassified tags use Unknown).

type_id and type_name (index into TYPE_NAMES)

type_id

type_name

0

Unknown

1

MIFARE_Classic_Mini

2

MIFARE_Classic_1K

3

MIFARE_Classic_2K

4

MIFARE_Classic_4K

5

MIFARE_Ultralight

6

MIFARE_Ultralight_EV1_1

7

MIFARE_Ultralight_EV1_2

8

MIFARE_Ultralight_Nano

9

MIFARE_UltralightC

10

NTAG_203

11

NTAG_210u

12

NTAG_210

13

NTAG_212

14

NTAG_213

15

NTAG_215

16

NTAG_216

17

ST25TA_512B

18

ST25TA_2K

19

ST25TA_16K

20

ST25TA_64K

21

ISO_14443_4

22

MIFARE_Plus_2K

23

MIFARE_Plus_4K

24

MIFARE_Plus_SE

25

MIFARE_DESFire_2K

26

MIFARE_DESFire_4K

27

MIFARE_DESFire_8K

28

MIFARE_DESFire_Light

29

NTAG_4XX

30

ISO_18092

user_memory

int — Advertised user memory size in bytes for known types (0 if unknown; used for Type 2 dump heuristics).

uid_str

UID as upper-case hex string.

is_classic()

Return True if type_id is a MIFARE Classic family id (Mini / 1K / 2K / 4K).

UiFlow2 Code Block:

is_classic.png

MicroPython Code Block:

card.is_classic()