RFID und Arduino

RFID-Logo

In den folgenden Experimenten werden passive NFC-Transmitter mit den RFID-Modulen RC522 und PN532 und einem Arduino ausgelesen und steuern eine programmierte Schaltung.

Funktionsweise von RFID

RFID

RFID (radio-frequency identification) funktioniert mittels Radiowellen. Es gibt ein breites Spektrum an Chips und Lesegeräten, die sich hauptsächlich in Speicherkapazität, Herstellverfahren, Kosten, Frequenzbereich und in ihrer Reichweite unterscheiden.

Frequenzbereiche

NFC

NFC (= "near field communication" oder "Nahfeldkopplung") basiert auf der RFID Technologie und zeichnet sich durch ein spezielles Kopplungsverfahren aus, dass in einem Standard genormt ist (ISO 14443, 18092, 21481). Dabei ist neben dem Kopplungsverfahren auch der Frequenzbereich festgelegt. (135 kHz; 13,56 MHz nach ISO 18000-2, -3; 22536).
Bei NFC beträgt die normierte Frequenz 107,7 MHz.

Standards

Im Hobbybereich werden häufig die beiden Standards "EM4100" für den Langwellen-Bereich und "Mifare classic" für den Kurzwellen-Bereich verwendet.

PICC & Token

Als PICC (engl. "proximity integrated circuit card") wird eine kontaktlose Chipkarte (hier mit RFID-Technologie) bezeichnet, die in unmittelbarer Nähe eines Kartenlesegerätes beispielsweise für Zahlungszwecke benutzt werden kann.
Token, (Chip)coins oder (Chip-)Marken sind mehr oder weniger münzenförmige Transponder, die oft auch als Schlüsselanhänger angeboten werden.

Verwendete Bauteile

RFID-Modul RC522

RFID-Modul RC522
Abb.: RFID-Modul RC522 (13,56MHz) mit gelieferten NFC-Tags (Schlüsselanhänger und Karte)

Anschlüsse und Aufbau

Der folgende Schaltungsaufbau bezieht sich auf beide der folgenden Experimente, d.h. für das erste Experiment können die LEDs mit ihren Vorwiderständen weggelassen werden.

Anschlüsse des RC522
Abb.: Anschlüsse des RC522

Die Pin-Belegung auf dem Arduino Uno ist für das SPI-Protokoll festgelegt und muss evtl. angepasst werden, wenn ein anderer Mikrocontroller verwendet wird.

RC522 Arduino
GND GND
3,3V 3,3V
SDA (SS) Pin 10
SCK Pin 13
MOSI Pin 11
MISO Pin 12
RST Pin 9
Aufbau der Schaltung
Abb.: Aufbau der Schaltung

Zugriff auf den Transponder

In diesem und den folgenden Experimenten wird die Library miguelbalboa/rfid verwendet.
Um die Funktionalität des RFID-Lesegerätes und des oder der Transponder zu testen, bietet sich an das Beispiel DumpInfo dieser Library zu verwenden, welches mitgeliefert wird.

Ausgabe des Resultats in der seriellen Konsole
Abb.: Ausgabe des Resultats in der seriellen Konsole

Auslesen der UID des NFC-Tags

Im diesem Versuch lesen wir die ID eines erkannten NFC-Tags aus und zeigen diese in der seriellen Konsole an.

#include <SPI.h>
#include <MFRC522.h>

#define PIN_RESET  9 // SPI Reset Pin
#define PIN_SS    10 // SPI Slave Select Pin

MFRC522 mfrc522(PIN_SS, PIN_RESET);

void setup()
{
    Serial.begin(9600);
    SPI.begin();
    mfrc522.PCD_Init();
}

void loop()
{
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
        Serial.print("Detected UID:");
        for (byte i = 0; i < mfrc522.uid.size; i++) {
        Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0x0" : " 0x");
        Serial.print(mfrc522.uid.uidByte[i], HEX);
    }
    Serial.println();

    mfrc522.PICC_HaltA();
    delay(1000);
    }
}
Ausgabe des Resultats in der seriellen Konsole
Abb.: Ausgabe des Resultats in der seriellen Konsole

Ändern der UID eines Transponders

Mit Hilfe der oben verwendeten Library kann man auch die auf dem Transponder eingestellte UID verändern. In diesem Beispiel geht das nur mit einem MIFARE-kompatiblen Transponder, auf dem die UID überschreibbar ist.

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN  9
#define SS_PIN  10

// New customizable UID for the transponder:
const byte newUid[4] = {0xDE, 0xAD, 0xBE, 0xEF};

MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

void setup()
{
  Serial.begin(9600);
  SPI.begin();
  mfrc522.PCD_Init();

  // Prepare key with factory settings (FFFFFFFFFFFFh)
  for (byte i=0; i<6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

void loop()
{
  if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
    delay(500);
    return;
  }

  // Reading and printing current UID
  Serial.print("Currrent UID: ");
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0x0" : " 0x");
    Serial.print(mfrc522.uid.uidByte[i], HEX);
  }
  Serial.println();

  // Writing new UID to transponder
  if (mfrc522.MIFARE_SetUid(newUid, (byte)4, true)) {
    Serial.println("Wrote new UID");
  }

  delay(1000);
}

Steuerung einer Schaltung durch NFC-Tags

In diesem Versuch lassen wir in Abhängigkeit von zwei verschiedenen NFC-Tag jeweils eine anders farbige LED aufleuchten. Somit "erkennt" unsere Logik die richtigen NFC-Tags.

#include <SPI.h>
#include <MFRC522.h>

#define PIN_RESET  9  // SPI Reset Pin
#define PIN_SS    10  // SPI Slave Select Pin

#define PIN_LED_BLUE 6
#define PIN_LED_RED  7


MFRC522 mfrc522(PIN_SS, PIN_RESET);
bool isBlue, isRed;

// insert the IDs of your personal NFC tags
byte uidBlue[] = {0x16, 0xE0, 0xC1, 0x49};
byte uidRed[]  = {0x3A, 0x56, 0xDA, 0x29};

void setup()
{
    SPI.begin();
    mfrc522.PCD_Init();

    pinMode(PIN_LED_BLUE, OUTPUT);
    pinMode(PIN_LED_RED, OUTPUT);
}

void loop()
{
    // PICC = proximity integrated circuit card
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
        isBlue = true;
        isRed = true;
        for (byte i=0; i<4; i++) {
            if (mfrc522.uid.uidByte[i] != uidBlue[i]) {
                isBlue = false;
            }
            if (mfrc522.uid.uidByte[i] != uidRed[i]) {
                isRed = false;
            }
        }

        if (isBlue) {
            digitalWrite(PIN_LED_BLUE, HIGH);
        }
        if (isRed) {
            digitalWrite(PIN_LED_RED, HIGH);
        }

        mfrc522.PICC_HaltA();
        delay(1000);

        digitalWrite(PIN_LED_BLUE, LOW);
        digitalWrite(PIN_LED_RED, LOW);
    }
}

Lesen und Schreiben der Daten eines Transponders

Dieses Experiment liest und schreibt Daten eines Transponders. Somit können je nach Speichergröße des Transponders 512Byte, 1kByte oder mehr Daten verarbeitet werden.
Hier wird am Besten das Beispiel ReadAndWrite verwendet.

Ausgabe des Resultats in der seriellen Konsole
Abb.: Ausgabe des Resultats in der seriellen Konsole

Auslesen der Daten mit einem RDM630/RDM6300 RFID reader

Der folgende Sketch stammt von mschoeffler.de und setzt einen RDM630/RDM6300 RFID reader als Lesegerät voraus:

// (c) Michael Schoeffler 2018, https://www.mschoeffler.de
#include <SoftwareSerial.h>

const int BUFFER_SIZE = 14; // RFID DATA FRAME FORMAT: 1byte head (value: 2), 10byte data (2byte version + 8byte tag), 2byte checksum, 1byte tail (value: 3)
const int DATA_SIZE = 10; // 10byte data (2byte version + 8byte tag)
const int DATA_VERSION_SIZE = 2; // 2byte version (actual meaning of these two bytes may vary)
const int DATA_TAG_SIZE = 8; // 8byte tag
const int CHECKSUM_SIZE = 2; // 2byte checksum

SoftwareSerial ssrfid = SoftwareSerial(6,8);

uint8_t buffer[BUFFER_SIZE]; // used to store an incoming data frame
int buffer_index = 0;

void setup() {
 Serial.begin(9600);

 ssrfid.begin(9600);
 ssrfid.listen();

 Serial.println("INIT DONE");
}

void loop() {
  if (ssrfid.available() > 0){
    bool call_extract_tag = false;

    int ssvalue = ssrfid.read(); // read
    if (ssvalue == -1) { // no data was read
      return;
    }

    if (ssvalue == 2) { // RDM630/RDM6300 found a tag => tag incoming
      buffer_index = 0;
    } else if (ssvalue == 3) { // tag has been fully transmitted
      call_extract_tag = true; // extract tag at the end of the function call
    }

    if (buffer_index >= BUFFER_SIZE) { // checking for a buffer overflow (It's very unlikely that an buffer overflow comes up!)
      Serial.println("Error: Buffer overflow detected!");
      return;
    }

    buffer[buffer_index++] = ssvalue; // everything is alright => copy current value to buffer

    if (call_extract_tag == true) {
      if (buffer_index == BUFFER_SIZE) {
        unsigned tag = extract_tag();
      } else { // something is wrong... start again looking for preamble (value: 2)
        buffer_index = 0;
        return;
      }
    }
  }
}

unsigned extract_tag() {
    uint8_t msg_head = buffer[0];
    uint8_t *msg_data = buffer + 1; // 10 byte => data contains 2byte version + 8byte tag
    uint8_t *msg_data_version = msg_data;
    uint8_t *msg_data_tag = msg_data + 2;
    uint8_t *msg_checksum = buffer + 11; // 2 byte
    uint8_t msg_tail = buffer[13];

    // print message that was sent from RDM630/RDM6300
    Serial.println("--------");

    Serial.print("Message-Head: ");
    Serial.println(msg_head);

    Serial.println("Message-Data (HEX): ");
    for (int i = 0; i < DATA_VERSION_SIZE; ++i) {
      Serial.print(char(msg_data_version[i]));
    }
    Serial.println(" (version)");
    for (int i = 0; i < DATA_TAG_SIZE; ++i) {
      Serial.print(char(msg_data_tag[i]));
    }
    Serial.println(" (tag)");

    Serial.print("Message-Checksum (HEX): ");
    for (int i = 0; i < CHECKSUM_SIZE; ++i) {
      Serial.print(char(msg_checksum[i]));
    }
    Serial.println("");

    Serial.print("Message-Tail: ");
    Serial.println(msg_tail);

    Serial.println("--");

    long tag = hexstr_to_value(msg_data_tag, DATA_TAG_SIZE);
    Serial.print("Extracted Tag: ");
    Serial.println(tag);

    long checksum = 0;
    for (int i = 0; i < DATA_SIZE; i+= CHECKSUM_SIZE) {
      long val = hexstr_to_value(msg_data + i, CHECKSUM_SIZE);
      checksum ^= val;
    }
    Serial.print("Extracted Checksum (HEX): ");
    Serial.print(checksum, HEX);
    if (checksum == hexstr_to_value(msg_checksum, CHECKSUM_SIZE)) { // compare calculated checksum to retrieved checksum
      Serial.print(" (OK)"); // calculated checksum corresponds to transmitted checksum!
    } else {
      Serial.print(" (NOT OK)"); // checksums do not match
    }

    Serial.println("");
    Serial.println("--------");

    return tag;
}

long hexstr_to_value(char *str, unsigned int length) { // converts a hexadecimal value (encoded as ASCII string) to a numeric value
  char* copy = malloc((sizeof(char) * length) + 1);
  memcpy(copy, str, sizeof(char) * length);
  copy[length] = '\0';
  // the variable "copy" is a copy of the parameter "str". "copy" has an additional '\0' element to make sure that "str" is null-terminated.
  long value = strtol(copy, NULL, 16);  // strtol converts a null-terminated string to a long value
  free(copy); // clean up
  return value;
}

RFID-Modul PN532

PN532 ist ein NFC-Controller von NXP, der auf dem 80C51-Mikrocontroller basiert und kontaktlose Kommunikation bei 13,56 MHz ermöglicht. Darüber hinaus ermöglicht die Unterstützung für MIFARE Classic 1K/MIFARE Classic 4K-Karten höhere Übertragungsgeschwindigkeiten von bis zu 424 kbit/s in beiden Richtungen.

RFID-Modul PN532
Abb.: RFID-Modul PN532 (13,56MHz) mit gelieferten NFC-Tags (Schlüsselanhänger und Karte)

Wichtige Features laut Hersteller

Anschlüsse und Aufbau

Anschlüsse des PN532
Abb.: Anschlüsse des PN532

Das PN532 verfügt mit SPI und I²C über zwei verschiedene Kommunikations-Schnittstellen, die mit switches (I0 und I1)auf dem Board eingestellt werden können:

I0 I1
HSV 0 0
I²C 1 0
SPI 0 1

Die Einstellung "HSV" steht für "High-Speed Variation" und ermöglicht es, die maximale Übertragungsgeschwindigkeit auf bis zu 848 kBit/s zu erhöhen. Diese Einstellung ist nur für NFCIP-1-Modus (Near Field Communication over the Internet Protocol) verfügbar und erfordert eine entsprechende Anpassung der Antennenabstimmung und der Stromversorgung.
Hinweis: Im Folgenden wird nur die I²C-Schnittstelle verwendet.

PN532 Arduino Uno
GND GND
VCC 5V
SDA A4
SCL A5

Auslesen von RFID-Tags mit dem PN532

Für den folgenden Sketch wird die Library adafruit/Adafruit-PN532 verwendet, wobei der Beispiel-Sketch "readMifare" angepasst und vereinfacht wurde:

#include <Wire.h>
#include <Adafruit_PN532.h>

#define PN532_IRQ   (2)
#define PN532_RESET (3)  // Not connected by default on the NFC Shield

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup()
{
  Serial.begin(9600);

  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Did not find PN53x board");
    while (1);
  }
  Serial.print("Found chip PN5");
  Serial.println((versiondata >> 24) & 0xFF, HEX);
  Serial.print("Firmware ver. ");

  Serial.print((versiondata >> 16) & 0xFF, DEC);

  Serial.print('.');
  Serial.println((versiondata >> 8) & 0xFF, DEC);

  Serial.println("Waiting for an ISO14443A Card...");
}

void loop()
{
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
  uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type)

  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  if (success) {
    Serial.println("Found an ISO14443A card");
    Serial.print("  UID Length: ");
    Serial.print(uidLength, DEC);
    Serial.println(" bytes");
    Serial.print("  UID Value: ");
    nfc.PrintHex(uid, uidLength);
    Serial.println("");

    if (uidLength == 4) {
      // We probably have a Mifare Classic card ...
      Serial.println("Seems to be a Mifare Classic card (4 byte UID)");

      // Now we need to try to authenticate it for read/write access
      // Try with the factory default KeyA: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
      Serial.println("Trying to authenticate block 4 with default KEYA value");
      uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

      // Start with block 4 (the first block of sector 1) since sector 0
      // contains the manufacturer data and it's probably better just
      // to leave it alone unless you know what you're doing
      success = nfc.mifareclassic_AuthenticateBlock(uid, uidLength, 4, 0, keya);

      if (success) {
        Serial.println("Sector 1 (Blocks 4..7) has been authenticated");
        uint8_t data[16];

        success = nfc.mifareclassic_ReadDataBlock(4, data);
        if (success) {
          Serial.println("Reading Block 4:");
          nfc.PrintHexChar(data, 16);
          Serial.println("");
          delay(1000);
        } else {
          Serial.println("Unable to read the requested block.");
        }
      } else {
        Serial.println("Authentication failed.");
      }
    }

    if (uidLength == 7) {
      Serial.println("Seems to be a Mifare Ultralight tag (7 byte UID)");

      Serial.println("Reading page 4");
      uint8_t data[32];
      success = nfc.mifareultralight_ReadPage (4, data);
      if (success)
      {
        nfc.PrintHexChar(data, 4);
        Serial.println("");
        delay(1000);
      } else {
        Serial.println("Unable to read the requested page.");
      }
    }
  }
}

Wenn das Board angeschlossen wurde und ein NFID-Tag in die Nähe des Boards gehalten wird, so sollte eine änhliche Ausgabe in der seriellen Konsole der Arduino-IDE erscheinen:

Ausgabe der seriellen Konsole der Arduino-IDE

Weiterführende Links

zurück