Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F103 · уровень средний · 32 мин чтения
Открыть в интерактивном справочнике

18г. RFID-считыватель RC522: MFRC522 по SPI, поиск карты, UID, антиколлизия, чтение блоков MIFARE Classic

Раздел 18 разобрал шину SPI на флеш-памяти, SD-карте и сдвиговых регистрах, разделы 18а–18в — звук. Здесь ещё одно устройство на SPI, самое частое в наборах для начинающих: модуль RC522 — считыватель бесконтактных карт и брелоков на 13,56 МГц. Прошивка с ним говорит на двух уровнях: по SPI — с регистрами микросхемы MFRC522, а через неё по радио — с картой. Метка с другой стороны, которую читает телефон, была в разделе 94а, считыватель NFC в Flipper Zero — в разделе 94б, ближняя зона на 13,56 МГц — в разделе 89.5.

Опоры: RM0008 (DocID13902 Rev 17): APB2 работает на полной частоте до 72 МГц, с. 48; бит SPI1EN, с. 112; настройка выводов SPI, таблица 25, с. 166; SPI1 без ремапа — NSS/PA4, SCK/PA5, MISO/PA6, MOSI/PA7, с. 185; SPI_CR1 — BR[2:0], CPOL, CPHA, с. 742–743; флаги BSY, TXE, RXNE, с. 745–746. DocID13587 Rev 17 (STM32F103x8/xB): PA4–PA7 и PB0 — без допуска 5 В, с. 29. Документа на MFRC522 и стандарта ISO 14443 в проекте нет: адреса регистров, биты и команды микросхемы взяты по документации производителя и не проверены, команды карты — общеизвестные. Все числа ниже — прогон драйвера _verify/rfid18e/rfid18e.c на компьютере (gcc 12.2.0) со своей моделью MFRC522 и карт на Python 3.11; прошивка собрана armclang 6.24 под Cortex-M3.

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

Оригинал Перевод
PCD — proximity coupling device считыватель
PICC — proximity card карта, брелок, метка
UID — unique identifier номер карты, 4, 7 или 10 байт
REQA / WUPA запрос карт в покое / разбудить и усыплённые
ATQA — answer to request ответ на запрос: 2 байта
anticollision, cascade level антиколлизия, каскад
BCC — block check character XOR четырёх байт UID
SAK — select acknowledge ответ на выбор: тип карты
HLTA усыпить карту
sector trailer трейлер сектора: ключи и биты доступа
Transceive команда «передать и принять»

Вверху — подключение модуля RC522 к STM32F103: SPI1 без ремапа в режиме 0 на 4,5 МГц, PA4 — SDA, то есть выбор микросхемы, PA5 — SCK, PA6 — MISO, PA7 — MOSI, PB0 — RST; модуль с микросхемой MFRC522 питается от 3,3 В, антенна — катушка на плате, в поле 13,56 МГц карта A с UID 4 байта и карта B с UID 7 байт; выводы PA4–PA7 и PB0 без допуска 5 В, после сброса антенна выключена. Посередине — адресный байт SPI: бит 7 — чтение или запись, биты 6:1 — адрес регистра, бит 0 — ноль; чтение VersionReg 0x37 начинается с байта 0xEE, запись CommandReg 0x01 — с байта 0x02. Внизу — обмен с одной картой: REQA 26 из 7 бит, ответ ATQA 04 00; антиколлизия 93 20, ответ UID 3A 9F 12 C4 и BCC 73, у двух карт — коллизия на бите 23; SELECT 93 70 с UID, BCC и CRC 28 C1, ответ SAK 08 — MIFARE Classic 1K, SAK 04 — дальше каскад 2; аутентификация 60 04 ключом A, при неверном ключе — тишина 25 мс; READ 30 04 — 16 байт и CRC_A; HLTA 50 00 57 CD — карта молчит, таймаут 25 мс и есть успех


18г.1 Модуль RC522

На плате модуля — микросхема MFRC522, кварц, согласующие детали и антенна: катушка, вытравленная по краю платы. Микросхема сама формирует поле 13,56 МГц, модулирует его командами и разбирает ответ карты по ISO 14443A. Скорость в эфире — 106 кбит/с: бит длится 128 периодов несущей, 128 / 13,56 МГц = 9,44 мкс. Карта своей батарейки не имеет — её питает поле считывателя (раздел 89.5), поэтому читает модуль на сантиметрах.

Наружу у микросхемы три интерфейса: SPI, I²C и UART; на модулях RC522 обычно разведён SPI, его здесь и берём. Внутри — регистры с шестибитным адресом, до 64 штук, FIFO на 64 байта, таймер, сопроцессор CRC и блок шифра Crypto1 для карт MIFARE Classic. Прошивка кладёт кадр в FIFO, даёт команду — микросхема передаёт его, ждёт ответ и складывает его в тот же FIFO.

Номер версии микросхемы лежит в регистре VersionReg (0x37). Драйвер принимает 0x91 и 0x92 — версии 1.0 и 2.0 по документации производителя (не проверено); модель отвечает 0x92. Если там 0x00 или 0xFF, связи по SPI нет.


18г.2 Подключение к F103: SPI1, адресный байт, сброс, питание

SPI1 без ремапа сидит на PA4–PA7 (с. 185). Вывод SDA модуля — это выбор микросхемы, его ведём сами обычным выходом на PA4 (программный CS, как в разделе 18.4). RST — на PB0. SPI1 висит на APB2, а та работает на полной частоте — 72 МГц (с. 48); делитель 16 (BR = 011, с. 743) даёт 4,5 МГц. Режим 0: CPOL = 0, CPHA = 0, старшим битом вперёд — режим MFRC522 по документации производителя, не проверено. Свой предел частоты SPI у микросхемы есть, но документа нет — 4,5 МГц взяты с запасом.

void rc_spi(const uint8_t *tx, uint8_t *rx, uint32_t n)
{
    GPIOA->BRR = 1u << 4;                               /* SDA (CS) вниз */
    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;                              /* CS вверх — конец транзакции */
}

Адресный байт. Первый байт каждой транзакции — адрес регистра, но не как есть: бит 7 — чтение (1) или запись (0), биты 6:1 — адрес, бит 0 — ноль. Адрес сдвигают на 1 влево. Чтение VersionReg (0x37) начинается с байта 0xEE, запись в CommandReg (0x01) — с байта 0x02. При чтении данные приходят со вторым байтом: пока ведущий шлёт адрес, ведомый ещё не знает, что отвечать. Модель стенда проверяет бит 0 каждого адресного байта: за весь прогон с ним не было ни одного байта.

void rc_write(uint8_t reg, uint8_t v)
{
    uint8_t tx[2] = {(uint8_t)((reg << 1) & 0x7Eu), v}, rx[2];
    rc_spi(tx, rx, 2);
}

uint8_t rc_read(uint8_t reg)
{
    uint8_t tx[2] = {(uint8_t)(0x80u | ((reg << 1) & 0x7Eu)), 0}, rx[2];
    rc_spi(tx, rx, 2);
    return rx[1];                       /* первый байт MISO — мусор, данные приходят со вторым */
}

Байт на 4,5 МГц — 1,778 мкс, чтение регистра — два байта, 3,56 мкс.

Сброс и настройка. rc_init опускает RST на 1 мс, ждёт 50 мс, даёт программный сброс SoftReset и читает версию. Потом — таймер, который ограничивает ожидание ответа карты: TPrescaler = 0xA9 даёт 13,56 МГц / (2 · 169 + 1) = 40 кГц, перезагрузка 1000 — 25,025 мс (формула — по документации производителя, не проверено; модель считает по ней). ModeReg = 0x3D задаёт начальное значение CRC 0x6363. Последний шаг — включить антенну: после сброса TxControlReg равен 0x80, выводы антенны выключены, после rc_init — 0x83. В модели вся настройка — 12 транзакций SPI, 24 байта и 51,04 мс, почти всё — ожидание после сброса.

Питание — 3,3 В. MFRC522 — микросхема на 3,3 В, и выводы F103 PA4–PA7 и PB0 тоже без допуска 5 В (с. 29). Вывод 3.3V модуля — к 3,3 В платы, не к 5 В.


18г.3 Регистры, FIFO, Transceive и CRC

Обмен с картой — одна функция. Она останавливает микросхему, снимает флаги, очищает FIFO, кладёт туда кадр, пишет в BitFramingReg, сколько бит в последнем байте и с какого бита класть ответ, даёт команду Transceive и бит StartSend. Дальше — опрос ComIrqReg: приём (0x20), простой (0x10) или таймер (0x01).

int rc_transceive(rc_t *r, const uint8_t *tx, uint32_t n, uint8_t tx_last,
                  uint8_t *rx, uint32_t *rx_n, uint8_t *rx_last, uint8_t rx_align)
{
    uint8_t irq = 0;
    *rx_n = 0;
    rc_write(RC_COMMAND, RC_CMD_IDLE);
    rc_write(RC_COMIRQ, 0x7Fu);         /* снять все флаги */
    rc_write(RC_FIFOLEVEL, 0x80u);      /* очистить FIFO */
    fifo_write(tx, n);
    rc_write(RC_BITFRAMING, (uint8_t)((rx_align << 4) | tx_last));
    rc_write(RC_COMMAND, RC_CMD_TRANSCEIVE);
    rc_write(RC_BITFRAMING, (uint8_t)(0x80u | (rx_align << 4) | tx_last));   /* StartSend */
    for (r->polls = 1; r->polls <= POLL_MAX; r->polls++) {
        irq = rc_read(RC_COMIRQ);
        if (irq & 0x31u)                /* приём, простой или таймер */
            break;
    }
    rc_write(RC_BITFRAMING, 0);
    if (!(irq & 0x30u))
        return RC_TIMEOUT;              /* таймер: карта не ответила (или POLL_MAX) */
    uint8_t err = rc_read(RC_ERROR);
    if (err & 0x13u)
        return RC_ERR;                  /* FIFO переполнен, чётность, протокол */
    uint32_t k = rc_read(RC_FIFOLEVEL) & 0x7Fu;
    if (k > 64u)
        return RC_ERR;
    fifo_read(rx, k);
    *rx_n = k;
    *rx_last = rc_read(RC_CONTROL) & 0x07u;
    if (err & 0x08u) {                  /* коллизия: где? */
        uint8_t c = rc_read(RC_COLL);
        if (c & 0x20u)
            return RC_ERR;
        r->coll_pos = (c & 0x1Fu) ? (c & 0x1Fu) : 32u;
        r->colls++;
        return RC_COLLISION;
    }
    return RC_OK;
}

FIFO пишется и читается одной транзакцией: при записи один адресный байт и дальше все данные подряд, при чтении — адрес FIFODataReg столько раз, сколько байт нужно, и в конце ноль.

Опрос стоит дороже, чем данные. Пустое поле: REQA уходит, отвечать некому, и драйвер 7063 раза читает ComIrqReg, пока таймер не скажет «тишина», — 25,141 мс и 14 142 байта по SPI. С картой REQA занимает 102 опроса и 0,407 мс. Опрос можно заменить прерыванием: у MFRC522 есть вывод IRQ, но в этом драйвере он не используется.

CRC_A. Кадры SELECT, READ и HLTA кончаются двумя байтами CRC_A: многочлен x¹⁶ + x¹² + x⁵ + 1, начальное значение 0x6363, младший байт идёт первым. Для исходящих кадров драйвер зовёт сопроцессор микросхемы (команда CalcCRC), ответ карты проверяет своей функцией crc_a — без лишних транзакций SPI. Сверка в прогоне: сопроцессор (в модели — побитный расчёт на Python) и crc_a на Си дали одно и то же на кадре SELECT (28 C1), HLTA 50 00 (57 CD), READ 30 04 (26 EE), 60 04 (D1 3D), 00 00 (A0 1E) и 12 34 (26 CF) и на 200 случайных кадрах длиной от 1 до 18 байт — на всех 200. CRC по кадру вместе с его CRC даёт 0 — так проверяют принятый кадр. Это самосогласованность трёх расчётов, а не сверка со стандартом: его на диске нет.


18г.4 Поиск карты: REQA, антиколлизия, SELECT, SAK

REQA и ATQA. Поиск начинается с короткого кадра в 7 бит: REQA (26) будит карты, которые лежат в поле в покое. Каждая отвечает двумя байтами ATQA; у карты A в модели — 04 00. WUPA (52) будит ещё и усыплённые командой HLTA.

Антиколлизия. Дальше считыватель шлёт 93 20: «каскад 1, я знаю 0 бит вашего UID, отвечайте». Байт 20 — NVB: старшая тетрада — сколько целых байт в кадре (2: сама команда и NVB), младшая — сколько ещё бит. Одна карта отвечает 40 битами: 4 байта UID и BCC — XOR этих четырёх байт. У карты A: 3A 9F 12 C4, BCC 73. Драйвер проверяет BCC и шлёт SELECT — 93 70, UID, BCC и CRC 28 C1; карта отвечает SAK 08 с CRC. Вся процедура для одной карты в модели — 558 транзакций SPI, 1137 байт, 2,021 мс.

Две карты. Если в поле две карты, они отвечают одновременно, и где их биты расходятся, микросхема видит коллизию и пишет её номер в CollReg. UID 3A 9F 12 C4 и 3A 9F 52 C4 расходятся в бите 23 — модель так и сообщила. Драйвер ставит этот бит в 1, считает известными 23 бита и шлёт их: 93 47 3A 9F 52 — 39 бит, NVB 47: 4 целых байта и 7 бит. Теперь отвечает только карта с единицей в бите 23, и её ответ ложится в FIFO с бита 7 — это поле выравнивания в BitFramingReg.

    for (;;) {
        uint32_t nb = known / 8u, nbit = known % 8u;
        buf[1] = (uint8_t)(((2u + nb) << 4) | nbit);           /* NVB: сколько байт и бит уходит */
        int st = rc_transceive(r, buf, 2u + nb + (nbit ? 1u : 0u), (uint8_t)nbit, rx, &n, &last, (uint8_t)nbit);
        if (st != RC_OK && st != RC_COLLISION)
            return st;
        r->rounds++;
        uint8_t keep = (uint8_t)((1u << nbit) - 1u);           /* младшие биты первого байта — наши */
        for (uint32_t i = 0; i < n && 2u + nb + i < 7u; i++)
            buf[2u + nb + i] = i ? rx[i] : (uint8_t)((buf[2u + nb] & keep) | (rx[0] & (uint8_t)~keep));
        if (st == RC_OK)
            break;
        uint32_t pos = nb * 8u + r->coll_pos;                  /* номер бита в CLn, от 1 */
        if (pos <= known || pos > 32u)
            return RC_ERR;
        buf[2u + (pos - 1u) / 8u] |= (uint8_t)(1u << ((pos - 1u) % 8u));   /* идём по ветке «1» */
        known = pos;
        if (r->rounds > 40u)
            return RC_ERR;
    }

Выбранную карту драйвер усыпляет HLTA и снова шлёт REQA — теперь отвечает только вторая. Скан двух карт нашёл сначала 3A 9F 52 C4, потом 3A 9F 12 C4, с одной коллизией, за 81,675 мс. Из них 76,0 мс — три ожидания по 25 мс: на HLTA карта не отвечает, и «успех» здесь — это таймаут, а последний REQA ждёт, что больше никого нет. После скана обе карты спят: REQA — тишина, WUPA — ATQA 04 00.

Каскады. UID бывает 4, 7 и 10 байт, а кадр антиколлизии несёт 4. У карты с UID 7 байт в каскаде 1 идёт байт CT (88) и три байта UID, SAK 04 значит «UID не весь», и драйвер повторяет всё с 95 — каскадом 2. Карта B (04 52 8A 1B 3C 5D 80) отдала 88 04 52 8A, BCC 54, SAK 04, потом 1B 3C 5D 80, BCC FA, SAK 00. С картами A и B вместе коллизия случилась дважды: в ATQA (04 00 и 44 00 расходятся в бите 7) и в каскаде 1 — на бите 2, где 88 встречается с 3A. Коллизию в ATQA драйвер пропускает — UID всё равно разберёт антиколлизия.

SAK говорит, что за карта: 08 — MIFARE Classic 1K, 00 — карта без шифра вроде MIFARE Ultralight (значения общеизвестные, не проверены по документу). В модели карта B — такая: страницы 4–7 она отдаёт командой READ без всяких ключей, а аутентификация на ней кончается таймаутом.


18г.5 MIFARE Classic: сектор, блок, ключ A, чтение

Память MIFARE Classic 1K — 16 секторов по 4 блока по 16 байт, 1024 байта. Блок 0 — заводской: UID, BCC, SAK, ATQA. Последний блок каждого сектора — трейлер: ключ A (6 байт), биты доступа (4 байта), ключ B (6 байт). Читать блок можно только после аутентификации в его секторе.

Аутентификацию ведёт сама микросхема — команда MFAuthent: в FIFO кладут 60 (ключ A), номер блока, 6 байт ключа и последние 4 байта UID. Если ключ подошёл, в Status2Reg поднимается бит MFCrypto1On, и дальше обмен с картой идёт под шифром — прошивка этого не видит, микросхема шифрует и расшифровывает сама. Ключ FF FF FF FF FF FF — заводской, общеизвестный; новые карты обычно продают с ним.

int rc_read_block(rc_t *r, uint8_t block, uint8_t out[16])
{
    uint8_t buf[4] = {MF_READ, block}, rx[64], last = 0;
    uint32_t n = 0;
    if (rc_calc_crc(buf, 2, buf + 2) != RC_OK)
        return RC_TIMEOUT;
    int st = rc_transceive(r, buf, 4, 0, rx, &n, &last, 0);
    if (st != RC_OK)
        return st;
    if (n == 1 && last == 4)
        return RC_NAK;                  /* 4 бита вместо 18 байт: карта отказала */
    if (n != 18 || last != 0)
        return RC_ERR;
    if (!r->no_crc_check && crc_a(rx, 16) != (uint16_t)(rx[16] | rx[17] << 8))
        return RC_CRC;                  /* ловит и помехи в эфире, и сбой на проводах SPI */
    memcpy(out, rx, 16);
    return RC_OK;
}

В модели: аутентификация в блоке 4 — 1,993 мс, чтение блока 4 — 2,107 мс, 582 транзакции SPI и 1185 байт; в блоке 16 байт Hello, STM32F103. Трейлер того же сектора (блок 7) читается так: на месте ключа A нули — ключ A из карты не прочитать, — затем биты доступа FF 07 80 69 и ключ B (поведение модели повторяет то, что описано для карт, — не проверено). Блок 0 из другого сектора карта не отдала: вместо 18 байт пришли 4 бита отказа. С неверным ключом карта замолкает — таймаут 25,424 мс, — и дальше не отвечает и на READ: её снова будят WUPA, выбирают и аутентифицируют заново.


18г.6 Почему это не защита

Ключ MIFARE Classic — 48 бит, шифр Crypto1 — собственный алгоритм NXP. Его давно восстановили и опубликовали, нашли слабости и в самом шифре, и в генераторе случайных чисел карты; с тех пор известны способы достать ключи сектора, имея на руках карту или подслушав обмен. Как именно — не тема этого раздела. Вывод для прошивки простой: MIFARE Classic и RC522 не годятся для защиты — замка, пропуска, денег.

Ещё слабее схема «пускаем по UID». UID карта отдаёт до всякой аутентификации любому считывателю — в 18г.4 он пришёл в первом же ответе. Продаются карты с перезаписываемым блоком 0, на которые UID копируется. UID — это номер для опознания, а не пароль.

Для учёта, игрушек, выбора профиля или «приложи брелок — включи светильник» RC522 хорош. Для защиты нужны карты с проверенной стойкой криптографией и считыватели, которые её поддерживают; о криптографии в микроконтроллере — раздел 43.


18г.7 Грабли

Версия читается как 0x00 или 0xFF. Нет связи по SPI: не тот режим, перепутаны MISO и MOSI, SDA не опускается или модуль без питания. Драйвер в этом случае дальше не идёт.

Модуль от 5 В. MFRC522 — на 3,3 В, выводы F103 PA4–PA7 и PB0 без допуска 5 В (с. 29). Питать модуль от 3,3 В.

Карта не находится никогда. После сброса антенна выключена: TxControlReg = 0x80. В модели без её включения REQA кончается таймаутом, скан находит 0 карт; после включения (0x83) та же карта находится за 53,024 мс.

Длинные провода SPI. Провода в десятки сантиметров на макетке дают сбои битов. В модели каждый бит MISO с вероятностью p переворачивается, по 200 чтений блока:

p проверка CRC верно ошибка CRC прочие ошибки неверные данные
10⁻⁴ да 159 4 37 0
10⁻⁴ нет 165 0 33 2
10⁻⁵ да 195 0 5 0
10⁻⁵ нет 195 0 5 0

Чтение блока — 9480 бит по SPI, полезных из них 144 (18 байт ответа): остальное — опрос и служебные регистры, и большинство сбоев попадает туда. CRC ответа проверяет данные на всём пути — в эфире и на проводах: с проверкой неверных данных 0, без неё — 2 из 200. Лечится короче проводами и меньшей частотой SPI.

Две карты в поле. Обе отвечают одновременно. Без антиколлизии драйвер получил бы смесь битов; с ней выбирается одна, а остальные находятся после HLTA (18г.4).

HLTA «не работает». На HLTA карта не отвечает — драйвер ждёт таймаут и считает это успехом. Каждая HLTA — 25,454 мс.

Карта больше не отвечает после неверного ключа. Она вышла из выбранного состояния: WUPA, SELECT и аутентификация заново.


18г.8 Итоги

Полные исходники

rfid18e.h — регистры и команды MFRC522, команды карты, интерфейс драйвера.

/* rfid18e.h — раздел 18г: драйвер считывателя MFRC522 (модуль RC522) без регистров STM32.
 * Драйвер говорит с микросхемой через три функции платформы: rc_spi, rc_rst, rc_delay_ms.
 * В прошивке fw18e.c их даёт SPI1 и GPIO STM32F103, в стенде host18e.c — модель model18e.py.
 * Адреса регистров, команды и биты MFRC522 — по документации производителя (в проекте её нет, не проверено);
 * команды карты — ISO 14443A и MIFARE Classic, общеизвестные. */
#ifndef RFID18E_H
#define RFID18E_H
#include <stdint.h>

/* ── регистры MFRC522 ─────────────────────────────────────────────────── */
#define RC_COMMAND     0x01u
#define RC_COMIRQ      0x04u            /* флаги: 0x01 таймер, 0x02 ошибка, 0x10 простой, 0x20 приём */
#define RC_DIVIRQ      0x05u            /* 0x04 — CRC посчитан */
#define RC_ERROR       0x06u            /* 0x01 протокол, 0x02 чётность, 0x04 CRC, 0x08 коллизия, 0x10 FIFO */
#define RC_STATUS2     0x08u            /* 0x08 — шифрование Crypto1 включено */
#define RC_FIFODATA    0x09u
#define RC_FIFOLEVEL   0x0Au            /* сколько байт в FIFO; запись 0x80 — очистить */
#define RC_CONTROL     0x0Cu            /* биты 2:0 — сколько бит в последнем принятом байте */
#define RC_BITFRAMING  0x0Du            /* 0x80 начать передачу, 6:4 выравнивание приёма, 2:0 бит в последнем байте */
#define RC_COLL        0x0Eu            /* 4:0 — номер бита первой коллизии, 0x20 — номер неверен */
#define RC_MODE        0x11u
#define RC_TXCONTROL   0x14u            /* биты 1:0 — антенна (выводы TX1, TX2) */
#define RC_TXASK       0x15u
#define RC_CRCRESULT_H 0x21u
#define RC_CRCRESULT_L 0x22u
#define RC_TMODE       0x2Au
#define RC_TPRESCALER  0x2Bu
#define RC_TRELOAD_H   0x2Cu
#define RC_TRELOAD_L   0x2Du
#define RC_VERSION     0x37u

/* ── команды MFRC522 ──────────────────────────────────────────────────── */
#define RC_CMD_IDLE       0x00u
#define RC_CMD_CALCCRC    0x03u
#define RC_CMD_TRANSCEIVE 0x0Cu
#define RC_CMD_MFAUTHENT  0x0Eu
#define RC_CMD_SOFTRESET  0x0Fu

/* ── команды карты ────────────────────────────────────────────────────── */
#define PICC_REQA    0x26u              /* короткий кадр 7 бит: ответят карты в покое */
#define PICC_WUPA    0x52u              /* 7 бит: ответят и усыплённые HLTA */
#define PICC_SEL1    0x93u              /* каскад 1; 0x95 — 2, 0x97 — 3 */
#define PICC_CT      0x88u              /* в каскаде 1: «UID длиннее, дальше каскад 2» */
#define PICC_HLTA    0x50u
#define MF_AUTH_A    0x60u
#define MF_READ      0x30u

/* ── ответ драйвера ───────────────────────────────────────────────────── */
enum { RC_OK, RC_TIMEOUT, RC_COLLISION, RC_ERR, RC_CRC, RC_NAK, RC_NOCHIP, RC_BCC };

typedef struct {
    uint8_t uid[10];
    uint8_t len;                        /* 4, 7 или 10 байт */
    uint8_t sak;                        /* SAK последнего каскада */
    uint8_t atqa[2];
} rc_card_t;

typedef struct {
    uint8_t version;                    /* VersionReg после сброса */
    uint8_t coll_pos;                   /* номер бита последней коллизии в CLn, от 1 */
    uint8_t rounds;                     /* шагов антиколлизии в последнем rc_select */
    uint8_t no_crc_check;               /* 1 — не проверять CRC ответа (для опыта в 18г.7) */
    uint32_t polls;                     /* чтений ComIrqReg в последнем обмене */
    uint32_t colls;                     /* всего коллизий */
} rc_t;

/* ── платформа: даёт прошивка или стенд ───────────────────────────────── */
void rc_spi(const uint8_t *tx, uint8_t *rx, uint32_t n);  /* CS вниз, n байт, CS вверх */
void rc_rst(int level);                                    /* вывод RST модуля */
void rc_delay_ms(uint32_t ms);

/* ── драйвер ──────────────────────────────────────────────────────────── */
void rc_write(uint8_t reg, uint8_t v);
uint8_t rc_read(uint8_t reg);
int rc_init(rc_t *r, int antenna);
uint16_t crc_a(const uint8_t *d, uint32_t n);               /* CRC_A программно: для сверки с микросхемой */
int rc_calc_crc(const uint8_t *d, uint32_t n, uint8_t out[2]);
int rc_transceive(rc_t *r, const uint8_t *tx, uint32_t n, uint8_t tx_last,   /* rx — 64 байта */
                  uint8_t *rx, uint32_t *rx_n, uint8_t *rx_last, uint8_t rx_align);
int rc_request(rc_t *r, uint8_t cmd, uint8_t atqa[2]);
int rc_select(rc_t *r, rc_card_t *c);
int rc_halt(rc_t *r);
int rc_auth(rc_t *r, uint8_t block, const uint8_t key[6], const rc_card_t *c);
void rc_stop_crypto(void);
int rc_read_block(rc_t *r, uint8_t block, uint8_t out[16]);
int rc_scan(rc_t *r, rc_card_t *cards, int max);

#endif

rfid18e.c — драйвер без регистров STM32: общая часть прошивки и стенда.

/* rfid18e.c — раздел 18г: драйвер MFRC522 — регистры, FIFO, Transceive, CRC_A, REQA, антиколлизия,
 * SELECT по каскадам, HLTA, аутентификация ключом A и чтение блока MIFARE Classic. Без регистров STM32. */
#include <string.h>
#include "rfid18e.h"

#define POLL_MAX 40000u                 /* предохранитель: таймер MFRC522 (25 мс) сработает раньше */

/* ── регистры: адресный байт — бит 7 чтение, биты 6:1 адрес, бит 0 — ноль ── */
void rc_write(uint8_t reg, uint8_t v)
{
    uint8_t tx[2] = {(uint8_t)((reg << 1) & 0x7Eu), v}, rx[2];
    rc_spi(tx, rx, 2);
}

uint8_t rc_read(uint8_t reg)
{
    uint8_t tx[2] = {(uint8_t)(0x80u | ((reg << 1) & 0x7Eu)), 0}, rx[2];
    rc_spi(tx, rx, 2);
    return rx[1];                       /* первый байт MISO — мусор, данные приходят со вторым */
}

static void fifo_write(const uint8_t *d, uint32_t n)
{
    uint8_t tx[65], rx[65];
    tx[0] = (uint8_t)((RC_FIFODATA << 1) & 0x7Eu);
    memcpy(tx + 1, d, n);
    rc_spi(tx, rx, n + 1);              /* один адрес — все байты подряд уходят в FIFO */
}

static void fifo_read(uint8_t *d, uint32_t n)
{
    uint8_t tx[65], rx[65];
    memset(tx, (int)(0x80u | ((RC_FIFODATA << 1) & 0x7Eu)), n);
    tx[n] = 0;                          /* последний адрес — 0: «больше не читаю» */
    rc_spi(tx, rx, n + 1);
    memcpy(d, rx + 1, n);
}

int rc_init(rc_t *r, int antenna)
{
    memset(r, 0, sizeof *r);
    rc_rst(0);                          /* жёсткий сброс выводом RST */
    rc_delay_ms(1);
    rc_rst(1);
    rc_delay_ms(50);                    /* запуск генератора — с запасом */
    rc_write(RC_COMMAND, RC_CMD_SOFTRESET);
    for (uint32_t i = 0; i < 100u && (rc_read(RC_COMMAND) & 0x10u); i++)
        rc_delay_ms(1);                 /* бит PowerDown: микросхема ещё просыпается */
    r->version = rc_read(RC_VERSION);
    if (r->version != 0x91u && r->version != 0x92u)
        return RC_NOCHIP;               /* 0x00 или 0xFF — нет связи по SPI */
    rc_write(RC_TMODE, 0x80u);          /* TAuto: таймер стартует в конце передачи */
    rc_write(RC_TPRESCALER, 0xA9u);     /* 13,56 МГц / (2 · 169 + 1) = 40 кГц */
    rc_write(RC_TRELOAD_H, 0x03u);      /* 1000 отсчётов — 25 мс на ответ карты */
    rc_write(RC_TRELOAD_L, 0xE8u);
    rc_write(RC_TXASK, 0x40u);          /* модуляция 100 % ASK, как требует ISO 14443A */
    rc_write(RC_MODE, 0x3Du);           /* CRC-сопроцессор начинает с 0x6363 */
    rc_write(RC_COLL, 0x00u);           /* биты после коллизии — нули */
    if (antenna)
        rc_write(RC_TXCONTROL, rc_read(RC_TXCONTROL) | 0x03u);  /* после сброса антенна выключена */
    return RC_OK;
}

uint16_t crc_a(const uint8_t *d, uint32_t n)
{
    uint16_t crc = 0x6363u;
    for (uint32_t i = 0; i < n; i++) {
        uint8_t b = (uint8_t)(d[i] ^ (crc & 0xFFu));
        b = (uint8_t)(b ^ (b << 4));
        crc = (uint16_t)((crc >> 8) ^ ((uint16_t)b << 8) ^ ((uint16_t)b << 3) ^ (b >> 4));
    }
    return crc;
}

int rc_calc_crc(const uint8_t *d, uint32_t n, uint8_t out[2])
{
    rc_write(RC_COMMAND, RC_CMD_IDLE);
    rc_write(RC_DIVIRQ, 0x04u);         /* снять флаг «CRC готов» */
    rc_write(RC_FIFOLEVEL, 0x80u);
    fifo_write(d, n);
    rc_write(RC_COMMAND, RC_CMD_CALCCRC);
    for (uint32_t i = 0; i < POLL_MAX; i++) {
        if (rc_read(RC_DIVIRQ) & 0x04u) {
            rc_write(RC_COMMAND, RC_CMD_IDLE);
            out[0] = rc_read(RC_CRCRESULT_L);   /* в эфир младший байт идёт первым */
            out[1] = rc_read(RC_CRCRESULT_H);
            return RC_OK;
        }
    }
    return RC_TIMEOUT;
}

int rc_transceive(rc_t *r, const uint8_t *tx, uint32_t n, uint8_t tx_last,
                  uint8_t *rx, uint32_t *rx_n, uint8_t *rx_last, uint8_t rx_align)
{
    uint8_t irq = 0;
    *rx_n = 0;
    rc_write(RC_COMMAND, RC_CMD_IDLE);
    rc_write(RC_COMIRQ, 0x7Fu);         /* снять все флаги */
    rc_write(RC_FIFOLEVEL, 0x80u);      /* очистить FIFO */
    fifo_write(tx, n);
    rc_write(RC_BITFRAMING, (uint8_t)((rx_align << 4) | tx_last));
    rc_write(RC_COMMAND, RC_CMD_TRANSCEIVE);
    rc_write(RC_BITFRAMING, (uint8_t)(0x80u | (rx_align << 4) | tx_last));   /* StartSend */
    for (r->polls = 1; r->polls <= POLL_MAX; r->polls++) {
        irq = rc_read(RC_COMIRQ);
        if (irq & 0x31u)                /* приём, простой или таймер */
            break;
    }
    rc_write(RC_BITFRAMING, 0);
    if (!(irq & 0x30u))
        return RC_TIMEOUT;              /* таймер: карта не ответила (или POLL_MAX) */
    uint8_t err = rc_read(RC_ERROR);
    if (err & 0x13u)
        return RC_ERR;                  /* FIFO переполнен, чётность, протокол */
    uint32_t k = rc_read(RC_FIFOLEVEL) & 0x7Fu;
    if (k > 64u)
        return RC_ERR;
    fifo_read(rx, k);
    *rx_n = k;
    *rx_last = rc_read(RC_CONTROL) & 0x07u;
    if (err & 0x08u) {                  /* коллизия: где? */
        uint8_t c = rc_read(RC_COLL);
        if (c & 0x20u)
            return RC_ERR;
        r->coll_pos = (c & 0x1Fu) ? (c & 0x1Fu) : 32u;
        r->colls++;
        return RC_COLLISION;
    }
    return RC_OK;
}

int rc_request(rc_t *r, uint8_t cmd, uint8_t atqa[2])
{
    uint8_t rx[64], last = 0;
    uint32_t n = 0;
    int st = rc_transceive(r, &cmd, 1, 7, rx, &n, &last, 0);   /* короткий кадр: 7 бит */
    if (st == RC_COLLISION)
        st = RC_OK;                     /* разные ATQA у двух карт — не беда, UID разберёт антиколлизия */
    if (st != RC_OK)
        return st;
    if (n != 2 || last != 0)
        return RC_ERR;
    atqa[0] = rx[0];
    atqa[1] = rx[1];
    return RC_OK;
}

/* Один каскад: антиколлизия до 40 известных бит CLn (4 байта и BCC), потом SELECT. */
static int cascade(rc_t *r, uint8_t sel, uint8_t cl[5], uint8_t *sak)
{
    uint8_t buf[9], rx[64], last = 0;
    uint32_t known = 0, n = 0;
    memset(buf, 0, sizeof buf);
    buf[0] = sel;
    for (;;) {
        uint32_t nb = known / 8u, nbit = known % 8u;
        buf[1] = (uint8_t)(((2u + nb) << 4) | nbit);           /* NVB: сколько байт и бит уходит */
        int st = rc_transceive(r, buf, 2u + nb + (nbit ? 1u : 0u), (uint8_t)nbit, rx, &n, &last, (uint8_t)nbit);
        if (st != RC_OK && st != RC_COLLISION)
            return st;
        r->rounds++;
        uint8_t keep = (uint8_t)((1u << nbit) - 1u);           /* младшие биты первого байта — наши */
        for (uint32_t i = 0; i < n && 2u + nb + i < 7u; i++)
            buf[2u + nb + i] = i ? rx[i] : (uint8_t)((buf[2u + nb] & keep) | (rx[0] & (uint8_t)~keep));
        if (st == RC_OK)
            break;
        uint32_t pos = nb * 8u + r->coll_pos;                  /* номер бита в CLn, от 1 */
        if (pos <= known || pos > 32u)
            return RC_ERR;
        buf[2u + (pos - 1u) / 8u] |= (uint8_t)(1u << ((pos - 1u) % 8u));   /* идём по ветке «1» */
        known = pos;
        if (r->rounds > 40u)
            return RC_ERR;
    }
    if ((uint8_t)(buf[2] ^ buf[3] ^ buf[4] ^ buf[5]) != buf[6])
        return RC_BCC;                  /* BCC — XOR четырёх байт */
    buf[1] = 0x70u;                     /* NVB = 7 байт: SELECT */
    if (rc_calc_crc(buf, 7, buf + 7) != RC_OK)
        return RC_TIMEOUT;
    int st = rc_transceive(r, buf, 9, 0, rx, &n, &last, 0);
    if (st != RC_OK)
        return st;
    if (n != 3 || last != 0)
        return RC_ERR;
    if (!r->no_crc_check && crc_a(rx, 1) != (uint16_t)(rx[1] | rx[2] << 8))
        return RC_CRC;
    memcpy(cl, buf + 2, 5);
    *sak = rx[0];
    return RC_OK;
}

int rc_select(rc_t *r, rc_card_t *c)
{
    uint8_t cl[5], sak = 0;
    r->rounds = 0;
    c->len = 0;
    for (uint8_t lvl = 0; lvl < 3u; lvl++) {
        int st = cascade(r, (uint8_t)(PICC_SEL1 + 2u * lvl), cl, &sak);
        if (st != RC_OK)
            return st;
        if (sak & 0x04u) {              /* UID не весь: CT и три байта, дальше следующий каскад */
            if (cl[0] != PICC_CT)
                return RC_ERR;
            memcpy(c->uid + c->len, cl + 1, 3);
            c->len = (uint8_t)(c->len + 3u);
            continue;
        }
        memcpy(c->uid + c->len, cl, 4);
        c->len = (uint8_t)(c->len + 4u);
        c->sak = sak;
        return RC_OK;
    }
    return RC_ERR;
}

int rc_halt(rc_t *r)
{
    uint8_t buf[4] = {PICC_HLTA, 0x00}, rx[64], last = 0;
    uint32_t n = 0;
    if (rc_calc_crc(buf, 2, buf + 2) != RC_OK)
        return RC_TIMEOUT;
    int st = rc_transceive(r, buf, 4, 0, rx, &n, &last, 0);
    return st == RC_TIMEOUT ? RC_OK : RC_ERR;   /* на HLTA карта молчит: успех — это таймаут */
}

int rc_auth(rc_t *r, uint8_t block, const uint8_t key[6], const rc_card_t *c)
{
    uint8_t buf[12], irq = 0;
    buf[0] = MF_AUTH_A;
    buf[1] = block;
    memcpy(buf + 2, key, 6);
    memcpy(buf + 8, c->uid + c->len - 4u, 4);   /* последние 4 байта UID */
    rc_write(RC_COMMAND, RC_CMD_IDLE);
    rc_write(RC_COMIRQ, 0x7Fu);
    rc_write(RC_FIFOLEVEL, 0x80u);
    fifo_write(buf, 12);
    rc_write(RC_COMMAND, RC_CMD_MFAUTHENT);
    for (r->polls = 1; r->polls <= POLL_MAX; r->polls++) {
        irq = rc_read(RC_COMIRQ);
        if (irq & 0x11u)
            break;
    }
    if (!(irq & 0x10u))
        return RC_TIMEOUT;              /* ключ не тот: карта замолчала */
    if (rc_read(RC_ERROR) & 0x1Bu)
        return RC_ERR;
    return (rc_read(RC_STATUS2) & 0x08u) ? RC_OK : RC_ERR;
}

void rc_stop_crypto(void)
{
    rc_write(RC_STATUS2, rc_read(RC_STATUS2) & (uint8_t)~0x08u);
}

int rc_read_block(rc_t *r, uint8_t block, uint8_t out[16])
{
    uint8_t buf[4] = {MF_READ, block}, rx[64], last = 0;
    uint32_t n = 0;
    if (rc_calc_crc(buf, 2, buf + 2) != RC_OK)
        return RC_TIMEOUT;
    int st = rc_transceive(r, buf, 4, 0, rx, &n, &last, 0);
    if (st != RC_OK)
        return st;
    if (n == 1 && last == 4)
        return RC_NAK;                  /* 4 бита вместо 18 байт: карта отказала */
    if (n != 18 || last != 0)
        return RC_ERR;
    if (!r->no_crc_check && crc_a(rx, 16) != (uint16_t)(rx[16] | rx[17] << 8))
        return RC_CRC;                  /* ловит и помехи в эфире, и сбой на проводах SPI */
    memcpy(out, rx, 16);
    return RC_OK;
}

int rc_scan(rc_t *r, rc_card_t *cards, int max)
{
    int n = 0;
    while (n < max) {
        uint8_t atqa[2];
        if (rc_request(r, PICC_REQA, atqa) != RC_OK)
            break;                      /* в поле больше никого: все найденные усыплены */
        if (rc_select(r, &cards[n]) != RC_OK)
            break;
        cards[n].atqa[0] = atqa[0];
        cards[n].atqa[1] = atqa[1];
        rc_halt(r);                     /* усыпить: на следующий REQA ответят остальные */
        n++;
    }
    return n;
}

fw18e.c — прошивка STM32F103: SPI1, RST, вывод в USART1.

/* fw18e.c — раздел 18г: считыватель RC522 (MFRC522) на STM32F103 (BluePill), 72 МГц.
 * SPI1 без ремапа: PA4 — SDA (выбор микросхемы, ведём сами), PA5 — SCK, PA6 — MISO, PA7 — MOSI; PB0 — RST.
 * SPI — режим 0 (CPOL = 0, CPHA = 0), старшим битом вперёд, 72 МГц / 16 = 4,5 МГц.
 * Модуль питается от 3,3 В. UID и блок 4 карты MIFARE Classic — в USART1 (PA9, 115200).
 * Тактирование до 72 МГц — как в разделе 6 (здесь не повторяется); APB2 = 72 МГц. */
#include "stm32f10x.h"
#include "rfid18e.h"

#define CPU_HZ 72000000u
#define SPI_BR (SPI_CR1_BR_0 | SPI_CR1_BR_1)   /* BR = 011: fPCLK / 16 */

_Static_assert(SPI_BR == 0x18u, "BR[2:0] = 011 — биты 5:3 SPI_CR1");
_Static_assert(CPU_HZ / 115200u == 625u, "USART1: BRR = 625 — 115 200 бод");

static const uint8_t key_ff[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};   /* заводской ключ A — общеизвестный */

/* ── платформа драйвера ───────────────────────────────────────────────── */
void rc_spi(const uint8_t *tx, uint8_t *rx, uint32_t n)
{
    GPIOA->BRR = 1u << 4;                               /* SDA (CS) вниз */
    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;                              /* CS вверх — конец транзакции */
}

void rc_rst(int level)
{
    GPIOB->BSRR = level ? 1u : 1u << 16;
}

void rc_delay_ms(uint32_t ms)
{
    uint32_t t0 = DWT->CYCCNT;
    while (DWT->CYCCNT - t0 < ms * (CPU_HZ / 1000u)) {
    }
}

/* ── вывод в 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_SPI1EN | RCC_APB2ENR_USART1EN;
    GPIOA->BSRR = 1u << 4;                              /* CS вверх до настройки вывода */
    GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu)
               | 0x3u << 16                             /* PA4 — выход 50 МГц: CS */
               | 0xBu << 20                             /* PA5 — SCK, альтернативная функция */
               | 0x4u << 24                             /* PA6 — MISO, вход без подтяжки */
               | 0xBu << 28;                            /* PA7 — MOSI, альтернативная функция */
    GPIOA->CRH = (GPIOA->CRH & ~(0xFu << 4)) | 0xBu << 4;      /* PA9 — USART1_TX */
    GPIOB->CRL = (GPIOB->CRL & ~0xFu) | 0x3u;          /* PB0 — RST модуля */
    GPIOC->CRH = (GPIOC->CRH & ~(0xFu << 20)) | 0x2u << 20;    /* PC13 — светодиод платы */
    SPI1->CR1 = SPI_CR1_MSTR | SPI_BR | SPI_CR1_SSM | SPI_CR1_SSI;   /* ведущий, режим 0, NSS программный */
    SPI1->CR1 |= SPI_CR1_SPE;
    USART1->BRR = CPU_HZ / 115200u;
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
}

int main(void)
{
    static rc_t r;
    rc_card_t c;
    uint8_t atqa[2], blk[16];
    board_init();
    if (rc_init(&r, 1) != RC_OK) {
        puts_("MFRC522: no answer, VersionReg=");
        hex(&r.version, 1);
        puts_("\r\n");
        for (;;) {
            GPIOC->ODR ^= 1u << 13;                     /* частое мигание: нет связи по SPI */
            rc_delay_ms(100);
        }
    }
    for (;;) {
        if (rc_request(&r, PICC_REQA, atqa) == RC_OK && rc_select(&r, &c) == RC_OK) {
            puts_("UID ");
            hex(c.uid, c.len);
            puts_(" SAK ");
            hex(&c.sak, 1);
            if (c.sak == 0x08u && rc_auth(&r, 4, key_ff, &c) == RC_OK) {   /* MIFARE Classic 1K */
                if (rc_read_block(&r, 4, blk) == RC_OK) {
                    puts_(" block4 ");
                    hex(blk, 16);
                }
            }
            puts_("\r\n");
            rc_halt(&r);                                /* карта уснула: пока лежит, второй раз не ответит на REQA */
            rc_stop_crypto();                           /* после HLTA: после аутентификации он уходит под шифром */
            GPIOC->ODR ^= 1u << 13;
        }
        rc_delay_ms(200);
    }
}

Дальше: раздел 18д «NFC-модуль PN532: кадры по I2C/SPI/UART, ACK, чтение UID и NTAG2xx, эмуляция и отличие от RC522» — тот же диапазон 13,56 МГц, но микросхема со своей прошивкой: команды вместо регистров, три шины на выбор и метки NTAG с NDEF.

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