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:
- 1x Arduino UNO
- 1x Tastierino numerico
- 1x relè 5V
- 2x Resistore da 220kΩ
- 1x BC547 (o altro NPN adatto)
- 1x Led
- Cavi jumper (breadboard opzionale)
Schema:
Codice base:
// 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:
// 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.