[169] Budujemy zamek szyfrowy cz. 1

Po długim wstępie związanym z obsługą klawiatury matrycowej przechodzimy do sedna, czyli właściwego zamka szyfrowego. I znowu zaczniemy od rozwiązania najprostszego, użytecznego jednak w pełni. Napiszemy pierwszą wersję zamka, która będzie pozbawiona możliwości zmiany kodu bez przeprogramowania kontrolera. Oznacza to, że jeśli będziemy chcieli zmienić szyfr, będzie potrzebny notebook i gniazdko USB – w praktyce ukryte albo znajdujące się wewnątrz chronionego pomieszczenia czy sejfu. Nie nadaje się więc to rozwiązanie dla zastosowań komercyjnych, gdy klucz jest przyznawany czasowo różnym osobom, ale jeśli będziemy chcieli tak chronić naszą tylko przestrzeń – wystarczy.

#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 char password[5] = "1234";                                                     // Hasło, które należy wprowadzić.
char enteredPassword[5];                                                             // Hasło wprowadzane.
byte keyCount = 0;                                                                   // Licznik znaków hasła.

void setup() {
  pinMode(buzzer, OUTPUT);  // Inicjuj port sterujący buzzerem.
  pinMode(lock, OUTPUT);    // Inicjuj port sterujący elektromagnesem zamka.
}
void 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.
    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 ustalone...
        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...
        beep(500);                                   // Pikaj - długo.
      }
      keyCount = 0;  // Wyzeruj licznik znaków hasła.
    }
  }
}
void beep(int time) {  // Piknięcie: czas trwania.
  digitalWrite(buzzer, HIGH);
  delay(time);
  digitalWrite(buzzer, LOW);
  delay(100);
}

W tej wersji wyświetlacz jest zbędny, więc pozbyłem się wszelkich wpisów, które go dotyczą. Podobnie jak poprzednio, będziemy używać biblioteki obsługi klawiatury, ale ograniczymy się do najprostszych działań: czytania wciskanych klawiszy. Tak więc wstępne deklaracje są nam już znane, a kto chciałby to zgłębić – zapraszam do poprzedniego artykułu. Zadeklarujemy sobie też port piszczka buzzer – ja sobie wykorzystam buzzer znajdujący się na płytce edukacyjnej TME oraz nowość – port elektromagnesu zamka lock. Różnego rodzaju zamki i rygle wymagają najczęściej podania jakiegoś napięcia i szczegóły te związane są z konkretnym przypadkiem. Z naszego punktu widzenia najlepiej użyć przekaźnika, ponieważ zapewnia izolację od części wykonawczej i rozwiązuje wszelkie problemy z napięciami. Sam przekaźnik wymaga prądu większego niż wydajność pinów kontrolera, w praktyce wystarczy wzmacniający tranzystor. O przekaźnikach pisałem kiedyś tutaj. Dla nas istotne jest to, że pin, który wybrałem do tych celów – trzynasty – ma jednocześnie reprezentanta w postaci diody świecącej, więc na czas testów będziemy po prostu widzieć owo działanie przekaźnika, nawet jeśli nie zostanie jeszcze podłączony fizycznie.

const byte buzzer = 9;            // Port sterujący buzzerem.
const byte lock = 13;             // Port sterujący elektromagnesem zamka.
const char password[5] = "1234";  // Hasło, które należy wprowadzić.
char enteredPassword[5];          // Hasło wprowadzane.

Tak więc tutaj mamy deklarację portu przekaźnika i diody jednocześnie. Poniżej – wstawiamy hasło. Jak widać, trudne nie jest, ale oczywiście każdy może sobie wstawić własne. Dla celów szkoleniowych ma cztery pozycje, lecz nie ma tu żadnych ograniczeń. Należy także powołać tablicę enteredPassword na hasło wpisywane przez uprawnionych – bądź nie – do otwarcia zamka. Oboe mają o jedną pozycję więcej od długości hasła, bo do porównań będzie potrzebna konstrukcja, która wymaga by łańcuchy miały na końcu bajt zerowy. Tak więc dla czterech znaków długość tablicy musi mieć pięć bajtów. Do kompletu będzie potrzebna zmienna numeru wpisywanego znaku hasła keyCount.

void 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.
    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 ustalone...
        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...
        beep(500);                                   // Pikaj - długo.
      }
      keyCount = 0;  // Wyzeruj licznik znaków hasła.
    }
  }
}

W głównej pętli najpierw pozyskamy kod wciśniętego przycisku, zapisując go w tablicy pod adresem wyznaczonym owym numerem kolejnego znaku. Zaraz potem trzeba numer zwiększyć i piknąć – co potwierdza działanie klawiatury, zwłaszcza w warunkach braku wyświetlacza. Teraz należy sprawdzić, czy to aby nie jest już czwarta pozycja hasła. Jeśli tak jest – dopiszemy owo wspomniane zero i tak zamknięty string możemy porównać z oryginałem. Użyjemy do tego funkcji strcmp, która porównuje ze sobą dwa łańcuchy i zwraca trzy wartości: zero – jeśli są równe lub wartości liczbowe, zależne od tego, który łańcuch od którego jest mniejszy w sensie kodów ASCII. Nas będzie interesować tylko przypadek, gdy oba łańcuchy będą takie same. Funkcja wymaga wspomnianego zera na końcu wyrażeń, stąd wcześniejsza instrukcja.

Jeśli w wyniku dostaniemy zero – znaczy że łańcuchy są identyczne, czyli hasło zostało wpisane poprawnie. Włączamy elektromagnes zamka, wysyłamy rozkaz piknięcia – dłuższego, czekamy sekundę i wyłączamy zamek. Jeśli hasło było błędne, wysyłamy rozkaz długiego piknięcia i to wszystko. Na koniec resetujemy licznik znaków i możemy zacząć wpisywać hasło od początku.

Taki zamek będzie działać i choć jest prosty, to użyteczny. Zauważmy, że nie ma tutaj znanego rozkazu zatwierdzającego hasło – zwykle krzyżykiem. Zawsze uważałem to za zbędny dodatek, zmuszający do jeszcze jednego wciskania klawiaturki. Lepiej owo wciśnięcie wykorzystać do wprowadzenia kolejnego znaku hasła, przy tej samej ilości kliknięć dziesięciokrotnie wzrośnie nam bezpieczeństwo. Zróbmy pewną modyfikację, która nie ma znaczenia dla idei, ale uprzyjemnia działanie projektu.

#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 char password[5] = "1234";                                                     // Hasło, które należy wprowadzić.
char enteredPassword[5];                                                             // Hasło wprowadzane.
byte keyCount = 0;                                                                   // Licznik znaków hasła.

void setup() {
  pinMode(buzzer, OUTPUT);  // Inicjuj port sterujący buzzerem.
  pinMode(lock, OUTPUT);    // Inicjuj port sterujący elektromagnesem zamka.
}
void 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.
    if (keyCount != 4) { beep(1, keyCount); }        // Pikaj - krótko i w liczbie równej pozycji znaku.
    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 ustalone...
        digitalWrite(lock, HIGH);                    // Włącz zasilanie elektromagnesu zamka.
        beep(10, 10);                                // Pikaj - krótko i dziesięć razy.
        digitalWrite(lock, LOW);                     // Wyłącz zasilanie elektromagnesu zamka.
      } else {                                       // Jeśli hasło było błędne...
        beep(250, 1);                                // Pikaj - długo i raz.
      }
      keyCount = 0;  // Wyzeruj licznik znaków hasła.
    }
  }
}
void beep(byte time, byte repeat) {  // Piknięcie: długość, liczba piknięć.
  while (repeat > 0) {
    digitalWrite(buzzer, HIGH);
    delay(time);
    digitalWrite(buzzer, LOW);
    delay(100);
    repeat--;
  }
}

Nasz podprogram pikający poszerzymy do dwóch elementów: czasu trwania piknięcia i liczby jego powtórzeń. Czas przerwy pomiędzy piknięciami ustaliłem na 100 ms. Zmiany obejmą owe pikania: przy wprowadzaniu pierwszej, drugiej i trzeciej cyfry usłyszymy takąż liczbę piknięć. Pozwoli nam się to zorientować, którą właśnie cyfrę wciskamy z kolei. Prawidłowe hasło będzie potwierdzane serią piknięć, a błędne – jednym, długim. Prawdę powiedziawszy, nie spotkałem się z takim rozwiązaniem, a szkoda, bo jest miłe.

Zanim przystąpimy do projektu ostatecznego, z powrotem podłączymy wyświetlacz. U mnie jest on osadzony na magistrali I2C i o jego podłączeniu pisałem już nie raz. Osadzę więc elementy z nim związane i teraz oprócz piknięć będą się pojawiać napisy. W tym celu zbuduję sobie podprogram. Problem w tym, że napisy – które dla oszczędności RAM-u siedzą w pamięci FLASH – można pozyskać jedynie taką skomplikowaną instrukcją: const __FlashStringHelper* text To jest ustalona klasa dla łańcuchów siedzących w tej pamięci, a gwiazdka w tym wypadku to adres początku łańcucha.

#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 <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 char password[5] = "1234";                                                     // Hasło, które należy wprowadzić.
char enteredPassword[5];                                                             // Hasło wprowadzane.
byte keyCount = 0;                                                                   // Licznik znaków hasła.

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.
  lcdText(F("HASLO: "));    // Wyczyść wyświetlacz i wyślij napis.
}
void 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 ustalone...
        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 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.
}

Teraz już w wybranych miejscach będziemy wysyłać na wyświetlacz napisy z prośbą o hasło, informujące o haśle źle wpisanym i otwarciu zamka. Do tego po wciśnięciu każdego znaku pojawi się gwiazdka. Program możemy przetestować, funkcjonalnie nie różni się niczym od wersji poprzedniej. W kolejnym artykule zbudujemy aplikację, która pozwala na zmianę hasła z poziomu klawiaturki.

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