Archivi tag: servo

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 🙂

PCTO A.S. 2020 – 2021 – SumoBot – lezione 3

In questa lezione mostrerò uno sketch di esempio in cui la velocità, il senso di rotazione e la durata di rotazione vengono passati come parametri alle funzioni che controllano il movimento del robot. Ovviamente potrete modificare e migliorare l’esempio proposto.
Si tenga in conto che questo tipo di controllo, sia per la bassa qualità dei servomotori utilizzati, e sia per la scelta del tipo di motore, non consente una regolazione precisa, ma in ogni caso ci permette di raggiungere gli obiettivi esposti nella lezione 1.

Lo schema di collegamento a cui farò riferimento sarà quello utilizzato nella lezione precedente, che indico di seguito.

L’inizializzazione dei servomotori viene eseguita nella stessa modalità come illustrato nella lezione 2.

Le funzioni principali di orientamento del robot permettono di controllare con discreta precisione:

  • la velocità di rotazione;
  • il senso di rotazione;
  • il tempo di rotazione;

Le funzioni di controllo sono:

antiorarioRobot()
orarioRobot()
stopRobot()

Le tre funzioni al loro interno utilizzano il metodo write() sugli oggetti motoreDx e motoreSx.

Le funzioni prevedono due parametri di ingresso: velocità e durata della rotazione.
Con l’impostazione della velocità impostiamo anche il senso di rotazione. Nel caso di rotazione antioraria il valore dovrà essere compreso tra 90 e 180 ed il valore scelto stabilirà anche la velocità di rotazione.

La funzione antiorarioRobot() accetta due parametri di ingresso:

  • velMaxAntioraria, massima velocità oraria
  • durata, indica la durata della rotazione in millisecondi

in questo caso i valori inseriti per la velocità dovranno essere compresi tra 0 e 90:

void antiorarioRobot(int velMaxAntioraria, int durata) {
  motoreDX.write(velMaxAntioraria);  // Rotazione antioraria del motore DX
  motoreSX.write(velMaxAntioraria);  // Rotazione antioraria del motore SX
  delay(durata);                     // durata: durata della rotazione
}

La funzione orarioRobot() funzionerà in modo simile:

void orarioRobot(int velMaxOraria, int durata) {
  motoreDX.write(velMaxOraria);    // Rotazione oraria del motore DX
  motoreSX.write(velMaxOraria);    // Rotazione oraria del motore SX
  delay(durata);                   // durata: durata della rotazione
}

Come esercizio invito gli studenti a realizzare un’unica funzione di comando che ingloba le due precedenti in grado di realizzare qualsiasi tipo di movimento.

La funzione stopRobot() accetta come unico parametro la durata dello stop.

void stopRobot(int ferma) {
  motoreDX.write(90);   // Ferma il motore DX
  motoreSX.write(90);   // Ferma il motore SX
  delay(ferma);         // Durata dello stop
}

Lo sketch completo è il seguente:

/*
 * Prof. Maffucci Michele
 * SumoRobot
 * Data: 26.01.2021
 *
 * Sketch 02: rotazione oraria e antioraria continua
 *            con funzioni parametrizzate
 *
 * Note:
 *          L'orientamento dei motori è fatto
 *          guardano il robot anteriormente
 *
 *          180: max velocità in senso antiorario
 *          90 : servomotori fermi
 *          0  : max velocità in senso orario
 *
 */

// inclusione della libreria servo.h per il controllo dei servomotori
#include <Servo.h>

// Creazione oggetti servo
Servo motoreDX;  // Inizializzazione del servomotore destro
Servo motoreSX;  // Inizializzazione del servomotore sinistro

byte pinDx = 4;     // Inizializza del pin 4 a cui è connesso il pin segnale del servo destro
byte pinSx = 5;     // Inizializza del pin 5 a cui è connesso il pin segnale del servo sinistro

void setup() {

  // attach() consente di definire a quale pin viene connesso il servomotore
  // e lo collega all'oggetto che gestisce il servomotore

  motoreDX.attach(pinDx); // pinDx collegato al motore destro
  motoreSX.attach(pinSx); // pinSx collegato al motore sinistro
}

void loop() {
  orarioRobot(30, 250);      // Rotazione in senso orario del robot
  stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
  antiorarioRobot(150, 250); // Rotazione in senso antiorario del robot
  stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
}

// rotazione del robot in senso antiorario
// velMaxOraria: velocità massima in senso antiorario
// durata: durata della rotazione

void antiorarioRobot(int velMaxAntioraria, int durata) {
  motoreDX.write(velMaxAntioraria);  // Rotazione antioraria del motore DX
  motoreSX.write(velMaxAntioraria);  // Rotazione antioraria del motore SX
  delay(durata);                     // durata: durata della rotazione
}

// rotazione del robot in senso orario
// velMaxOraria: velocità massima in senso orario
// durata: durata della rotazione

void orarioRobot(int velMaxOraria, int durata) {
  motoreDX.write(velMaxOraria);    // Rotazione oraria del motore DX
  motoreSX.write(velMaxOraria);    // Rotazione oraria del motore SX
  delay(durata);                   // durata: durata della rotazione
}

// stop del robot
// ferma: durata dello stop del robot

void stopRobot(int ferma) {
  motoreDX.write(90);   // Ferma il motore DX
  motoreSX.write(90);   // Ferma il motore SX
  delay(ferma);         // Durata dello stop
}

I tempi indicati inseriti nelle funzioni all’interno del loop potranno essere modificati secondo necessità.

Esercizio 01
Elenca le cause che provocano errori nel far ruotare precisamente di un determinato angolo scelto il robot.

Esercizio 02
Utilizzando un filo di connessione e modificando lo sketch precedente siete in grado di realizzare n sistema di START/STOP del robot.

Buon Coding a tutti 🙂

PCTO A.S. 2020 – 2021 – SumoBot – lezione 2

In questa lezione vedremo come collegare e controllare i servomotori a rotazione continua di SumoBot.
Fate riferimento allo schema di collegamento che segue, in cui i servomotori vengono connessi ai pin 4 e 5 della Sensor Shield per Arduino Uno Nano V3, come si nota a fianco di ogni pin è disponibile l’alimentazione, infatti troviamo sempre il positivo, indicato con la lettera V e il GND indicato con la lettera G. Come ribadito nella lezione 1 utilizziamo una Sensor Shield perchè permette rapidamente di realizzare tutti i collegamenti senza la necessità i dover ricorrere ad una breadboard oppure alla saldatura dei cavi.

Per questa lezione vengono indicati solo i collegamenti ai motori, non verranno collegati ne il sensore ad ultrasuoni e ne i sensori ad infrarossi.

Per quanto riguarda l’utilizzo dei servomotori a rotazione continua fare riferimento alla slide: Alfabeto di Arduino – Lezione 6, ma per completezza riporto di seguito la spiegazione adattandola all’utilizzo con SumoBot.

Il servomotore è costituito in genere da tre cavi connessi ad un connettore femmina con passo standard tra i fori di 2,54 mm quindi facilmente utilizzabile con qualsiasi strip che ne permette il collegamento ad esempio su una breadboard oppure ai pin maschio della Sensor Shield che utilizziamo per SumoBot.

I fili di connessione possono assumere colori diversi in funzione della marca del servo.

Pinout del servomotore

  • Filo ROSSO: +V
  • Filo NERO o MARRONE: GND
  • Filo BIANCO o ARANCIO o BIANCO o BLU: Segnale

Nel servomotori adottati per questa esperienza i fili di connessione sono:

  • Filo ROSSO: +V
  • Filo MARRONE: GND
  • Filo ARANCIO: Segnale

Collegamenti

Guardando SumoBot frontalmente, collegheremo il motore di destra al pin 4 e il motore di sinistra al pin 5.

Principio di funzionamento del servomotore a rotazione continua

Notoriamente i servomotori possono effettuare una rotazione che oscilla tipicamente da 0 a 180, esistono inoltre modelli che consentono una rotazione inferiore tra 0 e 120 gradi, questi tipi di servomotori possono essere modificati facendo in modo che possano effettuare una rotazione continua, ovvero tra 0 e 360 gradi, ma in commercio sono disponibili servomotori di diverse dimensioni che funzionano in questa modalità. Nel kit utilizzato per la realizzazione di SumoBot utilizziamo due servomotori FS90R.

Sul servomotore a rotazione continua possiamo controllare da programma il senso di rotazione e in modo non molto preciso anche la velocità.

Il funzionamento di un servomotore a rotazione continua è simile a quella di un motore in corrente continua con la differenza che non necessitano di appositi shield per poter funzionare.
Rispetto ad altri tipi di motori in CC offrono scelte limitate per il controllo della velocità e limitazioni di alimentazione.

L’alimentazione potrà avvenire direttamente Attraverso Arduino o mediante alimentazione esterna. L’alimentazione dei motori di SumoBot avverrà direttamente dalla scheda Arduino.

Caratteristiche tecniche

  • Velocità di funzionamento a 4,8V: 110RPM
  • Velocità di funzionamento a 6V: 130RPM
  • Coppia di stallo a 4,8V: 1.3kg.cm/18.09oz.in
  • Coppia di stallo a 6V: 1.5kg.cm/20.86oz.in
  • Tensione operativa: 4.8-6V
  • Sistema di controllo: Analogico
  • Angolo di rotazione: 360 gradi
  • Impulso richiesto: 900-2100us
  • Materiale ingranaggi: Plastica
  • Dimensioni: 2,32×1,25×2,2 cm
  • Peso: 9g

Programmazione

/*
 * Prof. Maffucci Michele
 * SumoRobot
 * Data: 26.01.2021
 *
 * Sketch 01: rotazione oraria e antioraria continua
 *
 * Note:
 *          Per l'orientamento del robot
 *          guardare SumoBot anteriormente
 *
 *          180: max velocità in senso antiorario
 *          90 : servomotori fermi
 *          0  : max velocità in senso orario
 *
 */

// inclusione della libreria servo.h per il controllo dei servomotori
#include <Servo.h>

// Creazione oggetti servo
Servo motoreDX;  // Inizializzazione del servomotore destro
Servo motoreSX;  // Inizializzazione del servomotore sinistro

byte pinDx = 4;     // Inizializza del pin 4 a cui è connesso il pin segnale del servo destro
byte pinSx = 5;     // Inizializza del pin 5 a cui è connesso il pin segnale del servo sinistro
int  durata = 250;  // Durata movimento (orario/antiorario)
int  ferma = 3000;  // Durata dello stop

void setup() {

  // attach() consente di definire a quale pin viene connesso il servomotore
  // e lo collega all'oggetto che gestisce il servomotore

  motoreDX.attach(pinDx); // pinDx collegato al motore destro
  motoreSX.attach(pinSx); // pinSxcollega to al motore sinistro
}

void loop() {
  orarioRobot();     // Rotazione in senso orario del robot
  stopRobot();       // Stop rotazione per un tempo fissato (vedere variabile ferma)
  antiorarioRobot(); // Rotazione in senso antiorario del robot
  stopRobot();       // Stop rotazione per un tempo fissato (vedere variabile ferma)
}

// rotazione del robot in senso antiorario
void antiorarioRobot(void) {
  motoreDX.write(150);  // Rotazione oraria del motore DX
  motoreSX.write(150);  // Rotazione antioraria del motore SX
  delay(durata);        // durata: durata della rotazione
}

// rotazione del robot in senso orario
void orarioRobot(void) {
  motoreDX.write(30);    // Rotazione antioraria del motore DX
  motoreSX.write(30);    // Rotazione oraria del motore SX
  delay(durata);         // durata: durata della rotazione
}

// stop del robot
void stopRobot(void) {
  motoreDX.write(90);   // Ferma il motore DX
  motoreSX.write(90);   // Ferma il motore SX
  delay(ferma);         // Durata dello stop
}

Per quanto riguarda il controllo dei servomotori seguire la spiegazione inserita come commento all’interno del codice, ricordo comunque che per controllare i servomotori sono necessarie  4 operazioni:

  1. includere la libreria Servo.h
  2. creazione dell’oggetto Servo. motoreDx e motoreSx saranno i due oggetti su cui opererete
  3. assegnare un nome al pin di controllo del servomotore (filo arancione nello schema)
  4. indicare nel setup il metodo attach() che permette di legare gli oggetti motoreDx e motoreSx ai pin su Arduino nell’esempio 4 e 5 a cui abbiamo assegnato i nomi pinDx e pinSx.

All’interno del codice utilizziamo il metodo write() che per i servomotori a rotazione continua permette il passaggio, all’oggetto motoreDx e motoreSx, la direzione e la velocità di rotazione del motore:

  • passando il valore 0 gradi al metodo write() il servo ruota alla massima velocità in una direzione.
  • passando il valore 90 gradi al metodo write() poniamo il servo in stop (posizione “neutra”)
  • passando il valore 180 gradi al metodo write() il servo di ruotare in senso opposto alla massima velocità.

Nel codice che segue SumoBot ripeterà continuamente una rotazione oraria di 250 millisecondi, si fermerà per 3 secondi e riprenderà la rotazione in senso antiorario per 250 millisecondi.

Per effettuare questa operazione vengono definite 3 funzioni:

  • orarioRobot()
  • stopRobot()
  • antiorarioRobot()

Nel codice si può notare che nella funzione antiorarioRobot() viene passato al metodo write() non il valore 180 che farebbe ruotare il robot alla massima velocità, ma un valore inferiore, nel nostro caso 150, ciò ridurrà la velocità di rotazione.

In  modo analogo accade per la funzione orarioRobot() in cui invece di passare il valore 0 alla metodo write(), che lo farebbe ruotare alla massima velocità in senso orario, passiamo un valore maggiore, 30, che lo farà ruotare ad una velocità inferiore.

La fermata del robot avviene utilizzando la funzione stopRobot() in cui viene passato il valore 90 al metodo write(), ciò fermerà i motori.

Si noti che i motori potranno ruoteranno in un senso o in un altro, oppure potranno essere fermati non solo invocando il metodo write, ma bisognerà sempre inserire un delay() in cui viene specificato per quanto tempo il metodo deve agire.

Esercizio 01

Far compiere a SumoBot rotazioni continue di 90 gradi in senso orario inserendo un intervallo di 3 secondi ad ogni quarto di giro

Esercizio 02

Far compiere a SumoBot una rotazione continua di 360° con intervalli di 3 secondi ad ogni quarto di giro, raggiunti i 360° far cambiare il senso di rotazione ripetendo le fermate di 3 secondi ad ogni quarto di giro.

Esercizio 03

Individuare quanto tempo necessita per far effettuare una rotazione di 45° in senso orario a SumoBot e realizzare un programma che permetta di fare le seguenti operazioni:

  1. rotazione di 45° in senso orario
  2. fermate di 3 secondi
  3. rotazione in senso antiorario di 90°
  4. fermata

Buon Making a tutti 🙂

RobotArm:bit, storia di un piccolo progetto

Qualche settimana fa…

Mannaggia fare questo mestiere è complicato, la giornata sarà ancora lunga, terminerà solo questa sera dopo la riunione con le famiglie…
ora mi prendo 10 minuti di pausa, è intervallo, mi chiudo in laboratorio di sistemi elettronici alla ricerca di un momento yogico, ma il frastuono degli allievi che chiacchierano a gran voce in corridoio non mi permette la “ricarica” completa.
Con la matita pasticcio su un foglio di carta millimetrata, ad un certo punto un frastuono mi fa sobbalzare dalla sedia, è la porta del laboratorio aperta malamente da un allievo che mi dice:

Allievo: “Vabbè Prof. poteva dircelo!”

Io: “Marco! La parola magicaaaa?”

Allievo: “Buooongioorno Proooof”

Io: “Marco il Prof. è un po’ scombussolato, tenta di far la dieta è in carenza di zuccheri ha dormito poco…. eeee sta cercando unaaa soluzione!”

Allievo: “Cosa Prof???
Aaaa ok ho capito!
Sta scarabocchiando sui nostri disegni, ci vuole fregare!
Si sta inventando una di quelle robe elettroniche per farci venire voglia di studiare…
vabbè vabbè Prof.! Volevo solo sapere perché non porta anche noi di seconda alla mostra Lego, porta tutti gli altri e a noi non ha detto nulla!
Vogliamo usare anche noi i robottini!”

Io: “Marco! Porta pazienza ma anche il tuo Prof. dimentica!”

Allievo: “Ok ok ok ho capito l’età si fa sentire!”

Io: “Marco tu sai che gestisco il registro elettronico, vuoi mica che tutti i tuoi voti diventino bit disordinati persi nel Matrix?”

Allievo: “Magari Prof.! Scomparirebbero i 4 di mate!”

…volevo dirle… allora… noi veniamo con lei,
siamo in quattro,
e se vuole diamo anche una mano a fare orientamento scolastico,
insomma lei ci porta alla mostra e noi la ripaghiamo facendo orientamento”

Io: ”Marco ma questa cosa si chiama estorsione! 🙂
ok ok dai vi porto”

E’ sera…
torni a casa correggi i disegni e ritrovi lo scarabocchio,
ma a cosa stavo pensando?

Pensavo agli “oggetti faro”.
Te le devi inventar tutte per far studiare gli allievi.

Cos’é “l’oggetto faro”?

Il progetto che vi condivido è quello di un piccolo e semplice braccio robot, che sarà inserito all’interno di un percorso più ampio di Coding in cui si utilizza sia di BBC micro:bit che di Arduino, l’attività sarà svolta tra qualche mese.

Durante i corsi mi capita che studenti appassionati chiedano di realizzare progetti che pregiudicano competenze ancora non acquisite ed il rischio è che lo studio inizia con gran voglia di fare e subito dopo sopraggiunge affanno perché non si riesce a gestire la complessità degli argomenti trattati.

Ecco che il metodo degli “oggetti faro” può venirci in aiuto.
Ma di cosa si tratta?

E’ una strategia che probabilmente con altri nomi già viene attuata da altri colleghi, a me piace chiamarla in questo modo e in sostanza mi permette, costruendo “oggetti faro” di far percepire allo studente la direzione in cui si sta andando e durante l’attività di formazione mi permette di mantenere vivo l’interesse anche se la complessità aumenta nel tempo.

“Professore voglio progettare un braccio robot che possa sollevare almeno 1 kg e che abbia 6 gradi di libertà, ma non voglio copiarlo on-line, ma voglio progettarlo io, ho visto che sta usando micro:bit, ritiene che debba usare questa scheda oppure e meglio usare Arduino? Mi spiega come fare?
A Prof. non so molto di meccanica e di elettronica se compro tutto e porto a scuola mi spiega?”

Se la richiesta viene da un allievo di prima superiore, l’attività può diventare un po’ complessa da gestire 🙂

Tornando agli “oggetti faro”, RobotArm:bit è uno di questi.
Arriverà un momento in cui l’allievo mi chiederà di non giocare più con la matrice di LED del micro:bit, ma vorrà incominciare a movimentare oggetti e dopo aver spiegato come gestire ad esempio un servo con micro:bit gli si spiega come usarne due insieme, poi come usarne tre e poi come pilotare i tre servo a distanza con un altro micro:bit e poiché bisogna dare delle risposte all’allievo citato prima, si aggiunge l’esercitazione con “l’oggetto faro” realizzato in modo molto elementare ed espandibile…

A questo punto si attua una strategia project learning breve che ha come punto di partenza “l’oggetto faro” RobotArm:bit per esempio. Cosa può succedere?

Nella mente del ragazzo nascono nuove idee:

“Prof. disegno una versione più grande e la stampiamo a scuola, quali software devo imparare ad usare? Lo faccio io da solo a casa!”

Quindi nuovi breanch si aprono e gli studenti portano ad esplorare in autonomia nuovi argomenti, tutto questo mi permette di insegnare ad esplorare, è una semplice strategia che serve per far nascere passioni.

Però bisogna proseguire con il corso.
Nuovi contenuti nuove sperimentazioni per giungere “all’oggetto faro” successivo, ad esempio un WalkerBot:bit e così di seguito.

Insomma un percorso intervallato da milestones (punti di riferimento) costituiti da “oggetti faro” su cui attuo del project learning da cui cerco di far nascere branch creativi gestiti in modo indipendente da parte dello studente.

Come realizzare RobotArm:bit?

Se avete necessità di realizzare RobotArm:bit vi rimando su Thingiverse dove potrete prelevare i sorgenti grafici per la stampa 3D, di seguito invece una sequenza fotografica che mostra i passi di costruzione, alla fine di questo post trovate video e link al programma di test realizzato con il JavaScript Blocks editor, è molto semplice e mi permette di selezionare tre movimenti.

Le leve collegate ai servo devono essere incollate alle parti stampate in 3D, ovviamente se desiderate potete utilizzare delle viti.

4 oggetti

Inserimento del primo servo

Incollare le leve con i giunti da collegare all’albero di ciascun servomotore

Fissare i servomotori con le viti in dotazione

Unire ogni parte

Per la stampa si poteva fare di meglio… ma vabbè lascio a voi i miglioramenti.

Se avete bisogno del codice per realizzare i medesimi movimenti del video seguite il link.