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:
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:
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
I2Cbus instance (for example fromhardware.I2C).- Parameters:
i2c – I2C bus used to talk to the ST25R3916.
UiFlow2 Code Block:

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.Cardinstance if a tag was found and identified, otherwiseNone.
UiFlow2 Code Block:

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.
datamust be 16 bytes;indexis the block number. Sector trailers and block0require valid keys/access rules from the card.- Parameters:
card (unit.nfc.Card) – Tag from
detect().index (int) – Block index.
data (bytes) – Exactly 16 bytes for Classic.
- Returns:
Trueon success,Falseotherwise.
UiFlow2 Code Block:

MicroPython Code Block:
ok = nfc.write(card, index, data)
- read(card, index)
Read one address unit for the current tag.
MIFARE Classic:
indexis the global block number (0-based). Returns 16 bytes on success, orNone.Type 2 family (Ultralight / NTAG / ST25TA / ISO18092 where applicable):
indexis the page number. Returns 4 bytes on success, orNone.Other chip types:
None(use chip-specific flows outside this helper).
- Parameters:
card (unit.nfc.Card) – Tag returned by
detect().index (int) – Block index (Classic) or page index (Type 2).
- Returns:
bytesorNone.
UiFlow2 Code Block:

MicroPython Code Block:
data = nfc.read(card, index)
- halt()
Send HLTA to put the Type A PICC into HALT state.
UiFlow2 Code Block:

MicroPython Code Block:
nfc.halt()
- rf_off()
Turn the reader RF field off.
UiFlow2 Code Block:

MicroPython Code Block:
nfc.rf_off()
- rf_on()
Turn the reader RF field on.
UiFlow2 Code Block:

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:

- uid
bytes— UID bytes (lengthuid_len).
- type_id
int— Internal type id used by this driver (aligned with theTYPE_NAMEStable inunit/nfc.py).
- type_name
str— Resolved chip label from the identification logic in firmware; same strings asTYPE_NAMESinunit/nfc.py.type_idselects the row below (unknown or unclassified tags useUnknown).type_idandtype_name(index intoTYPE_NAMES)type_idtype_name0
Unknown1
MIFARE_Classic_Mini2
MIFARE_Classic_1K3
MIFARE_Classic_2K4
MIFARE_Classic_4K5
MIFARE_Ultralight6
MIFARE_Ultralight_EV1_17
MIFARE_Ultralight_EV1_28
MIFARE_Ultralight_Nano9
MIFARE_UltralightC10
NTAG_20311
NTAG_210u12
NTAG_21013
NTAG_21214
NTAG_21315
NTAG_21516
NTAG_21617
ST25TA_512B18
ST25TA_2K19
ST25TA_16K20
ST25TA_64K21
ISO_14443_422
MIFARE_Plus_2K23
MIFARE_Plus_4K24
MIFARE_Plus_SE25
MIFARE_DESFire_2K26
MIFARE_DESFire_4K27
MIFARE_DESFire_8K28
MIFARE_DESFire_Light29
NTAG_4XX30
ISO_18092
- user_memory
int— Advertised user memory size in bytes for known types (0if unknown; used for Type 2 dump heuristics).
- uid_str
UID as upper-case hex string.
- is_classic()
Return
Trueiftype_idis a MIFARE Classic family id (Mini / 1K / 2K / 4K).UiFlow2 Code Block:

MicroPython Code Block:
card.is_classic()

