Embedded HandbookApps
All sections › III. Peripherals
STM32F103 · level intermediate · 32 min read
Open in the interactive handbook

18г. RFID reader RC522: MFRC522 over SPI, card search, UID, anticollision, reading MIFARE Classic blocks

Section 18 covered the SPI bus with flash memory, an SD card and shift registers, sections 18а–18в covered sound. Here is one more SPI device, the most common one in beginner kits: the RC522 module, a reader of contactless cards and key fobs at 13.56 MHz. The firmware talks to it on two levels: over SPI with the registers of the MFRC522 chip, and through the chip over radio with the card. The tag on the other side, read by a phone, was in section 94а, the NFC reader in the Flipper Zero in section 94б, the near field at 13.56 MHz in section 89.5.

Sources: RM0008 (DocID13902 Rev 17): APB2 runs at full speed up to 72 MHz, p. 48; the SPI1EN bit, p. 112; SPI pin configuration, table 25, p. 166; SPI1 without remap is NSS/PA4, SCK/PA5, MISO/PA6, MOSI/PA7, p. 185; SPI_CR1: BR[2:0], CPOL, CPHA, pp. 742–743; the BSY, TXE, RXNE flags, pp. 745–746. DocID13587 Rev 17 (STM32F103x8/xB): PA4–PA7 and PB0 are not 5 V tolerant, p. 29. The project has no MFRC522 document and no ISO 14443 standard: the register addresses, bits and commands of the chip are taken from the manufacturer's documentation and not checked, the card commands are the well-known ones. All numbers below come from a run of the driver _verify/rfid18e/rfid18e.c on a computer (gcc 12.2.0) with our own model of the MFRC522 and the cards in Python 3.11; the firmware is built by armclang 6.24 for the Cortex-M3.

What it is called in the documentation

Original Meaning
PCD — proximity coupling device reader
PICC — proximity card card, key fob, tag
UID — unique identifier card number, 4, 7 or 10 bytes
REQA / WUPA request idle cards / wake up halted ones too
ATQA — answer to request answer to the request: 2 bytes
anticollision, cascade level anticollision, cascade level
BCC — block check character XOR of the four UID bytes
SAK — select acknowledge answer to select: the card type
HLTA put the card to sleep
sector trailer sector trailer: keys and access bits
Transceive the "transmit and receive" command

Top: the RC522 module connected to the STM32F103: SPI1 without remap in mode 0 at 4.5 MHz, PA4 is SDA, that is chip select, PA5 is SCK, PA6 is MISO, PA7 is MOSI, PB0 is RST; the module with the MFRC522 chip is powered from 3.3 V, the antenna is a coil on the board, in the 13.56 MHz field there are card A with a 4-byte UID and card B with a 7-byte UID; pins PA4–PA7 and PB0 are not 5 V tolerant, the antenna is off after reset. Middle: the SPI address byte: bit 7 is read or write, bits 6:1 are the register address, bit 0 is zero; reading VersionReg 0x37 starts with the byte 0xEE, writing CommandReg 0x01 with the byte 0x02. Bottom: the exchange with one card: REQA 26 of 7 bits, answer ATQA 04 00; anticollision 93 20, answer UID 3A 9F 12 C4 and BCC 73, with two cards a collision at bit 23; SELECT 93 70 with UID, BCC and CRC 28 C1, answer SAK 08 is MIFARE Classic 1K, SAK 04 means cascade 2 follows; authentication 60 04 with key A, with a wrong key silence for 25 ms; READ 30 04 gives 16 bytes and CRC_A; HLTA 50 00 57 CD: the card stays silent, a 25 ms timeout is the success


18г.1 The RC522 module

The module board carries the MFRC522 chip, a crystal, matching parts and the antenna: a coil etched along the edge of the board. The chip itself creates the 13.56 MHz field, modulates it with commands and decodes the card's answer per ISO 14443A. The air rate is 106 kbit/s: a bit lasts 128 carrier periods, 128 / 13.56 MHz = 9.44 µs. The card has no battery of its own: it is powered by the reader's field (section 89.5), so the module reads at centimetres.

The chip has three host interfaces: SPI, I²C and UART; RC522 modules usually wire up SPI, and that is what we use. Inside there are registers with a six-bit address, up to 64 of them, a 64-byte FIFO, a timer, a CRC coprocessor and a Crypto1 cipher block for MIFARE Classic cards. The firmware puts a frame into the FIFO and gives a command; the chip sends it, waits for the answer and puts it into the same FIFO.

The chip version is in the VersionReg register (0x37). The driver accepts 0x91 and 0x92, versions 1.0 and 2.0 per the manufacturer's documentation (not checked); the model answers 0x92. If it reads 0x00 or 0xFF, there is no SPI link.


18г.2 Connecting to the F103: SPI1, address byte, reset, supply

SPI1 without remap sits on PA4–PA7 (p. 185). The module's SDA pin is chip select; we drive it ourselves as a plain output on PA4 (software CS, as in section 18.4). RST goes to PB0. SPI1 is on APB2, which runs at full speed, 72 MHz (p. 48); the divider 16 (BR = 011, p. 743) gives 4.5 MHz. Mode 0: CPOL = 0, CPHA = 0, MSB first; this is the MFRC522 mode per the manufacturer's documentation, not checked. The chip has its own SPI frequency limit, but there is no document: 4.5 MHz is taken with margin.

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 вверх — конец транзакции */
}

The address byte. The first byte of every transaction is the register address, but not as is: bit 7 is read (1) or write (0), bits 6:1 are the address, bit 0 is zero. The address is shifted left by 1. Reading VersionReg (0x37) starts with the byte 0xEE, writing to CommandReg (0x01) with the byte 0x02. On a read the data comes with the second byte: while the master sends the address, the slave does not yet know what to answer. The bench model checks bit 0 of every address byte: over the whole run there was not a single such byte.

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 — мусор, данные приходят со вторым */
}

A byte at 4.5 MHz takes 1.778 µs, a register read is two bytes, 3.56 µs.

Reset and setup. rc_init pulls RST low for 1 ms, waits 50 ms, issues the SoftReset command and reads the version. Then comes the timer that limits the wait for the card's answer: TPrescaler = 0xA9 gives 13.56 MHz / (2 · 169 + 1) = 40 kHz, reload 1000 gives 25.025 ms (the formula is per the manufacturer's documentation, not checked; the model computes by it). ModeReg = 0x3D sets the CRC start value 0x6363. The last step is turning the antenna on: after reset TxControlReg is 0x80, the antenna pins are off, after rc_init it is 0x83. In the model the whole setup is 12 SPI transactions, 24 bytes and 51.04 ms, almost all of it the wait after reset.

Supply is 3.3 V. The MFRC522 is a 3.3 V chip, and the F103 pins PA4–PA7 and PB0 are not 5 V tolerant either (p. 29). The module's 3.3V pin goes to the board's 3.3 V, not to 5 V.


18г.3 Registers, FIFO, Transceive and CRC

An exchange with the card is one function. It stops the chip, clears the flags, flushes the FIFO, puts the frame there, writes to BitFramingReg how many bits are in the last byte and from which bit to store the answer, issues the Transceive command and the StartSend bit. Then it polls ComIrqReg: receive (0x20), idle (0x10) or timer (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;
}

The FIFO is written and read in one transaction: on a write, one address byte and then all the data in a row; on a read, the FIFODataReg address as many times as there are bytes to read, and a zero at the end.

Polling costs more than the data. An empty field: REQA goes out, no one answers, and the driver reads ComIrqReg 7063 times until the timer says "silence": 25.141 ms and 14 142 bytes over SPI. With a card REQA takes 102 polls and 0.407 ms. Polling can be replaced by an interrupt: the MFRC522 has an IRQ pin, but this driver does not use it.

CRC_A. The SELECT, READ and HLTA frames end with two CRC_A bytes: the polynomial x¹⁶ + x¹² + x⁵ + 1, start value 0x6363, low byte first. For outgoing frames the driver calls the chip's coprocessor (the CalcCRC command); the card's answer it checks with its own function crc_a, with no extra SPI transactions. The check in the run: the coprocessor (in the model, a bitwise calculation in Python) and crc_a in C gave the same on the SELECT frame (28 C1), HLTA 50 00 (57 CD), READ 30 04 (26 EE), 60 04 (D1 3D), 00 00 (A0 1E) and 12 34 (26 CF), and on 200 random frames from 1 to 18 bytes long, on all 200. The CRC over a frame together with its CRC gives 0; that is how a received frame is checked. This is the self-consistency of three calculations, not a check against the standard: it is not on the disk.


18г.4 Finding a card: REQA, anticollision, SELECT, SAK

REQA and ATQA. The search starts with a short 7-bit frame: REQA (26) wakes the cards that lie idle in the field. Each answers with the two bytes of ATQA; card A in the model gives 04 00. WUPA (52) also wakes the cards put to sleep by HLTA.

Anticollision. Next the reader sends 93 20: "cascade 1, I know 0 bits of your UID, answer". The byte 20 is NVB: the high nibble is how many whole bytes are in the frame (2: the command itself and NVB), the low nibble is how many more bits. One card answers with 40 bits: 4 UID bytes and BCC, the XOR of those four bytes. Card A has 3A 9F 12 C4, BCC 73. The driver checks BCC and sends SELECT: 93 70, the UID, BCC and CRC 28 C1; the card answers with SAK 08 and a CRC. The whole procedure for one card in the model is 558 SPI transactions, 1137 bytes, 2.021 ms.

Two cards. With two cards in the field they answer at the same time, and where their bits differ the chip sees a collision and writes its position into CollReg. The UIDs 3A 9F 12 C4 and 3A 9F 52 C4 differ in bit 23, and the model reported exactly that. The driver sets this bit to 1, counts 23 bits as known and sends them: 93 47 3A 9F 52, 39 bits, NVB 47: 4 whole bytes and 7 bits. Now only the card with a one in bit 23 answers, and its answer goes into the FIFO from bit 7: that is the alignment field in 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;
    }

The driver puts the selected card to sleep with HLTA and sends REQA again: now only the second one answers. The scan of two cards found 3A 9F 52 C4 first, then 3A 9F 12 C4, with one collision, in 81.675 ms. Of that, 76.0 ms are three waits of 25 ms: the card does not answer HLTA, and the "success" here is a timeout, while the last REQA waits to see that no one is left. After the scan both cards sleep: REQA gets silence, WUPA gets ATQA 04 00.

Cascades. A UID is 4, 7 or 10 bytes, while an anticollision frame carries 4. A card with a 7-byte UID sends in cascade 1 the CT byte (88) and three UID bytes, SAK 04 means "the UID is not complete", and the driver repeats it all with 95, cascade 2. Card B (04 52 8A 1B 3C 5D 80) gave 88 04 52 8A, BCC 54, SAK 04, then 1B 3C 5D 80, BCC FA, SAK 00. With cards A and B together there were two collisions: in ATQA (04 00 and 44 00 differ in bit 7) and in cascade 1, at bit 2, where 88 meets 3A. The driver lets a collision in ATQA pass: the anticollision will sort out the UID anyway.

SAK tells what the card is: 08 is MIFARE Classic 1K, 00 is a card without a cipher such as MIFARE Ultralight (well-known values, not checked against a document). In the model card B is such a card: it gives pages 4–7 to the READ command without any keys, and authentication on it ends with a timeout.


18г.5 MIFARE Classic: sector, block, key A, reading

The memory of a MIFARE Classic 1K is 16 sectors of 4 blocks of 16 bytes, 1024 bytes. Block 0 is the factory block: UID, BCC, SAK, ATQA. The last block of each sector is the trailer: key A (6 bytes), access bits (4 bytes), key B (6 bytes). A block can be read only after authentication in its sector.

Authentication is done by the chip itself, with the MFAuthent command: the FIFO gets 60 (key A), the block number, 6 key bytes and the last 4 bytes of the UID. If the key fits, the MFCrypto1On bit rises in Status2Reg, and from then on the exchange with the card is encrypted; the firmware does not see it, the chip encrypts and decrypts by itself. The key FF FF FF FF FF FF is the factory one, well known; new cards are usually sold with it.

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;
}

In the model: authentication in block 4 takes 1.993 ms, reading block 4 takes 2.107 ms, 582 SPI transactions and 1185 bytes; the block holds the 16 bytes Hello, STM32F103. The trailer of the same sector (block 7) reads like this: zeros in place of key A (key A cannot be read from the card), then the access bits FF 07 80 69 and key B (the model's behaviour follows what is described for the cards, not checked). Block 0 from another sector the card did not give: instead of 18 bytes came 4 bits of refusal. With a wrong key the card falls silent, a timeout of 25.424 ms, and after that does not answer READ either: it is woken again with WUPA, selected and authenticated anew.


18г.6 Why this is not security

The MIFARE Classic key is 48 bits, the Crypto1 cipher is NXP's own algorithm. It was reconstructed and published long ago, and weaknesses were found both in the cipher itself and in the card's random number generator; since then there are known ways to get the sector keys with the card in hand or by eavesdropping on the exchange. How exactly is not the topic of this section. The conclusion for the firmware is simple: MIFARE Classic and the RC522 are not fit for security: a lock, an access pass, money.

Weaker still is the scheme "let in by UID". The card gives its UID to any reader before any authentication; in 18г.4 it came in the very first answer. Cards with a rewritable block 0 are on sale, and a UID can be copied onto them. A UID is a number for identification, not a password.

For inventory, toys, picking a profile or "tap the fob to switch on the lamp" the RC522 is good. Security needs cards with proven strong cryptography and readers that support it; on cryptography in a microcontroller, see section 43.


18г.7 Pitfalls

The version reads as 0x00 or 0xFF. There is no SPI link: the wrong mode, MISO and MOSI swapped, SDA not going low, or the module unpowered. The driver goes no further in this case.

The module on 5 V. The MFRC522 is a 3.3 V part, the F103 pins PA4–PA7 and PB0 are not 5 V tolerant (p. 29). Power the module from 3.3 V.

A card is never found. After reset the antenna is off: TxControlReg = 0x80. In the model without turning it on REQA ends with a timeout, the scan finds 0 cards; after turning it on (0x83) the same card is found in 53.024 ms.

Long SPI wires. Wires tens of centimetres long on a breadboard give bit errors. In the model each MISO bit flips with probability p, 200 block reads each:

p CRC check correct CRC error other errors wrong data
10⁻⁴ yes 159 4 37 0
10⁻⁴ no 165 0 33 2
10⁻⁵ yes 195 0 5 0
10⁻⁵ no 195 0 5 0

A block read is 9480 bits over SPI, of which 144 are useful (the 18 bytes of the answer): the rest is polling and service registers, and most errors land there. The CRC of the answer checks the data over the whole path, in the air and on the wires: with the check wrong data is 0, without it 2 out of 200. The cure is shorter wires and a lower SPI frequency.

Two cards in the field. Both answer at once. Without anticollision the driver would get a mix of bits; with it one card is selected, and the others are found after HLTA (18г.4).

HLTA "does not work". The card does not answer HLTA: the driver waits for the timeout and counts it as success. Each HLTA takes 25.454 ms.

The card no longer answers after a wrong key. It has left the selected state: WUPA, SELECT and authentication anew.


18г.8 Summary

Full sources

rfid18e.h — MFRC522 registers and commands, card commands, the driver interface.

/* 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 — the driver without STM32 registers: shared by the firmware and the bench.

/* 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 firmware: SPI1, RST, output to 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);
    }
}

Next: section 18д "NFC module PN532: frames over I2C/SPI/UART, ACK, reading the UID and NTAG2xx, emulation and the difference from RC522" — the same 13.56 MHz band, but a chip with its own firmware: commands instead of registers, three buses to choose from and NTAG tags with NDEF.

Open in the interactive handbook