Archivi tag: puntatori

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? 🙂

Restituire più valori in Arduino “alla C”: output parameters con puntatori – lezione 2/2

Nella lezione precedente abbiamo visto come ottenere più risultati da una funzione usando variabili globali, riferimenti e struct.
In questa seconda lezione facciamo un passo “più vicino al C puro” e affrontiamo una tecnica molto diffusa nei linguaggi embedded: gli output parameters con puntatori.

Cosa intendiamo quando parliamo di puntatori?
Una funzione in genere può restituire un solo valore con return, ma può anche ricevere gli indirizzi di memoria delle variabili che vogliamo aggiornare, in pratica le diciamo: “scrivi i risultati direttamente qui”. Questo approccio è estremamente comune in librerie e API, è efficiente, ma richiede più attenzione: con i puntatori entrano in gioco concetti come indirizzo (&), dereferenziazione (*) e la necessità di evitare errori (ad esempio puntatori nulli).

Quando usiamo i puntatori come parametri di uscita:

  • i parametri in ingresso possono essere valori normali (int numeratore)
  • i parametri in uscita sono puntatori (int *quoziente) perché indicano l’indirizzo della variabile da modificare
  • dentro la funzione, per modificare la variabile “esterna”, dovete usare *:
    • *quoziente = ...; significa “scrivi nella variabile puntata da quoziente”

La regola mentale utile è la seguente:

  • &variabile -> “dammi l’indirizzo”
  • *puntatore -> “vai a quella variabile e usala”

Esempio 01: scambio di due valori con puntatori (swap)

nello sketch che segue viene chiarito come:

  • usare & nella chiamata
  • usare * nella funzione
  • la funzione modifica le variabili chiamanti
/*
  Prof. Maffucci Michele
  15.02.26
  Scambia due valori usando puntatori (stile C).
*/

void scambiaConPuntatori(int *a, int *b) {
  if (a == nullptr || b == nullptr) {
    return; // sicurezza: evita dereferenziazioni invalide
  }

  int temporaneo = *a;
  *a = *b;
  *b = temporaneo;
}

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(A0));
}

void loop() {
  int n1 = random(10);
  int n2 = random(10);

  Serial.print("Valori prima dello scambio (n1, n2): ");
  Serial.print(n1);
  Serial.print(", ");
  Serial.println(n2);

  scambiaConPuntatori(&n1, &n2);

  Serial.print("Valori dopo lo scambio (n1, n2):  ");
  Serial.print(n1);
  Serial.print(", ");
  Serial.println(n2);

  Serial.println();
  delay(1000);
}

La funzione non riceve copie dei numeri, ma riceve gli indirizzi di n1 e n2. Quando scrive *a = ..., sta aggiornando proprio n1 (e analogo per n2).

Esempio 02: divisione con resto (quoziente + resto) e gestione errore

Nello sketch che segue viene mostrato:

  • una funzione che produce due output;
  • un valore booleano di ritorno per indicare successo/errore;
  • controllo se viene fatta una divisione per zero;
  • validazione di puntatori (nullptr).
/*
  Prof. Maffucci Michele
  15.02.26
  Calcola quoziente e resto usando puntatori come parametri di uscita.
*/

bool dividiConRestoPtr(int numeratore, int denominatore, int *quoziente, int *resto) {
  if (denominatore == 0 || quoziente == nullptr || resto == nullptr) {
    return false;
  }

  *quoziente = numeratore / denominatore;
  *resto = numeratore % denominatore;
  return true;
}

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(A0));
}

void loop() {
  int a = random(0, 101);
  int b = random(0, 11); // include lo zero per testare l'errore

  int q = 0;
  int r = 0;

  Serial.print("Operazione: ");
  Serial.print(a);
  Serial.print(" / ");
  Serial.println(b);

  if (dividiConRestoPtr(a, b, &q, &r)) {
    Serial.print("Quoziente: ");
    Serial.println(q);

    Serial.print("Resto: ");
    Serial.println(r);
  } else {
    Serial.println("Errore: divisione per zero o parametri non validi.");
  }

  Serial.println();
  delay(1500);
}

Approfondimento

  • return comunica se l’operazione è riuscita
  • gli output veri e propri vengono scritti nei parametri *quoziente e *resto

È una struttura molto comune anche in librerie C, dove spesso vedete funzioni del tipo:

  • bool readSensor(int *value);
  • int parseData(..., char *outBuffer); (qui l’output è un buffer)

Errori tipici con i puntatori e come evitarli

Errore 01: dimenticare & nella chiamata

scambiaConPuntatori(n1, n2);

state passando interi dove la funzione si aspetta indirizzi -> errore di compilazione (oppure warning serio).

Versione corretta:

scambiaConPuntatori(&n1, &n2);

Errore 02: dereferenziare un puntatore nullo

Dentro una funzione, *p = ... è pericoloso se p è nullptr

per questo motivo negli esempi trovate sempre:

if (p == nullptr) return false;

Errore 03: confondere * e &

Come indicato ad inizio post:

  • &si usa quando chiamate per dare l’indirizzo
  • * si usa dentro la funzione per leggere/scrivere la variabile puntata

Regola generale: quando scegliere i puntatori e quando no

Mi ripeterò però ritengo utile scriverlo.

I puntatori servono quando volete dire a una funzione:

“Non ti sto passando un valore. Ti sto passando dove si trova quel valore in memoria, così puoi modificarlo direttamente.”

dovete fare attenzione perché state lavorando con indirizzi di memoria.

Quando usare i puntatori

01. Quando lavorate in “stile C” o con librerie in C

Molte librerie e molti esempi “embedded” storici sono scritti così:

    • la funzione ritorna true/false (o un codice) per dire se tutto ok
    • i risultati veri e propri vengono scritti nei parametri puntatore

Esempio:

bool leggiSensore(int *valore);

02. Quando dovete far riempire un “contenitore” (buffer/array)

Se dovete far scrivere a una funzione dentro un array o una stringa, spesso userete puntatori.

Esempio:

    • leggere dati e salvarli in un array di byte
    • costruire una stringa di output

In quel caso, “passare l’indirizzo” è naturale perché l’array è già, di fatto, un’area di memoria.

Quando NON usare i puntatori

01. Quando potete usare i riferimenti (&) in Arduino (C++)

Se state scrivendo codice Arduino “normale”, i riferimenti fanno lo stesso lavoro dei puntatori (modificano variabili esterne), ma sono più leggibili e meno soggetti a errori.

Con riferimento:

void scambia(int &a, int &b);

Con puntatori:

void scambia(int *a, int *b);

Per chi inizia con la programmazione, il riferimento comunica meglio:

“questa variabile può essere modificata dalla funzione”

02. Quando il vostro obiettivo è imparare le l’uso delle funzioni senza complicarvi la vita

I puntatori aggiungono concetti nuovi:

    • & (indirizzo)
    • * (dereferenziazione)
    • rischio di puntatori nulli / sbagliati

Se state iniziando con la programmazione e volete capire “come passare dati alle funzioni”, come spiegato nella lezione precedente, è spesso meglio partire da:

    • per valore (copia)
    • per riferimento (modifica)
    • struct (pacchetto di dati)

Buon Coding a tutti 🙂