Освой Arduino играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Пример использования 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;
}
}
/* Александр Климов 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;
}
}