How to connect a magnetic reader to a microcontroller

Tomcat

Professional
Messages
2,656
Reputation
10
Reaction score
647
Points
113
Greetings to all!

A long time ago, I already wrote one post about magnetic cards, which talked about what kind of technology it is and how it works. Now we will talk about it from a completely different angle - how to control a magnetic reader with a TTL interface and what to do with it all.

vgf2vkvmz7er4tha0f6jcrb3p3q.jpeg


So, in today’s article we’ll talk about built-in magnetic readers. Let's figure out how to connect it and how to process data from it. As usual, there will be a lot of interesting things.

❯ What am I talking about?​


Magnetic cards promise to go down in history, probably from the very advent of cheap and mass-produced smart cards. However, despite the fact that they are buried almost every year, the final decline of this technology is still far off. And if so, it’s time to remember them again.

❯ Where else are magnetic cards used?​


I think we all understand perfectly well that their use is not limited to banks alone.

awdsfuziwqxu6h0n4m6km8w8q4m.jpeg


Such cards can still often be found as service or discount cards in stores. And if discount and club cards increasingly have only a barcode on them, then various cashier cards intended for logging into the system are still alive.

ilo7lalgvmry67hm1gsm3vm6m9e.jpeg


Another equally popular option are various electronic locks, access control systems or time attendance systems. Now many of them are being transferred to RFID or biometrics, but in some places the subjects of our discussion today are still used.
Here is an example of a card with a magnetic stripe from a hotel room in St. Petersburg.

gs4oe8tncuer4-6fgnoncwxjy-w.jpeg


Well, the most interesting application is, perhaps, this one, where both banks and access control systems are connected. This is an ATM access control device. Perhaps some of you have even used one yourself.

sma3gzf9jk7kqzhq79taxr4gaes.jpeg


Usually they are placed at 24-hour branches to protect the ATM from vandals, sleeping homeless people and other unwanted individuals. It’s simple - if the card you swiped is invalid or is not serviced by this bank, then you won’t be able to get inside.

s0y0ti2jfum--qia9vz-1yv8ali.jpeg


Some banks are already abandoning such readers, replacing them with cameras with facial recognition.

❯ Equipment overview​


Why did I mention these same access control systems? The thing is that just such a reader came into my hands. Instances used in access control systems usually do not have the usual USB, PS/2 or RS-232 interface, but output “raw” data that needs to be processed. And now we’ll talk about how to do this.

h4o6xoaufmmhdd2mdbvivfrvkss.jpeg


And here is the reader itself. This is the PerCo RM-3VR. Reads the second track, has standard TTL levels. You can also trim a two-color LED for indication.
The case is metal, but it is still a street-ready reader.

tgj5cyh7g_sqp9b1sk-6kllxacy.jpeg


Back side. Cover with four screws, cable for connection to the control unit.

5bylolgc_rvjtplrls1xb9hc2sa.jpeg


And here are the insides. The reader itself is screwed to a metal base, which also serves as the bottom cover.

bxygf_odqmmv7oto_nlhonpgzja.jpeg


Pay. It is based on some kind of LH6516G chip; a datasheet for it could not be found. Nearby there is space for another one (I assume that there were also copies for the first track).

❯ Connection​


There are eight wires coming from the reader, but only four are enough for us.
It is easy to find documentation for this device, where there is also a pinout:

5bsddaukfjzcb1eawtfxkpbpvpo.png


Such devices operate at the lowest level, the microcircuit only converts F/2F encoding into a bit string. Thus, data processing remains with the microcontroller.
The reader has DATA and CLOCK pins for clear purposes. Data is read when CLOCK is low. Actually, that's all you need to know to connect.
Some readers also have a CLS pin where it goes low when a card is detected, but my reader officially doesn't have one. Perhaps the undocumented green wire is responsible for it, but I have not checked this.

❯ Reading the data​


Well, it's time to try reading the map.

As a control, the MK took the same Arduino Mega board, just for the sake of compatibility with five-volt levels.

I decided not to use the CLS output and rely solely on the CLOCK data. It's simple - if the signal does not change for too long, it means that the card is no longer there and you can try to recognize what you read. It turns out something like this:

Code:
int index = 0;
uint8_t * rawData;
unsigned long limit = 0;
bool isCardReading = 0;

void getData() {
  rawData[index] = uint8_t(digitalRead(3));
  index++;
  isCardReading = 1;
  limit = millis();
}

The state of the DATA pin changes quite slowly, so it is quite possible to use a “slow” digitalRead.
We attach an external interrupt to the CLOCK pin. When the signal falls, data will be read. In the loop we check how it is there:

Code:
void callMSR() {
  if (millis() - limit > 500 && isCardReading == 1) {
    isCardReading = 0;
    processData(index);
    index = 0;
  }
}

If the data has not arrived for more than half a second, then the card presence flag is cleared and the function that processes the received values is called.
To check, we output the read array of bits to the port, swipe the card several times and make sure that the data is the same.

❯ Decoding​


Now what to do with these bits? The reader receives data from the second track, which uses a five-bit encoding: four data bits and one parity bit. At the ends of this row there are start and stop symbols. For the first track it is % and ? , for the second - ; And ? . After the stop symbol there is one control symbol - LRC. It also has its own parity bit. The bits themselves in each character consist of a quadruple in order from least significant to most significant, followed by a parity bit.
So, first you need to understand where the data even begins. Before the data itself begins, the reader produces a number of zero bits that should be skipped. Next, we pinch off the next five bits and push it into the next byte of the array. In general, this operation can be performed already at the stage of reading from the port, without a buffer of three hundred bytes, but here I did it this way for clarity. An option with a buffer of this size can be useful when reading several tracks at once, in order to simply set different bits depending on which track’s CLOCK has changed.
Now we have a set of sequences of five bits. Before we try to do anything about it, we check the parity by adding all the bits of the five and making sure that the result is an odd number:

Code:
uint8_t checkParity(uint8_t input) {
  uint8_t data = ~input;
  uint8_t test = 0;
  for (int i = 0; i < 5; i++) {
    test += data & 1;
    data >>= 1;
  }
  if (test % 2 != 0) return 0;
  else return 1;
}

The magnetic reader produces an inverted signal, so to begin with the data, it is necessary to bring the bits in the symbol to a standard form.
The character code table used here is like this:

_wx_mdvnktnwefwfoi49sv8lohy.png


It is easy to notice that the characters are in the same order as in the ASCII table, respectively, to convert a five-bit code into text, you need to remove the parity bit, and add the character code 0 (30h) to the remaining four-bit number . Something like that:

Code:
char convertToSymbol(uint8_t input) {
  uint8_t data = ~input;
  data &= B00001111;
  return data + '0';
}

Now it's LRC's turn. We discard parity bits from all data - LRC has its own. Next, we do a sequential XOR of all four bits and compare them with the last value. If it matches, you can print the finished string.
It turned out like this:

Code:
void processData(int maxSize) {
  int currentBit = 0;
  uint8_t * digits = new uint8_t[40];
  uint8_t countSymbols = 0;
  uint8_t lrc = 0, target = 0;
  String s;
  for (int i = 0; i < 40; i++) digits[i] = 0;
  while (currentBit < maxSize && rawData[currentBit] == 1) currentBit++;
  for (int i = 0; i < 40; i++) {
    for (int n = 0; n < 5; n++) {
      digits[i] |= rawData[currentBit] << n;
      currentBit++;
    }
    if (!checkParity(digits[i])) {
      s += convertToSymbol(digits[i]);
      countSymbols++;
    }
  }
  target = ~digits[countSymbols - 1];
  target &= B00001111;
  for (int i = 0; i < countSymbols - 1; i++) {
    lrc ^= ~digits[i];
  }
  lrc &= B00001111;
  if (lrc == target) {
    s[countSymbols - 1] = '\0';
    Serial.println(s);
  }
  delete[] digits;
}

Before printing the line, we remove the last character, since LRC is used exclusively for verification and is not printed anywhere else.
LRC also exempts most other data integrity checks; for example, a string without a beginning or end will fail precisely because of a checksum mismatch.

❯ Indication​


I don’t have a standard control unit, so I connected the indicator LED to the MK. Instead of denying or allowing passage, it now shows the success of the card reading.
By the way, despite the fact that in such equipment the indication is often already designed for direct connection, the local LED requires a resistor.
As a result, the whole program turned out like this:

Code:
#define BUFFER_SIZE 300
#define INTERVAL 500
#define RED_LED 12
#define GREEN_LED 13

int index = 0;
uint8_t * rawData;
unsigned long limit = 0;
bool isCardReading = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(3, INPUT);
  Serial.begin(115200);
  attachInterrupt(0, getData, FALLING);
  rawData = new uint8_t[BUFFER_SIZE];
  Serial.println("Swipe card");
}

void getData() {
  rawData[index] = uint8_t(digitalRead(3));
  index++;
  isCardReading = 1;
  limit = millis();
}

uint8_t checkParity(uint8_t input) {
  uint8_t data = ~input;
  uint8_t test = 0;
  for (int i = 0; i < 5; i++) {
    test += data & 1;
    data >>= 1;
  }
  if (test % 2 != 0) return 0;
  else return 1;
}

char convertToSymbol(uint8_t input) {
  uint8_t data = ~input;
  data &= B00001111;
  return data + '0';
}

void processData(int maxSize) {
  int currentBit = 0;
  uint8_t * digits = new uint8_t[40];
  uint8_t countSymbols = 0;
  uint8_t lrc = 0, target = 0;
  String s;
  for (int i = 0; i < 40; i++) digits[i] = 0;
  while (currentBit < maxSize && rawData[currentBit] == 1) currentBit++;
  for (int i = 0; i < 40; i++) {
    for (int n = 0; n < 5; n++) {
      digits[i] |= rawData[currentBit] << n;
      currentBit++;
    }
    if (!checkParity(digits[i])) {
      s += convertToSymbol(digits[i]);
      countSymbols++;
    }
  }
  target = ~digits[countSymbols - 1];
  target &= B00001111;
  for (int i = 0; i < countSymbols - 1; i++) {
    lrc ^= ~digits[i];
  }
  lrc &= B00001111;
  if (lrc == target) {
    s[countSymbols - 1] = '\0';
    Serial.println(s);
    digitalWrite(GREEN_LED, HIGH);
    delay(500);
    digitalWrite(GREEN_LED, LOW);
  }
  else {
    digitalWrite(RED_LED, HIGH);
    delay(500);
    digitalWrite(RED_LED, LOW);
  }
  delete[] digits;
}

void callMSR() {
  if (millis() - limit > 500 && isCardReading == 1) {
    isCardReading = 0;
    processData(index);
    index = 0;
  }
}

void loop() {
  callMSR();
  // put your main code here, to run repeatedly:
}

Everything works successfully.

❯ Something like that​


As you can see. connecting such a reader is not so difficult. However, even with all the advantages of cards with a magnetic stripe (of which there are actually not that many), such a device separately from its controller is now more of a toy than a device that is actually applicable somewhere.
By the way, I was a little amused by the description of this thing from the manufacturer:

The PERCo-RM-3VR magnetic card reader is designed to read identification information from plastic bank cards with a magnetic stripe and transmit it to the controller. The device only reads information about membership in a particular banking system and the validity period of the card. No secure banking information can be read from other stripes of the card, including information about the cardholder, account number, etc.

I don’t know what they meant by confidential information, but, as you might guess, the device reads the entire second track of the card.

So it goes.
 
Top