18д. NFC-модуль PN532: кадры по I2C/SPI/UART, ACK, чтение UID и NTAG2xx, эмуляция и отличие от RC522
Раздел 18г разобрал считыватель RC522: прошивка там сама пишет регистры MFRC522, сама ведёт антиколлизию, считает CRC_A и ждёт ответ карты по таймеру. Модуль PN532 работает на том же диапазоне 13,56 МГц и с теми же картами ISO 14443A, но устроен иначе: внутри него свой микроконтроллер со своей прошивкой, и разговор с ним — это команды и ответы в кадрах с контрольными суммами. Здесь — кадр, три шины на выбор, поиск метки и UID, страницы меток NTAG213 и NTAG215, запись и чтение ссылки в формате NDEF, сон, таймауты и что делать, когда PN532 молчит. Метка с двумя входами ST25DV была в разделе 94а, I²C — в разделе 17, SPI — в разделе 18.
Опоры: RM0008 (DocID13902 Rev 17): APB2 — до 72 МГц, APB1 — до 36 МГц,
с. 48; бит SPI1EN, с. 112; I2C1EN и USART2EN, с. 115; выводы USART,
SPI и I²C — таблицы 24, 25 и 27, с. 165–167; без ремапа SPI1 — PA4–PA7,
USART2 — TX/PA2, RX/PA3, I2C1 — SCL/PB6, SDA/PB7, с. 185; SPI_CR1 —
LSBFIRST (бит 7) и BR[2:0], с. 743; I2C_CR1.ACK, с. 774; FREQ,
с. 776; флаг AF, с. 779; SB и ADDR, с. 781; CCR и TRISE, с. 784;
флаги TXE и RXNE USART, с. 819. DocID13587 Rev 17 (STM32F103x8/xB):
PA2–PA7 — без допуска 5 В, с. 29; PA9 — с допуском, с. 31; PB6 и PB7 — с
допуском 5 В, с. 32. Документа на PN532 в проекте нет, драйвера PN532 нет
ни в дереве Zephyr, ни в других исходниках на диске: формат кадра, коды
команд и ответов, адрес, байты шины SPI, побудка по UART взяты по
документации производителя и не проверены. Команды меток NTAG, их объём и
разметка NDEF сверены с исходниками прошивки Flipper Zero (коммит 7f0b6e1).
Все числа ниже — прогон драйвера _verify/nfc18f/pn18f.c на компьютере
(gcc 12.2.0) со своей моделью PN532 и меток на Python 3.11; прошивка
собрана armclang 6.24 под Cortex-M3.
Как это называется в документации
Оригинал Перевод host controller ведущий: наш микроконтроллер normal / extended information frame обычный / расширенный кадр LEN, LCS — length checksum длина и её контрольная сумма TFI — frame identifier направление кадра: D4 или D5 DCS — data checksum контрольная сумма данных ACK / NACK frame кадр «принял» / «повтори» error frame кадр ошибки HSU — high speed UART UART модуля SAM — security access module внешний модуль безопасности SENS_RES / SEL_RES ATQA / SAK из раздела 18г target, initiator метка, считыватель CC — capability container служебная страница NDEF TLV — type, length, value тип, длина, значение
18д.1 Чем PN532 отличается от RC522
У MFRC522 из раздела 18г наружу смотрят регистры: FIFO, флаги, таймер, сопроцессор CRC. Всё остальное — REQA, антиколлизию, каскады UID, SELECT, проверку CRC_A — делает прошивка нашего микроконтроллера. В PN532 внутри своё ядро 80C51 с прошивкой в ПЗУ (по документации производителя, не проверено), и всю эту работу делает оно. Ведущий даёт команду InListPassiveTarget — и получает готовый UID, ATQA и SAK одним ответом.
Цена — другой протокол. Регистров нет, есть кадры: команда уходит в кадре с длиной и двумя контрольными суммами, PN532 сначала подтверждает приём кадром ACK, потом, когда закончит работу, отдаёт ответ. Пока ответа нет, ведущий ждёт: опрашивает байт готовности или смотрит на вывод IRQ.
| RC522 (раздел 18г) | PN532 | |
|---|---|---|
| с чем говорит прошивка | регистры MFRC522 | команды прошивки PN532 |
| антиколлизия, CRC_A | в нашей прошивке | внутри PN532 |
| шины | SPI на модулях | I²C, SPI или UART — перемычками |
| подтверждение приёма | нет | кадр ACK |
| эмуляция карты | нет | TgInitAsTarget |
Один и тот же кадр ходит по всем трём шинам. Меняется только обёртка:
по I²C перед ответом приходит байт готовности, по SPI каждую транзакцию
начинает служебный байт, по UART микросхему сначала будят. Драйвер этого
раздела pn18f.c прячет обёртку в трёх маленьких функциях, а команды над
ними общие.
18д.2 Кадр: LEN, LCS, TFI, DCS, ACK и NACK
Кадр — 00 (преамбула), 00 FF (начало), LEN, LCS, TFI, данные, DCS,
00 (конец). LEN считает TFI и данные. LCS подбирают так, чтобы
LEN + LCS = 0 по модулю 256; DCS — так, чтобы TFI + данные + DCS = 0. TFI
говорит, куда идёт кадр: D4 — от ведущего к PN532, D5 — обратно. Первый
байт данных — код команды, в ответе — код плюс один.
uint32_t pn_frame(uint8_t *out, uint8_t tfi, const uint8_t *d, uint32_t n)
{
uint32_t len = n + 1u, k = 0; /* LEN считает TFI и данные */
uint8_t sum = tfi;
out[k++] = 0x00; /* преамбула */
out[k++] = 0x00; /* код начала: 00 FF */
out[k++] = 0xFF;
if (len <= 254u) {
out[k++] = (uint8_t)len;
out[k++] = (uint8_t)(0x100u - len); /* LCS: LEN + LCS = 0 по модулю 256 */
} else { /* расширенный кадр: FF FF LENM LENL LCS */
out[k++] = 0xFF;
out[k++] = 0xFF;
out[k++] = (uint8_t)(len >> 8);
out[k++] = (uint8_t)len;
out[k++] = (uint8_t)(0x100u - ((len >> 8) + (len & 0xFFu)));
}
out[k++] = tfi;
for (uint32_t i = 0; i < n; i++) {
out[k++] = d[i];
sum = (uint8_t)(sum + d[i]);
}
out[k++] = (uint8_t)(0x100u - sum); /* DCS: TFI + данные + DCS = 0 по модулю 256 */
out[k++] = 0x00; /* постамбула */
return k;
}
Примеры из прогона — драйвер на Си и независимый расчёт на Python дали одно и то же:
| команда | кадр |
|---|---|
| GetFirmwareVersion | 00 00 FF 02 FE D4 02 2A 00 |
| SAMConfiguration | 00 00 FF 05 FB D4 14 01 14 01 02 00 |
| InListPassiveTarget | 00 00 FF 04 FC D4 4A 01 00 E1 00 |
| ответ на GetFirmwareVersion | 00 00 FF 06 FA D5 03 32 01 06 07 E8 00 |
У GetFirmwareVersion LEN = 2 (TFI и код 02), LCS = FE; D4 + 02 = D6,
и DCS = 2A. Ещё 200 случайных кадров с данными от 1 до 259 байт Си и
Python собрали одинаково, и драйвер разобрал их обратно — все 200.
ACK и NACK. ACK — 00 00 FF 00 FF 00: PN532 так говорит «кадр принят»,
а ведущий — «брось текущую команду». NACK — 00 00 FF FF 00 00: ведущий
просит повторить последний ответ. Это особые кадры: у обоих LEN + LCS = FF,
а не 0, и разборщик обычных кадров отвергает их как кадры с неверной
суммой. Поэтому драйвер узнаёт ACK по всем шести байтам сразу. Кадр
ошибки — 00 00 FF 01 FF 7F 81 00: один байт данных 7F без TFI; в модели
так PN532 отвечает на незнакомую команду (FE — кадр
00 00 FF 02 FE D4 FE 2E 00).
Расширенный кадр. LEN в обычном кадре — один байт. Когда данных
больше, после 00 FF идут FF FF, два байта длины (старший первым) и LCS
над ними. Драйвер переходит на расширенный кадр, когда LEN больше 254
(граница — по документации производителя, не проверено): кадр с 260 байтами
данных — LEN 261 = 01 05, начало 00 00 FF FF FF 01 05 FA D4, всего 271
байт.
Проверка ответа. Кадр ответа с одним испорченным битом драйвер ловит по DCS, с испорченной длиной — по LCS. Оба случая в прогоне дали «неверная сумма» — и драйвер просит повтор кадром NACK (18д.6).
18д.3 Три шины: I²C, SPI и HSU
Шину выбирают перемычки или переключатели на модуле; микросхема читает их один раз, при включении (по документации производителя, не проверено).
I²C. Адрес — 0x24, в линии 0x48 при записи и 0x49 при чтении
(о сдвинутом адресе — раздел 17.2). Команда — обычная запись кадра. Ответ
читают иначе, чем у датчиков: первый байт любого чтения — байт
готовности, 01 — кадр готов. Драйвер читает один байт, пока не увидит
01, потом читает байт готовности и кадр одним чтением. I2C1 F103 — на
PB6 и PB7 (с. 185), открытый сток (таблица 27, с. 167), 100 кГц: FREQ =
36, CCR = 180, TRISE = 37 при APB1 36 МГц.
SPI. Каждую транзакцию начинает служебный байт: 01 — запись кадра
(DW), 02 — чтение байта готовности (SR), 03 — чтение кадра (DR). Байты
идут младшим битом вперёд. В SPI F103 для этого есть бит LSBFIRST
(бит 7 SPI_CR1, с. 743), и порядок бит меняет сам блок — драйвер его не
видит. Режим 0, делитель 32 (BR = 100): 72 МГц / 32 = 2,25 МГц — с
запасом от 5 МГц, предела SPI PN532 по документации производителя (не
проверено).
SPI1->CR1 = SPI_CR1_MSTR | SPI_BR | SPI_CR1_LSBFIRST | SPI_CR1_SSM | SPI_CR1_SSI; /* режим 0 */
SPI1->CR1 |= SPI_CR1_SPE;
HSU — UART модуля, 115 200 бод по умолчанию. Байта готовности нет:
кадры просто приходят потоком, и драйвер читает пять байт заголовка, а
потом столько, сколько сказал LEN. Зато после включения и после сна
PN532 по UART надо разбудить: перед первой командой драйвер шлёт 55 55 и
14 нулей. USART2 F103 — PA2 и PA3 (с. 185), на APB1 36 МГц BRR = 313.
Готовность. Одна функция на две шины и на вывод IRQ:
static int ready(pn_t *p)
{
uint8_t tx[2] = {PN_SPI_SR, 0}, rx[2] = {0, 0}, st = 0;
if (p->use_irq)
return pn_irq() == 0; /* IRQ вниз — ответ готов */
if (p->bus == PN_BUS_I2C)
return pn_i2c_read(p->i2c_addr, &st, 1) == 0 && (st & 0x01u);
pn_spi(tx, rx, 2);
return rx[1] & 0x01u;
}
Одна команда GetFirmwareVersion на трёх шинах в модели (ACK готов через 0,5 мс после кадра, ответ — ещё через 1 мс; это допущения модели):
| шина | операций | байт на шине | время |
|---|---|---|---|
| I²C, 100 кГц, опрос | 6 | 39 | 4,63 мс |
| I²C, IRQ | 3 | 33 | 4,03 мс |
| SPI, 2,25 МГц, опрос | 7 | 39 | 2,139 мс |
| SPI, IRQ | 3 | 31 | 2,11 мс |
| HSU, 115 200 бод | 5 | 28 | 3,41 мс |
По I²C 39 байт — это запись кадра с адресом (10), три опроса по 2 байта, ACK с байтом готовности и адресом (8) и ответ (15). Вывод IRQ убирает опросы с шины: те же 3 проверки готовности идут по выводу PB1, и на шине остаются 33 байта.
18д.4 Поиск метки: SAMConfiguration, GetFirmwareVersion, InListPassiveTarget
pn_begin даёт две команды. SAMConfiguration 14 01 14 01 — обычный
режим без внешнего модуля безопасности и вывод IRQ включён (по
документации производителя, не проверено); ответ — код 15.
GetFirmwareVersion 02 — ответ 03 32 01 06 07: микросхема 32
(PN532), версия 1.6, 07 — какие типы меток поддержаны. Так модель
повторяет описание производителя (не проверено); настоящий чип может
отдать другую версию. Если ответа нет совсем — дальше идти бессмысленно:
шина, перемычки или питание (18д.7).
InListPassiveTarget 4A 01 00 — найти одну метку на 106 кбит/с, тип A.
PN532 сам шлёт REQA, ведёт антиколлизию по каскадам, проверяет CRC_A и
выбирает метку — всё то, что в разделе 18г было нашим кодом. Ответ на
метку NTAG213 в модели:
00 00 FF 0F F1 D5 4B 01 01 00 44 00 07 04 6F 1C 2A 5B 3D 80 C2 00
После D5 4B: одна метка (01), её номер для следующих команд (01),
ATQA 00 44, SAK 00, длина UID 07 и сам UID 04 6F 1C 2A 5B 3D 80.
ATQA PN532 отдаёт старшим байтом вперёд — в эфире, как в 18г, это 44 00
(порядок — по документации производителя, не проверено). Поиск занял
6,217 мс по SPI. Карта MIFARE Classic 1K из раздела 18г по I²C дала ATQA
00 04, SAK 08 и UID 3A 9F 12 C4 из 4 байт за 9,49 мс.
Сколько искать. По умолчанию PN532 ищет метку без конца: параметр
MaxRetries пассивной активации равен FF (по документации производителя,
не проверено; в модели так). Команда RFConfiguration 32 05 FF 01 nn
ограничивает число попыток: прошивка раздела ставит 10. Что бывает без
этого — в 18д.6.
18д.5 NTAG213 и NTAG215: страницы, READ и WRITE, ссылка в NDEF
Метки NTAG21x — потомки MIFARE Ultralight: память из страниц по 4 байта,
без ключей и шифра. У NTAG213 45 страниц, у NTAG215 — 135 (так в
исходниках Flipper Zero, mf_ultralight.c). Команды метки идут через
InDataExchange 40 01 …: PN532 передаёт их метке номер 1 и
возвращает ответ с байтом состояния, 00 — успех.
- READ
30 n— 16 байт: страницы n, n+1, n+2, n+3. - WRITE
A2 nи 4 байта — одна страница.
Страницы 0–2 — UID, две контрольные суммы BCC и байты блокировок:
04 6F 1C FF | 2A 5B 3D 80 | CC 48 00 00. BCC0 = 88 ⊕ 04 ⊕ 6F ⊕ 1C = FF,
BCC1 = 2A ⊕ 5B ⊕ 3D ⊕ 80 = CC — та же формула, что в Flipper Zero.
Страница 3 — CC: E1 10 12 00. E1 — метка размечена под NDEF, 10 —
версия 1.0, 12 — размер области данных в восьмёрках байт: 18 · 8 = 144
байта, страницы 4–39. У NTAG215 CC — E1 10 3E 00, 62 · 8 = 496 байт.
Один READ с адреса 3 приносит и CC, и начало данных — ntag_read за 4,21 мс:
E1 10 12 00 01 03 A0 0C 34 03 00 FE 00 00 00 00
Это заводская разметка NTAG213 (такая же — в генераторе меток Flipper
Zero): TLV 01 длиной 3 — где лежат биты блокировок, пустое сообщение NDEF
03 00 и конец FE. У NTAG215 — сразу 03 00 FE.
Ссылка в NDEF. Сообщение NDEF лежит в TLV 03. Запись ссылки —
заголовок D1 (первая и последняя запись, короткая, тип NFC Forum), длина
типа 1, длина данных, тип U, код приставки и остаток адреса. Приставки
кодируются числом: 01 — http://www., 02 — https://www., 03 —
http://, 04 — https://. Для адреса https://example.com/pn532
ndef_uri дала 25 байт:
03 16 D1 01 12 55 04 65 78 61 6D 70 6C 65 2E 63 6F 6D 2F 70 6E 35 33 32 FE
Длина записи 16 = 22: 4 байта заголовка и 18 байт данных — код 04 и 17
букв example.com/pn532.
while (i < have && m[i] != 0x03u && m[i] != 0xFEu) { /* пропустить TLV блокировок и памяти */
if (m[i] == 0x00u) {
i++;
continue;
}
i += 2u + m[i + 1u];
}
at = i; /* сюда ляжет новое сообщение; TLV до него не трогаем */
ntag_write_url читает CC, пропускает TLV 01 и кладёт сообщение со
смещения 5 — со второго байта страницы 5. Первый байт этой страницы,
34 от TLV блокировок, драйвер переписывает тем же. Записано 7
страниц (5–11) за 8 команд — один READ и семь WRITE, — 54,529 мс; в модели
запись страницы занимает 6 мс, это допущение. Обратно ntag_read_url
прочитала ссылку тремя READ за 12,629 мс, и независимый разбор памяти
метки на Python нашёл тот же адрес.
Длинная ссылка. Адрес из 300 знаков не влезает в короткую запись:
данных 293 байта, больше 255. Тогда заголовок — C1 (без признака
короткой записи), а длина данных — 4 байта; запись — 300 байт, и длина TLV
тоже не влезает в байт: 03 FF 01 2C. На NTAG215 вышло 305 байт, 77
страниц, 557,72 мс; чтение назад — 20 команд, 84,196 мс. На NTAG213 та же
ссылка не влезает в 144 байта, и драйвер отказывается писать, не тронув
метку.
18д.6 MIFARE Classic, эмуляция карты, таймауты и сон
MIFARE Classic через PN532. Те же команды карты, что в разделе 18г,
идут через InDataExchange: аутентификация 60 с номером блока, 6 байтами
ключа и UID, потом READ 30 — 16 байт. Шифр Crypto1 PN532 ведёт сам, как и
MFRC522. Всё сказанное в 18г.6 остаётся в силе: MIFARE Classic и UID не
годятся для защиты, какой бы считыватель ни стоял. Модель стенда Classic
только находит, но не читает.
Эмуляция карты. Командой TgInitAsTarget (8C) PN532 сам
притворяется меткой: отвечает считывателю, например телефону, и
обменивается с ним данными командами TgGetData и TgSetData. Так
делают обмен «телефон — прибор» без отдельной метки. По документации
производителя часть UID при этом задаёт сама микросхема, и копию чужой
карты так не сделать (не проверено). В драйвере раздела эмуляции нет, и
она не моделировалась.
Таймауты. Каждая команда ждёт ACK 10 мс, ответ — сколько ей отведено. Если ответа нет, драйвер шлёт ACK: PN532 бросает команду и готов к следующей. В модели поиск без метки с заводским числом попыток не кончился никогда: драйвер ждал 100 мс, отправил ACK — 124,09 мс и 103 опроса, — и следующая команда GetFirmwareVersion прошла, как обычно. С RFConfiguration на 5 повторов тот же поиск вернул «меток нет» за 16,69 мс.
Неверная сумма. В модели один бит ответа на GetFirmwareVersion
перевернулся на линии: D5 13 вместо D5 03. DCS не сошлась, драйвер
отправил NACK, PN532 повторил ответ — и команда прошла с одним повтором за
3,228 мс вместо 2,139 мс.
Сон. PowerDown 16 B0 усыпляет PN532 с побудкой по I²C, SPI и UART
(биты побудки — по документации производителя, не проверено). Как он
просыпается в модели:
| шина | как будит драйвер | GetFirmwareVersion после сна | без побудки |
|---|---|---|---|
| I²C | первый адрес не подтверждён — повтор через 1 мс | 6,55 мс | ошибка шины: адрес без ACK |
| SPI | пустая транзакция и пауза 1 мс | 3,146 мс | нет ACK, 10,114 мс |
| HSU | 55 55 и нули перед кадром |
4,799 мс | нет ACK, 10,781 мс |
Поведение при побудке по I²C и SPI — допущение модели: документа нет. Вывод из таблицы не зависит от деталей: после сна первый обмен теряется, и драйвер должен это ждать.
18д.7 Грабли
PN532 молчит: не та шина. Перемычки стоят на HSU, а прошивка говорит
по I²C — адрес не подтверждается, pn_begin возвращает ошибку шины.
Переставить перемычки на работающем модуле мало: в модели после
перестановки ответ тот же, и только после выключения и включения
pn_begin — SAMConfiguration и GetFirmwareVersion — проходит за 9,17 мс.
PN532 молчит: не тот порядок бит SPI. Без LSBFIRST блок SPI шлёт
байты старшим битом вперёд, и PN532 видит вместо DW 01 байт 80, вместо
SR 02 — 40. Так и вышло в модели: ни одной понятной транзакции, нет ACK
через 10,124 мс. Если блок SPI не умеет младшим битом вперёд, байты
переворачивают программно — на F103 бит есть.
Неверный адрес I²C. 0x48 — это уже сдвинутый адрес. С ним драйвер
трижды получил NACK на адрес — 5,76 мс и ошибка шины; с 0x24 —
ответ за 4,63 мс.
Модуль от 5 В. PN532 — микросхема на 3,3 В. Многие модули ставят свой стабилизатор и подтягивают I²C к своему питанию (по описаниям продавцов, не проверено). PB6 и PB7 F103 выдержат 5 В (с. 32), а PA2–PA7 — нет (с. 29): по SPI и UART модуль — только от 3,3 В.
Длинные провода. Сбой на линии ловят LCS и DCS; на ответ с неверной суммой драйвер отвечает NACK, а не принимает его (18д.6). Но не принятая команда выглядит как «нет ACK», и после десятков сантиметров проводов на I²C и SPI лучше снизить частоту.
Пропущенный ACK. Если не прочитать ACK, а сразу ждать ответ, по I²C и SPI ведущий прочитает ACK вместо ответа. Зависшую команду — например, поиск без метки — снимают кадром ACK от ведущего; без него PN532 занят ею (по документации производителя, не проверено).
Поиск без конца. Без RFConfiguration InListPassiveTarget без метки не отвечает никогда: прошивка ждёт до своего таймаута, а без него зависает.
18д.8 Итоги
- PN532 — 80C51 со своей прошивкой: команды и ответы вместо регистров; антиколлизию и CRC_A делает сам.
- Кадр:
00 00 FF LEN LCS D4 … DCS 00; GetFirmwareVersion —00 00 FF 02 FE D4 02 2A 00, ответ —03 32 01 06 07. - ACK
00 00 FF 00 FF 00, NACK00 00 FF FF 00 00; расширенный кадр — с LEN больше 254. - I²C: адрес
0x24, байт готовности01; SPI: DW01, SR02, DR03, младшим битом вперёд, 2,25 МГц; HSU: 115 200 бод, побудка55 55. - IRQ убирает опросы: по I²C 39 байт → 33.
- InListPassiveTarget: NTAG213 — ATQA
00 44, SAK00, UID из 7 байт, 6,217 мс по SPI. - NTAG213: 45 страниц, CC
E1 10 12 00— 144 байта; NTAG215: 135 страниц, 496 байт. - Ссылка
https://example.com/pn532— TLV 25 байт, 7 страниц, 54,529 мс. - Без RFConfiguration поиск без метки не кончается; таймаут — и ACK.
Полные исходники
pn18f.h — команды и ответы PN532, кадр, интерфейс драйвера и платформы.
/* pn18f.h — раздел 18д: драйвер NFC-модуля PN532 без регистров STM32.
* Драйвер говорит с микросхемой кадрами через функции платформы: I2C, SPI или UART (HSU) — что выбрано перемычками.
* В прошивке fw18f.c их даёт STM32F103, в стенде host18f.c — модель model18f.py.
* Формат кадра, коды команд и ответов PN532 — по документации производителя (в проекте её нет, не проверено);
* команды NTAG2xx и разметка NDEF сверены с исходниками прошивки Flipper Zero (выдержки — flipper_ntag.txt). */
#ifndef PN18F_H
#define PN18F_H
#include <stdint.h>
/* ── кадр ─────────────────────────────────────────────────────────────── */
#define PN_TFI_HOST 0xD4u /* кадр от ведущего к PN532 */
#define PN_TFI_CHIP 0xD5u /* кадр от PN532 к ведущему */
#define PN_I2C_ADDR 0x24u /* семибитный адрес: в линии 0x48 — запись, 0x49 — чтение */
#define PN_SPI_DW 0x01u /* первый байт SPI: запись данных */
#define PN_SPI_SR 0x02u /* чтение байта готовности */
#define PN_SPI_DR 0x03u /* чтение данных */
#define PN_MAXDATA 265u /* TFI и данные расширенного кадра — с запасом */
#define PN_FRAME_MAX (PN_MAXDATA + 10u)
/* ── команды PN532 ────────────────────────────────────────────────────── */
#define PN_GETFW 0x02u /* GetFirmwareVersion */
#define PN_SAMCONFIG 0x14u /* SAMConfiguration */
#define PN_POWERDOWN 0x16u
#define PN_RFCONFIG 0x32u /* RFConfiguration */
#define PN_INEXCHANGE 0x40u /* InDataExchange */
#define PN_INLIST 0x4Au /* InListPassiveTarget */
#define PN_INRELEASE 0x52u
#define PN_TGINIT 0x8Cu /* TgInitAsTarget — эмуляция карты, в драйвере не используется */
/* ── команды метки NTAG2xx ────────────────────────────────────────────── */
#define NTAG_READ 0x30u /* 16 байт: четыре страницы по 4 байта */
#define NTAG_WRITE 0xA2u /* одна страница: 4 байта */
/* ── ответ драйвера ───────────────────────────────────────────────────── */
enum { PN_OK, PN_TIMEOUT, PN_NOACK, PN_FRAME, PN_CHECKSUM, PN_ERRFRAME, PN_BUS, PN_STATUS, PN_NOTAG, PN_NOSPACE, PN_NDEF };
enum { PN_BUS_I2C = 1, PN_BUS_SPI, PN_BUS_HSU };
extern const uint8_t PN_ACK[6], PN_NACK[6];
typedef struct {
uint8_t sens_res[2]; /* ATQA — так, как его отдаёт PN532 */
uint8_t sel_res; /* SAK */
uint8_t uid[10];
uint8_t uid_len;
} pn_tag_t;
typedef struct {
uint8_t bus; /* PN_BUS_I2C, PN_BUS_SPI или PN_BUS_HSU */
uint8_t i2c_addr; /* 0x24; другое — опыт с неверным адресом */
uint8_t use_irq; /* 1 — ждать готовность по выводу IRQ, а не опросом шины */
uint8_t no_wake; /* 1 — не будить после сна (опыт «PN532 молчит») */
uint8_t asleep; /* драйвер считает, что микросхема спит */
uint8_t fw[4]; /* IC, Ver, Rev, Support из GetFirmwareVersion */
uint8_t status; /* байт состояния последнего InDataExchange */
uint32_t polls, nacks, retries, aborts, wakes, reads, writes;
} pn_t;
/* ── платформа: даёт прошивка или стенд ───────────────────────────────── */
int pn_i2c_write(uint8_t addr7, const uint8_t *d, uint32_t n); /* 0 — адрес и все байты подтверждены */
int pn_i2c_read(uint8_t addr7, uint8_t *d, uint32_t n); /* 0 — адрес подтверждён */
void pn_spi(const uint8_t *tx, uint8_t *rx, uint32_t n); /* NSS вниз, n байт младшим битом вперёд, NSS вверх */
void pn_uart_write(const uint8_t *d, uint32_t n);
uint32_t pn_uart_read(uint8_t *d, uint32_t n, uint32_t timeout_ms); /* сколько байт пришло */
int pn_irq(void); /* уровень IRQ: 0 — есть что читать */
void pn_delay_ms(uint32_t ms);
/* ── кадры ────────────────────────────────────────────────────────────── */
uint32_t pn_frame(uint8_t *out, uint8_t tfi, const uint8_t *d, uint32_t n); /* -> длина кадра */
int pn_unframe(const uint8_t *f, uint32_t n, uint8_t *d, uint32_t *dn); /* d — TFI и данные */
/* ── команды ──────────────────────────────────────────────────────────── */
int pn_cmd(pn_t *p, const uint8_t *cmd, uint32_t n, uint8_t *resp, uint32_t *rn, uint32_t rmax, uint32_t timeout_ms);
void pn_abort(pn_t *p);
int pn_begin(pn_t *p, uint8_t bus);
int pn_getfw(pn_t *p);
int pn_sam(pn_t *p);
int pn_retries(pn_t *p, uint8_t passive);
int pn_list(pn_t *p, pn_tag_t *t, uint32_t timeout_ms);
int pn_exchange(pn_t *p, const uint8_t *tx, uint32_t n, uint8_t *rx, uint32_t *rn, uint32_t rmax);
int pn_release(pn_t *p);
int pn_sleep(pn_t *p);
/* ── NTAG2xx и NDEF ───────────────────────────────────────────────────── */
int ntag_read(pn_t *p, uint8_t page, uint8_t out[16]);
int ntag_write(pn_t *p, uint8_t page, const uint8_t d[4]);
uint32_t ndef_uri(uint8_t *out, uint32_t cap, const char *url); /* TLV 03 … FE -> длина или 0 */
int ndef_parse(const uint8_t *m, uint32_t n, char *url, uint32_t cap, uint32_t *tlv_at);
int ntag_write_url(pn_t *p, const char *url, uint32_t *pages);
int ntag_read_url(pn_t *p, char *url, uint32_t cap);
#endif
pn18f.c — драйвер без регистров STM32: общая часть прошивки и стенда.
/* pn18f.c — раздел 18д: драйвер PN532 — кадры с LCS и DCS, ACK и NACK, обёртки I2C, SPI и HSU, ожидание
* готовности опросом или по IRQ, команды SAMConfiguration, GetFirmwareVersion, RFConfiguration,
* InListPassiveTarget, InDataExchange, InRelease, PowerDown; страницы NTAG2xx и запись URL в NDEF. Без регистров STM32. */
#include <string.h>
#include "pn18f.h"
#define ACK_MS 10u /* ждать ACK, мс */
#define RESEND 2u /* сколько раз просить повтор (NACK) при неверной сумме */
const uint8_t PN_ACK[6] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
const uint8_t PN_NACK[6] = {0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00};
static const uint8_t WAKE[16] = {0x55, 0x55}; /* HSU: 55 55 и нули — разбудить перед первой командой */
/* ── кадр: 00 00 FF LEN LCS TFI данные DCS 00 ─────────────────────────── */
uint32_t pn_frame(uint8_t *out, uint8_t tfi, const uint8_t *d, uint32_t n)
{
uint32_t len = n + 1u, k = 0; /* LEN считает TFI и данные */
uint8_t sum = tfi;
out[k++] = 0x00; /* преамбула */
out[k++] = 0x00; /* код начала: 00 FF */
out[k++] = 0xFF;
if (len <= 254u) {
out[k++] = (uint8_t)len;
out[k++] = (uint8_t)(0x100u - len); /* LCS: LEN + LCS = 0 по модулю 256 */
} else { /* расширенный кадр: FF FF LENM LENL LCS */
out[k++] = 0xFF;
out[k++] = 0xFF;
out[k++] = (uint8_t)(len >> 8);
out[k++] = (uint8_t)len;
out[k++] = (uint8_t)(0x100u - ((len >> 8) + (len & 0xFFu)));
}
out[k++] = tfi;
for (uint32_t i = 0; i < n; i++) {
out[k++] = d[i];
sum = (uint8_t)(sum + d[i]);
}
out[k++] = (uint8_t)(0x100u - sum); /* DCS: TFI + данные + DCS = 0 по модулю 256 */
out[k++] = 0x00; /* постамбула */
return k;
}
int pn_unframe(const uint8_t *f, uint32_t n, uint8_t *d, uint32_t *dn)
{
uint32_t i = 0, len;
while (i + 1u < n && !(f[i] == 0x00 && f[i + 1u] == 0xFF))
i++; /* преамбул бывает сколько угодно: ищем 00 FF */
i += 2u;
if (i + 2u > n)
return PN_FRAME;
if (f[i] == 0xFF && f[i + 1u] == 0xFF) { /* расширенный */
if (i + 5u > n || (uint8_t)(f[i + 2u] + f[i + 3u] + f[i + 4u]) != 0)
return PN_CHECKSUM;
len = (uint32_t)f[i + 2u] << 8 | f[i + 3u];
i += 5u;
} else {
if ((uint8_t)(f[i] + f[i + 1u]) != 0)
return PN_CHECKSUM; /* LCS не сошёлся (у NACK тоже: FF + 00) */
len = f[i];
i += 2u;
}
if (len == 0u || len > PN_MAXDATA || i + len + 1u > n)
return PN_FRAME;
uint8_t sum = 0;
for (uint32_t k = 0; k <= len; k++)
sum = (uint8_t)(sum + f[i + k]);
if (sum != 0)
return PN_CHECKSUM; /* DCS не сошёлся */
memcpy(d, f + i, len);
*dn = len;
if (len == 1u && d[0] == 0x7Fu)
return PN_ERRFRAME; /* кадр ошибки 00 00 FF 01 FF 7F 81 00 */
return PN_OK;
}
/* ── шина ─────────────────────────────────────────────────────────────── */
static int bus_write(pn_t *p, const uint8_t *f, uint32_t n)
{
static uint8_t tx[PN_FRAME_MAX + 1u], rx[PN_FRAME_MAX + 1u];
p->writes++;
if (p->bus == PN_BUS_I2C) {
for (uint32_t t = 0; t < 3u; t++) { /* после сна первый адрес будит и теряется */
if (pn_i2c_write(p->i2c_addr, f, n) == 0)
return PN_OK;
if (p->no_wake)
break;
p->retries++;
pn_delay_ms(1);
}
return PN_BUS;
}
if (p->bus == PN_BUS_SPI) {
tx[0] = PN_SPI_DW;
memcpy(tx + 1, f, n);
pn_spi(tx, rx, n + 1u);
return PN_OK;
}
pn_uart_write(f, n);
return PN_OK;
}
static void wake(pn_t *p)
{
uint8_t tx[2] = {PN_SPI_SR, 0}, rx[2];
if (!p->asleep)
return;
p->asleep = 0;
if (p->no_wake)
return;
p->wakes++;
if (p->bus == PN_BUS_HSU) {
pn_uart_write(WAKE, sizeof WAKE);
} else if (p->bus == PN_BUS_SPI) {
pn_spi(tx, rx, 2); /* NSS вниз будит; эта транзакция теряется */
pn_delay_ms(1);
} /* I2C: будит первый адрес, его повторяет bus_write */
}
static int ready(pn_t *p)
{
uint8_t tx[2] = {PN_SPI_SR, 0}, rx[2] = {0, 0}, st = 0;
if (p->use_irq)
return pn_irq() == 0; /* IRQ вниз — ответ готов */
if (p->bus == PN_BUS_I2C)
return pn_i2c_read(p->i2c_addr, &st, 1) == 0 && (st & 0x01u);
pn_spi(tx, rx, 2);
return rx[1] & 0x01u;
}
static int wait_ready(pn_t *p, uint32_t timeout_ms)
{
for (uint32_t ms = 0;; ms++) {
p->polls++;
if (ready(p))
return PN_OK;
if (ms >= timeout_ms)
return PN_TIMEOUT;
pn_delay_ms(1);
}
}
/* Прочитать кадр: I2C и SPI — want байт после готовности, HSU — по потоку, сколько скажет LEN. */
static int read_frame(pn_t *p, uint8_t *f, uint32_t want, uint32_t timeout_ms, uint32_t *got)
{
static uint8_t tx[PN_FRAME_MAX + 1u], rx[PN_FRAME_MAX + 1u];
p->reads++;
*got = 0;
if (p->bus == PN_BUS_HSU) {
uint32_t k = pn_uart_read(f, 5, timeout_ms), more;
if (k < 5u)
return PN_TIMEOUT;
if (f[3] == 0x00 && f[4] == 0xFF)
more = 1; /* ACK: остался байт 00 */
else if (f[3] == 0xFF && f[4] == 0xFF) {
if (pn_uart_read(f + 5, 3, ACK_MS) < 3u)
return PN_TIMEOUT;
k = 8;
more = ((uint32_t)f[5] << 8 | f[6]) + 2u;
} else
more = (uint32_t)f[3] + 2u;
if (k + more > PN_FRAME_MAX || pn_uart_read(f + k, more, ACK_MS) < more)
return PN_TIMEOUT;
*got = k + more;
return PN_OK;
}
if (wait_ready(p, timeout_ms) != PN_OK)
return PN_TIMEOUT;
if (p->bus == PN_BUS_I2C) {
if (pn_i2c_read(p->i2c_addr, rx, want + 1u) != 0 || !(rx[0] & 0x01u))
return PN_BUS;
memcpy(f, rx + 1, want); /* первый байт — снова байт готовности */
} else {
memset(tx, 0, want + 1u);
tx[0] = PN_SPI_DR;
pn_spi(tx, rx, want + 1u);
memcpy(f, rx + 1, want);
}
*got = want;
return PN_OK;
}
void pn_abort(pn_t *p)
{
p->aborts++;
bus_write(p, PN_ACK, 6); /* ACK от ведущего — «бросить текущую команду» */
}
int pn_cmd(pn_t *p, const uint8_t *cmd, uint32_t n, uint8_t *resp, uint32_t *rn, uint32_t rmax, uint32_t timeout_ms)
{
static uint8_t f[PN_FRAME_MAX], d[PN_MAXDATA];
uint32_t got = 0, dn = 0, len = pn_frame(f, PN_TFI_HOST, cmd, n);
*rn = 0;
wake(p);
if (bus_write(p, f, len) != PN_OK)
return PN_BUS;
if (read_frame(p, f, 6, ACK_MS, &got) != PN_OK || memcmp(f, PN_ACK, 6) != 0)
return PN_NOACK; /* кадр не принят: нет связи, не тот режим, неверная сумма */
for (uint32_t t = 0; t <= RESEND; t++) {
int st = read_frame(p, f, rmax + 8u, timeout_ms, &got);
if (st == PN_TIMEOUT) {
pn_abort(p);
return PN_TIMEOUT;
}
st = st == PN_OK ? pn_unframe(f, got, d, &dn) : st;
if (st == PN_ERRFRAME)
return PN_ERRFRAME;
if (st != PN_OK) {
p->nacks++;
bus_write(p, PN_NACK, 6); /* NACK — «пришли ответ ещё раз» */
continue;
}
if (dn < 2u || d[0] != PN_TFI_CHIP || d[1] != (uint8_t)(cmd[0] + 1u))
return PN_FRAME;
memcpy(resp, d + 1, dn - 1u); /* код ответа (команда + 1) и данные */
*rn = dn - 1u;
return PN_OK;
}
return PN_CHECKSUM;
}
/* ── команды ──────────────────────────────────────────────────────────── */
int pn_begin(pn_t *p, uint8_t bus)
{
memset(p, 0, sizeof *p);
p->bus = bus;
p->i2c_addr = PN_I2C_ADDR;
p->asleep = bus == PN_BUS_HSU; /* по UART микросхему будят и после включения */
int st = pn_sam(p);
return st != PN_OK ? st : pn_getfw(p);
}
int pn_getfw(pn_t *p)
{
uint8_t c = PN_GETFW, r[8];
uint32_t n = 0;
int st = pn_cmd(p, &c, 1, r, &n, 5, 20);
if (st != PN_OK)
return st;
if (n != 5u)
return PN_FRAME;
memcpy(p->fw, r + 1, 4); /* IC, Ver, Rev, Support */
return PN_OK;
}
int pn_sam(pn_t *p)
{
uint8_t c[4] = {PN_SAMCONFIG, 0x01, 0x14, 0x01}, r[4]; /* обычный режим, 0x14 × 50 мс, IRQ включён */
uint32_t n = 0;
return pn_cmd(p, c, 4, r, &n, 1, 20);
}
int pn_retries(pn_t *p, uint8_t passive)
{
uint8_t c[5] = {PN_RFCONFIG, 0x05, 0xFF, 0x01, passive}, r[4]; /* MaxRetries: ATR, PSL, пассивная активация */
uint32_t n = 0;
return pn_cmd(p, c, 5, r, &n, 1, 20);
}
int pn_list(pn_t *p, pn_tag_t *t, uint32_t timeout_ms)
{
uint8_t c[3] = {PN_INLIST, 0x01, 0x00}, r[24]; /* одна метка, 106 кбит/с, тип A */
uint32_t n = 0;
int st = pn_cmd(p, c, 3, r, &n, 17, timeout_ms);
if (st != PN_OK)
return st;
if (n < 2u || r[1] == 0u)
return PN_NOTAG;
if (n < 7u || r[6] > 10u || n < 7u + r[6])
return PN_FRAME;
t->sens_res[0] = r[3];
t->sens_res[1] = r[4];
t->sel_res = r[5];
t->uid_len = r[6];
memcpy(t->uid, r + 7, r[6]);
return PN_OK;
}
int pn_exchange(pn_t *p, const uint8_t *tx, uint32_t n, uint8_t *rx, uint32_t *rn, uint32_t rmax)
{
uint8_t c[64], r[64];
uint32_t k = 0;
if (n + 2u > sizeof c || rmax + 2u > sizeof r)
return PN_FRAME;
c[0] = PN_INEXCHANGE;
c[1] = 0x01; /* номер метки из InListPassiveTarget */
memcpy(c + 2, tx, n);
int st = pn_cmd(p, c, n + 2u, r, &k, rmax + 2u, 100);
*rn = 0;
if (st != PN_OK)
return st;
if (k < 2u)
return PN_FRAME;
p->status = r[1];
if (r[1] & 0x3Fu)
return PN_STATUS; /* метка не ответила или отказала */
memcpy(rx, r + 2, k - 2u);
*rn = k - 2u;
return PN_OK;
}
int pn_release(pn_t *p)
{
uint8_t c[2] = {PN_INRELEASE, 0x00}, r[4];
uint32_t n = 0;
return pn_cmd(p, c, 2, r, &n, 2, 20);
}
int pn_sleep(pn_t *p)
{
uint8_t c[2] = {PN_POWERDOWN, 0xB0}, r[4]; /* будить по I2C, SPI и HSU */
uint32_t n = 0;
int st = pn_cmd(p, c, 2, r, &n, 2, 20);
if (st == PN_OK)
p->asleep = 1;
return st;
}
/* ── NTAG2xx ──────────────────────────────────────────────────────────── */
int ntag_read(pn_t *p, uint8_t page, uint8_t out[16])
{
uint8_t c[2] = {NTAG_READ, page}, r[20];
uint32_t n = 0;
int st = pn_exchange(p, c, 2, r, &n, 16);
if (st != PN_OK)
return st;
if (n != 16u)
return PN_FRAME;
memcpy(out, r, 16);
return PN_OK;
}
int ntag_write(pn_t *p, uint8_t page, const uint8_t d[4])
{
uint8_t c[6] = {NTAG_WRITE, page, d[0], d[1], d[2], d[3]}, r[4];
uint32_t n = 0;
return pn_exchange(p, c, 6, r, &n, 0);
}
/* ── NDEF: TLV 03, запись URI (тип «U»), TLV FE ───────────────────────── */
static const char *const PREFIX[5] = {"", "http://www.", "https://www.", "http://", "https://"};
uint32_t ndef_uri(uint8_t *out, uint32_t cap, const char *url)
{
uint32_t code = 0, best = 0, k = 0;
for (uint32_t i = 1; i < 5u; i++) {
uint32_t l = (uint32_t)strlen(PREFIX[i]);
if (l > best && strncmp(url, PREFIX[i], l) == 0) {
best = l;
code = i;
}
}
const char *rest = url + best;
uint32_t pl = 1u + (uint32_t)strlen(rest); /* поле полезной нагрузки: код приставки и остаток */
uint32_t rec = (pl <= 255u ? 4u : 7u) + pl; /* заголовок, длина типа, длина нагрузки, тип «U» */
uint32_t tlv = (rec <= 254u ? 2u : 4u) + rec + 1u;
if (tlv > cap)
return 0;
out[k++] = 0x03; /* TLV: сообщение NDEF */
if (rec <= 254u) {
out[k++] = (uint8_t)rec;
} else {
out[k++] = 0xFF; /* длинная длина: FF и два байта */
out[k++] = (uint8_t)(rec >> 8);
out[k++] = (uint8_t)rec;
}
out[k++] = pl <= 255u ? 0xD1u : 0xC1u; /* MB, ME, (SR), TNF = 1 — тип NFC Forum */
out[k++] = 0x01; /* длина типа */
if (pl <= 255u) {
out[k++] = (uint8_t)pl;
} else {
out[k++] = 0;
out[k++] = 0;
out[k++] = (uint8_t)(pl >> 8);
out[k++] = (uint8_t)pl;
}
out[k++] = 'U';
out[k++] = (uint8_t)code;
memcpy(out + k, rest, pl - 1u);
k += pl - 1u;
out[k++] = 0xFE; /* TLV: конец */
return k;
}
int ndef_parse(const uint8_t *m, uint32_t n, char *url, uint32_t cap, uint32_t *tlv_at)
{
uint32_t i = 0;
while (i < n) {
uint8_t t = m[i];
if (t == 0x00) { /* заполнитель: без длины */
i++;
continue;
}
if (t == 0xFE || i + 2u > n)
return PN_NDEF; /* конец, а сообщения NDEF не было */
uint32_t len = m[i + 1u], h = 2;
if (len == 0xFFu) {
if (i + 4u > n)
return PN_NDEF;
len = (uint32_t)m[i + 2u] << 8 | m[i + 3u];
h = 4;
}
if (t != 0x03) { /* 01 — блокировки, 02 — память, FD — своё: пропустить */
i += h + len;
continue;
}
*tlv_at = i;
const uint8_t *r = m + i + h;
if (i + h + len > n || len < 4u || (r[0] & 0x07u) != 0x01u || r[1] != 1u)
return PN_NDEF; /* нужен тип NFC Forum с однобуквенным именем */
uint32_t pl, j;
if (r[0] & 0x10u) { /* SR: длина нагрузки — один байт */
pl = r[2];
j = 3;
} else {
pl = (uint32_t)r[2] << 24 | (uint32_t)r[3] << 16 | (uint32_t)r[4] << 8 | r[5];
j = 6;
}
if (r[0] & 0x08u)
j++; /* IL: есть длина идентификатора (пропускаем; у нас 0) */
if (j + 1u + pl > len || r[j] != 'U' || pl < 1u || r[j + 1u] > 4u)
return PN_NDEF;
const char *pre = PREFIX[r[j + 1u]];
uint32_t lp = (uint32_t)strlen(pre);
if (lp + pl > cap)
return PN_NOSPACE;
memcpy(url, pre, lp);
memcpy(url + lp, r + j + 2u, pl - 1u);
url[lp + pl - 1u] = 0;
return PN_OK;
}
return PN_NDEF;
}
/* Память метки с 4-й страницы: читает по 16 байт, пока не найдёт TLV FE или не дойдёт до конца области. */
static int user_area(pn_t *p, uint8_t *m, uint32_t *size, uint32_t *have)
{
uint8_t b[16];
int st = ntag_read(p, 3, b); /* страница 3 — CC, за ней сразу страницы 4–6 */
if (st != PN_OK)
return st;
if (b[0] != 0xE1u || b[1] != 0x10u)
return PN_NDEF; /* метка не размечена под NDEF */
*size = b[2] * 8u; /* NTAG213: 0x12 × 8 = 144 байта */
memcpy(m, b + 4, 12);
*have = 12;
while (*have < *size && !memchr(m, 0xFE, *have)) {
if ((st = ntag_read(p, (uint8_t)(4u + *have / 4u), b)) != PN_OK)
return st;
memcpy(m + *have, b, 16);
*have += 16u;
}
if (*have > *size)
*have = *size;
return PN_OK;
}
int ntag_read_url(pn_t *p, char *url, uint32_t cap)
{
static uint8_t m[1024];
uint32_t size = 0, have = 0, at = 0;
int st = user_area(p, m, &size, &have);
return st != PN_OK ? st : ndef_parse(m, have, url, cap, &at);
}
int ntag_write_url(pn_t *p, const char *url, uint32_t *pages)
{
static uint8_t m[1024], t[1024];
uint32_t size = 0, have = 0, at = 0, i = 0;
*pages = 0;
int st = user_area(p, m, &size, &have);
if (st != PN_OK)
return st;
while (i < have && m[i] != 0x03u && m[i] != 0xFEu) { /* пропустить TLV блокировок и памяти */
if (m[i] == 0x00u) {
i++;
continue;
}
i += 2u + m[i + 1u];
}
at = i; /* сюда ляжет новое сообщение; TLV до него не трогаем */
uint32_t n = ndef_uri(t, sizeof t, url);
if (n == 0u || at + n > size)
return PN_NOSPACE;
uint32_t first = at / 4u, last = (at + n - 1u) / 4u;
memcpy(m + at, t, n); /* байты страницы до at остаются прежними */
memset(m + at + n, 0, (last + 1u) * 4u - (at + n));
for (uint32_t pg = first; pg <= last; pg++) {
if ((st = ntag_write(p, (uint8_t)(4u + pg), m + pg * 4u)) != PN_OK)
return st;
(*pages)++;
}
return PN_OK;
}
fw18f.c — прошивка STM32F103: I2C1, SPI1 или USART2, вывод в USART1.
/* fw18f.c — раздел 18д: NFC-модуль PN532 на STM32F103 (BluePill), 72 МГц. Шина — макрос FW_BUS:
* 1 — I2C1 без ремапа: PB6 — SCL, PB7 — SDA, 100 кГц, открытый сток (подтяжки — на модуле);
* 2 — SPI1 без ремапа: PA4 — NSS (ведём сами), PA5 — SCK, PA6 — MISO, PA7 — MOSI;
* режим 0, МЛАДШИМ битом вперёд (LSBFIRST), 72 МГц / 32 = 2,25 МГц;
* 3 — USART2 (HSU): PA2 — TX, PA3 — RX, 115 200 бод.
* PB1 — IRQ модуля (вход с подтяжкой; драйвер по умолчанию опрашивает шину, use_irq = 1 — ждёт IRQ).
* Модуль питается от 3,3 В. UID метки и адрес из NDEF — в USART1 (PA9, 115 200).
* Тактирование до 72 МГц — как в разделе 6 (здесь не повторяется); APB2 = 72 МГц, APB1 = 36 МГц. */
#include "stm32f10x.h"
#include "pn18f.h"
#ifndef FW_BUS
#define FW_BUS PN_BUS_SPI
#endif
#define CPU_HZ 72000000u
#define APB1_HZ 36000000u
#define SPI_BR SPI_CR1_BR_2 /* BR = 100: fPCLK / 32 */
_Static_assert(SPI_BR == 0x20u, "BR[2:0] = 100 — биты 5:3 SPI_CR1");
_Static_assert(SPI_CR1_LSBFIRST == 0x80u, "LSBFIRST — бит 7 SPI_CR1");
_Static_assert(APB1_HZ / (2u * 100000u) == 180u, "I2C1: CCR = 180 — 100 кГц");
_Static_assert((APB1_HZ + 57600u) / 115200u == 313u, "USART2: BRR = 313 — 115 200 бод");
_Static_assert(CPU_HZ / 115200u == 625u, "USART1: BRR = 625 — 115 200 бод");
static uint32_t now(void)
{
return DWT->CYCCNT;
}
void pn_delay_ms(uint32_t ms)
{
uint32_t t0 = now();
while (now() - t0 < ms * (CPU_HZ / 1000u)) {
}
}
int pn_irq(void)
{
return (GPIOB->IDR >> 1) & 1u; /* PB1: ноль — PN532 приготовил кадр */
}
/* ── I2C1: старый блок F1, ожидания с таймаутом 10 мс ─────────────────── */
static int i2c_wait(uint32_t flag)
{
uint32_t t0 = now();
while (!(I2C1->SR1 & flag)) {
if (I2C1->SR1 & I2C_SR1_AF)
return -1; /* NACK: на адрес никто не ответил */
if (now() - t0 > CPU_HZ / 100u)
return -2;
}
return 0;
}
static int i2c_fail(void)
{
I2C1->CR1 |= I2C_CR1_STOP;
I2C1->SR1 &= (uint16_t)~I2C_SR1_AF;
return -1;
}
int pn_i2c_write(uint8_t addr7, const uint8_t *d, uint32_t n)
{
I2C1->CR1 |= I2C_CR1_START;
if (i2c_wait(I2C_SR1_SB))
return i2c_fail();
I2C1->DR = (uint8_t)(addr7 << 1); /* 0x24 -> 0x48: запись */
if (i2c_wait(I2C_SR1_ADDR))
return i2c_fail();
(void)I2C1->SR1;
(void)I2C1->SR2;
for (uint32_t i = 0; i < n; i++) {
if (i2c_wait(I2C_SR1_TXE))
return i2c_fail();
I2C1->DR = d[i];
}
if (i2c_wait(I2C_SR1_BTF))
return i2c_fail();
I2C1->CR1 |= I2C_CR1_STOP;
return 0;
}
int pn_i2c_read(uint8_t addr7, uint8_t *d, uint32_t n)
{
I2C1->CR1 |= I2C_CR1_ACK | I2C_CR1_START;
if (i2c_wait(I2C_SR1_SB))
return i2c_fail();
I2C1->DR = (uint8_t)((addr7 << 1) | 1u); /* 0x49: чтение */
if (i2c_wait(I2C_SR1_ADDR))
return i2c_fail();
if (n == 1u)
I2C1->CR1 &= (uint16_t)~I2C_CR1_ACK; /* один байт: снять ACK до сброса ADDR */
(void)I2C1->SR1;
(void)I2C1->SR2;
if (n == 1u)
I2C1->CR1 |= I2C_CR1_STOP;
for (uint32_t i = 0; i < n; i++) {
if (i2c_wait(I2C_SR1_RXNE))
return i2c_fail();
d[i] = (uint8_t)I2C1->DR;
if (n > 1u && i == n - 2u) {
I2C1->CR1 &= (uint16_t)~I2C_CR1_ACK; /* последний байт — без подтверждения */
I2C1->CR1 |= I2C_CR1_STOP;
}
}
return 0;
}
/* ── SPI1: байты младшим битом вперёд делает сам блок (LSBFIRST) ──────── */
void pn_spi(const uint8_t *tx, uint8_t *rx, uint32_t n)
{
GPIOA->BRR = 1u << 4; /* NSS вниз */
for (uint32_t i = 0; i < n; i++) {
while (!(SPI1->SR & SPI_SR_TXE)) {
}
SPI1->DR = tx[i];
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
rx[i] = (uint8_t)SPI1->DR;
}
while (SPI1->SR & SPI_SR_BSY) {
}
GPIOA->BSRR = 1u << 4; /* NSS вверх — конец транзакции */
}
/* ── USART2 (HSU) ─────────────────────────────────────────────────────── */
void pn_uart_write(const uint8_t *d, uint32_t n)
{
for (uint32_t i = 0; i < n; i++) {
while (!(USART2->SR & USART_SR_TXE)) {
}
USART2->DR = d[i];
}
while (!(USART2->SR & USART_SR_TC)) {
}
}
uint32_t pn_uart_read(uint8_t *d, uint32_t n, uint32_t timeout_ms)
{
uint32_t k = 0, t0 = now();
while (k < n && now() - t0 < timeout_ms * (CPU_HZ / 1000u)) {
if (USART2->SR & USART_SR_RXNE)
d[k++] = (uint8_t)USART2->DR;
}
return k;
}
/* ── вывод в USART1 ───────────────────────────────────────────────────── */
static void put(char c)
{
while (!(USART1->SR & USART_SR_TXE)) {
}
USART1->DR = (uint8_t)c;
}
static void puts_(const char *s)
{
while (*s)
put(*s++);
}
static void hex(const uint8_t *d, uint32_t n)
{
static const char h[] = "0123456789ABCDEF";
for (uint32_t i = 0; i < n; i++) {
put(h[d[i] >> 4]);
put(h[d[i] & 15u]);
}
}
static void board_init(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN
| RCC_APB2ENR_SPI1EN | RCC_APB2ENR_USART1EN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN | RCC_APB1ENR_USART2EN;
GPIOA->BSRR = 1u << 4; /* NSS вверх до настройки вывода */
GPIOA->CRL = (GPIOA->CRL & 0x000000FFu)
| 0xBu << 8 /* PA2 — USART2_TX, альтернативная функция */
| 0x4u << 12 /* PA3 — USART2_RX, вход */
| 0x3u << 16 /* PA4 — выход: NSS */
| 0xBu << 20 /* PA5 — SCK */
| 0x4u << 24 /* PA6 — MISO, вход */
| 0xBu << 28; /* PA7 — MOSI */
GPIOA->CRH = (GPIOA->CRH & ~(0xFu << 4)) | 0xBu << 4; /* PA9 — USART1_TX */
GPIOB->ODR |= 1u << 1; /* PB1 — подтяжка вверх */
GPIOB->CRL = (GPIOB->CRL & 0x00FFFF0Fu)
| 0x8u << 4 /* PB1 — IRQ, вход с подтяжкой */
| 0xFu << 24 /* PB6 — SCL, альтернативная функция, открытый сток */
| 0xFu << 28; /* PB7 — SDA */
GPIOC->CRH = (GPIOC->CRH & ~(0xFu << 20)) | 0x2u << 20; /* PC13 — светодиод платы */
SPI1->CR1 = SPI_CR1_MSTR | SPI_BR | SPI_CR1_LSBFIRST | SPI_CR1_SSM | SPI_CR1_SSI; /* режим 0 */
SPI1->CR1 |= SPI_CR1_SPE;
I2C1->CR2 = APB1_HZ / 1000000u; /* FREQ = 36 */
I2C1->CCR = APB1_HZ / (2u * 100000u); /* 180: стандартный режим, 100 кГц */
I2C1->TRISE = APB1_HZ / 1000000u + 1u; /* 37 */
I2C1->CR1 = I2C_CR1_PE;
USART2->BRR = (APB1_HZ + 57600u) / 115200u;
USART2->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
USART1->BRR = CPU_HZ / 115200u;
USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
}
int main(void)
{
static pn_t p;
static char url[160];
pn_tag_t t;
board_init();
pn_delay_ms(10);
if (pn_begin(&p, FW_BUS) != PN_OK) {
puts_("PN532: no answer\r\n");
for (;;) {
GPIOC->ODR ^= 1u << 13; /* частое мигание: перемычки, шина, питание */
pn_delay_ms(100);
}
}
puts_("PN532 IC=");
hex(p.fw, 1);
puts_(" v");
hex(p.fw + 1, 2);
puts_("\r\n");
pn_retries(&p, 0x10); /* без этого поиск без метки не кончается никогда */
for (;;) {
if (pn_list(&p, &t, 200) == PN_OK) {
puts_("UID ");
hex(t.uid, t.uid_len);
puts_(" SAK ");
hex(&t.sel_res, 1);
if (t.sel_res == 0x00u && ntag_read_url(&p, url, sizeof url) == PN_OK) {
puts_(" URL ");
puts_(url);
}
puts_("\r\n");
pn_release(&p);
GPIOC->ODR ^= 1u << 13;
}
pn_delay_ms(300);
}
}
Дальше: раздел 18е «DDS-генератор AD9833 по SPI: фазовый аккумулятор, частота и фаза, синус/треугольник/меандр, свип и FSK» — готовая микросхема DDS: частота, фаза и форма сигнала словами по SPI, свип и FSK.