Освой Arduino играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Чтение данных транспортных карточек (RFID)

Пример использования RFID-модуля RC522 для чтения карточек.

Автор оригинального скетча описал создание кода на странице Считыватель карточек RFID RC522 в домашнем хозяйстве.

Первоначально скетч писался в 2014 году. Кстати, у меня транспортные билеты остались от тех времён (2012, 2013, 2014). Где-то в 2017 году поменяли частично формат - это затронуло данные с датами и числом оставшихся поездок. Автор переписал скетч. Я решил выложить немного подправленные два варианта для новых и старых билетов.

Скетч для новых карт с 2017 года.


/* Александр Климов http://developer.alexanderklimov.ru/arduino/
  version 0.3 25.12.2019 Arduino Метро Единый билет RC522 Card Read Module RFID
  ====================================
  Чтение данных с бумажного билета "Единый" на метро и на наземном транспорте.
  ----------------
  Выводим в Serial Monitor информацию о карте:
  1) тип карты
  2) Номер билета (который отпечатан на нём)
  3) Дата выдачи билета
  4) Срок действия (сколько дней)
  5) Срок действия (до какого числа)
  6) количество оставшихся поездок
  7) Card UID — уникальный идентификатор карты
  ------------------
  Нам понадобятся:
  1) Arduino.
  2) RC522 Card Read Module

  — Поддерживаемые типы карт: MIFARE S50, MIFARE S70,
  MIFARE UltraLight, MIFARE Pro, MIFARE DESfire.
  Напряжение / питание: 3.3 v.
  3) Библиотека "MFRC522"
  ============================================
  Автор оригинального скетча
  Считыватель карточек RFID RC522 в домашнем хозяйстве.
  http://mysku.ru/blog/aliexpress/23114.html
*/

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

/*
  подключение для Arduino Uno описано в статье
  http://developer.alexanderklimov.ru/arduino/rfid_rc522.php
  ------------------------------------------------------------
  -------------------------------------
            MFRC522      Arduino
            Reader/PCD   Uno/101/Nano
  Signal      Pin          Pin
  -------------------------------------
  RST/Reset   RST          9
  SPI SS      SDA(SS)      10
  SPI MOSI    MOSI         11
  SPI MISO    MISO         12
  SPI SCK     SCK          13
*/

#define SS_PIN 10
#define RST_PIN 9

MFRC522 mfrc522(SS_PIN, RST_PIN);       // объект MFRC522
unsigned long uidDec, uidDecTemp; // для отображения номера карточки в десятичном формате
byte bCounter, readBit;
unsigned long ticketNumber, issueDate;

void setup() {
  Serial.begin(9600);
  SPI.begin();            // инициализация SPI
  mfrc522.PCD_Init();     // инициализация MFRC522
  Serial.println("Приложите карту.../Waiting for card...");
}

void loop() {
  // Поиск новой карточки
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  // Выбор карточки
  if ( ! mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  uidDec = 0;
  Serial.println("================================================");

  // Выдача серийного номера карточки
  Serial.print("Серийный номер карты/Card UID: ");

  for (byte i = 0; i < mfrc522.uid.size; i++) {
    uidDecTemp = mfrc522.uid.uidByte[i];
    uidDec = uidDec * 256 + uidDecTemp;
  }

  Serial.println(uidDec);
  Serial.println("================================================");

  // Выдача типа карты
  byte piccType = mfrc522.PICC_GetType(mfrc522.uid.sak); // запрос типа карты
  Serial.print("Тип карты/Card type: ");
  Serial.println(mfrc522.PICC_GetTypeName(piccType)); // трансляция типа в читаемый вид
  Serial.println("================================================");

  if (piccType != MFRC522::PICC_TYPE_MIFARE_UL) { // если не билетная карта
    Serial.print("Неизвестная карта/Not a valid card: "); // так и говорим
    Serial.println(piccType);
    Serial.println("================================================");
    // Halt PICC
    mfrc522.PICC_HaltA();                   // остановка чипа
    return;
  }

  // сюда мы придём, если чип правильный
  byte status;
  byte byteCount;
  byte buffer[18]; // длина массива (16 байт + 2 байта контрольная сумма)
  byte pages[3] = {4, 6, 8}; // страницы с данными

  byte pageByte; // счетчик байтов страницы
  byteCount = sizeof(buffer);
  byte bCount = 0;

  for (byte i = 0; i < 3; i++) { // начинаем читать страницы
    status = mfrc522.MIFARE_Read(pages[i], buffer, &byteCount);

    if (status != MFRC522::STATUS_OK) {
      Serial.print("Read error: ");
      Serial.println(mfrc522.GetStatusCodeName(status));
    }
    else {
      if (pages[i] == 4) {
        bCounter = 0; // 32-битный счетчик для номера

        // биты 0-3
        for (bCount = 0; bCount < 4; bCount++) {
          readBit = bitRead(buffer[6], (bCount + 4));
          setBitsForGood(readBit);
        }

        // биты 4 - 27
        for (pageByte = 5; pageByte > 2; pageByte--) {
          for (bCount = 0; bCount < 8; bCount++) {
            readBit = bitRead(buffer[pageByte], bCount);
            setBitsForGood(readBit);
          }
        }

        // биты 28-31
        for (bCount = 0; bCount < 4; bCount++) {
          readBit = bitRead(buffer[2], bCount);
          setBitsForGood(readBit);
        }

        Serial.println("Напечатанный номер карты/Ticket number: ");
        Serial.println(ticketNumber, DEC);
        ticketNumber = 0;
        Serial.println("================================================");
      }

      // получение даты
      if (pages[i] == 6) {
        bCounter = 0; // 32-битный счетчик для номера

        // 0-3
        for (bCount = 0; bCount < 4; bCount++) {
          readBit = bitRead(buffer[1], bCount + 4);
          setBitsForGood(readBit);
        }

        // биты 4-11
        for (bCount = 0; bCount < 8; bCount++) {
          readBit = bitRead(buffer[0], bCount);
          setBitsForGood(readBit);
        }

        Serial.print("Выпущена/Issued on: ");
        printIssueDate(ticketNumber);
        issueDate = ticketNumber;

        ticketNumber = 0;
        bCounter = 0;

        // биты 16 - 31
        for (pageByte = 3; pageByte > 1; pageByte--) {
          for (bCount = 0; bCount < 8; bCount++) {
            readBit = bitRead(buffer[pageByte], bCount);
            setBitsForGood(readBit);
          }
        }

        if (ticketNumber == 14400) {
          Serial.println("Действителен 5 дней/Ticket valid for 5 days");
          Serial.print("Действителен до/Valid till: ");
          printIssueDate(issueDate + 4);
        } else {
          if (ticketNumber == 62592) {
            Serial.print("Действителен 90 дней/Ticket valid for 90 days");
            Serial.println("Действителен до/Valid till: ");
            printIssueDate(issueDate + 89);
          }
        }
      }

      // получение количества оставшихся поездок
      if (pages[i] == 8) {

        Serial.print("Осталось поездок/Trip reminder: "); // количество оставшихся поездок
        Serial.print(buffer[0], DEC);
        Serial.println();
      }
    }
  }
  // Halt PICC
  mfrc522.PICC_HaltA();
}

void printIssueDate(unsigned int incoming) {

  boolean isLeap = true; // признак високосного года

  // последний по порядку день месяца для обычного года
  int days[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

  byte dayOfMonth, monthCounter;
  unsigned int yearCount;

  // считаем год и количество дней, прошедших с выдачи билета
  for (yearCount = 2016; incoming > 366; yearCount++) {
    if ((yearCount % 4 == 0 && yearCount % 100 != 0) ||  yearCount % 400 == 0) {
      incoming = incoming - 366;
      isLeap = true;
    } else {
      incoming = incoming - 365;
      isLeap = false;
    }
  }

  for (monthCounter = 0; incoming > days[monthCounter]; monthCounter++) { // узнаем номер месяца
  }

  // считаем день месяца
  if (isLeap == true) { // если високосный год
    if (days[monthCounter - 1] > 31) { // если не первый месяц, то добавляем к последнему дню месяца единицы
      dayOfMonth = incoming - (days[monthCounter - 1] + 1);
    } else {
      // если первый - ничего не добавляем, потому что сдвиг начинается с февраля
      dayOfMonth = incoming - (days[monthCounter - 1]); 
    }
  } else {
    dayOfMonth = incoming - (days[monthCounter - 1]); // если не високосный год
  }

  Serial.print(dayOfMonth);
  Serial.print(".");
  Serial.print(monthCounter);
  Serial.print(".");
  Serial.print(yearCount);
  Serial.println();
}

void setBitsForGood(byte daBeat) {
  if (daBeat == 1) {
    bitSet(ticketNumber, bCounter);
    bCounter = bCounter + 1;
  }
  else {
    bitClear(ticketNumber, bCounter);
    bCounter = bCounter + 1;
  }
}

Скетч для старых билетов до 2017 года


/* Александр Климов http://developer.alexanderklimov.ru/arduino/
  version 0.2 21.12.2019 Arduino Метро Единый билет RC522 Card Read Module RFID
  ====================================
  Чтение данных с бумажного билета "Единый" на метро и на наземном транспорте.
  ----------------
  Выводим в Serial Monitor информацию о карте:
  1) тип карты
  2) Номер билета (который отпечатан на нём)
  3) Дата выдачи билета
  4) Срок действия (сколько дней)
  5) Срок действия (до какого числа)
  6) количество оставшихся поездок
  7) Card UID — уникальный идентификатор карты
  ------------------
  Нам понадобятся:
  1) Arduino.
  2) RC522 Card Read Module

  — Поддерживаемые типы карт: MIFARE S50, MIFARE S70,
  MIFARE UltraLight, MIFARE Pro, MIFARE DESfire.
  Напряжение / питание: 3.3 v.
  3) Библиотека "MFRC522"
  ============================================
  Автор оригинального скетча
  Считыватель карточек RFID RC522 в домашнем хозяйстве.
  http://mysku.ru/blog/aliexpress/23114.html
*/

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

/*
  подключение для Arduino Uno описано в статье
  http://developer.alexanderklimov.ru/arduino/rfid_rc522.php
  ------------------------------------------------------------
  -------------------------------------
            MFRC522      Arduino
            Reader/PCD   Uno/101/Nano
  Signal      Pin          Pin
  -------------------------------------
  RST/Reset   RST          9
  SPI SS      SDA(SS)      10
  SPI MOSI    MOSI         11
  SPI MISO    MISO         12
  SPI SCK     SCK          13
*/

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

#define SS_PIN 10
#define RST_PIN 9

MFRC522 mfrc522(SS_PIN, RST_PIN); // объект MFRC522
unsigned long uidDec, uidDecTemp; // для отображения номера карточки в десятичном формате.
byte bCounter, readBit;
unsigned long ticketNumber;

void setup() {
  Serial.begin(9600);
  SPI.begin(); // инициализация SPI.
  mfrc522.PCD_Init(); // инициализация MFRC522.
  Serial.println("Приложите карту.../Waiting for card...");
}

void loop() {
  // Поиск новой карточки.
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  // Выбор карточки.
  if ( ! mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  uidDec = 0;
  Serial.println("================================================");
  
  // Выдача серийного номера карточки.
  Serial.println("Серийный номер карты/Card UID: ");

  for (byte i = 0; i < mfrc522.uid.size; i++) {
    uidDecTemp = mfrc522.uid.uidByte[i];
    uidDec = uidDec * 256 + uidDecTemp;
  }

  Serial.print(uidDec);
  Serial.println("================================================");

  // Выдача типа карты
  byte piccType = mfrc522.PICC_GetType(mfrc522.uid.sak); // запрос типа.
  Serial.println("Тип карты/Card type: ");
  Serial.print(mfrc522.PICC_GetTypeName(piccType)); // трансляция типа в читаемый вид.
  Serial.println("================================================");

  if (piccType != MFRC522::PICC_TYPE_MIFARE_UL) { // если не билетная карта.
    Serial.println("Неизвестная карта/ Not a valid card: Type");
    Serial.print(piccType);
    Serial.println("================================================");

    // Halt PICC
    mfrc522.PICC_HaltA(); // остановка чипа.
    return;
    delay(1);
  }

  // сюда мы придем, если чип правильный.
  byte status;
  byte byteCount;
  byte buffer[18]; // длина массива (16 байт + 2 байта контрольная сумма).
  byte pages[2] = {
    4, 8
  }; // страницы с данными.

  byte pageByte; // счетчик байтов страницы
  byteCount = sizeof(buffer);
  byte bCount = 0;

  for (byte i = 0; i < 2; i++) { // начинаем читать страницы.
    status = mfrc522.MIFARE_Read(pages[i], buffer, &byteCount);
    if (status != MFRC522::STATUS_OK) {
      Serial.print("Read error: ");
      Serial.println(mfrc522.GetStatusCodeName(status));
    }
    else {
      if (pages[i] == 4) {
        bCounter = 0; // 32-битный счетчик для номера.
        // биты 0-3.
        for (bCount = 0; bCount < 4; bCount++) {
          readBit = bitRead(buffer[6], (bCount + 4));
          setBitsForGood(readBit);
        }
        // биты 4 - 27.
        for (pageByte = 5; pageByte > 2; pageByte--) {
          for (bCount = 0; bCount < 8; bCount++) {
            readBit = bitRead(buffer[pageByte], bCount);
            setBitsForGood(readBit);
          }
        }
        // биты 28-31.
        for (bCount = 0; bCount < 4; bCount++) {
          readBit = bitRead(buffer[2], bCount);
          setBitsForGood(readBit);
        }

        Serial.println("Напечатанный номер карты/Ticket number: ");
        Serial.print(ticketNumber, DEC);
        Serial.println("================================================");
      }

      if (pages[i] == 8) { // читаем дату выдачи.
        Serial.println("Дата покупки карты/ Issued: ");
        Serial.println("Число Месяц Год");
        // количество дней с 01.01.1992 в десятичном формате, 256 - сдвиг на 8 бит.
        unsigned int issueDate = buffer[0] * 256 + buffer[1];
        printIssueDate(issueDate);
        Serial.println("================================================");
        Serial.println("Срок действия карты/Good for (days): "); // срок действия.
        Serial.print(buffer[2], DEC);
        Serial.println(" дней");
        Serial.println("================================================");
        Serial.println("Количество оставшихся поездок/Trip reminder: ");

        // количество оставшихся поездок.
        Serial.print(buffer[5], DEC);
        Serial.println("================================================");
      }
    }
  }
  // Halt PICC
  mfrc522.PICC_HaltA();
}

void printIssueDate(unsigned int incoming) {
  boolean isLeap = true; // признак високосного года
  int days[] = {
    // последний по порядку день месяца для обычного года.
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
  };

  byte dayOfMonth, monthCounter;
  unsigned int yearCount;

  // подогнал под ответ, но возможно это как раз необходимая.
  // коррекция, потому что начало отсчета - 01.01.1992, а не 00.01.1992.
  // incoming = incoming+1;
  // считаем год и количество дней, прошедших с выдачи билета.
  for (yearCount = 1992; incoming > 366; yearCount++) {
    if ((yearCount % 4 == 0 && yearCount % 100 != 0) || yearCount % 400 == 0) {
      incoming = incoming - 366;
      isLeap = true;
    }
    else {
      incoming = incoming - 365;
      isLeap = false;
    }
  }

  // узнаем номер месяца.
  for (monthCounter = 0; incoming > days[monthCounter]; monthCounter++) {
  }

  // считаем день месяца.
  if (isLeap == true) { // если високосный год.
    // если не первый месяц, то добавляем к последнему дню месяца единицы.
    if (days[monthCounter - 1] > 31) {
      dayOfMonth = incoming - (days[monthCounter - 1] + 1);
    }
    else {
      dayOfMonth = incoming - (days[monthCounter - 1]);
    }
  }
  // если первый - ничего не добавляем, потому что сдвиг начинается с февраля.
  else {
    dayOfMonth = incoming - (days[monthCounter - 1]); // если не високосный год.
  }

  Serial.print(dayOfMonth);
  Serial.print(".");
  Serial.print(monthCounter);
  Serial.print(".");
  Serial.print(yearCount);
}

void setBitsForGood(byte daBeat) {
  if (daBeat == 1) {
    bitSet(ticketNumber, bCounter);
    bCounter = bCounter + 1;
  }
  else {
    bitClear(ticketNumber, bCounter);
    bCounter = bCounter + 1;
  }
}
Транспортные билеты
Реклама