Zapis i odczyt danych na karty MIFARE

Jeżeli posiadamy czytnik kart RC522 oraz kilka tagów do niego możemy poza samym odczytem ich unikalnych identyfikatorów chcieć przechowywać na nich dane. W końcu posiadają one 1kb nieulotnej pamięci, która tylko czeka aż coś do niej wpiszemy.

Podłączenie czytnika

Czytnik należy podłączyć dokładnie w taki sam sposób jak opisany w tym lub tym artykule pamiętając o tym, żeby zasilanie 3.3V podłączyć do linii 3.3V (w przeciwnym wypadku spalimy czytnik), czyli:

  • SDA – 10
  • SCK – 13
  • MOSI – 11
  • MISO – 12
  • GND – GND
  • RST – 9
  • 3.3V – 3.3V

Po lewej stronie są nazwy pinów opisane na czytniku kart, a po prawej te na Arduino.

Wymagane komponenty

Aby poprawnie komunikować się z czytnikiem kart musimy wyposażyć nasze Arduino IDE w bibliotekę RFID z GitHuba.

Szkic programu

Najpierw musimy załączyć niezbędne biblioteki:

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

Kiedy Arduino już wie któych plików użyć możemy powiedzieć mu, aby utworzył globalny obiekt do komunikacji z czytnikiem:

#define RST_PIN         9
#define SS_PIN          10

MFRC522 mfrc522(SS_PIN, RST_PIN);

W funkcji setup() uruchamiamy port szeregowy oraz inicjujemy połączenie z czytnikiem:

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

loop() póki co zostawimy pusty. Zaimplementujemy teraz funkcje saveData() oraz readData().

Uwaga! Te funkcje w implementacji są do siebie bardzo podobne. W normalnym programie nie powinniśmy kopiować i wklejać w kółko tego samego kodu, jednak w poniższym przykładzie zrobimy to aby pokazać jak niezależnie odczytać i zapisać dane z/do karty.

bool saveData() {
  if (!mfrc522.PICC_IsNewCardPresent()) {
    return false;
  }

  if (!mfrc522.PICC_ReadCardSerial()) {
    return false;
  }

  MFRC522::MIFARE_Key key;
  for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
  
  byte block = 53;
  char buffer[16] = {0};
  strcpy(buffer, "Tekst");

  if(mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block, &key, &(mfrc522.uid)) != MFRC522::STATUS_OK) {
    return false;
  }

  if(mfrc522.MIFARE_Write(block, buffer, 16) != MFRC522::STATUS_OK) {
    return false;
  }

  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  return true;
}

Opis przebiegu funkcji:

  1. Sprawdzamy, czy jakaś karta została przyłożona do czytnika. Jeżeli nie to zwracamy fałsz (niepowodzenie)
  2. Odczytujemy UID karty w celu nawiązania dalszej komunikacji z tą konkretną kartą. Jeżeli to się nie powiedzie zwracamy fałsz.
  3. Teraz możemy przygotować dane do zapisania na kartę. Te dane to:
    • klucz dostępu (domyślnie jest to 6 bajtów wypełnionych jedynkami)
    • numer bloku (nie może to być blok 0, ani żaden podzielny przez 3 – modyfikacja bloku o numerze podzielnym przez 3 grozi nadpisaniem klucza autoryzacyjnego i utraceniem dostępu do bloku)
    • bufor z danymi do zapisu wypełniony zerami
  4. Do bufora wklejamy tekst, który chcemy zapisać przez funkcję strcpy()
  5. Autoryzujemy się za pomocą naszego domyślnego klucza (jeżeli to się nie powiedzie to zwracamy fałsz)
  6. Zapisujemy dane na karę, w przypadku niepowodzenia standardowo zwracamy fałsz
  7. Kończymy komunikację z kartą
  8. Zwracamy prawdę (cały proces zapisu powiódł się)

Zobaczmy teraz funkcję służącą do odczytu danych. Są tu cztery ledwie widoczne na pierwszy rzut oka zmiany:

bool readData() {
  if (!mfrc522.PICC_IsNewCardPresent()) {
    return false;
  }

  if (!mfrc522.PICC_ReadCardSerial()) {
    return false;
  }

  MFRC522::MIFARE_Key key;
  for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
  
  byte block = 53;
  byte len;
  char buffer[16] = {0};

  if(mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block, &key, &(mfrc522.uid)) != MFRC522::STATUS_OK) {
    return false;
  }

  if(mfrc522.MIFARE_Read(block, buffer, &len) != MFRC522::STATUS_OK) {
    return false;
  }

  Serial.println(buffer);

  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  return true;
}

Pierwsza z nich to zamiana metody MIFARE_Write na MIFARE_Read. Jej ostatnim parametrem jest długość odczytanych danych z bloku (jako referencja), dlatego też musimy utworzyć sobie zmienną len, a następnie przekazać ją do metody przez operator dereferencji &. To była druga zmiana. Trzecia jest taka, że nie używamy funkcji strcpy, ponieważ to MIFARE_Read zapisze nam dane w buforze. Ostatnią zmianą jest dopisanie linijki

Serial.println(buffer);

w celu wypisania zawartości pamięci na port szeregowy.

Teraz na koniec funkcji setup dopiszmy

while(!saveData());
Serial.println("Data saved");

oraz zaimplementujmy loop():

void loop() {
  readData();
}

To, co właśnie zrobiliśmy oznacza, że nasze Arduino zaraz po włączeniu będzie oczekiwało na przyłożenie karty. Po pierwszym przyłożeniu karty od włączenia w bloku 53 zostanie zapisany ciąg „Tekst”. Każde kolejne przyłożenie karty będzie odczytywało zawartość owego 53 bloku i wypisywało to co w nim znajdzie na port szeregowy jako tekst.

W ten sposób możemy personalizować karty przykładowo zapisując na nich imię właściciela, jakiś identyfikator itp. dane.

Dodatkowo dane zapisane na karcie możemy odczytać za pomocą przykładowego szkicu dołączonego do biblioteki MFRC522 o nazwie DumpInfo. Po jego wgraniu i uruchomieniu portu szeregowego musimy przyłożyć kartę do czytnika i przytrzymać ją aż do momentu odczytania całej pamięci.