RFID Access Control: How to Create a Smart Usage-Limited System with RYRR20I_DE and Mifare Ultralight Tags

I created an access system that can track how many times a card is used and once credits are exhausted, it can limit access until topped up.
Apr 05, 2024 — 9 mins read — Electronics

RFID Access Control: How to Create a Smart Usage-Limited System with RYRR20I_DE and Mifare Ultralight Tags

When working with the classic 125kHz RFID system, the sole purpose of the cards is to provide an identifier so that the system can check that identifier against a database and decide if it should be granted access. These cards are very simple as they do not store any data on them and only provide their UID for reference.

Contrary to that, systems that work on the 13.56Mhz frequency are designed so tags/cards have available memory that can be utilized to save state and user-unique data for later use. One such system is the Mifare Ultralight or the so-called ISO 14443 standard so when Reyax offered to send a device working with these cards to test out I was intrigued and accepted the challenge.

To demonstrate the capabilities of the module, I designed a system where a set of "usage tokens" can be pre-programmed on the card and they will be deducted with each use. Once the usage is depleted, the system no longer allows entry, and the card needs to be topped up in order for it to continue its operation.



Details of the RYRR20I_DE Module

The RYRR20I_DE module from Reyax is a Fully Integrated 13.56MHz +3.3V UART Interface RFID Antenna Module that can work with multiple protocols and straight out of the box, once power is applied, it can directly read cards and output their UID over serial communication.

This data can then be very easily read from a microcontroller and we can build a classical RFID access control system in literally no time. However, what this module does better is its ability to interact with Mifare Ultralight based tags and cards where it can access their internal memory for both reading and writing.

Additionally, the module can interact with ISO14443A/B, ISO15693, FeliCa, and ISO18092 protocols where it can read their UID blocks and provide a versatile solution for any access control system.


Demo Device Setup

To build a working prototype for the module, I used an ESP32 Development board to use its hardware serial port to interact with the RYRR20I_DE module. The module is powered from the Vin (5V) on the ESP32 and the serial communication is done on pins 16 and 17.

Pin 23 is connected to the module reset pin so I can programmatically pull it low to reset the module when necessary.

The interaction is currently implemented through the Arduino IDE serial monitor so I can read out the data coming in from the module as well as being able to write commands to send to the module. By sending these commands, we are instructing the ESP32 and the module to write the allowance info to the cards so it can be read later when accessed.


Writing data to the tags

To be able to initialize a card for usage on the system, I'm sending the command "write" followed by a space and the number of "credits" that I want to save to the card, ex. "write 10" to init the card with 10 usages.

This command is picked up by the code, and when detected, it activates a special mode on the ESP32 where it starts constantly listening for the presence of a card on the reader. Once a card is detected, the number is written at block 07, with a string starting with "AABBCC". This is something I just made up as an identifier to know that the data is most likely ours.

This is not secure at all, and the example does not bother with security as that is not our goal, but if you are working on a commercial system that uses the same principle, you will need to consider the security aspects of the implementation as well as encryption of the data stored on the cards.

Since we are only using one byte for our data, we can store up to 255 usage tokens on a single card, and right now we do not bother with checking if the card had any previous tokens to append to but we overwrite any previous state.

Once the write command is executed, I immediately follow it with a read command so that I can confirm that the write was successful. If it was not, then the entire process continues until the card is successfully written.


Uses of such "credit" based RFID system

This system has many practical applications and the first thing that comes to mind is a bus ride ticket. You can purchase a certain amount of rides and the reader on the bus can check and deduct from your balance. Similarly, the system can be applied to gym access, amusement park rides, smart home systems where usage of a certain resource (ex. pool table) can be limited per user, and other similar cases.

If you have any ideas or suggestions for a particular implementation, please feel free to reach out and suggest in the comments below.


Arduino Code of the Access Control System

The entire device code is added with the Arduino IDE and you can find the full code below. For a detailed explanation of the code, please refer to the video above.

#include <HardwareSerial.h>

HardwareSerial mySerial(2);
#define NRST 23
#define APPROVE_LED 27
#define DENY_LED 32
#define DELAY 500

void setup() {
  pinMode(NRST, OUTPUT);
  pinMode(APPROVE_LED, OUTPUT);
  pinMode(DENY_LED, OUTPUT);
  digitalWrite(APPROVE_LED, HIGH);
  digitalWrite(DENY_LED, HIGH);
  delay(500);
  digitalWrite(APPROVE_LED, LOW);
  digitalWrite(DENY_LED, LOW);
  
  Serial.begin(115200);
  
  // Wait for serial port to connect. Necessary only for native USB
  while (!Serial) {
    ; 
  }
  
  // Set pins for RX and TX for mySerial (e.g., 16 for RX, 17 for TX)
  mySerial.begin(115200, SERIAL_8N1, 16, 17); // Set baud rate and pins for ESP32

  // Wait for everything to stabilize
  //delay(1000); 
  HW_RESET();
  delay(DELAY); 
  // Setting up as Anchor
  sendATCommand("0108000304FF0000"); // Set the mode to Anchor
  readResponse();
  delay(DELAY);
  
  sendATCommand("010C00030410008001210000"); // Set network ID
  readResponse();
  delay(DELAY);

  sendATCommand("010C00030410002001090000"); // Set device address
  readResponse();
  delay(DELAY);

  sendATCommand("0109000304F0000000"); // Set encryption key
  readResponse();
  delay(DELAY);
  
  sendATCommand("0109000304F1FF0000"); // Set encryption key
  readResponse();
  delay(DELAY);
}

void loop() {
  if (Serial.available()) {
    String content = Serial.readString();
    content.trim();
    if(content.startsWith("write")) {
      int data = content.substring(6).toInt();
      char command [30];
      sprintf(command, "010E00030418A207AABBCC%02x0000", data);
      bool write_success = false;
      do {
        digitalWrite(DENY_LED, HIGH);
        sendATCommand("0109000304A0000000");
        String response = returnResponse();
        String ID = "";
        if(response.indexOf('[') != -1) {
          response = response.substring(response.indexOf('[') + 1, response.indexOf(']'));
          if(response != "") {
            ID = response.substring(0, response.indexOf(','));
            Serial.print("ID: ");
            Serial.println(ID);
          }
        }
        delay(100);
        if(ID != "") {
          sendATCommand(command);
          response = returnResponse();
          delay(100);
          sendATCommand("010A0003041830070000");
          response = returnResponse();
          response = response.substring(response.indexOf('[') + 1, response.indexOf(']'));
          if(response != "") {
            //Serial.print("DATA: ");
            //Serial.println(response);
            if(response.startsWith("AABBCC")) {
              int allowed = strtol(response.substring(6, 8).c_str(), NULL, 16);
              Serial.print("Allowed: ");
              Serial.println(allowed);
              if(allowed == data) {
                write_success = true;
              }
            }
          }
        }
        digitalWrite(DENY_LED, LOW);
        delay(100);
      } while (write_success == false);
      flash_success();
      delay(DELAY);
    }
  } else {
    sendATCommand("0109000304A0000000");
    String response = returnResponse();
    String ID = "";
    if(response.indexOf('[') != -1) {
      response = response.substring(response.indexOf('[') + 1, response.indexOf(']'));
      if(response != "") {
        ID = response.substring(0, response.indexOf(','));
        Serial.print("ID: ");
        Serial.println(ID);
      }
    }
    delay(100);
    if(ID != "") {
      sendATCommand("010A0003041830070000");
      response = returnResponse();
      response = response.substring(response.indexOf('[') + 1, response.indexOf(']'));
      if(response != "") {
        //Serial.print("DATA: ");
        //Serial.println(response);
        if(response.startsWith("AABBCC")) {
          int allowed = strtol(response.substring(6, 8).c_str(), NULL, 16);
          Serial.print("Allowed: ");
          Serial.println(allowed);
          if(allowed > 0) {
            write_update(allowed - 1);
            approved();
          } else {
            denied();
          }
        } else {
          denied();
        }
      } else {
        denied();
      }
      delay(100);
    }
  }
  
}

void sendATCommand(String command) {
  //Serial.print("Sending command: ");
  //Serial.println(command);
  mySerial.print(command); // Send the command to RYUW122
  delay(100);
}

String returnResponse() {
  long startTime = millis();
  while (!mySerial.available() && millis() - startTime < 5000) { 
    // Wait for response for up to 5 seconds
  }
  String data_incomming = "";
  while (mySerial.available()) {
    char c = (char)mySerial.read();
    data_incomming += c;
  }
  return data_incomming;
}

void readResponse() {
  long startTime = millis();
  while (!mySerial.available() && millis() - startTime < 5000) { 
    // Wait for response for up to 5 seconds
  }
  while (mySerial.available()) {
    Serial.print(mySerial.read());
  }
  Serial.println();
}

void HW_RESET(){
  digitalWrite(NRST, LOW );
  delay(500);
  digitalWrite(NRST, HIGH);
}

void approved() {
  digitalWrite(APPROVE_LED, HIGH );
  delay(3000);
  digitalWrite(APPROVE_LED, LOW);
}

void denied() {
  digitalWrite(DENY_LED, HIGH );
  delay(3000);
  digitalWrite(DENY_LED, LOW);
}

void flash_success() {
  digitalWrite(DENY_LED, LOW);
  for(int i = 0; i < 5; i++) {
    digitalWrite(APPROVE_LED, HIGH );
    delay(300);
    digitalWrite(APPROVE_LED, LOW);
    delay(300);
  }
  delay(3000);
}

void write_update(int new_allowed) {
  char hexadecimalnum [30];
  sprintf(hexadecimalnum, "010E00030418A207AABBCC%02x0000", new_allowed);
  sendATCommand(hexadecimalnum);
  returnResponse();
  delay(100);
}


Conclusion and next steps

This was an interesting and fun little project for me so I would like to know your thoughts on it. Feel free to write in the comments below for suggestions and if there is an interest, maybe we can create a project for a full-blown open-sourced access control system based on it.

If you liked it, then please consider liking the video on YouTube and subscribing to my channel as it helps me continue these projects and videos. Below are links to all of the necessary tools and materials so that you can replicate the project and by buying through them, you support my channel at no extra cost to you!


Tools and materials used in the video



esp32 NFC RFID Reyax
Read next

Soil Moisture Sensor on batteries with ESP-07 running ESPHome - Start to Finish

Last year I made and installed a DIY garden irrigation controller for my drip irrigation system and it worked wonders during the last growin...

You might also enojy this

Trail mapping with GPS, ESP32 and microSD card

Where I live, hiking is a big thing as we have plenty of mountains around and there are a ton of trails that people can explore and enjoy.Ho...