Arduino-Tutorial: How to use the RDM630/RDM6300 RFID reader

On the left hand side is the RM6300, which is a very affordable (1-3$) RFID reader. On the right hand side, is the RDM630. The RDM630 is more expensive (10-15$) but also more robust.

On the left hand side is the RDM6300, which is a very affordable (1-3$) RFID reader. On the right hand side, is the RDM630. The RDM630 is more expensive (10-15$) but also more robust.

Seeedstudio’s RDM630 and the RDM6300 are two different types of RFID readers, even though they often get mixed up. Besides the similar names, another reason for the confusion might be that they share the same pin layout and transfer protocol. According to my knowledge, besides the same technical functionality, they have nothing else in common (like same manufacturer, predecessor/successor version, etc.). Usually, you should be able to purchase Seeedstudio’s RDM630 for about 10-15$ and the RDM6300 for about 1-3$. In my opinion, the price difference can be easily justified as the RDM630 is more robust and allows much higher reading distances.

Both readers work at a frequency of 125 kHz and enable to read EM4100-compatible tags. Other tags (for example EM4305 tags) do not (or might not) work with these readers.

In this tutorial, I demonstrate how to use the RDM6300 and the RDM630 with an Arduino Uno. Luckily, both RFID readers use the same protocol via a serial connection. As a result, the same source code can be used to make both readers work. Therefore, regardless of whether you own an RDM630 or RDM6300, this tutorial should be of use to you if you want to make it work with an Arduino.

List of materials:
– Arduino Uno  [Search on Aliexpress | Amazon]
– Jumper wires  [Search on Aliexpress | Amazon]
– Seeedstudio RDM630* [Search on Aliexpress | Amazon]
– RDM6300 [Search on Aliexpress | Amazon]
– 125 kHz tags (check for EM4100 compatibility) [Search on Aliexpress | Amazon]
* If you are looking for an RDM630, please keep in mind that many offers are wrongly labeled. You can find many products that are advertised as “RDM630”, although they are of type RDM6300. Therefore, take a close look at the pictures.

Pin layout RDM630 / RDM6300:

Pin layout of the RDM630. The RDM6300 has the exact same pin layout.

Pin layout of the RDM630. The RDM6300 has the exact same pin layout.

In total, the RDM630/RDM6300 has 9 pins, where Vcc and GND exist two times. The Vcc pin must be connected to a 5V DC. ANT1 and ANT2 are used to connect an antenna to the board. Typically, the RDM630 as well as the RDM6300 already come with an antenna. Nonetheless, it is possible to switch the antenna, for example to a custom-made antenna. TX is used to transmit data and RX is used to retrieve data. The LED pin can be used to quickly lookup whether an RFID tag was successfully read. If RFID tag is not present, the LED pin is at 5V (HIGH). If an RFID was read, the LED pin goes to 0V (LOW) for some moments (yes, it goes from HIGH to LOW!).

Wiring to Arduino Uno:

Wiring between an Arduino Uno and a RDM6300. The same wiring can be applied to an RDM630.

Wiring between an Arduino Uno and a RDM6300. The same wiring can be applied to an RDM630.

In this tutorial, four pins of the RDM630/RDM6300 are wired to the Arduino Uno. Vcc has to be connected to the Arduino’s 5V pin (red wire) and GND to the Arduino’s GND (black wire). The TX pin has to be connected to digital pin #6 (green wire). Basically, the RX pin is not required as we do not send data to the RFID module in this tutorial. For the sake of completeness, RX is connected to digital pin #8 (yellow wire). Lastly, the antenna is connected to ANT1 and ANT2 (polarity does not matter).

Example source code:
As mentioned before, the same source code can be used with both models (I tested the code with both models). Basically, when an RFID tag was detected the RDM630/RDM6300 sends a frame with 14 bytes:  head [1 byte], data [10 byte],  checksum [2 byte], and tail [1 byte]. The head (or preamble) is always 0x02. Similarly, the tail is always 0x03. The data field contains ASCII-encoded HEX values. Sometimes, manufacturers tend to split the RFID tag data field into two parts: version and tag. Therefore, depending on your RFID tag type, the first two bytes of data might be the version and the other 8 bytes the actual RFID tag. I considered this in my source code. In order to calculate the checksum from the data field, one has to perform an XOR-operation over all data entries.

// (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;
}


If you execute the source code and hold an RFID tag close to the antenna, the Arduino IDE’s serial monitor should output something like this:

Video tutorial:

Leave a Reply

Your email address will not be published. Required fields are marked *