Wat is RFID?

RFID staat voor Radio-frequentie identificatie, ofwel identificatie door middel van radiogolven.
Voorbeelden zijn een kaart die je in bij een deur van een hotelkamer houdt om deze te openen en chips in de oren van vee. Sommige RFID kaarten bevatten een nummer dat wereldwijd uniek is. De fabrikant garandeert dat. Er bestaan echter ook type kaarten, waarbij je dat nummer zelf kunt veranderen. En dat zijn nu juist de kaarten waarmee je leuke projecten kunt maken. Naast plastic kaarten zie je ook RFID sleutelhangers, druppels en zelfs etiketten. Om deze reden wordt in documentatie vaak niet gesproken over een kaart, maar over een tag.
Je hebt nodig: een RFID lezer en bijbehorend kaartje en/of druppel. Er worden verschillende frequenties en systemen gebruikt, die niet uitwisselbaar zijn. De meest gebruikte frequenties zijn 13,56 MHz en 125 kHz. Bij de laatste is een grotere spoel nodig, welke meestal los bij de RFID lezer zit. Aan de druppels en kaarten kun je niet of nauwelijks zien welke van de twee frequenties gebruikt worden. Goed opletten bij de aanschaf dus. Beide systemen werken erg goed. Ik beschrijf eerst de 13,56 versie in twee totaal verschillende uitvoeringen, daarna de 125 kHz versie.

13,65 MHz (RF-ID-RC522) lezer/schrijver

Je kunt vrij goedkoop aan deze lezer/schrijver komen. De bijbehorende kaarten en druppels kosten minder dan 1 euro. Om deze kaartlezer te kunnen gebruiken moet je een bibliotheek installeren, bijvoorbeeld MFRC522.h van Miguel Balboa. Deze kan via het de bibliotheekbeheerder geïnstalleerd worden. De bibliotheek gaat ervan uit dat je de kaartlezer als volgt aansluit op de Arduino Uno: uiteraard GND naar GND en 3,3 V naar 3,3 V; verder RST naar 9; SDA naar 10; MOSI naar 11; MISO naar 12 en SCK naar 13. Op andere Arduino's is het vaak anders. Het werk allemaal vrij goed. De gewone kaarten hebben een identificatiecode van 4 bytes deze is niet gegarandeerd uniek (NUID: Non-Unique Identifier), maar de ultralight of NTAG kaarten gebruiken een wereldwijd unieke code van 7 bytes (UID: Unique Identifier).

RFID opstelling
Links: de Arduino met het leesprogramma; rechts de RFID lezer, de druppel (blauw) en de kaart (wit).

Ik heb een aantal kaarten voor de lezer gehouden. Hieronder zie je een paar resultaten.
Mijn witte kaartMijn druppelMijnID kaart, met UID weggelaktOV
Van links naar rechts: de kaart, de druppel, mijn ID kaart en mijn OV-chipkaart.
BIj de persoonlijke kaarten heb ik de NUID weg gelakt.

Erg leuk voor groepsopdrachten op school: wie krijgt toegang tot welke ruimte?
De kaarten waarmee je je kamerdeur in een hotel opent zijn vaak standaard MIFARE RFID kaarten. Als je het netjes vraagt mag je ze na gebruik bijna altijd meenemen naar huis. Ik testte een kaart van het gr8 hotel in Oosterhout. Deze was perfect te lezen en kan zo gebruikt worden in een project.

Ik testte ook een milieuvriendelijke bamboe kaart van het Van der Valk hotel in Groningen. Dit is een Ultralight variant, met 64 bytes geheugen, waarvan 48 door de gebruiker veranderd mogen worden. Hij was goed te lezen en te beschrijven.

Lezen en schrijven van MIFARE Classic en NTAG kaarten

Ultralight of Ultralight C kaarten zijn verouderd en worden niet meer verder ontwikkeld. Ze zijn redelijk goed te lezen en te schrijven met de NTAG programma's. NTAG kaarten zijn de moderne variant. Ze zijn er met verschillende opslagcapaciteit, van 48 bytes tot 888 bytes bruikbaar. MIFARE Classic kaarten zijn doorgaans 4 kB, waarvan 3440 bytes bruikbaar. Het voorbeeldprogramma ReadNUID, dat je bij de bibliotheek krijgt, geeft geen UID van NTAG kaarten. Met het volgende programma kun je de NUID vinden van beide type kaarten.

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

#define SS_PIN 10
#define RST_PIN 9

MFRC522 rfid(SS_PIN, RST_PIN);

void setup() {
   Serial.begin(115200);
   SPI.begin();
   rfid.PCD_Init();

   Serial.println("RFID reader gestart. Scan een kaart...");
}

void loop() {
   if (!rfid.PICC_IsNewCardPresent()) return; // Geen nieuwe kaart 

   if (!rfid.PICC_ReadCardSerial()) return; // Lezen mislukt

  Serial.print("UID:");

   for (byte i = 0; i < rfid.uid.size; i++) {
      Serial.print(" ");
      if (rfid.uid.uidByte[i] < 0x10) Serial.print("0");
      Serial.print(rfid.uid.uidByte[i], HEX);
   }

   Serial.println();

   // Stop communicatie met kaart
   rfid.PICC_HaltA();
   rfid.PCD_StopCrypto1();

   delay(500);
}

Uitvoer: RFID reader gestart. Scan een kaart... UID: 0A 8D 71 1B UID: 04 63 A7 A2 F5 21 91 De bovenste code is voor een gewone kaart en de onderste van een NTAG of Ultralight kaart.

Lezen en schrijven werkt bij beide kaartypen wezenlijk anders. Je hebt daarom twee verschillende programma's nodig of een gecombineerd programma dat beide protocollen ondersteunt. Wees voorzichtig met het schrijven: niet elke geheugenplaats mag overschreven worden. Doe je dat toch dan kan de kaart onbruikbaar worden. Bij het teruglezen van de kaart kun je het best de "verboden pagina's" overslaan, zodat je alleen de gebruikers-data ziet.
Bij de Ultralight kaart is het geheugen ingedeeld in 16 "pagina's" van elk vier bytes. De pagina's 0 t/m 3 mag je absoluut niet beschrijven.
In de schrijfprogramma;s, zet ik automatisch een ETX code (End of Text) = 0x03 aan het eind van de opgegeven tekst. Dit maakt het teruglezen veel gemakkelijker.

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

#define RST_PIN 9
#define SS_PIN 10
MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

const unsigned int MAX_INPUT_LENGTH = 753; // 752 tekens + \0
char inputString[MAX_INPUT_LENGTH];
unsigned int stringIndex = 0;
int toestand = 0; // 0 = Typen, 1 = Scannen
bool tekstWasTeLang = false;

void setup() {
  Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init();
  for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF; // Standaard fabriekssleutel
  Serial.println(F("=== MIFARE CLASSIC SCHRIJFPROGRAMMA ==="));
  Serial.println(F("Typ of plak een tekst van maximaal 752 tekens..."));
}

void loop() {
  if (toestand == 0) {
    while (Serial.available() > 0) {
      char inChar = Serial.read();
      if (inChar == '\n') {
        inputString[stringIndex] = '\0';
        Serial.print(F("\n--- TEKST ONTVANGEN! ---\nInhoud: ")); Serial.println(inputString);
        Serial.println(F("Houd nu een MIFARE Classic kaart voor de lezer..."));
        toestand = 1; break;
      } else if (stringIndex < MAX_INPUT_LENGTH - 1 && inChar != '\r') {
        inputString[stringIndex++] = inChar;
      }
      // GEFIXT: Als de buffer vol zit maar er komt nog data binnen, zet de vlag!
      else {
        if (inChar != '\r' && inChar != '\n') {
          tekstWasTeLang = true;
        }
      }
    }
  } 
  else if (toestand == 1) {
    if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) { delay(50); return; }
    Serial.println(F("Kaart gedetecteerd! Schrijven starten..."));

    unsigned int tekstLengte = strlen(inputString);
    unsigned int geschrevenTekens = 0;
    bool kaartVol = false;

    // Lus door alle 16 sectoren van de MIFARE Classic 1K
    for (byte sector = 0; sector < 16 && geschrevenTekens < tekstLengte; sector++) {
      byte startBlok = sector * 4; 
      
      // Authenticeer de sector
      if (mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, startBlok, &key, &(mfrc522.uid)) != MFRC522::STATUS_OK) {
        kaartVol = true; break;
      }

      // Lus door de 4 blokken binnen deze sector
      for (byte b = 0; b < 4 && geschrevenTekens < tekstLengte; b++) {
        byte huidigBlok = startBlok + b;

        // BEVEILIGING: Sla fabriekdata (Blok 0) en alle Trailer-blokken (Blok 3) over
        if (huidigBlok == 0 || b == 3) continue; 

        // Vul het 16-byte datablok (met de haakjes er netjes in!)
        byte dataBlok[16] = {0};
        for (byte i = 0; i < 16; i++) {
          if (geschrevenTekens < tekstLengte) dataBlok[i] = inputString[geschrevenTekens++];
          else dataBlok[i] = 0x03; // Vul op met stoptekens
        }

        // Schrijf de 16 bytes naar de kaart
        if (mfrc522.MIFARE_Write(huidigBlok, dataBlok, 16) != MFRC522::STATUS_OK) {
          kaartVol = true; 
          if (geschrevenTekens >= 16) geschrevenTekens -= 16; else geschrevenTekens = 0;
          break;
        }
      }
      if (kaartVol) break; 
    }

    // --- TERUGKOPPELING NAAR DE GEBRUIKER ---
    if (geschrevenTekens < tekstLengte || tekstWasTeLang) {
      Serial.print(F("\n⚠️ WAARSCHUWING: De tekst was te lang voor deze kaart!"));
      Serial.print(F("\nDe tekst is afgekapt tot 752 tekens\n"));  
      inputString[geschrevenTekens] = '\0'; 
      Serial.print(F("Werkelijk geschreven tekst: ")); Serial.println(inputString);
    } 
    else if (kaartVol) {
      Serial.println(F("\n❌ FOUT: Het schrijven is vroegtijdig afgebroken!"));
    } 
    else Serial.println(F("\n✅ SUCCES: De volledige tekst staat op de kaart!"));

    stringIndex = 0; 
    tekstWasTeLang = false;
    toestand = 0; 
    
    mfrc522.PICC_HaltA(); 
    mfrc522.PCD_StopCrypto1();
    
    Serial.println(F("\nTyp of plak opnieuw een tekst..."));
    while (Serial.available() > 0) Serial.read(); 
  }
}

Uitvoer als je "Deze kaart is van Theo" invoert in de Seriële monitor:

=== MIFARE CLASSIC SCHRIJFPROGRAMMA ===
Typ of plak een tekst van maximaal 752 tekens...

Tekst: Deze kaart is van Theo
Houd nu een MIFARE Classic kaart voor de lezer...
Kaart gedetecteerd! Schrijven starten...

✅ SUCCES: De volledige tekst staat op de kaart!

Typ of plak opnieuw een tekst...

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

#define RST_PIN 9
#define SS_PIN 10
MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

void setup() {
  Serial.begin(115200); SPI.begin(); mfrc522.PCD_Init();
  for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF; // Standaard fabriekssleutel
  Serial.println(F("=== MIFARE CLASSIC LEESPROGRAMMA ===\nHoud een kaart voor de lezer..."));
}

void loop() {
  if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) { delay(50); return; }
  
  Serial.print(F("\nInhoud: "));

  for (byte blok = 1; blok < 64; blok++) {
    // Sla de trailer-blokken (3, 7, 11...) over tijdens het lezen
    if ((blok + 1) % 4 == 0) continue; 

    // Authenticeer de sector
    if (mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, blok, &key, &(mfrc522.uid)) != MFRC522::STATUS_OK) goto stop_lezen;

    byte buffer[18]; byte lengte = 18;
    if (mfrc522.MIFARE_Read(blok, buffer, &lengte) == MFRC522::STATUS_OK) {
      // Verwerk alle 16 bytes uit het MIFARE-blok
      for (int i = 0; i < 16; i++) { 
        if (buffer[i] == 0x03) goto stop_lezen; // Stopteken gevonden

        // UNICODE FILTER: Laat normale ASCII (>=32) én UTF-8 (>127) door
        if (buffer[i] >= 32 && buffer[i] != 127) Serial.write(buffer[i]);
      }
    } else {
      goto stop_lezen; 
    }
  }

stop_lezen:
  Serial.println(F("\n--------------------------------------"));
  mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); 
  
  Serial.println(F("Haal de kaart weg om een volgende kaart te kunnen lezen"));
  delay(3000); 
}

Uitvoer: === MIFARE CLASSIC LEESPROGRAMMA === Houd een kaart voor de lezer... Inhoud: Deze kaart is van Theo -------------------------------------- Haal de kaart weg om een volgende kaart te kunnen lezen

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

#define RST_PIN 9 // Pas aan naar jouw RFID pinout
#define SS_PIN 10 // Pas aan naar jouw RFID pinout

MFRC522 mfrc522(SS_PIN, RST_PIN); // RFID instantie

const unsigned int MAX_INPUT_LENGTH = 889; // De grootste waarde die een NTAG kaart kan hebben is 888
char inputString[MAX_INPUT_LENGTH];
unsigned int stringIndex = 0;

int toestand = 0; // 0 = Wachten op tekst via Seriële Monitor; 0 is schrijven RFID kaart

void setup() {
  Serial.begin(115200);
  SPI.begin();
  mfrc522.PCD_Init(); // Initialiseer de RFID lezer
  Serial.println(F("--- NTAG kaart schrijven ---"));
  Serial.println(F("Voer een tekst in van maximaal 880 tekens"));
}

void loop() {

  if (toestand == 0) {
    while (Serial.available() > 0) {
      char inChar = Serial.read();

      if (inChar == '\n') {
        inputString[stringIndex] = '\0'; // String afsluiten

        // Echo de tekst naar de monitor
        Serial.print(F("Tekst: ")); Serial.println(inputString);
        Serial.println(F("------------------------"));
        Serial.println(F("Houd een RFID-kaart voor de lezer om te schrijven..."));
        toestand = 1; // Schakel over naar de RFID-modus
        break;
      }
      else if (stringIndex < MAX_INPUT_LENGTH - 1) {
        if (inChar != '\r') {
          inputString[stringIndex] = inChar;
          stringIndex++;
        }
      }
    }
  }
   else if (toestand == 1) {
    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
      Serial.println(F("Kaart gedetecteerd! Schrijven starten..."));

      unsigned int tekstLengte = strlen(inputString);
      unsigned int geschrevenTekens = 0;
      bool kaartVol = false;

      // GEFIXT: Veranderd naar <= zodat de loop ALTIJD nog één extra pagina pakt voor het stopteken
      for (byte pagina = 4; geschrevenTekens <= tekstLengte; pagina++) {
        
        // Standaard vullen met 0x03 in plaats van spaties
        byte dataBlok[4] = { 0x03, 0x03, 0x03, 0x03 };
        
        for (byte i = 0; i < 4; i++) {
          if (geschrevenTekens < tekstLengte) {
            dataBlok[i] = inputString[geschrevenTekens];
            geschrevenTekens++;
          } else {
            // GEFIXT: Als de letters op zijn, vul de rest van de pagina met stoptekens
            dataBlok[i] = 0x03; 
          }
        }

        MFRC522::StatusCode status = mfrc522.MIFARE_Ultralight_Write(pagina, dataBlok, 4);
        
        if (status != MFRC522::STATUS_OK) {
          kaartVol = true;
          if (geschrevenTekens >= 4) geschrevenTekens -= 4; else geschrevenTekens = 0; 
          break; 
        }

        // Als we de pagina met stoptekens beschreven hebben breken we de loop af
        if (geschrevenTekens >= tekstLengte && dataBlok[3] == 0x03) {
          break;
        }
      }

      if (kaartVol) {
        Serial.println(F("\nWAARSCHUWING: De tekst was te lang voor deze kaart!"));
        Serial.print(F("Alleen de eerste "));
        Serial.print(geschrevenTekens);
        Serial.println(F(" letters zijn opgeslagen."));
        
        inputString[geschrevenTekens] = '\0'; 
        
        Serial.print(F("Werkelijk geschreven tekst: "));
        Serial.println(inputString);
      } else Serial.println(F("\n✅ SUCCES: De volledige tekst staat op de kaart!"));
        
      Serial.println(F("Als je nog een kaart wil schrijven voer dan opnieuw een tekst in"));
      
      stringIndex = 0;
      toestand = 0;      
      
      mfrc522.PICC_HaltA();
      mfrc522.PCD_StopCrypto1();
    }
    
    delay(50); 
  }
}


Uitvoer als je "Deze kaart is van Françoise" invoert in de Seriële monitor:

--- NTAG kaart schrijven ---
Voer een tekst in van maximaal 880 tekens

Tekst: Deze kaart is van Françoise
------------------------
Houd een RFID-kaart voor de lezer om te schrijven...
Kaart gedetecteerd! Schrijven starten...

✅ SUCCES: De volledige tekst staat op de kaart!
Als je nog een kaart wil schrijven voer dan opnieuw een tekst in

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

#define RST_PIN 9
#define SS_PIN 10

MFRC522 mfrc522(SS_PIN, RST_PIN);

void setup() {
  Serial.begin(115200);
  while (!Serial); 
  SPI.begin();
  mfrc522.PCD_Init();
  Serial.println(F("     === NTAG LEESPROGRAMMA ===     "));
  Serial.println(F("Houd een NTAG-kaart voor de lezer..."));
}
void loop() {
  // Wacht rustig totdat er een kaart voor de lezer verschijnt
  while (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
    delay(50); 
  }
  
  Serial.print(F("\nInhoud: "));

  for (int page = 4; page < 48; page++) {
    byte buffer[18]; // Arraygrootte van 18 bytes is verplicht voor de bibliotheek
    byte length = 18;

    MFRC522::StatusCode status = mfrc522.MIFARE_Read(page, buffer, &length);

    if (status == MFRC522::STATUS_OK) {
      for (int i = 0; i < 4; i++) { 
        // Als het end-of-text teken wordt gevonden, stoppen we direct:
        if (buffer[i] == 0x03) goto stop_lezen; 
        if (buffer[i] >= 32 && buffer[i] != 127) Serial.write(buffer[i]);
      }
    } 
    else goto stop_lezen; 
  }

stop_lezen:
  Serial.println(F("\n--------------------------------------"));
  // Sluit de communicatie met de kaart netjes af
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1(); 
  
  Serial.println(F("Haal de kaart weg om een volgende te kunnen lezen..."));
  delay(3000); 
}


Uitvoer:

     === NTAG LEESPROGRAMMA ===     
Houd een NTAG-kaart voor de lezer...

Inhoud: Deze kaart is van Françoise
--------------------------------------
Haal de kaart weg om een volgende te kunnen lezen...
De uitvoer van de programma's is iets anders als de tekst die je schrijft niet in het geheugen past of als er een fout optreedt.
Een paar opmerkingen over de programma's hierboven:

Lezen met je smartphone: NDEF

NDEF is een universele standaard waarmee gegevens zo op een NTAG-kaart of sticker worden geschreven dat elke smartphone ze direct kan lezen en openen. Vrijwel alle moderne smartphones zijn uitgerust met een NFC-lezer. De locatie van deze lezer verschilt per systeem: bij een iPhone zit de antenne helemaal aan de bovenkant, terwijl u een Android-telefoon meestal met het midden van de achterkant tegen de kaart moet houden.

Wat kunt u op een NFC-kaart zetten?

Van al deze optie heb ik alleen de de url getest, met zowel een iPhone als een Samsung smartphone. Het bijbehorende Arduino programma staat hieronder.


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

#define SS_PIN 10
#define RST_PIN 9

MFRC522 mfrc522(SS_PIN, RST_PIN);

// --- HIER STAAT JOUW EXACTE WEBSITE ---
String url = "https://wiskunde.theohupkens.nl";

void setup() {
  Serial.begin(115200); 
  SPI.begin();
  mfrc522.PCD_Init();
  Serial.println(F("Houd de NTAG2xx tag tegen de lezer..."));
}

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

  Serial.println(F("Tag gedetecteerd! NDEF URL genereren en schrijven..."));
  
  byte prefixByte = 0x00; // Door de prefix 0x00 (= geen prefix) wordt de exacte URL verzonden!
  
  int urlLength = url.length();
  int payloadLength = 1 + urlLength;        
  int ndefRecordLength = 4 + payloadLength; 

  if (7 + urlLength + 1 > 144) {
    Serial.println(F("Fout: URL is te lang!"));
    mfrc522.PICC_HaltA();
    return;
  }

  int totalBytes = 7 + urlLength + 1; 
  int paddingNeeded = (4 - (totalBytes % 4)) % 4; 
  int bufferSize = totalBytes + paddingNeeded;

   byte* buffer = (byte*)malloc(bufferSize); 
  memset(buffer, 0, bufferSize); 

  // NDEF headers opbouwen via geheugen-pointers
  *(buffer + 0) = 0x03;               
  *(buffer + 1) = ndefRecordLength;   
  *(buffer + 2) = 0xD1;               
  *(buffer + 3) = 0x01;               
  *(buffer + 4) = payloadLength;      
  *(buffer + 5) = 0x55;               
  *(buffer + 6) = prefixByte;         

  // Kopieer de ALLES letter voor letter (inclusief https://...)
  for (int i = 0; i < urlLength; i++) {
    *(buffer + 7 + i) = (byte) url.charAt(i);
  }

  // Plaats de NDEF Terminator byte (0xFE)
  *(buffer + 7 + urlLength) = 0xFE;

  // Schrijf de Capability Container naar pagina 3
  byte* page3 = (byte*) malloc(4);
  *(page3 + 0) = 0xE1; *(page3 + 1) = 0x10; *(page3 + 2) = 0x12; *(page3 + 3) = 0x00;
  
  if (!writePage(3, page3)) {
    Serial.println(F("Fout bij schrijven naar pagina 3."));
    free(page3);
    free(buffer);
    return;
  }
  free(page3);

  // Schrijf de buffer weg vanaf pagina 4
  int pagesNeeded = bufferSize / 4;
  bool writeSuccess = true;

  for (byte p = 0; p < pagesNeeded; p++) {
    if (!writePage(4 + p, (buffer + (p * 4)))) {
      writeSuccess = false;
      break;
    }
  }

  if (writeSuccess) {
    Serial.println(F("Succes! Jouw website staat nu correct op de NTAG213."));
    Serial.print(F("Geschreven URL: "));
    Serial.println(url);
  } else Serial.println(F("Fout tijdens het wegschrijven."));

  free(buffer);

  mfrc522.PICC_HaltA();
  delay(5000);
  Serial.println(F("\nHoud een volgende tag tegen de lezer..."));
}

bool writePage(byte page, byte* data) {
  MFRC522::StatusCode status = mfrc522.MIFARE_Ultralight_Write(page, data, 4);
  return status == MFRC522::STATUS_OK;
}

Welk type RFID-of -tag kaart heb ik?

Aan de buitenkant zien RFID-kaarten er vaak hetzelfde uit. Dat geldt ook voor sleutelhangers (druppels). Hoe kunt u er achter komen welke kaart u heeft? Dat kan met een programma dat hardware-informatie van de kaart leest. Bij nieuw types kaart werkt dit perfect, maar bij oudere types is het ingewikkelder om het juiste type te vinden. Het programma dat hieronder staat werkt - voor zover ik het heb kunnen testen - goed. Hij vermeldt naast het kaarttype ook hoeveel bytes aan bruikbaar geheugen op de kaart aanwezig is.


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

#define RST_PIN         9
#define SS_PIN          10

MFRC522 mfrc522(SS_PIN, RST_PIN);

void setup() {
  Serial.begin(115200);
  while (!Serial);
  
  SPI.begin();
  mfrc522.PCD_Init();
  
  Serial.println(F("--- Detectie kaarttype gestart ---"));
}

void loop() {
  // Wacht op een kaart
  if ( ! mfrc522.PICC_IsNewCardPresent()) return;
  if ( ! mfrc522.PICC_ReadCardSerial()) return;

  Serial.print(F("\nKaart gescand, UID:"));
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
    Serial.print(mfrc522.uid.uidByte[i], HEX);
  }
  Serial.println();

  // Voer de diepe chiptest uit
  identificeerNTAG();

  // Stop communicatie met huidige kaart
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}

void identificeerNTAG() {
  // Controleer EERST de SAK-code om MIFARE Classic kaarten eruit te filteren
  byte sak = mfrc522.uid.sak;
  
  if (sak == 0x08) {
    Serial.println(F(" -> TYPE: MIFARE Classic 1K (752 bytes bruikbaar geheugen)"));
    return; // Stop hier, stuur geen NTAG-commando's naar een Classic chip
  }
  else if (sak == 0x18) {
    Serial.println(F(" -> TYPE: MIFARE Classic 4K (3440 bytes bruikbaar geheugen)"));
    return;
  }

  // Vanaf hier starten we de normale NTAG / Ultralight controle voor kaarten met SAK 0x00
  MFRC522::StatusCode status;
  
  mfrc522.PCD_WriteRegister(mfrc522.TxModeReg, 0x80); // TX CRC inschakelen
  mfrc522.PCD_WriteRegister(mfrc522.RxModeReg, 0x80); // RX CRC inschakelen
  
  byte cmd[] = {0x60}; 
  byte buffer[18]; 
  byte size = sizeof(buffer);

  status = mfrc522.PCD_TransceiveData(cmd, 1, buffer, &size);

  mfrc522.PCD_WriteRegister(mfrc522.TxModeReg, 0x00);
  mfrc522.PCD_WriteRegister(mfrc522.RxModeReg, 0x00);

  if (status == MFRC522::STATUS_OK) {
    byte storageSize = buffer[6];

    Serial.print(F(" -> TYPE: "));
    switch(storageSize) {
      case 0x0B:
        Serial.println(F("NTAG210 / Ultralight EV1 (48 bytes bruikbaar geheugen)"));
        break;
      case 0x0F:
        Serial.println(F("NTAG213 (144 bytes bruikbaar geheugen)"));
        break;
      case 0x11:
        Serial.println(F("NTAG215 (504 bytes bruikbaar geheugen - o.a. Amiibo)"));
        break;
      case 0x13:
        Serial.println(F("NTAG216 (888 bytes bruikbaar geheugen)"));
        break;
      default:
        Serial.print(F("Onbekende NXP variant (Geheugen ID Byte op index 6 = 0x"));
        Serial.print(storageSize, HEX);
        Serial.println(F(")"));
        break;
    }
  } 
  else if (status == MFRC522::STATUS_TIMEOUT) {
    // Alleen als de SAK 0x00 was EN we krijgen een timeout, is het een echte Ultralight 1e gen
    Serial.println(F(" -> TYPE: Klassieke MIFARE Ultralight (1e Generatie - 48 bytes bruikbaar geheugen)"));
  } 
  else {
    Serial.print(F(" -> FOUT: Communicatiestoring. Status: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
  }
}


Uitvoer:
--- Detectie kaarttype gestart ---

Kaart gescand, UID: 0A 8D 71 1B
 -> TYPE: MIFARE Classic 1K (752 bytes bruikbaar geheugen)

Kaart gescand, UID: 04 63 A7 A2 F5 21 91
 -> TYPE: NTAG210 / Ultralight EV1 (48 bytes bruikbaar geheugen)
 
Kaart gescand, UID: 04 24 E9 E1 FF 61 80
 -> TYPE: NTAG213 (144 bytes bruikbaar geheugen)
 
Kaart gescand, UID: 04 9C FC 5F CA 2A 81
 -> TYPE: NTAG215 (504 bytes bruikbaar geheugen - o.a. Amiibo)

Kaart gescand, UID: 04 11 1E CF 32 03 89
 -> TYPE: NTAG216 (888 bytes bruikbaar geheugen)

PN532 NFC RFID module

Deze module kan drie interfaces gebruiken: High Speed UART (TX en RX), I²C en SPI. Deze zijn via een microschakelaar te kiezen. Het I²C adres is 0x24. Bij aankoop is UART geselecteerd en kun je zo beginnen. Je kunt de bibliotheek PN532_HSU gebruiken, maar die is niet vanuit de IDE te vinden. Kijk op deze website van GitHub. De bibliotheken voor de drie interfaces moet anders worden geïstalleerd dan normaal. Lees goed hoe dat moet op Github. In totaal moet je 5 bibliotheken krijgen. Met deze bibliotheken kun je diverse soorten kaarten lezen. Je kunt ook een Mifare kaart voorzien van NDEF data, zodat je deze met je smartphone kunt uitlezen. Helaas kun je de bibliotheken niet zonder wijzigingen gebruiken omdat de selector (#ifdef 0 of #ifdef 1 aangepast moet worden). Na aanpassen volgens de online manual bevatten de programma's nog veel fouten. Het is storend als voorbeeld programma's niet meteen werken.

Let op: alles is 5 volt tolerant, behalve SPI. Als je SPI wilt gebruiken met een Arduino zet er dan een level converter tussen of gebruik weerstanden. Het is mij met PN533_SPI.h niet gelukt SPI aan de praat te krijgen met de WeMos D1 mini, ook niet met een andere bibliotheek. Met I²C lukt het met de WeMos, maar niet erg stabiel. Met HSU lukt het wel goed. Omdat je dan eigenlijk twee seriële poorten nodig hebt kun je het best SoftwareSerial gebruiken. Hiervoor heb je dan weer een andere PN535 bibliotheek nodig: PN532_SWHSU.h. Ik heb het voorbeeldprogramma hierbij enigszins verbeterd (zie hieronder). Ik heb het getest met een WeMos D1 mini en een Arduino Uno. Bij de Arduino had ik in de sketch D1 en D2 vervangen door 2 en 3 (de nummers van de pinnen waar ik TX en RX op had aangesloten). Ik heb I²C ook getest met een Arduino Uno, met een ander voorbeeldprogramma en dat werkte direct goed.
Als je verandert van bijvoorbeeld HSU naar I²C dan moet je de schakeling even van de stroombron afhalen anders krijg je mogelijk de melding dat de module "niet gevonden" wordt.

#include <SoftwareSerial.h>
#include <PN532_SWHSU.h>
#include <PN532.h>

SoftwareSerial SWSerial( D1, D2 ); // D1 = RX, deze gaat naar TX op de PN535; D2 is TX, deze gaat naar RX op de PN535)
PN532_SWHSU pn532swhsu( SWSerial );
PN532 nfc( pn532swhsu );

void setup(void) {
  Serial.begin(115200);
  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("\nIk kan geen PN535 vinden");
    while (1); // Stop hier -- maar de WeMos start zichzelf steeds opnieuw op
  }

  Serial.print("\nPN535 gevonden!");
  Serial.print("Firmware versie "); Serial.print((versiondata >> 16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);

  nfc.SAMConfig(); // configureer voor het lezen van RFID kaarten of druppels
  Serial.println("Houd een RFID kaart of druppel bij de sensor...");
}

void loop(void) {
  boolean success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer
  uint8_t uidLength;                        // Lengte van de UID (4 of 7 bytes, hangt af van ISO14443A kaart type)
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength);

  if (success) {
    Serial.println("Kaart gedetecteert");
    Serial.print("UID Lengte: "); Serial.print(uidLength, DEC); Serial.println(" bytes");
    Serial.print("UID Code: ");
    for (uint8_t i = 0; i < uidLength; i++) {
      Serial.print(" 0x");
      Serial.print(uid[i], HEX);
    }
    Serial.println();
    Serial.println("\nHoud een RFID kaart of druppel bij de sensor...");
    delay(1000);
  }
}

#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>

PN532_I2C pn532i2c(Wire);
PN532 nfc(pn532i2c);

void setup() {
  Serial.begin(115200);
  Serial.println("PN532 I2C test");

  Wire.begin();   // ESP32: Wire.begin(21, 22);

  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (!versiondata) {
    Serial.println("PN532 niet gevonden via I2C!");
    while (1);
  }

  Serial.print("Chip: PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
  Serial.print("Firmware: "); Serial.print((versiondata>>16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);

  nfc.SAMConfig();   // Activeer kaartlezer modus
  Serial.println("Wacht op een kaart…");
}

void loop() {
  boolean success;
  uint8_t uid[7];
  uint8_t uidLength;

  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);

  if (success) {
    Serial.print("Kaart gevonden! UID: ");
    for (uint8_t i = 0; i < uidLength; i++) {
      Serial.print(uid[i], HEX);
      Serial.print(" ");
    }
    Serial.println();
    delay(1000);
  }
}

Uitvoer

PN532 I2C test
Chip: PN532
Firmware: 1.6
Wacht op een kaart…
Kaart gevonden! UID: 4 63 A7 A2 F5 21 91 
Kaart gevonden! UID: A 8D 71 1B 

Spelen met de Adafruit_PN532 bibliotheek

Een bibliotheek die wel via de IDE is te installeren is Adafruit_PN532.h. Kruis bij het installeren van deze bibliotheek aan dat alles moet worden geïnstalleerd. De bibliotheek ondersteunt I²C, SPI en HSU. Er zijn veel voorbeeldprogramma's. Ze zijn default ingesteld op SPI-communicatie, maar door de commentaarstrepen aan te brengen voor de SPI-initialisatie en weg te halen bij de I2C-initialisatie kun je dat gemakkelijk veranderen. I²C is erg handig omdat er maar twee signaaldraden nodig zijn, maar SPI is sneller en betrouwbaarder.
Begin altijd met te testen of I²C goed werkt. Open hiervoor File → Voorbeelden → Wire → i2c_scanner. Dit programma moet als je het runt de waarde 0x24 laten zien. In eerste instantie werkten een aantal eigen programma's goed, maar geen enkel voorbeeldprogramma. Later ontdekte ik dat ik even de tweede resetpin met het label tekst RSTPOW moest aansluiten. De voorbeeldprogramma's zijn niet geweldig uitgevoerd, zo blijft de kaart gelezen worden tot je hem weghaalt en krijg je tientallen keren dezelfde uitvoer. Dat had heel veel beter gekund. Hier zie je de uitvoer van mijn gr8 kaart (met de herhalingen verwijderd):
Hello!
Found chip PN532
Firmware ver. 1.6
Waiting for an ISO14443A Card ...

Found an ISO14443A card
  UID Length: 4 bytes
  UID Value: 0x0A 0x8D 0x71 0x1B
Seems to be a Mifare Classic card #177041691
Bij verder testen kwam ik erg veel problemen tegen. Ook andere gebruikers melden veel problemen met deze bibliotheek. Ik ben er daarom mee gestopt

125 kHz (RMD6300) lezer

Deze module ziet er op het eerste gezicht ingewikkeld uit en er staat geen enkele informatie bij de pinnen. Maar toch is het aansluiten erg eenvoudig: Het blokje van 3 pinnen rechts onderaan is voor de voeding. Sluit de middelste pin aan op VCC en de linkse op GND. Op de twee pinnen links onder sluit je de spoel aan (het maakt niet uit hoe). Als je het geheel op een breadboard plaats dan kun je twee jumper kabeltjes gebruiken om het witte stekkertje te verbinden met het breadboard. Van de vijf pinnen bovenaan gebruik je alleen de pin die helemaal rechts zit. Verbind deze met digitale pin 2 van de Arduino. Meer is niet nodig! Als je wilt weten wat de andere pinnen doen: de twee linkse pinnen van de 5 pinnen bovenaan kun je ook gebruiken voor VCC en GND; de middelste pin is niet aangesluiten en de vierde pin is RX (maar die gebruik je niet). Op de pin onder rechts kun je een led aansluiten (er is al en weerstand van 100 Ω aanwezig). De led is altijd aan, maar hij knippert als er een kaart of druppel in de buurt is. Helaas is de knipperfrequentie 1000 Hz, dus vrijwel onzichtbaar.

RFID opstelling
Op het breadboard Arduino pro mini met de RFID lezer; rechts de RFID spoel.

Hieronder staat een eenvoudige sketch waarmee je de module kunt testen. Hij geeft een rij getallen die uniek zijn voor de kaart of de druppel. Hiermee kun je al allerlei leuke projecten doen, bijvoorbeeld in de klas.

#include <SoftwareSerial.h>
SoftwareSerial RFID(2, 3); // alleen RX (= TX van de module) wordt gebruikt

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

void loop()
{
  if ( RFID.available() ) {
    delay(50); // Dit zorgt ervoor dat je niet gemakkelijk meer dan 1 keer de code leest.
    int i = RFID.read(); // Lees het volgende getal
    Serial.print(i); Serial.print(" "); // Print het identifcatienummer van de druppel
    if (i == 3) { // De code eindigt altijd met een 3
      Serial.println();
      while ( RFID.read() >= 0); // Een keer is genoeg (kaart moet inmiddels weg zijn)
    }
  }
}

Hieronder zie je het resultaat van 10 verschillende druppels.

De getallen die je hierboven ziet bevatten ook controlegetallen en stuurgetallen. Als je de codes inzichtelijker wilt maken (en beter bruikbaar) dan kun je het beste een bibliotheek gebruiken. Een simpele bibliotheek is RFIDRdm630.h. Deze is bedoeld voor de RDM630, maar werkt ook met de RDM6300 en een aantal andere RFID lezers. Je ziet dat de uitvoer veel overzichtelijker is. Het gevonden kaartnummer komt overeen met het opschrift op de druppel. Erg handig dus. Zie hieronder de gebruikte schets.

#include "RFIDRdm630.h"
RFIDtag tag;  // RFIDtag object
RFIDRdm630 reader = RFIDRdm630(2, 3); // Resultaat komt binnen op poort 2

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

void loop() {
  if (reader.isAvailable()) {
    tag = reader.getTag();
    Serial.print("TAG: ");
    Serial.print(tag.getTag()); // TAG in ASCII formaat
    Serial.print(" Druppel nummer: ");
    Serial.println(tag.getCardNumber()); // Druppel nummer als decimaal getal
    Serial.println();
  }
}

Dit zie je op de seriële monitor: met deze druppel:

In het midden van de "TAG" zie je het kaartnummer.
Omdat mijn druppels niet schrijfbaar zijn heb ik dat ook niet getest.