Arduino

RFID-модуль: считывание карт и брелоков

Введение

RFID-модуль (обычно на чипе RC522) считывает уникальный идентификатор бесконтактных карт и брелоков на расстоянии нескольких сантиметров — классическое применение: системы контроля доступа, простая авторизация. В статье разберём считывание UID карты через библиотеку MFRC522 и базовую логику проверки доступа.

Концепция

RC522 общается с Arduino через интерфейс SPI (в отличие от ранее разобранного I2C для дисплея) — каждая бесконтактная карта/брелок имеет уникальный идентификатор (UID), считываемый модулем при приближении карты в зону действия антенны. Простейшая логика контроля доступа — сравнение считанного UID с заранее известным, разрешённым значением (или списком значений), что и демонстрируется в примере.

Пример кода

// firmware/src/main.cpp — считывание UID карты и простая проверка доступа
#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 10
#define RST_PIN 9

MFRC522 rfid(SS_PIN, RST_PIN);

// Разрешённый UID — определяется заранее через диагностический вывод UID каждой имеющейся карты
byte allowedUid[4] = {0xDE, 0xAD, 0xBE, 0xEF};

void setup()
{
    Serial.begin(9600);
    SPI.begin();
    rfid.PCD_Init();
    delay(100);
    Serial.println("READY");
}

void loop()
{
    if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
        return; // нет новой карты в зоне действия — обычная, частая ситуация, не ошибка
    }

    // Формирование читаемой шестнадцатеричной строки UID для отправки и логирования
    String uidString = "";
    for (byte i = 0; i < rfid.uid.size; i++) {
        if (rfid.uid.uidByte[i] < 0x10) uidString += "0";
        uidString += String(rfid.uid.uidByte[i], HEX);
    }
    uidString.toUpperCase();

    Serial.print("RFID:UID:");
    Serial.println(uidString);

    const bool isAuthorized = checkUidMatch(rfid.uid.uidByte, rfid.uid.size);
    Serial.println(isAuthorized ? "RFID:ACCESS_GRANTED" : "RFID:ACCESS_DENIED");

    rfid.PICC_HaltA(); // завершение взаимодействия с этой картой
}

bool checkUidMatch(byte *scannedUid, byte size)
{
    if (size != 4) return false;
    for (byte i = 0; i < 4; i++) {
        if (scannedUid[i] != allowedUid[i]) return false;
    }
    return true;
}

Пояснения к коду

rfid.PICC_IsNewCardPresent() и rfid.PICC_ReadCardSerial() — двухшаговая проверка, типичная для библиотеки MFRC522: сначала проверяется сам факт присутствия новой карты в зоне действия, и только затем выполняется попытка реально прочитать её серийный номер — оба вызова могут не дать результата в подавляющем большинстве итераций loop() (поскольку карта приближается к считывателю далеко не каждую миллисекунду), и в этом случае функция просто возвращается без какого-либо вывода, что является нормальным, ожидаемым поведением, а не ошибкой.

Формирование шестнадцатеричной строки UID требует ручного добавления ведущего нуля для байтов меньше 0x10 ("0" перед String(byte, HEX)) — без этой коррекции UID вида 0x05 отображался бы как один символ "5" вместо ожидаемых двух символов "05", что могло бы сделать визуально разные UID неотличимыми друг от друга при выводе (например, UID {0x05, 0xAB} и {0x5A, 0xB} без явного дополнения нулём могли бы дать одинаковую итоговую строку при определённом сочетании байтов).

Подводные камни

  • Подключение RC522 через SPI требует именно тех пинов, которые аппаратно поддерживают SPI на конкретной плате Arduino (на Uno это фиксированные пины 11, 12, 13 для MOSI/MISO/SCK, плюс настраиваемый SS) — в отличие от I2C, где адрес устройства программно настраиваемый, и от обычных цифровых пинов, свободно выбираемых почти произвольно, SPI-подключение менее гибко в выборе конкретных пинов, что нужно учитывать при планировании общей схемы подключения нескольких модулей одного проекта.
  • Хранение разрешённого UID непосредственно в виде константы в коде прошивки (allowedUid) означает, что добавление новой разрешённой карты требует перекомпиляции и повторной загрузки всей прошивки — для практичной системы контроля доступа, предполагающей администрирование списка разрешённых карт без постоянного перепрограммирования устройства, список разрешённых UID лучше хранить во внешней, изменяемой памяти (EEPROM Arduino или, что особенно логично в контексте этого цикла, в базе данных на стороне Qt-приложения, к которому прошивка просто пересылает считанный UID для проверки, как показано через сообщение RFID:UID:).
  • Дребезг повторного считывания одной и той же карты, оставленной в зоне действия считывателя на продолжительное время — без явного rfid.PICC_HaltA() (или дополнительной логики предотвращения повторной обработки той же карты, если она физически не была убрана из зоны считывания) одна и та же карта может быть зафиксирована как множественные отдельные считывания за короткий промежуток времени, что может быть нежелательным для логики контроля доступа, ожидающей одно событие на одно реальное приближение карты.
  • Безопасность простого сравнения UID для серьёзных систем контроля доступа недостаточна — UID многих бесконтактных карт (особенно дешёвых, без криптографической защиты) можно скопировать на специальное устройство-клон, и для проекта, где безопасность реально важна (а не только демонстрационный учебный пример), простое сравнение UID — заведомо слабый механизм защиты, не годящийся для реальной защиты ценного имущества; для подлинно защищённых применений нужны RFID-карты с криптографической аутентификацией и соответствующие, более сложные протоколы проверки.