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);
  }
}


Esempio 02: pressione del pulsante durante il movimento: interrompere o riavviare la sequenza (senza bloccare nulla)

Se premiamo mentre il servo è già in movimento, decidete voi cosa deve accadere:

  • Interrompi: stop immediato e ritorno a 0°
  • riavvia: riparte da 0° e ripete nuovamente 0 > 90 > 0

Di seguito trovate una variante completa configurabile con una costante.

Lo schema di collegamento è il medesimo dell’esercizio precedente.

/*
    Prof. Maffucci Michele
    23.02.26

    Multitasking (senza delay)
    - Task 1: Lampeggio LED su D9
    - Task 2: Messaggi su Serial ogni 1 s
    - Task 3: Lettura pulsante (INPUT_PULLUP) + debounce + gestione evento pressione
    - Task 4: Movimento servo SG90 0° -> 90° -> 0° a step temporizzati

    LED su D8 = "spia attività servo":
    - si accende quando parte/ri-parte una sequenza
    - si spegne quando la sequenza termina o viene interrotta
*/

#include <Servo.h> 

// === CONFIGURAZIONE ===
// true  = se premi durante il movimento, RIAVVIA la sequenza
// false = se premi durante il movimento, INTERROMPE e torna a 0°
const bool RIAVVIA_SE_IN_CORSO = false;

// -----------------------
// PIN HARDWARE
// -----------------------
#define PIN_LED_LAMPEGGIO  9
#define PIN_LED_PULSANTE   8
#define PIN_PULSANTE       2
#define PIN_SERVO         10

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

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

// -----------------------
// STATI (LED e SERVO)
// -----------------------
bool statoLedLampeggio = LOW;

Servo servoSg90;

// Stato del "processo" servo (TASK 4)
bool movimentoServoAttivo = false;  // true finché la sequenza 0->90->0 non è conclusa
int angoloServo = 0;                // angolo corrente
int direzioneServo = +1;            // +1 sale verso 90°, -1 scende verso 0°

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;

// =====================================================
// Funzioni di servizio (non sono task: sono "azioni")
// =====================================================
void avviaSequenzaServo() {
  // 1) Spia attività: ON (sto avviando/riavviando un movimento)
  digitalWrite(PIN_LED_PULSANTE, HIGH);

  // 2) Avvio/riavvio: imposto lo stato iniziale della sequenza
  movimentoServoAttivo = true;
  angoloServo = 0;
  direzioneServo = +1;

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

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

void interrompiSequenzaServo() {
  // 1) Interrompo il processo
  movimentoServoAttivo = false;

  // 2) Riporto il servo a 0° immediatamente
  angoloServo = 0;
  direzioneServo = +1;
  servoSg90.write(0);

  Serial.println("Sequenza servo INTERROTTA: ritorno a 0°");

  // 3) Spia attività: OFF (ho interrotto, quindi attività terminata)
  digitalWrite(PIN_LED_PULSANTE, LOW);
  Serial.println("LED2 (D8) SPENTO: sequenza interrotta");
}

// =====================================================
// SETUP
// =====================================================
void setup() {
  pinMode(PIN_LED_LAMPEGGIO, OUTPUT);
  pinMode(PIN_LED_PULSANTE,  OUTPUT);

  // INPUT_PULLUP: a riposo HIGH, premuto LOW (pulsante verso GND)
  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  // Stati iniziali
  digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  digitalWrite(PIN_LED_PULSANTE, LOW);  // Spia spenta all'avvio

  servoSg90.attach(PIN_SERVO);
  servoSg90.write(0);

  Serial.begin(9600);
}

// =====================================================
// LOOP: "dispatcher" dei task
// =====================================================
void loop() {
  taskLampeggioLed();   // TASK 1
  taskStampaSeriale();  // TASK 2
  taskLeggiPulsante();  // TASK 3
  taskMovimentoServo(); // TASK 4
}

// =====================================================
// TASK 1: Lampeggio LED su D9 (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: pulsante + servo (interrompi/riavvia) senza delay!");
  }
}

// =====================================================
// TASK 3: Pulsante + debounce + gestione evento pressione
//
// - Debounce: accetto il cambio solo se stabile per 50 ms
// - Evento di pressione: transizione a LOW (INPUT_PULLUP)
// - Alla pressione:
//   - se servo fermo: avvio sequenza
//   - se servo in movimento: riavvio oppure interrompo (costante RIAVVIA_SE_IN_CORSO)
// =====================================================
void taskLeggiPulsante() {
  static bool ultimaLettura = HIGH;
  static bool statoStabile  = HIGH;
  static unsigned long ultimoCambio = 0;

  const unsigned long RITARDO_DEBOUNCE = 50;

  bool letturaAttuale = digitalRead(PIN_PULSANTE);

  if (letturaAttuale != ultimaLettura) {
    ultimoCambio = millis();
    ultimaLettura = letturaAttuale;
  }

  if (millis() - ultimoCambio >= RITARDO_DEBOUNCE) {
    if (letturaAttuale != statoStabile) {
      statoStabile = letturaAttuale;

      // Premuto = LOW
      if (statoStabile == LOW) {
        Serial.println("Pulsante premuto.");

        if (!movimentoServoAttivo) {
          // Servo fermo -> avvio
          avviaSequenzaServo();
        } else {
          // Servo in movimento -> riavvio o interrompo
          if (RIAVVIA_SE_IN_CORSO) {
            avviaSequenzaServo();
          } else {
            interrompiSequenzaServo();
          }
        }
      }
    }
  }
}

// =====================================================
// TASK 4: Movimento servo (non bloccante)
// - step temporizzati con millis()
// - fine sequenza: spegne la spia LED2
// =====================================================
void taskMovimentoServo() {
  if (!movimentoServoAttivo) return;

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

    angoloServo += direzioneServo * PASSO_SERVO;

    if (angoloServo >= ANGOLO_MAX) {
      angoloServo = ANGOLO_MAX;
      direzioneServo = -1;
    }

    // Fine sequenza: tornato a 0° durante la discesa
    if (angoloServo <= 0 && direzioneServo == -1) {
      angoloServo = 0;
      servoSg90.write(0);

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

      // Spia attività: OFF a fine sequenza
      digitalWrite(PIN_LED_PULSANTE, LOW);
      Serial.println("LED2 (D8) SPENTO: fine sequenza servo");

      return;
    }

    servoSg90.write(angoloServo);
  }
}

Esempio 03: pressione del pulsante durante il movimento: interrompere o riavviare la sequenza (senza bloccare nulla), fade del LED su D6

Se premiamo mentre il servo è già in movimento, decidete voi cosa deve accadere:

  • Interrompi: stop immediato e ritorno a 0°
  • riavvia: riparte da 0° e ripete nuovamente 0 > 90 > 0

In modo continuo il LED su D9 lampeggia, mentre il LED su D8 si accende all’attivazione del servo (pressione pulsante).

Il led su D6 si accende e si spegne continuamente in fade.

Come per il precedente sketch di seguito trovate una variante completa configurabile con una costante.

/*
    Prof. Maffucci Michele
    23.02.26

    Multitasking (senza delay)
    - Task 1: Lampeggio LED su D9
    - Task 2: Messaggi su Serial ogni 1 s
    - Task 3: Lettura pulsante (INPUT_PULLUP) + debounce + gestione evento pressione
    - Task 4: Movimento servo SG90 0° -> 90° -> 0° a step temporizzati
    - Task 5: Fade PWM continuo su LED collegato a D6 (0->255->0)

    LED su D8 = "spia attività servo":
    - si accende quando parte/ri-parte una sequenza
    - si spegne quando la sequenza termina o viene interrotta
*/

#include <Servo.h> 

// === CONFIGURAZIONE ===
// true  = se premi durante il movimento, RIAVVIA la sequenza
// false = se premi durante il movimento, INTERROMPE e torna a 0°
const bool RIAVVIA_SE_IN_CORSO = true;

// -----------------------
// PIN HARDWARE
// -----------------------
#define PIN_LED_LAMPEGGIO  9
#define PIN_LED_PULSANTE   8
#define PIN_PULSANTE       2
#define PIN_SERVO         10

#define PIN_LED_FADE       6   // LED in PWM (fade continuo)

// -----------------------
// TIMING (millis) - Task temporizzati
// -----------------------
unsigned long tempoPrecedenteLed    = 0;  // TASK 1
unsigned long tempoPrecedenteSerial = 0;  // TASK 2
unsigned long tempoPrecedenteServo  = 0;  // TASK 4
unsigned long tempoPrecedenteFade   = 0;  // TASK 5

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

// -----------------------
// STATI (LED e SERVO)
// -----------------------
bool statoLedLampeggio = LOW;

Servo servoSg90;

// Stato del "processo" servo (TASK 4)
bool movimentoServoAttivo = false;
int angoloServo = 0;
int direzioneServo = +1;

const unsigned long INTERVALLO_SERVO = 15;
const int PASSO_SERVO = 2;
const int ANGOLO_MAX  = 90;

// -----------------------
// TASK 5: Fade PWM su D6
// -----------------------
int luminositaFade = 0;             // valore PWM 0..255
int direzioneFade = +1;             // +1 aumenta, -1 diminuisce
const unsigned long INTERVALLO_FADE = 10; // ms tra uno step e l'altro (più basso = più fluido)
const int PASSO_FADE = 5;           // quanto cambia la luminosità a step (1..10 tipicamente)

// =====================================================
// Funzioni di servizio (non sono task: sono "azioni")
// =====================================================
void avviaSequenzaServo() {
  // Spia attività: ON
  digitalWrite(PIN_LED_PULSANTE, HIGH);

  movimentoServoAttivo = true;
  angoloServo = 0;
  direzioneServo = +1;

  servoSg90.write(angoloServo);
  tempoPrecedenteServo = millis();

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

void interrompiSequenzaServo() {
  movimentoServoAttivo = false;

  angoloServo = 0;
  direzioneServo = +1;
  servoSg90.write(0);

  Serial.println("Sequenza servo INTERROTTA: ritorno a 0°");

  // Spia attività: OFF
  digitalWrite(PIN_LED_PULSANTE, LOW);
  Serial.println("LED2 (D8) SPENTO: sequenza interrotta");
}

// =====================================================
// SETUP
// =====================================================
void setup() {
  pinMode(PIN_LED_LAMPEGGIO, OUTPUT);
  pinMode(PIN_LED_PULSANTE,  OUTPUT);
  pinMode(PIN_LED_FADE,      OUTPUT);

  pinMode(PIN_PULSANTE, INPUT_PULLUP);

  digitalWrite(PIN_LED_LAMPEGGIO, statoLedLampeggio);
  digitalWrite(PIN_LED_PULSANTE, LOW);

  // LED fade parte da 0 (spento)
  analogWrite(PIN_LED_FADE, 0);

  servoSg90.attach(PIN_SERVO);
  servoSg90.write(0);

  Serial.begin(9600);
}

// =====================================================
// LOOP: dispatcher dei task
// =====================================================
void loop() {
  taskLampeggioLed();   // TASK 1
  taskStampaSeriale();  // TASK 2
  taskLeggiPulsante();  // TASK 3
  taskMovimentoServo(); // TASK 4
  taskFadeLedD6();      // TASK 5
}

// =====================================================
// TASK 1: Lampeggio LED su D9 (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: servo + pulsante + LED blink + fade (senza delay)!");
  }
}

// =====================================================
// TASK 3: Pulsante + debounce + gestione evento pressione
// =====================================================
void taskLeggiPulsante() {
  static bool ultimaLettura = HIGH;
  static bool statoStabile  = HIGH;
  static unsigned long ultimoCambio = 0;

  const unsigned long RITARDO_DEBOUNCE = 50;

  bool letturaAttuale = digitalRead(PIN_PULSANTE);

  if (letturaAttuale != ultimaLettura) {
    ultimoCambio = millis();
    ultimaLettura = letturaAttuale;
  }

  if (millis() - ultimoCambio >= RITARDO_DEBOUNCE) {
    if (letturaAttuale != statoStabile) {
      statoStabile = letturaAttuale;

      if (statoStabile == LOW) {
        Serial.println("Pulsante premuto.");

        if (!movimentoServoAttivo) {
          avviaSequenzaServo();
        } else {
          if (RIAVVIA_SE_IN_CORSO) {
            avviaSequenzaServo();
          } else {
            interrompiSequenzaServo();
          }
        }
      }
    }
  }
}

// =====================================================
// TASK 4: Movimento servo (non bloccante)
// =====================================================
void taskMovimentoServo() {
  if (!movimentoServoAttivo) return;

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

    angoloServo += direzioneServo * PASSO_SERVO;

    if (angoloServo >= ANGOLO_MAX) {
      angoloServo = ANGOLO_MAX;
      direzioneServo = -1;
    }

    if (angoloServo <= 0 && direzioneServo == -1) {
      angoloServo = 0;
      servoSg90.write(0);

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

      // Spia attività: OFF a fine sequenza
      digitalWrite(PIN_LED_PULSANTE, LOW);
      Serial.println("LED2 (D8) SPENTO: fine sequenza servo");

      return;
    }

    servoSg90.write(angoloServo);
  }
}

// =====================================================
// TASK 5: Fade PWM continuo su D6 (non bloccante)
// - Aggiorna la luminosità ogni INTERVALLO_FADE ms
// - L'effetto sembra continuo, ma è una sequenza di piccoli step temporizzati
// =====================================================
void taskFadeLedD6() {
  if (millis() - tempoPrecedenteFade >= INTERVALLO_FADE) {
    tempoPrecedenteFade = millis();

    luminositaFade += direzioneFade * PASSO_FADE;

    // Limiti 0..255 e inversione direzione
    if (luminositaFade >= 255) {
      luminositaFade = 255;
      direzioneFade = -1;
    } else if (luminositaFade <= 0) {
      luminositaFade = 0;
      direzioneFade = +1;
    }

    analogWrite(PIN_LED_FADE, luminositaFade);
  }
}

Come già sottolineato nella lezione precedente, il fade del LED su D6 sembra un effetto "continuo", ma in realtà non lo è: Arduino non sta aumentando la luminosità in modo costante come un dimmer analogico. Sta invece modificando il valore di PWM a piccoli step (da 0 a 255) a intervalli regolari (nell’esempio ogni 10 ms). Tra uno step e il successivo non c’è alcun delay(): il loop() continua a girare velocemente e può quindi eseguire anche gli altri task (lettura del pulsante con debounce, movimento del servo a step temporizzati, lampeggio del LED su D9 e stampa su Serial). È proprio questa alternanza rapida di micro-azioni temporizzate che crea l’illusione del "continuo", mantenendo però il programma reattivo e realmente "multi-attività".

Buon Coding a tutti :-)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.