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, и её память видна микроконтроллеру. Из этого получаются три типовых применения:
- Настройка без кнопок. Телефон пишет параметры в метку, прошивка читает их при старте или по прерыванию.
- Журнал из выключенного прибора. Прошивка складывает события в EEPROM метки; телефон читает их полем считывателя, даже если батарея прибора села.
- Обмен сообщениями на ходу. Почтовый ящик в ОЗУ метки — буфер на 256 байт, через который телефон и прошивка передают друг другу короткие сообщения, не изнашивая EEPROM.
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.
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, итого шесть
лишних чтений.
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.
После записи в динамические регистры и почтовый ящик драйвер готовности не ждёт: опрос стоит только за записью в 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 мкс.
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 Итоги
- ST25DV — EEPROM с двумя входами: телефон по радио, микроконтроллер по I²C.
- На шине два адреса:
0xA6(0x53) — данные, динамические регистры и ящик;0xAE(0x57) — системная область. Драйвер выбирает адрес по биту0x2000. - Системная область пишется только после пароля: 17 байт — пароль,
0x09, пароль; смена пароля — код0x07. - Запись EEPROM — кусками до 256 байт и с опросом готовности до 320 мс; ящик и динамические регистры — без ожидания.
- Зоны задаются границами в единицах по 32 байта; драйвер сначала раздвигает их в конец, потом ставит на место.
- GPO на вход EXTI плюс
IT_STS_DYN— и прошивка узнаёт о записи телефоном без опроса шины.
Дальше: раздел 94б «Flipper Zero изнутри: STM32WB55, FreeRTOS и радио 433 МГц, NFC, RFID и ИК в одной прошивке» — открытый прибор, где рядом с NFC работают ещё четыре радиотракта.