Archivi tag: arduino

Esercitazione 1 – Pulsante singolo con antirimbalzo, doppio clic, pressione lunga e timeout

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

Obiettivo didattico

Realizzare una piccola interfaccia utente con un solo pulsante. Il sistema deve riconoscere una pressione breve, un doppio clic, una pressione lunga e un timeout di inattività. L’attività allena la gestione degli eventi, l’antirimbalzo software e l’uso di millis() senza bloccare il programma.

Nota importante

La comprensione del funzionamento di questo esercizio permetterà lo svolgimento in autonomia dell’esercizio aggiuntivo che trovate al fondo di questa scheda di lavoro.

Materiali suggeriti

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

Schema di collegamento

Richiamo teorico

Per leggere correttamente un pulsante reale bisogna evitare il rimbalzo dei contatti. Si usa quindi una variabile con l’ultima lettura, una temporizzazione di stabilizzazione e una logica a eventi. Con millis() si misura il tempo senza usare delay(), così il programma può continuare a controllare altri compiti.

Schema logico dell’attività

Il programma legge continuamente il pulsante. Se la lettura cambia, attende il tempo di debounce. Quando il livello è stabile, riconosce pressione e rilascio. Dal tempo trascorso ricava l’evento: clic breve, doppio clic oppure pressione lunga. Se non accade nulla per molti secondi, attiva un LED di timeout.

Diagramma di flusso

Diagramma di flusso Mermaid

Ricordo che per realizzare il diagramma di flusso, copiate il codice Mermaid seguendo le indicazioni della lezione: Progettare bene, programmare meglio: diagrammi di flusso – cos’è il formato Mermaid? – Lezione 2/5

flowchart TD
    A[Inizio] --> B[Configura pin e variabili]
    B --> C[Leggi pulsante]
    C --> D{Lettura cambiata?}
    D -- Sì --> E[Salva istante variazione]
    D -- No --> F{Tempo debounce trascorso?}
    E --> F
    F -- No --> C
    F -- Sì --> G{Stato stabile cambiato?}
    G -- No --> H{Timeout inattività?}
    G -- Sì --> I{Pulsante premuto?}
    I -- Sì --> J[Memorizza istante pressione]
    I -- No --> K[Calcola durata pressione]
    K --> L{Pressione lunga?}
    L -- Sì --> M[Genera evento LONG]
    L -- No --> N{Secondo clic entro finestra?}
    N -- Sì --> O[Genera evento DOUBLE]
    N -- No --> P[Attendi possibile secondo clic]
    H -- Sì --> Q[Attiva LED timeout]
    H -- No --> C
    J --> C
    M --> C
    O --> C
    P --> C
    Q --> C

Programma

/*
  Prof. Maffucci Michele
  Esercizio 1: Pulsante singolo con antirimbalzo, doppio clic, pressione lunga e timeout
*/


// ---------------------------
// Definizione dei pin usati
// ---------------------------
const int PIN_PULSANTE = 2;
const int PIN_LED_BREVE = 8;
const int PIN_LED_DOPPIO = 9;
const int PIN_LED_TIMEOUT = 10;

// ---------------------------
// Costanti temporali
// ---------------------------
const unsigned long TEMPO_DEBOUNCE = 30;
const unsigned long SOGLIA_PRESSIONE_LUNGA = 800;
const unsigned long FINESTRA_DOPPIO_CLICK = 350;
const unsigned long TEMPO_TIMEOUT = 5000;

// ---------------------------
// Variabili per antirimbalzo
// ---------------------------
int ultimaLetturaGrezza = HIGH;
int statoStabile = HIGH;
unsigned long istanteUltimaVariazione = 0;

// ---------------------------
// Variabili per eventi utente
// ---------------------------
bool attesaSecondoClick = false;
unsigned long istantePrimoClick = 0;
unsigned long istantePressione = 0;
unsigned long ultimoEventoUtente = 0;

void setup() {
  // Il pulsante usa la resistenza interna di pull-up.
  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  // I tre LED rappresentano tre eventi diversi.
  pinMode(PIN_LED_BREVE, OUTPUT);
  pinMode(PIN_LED_DOPPIO, OUTPUT);
  pinMode(PIN_LED_TIMEOUT, OUTPUT);

  // All'avvio tutti i LED sono spenti.
  digitalWrite(PIN_LED_BREVE, LOW);
  digitalWrite(PIN_LED_DOPPIO, LOW);
  digitalWrite(PIN_LED_TIMEOUT, LOW);

  // La seriale aiuta a vedere quale evento viene riconosciuto.
  Serial.begin(9600);

  // Salvo il tempo iniziale come ultimo evento.
  ultimoEventoUtente = millis();
}

void loop() {
  // Leggo il pulsante in forma grezza.
  int letturaCorrente = digitalRead(PIN_PULSANTE);

  // Se la lettura è cambiata rispetto alla precedente,
  // aggiorno il tempo della variazione.
  if (letturaCorrente != ultimaLetturaGrezza) {
    istanteUltimaVariazione = millis();
    ultimaLetturaGrezza = letturaCorrente;
  }

  // Se il segnale è stabile da abbastanza tempo,
  // posso considerarlo affidabile.
  if ((millis() - istanteUltimaVariazione) >= TEMPO_DEBOUNCE) {

    // Se anche lo stato stabile è cambiato, ho un nuovo evento.
    if (letturaCorrente != statoStabile) {
      statoStabile = letturaCorrente;
      ultimoEventoUtente = millis();
      digitalWrite(PIN_LED_TIMEOUT, LOW);

      // Transizione verso livello basso = pulsante premuto.
      if (statoStabile == LOW) {
        istantePressione = millis();
      }
      // Transizione verso livello alto = pulsante rilasciato.
      else {
        unsigned long durataPressione = millis() - istantePressione;

        // Se la durata supera la soglia, classifico come pressione lunga.
        if (durataPressione >= SOGLIA_PRESSIONE_LUNGA) {
          Serial.println("Evento: PRESSIONE LUNGA");
          lampeggiaLed(PIN_LED_DOPPIO, 2, 120);
          attesaSecondoClick = false;
        }
        else {
          // Se sto già aspettando un secondo clic,
          // verifico la finestra temporale.
          if (attesaSecondoClick == true &&
              (millis() - istantePrimoClick) <= FINESTRA_DOPPIO_CLICK) {
            Serial.println("Evento: DOPPIO CLICK");
            lampeggiaLed(PIN_LED_DOPPIO, 1, 250);
            attesaSecondoClick = false;
          }
          else {
            // Primo clic breve: non lo confermo subito,
            // perché potrei ricevere un secondo clic.
            attesaSecondoClick = true;
            istantePrimoClick = millis();
          }
        }
      }
    }
  }

  // Se è in attesa un secondo clic e la finestra è scaduta,
  // confermo il clic breve.
  if (attesaSecondoClick == true && (millis() - istantePrimoClick) > FINESTRA_DOPPIO_CLICK) {
    Serial.println("Evento: CLICK BREVE");
    lampeggiaLed(PIN_LED_BREVE, 1, 250);
    attesaSecondoClick = false;
  }

  // Se passa troppo tempo senza eventi, attivo il LED di timeout.
  if ((millis() - ultimoEventoUtente) >= TEMPO_TIMEOUT) {
    digitalWrite(PIN_LED_TIMEOUT, HIGH);
  }
}

// ----------------------------------------------------------
// Funzione di servizio: fa lampeggiare un LED alcune volte.
// In questa attività è accettabile usare delay() perché
// la funzione serve solo come feedback visivo di conferma.
// ----------------------------------------------------------
void lampeggiaLed(int pinLed, int numeroLampi, int durata) {
  for (int i = 0; i < numeroLampi; i = i + 1) {
    digitalWrite(pinLed, HIGH);
    delay(durata);
    digitalWrite(pinLed, LOW);
    delay(durata);
  }
}

Continua a leggere

Arduino VENTUNO Q: la nuova piattaforma per AI, robotica e attuazione

Arduino ha annunciato VENTUNO Q, una nuova piattaforma descritta come una single-board computer progettata per applicazioni di intelligenza artificiale, robotica e attuazione. Il lancio è avvenuto oggi 9 marzo 2026 sul blog ufficiale Arduino, che la presenta come una soluzione pensata per unire su una sola board capacità di percezione, decisione e controllo del mondo fisico.

Per chi segue l’evoluzione dell’ecosistema Arduino, si tratta di un passaggio interessante: non più solo microcontrollori per prototipazione rapida, ma una piattaforma che punta a coprire anche scenari più avanzati, vicini alla robotica intelligente, alla computer vision on-device e ai sistemi che richiedono AI locale e controllo real-time. Questa lettura è inferenziale, ma coerente con il posizionamento ufficiale dichiarato da Arduino.

Una piattaforma pensata per “percepire, decidere e agire”

Uno dei concetti chiave con cui Arduino presenta VENTUNO Q è l’idea di una piattaforma capace di gestire, nello stesso sistema, l’acquisizione dei dati, l’elaborazione AI e il pilotaggio di attuatori. Il messaggio è chiaro: non soltanto “eseguire modelli”, ma costruire dispositivi in grado di interagire fisicamente con l’ambiente.

Nelle specifiche tecniche Arduino parla infatti di un’architettura “dual-brain“: da una parte un processore Qualcomm Dragonwing IQ8, dall’altra un microcontrollore STM32H5, collegati tramite un bridge RPC. L’obiettivo è separare i compiti di elaborazione complessa da quelli che richiedono tempi di risposta deterministici.

Architettura dual-brain: AI da una parte, controllo real-time dall’altra

Secondo Arduino, il processore principale Dragonwing IQ8 è dedicato ai carichi di lavoro più pesanti, inclusa l’inferenza di reti neurali, mentre lo STM32H5 si occupa delle operazioni a bassa latenza e del controllo in tempo reale. Sul blog ufficiale viene inoltre indicata una capacità fino a 40 dense TOPS (Tera Operations Per Second) per l’accelerazione AI, insieme a 16 GB di RAM e 64 GB di memoria espandibile.

Linux embedded e Arduino Core su Zephyr

VENTUNO Q può eseguire Ubuntu o Debian sul lato applicativo, mentre il microcontrollore real-time usa Arduino Core su Zephyr OS. Questa combinazione è rilevante perché mette insieme un ambiente Linux, più adatto a strumenti evoluti, librerie e workflow AI, con una componente embedded deterministica più vicina alla filosofia del controllo industriale e della robotica.

In termini pratici, ciò potrebbe consentire di sviluppare applicazioni in cui una parte del sistema gestisce visione, audio, interfacce o modelli AI, mentre l’altra mantiene il controllo puntuale di attuatori, encoder, segnali PWM o logiche di sicurezza. La possibilità concreta dipenderà naturalmente da tool, documentazione e maturità dell’ecosistema, ma la direzione indicata da Arduino è questa.

App Lab, Python e integrazione con Edge Impulse

Ovviamente VENTUNO Q si collega ad Arduino App Lab, l’ambiente in grado di unificare sketch Arduino, script Python e flussi di lavoro AI. Sarà possibile anche l’integrazione con Edge Impulse Studio e il supporto a modelli provenienti da Qualcomm AI Hub ed Edge Impulse.

Esempi applicativi: LLM locali, VLM, riconoscimento vocale automatico (ASR), sintesi vocale (TTS), stima di pose e gesture, oltre a funzioni di object tracking offline. Per chi lavora nella didattica o nella prototipazione, questa è forse una delle promesse più interessanti: portare l’AI “vicino” al dispositivo, senza dipendere sempre dal cloud.

Compatibilità hardware: Arduino, Qwiic e Raspberry Pi HAT

Arduino indica una compatibilità piuttosto ampia con diversi ecosistemi hardware. Nello specifico, VENTUNO Q viene presentata come compatibile con shield e carrier Arduino UNO, con i moduli Modulino, con i sensori Qwiic e addirittura con Raspberry Pi HATs.

Questo aspetto è importante perché amplia subito le possibilità di utilizzo: non una board isolata, ma una piattaforma che cerca di inserirsi in ambienti già familiari a maker, scuole, laboratori e sviluppatori. In ambito formativo questo può fare molta differenza, perché riduce la soglia di ingresso per sperimentare.

Connettività e interfacce per robotica ed Edge AI

Fra le caratteristiche che vengono evidenziate compaiono Wi-Fi 6, Bluetooth 5.3, CAN-FD nativo, PWM, GPIO ad alta velocità, connettori per più telecamere MIPI-CSI, audio avanzato, supporto display ed Ethernet 2.5 Gb. Interessantissimo, almeno per me la notizia che VENTUNO Q è ROS 2-ready, elemento che la colloca chiaramente anche nel perimetro della robotica moderna.

Nel complesso, la dotazione sembra andare oltre il classico uso educational di base e si spinge verso applicazioni più articolate: robot di servizio, sistemi di visione, automazione intelligente, interfacce vocali embedded, ispezione visiva e prototipazione preindustriale.

Alcuni scenari d’uso suggeriti da Arduino

Casi d’uso: interfacce vocali offline, sistemi interattivi basati su gesture, robot pick-and-place guidati dalla visione, robot di servizio che seguono le persone, applicazioni con Visual SLAM, monitoraggio intelligente e controllo qualità tramite modelli di visione locale.

Sono esempi significativi perché chiariscono il posizionamento del prodotto: VENTUNO Q non nasce come semplice variante di una board tradizionale, ma come piattaforma per progetti in cui servono insieme sensori, modelli AI, connettività e azione fisica sul mondo reale.

Perché potrebbe essere interessante anche per la scuola

Dal punto di vista didattico, VENTUNO Q potrebbe risultare particolarmente utile nei percorsi che incrociano elettronica, informatica, automazione, robotica e intelligenza artificiale. La presenza di un ambiente Linux, l’apertura verso Python e i workflow AI, unita al controllo real-time, la rende potenzialmente adatta a laboratori interdisciplinari più evoluti rispetto a quelli costruiti con microcontrollori tradizionali.

Naturalmente, molto dipenderà dalla disponibilità effettiva della scheda, dalla documentazione, dai costi, dagli esempi pronti e dalla facilità con cui queste caratteristiche potranno essere tradotte in attività scolastiche. Ma il segnale lanciato da Arduino è netto: l’AI embedded e la robotica intelligente stanno entrando sempre più chiaramente anche nel loro ecosistema.

Disponibilità

Arduino indica che VENTUNO Q sarà disponibile prossimamente tramite Arduino Store e presso rivenditori ufficiali come DigiKey, Farnell, Macfos, Mouser e RS. Al momento del lancio il prodotto è quindi annunciato come in arrivo, non ancora come board immediatamente acquistabile dal catalogo standard con una pagina e-commerce completa.

… cosa dire di più
sono curiosissimo ed impaziente di poterlo utilizzare 🙂

Con VENTUNO Q, Arduino sembra voler spingere il proprio ecosistema verso una nuova fascia progettuale: quella in cui non basta più leggere sensori e accendere attuatori, ma serve una piattaforma capace di analizzare, decidere e agire in locale. È un passaggio che guarda con decisione alla convergenza tra AI on-device, robotica e controllo embedded.

Resta ora da vedere come questa piattaforma si comporterà nella pratica, quali strumenti saranno davvero disponibili fin da subito e quanto sarà accessibile a scuole, maker e laboratori. Ma come annuncio, VENTUNO Q è sicuramente uno dei segnali più interessanti arrivati da Arduino negli ultimi tempi.

Fonte ufficiale Arduino: annuncio del 9 marzo 2026 sul blog Arduino e pagina prodotto ufficiale.

Mini progetto “dado elettronico” – due soluzioni

Ricordate la lezione: “randomSeed() su Arduino come usarla“. Vi avevo proposto un semplice esercizio sulla realizzazione di un dado digitale che genera un numero casuale da 1 a 6.

Vi propongo le due soluzioni:

  1. Con pulsante su pin digitale (lancio “fisico”)
  2. Con input da Serial Monitor (lancio “software”, immediato)

Entrambe sono adatte sia a Arduino UNO R3 sia a Arduino UNO R4.

Riprendiamo i concetti chiave visti nella lezione su randomSeed():

random(1,7) genera un numero pseudo-casuale tra 1 e 6.
Per evitare che la sequenza sia sempre uguale dopo un reset, conviene inizializzare il generatore con randomSeed(…). Nel dado, la cosa migliore è legare il seed a qualcosa di “imprevedibile”, ad esempio il momento in cui premete un pulsante (tempo umano) oppure un input seriale.

Versione A: dado con pulsante (pin digitale)

Materiale

  • 1 × Arduino UNO R3 o UNO R4
  • 1 × pulsante
  • cavetti e breadboard

Collegamenti

Usiamo la resistenza di pull-up interna, quindi niente resistenze esterne.

  • un piedino del pulsante -> GND
  • l’altro piedino del pulsante -> D2

Nel codice imposteremo pinMode(2, INPUT_PULLUP): a riposo il pin legge HIGH, quando premete legge LOW, ciò permette un cablaggio più semplice e rapido.

Nello sketch che segue imposteremo

  • debounce software (anti-rimbalzo)
  • seed inizializzato alla prima pressione usando micros() (molto efficace perché dipende dal tempo umano)
/*
  Prof. Maffucci Michele
  15.02.26
  Dado elettronico 1..6 con pulsante su D2 (INPUT_PULLUP).
  Include debounce e inizializzazione randomSeed al primo lancio.
*/

const byte PIN_PULSANTE = 2;

const unsigned long DEBOUNCE_MS = 30;

bool seedInizializzato = false;

int ultimoStatoLetto = HIGH; // con pull-up: HIGH a riposo
int statoStabile = HIGH;
unsigned long ultimoCambio = 0;

void setup() {
  Serial.begin(9600);
  delay(1000);
  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  Serial.println("Dado elettronico (pulsante su D2).");
  Serial.println("Premi il pulsante per lanciare.\n");
}

void loop() {
  int lettura = digitalRead(PIN_PULSANTE);

  // Rilevo cambiamenti (possibili rimbalzi)
  if (lettura != ultimoStatoLetto) {
    ultimoCambio = millis();
    ultimoStatoLetto = lettura;
  }

  // Se è passato il tempo di debounce, considero stabile
  if (millis() - ultimoCambio > DEBOUNCE_MS) {
    if (lettura != statoStabile) {
      statoStabile = lettura;

      // Evento: pressione (con pull-up la pressione è LOW)
      if (statoStabile == LOW) {
        lanciaDado();
      }
    }
  }
}

void lanciaDado() {
  // Seed al primo lancio: il tempo umano rende la sequenza non ripetibile
  if (!seedInizializzato) {
    unsigned long seme = micros() ^ (unsigned long)analogRead(A0); // A0 può anche essere scollegato
    randomSeed(seme);
    seedInizializzato = true;
  }

  int risultato = random(1, 7); // 1..6

  Serial.print("Lancio! Risultato: ");
  Serial.println(risultato);
}

Versione B: dado con Serial Monitor (input testuale)

Una volta caricato lo sketch procedete in questo modo:

  • aprite il Serial Monitor a 9600 baud
  • impostate “Invio” (newline) o “CR+LF”
  • digitate un qualsiasi tasto e premete invio -> il dado lancia
/*
  Prof. Maffucci Michele
  15.02.26
  Dado elettronico 1..6 con input da Serial Monitor.
  Scrivete un carattere e premete invio per lanciare.
*/

bool seedInizializzato = false;

void setup() {
  Serial.begin(9600);
  delay(1000);
  Serial.println("Dado elettronico (Serial Monitor).");
  Serial.println("Scrivi un carattere e premi INVIO per lanciare.");
  Serial.println("Esempio: scrivi 'l' e invia.\n");
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();

    // (Opzionale) scarto newline e carriage return
    if (c == '\n' || c == '\r') return;

    if (!seedInizializzato) {
      // Seed legato al momento in cui arriva il comando (tempo umano)
      unsigned long seme = micros() ^ (unsigned long)analogRead(A0);
      randomSeed(seme);
      seedInizializzato = true;
    }

    int risultato = random(1, 7);

    Serial.print("Comando ricevuto: '");
    Serial.print(c);
    Serial.print("' -> Risultato dado: ");
    Serial.println(risultato);

    Serial.println();
  }
}

In questo sketch l’istante in cui l’utente invia un carattere non è prevedibile: questo rende il seed variabile e quindi la sequenza di random() non riparte “uguale” ad ogni reset.

Esercizi per gli studenti

Attività 01: verifica del seed

  • lanciate 10 volte, annota i risultati;
  • premete reset e riprovate:
    • con seed legato alla pressione/comando dovreste vedere sequenze diverse.

Attività 02: statistica veloce

  • lanciate 60 volte;
  • contate quante volte esce ogni faccia (1..6);
  • discussione: la distribuzione è “circa uniforme?” Perché non è perfetta su pochi lanci?

Attività 03: debug consapevole

modificate il codice per usare un seed fisso, ad esempio randomSeed(1234) e confrontate:

  • questo è utile nei test perché rende i risultati ripetibili.

Buon Coding a tutti 🙂

Come ottenere più valori da una funzione in Arduino: variabili globali, riferimenti e struct – lezione 1/2

Durante la correzione delle esercitazioni di laboratorio mi accorgo spesso che il passaggio dei valori alle funzioni è un punto che genera parecchia confusione. Per questo ho deciso di preparare una breve lezione di ripasso, per chiarire i concetti fondamentali e aiutare a scegliere l’approccio più corretto nello sviluppo degli sketch.

Uno dei problemi molto comuni quando si programma in C/C++ su Arduino è: come ottenere più di un valore da una funzione?

In molti casi vorremmo che una funzione ci restituisse, ad esempio, due risultati (come nello scambio di due numeri), ma il linguaggio prevede che una funzione possa avere un solo valore di ritorno con l’istruzione return. Questo non significa che siamo bloccati: esistono più strategie corrette per gestire più risultati in modo chiaro e sicuro.

Partiremo da una soluzione semplice e intuitiva, basata su variabili globali, utile per capire il concetto ma generalmente sconsigliata nei programmi reali perché può creare effetti collaterali difficili da controllare. Passeremo poi a un approccio molto più robusto: il passaggio per riferimento, che permette a una funzione di modificare direttamente le variabili del chiamante senza ricorrere a globali. Infine vedremo l’uso dell’istruzione struct, cioè “contenitori” di più campi, che consentono di raggruppare dati correlati e di restituirli come un unico oggetto.

L’obiettivo non è solo far funzionare gli esempi, ma capire cosa succede davvero in memoria quando una funzione riceve parametri “per valore” (copie) oppure “per riferimento” (accesso diretto all’originale) e perché la scelta del metodo influisce su leggibilità, manutenzione e affidabilità del codice. Al termine della lezione saprete riconoscere quale tecnica usare a seconda del problema e scrivere funzioni più pulite, modulari e facili da riutilizzare.

Vedremo poi nella prossima lezione una quarta variante molto diffusa nel mondo embedded.

01. Versione “semplice” (ma sconsigliata): usare variabili globali

Esempio 01: scambio di valori

La funzione non “restituisce” due valori: modifica direttamente due variabili definite fuori da loop().

/*
  Prof. Maffucci Michele
  13.02.26
  Scambio di due valori usando variabili globali.
*/

int valoreA;
int valoreB;

void scambiaGlobali() {
  int temporaneo = valoreA;
  valoreA = valoreB;
  valoreB = temporaneo;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  valoreA = random(10);
  valoreB = random(10);

  Serial.print("Valori prima dello scambio (A, B): ");
  Serial.print(valoreA);
  Serial.print(", ");
  Serial.println(valoreB);

  scambiaGlobali();

  Serial.print("Valori dopo lo scambio (A, B):  ");
  Serial.print(valoreA);
  Serial.print(", ");
  Serial.println(valoreB);

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

Esempio 02: la funzione restituisce minimo e massimo aggiornando variabili globali.

Sulla serial monitor vengono mostrati ogni 5 secondi: la serie di 20 numeri generati, il minimo della serie, il massimo della serie.

/*
  Prof. Maffucci Michele
  13.02.26
  Calcola minimo e massimo su una serie di campioni usando variabili globali.
*/

int minimoCampione;
int massimoCampione;

void calcolaMinMaxGlobali(int numeroCampioni) {
  minimoCampione = 9999;
  massimoCampione = -9999;

  Serial.print("Campioni: ");

  for (int i = 0; i < numeroCampioni; i++) {
    int campione = random(0, 101); // 0..100

    // Stampa i campioni separati da virgola
    Serial.print(campione);
    if (i < numeroCampioni - 1) {
      Serial.print(", ");
    } else {
      Serial.println(); // a capo dopo l'ultimo campione
    }

    // Aggiorna minimo e massimo
    if (campione < minimoCampione) {
      minimoCampione = campione;
    }
    if (campione > massimoCampione) {
      massimoCampione = campione;
    }
  }
}

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

void loop() {
  const int numeroCampioni = 20;

  Serial.print("Genero ");
  Serial.print(numeroCampioni);
  Serial.println(" campioni casuali (0..100) e calcolo min/max...");

  calcolaMinMaxGlobali(numeroCampioni);

  Serial.print("Minimo trovato: ");
  Serial.println(minimoCampione);

  Serial.print("Massimo trovato: ");
  Serial.println(massimoCampione);

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

02. Passaggio per riferimento

Esempio 01: Scambia due valori passati per riferimento.

/*
  Prof. Maffucci Michele
  13.02.25
  Scambia due valori passati per riferimento.
*/

void scambiaPerRiferimento(int &a, int &b) {
  int temporaneo = a;
  a = b;
  b = temporaneo;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  int numero1 = random(10);
  int numero2 = random(10);

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

  scambiaPerRiferimento(numero1, numero2);

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

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

Esempio 02: divisione con resto (quoziente + resto)

Nello sketch che segue una funzione calcola due risultati (quoziente e resto) tramite riferimenti, e in più restituisce true/false per gestire il caso del divisore zero.

/*
  Prof. Maffucci Michele
  13.02.26
  Calcola due risultati (quoziente e resto) tramite riferimenti, 
  restituisce true/false per gestire il caso del divisore zero.
*/

bool dividiConResto(int numeratore, int denominatore, int &quoziente, int &resto) {
  if (denominatore == 0) {
    return false; // errore: divisione per zero
  }

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

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

void loop() {
  int a = random(0, 101);   // 0..100
  int b = random(0, 11);    // 0..10 (include lo zero apposta)

  int q = 0;
  int r = 0;

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

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

    Serial.print("Resto: ");
    Serial.println(r);
  } else {
    Serial.println("Errore: divisione per zero (b = 0).");
  }

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

03. Scambio di valori versione migliorata. Trattare più valori insieme: usare una struct

Con la struct impacchettiamo più campi in un solo tipo (una “coppia”), la passiamo alla funzione e la funzione restituisce una nuova struct con i campi scambiati.

Esercizio 01: scambio di valori

/*
  Prof. Maffucci Michele
  13.02.26
  Scambio di valori usando una struct.
*/

struct Coppia {
  int primo;
  int secondo;
};

Coppia scambiaCoppia(Coppia c) {
  int temporaneo = c.primo;
  c.primo = c.secondo;
  c.secondo = temporaneo;
  return c;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Coppia dati = { random(10), random(10) };

  Serial.print("Valori prima dello scambio (p, s): ");
  Serial.print(dati.primo);
  Serial.print(", ");
  Serial.println(dati.secondo);

  dati = scambiaCoppia(dati);

  Serial.print("Valori dopo lo scambio (p, s):  ");
  Serial.print(dati.primo);
  Serial.print(", ");
  Serial.println(dati.secondo);

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

Esempio 02: Lettura analogica completa (raw + volt + percentuale)

La funzione ritorna un oggetto con tre valori: lettura grezza, tensione stimata e percentuale.

/*
  Prof. MAffucci Michele
  13.02.26
  Restituisce più valori raggruppandoli in una struct.
*/

struct LetturaAnalogica {
  int raw;          // 0..1023
  float volt;       // tensione stimata
  int percentuale;  // 0..100
};

const int PIN_INGRESSO = A0;
const float VREF = 5.0; // se usi una scheda a 3.3V, imposta 3.3

LetturaAnalogica leggiAnalogicoCompleto(int pin) {
  LetturaAnalogica risultato;

  risultato.raw = analogRead(pin);
  risultato.volt = risultato.raw * (VREF / 1023.0);
  risultato.percentuale = map(risultato.raw, 0, 1023, 0, 100);

  return risultato;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  LetturaAnalogica misura = leggiAnalogicoCompleto(PIN_INGRESSO);

  Serial.print("Ingresso A0 -> raw: ");
  Serial.print(misura.raw);

  Serial.print(" | V: ");
  Serial.print(misura.volt, 2);

  Serial.print(" | %: ");
  Serial.println(misura.percentuale);

  delay(500);
}

Abbiamo visto tre strategie diverse per gestire più risultati in una funzione. E’ fondamentale scegliere quale usare nei vostri sketch, è importante capire bene cosa cambia tra passaggio per valore, passaggio per riferimento e uso di una struct.

Riassumo di seguito.

Passaggio dei valori a una funzione

Quando viene chiamata una funzione, potete consegnare i dati in due modi principali:

  1. Per valore (default)
  • la funzione riceve una copia dei parametri;
  • se dentro la funzione modificate quei parametri, state modificando la copia, non le variabili originali;
  • risultato: fuori dalla funzione i valori non cambiano.
  1. Per riferimento (&) — tipico in Arduino perché è C++

  • la funzione riceve un alias delle variabili originali;
  • dentro la funzione, quando assegnate a = …, state scrivendo proprio nella variabile chiamante.
  • risultato: fuori dalla funzione i valori cambiano davvero.

Nota: in C “puro” non esistono i riferimenti, quindi si userebbero puntatori (int *a, int *b). Arduino, però, compila in C++: i riferimenti sono disponibili.

Quando usare una struct (o un tipo “aggregato”)

La struct è utile quando:

  • volete trattare più valori come un unico oggetto logico (es. coordinate, min/max, stato di sensori, ecc.);
  • volete restituire più informazioni in un colpo solo (una sola return, ma con più campi dentro);
  • volete migliorare leggibilità: c.primo, c.secondo comunica il significato meglio di due variabili scollegate.

Costo/beneficio nell’uso della struct

  • Pro: codice più espressivo, dati “impacchettati”, interfaccia pulita.
  • Contro: c’è una copia della struct quando la passi/ritorni (di solito trascurabile se la struct è piccola). Su microcontrollori molto limitati, per struct grandi conviene passare per riferimento anche la struct.

Come detto ad inizio di questo post, nella prossima lezione vedremo una quarta variante molto diffusa nel mondo embedded: gli output parameters con puntatori (stile C) e una versione più moderna che usa const per rendere immediatamente chiaro quali dati sono solo in ingresso e quali vengono modificati dalla funzione.

Buon Coding a tutti 🙂

Sensori e attuatori lontani da Arduino: guida pratica al cablaggio corretto

Metto in evidenza la mia risposta ad una domanda di un utente in riferimento ai problemi causati dalla lunghezza delle linee che collegano Arduino ai dispositivi elettronici.

Marco sei gentilissimo, grazie 🙂
Immagino che tu ti stia riferendo ai collegamenti diretti dei tuoi dispositivi elettronici ad Arduino, è un problema tipico che mi trovo spesso ad affrontare, inoltre l’attività di automazione del controllo di un plastico di trenini è qualcosa che spesso viene svolto a scuola.

Di seguito ti faccio una lista di problemi e soluzioni in riferimento alle problematiche di collegamento, sono molto sintetiche perché sono tratte dalle slide per i miei studenti, quindi sono quasi un copia ed incolla dalle slide sono strutturate a lista puntata con sintesi di spiegazione spero sia sufficiente per risolvere il tuo problema.

Spero di non spaventare con la risposta, in realtà mentre scrivo è diventata una guida sulle possibili cause del problema che stai riscontrando, credo che tu possa risolvere seguendo i 3 passi che trovi alla fine di questa risposta, (al fondo dove scrivo: “ATTENZIONE giusto per non spaventare“), credo che risolverai al PASSO 1.

Allora come dici: perché “allungando i fili di 2 metri il giochino non funziona più?”

Le cause possono essere:

1. Caduta di tensione sui cavi (power)
A 2 m il cavo introduce resistenza: se il dispositivo assorbe corrente (anche solo un relè, un servo o una striscia LED), la tensione arriva più bassa al carico.
Sul banco hai 5V applicati al dispositivo, sul plastico può diventare “4.4–4.7 V” e alcuni moduli iniziano a fare cose strane (reset, letture errate, relè che non agganciano, servo che impazziscono).

Sintomi tipici: Arduino che si resetta quando scatta un relè/servo, sensori instabili, moduli che “a volte sì a volte no”.

2. Disturbi elettromagnetici e ritorni di massa (ground)
Nel plastico ci sono spesso motori, bobine, elettromagneti, alimentatori switching: generano disturbi. Con cavi lunghi aumenta l’area del “loop” e quindi la sensibilità ai disturbi.
In più, se la massa (GND) non è distribuita bene, il riferimento logico “0 V” non è più uguale dappertutto: per Arduino un “HIGH” può non essere più così HIGH 🙂

Sintomi tipici: ingressi digitali che cambiano da soli, finecorsa che “rimbalzano” anche senza toccarli, comunicazioni che falliscono.

Precisazione: quando dico area di “loop” intendo l’area racchiusa dal percorso andata + ritorno della corrente

  • andata: dal pin Arduino (o dall’alimentazione) verso il componente tramite il filo del segnale o del +V
  • ritorno: dal componente che torna a GND (massa) e rientra verso Arduino/alimentatore

Questi due fili (andata e ritorno) formano, di fatto, una spira. Più i due conduttori sono lontani tra loro, più la spira ha area grande.

Una spira con area grande:

  • capta disturbi (si comporta come un’antenna “ricevente”)
  • irradia disturbi (si comporta anche come un’antenna “trasmittente”), soprattutto se nel loop scorrono correnti impulsive (relè, motori, servo)

Ti faccio un esempio pratico che riguarda il tuo progetto:

  • caso “peggiore”: cavo del segnale che va da solo lungo il plastico, e il ritorno GND passa da un’altra parte (magari su una barra massa distante). Loop grande > più problemi.
  • caso “migliore”: segnale e GND viaggiano vicini (idealmente intrecciati). Loop piccolo > molto più robusto.

Come risolvere in questo caso

Per ogni collegamento “lungo”:

  • porta SEMPRE insieme segnale + GND (o +V + GND) nello stesso cavo
  • meglio ancora: usa doppino intrecciato (twisted pair): riduce l’area del loop e quindi la sensibilità ai disturbi

3. Linee di segnale troppo “deboli” (capacità + riflessioni)

Un cavo lungo aggiunge capacità e a volte riflessioni/ringing (fronti ripidi su linee non terminate).
Se stai usando bus come I²C o SPI, 2 metri sono spesso già fuori specifica senza accorgimenti.

Sintomi tipici: sensori I²C che non vengono più visti, display che si bloccano, letture non valide, bus che va in errore.

Vediamo ora come risolvere

Continua a leggere