Embedded HandbookПриложения
Все разделы › X. Проектирование, сети и IoT
STM32L476, STM32F103 · уровень средний · 11 мин чтения
Открыть в интерактивном справочнике

94а. NFC-метка ST25DV: телефон и микроконтроллер делят одну память

Кнопок и экрана нет, а настроить прибор надо: адрес, порог, ключ сети. Или прибор сломался, а журнал из него всё равно хочется достать. Для этого есть NFC-метки с двумя входами. С одной стороны к памяти метки по радио обращается телефон, с другой — микроконтроллер по I²C. ST25DV — такая метка от ST. Раздел о том, как с ней работает прошивка: какие адреса на шине, что лежит по какому адресу, зачем пароль и как метка сообщает, что телефон что-то записал.

Опорные значения — из драйвера ST25DV, который ST кладёт в пакет поддержки STM32L4 для Keil (st25dv.c, st25dv.h, st25dv_reg.h). Опыт: этот драйвер без изменений собран на компьютере и подключён к модели метки, которая печатает каждую транзакцию I²C. Сверяет _verify/nfc_st25_check.py.

Сам радиоинтерфейс в этом разделе не разбирается. Про 13,56 МГц, ближнюю зону и питание метки от поля считывателя — раздел 89.

Как это называется в документации

Оригинал Перевод
dual interface EEPROM EEPROM с двумя входами: радио и проводным
user memory / user area пользовательская память / зона
system configuration area системная область: статические настройки
dynamic register динамический регистр: текущее значение настройки, а не сохранённое
fast transfer mode, mailbox быстрая передача через почтовый ящик в ОЗУ
GPO — General Purpose Output выход прерывания метки
I2C password, security session пароль I²C, сессия после его предъявления
energy harvesting сбор энергии извне; у NFC-метки — питание от поля считывателя, выведенное на отдельный вывод для внешней схемы

94а.1 Зачем метка с двумя входами

Обычная NFC-метка — пассивная память: телефон пишет, телефон читает. У ST25DV есть второй вход, I²C, и её память видна микроконтроллеру. Из этого получаются три типовых применения:


94а.2 Два адреса на шине и карта памяти

Метка отвечает на шине I²C двумя адресами. По ним видны разные части памяти, а драйвер выбирает адрес по одному биту номера регистра:

Адрес (8 бит) 7 бит Что за ним
0xA6 0x53 пользовательская EEPROM с 0x0000, динамические регистры 0x2000…0x2007, почтовый ящик с 0x2008
0xAE 0x57 системная область: статические настройки, паспорт метки, пароль

Правило драйвера для регистров: если в номере поднят бит 0x2000, запрос уходит на 0xA6, иначе на 0xAE. Пользовательскую EEPROM читают и пишут отдельные функции драйвера ReadData и WriteData (St25Dv_Drv.ReadData), и они всегда идут на 0xA6. HAL STM32 и этот драйвер передают адрес уже сдвинутым влево, поэтому в коде стоит 0xA6, а в семибитной записи (раздел 17) тот же адрес — 0x53.

Нужные регистры системной области:

Регистр Адрес Что хранит
GPO 0x0000 какие события выводить на вывод GPO
ENDA1, ENDA2, ENDA3 0x0005, 0x0007, 0x0009 границы зон пользовательской памяти
I2CSS 0x000B защита зон со стороны I²C
MEM_SIZE 0x0014–0x0015 число блоков минус один
BLK_SIZE 0x0016 размер блока минус один
ICREF 0x0017 код микросхемы
UID 0x0018 уникальный номер, 8 байт
I2C_PWD 0x0900 окно для пароля

Динамические: GPO_DYN 0x2000, EH_CTRL_DYN 0x2002, RF_MNGT_DYN 0x2003, I2C_SSO_DYN 0x2004 (открыта ли сессия), IT_STS_DYN 0x2005 (что случилось), MB_CTRL_DYN 0x2006 (включение ящика), MB_LEN_DYN 0x2007.

ST25DV: одна память, два входа — радио для телефона и I²C для микроконтроллера


94а.3 Опыт: настоящий драйвер и модель метки

Проверять драйвер на живой метке — значит видеть только итог: получилось или нет. Здесь сделано иначе. Файлы драйвера из пакета собраны gcc на компьютере без единой правки, а функции шины — Read, Write, IsReady, GetTick, которые драйвер получает через ST25DV_RegisterBusIO, — это модель метки, печатающая каждую транзакцию: W или R, адрес, регистр, длина и байты.

Модель нужна только для того, чтобы драйверу было с кем разговаривать. Её поведение задано в опыте и отмечено в выводе словом «модель»: паспорт на 2048 байт, пароль из восьми нулей, три отказа на опрос готовности после каждой записи в EEPROM, отказ в записи системной области без пароля. Байты на шине — ровно то, что сформировал драйвер.

Единственная замена — пустой cmsis_compiler.h: на компьютере нет CMSIS, а драйверу из него нужен только stdint.h.


94а.4 Опознание и объём

== 1. Init: опознание
R 0xAE 0x0017 [  1] 26
Init = 0
== 2. объём памяти
R 0xAE 0x0014 [  1] FF
R 0xAE 0x0015 [  1] 01
R 0xAE 0x0016 [  1] 03
Mem_Size = 511, BlockSize = 3 -> 2048 байт

Init читает один байт — ICREF — и сравнивает с двумя константами драйвера: 0x24 — метка на 4 Кбит, 0x26 — на 16 или 64 Кбит. Объём считается как (MEM_SIZE + 1) × (BLK_SIZE + 1): 512 блоков по 4 байта — 2048 байт, это 16 Кбит.

В том же заголовке есть и коды 0x25 и 0x27: макрос ST25DV_AM_I_CMOS называет их CMOS-вариантами, а ST25DV_AM_I_OPEN_DRAIN — 0x24 и 0x26 вариантами с открытым стоком. Но Init принимает только 0x24 и 0x26: CMOS-метку этот драйвер при опознании отвергнет.


94а.5 Пароль и сессия I²C

Системная область хранит настройки, от которых зависит безопасность: границы зон, их защиту, режим почтового ящика. Писать туда можно только после предъявления пароля. Комментарии драйвера у таких функций говорят одно и то же: запись подействует только после предъявления пароля I²C. В опыте модель без пароля ответила NACK:

== 3. зоны без пароля
W 0xAE 0x0005 [  1] 0F
        модель: NACK — системная область без пароля не пишется
WriteEndZonex = -102
== 4. пароль
W 0xAE 0x0900 [ 17] 00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 00
        модель: пароль верный, сессия I2C открыта
        опрос готовности: 1 раз, из них NACK 0
PresentI2CPassword = 0
R 0xA6 0x2004 [  1] 01
сессия = 1

Пароль — 64 бита. Драйвер строит посылку из 17 байт: пароль (8 байт, старший байт первым), код 0x09, и ещё раз тот же пароль. Смена пароля — та же посылка с кодом 0x07, где оба раза стоит новый пароль, и сделать её можно только внутри открытой сессии. Открыта ли сессия, показывает динамический регистр I2C_SSO_DYN (0x2004); -102 — это ST25DV_NACK.

Пароль в прошивке — такой же секрет, как ключ шифрования: кто прочитал его из Flash, тот может перенастроить метку.


94а.6 Зоны памяти

Пользовательскую EEPROM можно разрезать на четыре зоны и каждой задать свою защиту — отдельно для радио и для I²C. Границы хранятся в ENDA1… ENDA3 в единицах по 32 байта: номер последнего 32-байтного куска зоны.

ST25DV_CreateUserZone(512, 512, 512, 512) в опыте:

W 0xAE 0x0009 [  1] 3F
W 0xAE 0x0007 [  1] 3F
W 0xAE 0x0005 [  1] 3F
W 0xAE 0x0005 [  1] 0F
W 0xAE 0x0007 [  1] 1F
W 0xAE 0x0009 [  1] 2F

Сначала все три границы ставятся в конец памяти: 2048 / 32 − 1 = 0x3F, в порядке 3, 2, 1. Затем пишутся настоящие: 512 / 32 − 1 = 0x0F, 1024 / 32 − 1 = 0x1F, 1536 / 32 − 1 = 0x2F, теперь в порядке 1, 2, 3. На первом шаге метка может ответить NACK — когда ENDA2 или ENDA3 становится равной предыдущей границе. Комментарий драйвера называет такой NACK нормальным, и функция идёт дальше. Длины, не кратные 32, драйвер округляет вниз.

Каждая из шести записей — в EEPROM, и после каждой драйвер ждёт готовности (94а.7). Объём памяти при этом читается дважды: один раз в CreateUserZone, второй — во вложенной InitEndZone, итого шесть лишних чтений.

Зоны ST25DV: 2048 байт на четыре зоны по 512, границы ENDA1…ENDA3 = 0x0F, 0x1F, 0x2F в кусках по 32 байта; драйвер сначала ставит все три в конец 0x3F в порядке 3, 2, 1, потом на место в порядке 1, 2, 3


94а.7 Запись в EEPROM: куски по 256 байт и опрос готовности

== 6. запись 300 байт в EEPROM
W 0xA6 0x0000 [256] 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 …
        опрос готовности: 4 раз, из них NACK 3
W 0xA6 0x0100 [ 44] 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 …
        опрос готовности: 4 раз, из них NACK 3
WriteData = 0

За одну транзакцию метка принимает не больше 256 байт, поэтому 300 байт ушли двумя кусками: 256 с адреса 0x0000 и 44 с 0x0100.

После каждого куска метка некоторое время пишет EEPROM и на свой адрес не отвечает. Драйвер спрашивает её IsReady в цикле, пока не получит ACK, — это опрос готовности. Предел ожидания — ST25DV_WRITE_TIMEOUT = 320 мс; комментарий в драйвере выводит его как 256 байт / 4 байта внутренней страницы × 5 мс на страницу. Если метка так и не ответила, функция вернёт ST25DV_TIMEOUT.

Запись 300 байт в EEPROM ST25DV: кусок 256 байт с адреса 0x0000, опрос готовности — три NACK и ACK, затем кусок 44 байта с 0x0100 и снова четыре опроса; предел ожидания 320 мс — это 256 байт на 4 байта внутренней страницы по 5 мс

После записи в динамические регистры и почтовый ящик драйвер готовности не ждёт: опрос стоит только за записью в EEPROM и системную область.


94а.8 Почтовый ящик и вывод GPO

== 7. почтовый ящик
R 0xA6 0x2006 [  1] 00
W 0xA6 0x2006 [  1] 01
W 0xA6 0x2008 [ 10] 74 65 6D 70 3D 32 33 2E 35 43
WriteMailboxData = 0
WriteMailboxData(257) = -1
== 8. вывод GPO
W 0xAE 0x0000 [  1] C8
        опрос готовности: 4 раз, из них NACK 3
ConfigIT = 0

ST25DV_SetMBEN_Dyn включает ящик по принципу «прочитать — изменить — записать»: прочитала MB_CTRL_DYN, подняла бит MBEN (0x01), записала. На живой метке этого мало: MBEN поднимается, только если быструю передачу разрешает статический регистр MB_MODE (0x000D, бит 0), а с завода там 0 — и запись в MB_CTRL_DYN метка не принимает. Поэтому один раз, после PresentI2CPassword, вызывают ST25DV_WriteMBMode(…, ST25DV_ENABLE): это запись в системную область, с опросом готовности, как у GPO. Значение хранится в EEPROM, и дальше SetMBEN_Dyn работает при каждом включении. Модель в опыте этот запрет не изображает, поэтому в журнале его не видно. Сообщение temp=23.5C — 10 байт с адреса 0x2008. Больше 256 байт драйвер в ящик не пишет: на 257 он вернул -1 (ST25DV_ERROR), не выйдя на шину.

GPO — вывод метки, который можно завести на вход EXTI микроконтроллера (раздел 7). В регистре GPO выбирают, какие события его дёргают. По маскам из st25dv_reg.h:

Бит Маска Событие
0 0x01 RFUSERSTATE — состояние, заданное по радио
1 0x02 RFACTIVITY — активность по радио
2 0x04 RFINTERRUPT — прерывание, запрошенное по радио
3 0x08 FIELDCHANGE — поле появилось или пропало
4 0x10 RFPUTMSG — телефон положил сообщение в ящик
5 0x20 RFGETMSG — телефон забрал сообщение
6 0x40 RFWRITE — запись по радио
7 0x80 ENABLE — вывод включён вообще

В опыте 0xC8 = 0x80 | 0x40 | 0x08: вывод включён, реагирует на запись телефоном и на поле. GPO — системный регистр, значит, нужен пароль и опрос готовности. После прерывания прошивка читает IT_STS_DYN (0x2005), чтобы узнать, что именно случилось. Длительность импульса выбирают в ITTIME из восьми значений — от 302 до 37 мкс.

Регистры GPO и IT_STS_DYN друг под другом: три младших бита совпадают, выше IT_STS_DYN сдвинут на один, и запись телефоном — это 0x40 в GPO и 0x80 в IT_STS_DYN; ниже путь события от записи по радио через импульс GPO и EXTI к чтению IT_STS_DYN


94а.9 Что увидит телефон

Телефон без особого приложения читает с метки не байты, а сообщение NDEF — стандартную обёртку для ссылки, текста или записи приложения. В начале памяти метки лежит служебный файл CC (Capability Container): по нему телефон понимает, что метка размечена под NDEF и сколько места у сообщения. Драйвер умеет запрещать запись блоков CC-файла — одного или двух (LOCKCCFILE, 0x000C), — чтобы случайная запись не сломала разметку.

Побайтный формат CC и NDEF задают спецификации NFC Forum; в этом разделе их нет. На практике прибор поступает так: первые блоки отдаёт под CC и сообщение NDEF (например, ссылку на страницу прибора), а свои данные держит в отдельной зоне, закрытой от записи по радио.


94а.10 Грабли

Метка не отвечает на I²C, пока рядом телефон. Комментарий драйвера к IsDeviceReady об этом и говорит: во время обмена по радио I²C получает NACK. Прошивка должна повторять запрос, а не падать с ошибкой шины.

Запись настроек «не работает». В системную область без пароля не пишется (94а.5). Сначала PresentI2CPassword, потом всё остальное.

Не тот адрес. В коде 0xA6/0xAE, на сканере — 0x53/0x57 (94а.2). Для системных регистров нужен второй адрес: одного «адреса метки» не бывает.

Комментарий врёт про GPO. Над ST25DV_GetGPOStatus и ST25DV_ConfigureGPO в драйвере перечислены биты RFBUSY = 0x02, FIELDFALLING = 0x08, FIELDRISING = 0x10, RFPUTMSG = 0x20, RFGETMSG = 0x40, RFWRITE = 0x80. Это раскладка IT_STS_DYN, а не GPO: в GPO запись телефоном — 0x40, а 0x80 — включение вывода (94а.8). Верить надо маскам ST25DV_GPO_*_MASK.

CMOS-метка не проходит Init (94а.4): драйвер знает её коды, но при опознании сравнивает только с 0x24 и 0x26.

Лишние циклы записи. EEPROM изнашивается, а в сообщениях ящика — ОЗУ. Частые короткие обмены — через ящик, в EEPROM — только то, что должно пережить отключение питания.


94а.11 Итоги

Дальше: раздел 94б «Flipper Zero изнутри: STM32WB55, FreeRTOS и радио 433 МГц, NFC, RFID и ИК в одной прошивке» — открытый прибор, где рядом с NFC работают ещё четыре радиотракта.

Открыть в интерактивном справочнике