RFID und Arduino

In den folgenden beiden Experimenten werden passive NFC-Transmitter mit dem RFID-Modul RC522 und dem Arduino auslesen und steuern eine programmierte Schaltung.

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

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

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, http://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;
}

Weiterführende Links

zurück