[170] Budujemy zamek szyfrowy cz. 2

Czas na wersję finałową zamka szyfrowego, która będzie już w pełni funkcjonalna. Ta wersja jednak także będzie się nieco różnić od spotykanych seryjnie rozwiązań, konkretnie – innym podejściem do zapisywania hasła użytkownika. Wykorzystam w tym celu osobny pin, którego zwarcie – w moim przypadku do stanu wysokiego – wymusi natychmiastową zmianę hasła. Przełącznik ten należy ukryć przed dostępem z zewnątrz. Oczywiście zmianę hasła można zrealizować inaczej, na przykład hasłem nadrzędnym albo którymś z przycisków – choćby krzyżykiem, podczas otwarcia zamka. Ja wybrałem taki sposób, wydaje mi się wygodny i funkcjonalny.

Ponieważ sensowny zamek musi być odporny na resety, hasło należy przechowywać w pamięci nieulotnej. Jak wiadomo, Arduino ma taką na pokładzie i do jej obsługi potrzebna jest biblioteka. Dołączymy ją więc na początku.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
#include <EEPROM.h>                                                    // Biblioteka obsługująca pamięć EEPROM.

#include <Keypad.h>                               // Biblioteka obsługująca klawiaturę.
const byte rowsKeypad = 4;                        // Liczba wierszy matrycy klawiatury.
const byte colsKeypad = 4;                        // Liczba kolumn matrycy klawiatury.
const byte rowPins[rowsKeypad] = { 7, 6, 5, 4 };  // Adresy kolejnych wierszy klawiatury.
const byte colPins[colsKeypad] = { 3, 2, 1, 0 };  // Adresy kolejnych kolumn klawiatury.
const char keys[rowsKeypad][colsKeypad] = {       // Tablica znaków dla kolejnych klawiszy.
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rowsKeypad, colsKeypad);  // Definiuj klawiaturę zgodnie z powyższymi deklaracjami.
const byte buzzer = 9;                                                               // Port sterujący buzzerem.
const byte lock = 13;                                                                // Port sterujący elektromagnesem zamka.
const byte changePassword = 8;                                                       // Port umożliwiający zmianę hasła.
char enteredPassword[5];                                                             // Hasło wprowadzane.
byte keyCount = 0;                                                                   // Licznik znaków hasła.
const int eepromAddr = 0;                                                            // Adres w pamięci EEPROM, w którym przechowuje się hasło.
char password[5];                                                                    // Hasło wczytywane do pamięci RAM z pamięci EEPROM

void setup() {
  lcd.begin(16, 2);                       // Inicjuj wyświetlacz.
  pinMode(buzzer, OUTPUT);                // Inicjuj port sterujący buzzerem.
  pinMode(lock, OUTPUT);                  // Inicjuj port sterujący elektromagnesem zamka.
  pinMode(changePassword, INPUT_PULLUP);  // Inicjuj port umożliwiający zmianę hasła.
  EEPROM.get(eepromAddr, password);       // Wczytaj hasło do pamięci RAM z pamięci EEPROM
  lcdText(F("HASLO: "));                  // Wyczyść wyświetlacz i wyślij napis.
}
void loop() {
  if (digitalRead(changePassword) == HIGH) {  // Jeśli wciśnięto przycisk zmiany hasła...
    changePasswordMode();                     // Przejdź do programu obsługi zmiany hasła.
    return;                                   // Po powrocie pomiń resztę pętli loop.
  }
  char key = keypad.getKey();                        // Pozyskaj znak klawiatury.
  if (key) {                                         // Jeśli się pojawił...
    enteredPassword[keyCount] = key;                 // Zapisz znak w tablicy.
    keyCount++;                                      // Zwiększ licznik wpisanych znaków.
    lcd.print('*');                                  // Wyślij na wyświetlacz gwiazdkę.
    beep(1);                                         // Pikaj - krótko.
    if (keyCount == 4) {                             // Jeśli wprowadzono wszystkie cztery znaki...
      enteredPassword[4] = '\0';                     // Zamknij ich ciąg.
      if (strcmp(enteredPassword, password) == 0) {  // Jeśli hasło wprowadzone jest takie samo jak w pamięci EEPROM...
        lcdText(F("OTWARTE"));                       // Wyczyść wyświetlacz i wyślij napis.
        digitalWrite(lock, HIGH);                    // Włącz zasilanie elektromagnesu zamka.
        beep(100);                                   // Pikaj - dłużej.
        delay(1000);                                 // Czas zasilania elektromagnesu zamka.
        digitalWrite(lock, LOW);                     // Wyłącz zasilanie elektromagnesu zamka.
      } else {                                       // Jeśli hasło było błędne...
        lcdText(F("BLEDNE HASLO"));                  // Wyczyść wyświetlacz i wyślij napis.
        beep(500);                                   // Pikaj - długo.
      }
      keyCount = 0;           // Wyzeruj licznik znaków hasła.
      lcdText(F("HASLO: "));  // Wyczyść wyświetlacz i wyślij napis.
    }
  }
}
void changePasswordMode() {            // Program obsługi zmiany hasła.
  beep(100);                           // Pikaj - dłużej.
  lcdText(F("NOWE HASLO: "));          // Wyczyść wyświetlacz i wyślij napis.
  char newPassword[5];                 // Nowe hasło.
  byte newKeyCount = 0;                // Licznik znaków nowego hasła.
  while (newKeyCount < 4) {            // Powtarzaj aż zostaną wprowadzone wszystkie cztery znaki.
    char key = keypad.getKey();        // Pozyskaj znak klawiatury.
    if (key) {                         // Jeśli się pojawił...
      newPassword[newKeyCount] = key;  // Zapisz znak w tablicy.
      newKeyCount++;                   // Zwiększ licznik wpisanych znaków.
      lcd.print(key);                  // Wyślij na wyświetlacz jawny znak.
      beep(1);                         // Pikaj - krótko.
    }
  }
  newPassword[4] = '\0';                // Jeśli wprowadzono wszystkie cztery znaki, zamknij ich ciąg.
  EEPROM.put(eepromAddr, newPassword);  // Zapisz nowe hasło w pamięci EEPROM
  strcpy(password, newPassword);        // Zaktualizuj hasło obowiązujące.
  lcdText(F("ZAPISANO"));               // Wyczyść wyświetlacz i wyślij napis.
  beep(500);                            // Pikaj - długo.
  keyCount = 0;                         // Wyzeruj licznik znaków hasła.
  lcdText(F("HASLO: "));                // Wyczyść wyświetlacz i wyślij napis.
}
void beep(int time) {  // Piknięcie: czas trwania.
  digitalWrite(buzzer, HIGH);
  delay(time);
  digitalWrite(buzzer, LOW);
  delay(100);
}
void lcdText(const __FlashStringHelper* text) {
  lcd.clear();      // Wyczyść wyświetlacz.
  lcd.print(text);  // Wyślij napis na wyświetlacz.
}

Program będzie się różnił obecnością modułu umożliwiającego zapamiętanie nowego hasła. W deklaracja przybywa więc nowy port changePassword – tajnego przycisku oraz zmienne związane z obsługą hasła w pamięci EEPROM. W części wstępnej należy załadować hasło z tej pamięci do RAM-u. W przypadku pierwszego użycia kontrolera z tym kodem, będą tam jakieś śmieci, więc jedynym sensownym krokiem będzie zadeklarowanie nowego hasła, wciskając ów tajny przycisk.

Jest on też analizowany potem, w głównej pętli, za każdym razem na jej początku. Jeśli zostanie aktywowany, program przejdzie do bloku obsługi deklaracji nowego hasła. Na wyświetlaczu pojawi się stosowny napis, po czym program będzie czekać na wprowadzenie czterech znaków. Wygląda to podobnie jak w poprzedniej wersji, ale tym razem znaki są wyświetlane jawnie zamiast gwiazdek. Dzięki temu będziemy mieli pewność co do wprowadzonego hasła. Także tutaj nie ma żadnego potwierdzania, hasło automatycznie zmienia się po wpisaniu czwartego znaku. W razie pomyłki wciskamy przycisk zmiany hasła ponownie i wpisujemy nowe hasło. Na końcu procedury hasło jest zapamiętywane w pamięci EEPROM oraz aktualizowane w RAM-ie.

Reszta programu pracuje identycznie. Jak mówiłem, po resecie hasło jest zaczytywane, więc nie ma tutaj problemu z przerwami z zasilaniem. Oczywiście jeśli nie będzie prądu, zamek się nie otworzy i trzeba to uwzględnić, stosując alternatywne metody mechaniczne. Wyświetlacz w tym projekcie tak naprawdę nie jest konieczny, acz ułatwia orientację. Można także zastosować metodę ze zmienną liczbą piknięć, jak w jednym z przykładów wcześniejszych.

Pozostała jeszcze jedna zmiana, tak zwana pułapka na dociekliwych, którzy – przy 65 tysiącach kombinacji – mieliby ochotę na przetestowanie ich hurtem.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD
#include <EEPROM.h>                                                    // Biblioteka obsługująca pamięć EEPROM.

#include <Keypad.h>                               // Biblioteka obsługująca klawiaturę.
const byte rowsKeypad = 4;                        // Liczba wierszy matrycy klawiatury.
const byte colsKeypad = 4;                        // Liczba kolumn matrycy klawiatury.
const byte rowPins[rowsKeypad] = { 7, 6, 5, 4 };  // Adresy kolejnych wierszy klawiatury.
const byte colPins[colsKeypad] = { 3, 2, 1, 0 };  // Adresy kolejnych kolumn klawiatury.
const char keys[rowsKeypad][colsKeypad] = {       // Tablica znaków dla kolejnych klawiszy.
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rowsKeypad, colsKeypad);  // Definiuj klawiaturę zgodnie z powyższymi deklaracjami.
const byte buzzer = 9;                                                               // Port sterujący buzzerem.
const byte lock = 13;                                                                // Port sterujący elektromagnesem zamka.
const byte changePassword = 8;                                                       // Port umożliwiający zmianę hasła.
char enteredPassword[5];                                                             // Hasło wprowadzane.
byte keyCount = 0;                                                                   // Licznik znaków hasła.
const int eepromAddr = 0;                                                            // Adres w pamięci EEPROM, w którym przechowuje się hasło.
char password[5];                                                                    // Hasło wczytywane do pamięci RAM z pamięci EEPROM
byte wrongAttempts = 0;                                                              // Licznik błędnie wpisanych haseł.

void setup() {
  lcd.begin(16, 2);                       // Inicjuj wyświetlacz.
  pinMode(buzzer, OUTPUT);                // Inicjuj port sterujący buzzerem.
  pinMode(lock, OUTPUT);                  // Inicjuj port sterujący elektromagnesem zamka.
  pinMode(changePassword, INPUT_PULLUP);  // Inicjuj port umożliwiający zmianę hasła.
  EEPROM.get(eepromAddr, password);       // Wczytaj hasło do pamięci RAM z pamięci EEPROM
  lcdText(F("HASLO: "));                  // Wyczyść wyświetlacz i wyślij napis.
}
void loop() {
  if (digitalRead(changePassword) == HIGH) {  // Jeśli wciśnięto przycisk zmiany hasła...
    changePasswordMode();                     // Przejdź do programu obsługi zmiany hasła.
    return;                                   // Po powrocie pomiń resztę pętli loop.
  }
  char key = keypad.getKey();                        // Pozyskaj znak klawiatury.
  if (key) {                                         // Jeśli się pojawił...
    enteredPassword[keyCount] = key;                 // Zapisz znak w tablicy.
    keyCount++;                                      // Zwiększ licznik wpisanych znaków.
    lcd.print('*');                                  // Wyślij na wyświetlacz gwiazdkę.
    beep(1);                                         // Pikaj - krótko.
    if (keyCount == 4) {                             // Jeśli wprowadzono wszystkie cztery znaki...
      enteredPassword[4] = '\0';                     // Zamknij ich ciąg.
      if (strcmp(enteredPassword, password) == 0) {  // Jeśli hasło wprowadzone jest takie samo jak w pamięci EEPROM...
        lcdText(F("OTWARTE"));                       // Wyczyść wyświetlacz i wyślij napis.
        wrongAttempts = 0;                           // Zeruj licznik błędów po poprawnym wpisaniu hasła.
        digitalWrite(lock, HIGH);                    // Włącz zasilanie elektromagnesu zamka.
        beep(100);                                   // Pikaj - dłużej.
        delay(1000);                                 // Czas zasilania elektromagnesu zamka.
        digitalWrite(lock, LOW);                     // Wyłącz zasilanie elektromagnesu zamka.
      } else {                                       // Jeśli hasło było błędne...
        wrongAttempts++;                             // Zwiększ licznik nieudanych prób wpisywania hasła.
        lcdText(F("BLEDNE HASLO"));                  // Wyczyść wyświetlacz i wyślij napis.
        beep(500);                                   // Pikaj - długo.
        if (wrongAttempts > 2) {                     // Sprawdź czy osiągnięto limit trzech błędnie wpisanych haseł.
          wrongPasswordMode();                       // Przejdź do podprogramu blokady zamka.
        }
      }
      keyCount = 0;           // Wyzeruj licznik znaków hasła.
      lcdText(F("HASLO: "));  // Wyczyść wyświetlacz i wyślij napis.
    }
  }
}
void wrongPasswordMode() {         // Blokada zamka na minutę - po wpisaniu trzech błędnych haseł.
  wrongAttempts = 0;               // Resetuj licznik nieudanych prób wpisywania hasła.
  keyCount = 0;                    // Wyzeruj licznik znaków hasła.
  lcdText(F("Czekaj"));            // Wyczyść wyświetlacz i wyślij napis.
  for (byte x = 60; x > 0; x--) {  // Odliczanie od 60 do 1 sekundy.
    lcd.setCursor(7, 0);           // Przesuń kursor.
    lcd.print(x);                  // Wyświetl licznik.
    lcd.print(F(" sekund "));      // Wyślij napis.
    beep(1);                       // Pikaj - krótko.
    delay(899);                    // Dopełnienie do pełnej sekundy.
  }
}
void changePasswordMode() {            // Program obsługi zmiany hasła.
  beep(100);                           // Pikaj - dłużej.
  lcdText(F("NOWE HASLO: "));          // Wyczyść wyświetlacz i wyślij napis.
  char newPassword[5];                 // Nowe hasło.
  byte newKeyCount = 0;                // Licznik znaków nowego hasła.
  while (newKeyCount < 4) {            // Powtarzaj aż zostaną wprowadzone wszystkie cztery znaki.
    char key = keypad.getKey();        // Pozyskaj znak klawiatury.
    if (key) {                         // Jeśli się pojawił...
      newPassword[newKeyCount] = key;  // Zapisz znak w tablicy.
      newKeyCount++;                   // Zwiększ licznik wpisanych znaków.
      lcd.print(key);                  // Wyślij na wyświetlacz jawny znak.
      beep(1);                         // Pikaj - krótko.
    }
  }
  newPassword[4] = '\0';                // Jeśli wprowadzono wszystkie cztery znaki, zamknij ich ciąg.
  EEPROM.put(eepromAddr, newPassword);  // Zapisz nowe hasło w pamięci EEPROM
  strcpy(password, newPassword);        // Zaktualizuj hasło obowiązujące.
  lcdText(F("ZAPISANO"));               // Wyczyść wyświetlacz i wyślij napis.
  beep(500);                            // Pikaj - długo.
  keyCount = 0;                         // Wyzeruj licznik znaków hasła.
  lcdText(F("HASLO: "));                // Wyczyść wyświetlacz i wyślij napis.
}
void beep(int time) {  // Piknięcie: czas trwania.
  digitalWrite(buzzer, HIGH);
  delay(time);
  digitalWrite(buzzer, LOW);
  delay(100);
}
void lcdText(const __FlashStringHelper* text) {
  lcd.clear();      // Wyczyść wyświetlacz.
  lcd.print(text);  // Wyślij napis na wyświetlacz.
}

W tym celu w blok wpisywania hasła dołożymy zmienną liczącą pomyłki wrongAttempts. Gdy ich liczba dojdzie do trzech, na chwilę opuścimy główną pętlę, lądując w podprogramie „kary” wrongPasswordMode() Pozostaniemy tutaj przez minutę, ale żebyśmy tak bardzo nie cierpieli, na wyświetlaczu pojawi się odliczający w dół licznik sekund. Po kwarantannie znowu będziemy mieli trzy próby na wpisanie prawidłowego hasła. Wszystkie elementy: liczbę znaków, ich zbiór, liczbę prób i tak dalej, to wszystko możemy dopasować do własnych potrzeb. Arduino jest znakomitym środowiskiem dla takich urządzeń i tylko wyobraźnia nas tu ograniczy.

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2

Inne artykuły z tej kategorii

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2026 arduino.pl