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.