Archivi categoria: arduino

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 🙂

Arduino – Interrupts – lezione 2

Ormai sono passati diversi anni da quando scrissi il primo post sull’uso degli interrupt e durante gli anni a scuola puntualmente svolgo diversi esercizi, pertanto ho deciso di riprendere ed ampliare quanto detto nel precedente post e sicuramente più avanti ci saranno occasioni per aggiungere ulteriori esempi.

La maggior parte dei microcontrollori permette la gestione degli interrupt. Possiamo pensare all’intterrupt come a un sistema che ci permette di rispondere agli eventi “esterni” mentre facciamo qualcos’altro.

Riprendo quindi il post: Appunti su Arduino: interrupts da cui partire per comprendere cosa sono e come usare gli interrupts con Arduino UNO e con questa lezione voglio proporre alcuni esempi in modo che l’argomento spero possa essere più chiaro.

Per comprendere meglio cos’è un interrupt immaginate la seguente situazione:

tornate a casa dopo la scuola e desiderate cucinarvi un piatto di spaghetti. Fate bollire l’acqua ed inserite gli spaghetti in pentola fissando un tempo di 8 minuti, aspettate che gli spaghetti cucinino e poi mangiate. Possiamo paragonare questa azione all’esecuzione del codice nel loop() di Arduino.

In modo diverso potreste avviare un timer che scade ad 8 minuti e in questo intervallo di tempo potreste guardate il notiziario in TV. Quando il timer suona interromperà (interrupt) la vostra visione che vi ricorderà che dovrete porre attenzione alla pasta in pentola. Quindi verrà eseguito un interrupt (il suono del timer) che permetterà di eseguire il programma: “scola la pasta”.

In altro modo, riprendendo ciò che avevo scritto in passato:

Supponete di dover rilevare lo stato di alcuni sensori esterni. All’interno del loop() tutte le istruzioni sono eseguite in modo sequenziale e quindi anche la rilevazione del cambiamento di stato dei sensori collegati ad Arduino avviene in modo sequenziale. Supponete di dover eseguire il controllo della variazione di stato di 3 sensori, il vostro sketch eseguirà il controllo sul primo sensore, sul secondo e poi sul terzo.

Supponete che tutti i sensori si trovino al medesimo stato iniziale che chiameremo S1 e che il controllo di Arduino sia in un determinato istante sul secondo sensore, potrebbe capitare nello stesso istante una variazione repentina di stato sul primo sensore che passa da S1 a S2 e poi a S1, Arduino non si accorgerà di nulla perché la sua attenzione è sul secondo sensore; ecco che in questa situazione potrebbe essere utile l’utilizzo degli interrupt.

In ambito elettronico possiamo avere due tipi di interrupt:

  • Interrupt hardware: si verificano in risposta ad un evento esterno, come un pin che assume uno stato HIGH o LOW
  • Interrupt software: si verificano in risposta a un’istruzione software.

In questo post ci concentreremo sugli interrupt hardware.

Sulla scheda Arduino UNO, Nano, Mini e altre schede basate sul microcontrollore ATmega328 due sono i pin su cui realizzare un interrupt hardware:

pin digitale 2: interrupt 0
pin digitale 3: interrupt 1

Mentre per altre schede Arduino:

  • Uno WiFi Rev.2, Nano Every
    tutti i pin sono utilizzabili per l’interrupt
  • Mega, Mega2560, MegaADK
    2, 3, 18, 19, 20, 21
  • Micro, Leonardo e schede basate sull’ATmega32u4
    0, 1, 2, 3, 7
  • Zero
    tutti i pin eccetto il 4
  • Tutta la famiglia MKR
    0, 1, 4, 5, 6, 7, 8, 9, A1, A2
  • Nano 33 IoT
    2, 3, 9, 10, 11, 13, 15, A5, A7
  • Nano 33 BLE, Nano 33 BLE Sense
    tutti i pin
  • Due
    tutti i pin
  • 101
    tutti i pin e solo i pin 2, 5, 7, 8, 10, 11, 12, 13 lavorano in modalità CHANGE

Ma come funziona un interrupt?
In estrema sintesi quando si verifica l’interrupt (interruzione), il microcontrollore salva il suo stato di esecuzione attuale ed esegue una piccola porzione di codice che l’utente desidera venga eseguita in presenza di un interrupt, questa porzione di codice prende anche il nome di: interrupt handler o interrupt service routine (in italiano gestore di interrupt o routine di servizio interrupt).

Il programmatore quindi definisce il codice del gestore di interrupt che deve essere eseguito quando si verifica un particolare interrupt all’interno del programma stesso e per fare ciò in Arduino, come indicato nella mia precedente lezione, utilizziamo una funzione chiamata attachInterrupt():

attachInterrupt(digitalPinToInterrupt(PIN), ISR, modo);
  • digitalPinToInterrupt(PIN)
    è la funzione che consente di convertire il numero del pin su cui effettuare l’interrupt con il numero dell’interrupt quindi nel caso di Arduino UNO si avrà:
    digitalPinToInterrupt (2) > 0
    digitalPinToInterrupt (3) > 1
    PIN è quindi il pin abilitato all’interrupt, che indica al microcontrollore qual è il PIN da monitorare e come indicato nell’elenco sopra Il PIN dipende dal microcontrollore utilizzato.
  • ISR è la porzione di codice che deve essere eseguito se l’interrupt viene attivato
  • modo
    è il tipo di trigger che attiva l’interrupt che, come indicato nella precedente lezione, può essere di 4 tipi:

    • LOW l’interrupt viene eseguito quando il livello del segnale è basso
    • CHANGE l’interrupt viene eseguito quando avviene un cambiamento di stato sul pin
    • RISING l’interrupt viene eseguito quando si passa da un livello LOW ad un livello HIGH
    • FALLING l’interrupt viene eseguito quando si passa da un livello HIGH ad un livello LOW

Nota: all’interno della funzione utilizzata in attachInterrupt:

  • delay() non funziona;
  • il valore restituito dalla funzione millis() non verrà incrementato.
  • i dati seriali ricevuti durante l’esecuzione della funzione di interrupt possono essere sbagliati.
  • qualsiasi variabile modificabile all’interno della funzione attached (chiamata all’interno attachInterrupt) devono essere dichiarare come volatili.
  • le funzione non può avere parametri di ingresso

Esempio 01

Realizziamo uno sketch che al verificarsi di un interrupt sul pin 2 cambia lo stato in cui si trova il LED sulla scheda di Arduino

Circuito

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 01

*/

int pinLed = 13;
volatile int stato = LOW;
int pulsante = 2;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  pinMode(pinLed, OUTPUT);       // definiamo pin output
  pinMode(pulsante, INPUT);      // pulsante collegato al pin 2
  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);

  // usiamo l'interrupt 0 che è associato al pin digitale 2
  // attachInterrupt chiamerà la funzione collegata, cambiaStato
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l’interrupt viene eseguito quando si passa
  // da un livello HIGH ad un livello LOW

}

void loop()
{
  digitalWrite(pinLed, stato);
  // il pin digitale 13 viene impostato a "state"
  // che può essere LOW o HIGH
  // all'avvio di Arduino il LED sarà spento
}

void cambiaStato()
// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW

{
  stato = !stato;
}

quindi il cambiamento di stato avviene solo e soltanto se cambiamo lo stato sul pin 2.

Non si confonda l’azione del cambiamento di stato del LED con l’azione che si potrebbe avere con un semplice controllo sul pin 2 fatto con un’istruzione IF: “se il pulsante è premuto allora cambia stato al LED”.
In questo caso il funzionamento è totalmente diverso, non è presente un’istruzione di controllo di flusso, ma solamente il cambiamento di stato su un pin di interrupt, quando presente questo cambiamento viene invocata la funzione cambiaStato().

Si noti che le variabili utilizzate nella routine di servizio interrupt devono sempre essere globali e volatili.
Nel nostro caso la variabile “stato” utilizzate nella routine di servizio interrupt che abbiamo chiamato cambiaStato() è una variabile di tipo globale e volatile.

Ma cosa vuol dire dichiarare una variabile volatile? Come si può notare la variabile “stato” non è presente all’interno del loop(). Se il compilatore si accorge che sono presenti variabili non usate nel setup() e nel loop(), allora in fase di compilazione, per risparmiare memoria queste variabili vengono cancellate.

Dichiarare quindi “stato” volatile garantisce che il compilatori non elimini questa variabile perchè ci servirà all’interno della routine di servizio interrupt che abbiamo chiamato cambiaStato().
Una variabile volatile indicherà al compilatore di non memorizzare il contenuto della variabile in uno dei registri del microcontrollore, ma di leggerlo quando necessario dalla memoria. Attenzione però che questa modalità di azione rallenterà l’elaborazione del programma, pertanto non bisogna mai rendere volatile ogni variabile, ma l’operazione è da fare solamente quando necessario.

In generale una variabile dovrebbe essere dichiarata come volatile solamente se è utilizzata sia all’interno dell’ISR (interrupt service routine, in italiano gestore di interrupt) che all’esterno dell’ISR.

Esempio 02

Per comprendere meglio quanto scritto nell’esempio precedente facciamo alcune modifiche allo sketch precedente, il circuito rimane invariato.

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 02

*/

int pin = 13;
volatile int stato = LOW;
int pulsante = 2;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  pinMode(pin, OUTPUT);       // definiamo pin output
  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);

  // usiamo l'interrupt 0 che è associato al pin digitale 2
  // attachInterrupt chiamerà la funzione collegata, cambiaStato()
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l’interrupt viene eseguito quando si passa
  // da un livello HIGH ad un livello LOW

}

void loop()
{
  delay(5000); // intervallo di 5 secondi
}

void cambiaStato()
// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW

{
  stato = !stato;
  digitalWrite(pin, stato);
  // il pin digitale 13 viene impostato a "stato"
  // che può essere LOW o HIGH
  // all'avvio di Arduino il LED sarà spento
}

L’unica variazione al codice è stato l’inserimento di:

delay(5000); // intervallo di 5 secondi

che interrompe l’esecuzione del loop per 5 secondi.

Si noti però che non appena premiamo il pulsante avviene una modifica istantanea del codice indipendentemente dal delay di 5 secondi

Attenzione che il LED rosso poteva essere controllato internamente al loop, ma in questo caso il codice, eseguito sequenzialmente, avrebbe prima eseguito il blink del LED verde e successivamente controlla se il pulsante è premuto per poter accendere il LED verde, ciò non accade con l’uso dell’interrupt.

Esempio 03

Nell’esempio che segue potrete notare il ritardo con cui si manifesta la variazione di stato se il controllo viene effettuato all’interno del loop().

Circuito

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: controllo di stato subordinato dai delay

   Esempio 03

*/

int pinRosso = 5; // pin a cui è connesso il LED rosso
int pinVerde = 4; // pin a cui è connesso il LED verde
int pulsante = 2;
int stato = LOW;

void setup()
{
  pinMode(pinRosso, OUTPUT);
  pinMode(pinVerde, OUTPUT);
  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
}

void loop()
{ 
  digitalWrite(pinVerde, HIGH);
  delay(3000);
  digitalWrite(pinVerde, LOW);
  delay(3000);

  if (digitalRead(pulsante)) {
    digitalWrite(pinRosso, !stato);
    stato = !stato; 
  }
}

// Il controllo sul LED rosso potrà avvenire solamente dopo 6 secondi
// La pressione ripetuta del pulsante nei primi 6 secondi non modifica
// lo stato del LED fino a quando il controllo non giunge all'IF.

Esempio 04

Realizziamo ora un quarto sketch in cui abbiamo un LED verde che lampeggia controllato dal codice all’interno del loop ed un secondo LED rosso che cambia stato se premiamo il pulsante connesso al pin 2. Noterete che la pressione del pulsante, che varia lo stato del LED rosso, non influirà sulla normale esecuzione del loop().

Il circuito è il medesimo dell’esercizio precedente.

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 04

*/

int pinRosso = 5;
int pinVerde = 4;
int pulsante = 2;
volatile int stato = LOW;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  pinMode(pinRosso, OUTPUT);
  pinMode(pinVerde, OUTPUT); 
  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
  
  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);

  // usiamo l'interrupt 0 che è associato al pin digitale 2
  // attachInterrupt chiamerà la funzione collegata, cambiaStato()
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l’interrupt viene eseguito quando si passa
  // da un livello HIGH ad un livello LOW

}

void loop()
{
  digitalWrite(pinVerde, HIGH);
  delay(3000);
  digitalWrite(pinVerde, LOW);
  delay(3000);
}

// Durante i 6 secondi di accensione e spegnimento del LED verde
// non saremo bloccati dai delay presenti nel loop, potremo modificare
// lo stato del LED rosso ogni volta nei 6 secondi di pausa del loop

void cambiaStato()
// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW

{
  stato = !stato;
  digitalWrite(pinRosso, stato);
  // il pin digitale 5 viene impostato a "stato"
  // che può essere LOW o HIGH
  // all'avvio di Arduino il LED sarà spento
}

Esempio 05

Vediamo ora un’altro esempio dove con due pulsanti controlliamo l’incremento e il decremento di una variabile che viene stampata sulla Serial Monitor.
Notate all’interno del loop() che viene implementato un blink lento e ll’incremento e il decremento della variabile non è influenzata dai delay() del blink, ma risulta immediata.

Circuito

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 05

*/

volatile int valore = 10;

int pulsanteIncrementa = 2;
int pulsanteDecrementa = 3;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  Serial.begin(9600);
  
  pinMode(pulsanteIncrementa, INPUT);   // pulsante collegato al pin 2
  pinMode(pulsanteDecrementa, INPUT);   // pulsante collegato al pin 3 
   
  attachInterrupt(digitalPinToInterrupt(2), incrementa, FALLING);
  attachInterrupt(digitalPinToInterrupt(3), decrementa, FALLING);

  // usiamo l'interrupt 0 e 1 che sono associati ai pin digitali 2 e 3
  // attachInterrupt chiamerà la funzione collegata, cambiaStato
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l'interrupt viene eseguito quando vi è un passaggio da HIGH a LOW

}

void loop()
{
  Serial.println(valore);
}

void incrementa() {
  valore++;
}

void decrementa() {
  valore--;
}

Attenzione che durante le prove potreste notare qualche problema nella gestione dei pulsanti su cui dovrebbe essere effettuato un controllo di antirimbalzo, in questo caso però potrà essere realizzato solamente in modo hardware (in modo elettronico) e non per via software in cui viene richiesto un controllo del tempo di pressione del pulsante mediante il delay() o di calcoli di intervalli di tempo e tutto ciò non è permesso all’interno dell’interrupt service routine .

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 🙂

Arduino – realizzare un sensore di seduta a pressione con un tubo di gomma

Questa mattina, durante la realizzazione e l’analisi dei problemi per il progetto di PCTO: “misura di sedentarietà delle persone sedute alla scrivania” che stanno realizzando i miei studenti di 3′ Elettronica, è nata l’esigenza di associare un doppio controllo per la valutazione della presenza della persona seduta alla scrivania, un controllo effettuato con PIR HC-SR501 ed un sensore di forza resistivo (FSR) inserito all’interno del cuscino della seduta.

Per evitare l’acquisto di un sensore di forza resistivo e non pesare sulle finanze dei ragazzi le modalità sono tre:

  • richiesta alla scuola
  • compra il Prof.
  • farlo costruire ai ragazzi

l’acquisto da parte della scuola o mia non è un problema, ma la terza soluzione è quella che in questo momento prediligo, perché può essere realizzata in 5 minuti, credo che possa gratificare di più lo studente Maker in erba 🙂 , inoltre ritengo importante che gli allievi assumano la capacità di costruire il sensore perché ne dovranno ottimizzare l’uso, scontrandosi inevitabilmente con una serie di variabili fisiche che dovranno gestire.

Ma come si costruisce il sensore?

E’ indispensabile piccolo tubo cilindrico non trasparente, preferibilmente nero che possa essere compresso e al termine della compressioni ritorni abbastanza velocemente nella sua posizione di riposo. Possiamo ricavare il tubo sguainando un cavo elettrico o cavo di rete, oppure come ho fatto in questo tutorial, prendendo una guaina termorestingente.

Inserire un diodo LED ad un’estremità del cilindro e dalla parte opposta inserire un LDR.
Collegare il sistema nella solita modalità, inserendo in serie al LED un resistore da 220 Ohm e creando un partitore di tensione tra l’LDR e un resistore da 10KOhm, così come indicato nel circuito indicato di seguito.

Come test di funzionamento utilizzare il semplice sketch che trovate di seguito, nei commenti la spiegazione di tutte le parti del codice.

Aprite la Serial Monitor e premete e rilasciate il tubo

/*
 * Prof. Michele Maffucci
 * Data 01.03.2021
 * 
 * Oggetto: sensore di seduta a pressione
 * 
*/

// variabile in cui verrà memorizzato il valore presente sul pin A0
const int misura = A0;

// valore restituito dall'analogRead
int val = 0;

// pin a cui è connesso il LED del sensore di seduta
int pinLed = 2;

// LED che segnala la seduta della persona
int pinLedAlert = 13;

void setup() {
  // Inizializzazione della Serial Monitor
  Serial.begin(9600);

  // ledPin è il pin a cui è connesso il LED del sensore di seduta
  pinMode(pinLed, OUTPUT);

  // pinLedAlert è il pin a cui è connesso il LED che segnala la seduta della persona
  pinMode(pinLedAlert, OUTPUT);

  // Attivazione del LED del sensore di seduta
  digitalWrite(pinLed, HIGH);

  // Messaggio di avvio
  Serial.println("Sistema di rilevazione seduta");
  Serial.println("-----------------------------");
  Serial.println(""); 
  delay(1000);
}

void loop() {
  // analogRead leggerà il valore su A0 restituendo un valore tra 0 e 1023
  val = analogRead(misura);

  // il valore di controllo nell'if deve essere sperimentato in funzione
  // delle necessità costruttive (ad es. la lunghezza del tubo)

  // se vero la persona è seduta
  if (val >= 100) {
    digitalWrite(pinLedAlert, HIGH);                      // accensione del LED di avviso
    Serial.println("Persona NON seduta alla scrivania");  // segnalazione di assenza persona
    Serial.print("Valore letto dal sensore = ");          // Stringa di stampa 
    Serial.println(val);                                  // Valore restituito dall'AnalogRead
    Serial.println("");                                   // Stampa linea vuota di separazione
    delay(1000);                                          // Intervallo di 1 secondo tra ogni stampa
  }
  else
  {
    digitalWrite(pinLedAlert, LOW);                       // spegnimento del LED di avviso
    Serial.println("Persona seduta alla scrivania");      // segnalazione di presenza persona
    Serial.print("Valore letto dal sensore = ");          // Stringa di stampa 
    Serial.println(val);                                  // Valore restituito dall'AnalogRead
    Serial.println("");                                   // Stampa linea vuota di separazione
    delay(1000);                                          // Intervallo di 1 secondo tra ogni stampa
  }
}

Il risultato sulla Serial Monitor è il seguente

Il valore di soglia scelto deve essere ricavato sperimentalmente in funzione della lunghezza e della trasparenza del tubo.

Buon Making a tutti 🙂

Arduino – usare un PIR HC-SR501

Noto spesso nei progetti dei miei studenti il divertimento che nasce nell’utilizzo di un sensore ad infrarossi passivo che viene utilizzato per realizzare dei semplici dispositivi antintrusione, attualmente alcuni allievi li stanno utilizzando in due diversi progetti:

  • all’interno del kit EduRobot Home in cui il PIR HC-SR501 viene utilizzato come sensore di rilevamento per realizzare un antifurto
  • attività di PCTO in cui i ragazzi devono implementare un misuratore di sedentarietà, la rilevazione dell’essere umano davanti la computer avviene mediante un PIR.

Questa tipologia di sensore è molto diffuso, li utilizziamo ogni giorno: antifurto di casa, interruttori automatici di luci e molto altro in generale in tutti quei sistemi in cui è indispensabile attivare un’azione nel caso in cui venga rilevata la presenza di un essere umano o di un animale.

Come funziona il sensore di movimento PIR?

Come sicuramente saprete tutti gli oggetti con una temperatura superiore allo zero assoluto (0 Kelvin / -273,15 ° C) emettono energia termica sotto forma di radiazione infrarossa, inclusi i corpi umani. Più un oggetto è caldo, più radiazioni emette.

Il sensore PIR è appositamente progettato per rilevare tali livelli di radiazione infrarossa, è in grado di percepire oggetti caldi in movimento con uno lunghezza d’onda compreso tra i 700 nm e 1 mm . Nel caso della rilevazione di esseri umani la lunghezza d’onda da rilevare è compresa tra 8-14 micrometri.

Il mercato offre diverse tipologie di PIR in funzione del campo di impiego, i più comuni, quelli a lente di Fresnel hanno un range tra i 10 m e i 12 m con un campo di visivo di 300°. E’ possibile trovare sul mercato PIR con un campo visivo che raggiunge i 360° e distanze dell’oggetto rilevato fino a 30 m.

Il PIR fondamentalmente si compone di due parti principali: un sensore piroelettrico e una lente speciale chiamata lente di Fresnel che focalizza i segnali a infrarossi sul sensore piroelettrico.

Un sensore piroelettrico è dotato di due fessure rettangolari al suo interno fatte di un materiale che permette il passaggio della radiazione infrarossa, ciò che verrà rilevato dal sensore sarà la variazione della quantità di raggi infrarossi causata ad esempio dal passaggio di un essere umano e non la quantità di infrarossi totale dell’ambiente.
Le due fessure sono da considerare come due finestre che verranno colpite dai raggi infrarossi e se attraverso le due fessure viene rilevata la stessa quantità di infrarosso allora il segnale risultante di uscita sarà pari a zero, ovvero corpo non rilevato.
Quando però davanti al sensore passa un corpo caldo, come un essere umano, attraverso una finestra verrà rilevata una certa quantità di infrarosso che causerà una differenza di potenziale positiva tra le due finestra, quando il corpo caldo lascia l’area di rilevamento, avviene il contrario, per cui il sensore genera una variazione differenziale negativa. L’impulso di segnali corrispondente fa sì che il sensore imposti il pin di uscita alto, indicandoci quindi la presenza di una persona.

Il sensore HC-SR501, trattandosi di un sensore molto economico, possiede caratteristiche  limitate rispetto a quelli che potremo utilizzare per scopi civili o industriali, ma il loro principio di funzionamento è il medesimo.

Analizziamo la scheda

Pinout

Caratteristiche tecniche

  • Tensione continua di lavoro compresa tra i 4,5V e i 20V
  • Livelli di tensione di uscita: 3,3V HIGH – 0V LOW
  • Massimo angolo di visione 110°
  • Distanza rilevata compresa tra i 3 m e i 7 m

Sulla scheda sono presenti i due trimmer

  • PIR Range Adjustment (Sensibilità)
    Ruotando in senso orario si avrà una diminuzione della sensibilità con decremento della distanza di rilevamento, a fine corsa si avranno approssimativamente una sensibilità di 3 metri.
    Ruotando in senso antiorario aumentiamo la sensibilità per raggiungere approssimativamente 7 m.
  • Time Delay Adjustment (Tempo di delay)
    determina la quantità di tempo in cui l’output del sensore PIR rimarrà alto dopo il rilevamento del movimento. L’intervallo è compreso tra circa 3 secondi e 5 minuti. Girando in senso orario, a fine corsa si avrà un delay di 5 minuti, ruotando in senso antiorario, a fine corsa si otterrà un delay di circa 3 secondi.

Si ricordi che impostato il tempo di delay, ad esempio a 5 secondi, ciò comporterà che se viene rilevato il passaggio di un essere umano l’uscita del sensore sarà alta per 5 secondi e in questi 5 secondi il sensore è bloccato, cioè non sarà in grado di rilevare altri movimenti causati da altri corpi.

IMPORTANTE
Dopo il completamento del Time Delay  l’uscita sarà a LOW (off)  per circa 3 secondi , ciò vuol dire che in questi 3 secondi non verrà rilevato nulla dal sensore.

Sulla scheda è presente un jumper che ha la seguente funzionalità:

Con questo Jumper è possibile impostare le seguenti modalità:

Single Trigger Mode
Il Time Delay viene avviato immediatamente al rilevamento del movimento e il rilevamento continuo viene bloccato.
Questo vuol dire che se ad esempio viene impostato un Time Delay di 5 secondi, al rilevamento di una persona l’uscita passa ad HIGH, se nei 5 secondi passano davanti al sensore un’altra persone questa non viene rilevata e non viene aggiunto un’ulteriore Time Delay.

Immaginate di essere in modalità Single Trigger Mode con un Time Delay di 5 secondi.

  • Il PIR rileverà il movimento e lo imposterà ad HIGH per 5 secondi.
  • Dopo cinque secondi il PIR imposta la sua uscita bassa per circa 3 secondi.
  • Durante i tre secondi, il PIR non rileverà il movimento.
  • Dopo tre secondi di di LOW, il PIR è abilitato nuovamente al rilevamento di movimenti, al passaggio di una persona verrà impostata nuovamente l’uscita alta per il Time Delay impostato.

Repetable Trigger
Ogni movimento rilevato ripristina il Time Delay, pertanto il Time Delay inizia con l’ultimo movimento rilevato.
Questo vuol dire che se ad esempio viene impostato un Time Delay di 5 secondi, al rilevamento di una persona l’uscita passa ad HIGH se nei 5 secondi viene rilevato il passaggio di un’altra persona il Time Delay riparte nuovamente. Quindi supponendo che:

  • Persona 1 passa davanti al PIR, parte il Time Delay
  • Persona 2 passa davanti al PIR al secondo 3, se non passa nessuna persona il Time Delay cesserà ad 8 secondi ed il sensore potrà leggere nuovamente il passaggio di una persona al secondo 11.

IMPORTANTE

Prima di progettare un sistema in cui sono presenti dei PIR ricordare che questi sensori richiedono un po’ di tempo prima di adattarsi alla quantità di energia infrarossa presente nella stanza in cui vengono inseriti, per l’HC-SR501 sono necessari dai 30 ai 60 secondi da quando il sensore viene alimentato.
Ricordare inoltre che il sensore ha un periodo di reset che può oscillare dai 3 ai 6 secondi dopo aver effettuato la lettura, durante tale periodo non verrà rilevato nessun movimento.

Esempio 01
Lettura dello stato del PIR e relativa accensione del LED 13

Schema di collegamento

Il codice per l’utilizzo del sensore è estremamente semplice, è necessario solamente rilevare se il pin OUT si trova a valore alto o basso. Il funzionamento è dettagliato nei commenti.

/*
 * Prof. Michele Maffucci
 * data: 28.02.2021
 * 
 * Esempio 01
 * Utilizzo del sensore PIR: PIR HC-SR501
 * Lettura dello stato del PIR e relativa accensione 
 * del LED 13
 * 
 */
 
byte pinLed = 13;   // pin a cui è collegato il LED
byte pinInput = 8;  // pin di input a cui è collegato l'OUT del PIR
bool valPIR = 0;    // variabile utilizzata per memorizzre lo stato del PIR
 
void setup() {
  pinMode(pinLed, OUTPUT);    // pin a cui è collegato il LED è un output
  pinMode(pinInput, INPUT);   // pin a cui è collegato il PIR è un input
 
  Serial.begin(9600);
}

void loop() {
  valPIR = digitalRead(pinInput);  // viene letto il valore restituito dal PIR
  digitalWrite(pinLed, valPIR);    // il LED sulla scheda verrà acceso se viene
                                   // rilevata una persona altrimenti il LED
                                   // resterà spento. valPIR assume i valori 0 o 1
 }

Esempio 02
Lettura dello stato del PIR e relativa accensione del LED 13 e stampa dello stato sulla Serial Monitor

/*
 * Prof. Michele Maffucci
 * data: 28.02.2021
 * 
 * Esempio 02
 * Utilizzo del sensore PIR: PIR HC-SR501
 * Lettura dello stato del PIR e relativa accensione 
 * del LED 13 e stampa dello stato sulla Serial Monitor
 */
 
byte pinLed = 13;   // pin a cui è collegato il LED
byte pinInput = 8;  // pin di input a cui è collegato l'OUT del PIR
bool valPIR = 0;    // variabile utilizzata per memorizzre lo stato del PIR
 
void setup() {
  pinMode(pinLed, OUTPUT);    // pin a cui è collegato il LED è un output
  pinMode(pinInput, INPUT);   // pin a cui è collegato il PIR è un input
 
  Serial.begin(9600);
}

void loop() {
  valPIR = digitalRead(pinInput);  // viene letto il valore restituito dal PIR
  Serial.println(valPIR);          // stampa sulla Serial Monitor il valore letto
  delay(150);                      // ritardo per rallentare la frequenza di stampa
                                   // sulla Serial Monitor

  // Viene controllato se il PIR rileva una persona
  // Nota: scrivere "if (valPIR)" è analogo che scrivere
  // "if (valPIR == HIGH)" in quanto valPIR assume i valori 0 o 1
  
  if (valPIR) {
    digitalWrite(pinLed, HIGH);
    Serial.println("Rilevata una persona");
  }
  else
  {
    digitalWrite(pinLed, LOW);
    Serial.println("Non è presente nessuno");
  }
 }

Esercizio 01

Realizzare un semplice sistema di allarme costituito dal sensore PIR, un buzzer e due LED uno rosso ed uno verde.

  • Se il PIR non rileva nessuna persona il LED verde risulta acceso, il LED rosso spento ed il buzzer non emette nessun suono.
  • Se il PIR rileva una persona il LED verde risulta spento, il LED rosso acceso ed il buzzer emette un suono.

Esercizio 02

Realizzare le medesime funzionalità dell’esercizio 01 ed aggiungere un display I2C su cui inviare l’output:

  • Allarme
  • No presenza

Buon Making a tutti 🙂