Arduino UNO Q: guida introduttiva (caratteristiche, pinout, esempi)

Sono passati circa quattro mesi dall’acquisizione di Arduino da parte di Qualcomm e dall’arrivo sul mercato di Arduino UNO Q. In questo periodo diversi lettori mi hanno scritto chiedendomi una presentazione più approfondita della scheda e un primo tutorial d’uso.
Va detto che negli ultimi anni Arduino ha fatto un grande passo avanti sulla qualità della documentazione: sul sito ufficiale trovate già molte informazioni e riferimenti utili. Come faccio spesso, però, durante i miei test e soprattutto quando devo preparare materiali per la didattica, preferisco raccogliere, riorganizzare e riformulare i contenuti provenienti da Arduino.cc e dalla documentazione tecnica, così da trasformarli in un percorso più chiaro, progressivo e “a misura di studente”.

Il formato di Arduino UNO Q resta quello “storico” della famiglia UNO, quindi compatibile come ingombri e disposizione generale, ma l’hardware introduce funzionalità insolite per questa categoria di schede. L’elemento distintivo è la presenza di due mondi in uno: da un lato un microcontrollore, dall’altro un microcomputer capace di eseguire Linux. I due sottosistemi sono pensati per dialogare con facilità, aprendo scenari interessanti per progetti IoT e applicazioni più evolute.

Specifiche tecniche

Arduino UNO Q unisce un microcontrollore STM32U585 e un microcomputer Linux basato su Qualcomm Dragonwing QRB2210, mantenendo l’impronta e il layout tipico delle schede Arduino.

Qualcomm Dragonwing™ QRB2210:

  • CPU quad-core Arm® Cortex®-A53 fino a 2,0 GHz
  • GPU Adreno per accelerazione grafica 3D
  • Doppio ISP (13 MP + 13 MP oppure 25 MP) fino a 30 fps

MCU STM32U585 Arm® Cortex®-M33 a 32 bit:

  • Core Arm® Cortex®-M33 fino a 160 MHz
  • 2 MB di memoria Flash
  • 786 kB di SRAM
  • FPU (unità di calcolo in virgola mobile)

Programmare la scheda Arduino UNO Q

Dal punto di vista della programmazione, UNO Q può essere usata come un Arduino tradizionale per gestire i GPIO tramite Arduino IDE, oppure si può sfruttare Arduino App Lab per lavorare in modo integrato sia sul microcontrollore sia sul microcomputer.

La parte MCU si sviluppa in C/C++ (come su molte altre schede Arduino), mentre sulla parte Linux è possibile creare ed eseguire script e programmi in Python.

Anche se la programmazione “a doppio binario” è una delle novità più interessanti, nulla vieta di usare la scheda in modo più semplice: solo MCU (come un Arduino classico) oppure solo Linux (come una single-board computer in stile Raspberry Pi).

In pratica, i possibili approcci sono:

  • Arduino classico: utilizzo del solo MCU;
  • App Lab integrato: ambiente unico con sketch C/C++ (MCU) e programmi Python (Linux/MPU);
  • Linux standalone: uso della scheda come computer Linux, con accesso diretto (ad esempio via SSH) o con periferiche collegate.


Continua a leggere

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 🙂

Multitasking con Arduino – lezione 1/3

Per chi lavora con Arduino da un po’ di tempo, prima o poi si imbatte in questa frase letta online: delay() è il “male assoluto” :-). In realtà non è delay() in sé il problema: il punto è l’abuso che spesso se ne fa. Ogni volta che lo inseriamo nel codice, Arduino si ferma per tutto il tempo indicato, smettendo di leggere pulsanti, aggiornare uscite, gestire sensori o comunicazioni. In progetti semplici può anche andare bene, ma appena aumentano le funzioni da gestire “in parallelo”, quel blocco diventa un collo di bottiglia che rende il comportamento poco reattivo e difficile da far crescere.

Quando iniziamo a realizzare progetti con più componenti elettronici che interagiscono (LED che lampeggiano, pulsanti, messaggi su seriale, sensori…), delay() diventa subito un problema: blocca tutto. Se mettete un delay(500), per mezzo secondo Arduino non fa nient’altro: non legge pulsanti, non aggiorna altre uscite, non gestisce eventi.

La soluzione classica è passare a una logica non bloccante usando millis() e dividendo il programma in task (piccole funzioni) che vengono richiamate continuamente nel loop().

Ho trattato più volte dell’uso del millis() su questo sito, vi rimando al post: “Guida all’uso di millis() – Lezione 1” in cui ne mostro l’utilizzo in diverse situazioni.

Nel primo esempio realizziamo 3 processi (task) simultanei:

  1. LED1 su D9: lampeggia ogni 500 ms;
  2. seriale: stampa un messaggio ogni 1000 ms;
  3. pulsante su D2: ad ogni pressione commuta (ON/OFF) LED2 su D8 con debounce.

Per i collegamenti seguire lo schema che segue.

  • LED1: anodo > D9, catodo > resistenza 220 Ω > GND
  • LED2: anodo > D8, catodo > resistenza 220 Ω > GND
  • Pulsante: un capo > D2, l’altro capo > GND

Nota importante: non inseriamo la resistenza di pull-up esterna da 10 kΩ, perché abilitiamo la resistenza di pull-up interna con INPUT_PULLUP, pertanto a riposo il pin legge HIGH, premuto legge LOW.

L’idea di base è che ogni task controlla il suo tempo, quindi useremo una variabile che memorizza l’ultimo istante in cui ha fatto qualcosa.
Quando millis() - ultimoTempo >= intervallo, allora esegue l’azione e aggiorna ultimoTempo.
Ciò permette di far funzionare il ciclo di loop di Arduino senza nessun blocco e nessun task blocca gli altri task.

/*
    Prof. Maffucci Michele
    16.02.26

    Multitasking senza delay()
    - LED su D9 lampeggia ogni 500 ms
    - Stampa su Serial ogni 1000 ms
    - Pulsante su D2 (INPUT_PULLUP) commuta un LED su D8
*/

// Pin hardware
#define PIN_LED_LAMPEGGIO 9
#define PIN_LED_PULSANTE  8
#define PIN_PULSANTE      2

// "Timestamp" (in millisecondi) dell'ultima esecuzione dei task temporizzati
unsigned long tempoPrecedenteLed    = 0;
unsigned long tempoPrecedenteSerial = 0;

// Intervalli di esecuzione dei task (in millisecondi)
const unsigned long INTERVALLO_LED    = 500;
const unsigned long INTERVALLO_SERIAL = 1000;

// Stato logico dei LED
bool statoLedLampeggio = LOW;   // LED su D9
bool statoLedPulsante  = LOW;   // LED su D8

void setup() {
  // Impostiamo i pin dei LED come uscite
  pinMode(PIN_LED_LAMPEGGIO, OUTPUT);
  pinMode(PIN_LED_PULSANTE, OUTPUT);

  // Pulsante con pull-up interna:
  // - a riposo legge HIGH
  // - premuto (verso GND) legge LOW
  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  // Stato iniziale delle uscite (opzionale ma consigliato per chiarezza)
  digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  digitalWrite(PIN_LED_PULSANTE,  statoLedPulsante);

  Serial.begin(9600);
}

void loop() {
  // Il loop richiama continuamente i task.
  // Ogni task decide autonomamente "se è il momento" di fare qualcosa.
  taskLampeggioLed();
  taskStampaSeriale();
  taskLeggiPulsante();
}

// -------------------------------
// TASK 1: Lampeggio LED (non bloccante)
// -------------------------------
void taskLampeggioLed() {
  // Se è passato almeno INTERVALLO_LED dall'ultima volta, invertiamo lo stato del LED
  if (millis() - tempoPrecedenteLed >= INTERVALLO_LED) {
    tempoPrecedenteLed = millis();              // aggiorno l'istante di riferimento
    statoLedLampeggio = !statoLedLampeggio;     // toggle
    digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  }
}

// -------------------------------
// TASK 2: Stampa su Serial (non bloccante)
// -------------------------------
void taskStampaSeriale() {
  // Stampa un messaggio ogni INTERVALLO_SERIAL millisecondi
  if (millis() - tempoPrecedenteSerial >= INTERVALLO_SERIAL) {
    tempoPrecedenteSerial = millis();
    Serial.println("Multitasking: 3 task attivi (senza delay)!");
  }
}

// -------------------------------
// TASK 3: Lettura pulsante + debounce + commutazione LED2
// -------------------------------
void taskLeggiPulsante() {
  // Variabili statiche: mantengono il valore tra una chiamata e la successiva.
  // Servono perché questa funzione viene richiamata di continuo nel loop().
  static bool ultimaLettura = HIGH;                 // ultima lettura "grezza" (non filtrata)
  static bool statoPulsanteStabile = HIGH;          // stato stabilizzato dopo debounce
  static unsigned long ultimoCambio = 0;            // quando è cambiata l'ultimaLettura

  const unsigned long RITARDO_DEBOUNCE = 50;        // tempo minimo per considerare stabile un cambio

  bool letturaAttuale = digitalRead(PIN_PULSANTE);  // lettura istantanea (può "rimbalzare")

  // Se la lettura è cambiata rispetto a prima, resettiamo il timer di debounce
  if (letturaAttuale != ultimaLettura) {
    ultimoCambio = millis();
    ultimaLettura = letturaAttuale;
  }

  // Se la lettura resta invariata per almeno RITARDO_DEBOUNCE ms,
  // possiamo considerarla stabile e aggiornare lo stato "ufficiale" del pulsante.
  if (millis() - ultimoCambio >= RITARDO_DEBOUNCE) {
    // Se lo stato stabile del pulsante è cambiato davvero...
    if (letturaAttuale != statoPulsanteStabile) {
      statoPulsanteStabile = letturaAttuale;

      // Con INPUT_PULLUP: premuto = LOW
      // Eseguiamo la commutazione SOLO sul fronte di pressione (quando diventa LOW).
      if (statoPulsanteStabile == LOW) {
        statoLedPulsante = !statoLedPulsante;                 // toggle LED2
        digitalWrite(PIN_LED_PULSANTE, statoLedPulsante);
      }
    }
  }
}
  • taskLampeggioLed()
    Controlla il tempo con millis(). Quando sono passati 500 ms, inverte lo stato del LED su D9.
  • taskStampaSeriale()
    Ogni 1000 ms stampa un messaggio. Anche qui: nessun delay(), quindi non blocca la lettura del pulsante.
  • taskLeggiPulsante()
    Legge D2 con pull-up interna. Implementa un debounce software:

    • registra quando la lettura cambia,
    • aspetta 50 ms di stabilità,
    • considera valido il cambio e, solo quando il pulsante viene premuto (LOW), commuta LED2.

Realizziamo ora una seconda versione dello sketch in cui:

  • Task 4: “fade” PWM non bloccante su un pin PWM (D10).
  • Stampa su Serial dello stato di LED2 (D8) ogni volta che viene commutato dal pulsante.

Collegamento aggiuntivo: un terzo LED (con resistenza 220 Ω) su un pin PWM (quelli con il simbolo tilde ~ sulla scheda). Nell’esempio utilizzo D10.

/*
    Prof. Maffucci Michele
    16.02.26

    Multitasking senza delay()
    - Task 1: LED su D9 lampeggia ogni 500 ms
    - Task 2: Stampa su Serial ogni 1000 ms
    - Task 3: Pulsante su D2 (INPUT_PULLUP) commuta LED su D8 + stampa stato
    - Task 4: Fade PWM su un pin PWM (es. D10) senza bloccare nulla
*/


// Pin hardware
#define PIN_LED_LAMPEGGIO   9     // LED1
#define PIN_LED_PULSANTE    8     // LED2
#define PIN_PULSANTE        2     // Pulsante verso GND (INPUT_PULLUP)
#define PIN_LED_FADE_PWM   10     // LED3 (serve un pin PWM: ~)

// Timestamp (millis) per i task temporizzati
unsigned long tempoPrecedenteLed     = 0;
unsigned long tempoPrecedenteSerial  = 0;
unsigned long tempoPrecedenteFadePwm = 0;

// Intervalli (ms)
const unsigned long INTERVALLO_LED     = 500;
const unsigned long INTERVALLO_SERIAL  = 1000;
const unsigned long INTERVALLO_FADEPWM = 10;   // velocità aggiornamento fade (più basso = più fluido/rapido)

// Stati
bool statoLedLampeggio = LOW;   // LED su D9
bool statoLedPulsante  = LOW;   // LED su D8

// Parametri fade PWM (0..255 su molte schede Arduino)
int  luminositaPwm = 0;         // livello corrente
int  passoPwm      = 5;         // quanto cambia ad ogni aggiornamento (es. 1..10)
bool aumentaPwm    = true;      // direzione del fade

void setup() {
  pinMode(PIN_LED_LAMPEGGIO, OUTPUT);
  pinMode(PIN_LED_PULSANTE,  OUTPUT);

  // Pull-up interna: a riposo HIGH, premuto LOW (pulsante collegato a GND)
  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  // Pin PWM: lo usiamo con analogWrite() (non serve pinMode obbligatorio, ma lo mettiamo per chiarezza)
  pinMode(PIN_LED_FADE_PWM, OUTPUT);

  // Stato iniziale delle uscite
  digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  digitalWrite(PIN_LED_PULSANTE,  statoLedPulsante);
  analogWrite(PIN_LED_FADE_PWM,   luminositaPwm);

  Serial.begin(9600);
}

void loop() {
  // Ogni task viene richiamato continuamente.
  // Nessuno usa delay(): ogni task decide da solo quando agire.
  taskLampeggioLed();
  taskStampaSeriale();
  taskLeggiPulsante();
  taskFadePwm();
}

// -------------------------------
// TASK 1: Lampeggio LED (non bloccante)
// -------------------------------
void taskLampeggioLed() {
  if (millis() - tempoPrecedenteLed >= INTERVALLO_LED) {
    tempoPrecedenteLed = millis();
    statoLedLampeggio = !statoLedLampeggio;
    digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  }
}

// -------------------------------
// TASK 2: Stampa su Serial (non bloccante)
// -------------------------------
void taskStampaSeriale() {
  if (millis() - tempoPrecedenteSerial >= INTERVALLO_SERIAL) {
    tempoPrecedenteSerial = millis();
    Serial.println("Multitasking cooperativo: 4 task attivi (senza delay)!");
  }
}

// -------------------------------
// TASK 3: Lettura pulsante + debounce + commutazione LED2
//       + stampa dello stato di LED2 quando cambia
// -------------------------------
void taskLeggiPulsante() {
  static bool ultimaLettura = HIGH;            // lettura grezza precedente
  static bool statoStabile  = HIGH;            // stato stabilizzato (debounce)
  static unsigned long ultimoCambio = 0;       // momento dell'ultimo cambio lettura

  const unsigned long RITARDO_DEBOUNCE = 50;

  bool letturaAttuale = digitalRead(PIN_PULSANTE);

  // Se la lettura grezza cambia, aggiorniamo il timer di debounce
  if (letturaAttuale != ultimaLettura) {
    ultimoCambio = millis();
    ultimaLettura = letturaAttuale;
  }

  // Se la lettura resta stabile per un tempo sufficiente, la accettiamo come valida
  if (millis() - ultimoCambio >= RITARDO_DEBOUNCE) {
    if (letturaAttuale != statoStabile) {
      statoStabile = letturaAttuale;

      // Con INPUT_PULLUP: premuto = LOW
      // Commutiamo LED2 solo sul fronte di pressione (quando diventa LOW)
      if (statoStabile == LOW) {
        statoLedPulsante = !statoLedPulsante;
        digitalWrite(PIN_LED_PULSANTE, statoLedPulsante);

        // Stampa su Serial dello stato di LED2 al momento della commutazione
        Serial.print("LED2 (D8) ora e': ");
        Serial.println(statoLedPulsante ? "ACCESO" : "SPENTO");
      }
    }
  }
}

// -------------------------------
// TASK 4: Fade PWM (non bloccante) su pin PWM
// -------------------------------

void taskFadePwm() {
  // Aggiorniamo la luminosità con una cadenza regolare (INTERVALLO_FADEPWM).
  // Così l'effetto è fluido e, soprattutto, non blocca gli altri task.
  if (millis() - tempoPrecedenteFadePwm >= INTERVALLO_FADEPWM) {
    tempoPrecedenteFadePwm = millis();

    // Aggiorna luminosità in base alla direzione
    if (aumentaPwm) {
      luminositaPwm += passoPwm;
      if (luminositaPwm >= 255) {             // limite alto (classico 8-bit)
        luminositaPwm = 255;
        aumentaPwm = false;                   // inverti direzione
      }
    } else {
      luminositaPwm -= passoPwm;
      if (luminositaPwm <= 0) {               // limite basso
        luminositaPwm = 0;
        aumentaPwm = true;                    // inverti direzione
      }
    }

    // Applica PWM (luminosità percepita del LED)
    analogWrite(PIN_LED_FADE_PWM, luminositaPwm);
  }
}

Il fade PWM è un esempio perfetto per capire il multitasking su Arduino, perché l’effetto sembra continuo e fluido, quasi come se Arduino stesse “dimmerando” il LED in tempo reale senza occuparsi d’altro. In realtà non c’è nessuna operazione continua: il programma aggiorna la luminosità a piccoli passi (ad esempio +5 o −5) a intervalli regolari (per esempio ogni 10 ms), usando millis() per decidere quando fare l’aggiornamento successivo. Tra un passo e l’altro il loop() continua a girare e può eseguire gli altri task (lampeggio, lettura pulsante, seriale). È proprio questa alternanza rapida di micro-azioni temporizzate che crea l’illusione del “continuo”, mantenendo però il codice reattivo e capace di gestire più funzioni “in parallelo” senza blocchi e senza delay().

Buon Making a tutti 🙂

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 🙂

randomSeed() su Arduino come usarla

In riferimento al precedente post: “Come ottenere più valori da una funzione in Arduino: variabili globali, riferimenti e struct – lezione 1/2” un utente mi ha scritto in privato per chiedermi chiarimenti in merito all’uso di randomSeed(), nello specifico di randomSeed(analogRead(A0)). La spiegazione che ho fornito è diventata alla fine qualcosa che posso riutilizzare con i miei studenti quindi ne ho realizzato un post sul sito in modo che possa essere letto da tutti.

Quando in uno sketch usate random(), state usando un generatore pseudo-casuale: i numeri “sembrano” casuali, ma in realtà seguono una sequenza deterministica. Se riavviate la scheda e non fate nulla, spesso la sequenza riparte uguale. Per evitare questo comportamento (molto evidente nei giochi, nelle simulazioni e nelle attività di laboratorio), Arduino mette a disposizione randomSeed(), che inizializza il punto di partenza della sequenza.

Sintetizzo cosa fa nel dettaglio randomSeed()

  • random() genera numeri pseudo-casuali.
  • randomSeed(seed) imposta il seme (seed), cioè il valore da cui parte la sequenza.

questo vuol dire che:

  • Seed uguale > sequenza uguale.
  • Seed diverso > sequenza diversa.

L’effetto di randomSeed(analogRead(A0)) utilizzato nella lezione precedente è duplice:

  1. legge un valore analogico da A0 con analogRead(A0)
  2. usa quel valore come seme per randomSeed(...)

L’idea (consigliata anche nella documentazione Arduino) è: se A0 è scollegato, la lettura è influenzata dal “rumore” elettrico ambientale e tende a variare; quindi anche il seed cambia e, a ogni reset, random() produce una sequenza diversa.

Però attenzione, un pin “floating” non garantisce casualità perfetta; spesso fornisce poca entropia, ma per scopi didattici (giochi, simulazioni, esercizi) è in genere sufficiente. Ovviamente non è una tecnica adatta a sicurezza/criptografia.

Precisazione: differenze tra UNO R3 e UNO R4

  • Arduino UNO R3: analogRead() è a 10 bit: valori 0–1023.
  • Arduino UNO R4: per compatibilità, analogRead() parte di default a 10 bit (0–1023), ma l’UNO R4 supporta risoluzioni più alte (fino a 14 bit) tramite analogReadResolution().

Questa informazioni può essere utile anche per il seed: più risoluzione vuol dire più livelli possibili nella lettura analogica, quindi (potenzialmente) seed più variabile.

Di seguito alcune proposte di attività da svolgere a scuola o per esercitarsi.
Tutti gli esempi stampano su Serial Monitor. Usate 9600 baud e, quando richiesto, lasciate A0 scollegato.

Attività 01: serie che si ripete – (senza randomSeed)

Obiettivo: vedere che, senza inizializzazione, la sequenza tende a ripetersi dopo un reset
Consegna: fate partire lo sketch, annotate i primi 10 numeri, premete reset e confrontate.

/*
  Prof. Maffucci Michele
  14.02.26
  Attività 01: serie che si ripete - (senza randomSeed)
*/

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

void loop() {
  Serial.println("Primi 10 numeri pseudo-casuali (senza seed):");
  for (int i = 0; i < 10; i++) {
    Serial.println(random(0, 100));
  }
  Serial.println("--- Premi reset della scheda e confronta ---\n");
  delay(3000);
}

Attività 02: Seed fisso = sequenza ripetibile (utile nei test)

Obiettivo: capire che un seed costante produce sempre la stessa sequenza (utile per il debug).
Consegna: verificare che a ogni reset i numeri restano identici.

/*
  Prof. Maffucci Michele
  14.02.26
  Attività 02: capire che un seed costante produce sempre la stessa sequenza (utile per il debug)
*/

void setup() {
  Serial.begin(9600);
  randomSeed(12345); // seed fisso
}

void loop() {
  Serial.println("Primi 10 numeri con seed fisso (12345):");
  for (int i = 0; i < 10; i++) {
    Serial.println(random(0, 100));
  }
  Serial.println("--- Premi reset: deve ripetersi uguale ---\n");
  delay(3000);
}

Attività 03: Seed “rumoroso” con analogRead(A0) (A0 scollegato)

Obiettivo: ottenere sequenze diverse a ogni reset usando randomSeed(analogRead(A0)).
Consegna:

  1. eseguite con A0 scollegato > osservate seed e sequenza
  2. collega A0 a GND o 5V > osservate come seed e sequenza diventano più “stabili” (meno variabili)
/*
  Prof. Maffucci Michele
  14.02.26
  Attività 03: ottenere sequenze diverse a ogni reset usando randomSeed(analogRead(A0))
*/

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

  // A0 deve essere scollegato per sfruttare la variabilità del rumore
  int seme = analogRead(A0);
  randomSeed(seme);

  Serial.print("Seed letto da A0: ");
  Serial.println(seme);
  Serial.println();
}

void loop() {
  Serial.println("Primi 10 numeri con seed da A0:");
  for (int i = 0; i < 10; i++) {
    Serial.println(random(0, 100));
  }
  Serial.println("--- Premi reset: dovrebbero cambiare ---\n");
  delay(3000);
}

Attività 04: utilizzo di UNO R4 per avere seed più “ricco” aumentando la risoluzione (12/14 bit)

Obiettivo: su UNO R4 aumentare la risoluzione ADC e “mescolare” più letture per un seed più variabile.
Consegna:

  • fate 5 reset su UNO R3 e 5 reset su UNO R4, annotate i seed e valutate quanto cambiano
  • su UNO R4 provate anche analogReadResolution(12) e confronta con 14

Lo sketch che segue viene compilato anche su UNO R3: la parte analogReadResolution() viene attivata solo su UNO R4.

/*
  Prof. Maffucci Michele
  14.02.26
  Attività 04: utilizzo di UNO R4 per avere seed più "ricco" aumentando la risoluzione (12/14 bit)
*/

unsigned long creaSeedDaRumoreA0() {
  unsigned long seed = 0;

  // Su UNO R4 puoi aumentare la risoluzione (10 bit default, fino a 14 bit)
#if defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI)
  analogReadResolution(14);
#endif

  // Mescolo 32 letture per raccogliere più variabilità
  for (int i = 0; i < 32; i++) {
    seed = (seed << 1) ^ (unsigned long)analogRead(A0);
    delay(2);
  }
  return seed;
}

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

  Serial.println("Seed avanzato da A0 (A0 scollegato):");
  unsigned long seed = creaSeedDaRumoreA0();
  Serial.print("Seed calcolato: ");
  Serial.println(seed);

  randomSeed(seed);
  Serial.println();
}

void loop() {
  Serial.println("Primi 10 numeri con seed avanzato:");
  for (int i = 0; i < 10; i++) {
    Serial.println(random(0, 100));
  }
  Serial.println("--- Reset e confronta ---\n");
  delay(3000);
}

Ora vi propongo un piccolo progetto, il classico dado, che sicuramente riuscirete a svolgere velocemente.

Esercizio: “dado elettronico”

Obiettivo: usare random() in un contesto concreto e verificare che con randomSeed i lanci non siano sempre uguali dopo reset.

Vi lascio qualche giorno per provare a sviluppare una soluzione e poi ve ne propongo una io. 🙂

Terminiamo questa lezione riassumendo quanto visto:

  • random() = pseudo-casuale (sequenza deterministica);
  • randomSeed() sposta il punto di partenza della sequenza;
  • randomSeed(analogRead(A0)) funziona solo se A0 varia davvero (meglio scollegato o con una sorgente “rumorosa”);
  • UNO R4 può aumentare la risoluzione ADC con analogReadResolution() (default 10 bit per compatibilità).

Buon Coding a tutti 🙂