Archivi tag: millis()

Multitasking con Arduino – lezione 2/3

Nella lezione precedente abbiamo visto come organizzare uno sketch in task indipendenti (lampeggio LED, stampa su Serial, lettura pulsante) usando millis() al posto di delay().

In questa seconda lezione aggiungiamo un attuatore: un servomotore SG90, mantenendo la stessa logica non bloccante. L’obiettivo è far capire che anche un movimento meccanico può essere gestito “in parallelo” agli altri processi, senza congelare l’esecuzione.

Specifiche

LED e pulsante

  • LED1 (D9): lampeggia ogni 500 ms (task temporizzato).
  • Serial: stampa un messaggio ogni 1000 ms (task temporizzato).
  • Pulsante (D2): configurato con INPUT_PULLUP
    • a riposo legge HIGH
    • premuto (verso GND) legge LOW
  • Alla pressione del pulsante:
    • LED2 (D8) si accende
    • parte la sequenza del servo 0° > 90° > 0°

Servo SG90

  • Movimento gestito a micro-step (es. 2° ogni 15 ms), usando millis():
    • movimento fluido,
    • il movimento non blocca gli altri task.

Collegamenti del servo SG90

  • Rosso > 5V
  • Marrone/Nero > GND
  • Arancione (segnale) > D10

Nota pratica: un SG90 può generare picchi di corrente. Se noti reset o instabilità, usa un’alimentazione 5V separata per il servo con GND in comune con Arduino.
Nota tecnica (UNO classico): la libreria Servo usa un timer; evita di contare su analogWrite() (PWM) su alcuni pin (in particolare 9/10 su UNO) quando usi Servo.

Esempio 01: spegnimento LED attivazione servo automatico a fine sequenza

/*
    Prof. Maffucci Michele
    23.02.26

    Multitasking senza delay()
      - TASK 1: LED1 su D9 lampeggia ogni 500 ms (uso di millis)
      - TASK 2: Messaggio su Serial ogni 1000 ms (uso di millis)
      - TASK 3: Pulsante su D2 (INPUT_PULLUP) con debounce:
                - alla pressione ACCENDE LED2 (D8)
                - avvia la sequenza del servo 0° -> 90° -> 0° (se non già in corso)
      - TASK 4: Movimento servo non bloccante a piccoli step temporizzati.
                Quando la sequenza termina, LED2 si SPEGNE automaticamente.
*/

#include <Servo.h> 


// -----------------------
// PIN HARDWARE
// -----------------------
#define PIN_LED_LAMPEGGIO  9     // LED1 (lampeggio)
#define PIN_LED_PULSANTE   8     // LED2 (spia attività: ON durante sequenza servo)
#define PIN_PULSANTE       2     // Pulsante collegato a GND (INPUT_PULLUP)
#define PIN_SERVO         10     // Segnale servo SG90

// -----------------------
// TIMING (millis) - Task temporizzati
// -----------------------
unsigned long tempoPrecedenteLed    = 0;  // riferimento per TASK 1
unsigned long tempoPrecedenteSerial = 0;  // riferimento per TASK 2

const unsigned long INTERVALLO_LED    = 500;
const unsigned long INTERVALLO_SERIAL = 1000;

// -----------------------
// STATI LED
// -----------------------
bool statoLedLampeggio = LOW;

// -----------------------
// SERVO e parametri sequenza
// -----------------------
Servo servoSg90;

bool movimentoServoAttivo = false;  // true mentre il servo sta eseguendo la sequenza
int angoloServo = 0;                // angolo corrente
int direzioneServo = +1;            // +1 sale verso 90°, -1 scende verso 0°

unsigned long tempoPrecedenteServo = 0;       // riferimento per TASK 4
const unsigned long INTERVALLO_SERVO = 15;    // ms tra uno step e il successivo
const int PASSO_SERVO = 2;                    // gradi per step
const int ANGOLO_MAX = 90;                    // angolo massimo della sequenza

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

  // INPUT_PULLUP: il pin è tenuto HIGH internamente.
  // Quando premi il pulsante (verso GND) la lettura diventa LOW.
  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  // Stati iniziali
  digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  digitalWrite(PIN_LED_PULSANTE,  LOW);       // LED2 parte spento

  // Servo: inizializzo e porto a 0°
  servoSg90.attach(PIN_SERVO);
  servoSg90.write(0);

  Serial.begin(9600);
}

void loop() {
  // Il loop è lo "scheduler cooperativo": richiama spesso i task.
  // Ogni task decide autonomamente se è il momento di agire.
  taskLampeggioLed();    // TASK 1
  taskStampaSeriale();   // TASK 2
  taskLeggiPulsante();   // TASK 3
  taskMovimentoServo();  // TASK 4
}

// =====================================================
// TASK 1: Lampeggio LED1 (non bloccante)
// =====================================================
void taskLampeggioLed() {
  // Se è passato l'intervallo, invertiamo lo stato del LED
  if (millis() - tempoPrecedenteLed >= INTERVALLO_LED) {
    tempoPrecedenteLed = millis();
    statoLedLampeggio = !statoLedLampeggio;
    digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  }
}

// =====================================================
// TASK 2: Stampa su Serial (non bloccante)
// =====================================================
void taskStampaSeriale() {
  // Stampa periodica indipendente dagli altri task
  if (millis() - tempoPrecedenteSerial >= INTERVALLO_SERIAL) {
    tempoPrecedenteSerial = millis();
    Serial.println("Multitasking: LED blink + pulsante + servo (senza delay)!");
  }
}

// =====================================================
// TASK 3: Lettura pulsante + debounce + avvio eventi
//
// - "Debounce": il contatto del pulsante rimbalza per alcuni millisecondi.
//   Qui accettiamo il cambio di stato solo se resta stabile per 50 ms.
// - Evento: gestiamo l'azione solo sul fronte di pressione (quando diventa LOW).
// - Azioni su pressione:
//   1) Accende LED2 (spia attività)
//   2) Avvia la sequenza servo (se non già in corso)
// =====================================================
void taskLeggiPulsante() {
  // static: mantengono il valore tra una chiamata e l'altra (task sempre richiamato nel loop)
  static bool ultimaLettura = HIGH;          // lettura grezza precedente (può rimbalzare)
  static bool statoStabile  = HIGH;          // stato validato dopo debounce
  static unsigned long ultimoCambio = 0;     // quando è cambiata l'ultimaLettura

  const unsigned long RITARDO_DEBOUNCE = 50;

  bool letturaAttuale = digitalRead(PIN_PULSANTE);

  // Se la lettura grezza cambia, ripartiamo col conteggio del tempo di stabilità
  if (letturaAttuale != ultimaLettura) {
    ultimoCambio = millis();
    ultimaLettura = letturaAttuale;
  }

  // Se la lettura resta invariata abbastanza, la consideriamo "stabile"
  if (millis() - ultimoCambio >= RITARDO_DEBOUNCE) {
    // Cambio reale dello stato stabile
    if (letturaAttuale != statoStabile) {
      statoStabile = letturaAttuale;

      // INPUT_PULLUP: premuto = LOW
      // Eseguiamo l'azione solo quando il pulsante viene premuto
      if (statoStabile == LOW) {

        // 1) LED2: spia attività -> ON quando parte la sequenza
        digitalWrite(PIN_LED_PULSANTE, HIGH);
        Serial.println("Pulsante premuto: LED2 (D8) ACCESO (sequenza servo in corso)");

        // 2) Avvia la sequenza del servo solo se non è già attiva
        if (!movimentoServoAttivo) {
          movimentoServoAttivo = true;
          angoloServo = 0;
          direzioneServo = +1;

          // Porto subito a 0° e aggancio il timing del task servo
          servoSg90.write(angoloServo);
          tempoPrecedenteServo = millis();

          Serial.println("Avvio sequenza servo: 0° -> 90° -> 0°");
        }
      }
    }
  }
}

// =====================================================
// TASK 4: Movimento servo (non bloccante)
//
// La sequenza è gestita a piccoli step temporizzati:
// - ogni INTERVALLO_SERVO ms, angoloServo cambia di PASSO_SERVO gradi
// - quando raggiunge 90°, inverte direzione
// - quando torna a 0°, la sequenza termina
//
// Importante: qui NON c'è delay(), quindi gli altri task continuano a funzionare.
// A fine sequenza, LED2 si spegne automaticamente.
// =====================================================
void taskMovimentoServo() {
  if (!movimentoServoAttivo) return;

  if (millis() - tempoPrecedenteServo >= INTERVALLO_SERVO) {
    tempoPrecedenteServo = millis();

    // Aggiorna angolo in base alla direzione (salita/discesa)
    angoloServo += direzioneServo * PASSO_SERVO;

    // Raggiunto il massimo: blocca a 90° e inverte direzione
    if (angoloServo >= ANGOLO_MAX) {
      angoloServo = ANGOLO_MAX;
      direzioneServo = -1;
    }

    // Tornato a 0° in discesa: fine sequenza
    if (angoloServo <= 0 && direzioneServo == -1) {
      angoloServo = 0;
      servoSg90.write(0);

      movimentoServoAttivo = false;
      Serial.println("Sequenza servo completata (ritorno a 0°)");

      // Correzione richiesta: LED2 si spegne automaticamente a fine sequenza
      digitalWrite(PIN_LED_PULSANTE, LOW);
      Serial.println("LED2 (D8) SPENTO: fine sequenza servo");

      return;
    }

    // Applica l'angolo al servo
    servoSg90.write(angoloServo);
  }
}


Continua a leggere

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 🙂

Guida all’uso di millis() – Lezione 1

Credo che una delle problematiche più ostiche da gestire soprattutto per i neofiti è l’utilizzo degli intervalli di tempo in cui eseguire operazioni con Arduino, mi riferisco all’uso e all’abuso improprio del delay(). Infatti gli studenti scoprono che, sebbene la funzione delay() sia facile da usare, ha degli effetti collaterali importanti; il principale è che ferma l’esecuzione dello sketch Arduino fino a quando non è trascorso il periodo di delay. Quando ciò accade, di solito chi spiega indirizza lo studente sull’esempio di defaut sulla temporizzazione non bloccante che troviamo nell’IDE di Arduino: BlinkWithoutDelay.

Molto spesso però questo confonde ancora di più le idee perché in realtà non si vuole solo far lampeggiare un LED ma si vogliono eseguire più operazioni contemporaneamente, quindi è bene comprendere a fondo il principio di funzionamento del BlinkWithoutDelay prima di poterlo applicare alla propria situazione.

Ho pensato quindi di realizzare qualche post tematico sull’uso di millis(), prendendo spunto dalle spiegazioni che realizzo per gli studenti.

Per usare millis() per la temporizzazione è necessario registrare il momento in cui un’azione si è verificata (ad esempio accensione di un LED ritardata alla pressione di un pulsante) affinché possiate iniziare a contare il tempo trascorso da tale evento, dovrete quindi controllare ad intervalli regolari se il periodo richiesto è trascorso.
Se tale intervallo di tempo non è trascorso allora il vostro programma potrà fare altro fino al prossimo controllo.

Nei programmi che seguono userò i commenti all’interno dello sketch per spiegare l’utilizzo delle varie parti del programma.

Ma cos’è millis()?

La funzione millis() restituisce il numero di millisecondi trascorsi dall’avvio del programma corrente su una scheda Arduino. Questo valore è restituito come un numero di tipo unsigned long.

Come Funziona

  • Incremento Automatico: il conteggio inizia automaticamente quando il microcontrollore sulla scheda Arduino viene alimentato o resettato. Il conteggio continua ad aumentare fino a che la scheda rimane alimentata.
  • Overflow: poiché millis() utilizza una variabile di tipo unsigned long, che ha una dimensione di 32 bit su Arduino, il valore massimo che può raggiungere è 4,294,967,295, dopo aver raggiunto questo valore, si andrà in overflow (ovvero Arduino non è in grado di memorizzare un numero più grande) e il valore restituito da millis() ripartirà da zero. Questo avviene dopo circa 49 giorni e 17 ore dall’ultimo reset della scheda.

Utilizzi di millis()

Di seguito una lista non esaustiva di alcuni utilizzi della funzione millis():

  • Temporizzazione non bloccante: a differenza di delay(), che ferma l’esecuzione del programma per un periodo specificato, millis() può essere utilizzato per realizzare pause o attese senza bloccare altre operazioni. Questo è particolarmente utile in applicazioni multitasking dove altre attività devono continuare ad essere eseguite.
  • Debounce: viene spesso usata per implementare il debounce di pulsanti o switch, riducendo gli effetti dei rimbalzi meccanici che possono causare letture multiple per una singola pressione.
  • Esecuzione di azioni a intervalli regolari: può essere utilizzata per eseguire specifiche azioni a intervalli regolari, come leggere sensori, aggiornare display, o inviare dati.

Pratica di utilizzo

Per utilizzare millis() per la temporizzazione, il vostro sketch deve conoscere il valore attuale del tempo (valore restituito da millis()) e questa rilevazione può essere fatto quando volete, in più punti dello sketch ovviamente dovrebbe essere fatta quando serve, ma vediamo cosa vuol dire.

Tipicamente il valore di millis() conviene registrarlo in una variabile ad inizio loop() o all’interno del setup() in questo modo:

millisCorrente = millis();

Regola di utilizzo:

  1. La variabile millisCorrente deve essere stata precedentemente dichiarata.
  2. Deve essere di tipo unsigned long perché millis() restituisce un intero long senza segno.

Regole generali che valgono per tutti i programmi che realizzerete, lo scrivo perchè puntualmente durante le correzioni qualcuno dimentica questa due regolette:

  1. Il nome della variabile, così come accade per tutte le variabili dovrebbe essere autoesplicativa, quindi il suo nome deve dare informazioni: “a cosa serve”
  2. Inoltre è buona regola utilizzare la notazione Camel Case per gestire nomi di variabili composte da più parole, ciò vale anche per le funzioni.

Come spesso accade durante le attività di laboratorio, lascio come semplicissimo esercizio per lo studente il desumere i componenti utilizzati e connessioni dagli sketch di esempio, quindi fate riferimento a quanto indicato in ogni singolo programma, si tratterà semplicemente di connettere dei LED con in serie un resistore da 220 Ohm. Per quanto riguarda l’ultimo esempio fate riferimento al link indicato che rimanda ad un altro post su questo sito.

Siete ora pronti per seguire le spiegazioni successive.

Continua a leggere