Как подключить магнитный считыватель к микроконтроллеру

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Приветствую всех!

Давным-давно я уже писал один пост про магнитные карты, где рассказывалось о том, что это вообще за технология и как оно работает. Сейчас же речь пойдёт о ней с совершенно другой стороны — как управлять магнитным считывателем с TTL-интерфейсом и что с этим всем делать.



Итак, в сегодняшней статье поговорим о встраиваемых магнитных считывателях. Разберёмся, как подключить его и как обрабатывать данные с него. Традиционно будет много интересного.

О чём это я?


Магнитные карты обещают уйти в историю, наверное, с самого появления дешёвых и массовых смарт-карт. Тем не менее, несмотря на то, что их хоронят чуть ли не каждый год, окончательный закат этой технологии ещё далёк. А раз так — самое время вспомнить их ещё раз.

Где ещё применяются магнитные карты?


Думаю, все мы прекрасно понимаем, что одними банками использование их не ограничивается.



Такие карты всё так же частенько можно встретить в качестве служебных или дисконтных в магазинах. И если скидочные и клубные всё чаще имеют на себе только штрих-код, то вот различные карты кассиров, предназначенные для входа в систему, пока ещё живы.



Другим, не менее популярным вариантом являются различные электронные замки, системы контроля доступа или учёта рабочего времени. Сейчас многие из них переводятся на RFID или биометрию, но кое-где до сих пор применяются и предметы нашего сегодняшнего обсуждения.
Вот для примера карта с магнитной полосой от номера отеля в Питере.



Ну и самым интересным применением является, пожалуй, вот это, где связываются и банки, и СКУД. Это устройство контроля доступа к банкомату. Возможно, кому-то из вас даже доводилось самому воспользоваться таким.



Обычно их ставят на круглосуточные отделения для защиты банкомёта от вандалов, спящих бомжей и прочих нежелательных личностей. Всё просто — если проведённая карта недействительна или не обслуживается в этом банке, то попасть внутрь не удастся.



В некоторых банках от таких считывателей уже отказываются, заменяя их на камеры с распознаванием лиц.

Обзор оборудования


К чему же я упомянул эти самые системы контроля доступа? Всё дело в том, что ко мне в руки попал как раз такой считыватель. Экземпляры, использующиеся в СКУД, обычно не имеют привычного интерфейса USB, PS/2 или RS-232, а выдают наружу «сырые» данные, которые надо обрабатывать. А о том, как это делать — сейчас и поговорим.



А вот и сам считыватель. Это PerCo RM-3VR. Читает вторую дорожку, имеет стандартные уровни TTL. Можно также подрубить двухцветный светодиод для индикации.
Корпус металлический, всё же считыватель уличного исполнения.



Обратная сторона. Крышка на четырёх винтах, кабель для подключения к блоку управления.



А вот и внутренности. Сам считыватель прикручен к металлическому основанию, оно же является и нижней крышкой.



Плата. Она основана на каком-то чипе LH6516G, даташит на него найти не удалось. Рядом место для ещё одного (предположу, что были экземпляры ещё и для первой дорожки).

Подключение


От считывателя идут восемь проводов, нам же достаточно всего четырёх.
На данный девайс легко ищется документация, где есть и распиновка:



Такие устройства работают на самом низком уровне, микросхема лишь преобразует F/2F кодирование в строку бит. Таким образом, обработка данных остаётся за микроконтроллером.
Считыватель имеет выводы DATA и CLOCK понятного назначения. Данные считываются при низком уровне CLOCK. Собственно, это всё, что нужно знать для подключения.
Некоторые считыватели также имеют вывод CLS, где устанавливается низкий уровень при обнаружении карты, однако у моего считывателя его официально нет. Возможно, за него отвечает недокументированный зелёный провод, но я это не проверял.

Считываем данные


Ну что же, самое время попробовать прочитать карту.

В качестве управляющего МК взял всю ту же плату Arduino Mega, просто ради совместимости с пятивольтовыми уровнями.

Я решил не использовать вывод CLS, а опираться исключительно на данные CLOCK. Всё просто — если сигнал слишком долго не меняется, значит, карты больше нет и можно пробовать распознать то, что прочиталось. Получается примерно так:

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

Состояние ножки DATA меняется достаточно медленно, так что вполне возможно использовать «медленный» digitalRead.
На вывод CLOCK навешиваем внешнее прерывание. По спаду сигнала будут считываться данные. В цикле проверяем, как оно там:

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

Если данные не приходили больше, чем полсекунды, то флаг наличия карты снимается и вызывается функция, обрабатывающая полученные значения.
Для проверки выводим считанный массив бит в порт, проводим несколько раз картой и убеждаемся, что данные приходят одни и те же.

Декодирование


Что же теперь делать с этими битами? Считыватель получает данные со второй дорожки, где используется пятибитная кодировка: четыре бита данных и один бит чётности. На концах этого ряда стоят старт- и стоп-символы. Для первой дорожки это % и ?, для второй — ; и ?. После стоп-символа идёт один контрольный — LRC. Он также имеет свой бит чётности. Сами биты в каждом символе состоят из четвёрки в порядке от младшего к старшему, за которой следует бит чётности.
Итак, для начала необходимо понять, где вообще начинаются данные. Перед началом самих данных считыватель выдаёт некоторое количество нулевых бит, которые следует пропустить. Далее отщипываем очередную пятёрку битов и запихиваем её в очередной байт массива. Вообще, эту операцию можно совершать уже на этапе считывания из порта, обходясь при этом без буфера в триста байт, но тут для наглядности сделал так. Вариант с буфером такого размера может пригодиться при чтении сразу нескольких дорожек, чтобы просто задавать разные биты в зависимости от того, CLOCK какой дорожки изменился.
Теперь у нас есть набор из последовательностей по пять бит. Перед тем, как пытаться с этим что-то сделать, проверяем чётность, складывая все биты пятёрки и убеждаясь, что получилось нечётное число:

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

Магнитный считыватель выдаёт инвертированный сигнал, поэтому для начала с данными необходимо привести биты в символе к стандартному виду.
Кодовая таблица символов здесь используется вот такая:



Легко заметить, что символы идут в том же порядке, что и в таблице ASCII, соответственно, для преобразования пятибитного кода в текст необходимо выкинуть бит чётности, а к оставшемуся четырёхбитному числу прибавить код символа 0 (30h). Вот как-то так:

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

Теперь очередь LRC. От всех данных отбрасываем биты чётности — у LRC он свой. Далее делаем последовательный XOR всех четвёрок бит и сравниваем с последним значением. Если оно совпадает — можно выводить готовую строку.
Получилось вот так:

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

Перед тем, как выводить строку, убираем последний символ, так как LRC используется исключительно для проверки и больше нигде не печатается.
LRC также освобождает от большинства других проверок целостности данных, например, строка без начала или конца не пройдёт именно из-за несовпадения контрольной суммы.

Индикация


У меня нет штатного блока управления, поэтому индикаторный светодиод я подрубил к МК. Вместо запрета или разрешения на проход он теперь показывает успех чтения карты.
Кстати, несмотря на то, что в подобном оборудовании индикация часто уже рассчитана на непосредственное подключение, здешний светодиод требует наличия резистора.
В итоге вся программа получилась вот такая:

#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:
}

Всё успешно работает.

Вот как-то так


Как можно видеть. подключить такой считыватель не так уж и сложно. Впрочем, даже при всех преимуществах карт с магнитной полосой (которых на самом деле не так уж и много) такой девайс отдельно от его контроллера сейчас является скорее игрушкой, нежели реально применимым где-то устройством.
Кстати, немного позабавило описание данной штуки от производителя:

Считыватель магнитных карт PERCo-RM-3VR предназначен для считывания идентификационной информации с пластиковых банковских карт с магнитной полосой и передачи ее в контроллер. Устройство считывает только информацию о принадлежности к той или иной банковской системе и сроке действия карты. Никакая защищенная банковская информация с других полос карты не может быть считана, в том числе данные о владельце карты, номере счета и т.д.

Не знаю, что они там имели в виду под конфиденциальной информацией, но, как нетрудно догадаться, девайс читает всю вторую дорожку карты целиком.

Такие дела.

Возможно, захочется почитать и это:

  • ➤ Не дадим Windows Phone умереть! Как я написал свои клиенты VK, YouTube для Nokia Lumia? Сам себе экосистема
  • ➤ Запускаем матричный принтер от старого банковского терминала
  • ➤ OPQAM, IJKL, 67890. О том, как нам реально повезло с курсорными клавишами
  • ➤ Девайс HabrScore для хаброзависимых с блекджеком и …
  • ➤ Как, откуда и зачем появился Bioshock 2


Источник: https://habr.com/ru/companies/timeweb/articles/757376/


Интересные статьи

Интересные статьи

Инструкция для получения доступа к устройству в целях получения и хранения данных с него удобным образом. Зачем? Чтобы в перспективе настроить свой Умный Дом без ограничений приложений, которые предос...
Типовая схема подключения энкодера к микроконтроллеру ATmega8 представлена на рисунке 1. На схеме тактовые выводы A и B подтянуты с помощью резисторов R1 и R2 к питанию и дают низкий сигнал при срабат...
Киберзащита АСУ ТП пока остается для безопасников сложной задачкой на логику. С одной стороны, нельзя оставить без ИБ-мониторинга сеть, которая, например, обеспечивает теплом и светом население или пе...
Со времен первых сотовых телефонов, которые весили до одного килограмма и работали от силы пол часа без подзарядки, в мире технологий произошло множество полезных усовершенствован...
Хотите использовать линукс на работе, но корпоративный VPN не даёт? Тогда эта статья может помочь, хотя это не точно. Хочу заранее предупредить, что вопросы администрирования сетей я понимаю плох...