Esercitazione 4 – Macchina a stati per un ciclo automatico con start, pausa, allarme e reset

Allenamento per l’esame di maturità
Percorso di laboratorio con Arduino per studenti di quinta ITIS

Obiettivo didattico

Modellare un ciclo automatico come macchina a stati: attesa, marcia, pausa, allarme e reset. L’attività avvicina gli studenti al modo in cui si progetta un processo tecnico da esame.

Materiali suggeriti

  • Arduino UNO R3 o UNO R4,
  • 3 pulsanti (START, PAUSA, RESET);
  • 2 LED;
  • 2 Resistori da 220 Ohm (per i LED);
  • jumper.

Schema di collegamento

Richiamo teorico

Una macchina a stati finiti rappresenta un processo come un insieme di stati mutuamente esclusivi. In ogni stato il sistema esegue azioni ben definite e attende eventi che causano il passaggio allo stato successivo. Questo metodo riduce gli errori logici nei programmi complessi.

Schema logico dell’attività

  • All’avvio il sistema è in ATTESA;
  • se arriva START passa a MARCIA;
  • in MARCIA può ricevere PAUSA oppure entrare in ALLARME se scade il tempo massimo;
  • in PAUSA attende una nuova pressione di START;
  • da ALLARME si esce solo con RESET, che riporta il sistema in ATTESA.

Diagramma a stati

Per rappresentare in modo corretto il comportamento di questo esercizio è molto utile affiancare al codice un diagramma a stati. In questo caso, infatti, il sistema non si limita a eseguire istruzioni una dopo l’altra, ma cambia comportamento in funzione della condizione operativa in cui si trova.

Il programma può trovarsi in un solo stato per volta, e ogni stato descrive una precisa fase di funzionamento del sistema. Nel nostro sketch gli stati sono quattro:

  • STATO_ATTESA
  • STATO_MARCIA
  • STATO_PAUSA
  • STATO_ALLARME

Il diagramma a stati è particolarmente adatto a questa situazione perché mostra in modo immediato tre aspetti fondamentali:

  • quali sono gli stati possibili del sistema;
  • quali eventi provocano il passaggio da uno stato all’altro;
  • quale logica di funzionamento è stata implementata nel codice.

In altre parole, mentre il diagramma di flusso descrive bene la sequenza dei controlli svolti nel loop(), il diagramma a stati descrive meglio la struttura logica del processo.

Stato iniziale del sistema

Nel diagramma compare il simbolo iniziale che punta verso STATO_ATTESA. Questo significa che, all’accensione di Arduino o dopo un reset, il sistema parte nello stato di attesa.

In questa fase il ciclo automatico non è attivo. Il LED di marcia è spento, il LED di allarme è spento e il programma controlla continuamente i pulsanti in attesa di un comando di avvio.

Dal punto di vista didattico, questo è il comportamento tipico di molti sistemi automatici: il processo non parte in modo autonomo, ma richiede un comando esplicito dell’operatore.

Transizione da ATTESA a MARCIA

Dal nodo STATO_ATTESA parte una freccia verso STATO_MARCIA con l’etichetta:

START premuto / istanteInizioMarcia = millis()

Questa notazione va letta così: se il sistema si trova in attesa e viene rilevata la pressione del pulsante START, allora il programma passa allo stato di marcia. Durante questa transizione viene anche eseguita un’azione importante, cioè il salvataggio dell’istante iniziale della marcia tramite millis().

Questo passaggio è fondamentale perché permette al sistema di iniziare a contare il tempo trascorso in marcia senza bloccare il programma. In pratica, nel momento in cui parte la marcia, viene memorizzato il riferimento temporale da cui inizierà il controllo del timeout.

Significato dello stato MARCIA

Lo stato STATO_MARCIA rappresenta la fase in cui il sistema è attivo.

In questa condizione il LED di marcia viene acceso, il LED di allarme resta spento e il programma continua a controllare due possibili situazioni:

    • la richiesta di pausa;
    • il superamento del tempo massimo consentito.

Importante: nello stato di marcia il sistema non esegue una sola azione, ma resta in una condizione operativa in cui il programma continua a verificare gli eventi che possono modificare il comportamento complessivo del processo.

Transizione da MARCIA a PAUSA

Se durante la marcia viene premuto il pulsante PAUSA, il sistema passa a STATO_PAUSA.

Questa transizione rappresenta una sospensione del ciclo. Dal punto di vista logico, il processo non è terminato, ma è momentaneamente fermo in attesa di un nuovo comando.

Nel diagramma questa freccia mostra che la pausa può essere richiesta solo quando il sistema si trova effettivamente in marcia. Questo rende molto chiaro un concetto fondamentale: non tutti i comandi hanno senso in tutti gli stati. Il significato di un ingresso dipende dallo stato corrente del sistema.

Transizione da MARCIA a ALLARME

La seconda uscita possibile da STATO_MARCIA conduce a STATO_ALLARME ed è attivata dal superamento del tempo massimo di marcia.

Nel codice questa condizione è realizzata confrontando il tempo attuale, ottenuto con millis(), con l’istante iniziale salvato all’ingresso nello stato di marcia. Quando la differenza raggiunge o supera il valore impostato in TEMPO_MASSIMO_MARCIA, il sistema genera l’allarme.

Questo passaggio è interessante perché mostra come una macchina a stati possa cambiare stato non solo in risposta a un pulsante, ma anche in risposta a una condizione temporale. In molti sistemi automatici reali, infatti, il tempo costituisce un vincolo di sicurezza o di controllo del processo.

Significato dello stato PAUSA

Lo stato STATO_PAUSA rappresenta una sospensione controllata del ciclo.

In questa fase il LED di marcia è spento e il LED di allarme è spento. Il programma non torna allo stato iniziale, ma resta in una condizione intermedia da cui il processo può riprendere.

Dal diagramma si vede che da questo stato esiste una sola transizione utile: la pressione di START, che riporta il sistema in STATO_MARCIA.

È importante però osservare un dettaglio del codice: quando si torna in marcia dalla pausa, il programma esegue di nuovo l’istruzione che salva istanteInizioMarcia = millis(). Questo significa che il conteggio del tempo massimo non viene congelato e poi ripreso, ma riparte da zero.

Questo aspetto è importante, perché aiuta a capire che il diagramma descrive la struttura generale del comportamento, mentre le variabili e le azioni associate alle transizioni determinano il comportamento concreto del sistema.

Significato dello stato ALLARME

Lo stato STATO_ALLARME rappresenta una condizione anomala o di sicurezza.

Quando il sistema entra in allarme, il LED di marcia viene spento e il LED di allarme viene acceso. In questo modo l’operatore può riconoscere immediatamente che il ciclo si è interrotto per il superamento del limite previsto.

Dal diagramma si vede che da STATO_ALLARME non si può tornare direttamente in marcia. L’unica uscita prevista è verso STATO_ATTESA, e questa transizione è attivata dal pulsante RESET.

Anche questa è una scelta molto importante dal punto di vista progettuale: in presenza di un allarme il sistema non riparte automaticamente, ma richiede un intervento esplicito da parte dell’operatore. È una logica tipica dei sistemi in cui la sicurezza o il controllo delle anomalie hanno priorità sulla continuità del funzionamento.

IMPORTANTE

Quando si studiano le macchine a stati è utile distinguere tra:

      • azioni di stato, cioè ciò che il sistema fa mentre si trova in un determinato stato;
      • azioni di transizione, cioè ciò che il sistema esegue nel momento in cui passa da uno stato a un altro.

Nell’esercizio proposto, l’accensione o lo spegnimento dei LED, è un’azione associata allo stato, perché avviene nel blocco di codice relativo a ciascun case.

Il salvataggio di istanteInizioMarcia, invece, è un’azione di transizione, perché viene eseguito nel momento in cui il sistema entra nello stato di marcia.
Questa distinzione è molto utile per comprendere meglio sia il diagramma sia il codice.

Continua a leggere

Domande dagli utenti – Perché Arduino legge valori strani se il pin non è collegato?

Ho deciso di mettere maggiormente in evidenza le richieste di chiarimento su diversi argomenti tecnici che mi giungono via mail o le domande lasciate nei commenti ai miei post. Spesso questi interventi nascono da dubbi autentici e molto concreti, ma rischiano di perdersi tra i tanti contenuti pubblicati. Per questo motivo ho pensato che alcune di queste domande meritino di essere riprese e trasformate in risposte più ampie, dettagliate e utili anche per altri lettori.

Molto spesso a scrivermi sono studenti giovanissimi, che stanno iniziando il loro percorso nell’elettronica, nella programmazione o nell’uso di Arduino. Ed è proprio a loro che sento di dover dedicare un’attenzione particolare, perché dietro una domanda apparentemente semplice si nasconde quasi sempre un passaggio importante da chiarire. Sono convinto, infatti, che proprio dalle domande più essenziali si costruiscano le basi migliori per capire davvero.

Molti, quando mi scrivono, aggiungono frasi come:

“Mi scusi per la domanda forse banale…”

oppure

“Preferisco scriverle in privato perché non vorrei fare una figura da inesperto nei commenti”

Su questo vorrei essere molto chiaro, perché per me è un aspetto importante, sia come docente sia come autore di contenuti didattici: non esiste la domanda sciocca, esiste solo la domanda non fatta.

Anzi, molto spesso le domande più utili sono proprio quelle che sembrano più elementari, perché costringono a chiarire i concetti fondamentali. E senza fondamenta solide, anche gli argomenti più avanzati restano fragili.

Una delle domande che mi è arrivata di recente è questa:

“Prof, perché Arduino mi legge valori strani anche se non ho collegato niente al pin?”

È una domanda ottima ed è anche una domanda che può aiutare molte persone che iniziano a usare Arduino, l’elettronica o i sensori.

Risposta

Perché un pin lasciato scollegato non è in uno stato definito.

Se un ingresso non è collegato né al positivo né alla massa, il microcontrollore non possiede un riferimento elettrico chiaro. In questa situazione il pin può captare piccoli disturbi presenti nell’ambiente circostante e restituire valori instabili, apparentemente casuali.

Quindi, nella maggior parte dei casi, non c’è nessun guasto nella scheda.
Il pin sta semplicemente lavorando in una condizione detta flottante.

Che cosa significa “pin flottante”

Quando si comincia a usare Arduino, è facile immaginare che un pin non collegato equivalga a “zero” oppure a “nessun segnale”.

In realtà non funziona così.

Un ingresso lasciato libero può comportarsi un po’ come una piccola antenna: intercetta disturbi elettrici, rumori, influenze del circuito circostante e può quindi produrre letture variabili anche se noi non stiamo toccando nulla.

Questo significa che:

  • un ingresso digitale può passare casualmente tra HIGH e LOW;
  • un ingresso analogico può mostrare valori che cambiano continuamente nel monitor seriale.

È un comportamento molto istruttivo, perché mostra subito una differenza importante tra lo schema ideale disegnato sulla carta e il comportamento reale dei circuiti.

L’idea fondamentale da ricordare

Il concetto chiave è questo:

in elettronica, “non collegato” non significa “zero”.

Questo è uno di quei principi che ritornano continuamente quando si lavora con pulsanti, sensori, interruttori, linee di ingresso e acquisizioni analogiche.

Capirlo bene fin dall’inizio evita molti errori e, soprattutto, aiuta a ragionare in modo più corretto sul funzionamento dei circuiti.

Come si risolve

Per evitare che un ingresso fluttui, bisogna assegnargli uno stato elettrico preciso.

Di solito si usa una di queste soluzioni:

  • una resistenza di pull-down, che mantiene il pin a livello basso quando non arriva alcun segnale;
  • una resistenza di pull-up, che mantiene il pin a livello alto;
  • la modalità INPUT_PULLUP di Arduino, molto comoda per gli ingressi digitali, perché sfrutta una resistenza interna del microcontrollore.

È una delle prime configurazioni corrette da imparare, soprattutto quando si cominciano a collegare pulsanti.

Un piccolo esperimento

Per osservare direttamente il fenomeno si può fare una prova semplicissima.

Si collega Arduino al computer, si carica uno sketch che legge un ingresso analogico lasciato libero, e si osservano i valori nel monitor seriale.

Esempio di sketch: lettura di un pin analogico flottante

/*
  Lettura di un ingresso analogico lasciato scollegato
  per mostrare il comportamento di un pin flottante.

  Collegamenti:
  - Non collegare nulla al pin A0
  - Aprire il Monitor Seriale a 9600 baud

  Osservazione attesa:
  I valori letti non saranno stabili, ma varieranno nel tempo.
*/

const int pinAnalogico = A0;   // Pin analogico lasciato volutamente scollegato

void setup() {
  Serial.begin(9600);
  delay(1000); // Piccola attesa per dare tempo al monitor seriale di aprirsi

  Serial.println("Lettura di un pin analogico flottante");
  Serial.println("Il pin A0 non e' collegato a nulla.");
  Serial.println("Osserva i valori letti:");
}

void loop() {
  int valoreLetto = analogRead(pinAnalogico);  // Legge il valore presente su A0

  Serial.print("Valore letto: ");
  Serial.println(valoreLetto);

  delay(200); // Solo per rallentare la visualizzazione
}

Che cosa si osserva

Aprendo il monitor seriale, i valori non resteranno fermi.
Potranno cambiare anche senza alcun intervento esterno.

A seconda della scheda, dell’ambiente, dei cavi vicini, della posizione delle mani e del rumore elettrico circostante, potresti vedere numeri che oscillano in modo più o meno evidente.

Ed è proprio questo il punto interessante: il pin non è “a zero”, ma è in una condizione indefinita.

Una seconda prova ancora più istruttiva

Dopo aver osservato il comportamento flottante, potete ripetere l’esperimento collegando:

  • una resistenza di pull-down verso GND;
  • oppure un partitore o un riferimento fisso;
  • oppure, nel caso di un ingresso digitale, attivando INPUT_PULLUP.

Il confronto tra “prima” e “dopo” è didatticamente molto efficace, perché fa vedere in modo concreto cosa significa stabilizzare un ingresso.

Variante digitale

Lo stesso fenomeno si può osservare anche con un ingresso digitale lasciato libero.

/*
  Lettura di un ingresso digitale flottante.

  Collegamenti:
  - Non collegare nulla al pin 2
  - Aprire il Monitor Seriale a 9600 baud

  Osservazione attesa:
  Il pin potra' cambiare in modo imprevedibile tra HIGH e LOW.
*/

const int pinDigitale = 2;   // Pin digitale lasciato scollegato

void setup() {
  Serial.begin(9600);
  pinMode(pinDigitale, INPUT);  // INPUT semplice: nessuna pull-up interna attiva

  delay(1000);

  Serial.println("Lettura di un pin digitale flottante");
  Serial.println("Il pin 2 non e' collegato a nulla.");
}

void loop() {
  int statoPin = digitalRead(pinDigitale);

  Serial.print("Stato letto: ");
  if (statoPin == HIGH) {
    Serial.println("HIGH");
  } else {
    Serial.println("LOW");
  }

  delay(200);
}

In questo caso il risultato potrà essere meno “spettacolare” oppure più irregolare, ma il principio resta identico: senza riferimento, l’ingresso non è affidabile.

Una domanda come questa può sembrare piccola, ma in realtà apre la porta a uno dei concetti più importanti dell’elettronica di base:

ogni ingresso deve avere un riferimento chiaro.

Capire bene questo passaggio significa costruire basi più solide per affrontare tutto il resto: pulsanti, sensori, automazione, misure, controllo e programmazione embedded.

E quindi, come dico sempre a chi mi scrive in privato:
fate domande, SEMPRE.

Perché una domanda sincera, anche molto semplice, vale molto più di un silenzio pieno di dubbi.

Piccola proposta pratica per il laboratorio

Chi vuole può provare questa sequenza:

  1. caricare lo sketch con A0 scollegato;
  2. osservare i valori nel monitor seriale;
  3. toccare con un dito il filo collegato ad A0 o avvicinare la mano;
  4. osservare come cambiano i valori;
  5. collegare poi il pin a GND oppure a 5 V tramite una configurazione corretta;
  6. confrontare il comportamento.

È una prova molto semplice, ma estremamente utile per capire, come dicevo ad inizio post, che in elettronica anche ciò che sembra “non collegato” può avere effetti reali.

Buona esplorazione 🙂

Esercitazione 3 – Scheduler cooperativo con tre task e supervisione dei tempi

Allenamento per l’esame di maturità
Percorso di laboratorio con Arduino per studenti di quinta ITIS

Obiettivo didattico

Organizzare il programma come scheduler cooperativo con tre task indipendenti: acquisizione analogica, lampeggio di stato e trasmissione seriale periodica. L’attività mostra come il loop() possa diventare un piccolo supervisore software.

Materiali suggeriti

  • Arduino UNO R3 o UNO R4;
  • 1 potenziometro;
  • 2 LED;
  • 2 resitori (per i LED);
  • breadboard;
  • cavetti jumper.

Schema di collegamento

Richiamo teorico

In un sistema embedded semplice non si usa un vero sistema operativo, ma si può costruire uno scheduler cooperativo con millis(). Ogni task possiede il proprio intervallo e il proprio istante dell’ultima esecuzione. Il loop() controlla se ciascun task è pronto e lo richiama.

Schema logico dell’attività

Il programma inizializza i timer dei tre task. Nel loop legge il tempo corrente e verifica uno dopo l’altro se è il momento di eseguire il task di acquisizione, quello di segnalazione e quello di stampa seriale. Se il tempo non è scaduto, passa al controllo successivo.

Diagramma di flusso

Diagramma di flusso Mermaid

flowchart TD
    A[Inizio] --> B[Configura pin, seriale e timer]
    B --> C[Leggi tempo attuale]
    C --> D{Task acquisizione pronto?}
    D -- Sì --> E[Esegui acquisizione]
    D -- No --> F{Task LED pronto?}
    E --> F
    F -- Sì --> G[Commuta LED di stato]
    F -- No --> H{Task seriale pronto?}
    G --> H
    H -- Sì --> I[Invia dati in seriale]
    H -- No --> J[Ritorna al loop]
    I --> J
    J --> C

Programma

/*
  Prof. Maffucci Michele
  Esercizio 3: scheduler cooperativo con tre task indipendenti
*/

const int PIN_SENSORE = A0;
const int PIN_LED_STATO = 8;
const int PIN_LED_SOGLIA = 9;

// ---------------------------
// Intervalli dei tre task
// ---------------------------
const unsigned long PERIODO_ACQUISIZIONE = 50;
const unsigned long PERIODO_LED = 300;
const unsigned long PERIODO_SERIALE = 500;

// ---------------------------
// Istanti ultima esecuzione
// ---------------------------
unsigned long ultimoTaskAcquisizione = 0;
unsigned long ultimoTaskLed = 0;
unsigned long ultimoTaskSeriale = 0;

// ---------------------------
// Variabili condivise
// ---------------------------
int valoreGrezzo = 0;
float tensione = 0.0;
bool statoLed = false;

void setup() {
  pinMode(PIN_LED_STATO, OUTPUT);
  pinMode(PIN_LED_SOGLIA, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // Il loop assume il ruolo di piccolo scheduler.
  unsigned long adesso = millis();

  // ---------------------------
  // Task 1: acquisizione
  // ---------------------------
  if ((adesso - ultimoTaskAcquisizione) >= PERIODO_ACQUISIZIONE) {
    ultimoTaskAcquisizione = adesso;
    taskAcquisizione();
  }

  // ---------------------------
  // Task 2: LED di stato
  // ---------------------------
  if ((adesso - ultimoTaskLed) >= PERIODO_LED) {
    ultimoTaskLed = adesso;
    taskLed();
  }

  // ---------------------------
  // Task 3: seriale
  // ---------------------------
  if ((adesso - ultimoTaskSeriale) >= PERIODO_SERIALE) {
    ultimoTaskSeriale = adesso;
    taskSeriale();
  }
}

// ----------------------------------------------------------
// Legge il sensore e calcola una tensione equivalente.
// ----------------------------------------------------------
void taskAcquisizione() {
  valoreGrezzo = analogRead(PIN_SENSORE);

  // Conversione esplicita ADC -> tensione.
  tensione = (valoreGrezzo * 5.0) / 1023.0;

  // Uso della misura per attivare un LED di soglia.
  if (tensione >= 2.50) {
    digitalWrite(PIN_LED_SOGLIA, HIGH);
  } else {
    digitalWrite(PIN_LED_SOGLIA, LOW);
  }
}

// ----------------------------------------------------------
// Task di segnalazione periodica.
// ----------------------------------------------------------
void taskLed() {
  if (statoLed == false) {
    statoLed = true;
    digitalWrite(PIN_LED_STATO, HIGH);
  } else {
    statoLed = false;
    digitalWrite(PIN_LED_STATO, LOW);
  }
}

// ----------------------------------------------------------
// Task di comunicazione seriale.
// ----------------------------------------------------------
void taskSeriale() {
  Serial.print("ADC = ");
  Serial.print(valoreGrezzo);
  Serial.print("  Tensione = ");
  Serial.print(tensione, 2);
  Serial.println(" V");
}

Continua a leggere

Capire prima di costruire: una nuova serie di elettrotecnica ed elettronica

Un percorso per chiarire i concetti fondamentali e lavorare in laboratorio con maggiore consapevolezza.

Chi frequenta da tempo questo sito sa che qui trovano spazio soprattutto articoli approfonditi: lezioni estese, tutorial dettagliati, schemi, immagini, codice, proposte operative per il laboratorio e percorsi pensati per la didattica.

L’obiettivo, in queste pagine, è sempre stato quello di affrontare gli argomenti con attenzione, provando a costruire materiali chiari, solidi e riutilizzabili per studenti, docenti, appassionati e maker.

Proprio per questo, può forse sorprendere la nascita di una nuova serie di post più brevi. Eppure questa scelta non nasce da un cambio di rotta, né da una riduzione delle ambizioni didattiche. Nasce, al contrario, da un’esigenza molto concreta che negli ultimi anni è diventata sempre più evidente: per affrontare bene i temi più complessi, bisogna avere chiari i concetti fondamentali.

Da anni lavoro soprattutto con studenti del triennio dell’ITIS, in particolare delle classi quarte e quinte. Più recentemente, però, mi è capitato sempre più spesso di accompagnare anche studenti del biennio, molti dei quali mostrano un entusiasmo autentico verso l’uso di Arduino e desiderano progettare dispositivi, automazioni e piccoli sistemi anche piuttosto articolati.

Questo entusiasmo è prezioso. Curiosità, desiderio di costruire, voglia di sperimentare: sono spesso il miglior punto di partenza possibile per avvicinarsi all’elettronica e all’automazione.

Accanto a questo slancio iniziale, però, nel lavoro quotidiano in laboratorio emerge con chiarezza anche un altro aspetto: le nozioni di base dell’elettrotecnica e dell’elettronica non sempre sono sufficientemente stabili e consapevoli.

Capita così che termini come tensione, corrente, resistenza, massa, polarità, collegamento in serie e in parallelo, uso corretto del multimetro o funzione di una resistenza di pull-up vengano utilizzati in modo intuitivo, ma non pienamente compresi.

Molti dei temi che compariranno in questa serie nascono proprio dalle domande ricorrenti che gli studenti più giovani mi pongono durante le lezioni e le attività di laboratorio. Ho voluto raccogliere quei dubbi, quelle richieste di chiarimento e quelle incertezze che emergono con maggiore frequenza per trasformarle in un percorso ordinato, accessibile e utile anche al di fuori della classe.

Ed è precisamente da questa osservazione che nasce l’idea della nuova rubrica.

Molto spesso, infatti, la difficoltà non sta soltanto negli argomenti avanzati, ma nel fatto che i fondamenti vengono incontrati presto, usati presto, ma non sempre assimilati con la necessaria profondità. Così può accadere che uno studente riesca a montare un circuito, caricare uno sketch e vedere un sistema funzionare, ma fatichi poi a spiegare che cosa stia realmente accadendo dal punto di vista elettrico. E senza questa comprensione, anche le attività più motivanti rischiano di restare fragili.

Per questo ho deciso di affiancare ai tutorial più articolati una serie di lezioni brevi, chiare e mirate, pensate per tornare sui concetti essenziali con un linguaggio accessibile, ma senza rinunciare al rigore.

Saranno testi più snelli nella forma, ma costruiti con la stessa attenzione che riservo ai contenuti più estesi: attenzione ai dubbi reali degli studenti, agli errori ricorrenti, alle semplificazioni fuorvianti e ai passaggi che, se trascurati, rendono più difficile tutto ciò che viene dopo.

A questa scelta si lega anche una riflessione sul formato.

So bene che oggi il video è uno strumento potente, immediato e molto efficace sotto molti aspetti. Non ho nulla contro questa forma di comunicazione, che anzi può essere molto utile in diversi contesti. Tuttavia, realizzare video con continuità richiede tempi di progettazione, registrazione, montaggio e revisione che, almeno in questa fase, non riesco a sostenere in modo regolare. Ma il punto, per me, non è soltanto organizzativo.

Da sempre prediligo la lezione scritta.

La scrittura mi obbliga a rallentare, a ordinare meglio le idee, a scegliere con maggiore precisione le parole e a riflettere più a fondo sul modo in cui un concetto può essere spiegato. Ogni testo diventa così non solo un contenuto da pubblicare, ma anche una traccia di lavoro, una struttura didattica, un riferimento che posso riprendere, sviluppare e collegare ad altri argomenti nel tempo. In questo senso, le lezioni scritte funzionano per me anche come uno storyboard permanente: aiutano chi legge, ma aiutano anche me a costruire percorsi più coerenti.

C’è poi un secondo aspetto che considero importante.

Nel caso di argomenti tecnici di base, credo che leggere un testo mentre si osserva uno schema, si prende un appunto, si prova un collegamento, si misura un valore o si monta un piccolo circuito possa essere estremamente formativo. Il testo scritto ha un ritmo diverso: permette di fermarsi, tornare indietro, rileggere, verificare, annotare, confrontare subito teoria e pratica. Non impone velocità, lascia spazio al tempo dell’apprendimento.

Non penso affatto che il testo debba sostituire il video in assoluto. Penso però che, almeno per alcuni apprendimenti fondamentali, la combinazione tra lettura, riflessione e azione pratica sia particolarmente efficace. In un contesto in cui siamo sempre più abituati a fruire contenuti rapidamente, riprendere un passo più lento può sembrare controcorrente, eppure, proprio questa lentezza consente spesso di fissare meglio i concetti e di trasformarli in competenza reale.

Questa nuova serie, dunque, non nasce per sostituire gli articoli lunghi, né per semplificare artificialmente i contenuti. Nasce per affiancarli e per offrire un percorso di accesso più graduale a studenti, principianti e lettori curiosi che desiderano chiarire bene i fondamenti prima di affrontare temi più complessi.

Ogni post sarà costruito attorno a una domanda semplice o a un nodo concettuale essenziale. L’obiettivo sarà spiegare in modo comprensibile, ma corretto, un singolo aspetto dell’elettrotecnica o dell’elettronica, cercando di collegarlo, quando possibile, a situazioni reali di laboratorio, a esempi concreti o a errori frequenti da evitare.

Questa serie è pensata soprattutto per:

  • studenti che stanno iniziando;
  • docenti che cercano spiegazioni sintetiche ma affidabili da riprendere in classe;
  • appassionati e maker che desiderano chiarire alcuni concetti di base;
  • lettori che preferiscono contenuti brevi, ma non superficiali.

Continuo a credere che, soprattutto nelle discipline tecniche, spiegare bene le basi sia un lavoro importante quanto affrontare gli argomenti più avanzati. Anzi, spesso è proprio dalla qualità delle fondamenta che dipende la possibilità di comprendere davvero tutto il resto.

Per questo motivo, accanto ai tutorial più ampi e strutturati, da oggi troveranno spazio anche queste brevi lezioni essenziali, pensate per rendere più accessibili i concetti fondamentali dell’elettrotecnica e dell’elettronica e per accompagnare, un passo alla volta, chi sta iniziando questo percorso.

Da dove partirà la serie

Questa collezione di lezioni è attualmente in costruzione e si svilupperà progressivamente a partire da alcuni concetti fondamentali che, nell’attività di laboratorio, si rivelano spesso decisivi per comprendere davvero ciò che si sta facendo.

Tra i primi temi che intendo affrontare ci sono:

  • differenza tra tensione, corrente e resistenza;
  • perché un LED ha bisogno di una resistenza in serie;
  • che cosa significa GND in un circuito;
  • collegamento in serie e in parallelo;
  • che cos’è un cortocircuito e perché è pericoloso;
  • a cosa serve il multimetro e come iniziare a usarlo;
  • pull-up e pull-down;
  • differenza tra segnale analogico e digitale.

Il percorso crescerà nel tempo, anche a partire dalle difficoltà più frequenti che emergono durante le attività pratiche e dalle domande ricorrenti degli studenti.

Spero che questa nuova formula possa essere utile.

Come sempre, osservazioni, suggerimenti e proposte di temi da affrontare saranno molto ben accetti.

Buona lettura e buon lavoro 🙂

Esercitazione 2 – Tastiera 4×4 non bloccante con codice di accesso e feedback di errore

Allenamento per l’esame di maturità
Percorso di laboratorio con Arduino per studenti di quinta ITIS

Obiettivo didattico

Scansionare una tastiera 4×4 senza usare una libreria esterna, acquisire una sequenza di tasti, confrontarla con un codice corretto e fornire un feedback di esito positivo o errore. L’attività allena gestione di matrici di pin, scansione righe/colonne, buffer di input e logica non bloccante.

Materiali suggeriti

  • Arduino UNO R3 o UNO R4;
  • tastiera 4×4;
  • LED verde;
  • LED rosso,
  • buzzer opzionale;
  • 2 resitori da 220 Ohm (per i LED);
  • breadboard;
  • cavetti jumper.

Schema di collegamento

Richiamo teorico

Una tastiera a matrice riduce il numero di fili usando righe e colonne. Il microcontrollore attiva una riga alla volta e legge le colonne. Se una colonna va a livello attivo, il tasto corrispondente a quella riga e colonna è premuto. Anche qui bisogna evitare il rimbalzo e gestire l’ingresso come sequenza di eventi.

Schema logico dell’attività

Il programma scansiona una riga alla volta. Quando rileva un tasto stabile, lo aggiunge al buffer. Se viene premuto # confronta il buffer con il codice atteso. Se il codice è corretto accende il LED verde. Se è errato accende il LED rosso, svuota il buffer e ricomincia.

Diagramma di flusso

Diagramma di flusso Mermaid

flowchart TD
    A[Inizio] --> B[Configura righe, colonne e LED]
    B --> C[Scansione tastiera]
    C --> D{Tasto valido trovato?}
    D -- No --> C
    D -- Sì --> E{Tasto uguale a # ?}
    E -- No --> F[Aggiungi carattere al buffer]
    F --> C
    E -- Sì --> G[Confronta buffer con codice]
    G --> H{Codice corretto?}
    H -- Sì --> I[Feedback verde e reset buffer]
    H -- No --> J[Feedback rosso e reset buffer]
    I --> C
    J --> C

Programma

/*
  Prof. Maffucci Michele
  Esercizio 2: Tastiera 4x4 non bloccante con codice di accesso e feedback di errore
*/

// ---------------------------
// Pin delle 4 righe
// ---------------------------
const int righe[4] = { 2, 3, 4, 5 };

// ---------------------------
// Pin delle 4 colonne
// ---------------------------
const int colonne[4] = { 6, 7, 8, 9 };

// ---------------------------
// Mappa dei tasti
// ---------------------------
char mappaTasti[4][4] = {
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};

// ---------------------------
// LED di feedback
// ---------------------------
const int PIN_LED_VERDE = 10;
const int PIN_LED_ROSSO = 11;

// ---------------------------
// Codice corretto da inserire
// ---------------------------
char codiceCorretto[] = "2580";

// ---------------------------
// Buffer di ingresso utente
// ---------------------------
char bufferInput[8];
int indiceInput = 0;

// ---------------------------
// Variabili per antirimbalzo
// ---------------------------
char ultimoTastoLetto = '\0';
char ultimoTastoConfermato = '\0';
unsigned long istanteCambio = 0;
const unsigned long TEMPO_DEBOUNCE = 40;

void setup() {
  // Le righe vengono pilotate in uscita.
  for (int i = 0; i < 4; i = i + 1) {
    pinMode(righe[i], OUTPUT);
    digitalWrite(righe[i], HIGH);
  }

  // Le colonne sono ingressi con pull-up.
  for (int i = 0; i < 4; i = i + 1) {
    pinMode(colonne[i], INPUT_PULLUP);
  }

  pinMode(PIN_LED_VERDE, OUTPUT);
  pinMode(PIN_LED_ROSSO, OUTPUT);

  Serial.begin(9600);
  svuotaBuffer();
}

void loop() {
  // Leggo il tasto corrente tramite scansione.
  char tastoCorrente = leggiTastiera();

  // Se è cambiato il valore grezzo, aggiorno il tempo.
  if (tastoCorrente != ultimoTastoLetto) {
    ultimoTastoLetto = tastoCorrente;
    istanteCambio = millis();
  }

  // Se il valore è stabile da abbastanza tempo, lo confermo.
  if ((millis() - istanteCambio) >= TEMPO_DEBOUNCE) {
    if (tastoCorrente != ultimoTastoConfermato) {
      ultimoTastoConfermato = tastoCorrente;

      // Elaboro il tasto solo quando è reale e non nullo.
      if (ultimoTastoConfermato != '\0') {
        gestisciTasto(ultimoTastoConfermato);
      }
    }
  }
}

// ----------------------------------------------------------
// Scansione manuale della tastiera.
// Attivo una riga alla volta e leggo tutte le colonne.
// ----------------------------------------------------------
char leggiTastiera() {
  for (int r = 0; r < 4; r = r + 1) {

    // Prima porto tutte le righe alte.
    for (int i = 0; i < 4; i = i + 1) {
      digitalWrite(righe[i], HIGH);
    }

    // Poi attivo solo la riga corrente.
    digitalWrite(righe[r], LOW);

    // Leggo tutte le colonne.
    for (int c = 0; c < 4; c = c + 1) {
      if (digitalRead(colonne[c]) == LOW) {
        return mappaTasti[r][c];
      }
    }
  }

  // Se non trovo alcun tasto, restituisco nullo.
  return '\0';
}

// ----------------------------------------------------------
// Gestione del carattere ricevuto.
// * cancella, # conferma, altri tasti vengono memorizzati.
// ----------------------------------------------------------
void gestisciTasto(char tasto) {
  Serial.print("Tasto ricevuto: ");
  Serial.println(tasto);

  if (tasto == '*') {
    svuotaBuffer();
    Serial.println("Buffer cancellato");
  } else if (tasto == '#') {
    verificaCodice();
  } else {
    if (indiceInput < 7) {
      bufferInput[indiceInput] = tasto;
      indiceInput = indiceInput + 1;
      bufferInput[indiceInput] = '\0';
    }
  }
}

// ----------------------------------------------------------
// Confronto del buffer con il codice corretto.
// ----------------------------------------------------------
void verificaCodice() {
  bool corretto = true;

  for (int i = 0; codiceCorretto[i] != '\0'; i = i + 1) {
    if (bufferInput[i] != codiceCorretto[i]) {
      corretto = false;
    }
  }

  // Verifico anche la lunghezza.
  if (indiceInput != 4) {
    corretto = false;
  }

  if (corretto == true) {
    Serial.println("CODICE CORRETTO");
    digitalWrite(PIN_LED_VERDE, HIGH);
    delay(500);
    digitalWrite(PIN_LED_VERDE, LOW);
  } else {
    Serial.println("CODICE ERRATO");
    digitalWrite(PIN_LED_ROSSO, HIGH);
    delay(500);
    digitalWrite(PIN_LED_ROSSO, LOW);
  }

  svuotaBuffer();
}

// ----------------------------------------------------------
// Ripulisce il buffer e rimette l'indice a zero.
// ----------------------------------------------------------
void svuotaBuffer() {
  for (int i = 0; i < 8; i = i + 1) {
    bufferInput[i] = '\0';
  }
  indiceInput = 0;
}

Continua a leggere