Servomotori controllati tramite joystick.

Serratura con password

Questo progetto è un sistema di controllo tramite password basato su Arduino che utilizza una tastiera matriciale 4x4. La tastiera permette all'utente di inserire una password numerica, cancellare l'input e confermare il codice utilizzando tasti dedicati. Quando viene inserita e confermata la password corretta, Arduino commuta lo stato di due uscite digitali, che possono essere utilizzate per controllare LED, relè, buzzer, serrature elettroniche o altri dispositivi esterni tramite un circuito di pilotaggio adeguato. Il progetto è utile per imparare come funzionano le tastiere matriciali, come leggere ingressi digitali, come gestire password nel codice e come semplificare la gestione della tastiera utilizzando la libreria Keypad.h.

Componenti necessari:

Schema:

Circuit Scheme

Codice base:

base_password_door_lock.ino
// https://nemiatools.com
#include <string.h> // libreria stringhe usata qui per il confronto

const byte ROW_NUM = 4; // numero di righe
const byte COLUMN_NUM = 4; // numero di colonne

char keys[ROW_NUM][COLUMN_NUM] = { // mappa dei tasti
  {'1', '2', '3', 'A'}, // prima riga
  {'4', '5', '6', 'B'}, // seconda riga
  {'7', '8', '9', 'C'}, // terza riga
  {'*', '0', '#', 'D'}  // quarta riga
};

byte pin_rows[ROW_NUM] = {2, 3, 4, 5}; // pin delle righe
byte pin_cols[COLUMN_NUM] = {6, 7, 8, 9}; // pin delle colonne

const String correctPassword = "1234"; // password corretta
String inputPassword = ""; // password inserita
bool outputState = false; // stato delle uscite
bool clearNext = false; // cancella dopo l'invio

void setup() {
  pinMode(10, OUTPUT); // imposta il pin 10 come uscita
  pinMode(11, OUTPUT); // imposta il pin 11 come uscita
  digitalWrite(10, LOW); // pin 10 spento
  digitalWrite(11, LOW); // pin 11 spento

  for (byte i = 0; i < ROW_NUM; i++) { // configura le righe
    pinMode(pin_rows[i], INPUT_PULLUP); // attiva la resistenza di pull-up interna
  }

  for (byte i = 0; i < COLUMN_NUM; i++) { // configura le colonne
    pinMode(pin_cols[i], OUTPUT); // imposta le colonne come uscite
    digitalWrite(pin_cols[i], HIGH); // tutte le colonne inattive
  }
}

char getKey() { // legge il tastierino

  for (byte col = 0; col < COLUMN_NUM; col++) { // scansione delle colonne

    for (byte i = 0; i < COLUMN_NUM; i++) { // porta tutte le colonne a livello alto
      digitalWrite(pin_cols[i], HIGH); // colonna inattiva
    }

    digitalWrite(pin_cols[col], LOW); // attiva la colonna corrente

    for (byte row = 0; row < ROW_NUM; row++) { // scansione delle righe
      if (digitalRead(pin_rows[row]) == LOW) { // tasto premuto
        return keys[row][col]; // restituisce il carattere corrispondente
      }
    }
  }

  return 0; // nessun tasto premuto
}

void loop() {

  if (clearNext) { // richiesta di reset
    inputPassword = ""; // svuota la password inserita
    clearNext = false; // azzera il flag
  }

  char key = getKey(); // legge il tasto premuto

  if (!key) { // nessun tasto premuto
    return; // esce immediatamente
  }

  delay(150); // ritardo antirimbalzo

  if (key == '*') { // comando di cancellazione
    inputPassword = ""; // svuota la password
  }
  else if (key == '#') { // comando di invio
    if (inputPassword == correctPassword) { // password corretta
      outputState = !outputState; // inverte lo stato delle uscite
      digitalWrite(10, outputState ? HIGH : LOW); // aggiorna il pin 10
      digitalWrite(11, outputState ? HIGH : LOW); // aggiorna il pin 11
    }
    clearNext = true; // cancella la password dopo l'invio
  }
  else if (key >= '0' && key <= '9') { // accetta solo cifre numeriche
    inputPassword += key; // aggiunge la cifra alla password
  }

  while (getKey() != 0) { // attende il rilascio del tasto
    delay(10); // breve attesa
  }
}

Codice con Keypad.h:

password_door_lock_keypad_h.ino
// https://nemiatools.com
#include <Keypad.h> // libreria keypad

const byte ROW_NUM = 4; // numero righe
const byte COLUMN_NUM = 4; // numero colonne

char keys[ROW_NUM][COLUMN_NUM] = { // mappa tasti
  {'1', '2', '3', 'A'}, // prima riga
  {'4', '5', '6', 'B'}, // seconda riga
  {'7', '8', '9', 'C'}, // terza riga
  {'*', '0', '#', 'D'}  // quarta riga
};

int pin_rows[ROW_NUM] = {2, 3, 4, 5}; // pin righe
int pin_cols[COLUMN_NUM] = {6, 7, 8, 9}; // pin colonne

Keypad keypad = Keypad(makeKeymap(keys), pin_rows, pin_cols, ROW_NUM, COLUMN_NUM); // oggetto keypad

const String correctPassword = "1234"; // password giusta
String inputPassword = ""; // password inserita
bool outputState = false; // stato uscite
bool clearNext = false; // cancella dopo invio

void setup() {
  pinMode(10, OUTPUT); // pin 10 uscita
  pinMode(11, OUTPUT); // pin 11 uscita
  digitalWrite(10, LOW); // pin 10 spento
  digitalWrite(11, LOW); // pin 11 spento
}

void loop() {
  
  if (clearNext) { // reset richiesto
    inputPassword = ""; // svuota password
    clearNext = false; // reset flag
  }

  char key = keypad.getKey(); // legge tasto

  if (!key) { // nessun tasto
    return; // esce subito e non esegue il resto del codice
  }

  delay(150); // anti rimbalzo

  if (key == '*') { // cancella testo
    inputPassword = ""; // svuota password
  }
  else if (key == '#') { // invia password
    if (inputPassword == correctPassword) { // password giusta
      outputState = !outputState; // cambia stato
      digitalWrite(10, outputState ? HIGH : LOW); // pin 10
      digitalWrite(11, outputState ? HIGH : LOW); // pin 11
    }
    clearNext = true; // pulisci dopo
  }
  else if (key >= '0' && key <= '9') { // solo numeri
    inputPassword += key; // aggiunge cifra
  }
}

Come funziona:

Questo progetto utilizza un Arduino e una tastiera matriciale 4x4 per creare un semplice sistema di controllo basato su password. La tastiera contiene 16 tasti disposti in 4 righe e 4 colonne. Ogni tasto funziona collegando una riga a una colonna quando viene premuto. Grazie a questa struttura matriciale, Arduino non ha bisogno di un pin di ingresso per ogni tasto. Gli servono invece solo 8 pin: 4 per le righe e 4 per le colonne.

Nella prima versione del codice, la tastiera viene letta manualmente senza usare una libreria esterna. Il numero di righe e colonne è definito con ROW_NUM e COLUMN_NUM, mentre l'array keys contiene la disposizione della tastiera. Questo array dice ad Arduino quale carattere corrisponde a ogni posizione di riga e colonna, per esempio '1', '2', '3', 'A' e così via.

I pin delle righe sono definiti con byte pin_rows[ROW_NUM] = {2, 3, 4, 5};, mentre i pin delle colonne sono definiti con byte pin_cols[COLUMN_NUM] = {6, 7, 8, 9};. I pin 10 e 11 sono configurati come uscite e vengono accesi o spenti quando viene inserita la password corretta. Queste uscite possono essere collegate a LED o, con un transistor, un MOSFET o un modulo relè adeguato, a carichi esterni come serrature o altri dispositivi.

La variabile correctPassword memorizza la password che deve essere inserita, in questo caso "1234". La variabile inputPassword memorizza le cifre premute dall'utente. La variabile outputState ricorda se le uscite sono attualmente accese o spente, mentre clearNext viene usata per cancellare la password inserita dopo l'invio.

Nella funzione setup() della versione manuale, i pin di uscita 10 e 11 vengono impostati come output usando pinMode(10, OUTPUT); e pinMode(11, OUTPUT);. All'inizio vengono spenti con digitalWrite(10, LOW); e digitalWrite(11, LOW);. I pin delle righe vengono configurati come ingressi con pull-up tramite pinMode(pin_rows[i], INPUT_PULLUP);. Questo è importante perché un pin di ingresso di Arduino deve avere sempre uno stato elettrico definito. Se un pin viene lasciato flottante, può leggere casualmente HIGH o LOW a causa del rumore elettrico. La resistenza di pull-up interna mantiene ogni riga normalmente HIGH quando nessun tasto è premuto, senza bisogno di resistenze esterne.

Poiché le righe usano INPUT_PULLUP, la logica è invertita: un tasto non premuto legge HIGH, mentre un tasto premuto legge LOW. Questo accade perché le colonne vengono normalmente tenute HIGH con digitalWrite(pin_cols[i], HIGH);, ma durante la scansione Arduino attiva una colonna alla volta impostandola LOW con digitalWrite(pin_cols[col], LOW);. Se un tasto di quella colonna viene premuto, collega la colonna LOW selezionata a uno dei pin di riga, quindi Arduino rileva un segnale LOW su quella riga.

La funzione getKey() esegue la scansione manuale della tastiera. Controlla una colonna alla volta. Prima tutte le colonne vengono impostate HIGH, poi la colonna corrente in prova viene portata LOW. Dopo di ciò, Arduino legge tutti i pin di riga usando digitalRead(pin_rows[row]). Se una riga legge LOW, significa che il tasto all'incrocio tra quella riga e la colonna attiva è premuto. La funzione restituisce quindi il carattere corrispondente con return keys[row][col];. Se nessun tasto è premuto, la funzione restituisce return 0;.

Nella funzione loop(), Arduino controlla prima se clearNext è true. In caso affermativo, la password inserita viene cancellata con inputPassword = ""; e il flag viene azzerato con clearNext = false;. Poi il codice richiama char key = getKey(); per verificare se è stato premuto un tasto. Se non viene rilevato alcun tasto, la condizione if (!key) diventa vera e la funzione termina immediatamente con return;, saltando il resto del ciclo. Questo mantiene il programma semplice e impedisce operazioni inutili quando la tastiera non viene usata.

Quando viene rilevato un tasto, il codice attende 150 millisecondi usando delay(150);. Questo ritardo serve per il debounce. I pulsanti meccanici di solito non generano un segnale perfettamente pulito quando vengono premuti. Per un brevissimo tempo, il contatto elettrico può rimbalzare, facendo sì che Arduino rilevi più pressioni rapide anche se l'utente ha premuto il tasto una sola volta. Il ritardo di debounce dà al segnale il tempo di stabilizzarsi prima che il programma continui. Questo è un semplice metodo di debounce bloccante, ma funziona bene per progetti base con tastiera come questo.

Dopo il ritardo di debounce, il programma controlla quale tasto è stato premuto. Se il tasto è '*', la condizione if (key == '*') è vera e la password viene cancellata con inputPassword = "";. Questo permette all'utente di ricominciare l'inserimento della password. Se il tasto è '#', la condizione else if (key == '#') è vera e la password viene inviata. Il programma confronta la password inserita con quella corretta usando if (inputPassword == correctPassword). Se le due stringhe sono uguali, la password è corretta e lo stato delle uscite viene commutato.

L'istruzione outputState = !outputState; cambia lo stato dell'uscita da false a true oppure da true a false. Questo significa che ogni volta che viene inserita la password corretta, le uscite cambiano stato: se erano spente, si accendono; se erano accese, si spengono.

Le righe digitalWrite(10, outputState ? HIGH : LOW); e digitalWrite(11, outputState ? HIGH : LOW); usano l'operatore ternario. L'operatore ternario è un modo compatto per scrivere una semplice condizione if/else. In questo caso, outputState ? HIGH : LOW significa: se outputState è true, usa HIGH; altrimenti, usa LOW. Quindi i pin vengono accesi quando outputState è true e spenti quando outputState è false.

Se il tasto premuto è un numero tra '0' e '9', la condizione else if (key >= '0' && key <= '9') diventa vera e il codice aggiunge quella cifra alla password inserita usando inputPassword += key;. Questo permette di costruire la password un numero alla volta. I tasti A, B, C e D vengono ignorati in questo progetto perché è accettato solo l'input numerico.

Alla fine del codice manuale, il ciclo while (getKey() != 0) attende finché l'utente non rilascia il tasto. Questo impedisce che la stessa pressione venga letta molte volte mentre il pulsante è ancora tenuto premuto. All'interno di questo ciclo viene usato un piccolo ritardo di 10 millisecondi con delay(10); per evitare di controllare la tastiera troppo aggressivamente.

La seconda versione del codice utilizza la libreria Keypad.h, inclusa con #include <Keypad.h>. Questa libreria semplifica il progetto perché gestisce internamente gran parte della scansione della tastiera. Invece di impostare manualmente le righe come ingressi, le colonne come uscite, attivare una colonna alla volta e controllare ogni riga, il codice crea un oggetto Keypad usando la disposizione della tastiera, i pin delle righe, i pin delle colonne e la dimensione della tastiera.

L'istruzione Keypad keypad = Keypad(makeKeymap(keys), pin_rows, pin_cols, ROW_NUM, COLUMN_NUM); crea l'oggetto tastiera. La parte makeKeymap(keys) dice alla libreria quale carattere appartiene a ogni posizione fisica del tasto. Gli array pin_rows e pin_cols dicono alla libreria come la tastiera è collegata ad Arduino.

Una volta creato l'oggetto della tastiera, il programma può semplicemente chiamare char key = keypad.getKey();. La funzione keypad.getKey() restituisce il carattere del tasto premuto quando viene rilevata una pressione. Se nessun tasto è premuto, non restituisce alcun tasto valido. Questo rende il ciclo principale molto più pulito perché la funzione di scansione manuale getKey() non è più necessaria.

Usare la libreria Keypad.h è comodo perché migliora la leggibilità e riduce il rischio di errori nel codice di scansione. Inoltre rende il programma più facile da modificare se si usa una tastiera di dimensioni diverse, come una 3x4 invece di una 4x4. La libreria può anche supportare funzioni più avanzate della tastiera, come gli stati dei tasti, il rilevamento della pressione prolungata, il rilevamento del rilascio e la gestione di più tasti, che sono utili in progetti più complessi.

In questo progetto, entrambe le versioni del codice svolgono la stessa funzione principale: l'utente inserisce una password usando la tastiera, preme '#' per confermarla e preme '*' per cancellare l'input. Se la password inserita corrisponde a quella memorizzata, Arduino commuta le uscite sui pin 10 e 11.

Quando si controllano carichi come relè, motori, solenoidi o serrature elettroniche, il carico non deve essere alimentato direttamente dal pin di uscita di Arduino. È necessario usare un transistor, un MOSFET, un modulo relè o un circuito driver adeguato, e la massa di Arduino deve essere collegata alla massa dell'alimentatore esterno quando richiesto.

Pagina Tinkercad:

LINK: https://www.tinkercad.com/things/fB0HnubMKuA-servo-and-irremote