Przy zatrudnianiu osób "na godziny" często zachodzi problem kontroli czasu pracy. Dawniej po prostu robiło się notatki czasów przyjścia do pracy i jej zakończenia. Taki sposób rozliczania pracowników posiada jednak podstawową wadę. Ktoś na jakiejś podstawie musi te wpisy weryfikować. Chciałbym w tym odcinku przedstawić elektroniczny system kontroli czasu pracy, który jest pozbawiony tego mankamentu. W warunkach domowych można go wykorzystać do rozliczeń płatności np. za opiekę nad dzieckiem czy osobą w podeszłym wieku. Przed zaprojektowaniem systemu postawiono kilka założeń wstępnych:
Realizację takiego układu umożliwia wykorzystanie modułów: zegara czasu rzeczywistego, radiowego przesyłania danych oraz rejestratora RFID z kartami elektronicznymi i brelokami.
Rejestrator czasu pracy jaki wykonamy będzie składał się z dwóch autonomicznych układów: logera-nadajnika oraz odbiornika-rejestratora (Fig. 1). Oba układy będą oparte na mikrokontrolerach Arduino (Nano oraz Uno) i będą zasilane przez ogniwa litowo-jonowe 18650. W związku z tym, że ich napięcie wynosi około 4,2V, do zasilania obu układów zastosujemy dwie identyczne przetwornice impulsowe step-up podwyższające napięcie do 5V (Fig. 2). Oba układy będą także zaopatrzone w moduły ładowania ogniw Li-jon 18650 TP4056 (Fig. 3).
Zadaniem układu loggera z nadajnikiem będzie rejestrowanie migracji osób. Każda osoba wchodząc lub wychodząc z pomieszczenia będzie logowała się do systemu za pomocą jednego urządzenia RFID. Rejestrator odnotuje czas oraz status (wejście/wyjście) każdej z nich. Realizacja takiego zadania wymaga uwzględnienia możliwych awarii systemu np. spowodawanych zanikiem zasilania. Załóżmy, że osoba wchodząca rejestruje swoje wejście za pomocą przyłożenia karty zbliżeniowej PICC. System w odpowiednich zmiennych zapamięta czas i status tego zdarzenia. Jeśli przed wyjściem tej samej osoby z pomieszczenia nastąpi awaria, zmienne zostaną zresetowane i system przy wyjściu tej osoby zarejestruje niepoprawny status wejścia. Widać więc, że obok rejestracji UID osoby istnieje konieczność rozróżnienia konkretnego stanu wejścia lub wyjścia. Można to zrealizować na dwa sposoby. Pierwszym z nich jest rejestracja aktualnego statusu każdej z osób w pamięci EEPROM mikrokontrolera. Przy każdym zdarzeniu, dla każdego UID, układ może trwale zapisywać statusy w układzie scalonym. Istnieją dwie słabe strony takiego rozwiązania. Po pierwsze liczba możliwych cykli zapisu / odczytu do i z pamięci EEPROM nie jest nieograniczona. Według danych producentów możliwe jest wykonanie około 100000 takich cykli. Druga wada tego rozwiązania, podobnie jak poprzednio, jest związana z możliwością rejestracji nieprawdziwego statusu osób. Jeśli pomiędzy dwoma zdarzeniami nastąpiła awaria systemu, do pamięci EEPROM może być zapisywany niepoprawny status kolejnego zdarzenia. W realizowanym układzie proponuję inne rozwiązanie. Układ zostanie wyposażony w dwa przyciski. Za pomocą, jednego z nich każda rejestrowana osoba będzie samodzielnie deklarowała status aktualnego zdarzenia. Drugi przycisk wykorzystamy do zatwierdzenia wprowadzanych danych.
Projekt układu czytnika kart zbliżeniowej PICC z nadajnikiem oprzemy na projekcie rejestratora RFID RC522. Modyfikacje układu będą obejmowały: zmianę wyświetlacza LCD z 16 × 2 na 20 × 4, dodanie modułu zegara czasu rzeczywistego (Fig. 5), dodanie buzzera oraz dodanie dwóch przycisków sterujących. Zmiana modułu wyświetlacza umożliwi stałe wyświetlanie czasu oraz daty. Moduł zegara czasu rzeczywisytego, podobnie jak wyświetlacz LCD, wykorzystuje magistralę I2C. Sygnały SDA oraz SCL podpinamy więc odpowiednio do pinów A4 i A5 układu arduino. Buzzer poprzez rezystor 100Ω ograniczający prąd podpinamy do pinu cyfrowego D4. Przyciski podpinamy bezpośrednio do pinów cyfrowych D2 i D3 mikrokontrolera. Zastosujemy podciąganie programowe pullup. Po zarejestrowaniu zdarzenia układ będzie miał zadanie bezprzewodowo przesłać niezbędne informacje (czas zdarzenia, identyfikator osoby oraz status zdarzenia) do układu odbiornika z rejestratorem. Wykorzystamy do tego nadajnik komunikacji radiowej FS100A (Fig. 6).
#include <SPI.h> // Attach SPI interface library #include <MFRC522.h> // Attach RC522 module library #include <Wire.h> // Attach I2C interface library #include <LiquidCrystal_I2C.h> // Attach LCD I2C library #include <VirtualWire.h> // Attach RF modules library #define DS3231_I2C_ADDRESS 0x68 // Set up real time clock #define EVENT_PIN 2 // Define no of the EVENT button pin #define OK_PIN 3 // Define no of the OK button pin #define BUZZER_PIN 4 // Define no of the buzzer pin #define LED_PIN 5 // Define no of the LED pin #define TRANSMIT_PIN 6 // Define FS100A data transmit pin #define RST_PIN 9 // Define connection of the RST_PIN of the RFID module #define SS_PIN 10 // Define connection of the SS_PIN of the RFID module LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set up LCD MFRC522 mfrc522(SS_PIN, RST_PIN); // Set up mfrc522 (RFID) boolean event = 0; // Event selection variable - 0 / 1 boolean sending = 0; // Sending selection variable - 0 / 1 String eventName = "input"; // Default event name variable - input unsigned long timelight; // timelight variable to switch off the backlight of the LCD int screen = 0; int count_2 = 0; unsigned long time_screen_2 = 0; unsigned long screen_2_start = 0; byte bcdToDec(byte val) { // Konwersja liczby binarnej do postaci dziesiętnej return ( (val / 16 * 10) + (val % 16) ); } void setup() { pinMode(EVENT_PIN, INPUT_PULLUP); // Event selector button pinMode(OK_PIN, INPUT_PULLUP); // OK button pinMode(BUZZER_PIN, OUTPUT); // Buzzer pin declaration pinMode(LED_PIN, OUTPUT); // LED pin declaration Wire.begin(); // Initiate the wire library and join the I2C bus as a master or slave vw_set_tx_pin(TRANSMIT_PIN); // Set up virtual vire (FS100A) TX pin vw_setup(2000); // Set up virtual vire transmission speed digitalWrite(LED_PIN, HIGH); // Turn off the LED Serial.begin(9600); lcd.begin(20, 4); // Initialization the interface to the LCD screen and specify the dimensions (width and height) lcd.backlight(); // Switch LCD backlight on lcd.setCursor(3, 0); // Set cursor position to the 3 collumn and 0 row lcd.print("RFID controler"); // Write the text on LCD lcd.setCursor(10, 1); // Set cursor position to the 10 collumn and 1 row lcd.print("&"); // Write the text on LCD lcd.setCursor(5, 2); // Set cursor position to the 5 collumn and 2 row lcd.print("transsmiter"); // Write the text on LCD lcd.setCursor(1, 3); // Set cursor position to the 1 collumn and 3 row lcd.print("Tomasz Bartus'2020"); // Write the text on LCD delay(500); SPI.begin(); // Initialization of the SPI interface mfrc522.PCD_Init(); // Initialize Proximity Coupling Device delay(2000); lcd.clear(); // Clear the screen timelight = millis(); screen = 1; } void readDS3231time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year) { Wire.beginTransmission(DS3231_I2C_ADDRESS); Wire.write(0); // ustawia rejestr DS3231 na 00h Wire.endTransmission(); Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // żąda 7 bajtów danych od modułu DS3231 począwszy od rejestru 00h *second = bcdToDec(Wire.read() & 0x7f); *minute = bcdToDec(Wire.read()); *hour = bcdToDec(Wire.read() & 0x3f); *dayOfWeek = bcdToDec(Wire.read()); *dayOfMonth = bcdToDec(Wire.read()); *month = bcdToDec(Wire.read()); *year = bcdToDec(Wire.read()); } void displayTime() { byte second, minute, hour, dayOfWeek, dayOfMonth, month, year; readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year); // retrieve data from DS3231 // Przekazanie danych do wyświetlenie na LCD // Time // podczas wyświetlania konwertuje wartość zmiennej typu bitowego do postaci dziesiętnej if (hour < 10) { // Hours lcd.setCursor(0, 3); // Set cursor position to the 0 collumn and 3 row lcd.print("0"); lcd.print(hour, DEC); } else { lcd.setCursor(0, 3); // Set cursor position to the 0 collumn and 3 row lcd.print(hour, DEC); } // lcd.setCursor(2,1); lcd.print(":"); if (minute < 10) { // Minutes lcd.setCursor(3, 3); // Set cursor position to the 3 collumn and 3 row lcd.print("0"); lcd.print(minute, DEC); } else { lcd.setCursor(3, 3); // Set cursor position to the 3 collumn and 3 row lcd.print(minute, DEC); } lcd.print(":"); if (second < 10) { // Seconds lcd.setCursor(6, 3); // Set cursor position to the 6 collumn and 3 row lcd.print("0"); lcd.print(second, DEC); } else { lcd.setCursor(6, 3); // Set cursor position to the 6 collumn and 3 row lcd.print(second, DEC); } lcd.setCursor(4, 2); // Set cursor position to the 4 collumn and 2 row lcd.print("0"); // DATA if (dayOfMonth < 10) { // Day lcd.setCursor(4, 2); // Set cursor position to the 4 collumn and 2 row lcd.print("0"); lcd.print(dayOfMonth, DEC); } else { lcd.setCursor(4, 2); // Set cursor position to the 4 collumn and 2 row lcd.print(dayOfMonth, DEC); } lcd.print("."); if (month < 10) { // Mounth lcd.setCursor(7, 2); // Set cursor position to the 7 collumn and 2 row lcd.print("0"); lcd.print(month, DEC); } else { lcd.setCursor(7, 2); // Set cursor position to the 7 collumn and 2 row lcd.print(month, DEC); } lcd.print("."); lcd.print("20"); // Year if (year < 10) { lcd.setCursor(12, 2); // Set cursor position to the 12 collumn and 2 row lcd.print("0"); lcd.print(year, DEC); } else { lcd.setCursor(12, 2); // Set cursor position to the 12 collumn and 2 row lcd.print(year, DEC); } lcd.setCursor(0, 2); // Set cursor position to the 0 collumn and 2 row switch (dayOfWeek) { // Day of the week case 1: lcd.print("Sun"); break; case 2: lcd.print("Mon"); break; case 3: lcd.print("Tue"); break; case 4: lcd.print("Wed"); break; case 5: lcd.print("Thu"); break; case 6: lcd.print("Fri"); break; case 7: lcd.print("Sat"); break; } } void loop() { String toSend = ("Tomasz Bartus"); // Message text char msg[20]; // Create 20-elements char table toSend.toCharArray(msg, toSend.length() + 1); // convert text to char table if (millis() - timelight <= 10000) { // If 10 sec have not passed, lcd.backlight(); // switch LCD backlight on } else { lcd.noBacklight(); // switch LCD backlight off if ( (mfrc522.PICC_ReadCardSerial() == 1) || (digitalRead(2) == LOW) || (digitalRead(3) == LOW)) { // True, if RFID tag/card is present timelight = millis(); // switch LCD backlight on for 10 sec } } switch (screen) { case 1: // SCREEN 1 lcd.clear(); // Clear the screen lcd.setCursor(0, 0); // Set cursor position to the 0 collumn and 0 row lcd.print("Press your ID card"); // Write the text on LCD lcd.setCursor(0, 1); // Set cursor position to the 0 collumn and 1 row lcd.print("please..."); // Write the text on LCD displayTime(); // wyświetlanie czasu rzeczywistego delay(1000); // Refresh every second if ( mfrc522.PICC_IsNewCardPresent()) { // True, if RFID tag/card is present screen = 2; // Go to screen 2 lcd.clear(); // Clear the screen } break; case 2: // SCREEN 2 lcd.setCursor(0, 0); // Set cursor position to the 0 collumn and 0 row lcd.print("UID: "); // Write the text on LCD if (digitalRead(2) == LOW) { // If an event selector button has been pressed delay(20); // Wait 20ms to stabilise button debounce event = !event; // Change event variable to opposed if (event == 0) { // If an event variable is equal 0 eventName = "input "; // Set an eventName variable to "input" tone(4, 500); delay(200); tone(4, 1000); delay(200); tone(4, 1500); delay(200); noTone(4); } else { // If event variable is equal 1 eventName = "output"; // Set an eventName variable to "output" tone(4, 1500); delay(200); tone(4, 1000); delay(200); tone(4, 500); delay(200); noTone(4); } while (digitalRead(2) == LOW) { delay(20); } } lcd.setCursor(14, 1); // Set cursor position to the 14 collumn and 1 row lcd.print(eventName); // Write the eventName variable on LCD lcd.setCursor(4, 0); // Set cursor position to the 4 collumn and 0 row if ( mfrc522.PICC_IsNewCardPresent()) { // True, if RFID tag/card is present // lcd.backlight(); // Turning on LCD backlight if ( mfrc522.PICC_ReadCardSerial()) { // True, if RFID tag/card was read for (byte i = 0; i < mfrc522.uid.size; i++) { // read id (in parts) lcd.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "); lcd.print(mfrc522.uid.uidByte[i], DEC); // print uid as dec values } } } lcd.setCursor(0, 1); // Set cursor position to the 0 collumn and 1 row lcd.print("Select event: "); // Write the text on LCD lcd.setCursor(0, 2); // Set cursor position to the 0 collumn and 2 row lcd.print("Confirmation: none"); // Write the text on LCD if (digitalRead(OK_PIN) == LOW) { // If an OK button has been pressed delay(20); // Wait 20ms to stabilise button debounce digitalWrite(LED_PIN, LOW); // Turn on the LED lcd.setCursor(14, 2); // Set cursor position to the 15 collumn and 1 row String toSend = ("hello world"); // tekst wiadomości char msg[23]; // tworzymy tablicę typu char toSend.toCharArray(msg, toSend.length() + 1); // konwertujemy nasz tekst do tablicy char'ów vw_send((uint8_t *)msg, strlen(msg)); // wysyłamy vw_wait_tx(); lcd.print("send"); // Write the eventName variable on LCD tone(4, 400); delay(1000); noTone(4); screen = 3; // Go to screen 2 lcd.clear(); // Clear the screen while (digitalRead(OK_PIN) == LOW) { delay(20); } } //================= // If there is no confirmation - come back to screen 1 count_2++; if (count_2 == 1) { screen_2_start = millis(); // Measure time of the screen 2 appearance } time_screen_2 = millis() - screen_2_start; // Count period of time when the screen 2 is visible if (time_screen_2 >= 15000) { // If the screen 2 is visible longer than 15 sec screen = 1; // Come back to screen 1 count_2 = 0; // Reset the count_2 variable } //================= break; case 3: // SCREEN 3 lcd.setCursor(0, 0); // Set cursor position to the 0 collumn and 0 row lcd.print("The "); // Write the text on LCD lcd.print(eventName); // Write the eventName variable on LCD lcd.print(" event"); // Write the text on LCD lcd.setCursor(0, 1); // Set cursor position to the 0 collumn and 1 row lcd.print("has been registed."); // Write the text on LCD lcd.setCursor(11, 3); // Set cursor position to the 11 collumn and 3 row lcd.print("Thanks..."); // Write the text on LCD delay(2000); screen = 1; // Come back to screen 1 break; } digitalWrite(LED_PIN, HIGH); // Turn off the LED Serial.print(screen); Serial.println("; "); }
Odbiornik-rejestrator składa się z jednego urządzenia wejściowego - modułu odbiornika RFID RC522 (Fig. 5) . oraz jednego urządzenia wyjściowego - czytnika kard SD (Fig. 8) .
Układ czytnik RFID z nadajnikiem cały czas nasłuchuje pojawienia się karty zbliżeniowej lub breloka zbliżeniowego. W tym czasie na wyświetlaczu LCD wyświetlany jest monit oczekiwania wraz z aktualną datą i godziną (Fig. 10; zob. kod case 1
).
Po zbliżeniu urządzenia RFID do rejestratora RC522 rejestrowany jest dokładny czas zdarzenia. Na wyświetlaczu pojawia się komunikat o statusie zdarzenia (Event: input
) oraz komunikat o statusie rejestracji zdarzenia (Confirmation: none
) (Fig. 11; zob. kod case 2
).
Program przez 15 sekund oczekuje na reakcję użytkownika. Należy wtedy wybrać za pomocą przycisku "Input / Output" odpowiedni rodzaj zdarzenia, t.j. wejście (input
) lub wyjście (output
) oraz potwierdzić zapis zdarzenia klawiszem "Zarejestruj". W zależności od tego, które zdarzenie zostaje wybrane (wejście czy wyjście), generowany jest odpowiedni charakterystyczny sygnał dźwiękowy. Gdy zostanie naciśnięty przycisk "Zarejestruj", do transmitera przekazywany jest komunikat w formacie użytkownik#czas_zdarzebnia#status_zdarzenia
. Na wyświetlaczu w tym czasie pojawia się komunikat "Confirmation: send
" (Fig. 12; zob. kod case 2
).
Po wysłaniu komunikatu do rządzenia odbiornika-rejestratora na wyświetlaczu LCD czytnika-nadajnika pojawia się komunikat potwierdzający rejestrację zdarzenia (Fig. 13; zob. kod case 3
).
Jeżeli odbiornik odbierze przesłany tekst, po skonwertowaniu go z kodu szesnastkowego na znaki ASCII zapisuje wiersz komunikatu na karcie SD. Komunikaty zgromadzone na karcie można przetwarzać w arkuszach kalkulacyjnych w celu generowania raportów obliczających łączne czasy pracy.