Archivi tag: sistemi automatici

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

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

Allenamento alla maturità con Arduino

Esercitazioni progressive di laboratorio per studenti di quinta ITIS e professionale

Nota importante: questa pagina è in continuo aggiornamento. I link alle diverse esercitazioni saranno aggiunti progressivamente nell’indice, man mano che i contenuti verranno pubblicati.

La preparazione alla seconda prova dell’Esame di Stato passa anche attraverso l’attività pratica in laboratorio in modo che l’azione possa essere utile per focalizzare lo studio teorico e lo svolgimento delle tracce degli anni precedenti.

Per affrontare bene la maturità serve allenamento operativo, metodo e continuità.
Per questo motivo ho deciso di proporre una serie di esercitazioni pratiche con Arduino, pensate per studenti del quinto anno dell’ITIS – Elettronica e Automazione, utilizzabili sia a scuola in laboratorio con il supporto dei docenti sia a casa in autonomia come attività di ripasso e consolidamento.

Sono in realtà attività pratiche che possono essere svolte in circa 90 minuti di lavoro e che prendono in considerazione argomenti del triennio dell’ITIS che ritengo possano servire per lo svolgimento di problemi che potrebbero essere presenti nel tema d’esame di TPSEE.

Quindi questa raccolta nasce con un obiettivo molto preciso:

aiutare gli studenti a prendere confidenza con quelle strutture di programmazione che, molto spesso, risultano più difficili da capire e da usare in modo corretto, ad esempio:

  • temporizzazioni non bloccanti con millis()
  • multitasking cooperativo
  • array e matrici
  • interrupt
  • puntatori
  • macchine a stati
  • gestione ordinata degli eventi

Due percorsi distinti ma complementari

All’interno del lavoro di preparazione alla maturità sto sviluppando due percorsi paralleli.

Il primo è questa serie di esercitazioni trasversali di programmazione, focalizzate sulle strutture software più importanti e spesso più ostiche.

Il secondo, invece, è già in corso con un mia classe e nel breve pubblicherò le attività di laboratorio, si tratta di una reinterpretazione per il laboratorio di Sistemi elettronici e TPSEE della prova di maturità della sessione ordinaria 2018 di TPSEE, che richiede di affrontare un processo articolato con preallarme, attuatori ON/OFF, acquisizione di sensori, scelta dell’intervallo di campionamento, progettazione delle interfacce e descrizione dell’algoritmo di gestione.

Partiamo però dalle 20 attività.

Come sono costruite le esercitazioni

La struttura delle schede di lavoro rispecchia quelle che in genere consegno ai miei studenti.

Ogni attività sarà presentata con una struttura costante, così da aiutare anche gli studenti che hanno competenze di programmazione ancora deboli.

In ogni post troverete:

  • richiamo teorico iniziale delle istruzioni usate
  • analisi semplificata del problema
  • materiali necessari
  • schema logico di funzionamento
  • diagramma di flusso
  • codice Arduino completo e commentato nel dettaglio
  • spiegazione guidata del programma
  • errori tipici
  • possibili estensioni

Attività 0: prima di programmare, capire bene il testo

Prima ancora di partire con la costruzione del circuito e la programmazione iniziamo con l’Attività 0: la comprensione del testo tecnico, è un problema che si riscontra sempre, soprattutto nella comprensione del testo dell’esame di maturità.

Molto spesso gli studenti si bloccano non perché non sanno programmare, ma perché il testo della prova appare lungo, denso e complesso.
Per questo motivo, prima di scrivere codice, è fondamentale allenarsi a:

  • riconoscere ingressi, uscite, sensori e attuatori
  • distinguere dati misurati e condizioni logiche
  • individuare la sequenza del processo
  • separare la parte hardware dalla parte software
  • … e non ultimo progettare in modo ordinato

Pubblicazione delle soluzioni

Le attività saranno pubblicate progressivamente e in ogni attività lo studente troverà un esercizio aggiuntivo di complessità leggermente superiore rispetto a quello proposto.

Ogni esercizio sarà corredato da diagramma di flusso e codice Mermaid per replicare il diagramma di flusso.

Per favorire il ragionamento autonomo, la soluzione completa verrà resa disponibile dopo qualche giorno dalla pubblicazione dell’esercizio, così da lasciare agli studenti il tempo di provare davvero.

Nota importante

Questa serie di attività è attualmente in costruzione. Nel momento stesso in cui pubblico questo post sto ancora sviluppando e affinando le singole esercitazioni; per questo vi chiedo un po’ di pazienza e comprensione se dovessero essere presenti alcune imprecisioni o aspetti da migliorare. Ogni segnalazione, osservazione o suggerimento sarà quindi molto utile per rendere le schede di lavoro più chiare, efficaci e funzionali.

Naturalmente, quanto propongo non ha la pretesa di esaurire tutti i contenuti necessari per affrontare l’Esame di Stato. Si tratta di una mia selezione di attività, costruita a partire dagli argomenti ricorrenti nelle prove degli anni passati e pensata soprattutto per studenti con competenze di livello medio-base. Per questo motivo le esercitazioni possono essere integrate, adattate o modificate in base alle esigenze della classe, al livello di preparazione degli studenti e alle scelte didattiche del docente.

Di seguito trovate l’indice della serie, che al momento può essere considerato una versione beta, anche se con ogni probabilità resterà molto vicino alla struttura definitiva.
Questa pagina verrà aggiornata progressivamente, aggiungendo di volta in volta i link diretti alle singole esercitazioni.

Indice delle esercitazioni che verranno pubblicate

  • Esercitazione 1 – Pulsante singolo con antirimbalzo, doppio clic, pressione lunga e timeout
  • Esercitazione 2 – Tastiera 4×4 non bloccante con codice di accesso e feedback di errore
  • Esercitazione 3 – Scheduler cooperativo con tre task e supervisione dei tempi
  • Esercitazione 4 – Macchina a stati per un ciclo automatico con START, pausa, allarme e reset
  • Esercitazione 5 – Acquisizione analogica calibrata, filtrata e convertita in grandezza fisica
  • Esercitazione 6 – Controllo a finestra con isteresi, allarme latched e reset
  • Esercitazione 7 – Confronto tra filtro a media mobile e filtro esponenziale
  • Esercitazione 8 – Campionamento temporizzato con array di struct: tempo, valore e stato
  • Esercitazione 9 – Analisi statistica di una sequenza con rilevamento anomalie
  • Esercitazione 10 – Buffer circolare con trend, velocità di variazione e soglia dinamica
  • Esercitazione 11 – Matrice bidimensionale per organizzare campioni di più sensori nel tempo
  • Esercitazione 12 – Frame buffer per matrice LED: icone, animazioni e scorrimento testo
  • Esercitazione 13 – Conteggio impulsi con interrupt e validazione evento
  • Esercitazione 14 – Misura di periodo, frequenza, duty cycle e tempo alto di un segnale PWM
  • Esercitazione 15 – Encoder rotativo con menù parametrico semplificato
  • Esercitazione 16 – Funzioni con parametri passati per indirizzo e restituzione di più risultati
  • Esercitazione 17 – Ordinamento di misure e scambio di valori tramite puntatori
  • Esercitazione 18 – Macchina a stati per un menù su display LCD
  • Esercitazione 19 – Parser di comandi seriali con parametri e risposta strutturata
  • Esercitazione 20 – Mini progetto finale: stazione di monitoraggio completa

Quale sarà la periodicità delle attività? Probabilmente giornaliera, da domani o lunedì prossimo.

Siete pronti per ripassare? 🙂