//Система контроля доступа по RFID-метке //Этот скетч для 4-байтных меток //включение и выключение реле //ручное открытие / закрытие //режим полного доступа //можно подключить вторую метку //автоблокировка замка по таймеру //настройка параметров //эти настройки можно записать в энергонезависимую память #include <EEPROM.h> //библиотека работы с памятью #include <LiquidCrystal_I2C.h> //библиотека работы с LCD через I2C #include <Wire.h> //библиотека работы с протоколом I2C #include <SPI.h> //библиотека работы с шиной SPI #include <MFRC522.h> //библиотека работы с RFID #define RST_PIN 9 //пин линии сброса (Reset) #define SS_PIN 10 //пин SDA(SS,Rx) //пины подключения FRID для Arduino UNO //MOSI - 11 //MISO - 12 //SCK - 13 //питание 3,3В !!! //Не забудь шунтировать выход от кнопок резистором 10 КОм const int BtDownPin = 5 ; //пин кнопки уменьшить. const int BtUpPin = 6 ; //пин кнопки увеличить const int BtSettingPin = 7 ; //пин кнопки настроек const int LockPin = 8 ; //пин управления реле замка //пины модуля I2C LCD-дисплея : //SDA - A4 //SCL - A5 //питание от шилда int Init_Eeprom_Addr = 1000; //номер ячейки в памяти EEPROM (0-1023), куда мы запишем ключ о //самом первом запуске программы //это нужно, так как при первом запуске в памяти нет наших настроек //и нужно загрузить значения по умолчанию byte Init_Key = 246; //это ключ (0-255). //Если при пуске скетча данные из ячейки Init_Eeprom не совпадают //с Init_Key, вводим в настройки значения по умолчанию, //иначе берем настройки из памяти int SettingNumber = 0; //пункт меню настройки boolean LastButtonSetting = LOW; //предыдущее состояние кнопки Настройки boolean CurrentButtonSetting = LOW; //текущее состояние кнопки Настройки boolean LastButtonDown = LOW; //--- Меньше boolean CurrentButtonDown = LOW; //--- Меньше boolean LastButtonUp = LOW; //--- Больше boolean CurrentButtonUp = LOW; //--- Больше boolean FlagSettingLoop = true; //были ли мы в цикле нажатия кнопки Настройки boolean FlagUpLoop = true; //были ли мы в цикле нажатия кнопки Больше boolean FlagDownLoop = true; //были ли мы в цикле нажатия кнопки Меньше //нужна при нажатии и удерживании кнопки ВВЕРХ unsigned long ButtonHoldTimerUp = 0; //нужна при нажатии и удерживании кнопки ВНИЗ unsigned long ButtonHoldTimerDown = 0; //по прошествии этого времени кнопка ВВЕРХ или ВНИЗ //перейдет в режим удержания, мс const long ButtonHoldIncTime = 500; boolean FlagUpHold = true; //были ли мы в цикле удержания кнопки ВВЕРХ boolean FlagDownHold = true; //были ли мы в цикле удержания кнопки ВНИЗ boolean FlagSettingIn = false; //находимся ли мы в режиме настройки boolean FullUnlock = false; //полная разблокировка замка (метка не нужна) boolean AutoLockON = false; //автоматическая блокировка замка после определенного времени int AutoLockCount = 0; //обратный отсчет автозакрытия замка long AUTO_LOCK_TIMER=10000; //время до автоматической блокировки замка //предел верхней границы, выше нее подниматься нельзя, мс const long AUTO_LOCK_TIMER_MAX=120000; //предел нижней границы, ниже нее опустить нельзя, мс const long AUTO_LOCK_TIMER_MIN=5000; unsigned long currentTime = 0; //нужна, чтобы убрать delay() byte readCard[4]; //массив для ID метки String MasterTag = "E34DED96"; //ЗАМЕНИТЕ этот ID метки на ID своей метки!!! //если в ID есть 0 (ноль), уберите его, например, //String MasterTag = "D3974F0A"; замените на //String MasterTag = "D3974FA"; String tagID = ""; //переменная для хранения ID //дополнительная метка, которую может подключить пользователь String tagSecondID2 = ""; char tagSecondID[9] = {'e', 'm', 'p', 't', 'y', ' ', ' ', ' '}; String LastIdMemory[5]={"empty", "empty", "empty", "empty", "empty"}; int i_LastIdMemory = 0; MFRC522 mfrc522(SS_PIN, RST_PIN); //создаем объект LiquidCrystal_I2C lcd(0x27,16,2); //Задаем адрес и размерность дисплея. void setup() //************************************************************** { pinMode (BtSettingPin, INPUT) ; //прописываем режим пинов pinMode (BtDownPin, INPUT) ; pinMode (BtUpPin, INPUT) ; pinMode (LockPin, OUTPUT) ; //инициализация SPI.begin(); // SPI шина mfrc522.PCD_Init(); //MFRC522 //если запись в настройки когда-то была, то читаем их if (EEPROM.read(Init_Eeprom_Addr) == Init_Key) { EEPROM.get(0,AUTO_LOCK_TIMER); //для типа long нужны 4 ячейки памяти EEPROM.get(4,FullUnlock); EEPROM.get(6,AutoLockON); for ( int k = 0; k < 8; k++) { EEPROM.get(8 + k,tagSecondID[k]); tagSecondID2 = tagSecondID2 + String(tagSecondID[k]); } } else //если записи не было, записываем в память значения по умолчанию { EEPROM.put(0,AUTO_LOCK_TIMER); EEPROM.put(4,FullUnlock); EEPROM.put(6,AutoLockON); for ( int k = 0; k < 8; k++) { EEPROM.put(8 + k,tagSecondID[k]); tagSecondID2 = tagSecondID2 + String(tagSecondID[k]); } //и ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); } if(FullUnlock == true) { digitalWrite(LockPin, LOW); } else { digitalWrite(LockPin, HIGH); } lcd.init(); //Инициализация lcd lcd.backlight(); //Включаем подсветку lcd.setCursor(0,0); //рисуем первую строчку lcd.print(" Access Control "); lcd.setCursor(0,1); //рисуем вторую строчку lcd.print(" RFID "); } void loop() //**************************************************** { // Ждем, пока не будет доступна новая метка while (getID()) //функция определения ID { lcd.setCursor(0, 0); boolean n = true; //проверяем, что данной метки нет среди последних 5-ти for ( int m = 0; m < 5; m++) { if( LastIdMemory[m] == tagID ) { n = false; } } //если это новая метка, записываем ее if(n) { n = true; LastIdMemory[4] = LastIdMemory[3]; LastIdMemory[3] = LastIdMemory[2]; LastIdMemory[2] = LastIdMemory[1]; LastIdMemory[1] = LastIdMemory[0]; LastIdMemory[0] = tagID; } //если открыт полный доступ if(FullUnlock == true) { digitalWrite(LockPin, LOW); } else { if (tagID == MasterTag || tagID == tagSecondID2) { lcd.print("Access Granted! "); LockUnlock(); //функция открытия/закрытия замка } else { lcd.print("Access Denied! "); } } lcd.setCursor(0, 1); lcd.print("ID : "); lcd.print(tagID); lcd.print(" "); delay(1000); //если открыт полный доступ if(FullUnlock == true) { lcd.setCursor(0, 0); lcd.print("Full Access "); lcd.setCursor(0, 1); lcd.print("No RFID needed "); } else { lcd.setCursor(0, 0); lcd.print("Access Control "); lcd.setCursor(0, 1); lcd.print("Scan your RFID "); } } if(FlagSettingIn == false) { if(FullUnlock == true) { lcd.setCursor(0, 0); lcd.print("Full Access "); lcd.setCursor(0, 1); lcd.print("No RFID needed "); } else { lcd.setCursor(0, 0); lcd.print("Access Control "); lcd.setCursor(0, 1); if(digitalRead(LockPin) == LOW && AutoLockON == true) { lcd.print("Auto Lock : "); AutoLockCount = int((AUTO_LOCK_TIMER - (millis() - currentTime))/1000); lcd.print(AutoLockCount); lcd.print(" "); } else { lcd.print("Scan your RFID "); } } } if(FullUnlock == false) { if(digitalRead(LockPin) == LOW && AutoLockON == true) { if(millis() - currentTime > AUTO_LOCK_TIMER) { digitalWrite(LockPin, HIGH); } } } //************ Нажата кнопка Настройки ************************************ //при каждом нажатии кнопки перескакиваем на следующий параметр в очереди. //Дойдя до конца меню, выходим из настроек //считываем состояние кнопки с устраненным дребезгом CurrentButtonSetting = debounce(LastButtonSetting, BtSettingPin); if(CurrentButtonSetting == HIGH) //если кнопка нажата { //эта переменная нужна для однократного нажатия кнопки, даже если ее удерживать if (FlagSettingLoop == true) { FlagSettingLoop = false; FlagSettingIn = true; //вошли в режим настройки SettingNumber = SettingNumber + 1; if (SettingNumber > 11) { SettingNumber = 0; FlagSettingIn = false; //вышли из режима настройки lcd.clear(); lcd.print("Access Control "); lcd.setCursor(0, 1); lcd.print("Scan your FRID "); } } } else { FlagSettingLoop = true; } LastButtonSetting = CurrentButtonSetting; //обновляем текущее значение кнопки if (FlagSettingIn == true) //если мы в настройках { //******************* Нажата кнопка Увеличить ************************** CurrentButtonUp = debounce(LastButtonUp, BtUpPin); ButtonPlus(CurrentButtonUp); //функция реакции на нажатие кнопки Увеличить LastButtonUp = CurrentButtonUp; //обновляем текущее значение кнопки //******************* Нажата кнопка Уменьшить ************************** CurrentButtonDown = debounce(LastButtonDown, BtDownPin); ButtonMinus(CurrentButtonDown); //функция реакции на нажатие кнопки Уменьшить LastButtonDown = CurrentButtonDown; //обновляем текущее значение кнопки SettingsOnLCD(SettingNumber); //отображаем строку с настройками } } //void loop() //********************** ФУНКЦИИ ********************************* //*********** Функция отображения настроек на LCD ************* void SettingsOnLCD(int SettingItem) { lcd.setCursor(0,0); switch (SettingItem) { case 1: //показать последний ID lcd.print("Last ID : "); lcd.setCursor(0,1); lcd.print(tagID); lcd.print(" "); break; case 2: //включение и выключение замка в ручном режиме lcd.print("Manual Lock : "); lcd.setCursor(0,1); if(digitalRead(LockPin) == HIGH) { lcd.print("LOCKED "); } else { lcd.print("UNLOCKED "); } break; case 3: //автоматическая блокировка замка после определенного времени lcd.print("Auto Lock : "); lcd.setCursor(0,1); if(AutoLockON == true) { lcd.print("ON "); } else { lcd.print("OFF "); } break; case 4: //время автоматической блокировки замка lcd.print("Auto Lock Timer "); lcd.setCursor(0,1); lcd.print(AUTO_LOCK_TIMER/1000); lcd.print(" s "); break; case 5: //полная разблокировка замка (метка не нужна) lcd.print("Full Unlock : "); lcd.setCursor(0,1); if(FullUnlock == true) { lcd.print("YES "); } else { lcd.print("NO "); } break; case 6: //подключение второй метки lcd.print("Add : "); lcd.print(tagID); lcd.print(" "); lcd.setCursor(0,1); lcd.print("Press UP Button "); break; case 7: //отключение второй метки lcd.print("Remove 2-nd RFID"); lcd.setCursor(0,1); lcd.print("Press UP Button "); break; case 8: //запись настроек в EEPROM lcd.print("Save Settings "); lcd.setCursor(0,1); lcd.print("Press UP Button "); break; case 9: //чтение настроек из EEPROM lcd.print("Read Settings "); lcd.setCursor(0,1); lcd.print("Press UP Button "); break; case 10: //загрузка значений по умолчанию lcd.print("Load Default "); lcd.setCursor(0,1); lcd.print("Press UP Button "); break; case 11: //показать последние 5 ID lcd.print("Show Last 5 IDs "); lcd.setCursor(0,1); lcd.print(i_LastIdMemory+1); lcd.print(" "); lcd.print(LastIdMemory[i_LastIdMemory]); lcd.print(" "); break; } } //********* Функция кнопки Больше, Увеличить, Плюс и т.п. ************* void ButtonPlus(boolean BtState) //BtState - состояние кнопки (нажата-отжата) { if(BtState == HIGH) //кнопка нажата { if (SettingNumber == 4 ) //пункты настроек, где нужен разгон параметра в режиме удержания { if(FlagUpHold == true) //отсечка времени удержания кнопки { ButtonHoldTimerUp = millis(); FlagUpHold = false; } if(millis() - ButtonHoldTimerUp > ButtonHoldIncTime) //сначала однократное нажатие, но если держать кнопку более //ButtonHoldIncTime, она переходит в режим удержания { ButtonPlusAction(SettingNumber, true); } if(FlagUpLoop == true) //однократное нажатие, даже если удерживать кнопку, //пока не прошло ButtonHoldIncTime { FlagUpLoop = false; ButtonPlusAction(SettingNumber, false); } } else //пункты настроек, где нужно только однократное нажатие кнопки { if(FlagUpLoop == true) { FlagUpLoop = false; ButtonPlusAction(SettingNumber, false); } } } else //кнопка отжата { FlagUpLoop = true; FlagUpHold = true; } } //******** Функция изменения параметров при нажатии кнопки Больше ********** void ButtonPlusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //показать последний ID return; //нет действий break; case 2: //включение и выключение замка в ручном режиме LockUnlock(); break; case 3: //автоматическая блокировка замка после определенного времени TimeLock(); break; case 4: //время автоматической блокировки замка AUTO_LOCK_TIMER = AUTO_LOCK_TIMER + Inc_Val_Calc(IncTrigger); //если пытаемся уйти за верхний предел if (AUTO_LOCK_TIMER > AUTO_LOCK_TIMER_MAX) { AUTO_LOCK_TIMER = AUTO_LOCK_TIMER_MAX; lcd.setCursor(0,1); lcd.print("TimeLimit="); //выводим подсказку lcd.print(AUTO_LOCK_TIMER_MAX/1000); lcd.print("s "); delay(2000); } break; case 5: //полная разблокировка замка (метка не нужна) FullUnlockFunc(); break; case 6: //подключение второй метки tagSecondID2 = tagID; lcd.setCursor(0,1); lcd.print("Adding... OK "); delay(2000); break; case 7: //отключение второй метки tagSecondID2 = "empty"; for ( int k = 0; k < 8; k++) { tagSecondID[k] = tagSecondID2.charAt(k); EEPROM.put(8 + k,tagSecondID[k]); } lcd.setCursor(0,1); lcd.print("Removing... OK "); delay(2000); break; case 8: //запись настроек в EEPROM EEPROM.put(0,AUTO_LOCK_TIMER); EEPROM.put(4,FullUnlock); EEPROM.put(6,AutoLockON); for ( int k = 0; k < 8; k++) { tagSecondID[k] = tagSecondID2.charAt(k); EEPROM.put(8 + k,tagSecondID[k]); } //ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); lcd.setCursor(0,1); lcd.print("Saving... OK "); delay(2000); break; case 9: //чтение настроек из EEPROM EEPROM.get(0,AUTO_LOCK_TIMER); //для типа long нужны 4 ячейки памяти EEPROM.get(4,FullUnlock); EEPROM.get(6,AutoLockON); tagSecondID2 = ""; for ( int k = 0; k < 8; k++) { EEPROM.get(8 + k,tagSecondID[k]); tagSecondID2 = tagSecondID2 + String(tagSecondID[k]); } lcd.setCursor(0,1); lcd.print("Reading... OK "); delay(2000); break; case 10: //загрузка значений по умолчанию. Берем из начала скетча AUTO_LOCK_TIMER = 10000; FullUnlock = false; AutoLockON = false; tagSecondID2 = "empty"; for ( int k = 0; k < 8; k++) { tagSecondID[k] = tagSecondID2.charAt(k); EEPROM.put(8 + k,tagSecondID[k]); } lcd.setCursor(0,1); lcd.print("Loading... OK "); delay(2000); break; case 11: //показать последние 5 ID i_LastIdMemory = i_LastIdMemory + 1; if(i_LastIdMemory > 4) { i_LastIdMemory = 0; } break; } } //********* Функция кнопки Меньше, Уменьшить, Минус и т.п. ************* void ButtonMinus(boolean BtState) //BtState - состояние кнопки (нажата-отжата) { if(BtState == HIGH) //кнопка нажата { if (SettingNumber == 4 ) //пункты настроек, где нужен разгон параметра в режиме удержания { if(FlagDownHold == true) //отсечка времени удержания кнопки { ButtonHoldTimerDown = millis(); FlagDownHold = false; } if(millis() - ButtonHoldTimerDown > ButtonHoldIncTime) //сначала однократное нажатие, но если держать кнопку более //ButtonHoldIncTime, она переходит в режим удержания { ButtonMinusAction(SettingNumber, true); } if(FlagDownLoop == true) //однократное нажатие, даже если удерживать кнопку, //пока не прошло ButtonHoldIncTime { FlagDownLoop = false; ButtonMinusAction(SettingNumber, false); } } else //пункты настроек, где нужно только однократное нажатие кнопки { if(FlagDownLoop == true) { FlagDownLoop = false; ButtonMinusAction(SettingNumber, false); } } } else //кнопка отжата { FlagDownLoop = true; FlagDownHold = true; } } //******** Функция изменения параметров при нажатии кнопки Меньше ********** void ButtonMinusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //показать последний ID return; //нет действий break; case 2: //включение и выключение замка в ручном режиме LockUnlock(); break; case 3: //автоматическая блокировка замка после определенного времени TimeLock(); break; case 4: //время автоматической блокировки замка AUTO_LOCK_TIMER = AUTO_LOCK_TIMER - Inc_Val_Calc(IncTrigger); //если пытаемся уйти за верхний предел if (AUTO_LOCK_TIMER < AUTO_LOCK_TIMER_MIN) { AUTO_LOCK_TIMER = AUTO_LOCK_TIMER_MIN; lcd.setCursor(0,1); lcd.print("TimeLimit="); //выводим подсказку lcd.print(AUTO_LOCK_TIMER_MIN/1000); lcd.print("s "); delay(2000); } break; case 5: //полная разблокировка замка (метка не нужна) FullUnlockFunc(); break; case 6: //подключение второй метки lcd.setCursor(0,1); lcd.print("ID2 : "); lcd.print(tagSecondID2); lcd.print(" "); delay(2000); break; case 7: //отключение второй метки lcd.setCursor(0,1); lcd.print("ID2 : "); lcd.print(tagSecondID2); lcd.print(" "); delay(2000); break; case 11: //показать последние 5 ID i_LastIdMemory = i_LastIdMemory - 1; if(i_LastIdMemory < 0) { i_LastIdMemory = 4; } break; } } //*************** Функция устранения дребезга контактов *********** boolean debounce(boolean last, int ButtonPinDebounce) { boolean current = digitalRead(ButtonPinDebounce); //считываем текущее состояние кнопки if(last != current) //если оно иное, чем предыдущее... { delay(50); //ждем 50 мс current = digitalRead(ButtonPinDebounce); //считываем состояние снова } return current; //возвращаем текущее состояние кнопки } //*************** Функция разгона приращения параметра ************ float Inc_Val_Calc(boolean IncTrigger) { static int count_inc = 0; //счетчик итераций if (IncTrigger == true) //пришел сигнал на разгон { count_inc = count_inc + 1; //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 30) { return 1000; } if (count_inc > 30 && count_inc <= 60) { return 2000; } if (count_inc > 60) { return 5000; } } else { count_inc = 0; return 1000; //приращение по умолчанию } } //******************* открытия/закрытия замка **************** void LockUnlock() { if(FullUnlock == true) { lcd.setCursor(0, 1); lcd.print("Full Access !!! "); delay(1000); } else { if(digitalRead(LockPin) == LOW) { digitalWrite(LockPin, HIGH); } else { digitalWrite(LockPin, LOW); currentTime = millis(); } } } //******************* автоблокировка замка **************** void TimeLock() { if(AutoLockON == true) { AutoLockON = false; } else { AutoLockON = true; currentTime = millis(); } } //******************* полная разблокировка замка **************** void FullUnlockFunc() { if(FullUnlock == true) { FullUnlock = false; currentTime = millis(); } else { FullUnlock = true; digitalWrite(LockPin, LOW); } } //********** функция чтения новой метки, если она доступна ************* boolean getID() { // Получение готовности для чтения PICC карт if ( ! mfrc522.PICC_IsNewCardPresent()) { //Продолжать, если к RFID считывателю поднесена новая карта return false; } if ( ! mfrc522.PICC_ReadCardSerial()) { //Когда карта поднесена, считать серийный номер и продолжить return false; } tagID = ""; for ( uint8_t i = 0; i < 4; i++) { //Карты MIFARE, кторые мы используем, содержат 4-байтовый UID //readCard[i] = mfrc522.uid.uidByte[i]; //Сложить эти 4 байта в одну переменную String tagID.concat(String(mfrc522.uid.uidByte[i], HEX)); } tagID.toUpperCase(); mfrc522.PICC_HaltA(); //остановить чтение return true; }