Archivi tag: casualità

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 🙂

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 🙂