Archivi categoria: arduino

Lezione 2 – Arduino GamePad – LCD1602 Keypad Shield della Keyestudio

Pubblico la seconda lezione per il gruppo di lavoro della mia classe terza Automazione che sta operando con lo shield LCD1602 Keypad Shield della Keyestudio, suggerendo alcuni sketch che potranno poi essere riutilizzati e modificati opportunamente per realizzare alcune dinamiche di gioco.
Per chi volesse cimentarsi in queste sperimentazioni ricordo che non è essenziale possedere LCD1602 Keypad Shield della Keyestudio, il tutto può essere ottenuto anche mediante un normale display, a questo dovrete aggiungere i 6 pulsanti che potranno essere connessi o al pin A0, secondo la rete resistiva che potete riprodurre guardando gli schemi elettrici della shield oppure gestendo il tutto collegando i pulsanti a dei pin digitali.

Esempio 01

Il primo degli sketch, molto semplice, permette di far rimbalzare avanti e indietro un carattere. Per questo esempio sono state utilizzate le funzioni: scrollDisplayRight() e scrollDisplayLeft() ciascuna inclusa in un ciclo for, il primo muove il carattere “*” verso destra di 16 posizioni, tante quante sono le colonne del display ed il secondo muoverà il carattere da sinistra verso destra.

1/*
2   Prof. Michele Maffucci
3   Utilizzo dell'LCD Keypad Shield della Keystudio
4   Data: 14.03.2021
5 
6   Movimento ripetuto di un carattere, avanti e indietro su una riga
7*/
8 
9// inclusione della libreria LiquidCrystal.h
10#include <LiquidCrystal.h>
11 
12// inizializza la libreria con i numeri dei pin dell'interfaccia
13LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
14 
15// Velocità con cui viene stampato il carattere 
16int velocita = 200;
17 
18void setup() {
19  // impostazione del numero di colonne e righe del display
20  lcd.begin(16, 2);
21 
22  // Inizializzazione della Serial Monitor
23  Serial.begin(9600);
24 
25  // Carattere stampato nella prima colonna e prima riga (0,0)
26  lcd.print("*");
27}
28 
29void loop() {
30   
31  // Movimento verso destra del carattere
32  for (int contatorePosizioneColonna = 0; contatorePosizioneColonna < 16; contatorePosizioneColonna++) {
33    // Spostamento di una posizione verso destra
34    lcd.scrollDisplayRight();
35    // Attesa di un'istante per percepire il movimento del carattere
36    delay(velocita);
37  }
38 
39  // Movimento verso sinistra del carattere
40  for (int contatorePosizioneColonna = 0; contatorePosizioneColonna < 16; contatorePosizioneColonna++) {
41    // Spostamento di una posizione verso sinistra
42    lcd.scrollDisplayLeft();
43    // Attesa di un'istante per percepire il movimento del carattere
44    delay(velocita);
45  }
46}

Esercizio 1

Realizzare le medesime funzionalità dell’esempio 1, quando il carattere giunge nell’ultima colonna a destra, scende nella riga sottostante e ripercorre il tragitto verso sinistra, giunto alla prima colonna a sinistra risale sulla prima riga e si sposta nuovamente verso destra. Far ripetere in loop questa sequenza.

Esempio 02

Nell’esempio che segue vengono utilizzati i pulsanti RIGHT e LEFT del display per spostare verso destra o verso sinistra di un passo il carattere “*”.
L’identificazione del pulsante premuto avviene controllando il valore numerico restituito dall’analogRead() così come spiegato nella lezione 1.

Le due istruzioni if hanno come condizione il controllo del valore analogico, che indicherà il pulsante premuto il tutto è posto in AND con la posizione attuale (la colonna) del carattere, colonna memorizzata nella variabile “contatorePosizioneColonna” che nello spostamento verso destra dovrà essere incrementata e nello spostamento verso sinistra dovrà essere decrementata.

1/*
2   Prof. Michele Maffucci
3   Utilizzo dell'LCD Keypad Shield della Keystudio
4   Data: 14.03.2021
5 
6   Controllo dello spostamento di un carattere destra/sinistra
7   mediante i pulsanti: RIGHT e LEFT sul display
8*/
9 
10// inclusione della libreria LiquidCrystal.h
11#include <LiquidCrystal.h>
12 
13// inizializza la libreria con i numeri dei pin dell'interfaccia
14LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
15 
16// Colonna in cui si trova il carattere
17int contatorePosizioneColonna = 0;
18 
19// Valore restituito dall'analogRead su A0
20int val;
21 
22void setup() {
23  // impostazione del numero di colonne e righe del display
24  lcd.begin(16, 2);
25 
26  // Carattere stampato nella prima colonna e prima riga (0,0)
27  lcd.print("*");
28}
29 
30void loop() {
31 
32  // Memorizza in val il valore presente su A0
33  // per identificare il pulsante che viene premuto.
34  int val = analogRead(A0);
35 
36  // Premendo il pulsante RIGHT sul display, il carattere si sposta di una posizione
37  // a destra fino a quando non si raggiunge l'ultima colonna a destra.
38  // Premendo ancora il pulsante RIGHT non si ha l'avanzamento del carattere.
39  if ((val >= 0 && val <= 50) && contatorePosizioneColonna < 15) {
40    lcd.scrollDisplayRight();
41    delay(200);
42    contatorePosizioneColonna++;
43  }
44 
45  // Premendo il pulsante LEFT sul display, il carattere si sposta di una posizione
46  // a sinistra fino a quando non si raggiunge l'ultima colonna a sinistra.
47  // Premendo ancora il pulsante LEFT non si ha l'avanzamento del carattere.
48  if ((val >= 300 && val <= 500) && contatorePosizioneColonna > 0) {
49    lcd.scrollDisplayLeft();
50    delay(200);
51    contatorePosizioneColonna--;
52  }
53}

Esercizio 2

Realizzare le stesse funzionalità dell’esempio 2 ed aggiungere la possibilità di selezionare il carattere da movimentare, mediante un menù iniziale in cui potrà essere fatta la selezione secondo lo schema di seguito indicato:

Pulsante SELECT: @
Pulsante DOWN : X
Pulsante UP : #

La selezione del carattere avviene una sola volta all’avvio del programma.

Esercizio 3

Eseguire le stesse funzionalità dell’esercizio 3 con la possibilità di visualizzare il menù di scelta carattere in qualsiasi momento, così da permettere la selezione del carattere da movimentare. Scegliere liberamente il pulsante da premere per visualizzare il menù.

Esempio 3

Nell’esempio che segue si mostra come movimentare da destra a sinistra due caratteri: da destra a sinistra “>” e da sinistra verso destra “<“. In questo esempio viene utilizzata il metodo setCursor() che permette di posizionare ad una colonna e riga specifica il cursore.

1/*
2   Prof. Michele Maffucci
3   Utilizzo dell'LCD Keypad Shield della Keystudio
4   Data: 14.03.2021
5 
6   Movimento ripetuto avanti e indietro:
7   - movimento verso destra stampa del carattere: >
8   - movimento verso sinistra stampa del carattere: <
9    
10*/
11 
12// inclusione della libreria LiquidCrystal.h
13#include <LiquidCrystal.h>
14 
15// Velocità con cui viene stampato il carattere 
16int velocita = 200;
17 
18// inizializza la libreria con i numeri dei pin dell'interfaccia
19LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
20 
21void setup() {
22  // impostazione del numero di colonne e righe del display
23  lcd.begin(16, 2);
24 
25  // Inizializzazione della Serial Monitor
26  Serial.begin(9600);
27}
28 
29void loop() {
30   
31  // Movimento verso destra del carattere
32  for (int contatorePosizioneColonna = 0; contatorePosizioneColonna < 16; contatorePosizioneColonna++) {
33    // Cancella il display
34    lcd.clear();
35    // Spostamento di una posizione verso destra del cursore
36    lcd.setCursor(contatorePosizioneColonna, 0);
37    // Stampa del carattere: >
38    lcd.print(">");
39    // Attesa di un'istante per percepire il movimento del carattere
40    delay(velocita);
41  }
42 
43  // Movimento verso sinistra del carattere
44  for (int contatorePosizioneColonna = 16; contatorePosizioneColonna > 0; contatorePosizioneColonna--) {
45    // Cancella il display
46    lcd.clear();
47    // Spostamento di una posizione verso sinistra del cursore
48    lcd.setCursor(contatorePosizioneColonna, 0);
49    // Stampa del carattere: >
50    lcd.print("<");
51    // Attesa di un'istante per percepire il movimento del carattere
52    delay(velocita);
53  }
54}

Esercizio 4

All’interno del ciclo for, prima di posizionare e stampare il carattere, viene cancellato il display con il metodo clear(), sapresti indicare il motivo di questa scelta? Ci sono altri modi per ottenere il medesimo comportamento (movimento)?

Buon Coding a tutti.

Arduino – Trovare il valore più grande in un array

Durante queste settimane inevitabilmente le attività di PCTO vengono svolte in parte in presenza ed in parte in remoto. Le lezioni in remoto vengono ovviamente utilizzate per risolvere dubbi e spiegare qualcosa di nuovo che possa essere di aiuto ai ragazzi. Ultimamente alcuni mie studenti di classe 3′ sono impegnati chi nella realizzazione di serre idroponiche e chi nel simulare il funzionamento di un’incubatrice neonatale, in entrambe le situazioni bisogna rilavare grandezze fisiche, come ad esempio la temperatura. Spesso è necessario leggere dai sensori in un determinato intervallo di tempo una sequenza di valori che dovranno essere memorizzati in un array e successivamente elaborati.

Come detto sopra, è spesso utile memorizzare una sequenza di numeri, ad esempio una sequenza di letture di temperatura, all’interno di un array e poi dalla lista estrarre il valore che ci interessa, ad esempio: la massima temperatura, la minima temperatura, la media, ecc…

Per fare questa operazione il metodo più semplice ed intuitivo consiste nello leggere la sequenza dei valori per cercare il più grande.

Ma come costruire un algoritmo per fare questa operazione?

Riprendo un esempio che facevo tempo fa ai ragazzi declinato da alcune spiegazioni di un vecchio libro di informatica su cui studiai io 🙂

Immaginate la seguente situazione:

il mio collega con cui lavoro in compresenza, che chiamerò Prof. Rossi, mi detta la lista dei voti assegnati ad uno di voi, non mi dice in anticipo quanti voti mi detterà, me li detta ed io devo costruire un algoritmo che mi permetterà di dire al collega qual è il voto più altro nel momento in cui mi dirà:

“basta non ci sono più voti!”,

istantaneamente, senza rileggere tutta la lista devo fornire questa informazione al collega.

Partiamo!

  • Il primo voto che mi viene dettato è “3.5”, allora lo scriverò su un foglio di carta;
  • il secondo voto è 6.75 che è più grande di 3 quindi potrebbe essere questo il massimo dei voti, cancello 3.5 e scrivo sulla pagina il 6.75;
  • subito dopo mi viene detto: “5.5” che è più piccolo di 6.75 quindi non potrà essere questo il voto più alto, lasceremo scritto sul foglio 6.75;
  • mi viene detto: “9” ed essendo più grande di 6.75 lo indico sul mio foglio, cancellando il 6.75;
  • il voto successivo è 7.25, che è inferiore al 9 e non lo riporto sul foglio;
  • il voto successivo è 8.75, che è inferiore al 9 e non lo riporto sul foglio;
  • Il Prof. Rossi mi avvisa che ha finito di dettarmi i voti ed io dirò immmediatamente che il voto massimo è 9;

Come possiamo tradurre in C questo algoritmo?

Realizziamo un Array in cui inserire la sequenza dei 6 voti:

sequenzaVoti[] = {3.5, 6.75, 5.5, 9, 7.25, 8.75};

Il passo successivo sarà quello di definire un contenitore (una variabile) in cui di volta in volta andremo a memorizzare il numero più grande che abbiamo trovato.

Definisco quindi “votoMassimo” la variabile che contiene il voto massimo temporaneo (quello che nell’esempio mi veniva dettato dal Prof. Rossi)

float votoMassimo = 0;

Sarà necessario utilizzare un ciclo for per leggere uno alla volta i voti della lista.

Il ciclo parte con indice 0 arrivando fino alla fine dell’array, cioè con indice pari a n-1 (n: dimensione dell’array), quindi l’array è costituito da n oggetti e l’indice che identifica la posizione va da 0 a n-1.

In C è possibile calcolare automaticamente la dimensione dell’array con la funzione: sizeof di cui trovate tutte le informazioni sul references di Arduino

sizeof(sequenzaVoti)/sizeof(float))

mi darà la dimensione n dell’array.

Approfondiamo quanto detto.

sizeof permette di ottenere la dimensione in byte di una variabile di qualsiasi tipo (int, float, byte, char, ecc…) o del numero di byte che occupa un array.

Attenzione però per definire un array possiamo farlo in due modi:

modo 1, indicando la dimensione tra parentesi quadre
float sequenzaVoti[6]

modo 2, enumerando gli elementi, nel nostro caso i voti, ovvero l’elenco degli oggetti
float sequenzaVoti[] = {3.5, 6.75, 5.5, 9, 7.25, 8.75};

Per sapere la dimensione in byte dell’array useremo sizeof() che restituirà la dimensione dell’array in byte.

Quindi l’operatore sizeof(sequenzaVoti) non restituirà il numero di celle dell’array, ma la sua dimensioni in byte, pertanto:

sizeof(sequenzaVoti)

restituirà come valore: 24 perchè l’array sequenzaVoti[] è costituita da 6 numeri reali (con la virgola), ciascuno di esso rappresentato da quattro byte pertanto il numero totale di byte dell’array è 6×4=24.

Per calcolare la dimensione dell’array, partendo dalla dimensione in byte ricavata da sizeof(), possiamo operare in questo modo:

1for (int i = 0; i < (sizeof(sequenzaVoti)/sizeof(float)); i++) {
2//codice…
3}

Per trovare il voto massimo bisogna verificare ad ogni ciclo del for se il valore attuale (sequenzaVoti[i]) risulta maggiore del valore che abbiamo memorizzato all’interno della variabile votoMassimo, per fare questo controllo utilizzeremo l’istruzione if:

1if (sequenzaVoti[i] > votoMassimo) {
2   // salviamo il valore massimo attuale...
3}

Se la condizione dell’if è verificata, bisogna sovrascriviamo il valore di “votoMassimo” con quello trovato della sequenza dei voti che stiamo leggendo.

votoMassimo = sequenzaVoti[i];

Detto ciò costruiamo lo sketch che, data una lista di 6 voti memorizzati in un array, stampa sulla serial monitor

Il voto massimo è:…

al posto dei puntini dovete stampare il voto massimo tra quelli scritti nell’array.
La lista dei voti, per ora, viene inserita nel codice, non dovrà essere scritta da computer sulla serial monitor.

Potete far eseguire tutto il codice di estrazione del voto massimo nel setup() con il loop() vuoto.

Per completezza con quanto spiegato, aggiungo alla stampa del voto massimo la stampa della dimensione massima dell’array sequenzaVoti[]

Il listato completo sarà:

1/*
2 * Prof. Michele Maffucci
3 * data 15.03.2021
4 *
5 * Trovare il valore più grande in un array
6 *
7 */
8 
9// sequenza di 6 voti di uno studente
10float sequenzaVoti[] = {3.5, 6.75, 5.5, 9, 7.25, 8.75};
11 
12void setup() {
13  Serial.begin(9600);
14 
15  // la variabile con il numero massimo:
16 
17  int votoMassimo = 0;
18 
19  for (int i = 0; i < (sizeof(sequenzaVoti) / sizeof(float)); i++) {
20    if (sequenzaVoti[i] > votoMassimo) {
21      // se il voto corrente è maggiore di quello
22      // salvato dentro votoMassimo allora lo copio 
23      votoMassimo = sequenzaVoti[i];
24    }
25  }
26  Serial.print("Il voto massimo è: ");
27  Serial.println(votoMassimo);
28  Serial.print("Dimensione in byte dell'array ");
29  Serial.println(sizeof(sequenzaVoti)); 
30}
31 
32void loop() {}

Esercizio 1
Realizzare uno sketch che accetta l’imputo dei voti (float) da tastiera e restituisce il voto massimo.

Esercizio 2
Realizzare uno sketch che accetta l’imputo dei voti (float) da tastiera e restituisce la media dei voti.

Buon Coding 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:

1antiorarioRobot()
2orarioRobot()
3stopRobot()

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:

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

La funzione orarioRobot() funzionerà in modo simile:

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

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.

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

Lo sketch completo è il seguente:

1/*
2 * Prof. Maffucci Michele
3 * SumoRobot
4 * Data: 26.01.2021
5 *
6 * Sketch 02: rotazione oraria e antioraria continua
7 *            con funzioni parametrizzate
8 *
9 * Note:
10 *          L'orientamento dei motori è fatto
11 *          guardano il robot anteriormente
12 *      
13 *          180: max velocità in senso antiorario
14 *          90 : servomotori fermi
15 *          0  : max velocità in senso orario
16 *           
17 */
18 
19// inclusione della libreria servo.h per il controllo dei servomotori
20#include <Servo.h>
21 
22// Creazione oggetti servo
23Servo motoreDX;  // Inizializzazione del servomotore destro
24Servo motoreSX;  // Inizializzazione del servomotore sinistro
25 
26byte pinDx = 4;     // Inizializza del pin 4 a cui è connesso il pin segnale del servo destro
27byte pinSx = 5;     // Inizializza del pin 5 a cui è connesso il pin segnale del servo sinistro
28 
29void setup() {
30 
31  // attach() consente di definire a quale pin viene connesso il servomotore
32  // e lo collega all'oggetto che gestisce il servomotore
33   
34  motoreDX.attach(pinDx); // pinDx collegato al motore destro
35  motoreSX.attach(pinSx); // pinSx collegato al motore sinistro
36}
37 
38void loop() {
39  orarioRobot(30, 250);      // Rotazione in senso orario del robot
40  stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
41  antiorarioRobot(150, 250); // Rotazione in senso antiorario del robot
42  stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
43}
44 
45// rotazione del robot in senso antiorario
46// velMaxOraria: velocità massima in senso antiorario
47// durata: durata della rotazione
48 
49void antiorarioRobot(int velMaxAntioraria, int durata) {
50  motoreDX.write(velMaxAntioraria);  // Rotazione antioraria del motore DX
51  motoreSX.write(velMaxAntioraria);  // Rotazione antioraria del motore SX
52  delay(durata);                     // durata: durata della rotazione
53}
54 
55// rotazione del robot in senso orario
56// velMaxOraria: velocità massima in senso orario
57// durata: durata della rotazione
58 
59void orarioRobot(int velMaxOraria, int durata) {
60  motoreDX.write(velMaxOraria);    // Rotazione oraria del motore DX
61  motoreSX.write(velMaxOraria);    // Rotazione oraria del motore SX
62  delay(durata);                   // durata: durata della rotazione
63}
64 
65// stop del robot
66// ferma: durata dello stop del robot
67 
68void stopRobot(int ferma) {
69  motoreDX.write(90);   // Ferma il motore DX
70  motoreSX.write(90);   // Ferma il motore SX
71  delay(ferma);         // Durata dello stop
72}

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():

1attachInterrupt(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

1/*
2   Prof. Michele Maffucci
3   Data 03.03.2021
4 
5   Oggetto: utilizzo degli interrupt
6 
7   Esempio 01
8 
9*/
10 
11int pinLed = 13;
12volatile int stato = LOW;
13int pulsante = 2;
14 
15/* dichiariamo volatile la variabile
16  state usata nella funzione usata all'interno di attachInterrupt */
17 
18void setup()
19{
20  pinMode(pinLed, OUTPUT);       // definiamo pin output
21  pinMode(pulsante, INPUT);      // pulsante collegato al pin 2
22  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);
23 
24  // usiamo l'interrupt 0 che è associato al pin digitale 2
25  // attachInterrupt chiamerà la funzione collegata, cambiaStato
26  // il modo per la rilevazione del cambiamento di stato
27  // sarà di tipo: FALLING
28  // cioè l’interrupt viene eseguito quando si passa
29  // da un livello HIGH ad un livello LOW
30 
31}
32 
33void loop()
34{
35  digitalWrite(pinLed, stato);
36  // il pin digitale 13 viene impostato a "state"
37  // che può essere LOW o HIGH
38  // all'avvio di Arduino il LED sarà spento
39}
40 
41void cambiaStato()
42// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
43// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW
44 
45{
46  stato = !stato;
47}

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.

1/*
2   Prof. Michele Maffucci
3   Data 03.03.2021
4 
5   Oggetto: utilizzo degli interrupt
6 
7   Esempio 02
8 
9*/
10 
11int pin = 13;
12volatile int stato = LOW;
13int pulsante = 2;
14 
15/* dichiariamo volatile la variabile
16  state usata nella funzione usata all'interno di attachInterrupt */
17 
18void setup()
19{
20  pinMode(pin, OUTPUT);       // definiamo pin output
21  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
22  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);
23 
24  // usiamo l'interrupt 0 che è associato al pin digitale 2
25  // attachInterrupt chiamerà la funzione collegata, cambiaStato()
26  // il modo per la rilevazione del cambiamento di stato
27  // sarà di tipo: FALLING
28  // cioè l’interrupt viene eseguito quando si passa
29  // da un livello HIGH ad un livello LOW
30 
31}
32 
33void loop()
34{
35  delay(5000); // intervallo di 5 secondi
36}
37 
38void cambiaStato()
39// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
40// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW
41 
42{
43  stato = !stato;
44  digitalWrite(pin, stato);
45  // il pin digitale 13 viene impostato a "stato"
46  // che può essere LOW o HIGH
47  // all'avvio di Arduino il LED sarà spento
48}

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

1delay(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

1/*
2   Prof. Michele Maffucci
3   Data 03.03.2021
4 
5   Oggetto: controllo di stato subordinato dai delay
6 
7   Esempio 03
8 
9*/
10 
11int pinRosso = 5; // pin a cui è connesso il LED rosso
12int pinVerde = 4; // pin a cui è connesso il LED verde
13int pulsante = 2;
14int stato = LOW;
15 
16void setup()
17{
18  pinMode(pinRosso, OUTPUT);
19  pinMode(pinVerde, OUTPUT);
20  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
21}
22 
23void loop()
24{
25  digitalWrite(pinVerde, HIGH);
26  delay(3000);
27  digitalWrite(pinVerde, LOW);
28  delay(3000);
29 
30  if (digitalRead(pulsante)) {
31    digitalWrite(pinRosso, !stato);
32    stato = !stato;
33  }
34}
35 
36// Il controllo sul LED rosso potrà avvenire solamente dopo 6 secondi
37// La pressione ripetuta del pulsante nei primi 6 secondi non modifica
38// 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.

1/*
2   Prof. Michele Maffucci
3   Data 03.03.2021
4 
5   Oggetto: utilizzo degli interrupt
6 
7   Esempio 04
8 
9*/
10 
11int pinRosso = 5;
12int pinVerde = 4;
13int pulsante = 2;
14volatile int stato = LOW;
15 
16/* dichiariamo volatile la variabile
17  state usata nella funzione usata all'interno di attachInterrupt */
18 
19void setup()
20{
21  pinMode(pinRosso, OUTPUT);
22  pinMode(pinVerde, OUTPUT);
23  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
24   
25  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);
26 
27  // usiamo l'interrupt 0 che è associato al pin digitale 2
28  // attachInterrupt chiamerà la funzione collegata, cambiaStato()
29  // il modo per la rilevazione del cambiamento di stato
30  // sarà di tipo: FALLING
31  // cioè l’interrupt viene eseguito quando si passa
32  // da un livello HIGH ad un livello LOW
33 
34}
35 
36void loop()
37{
38  digitalWrite(pinVerde, HIGH);
39  delay(3000);
40  digitalWrite(pinVerde, LOW);
41  delay(3000);
42}
43 
44// Durante i 6 secondi di accensione e spegnimento del LED verde
45// non saremo bloccati dai delay presenti nel loop, potremo modificare
46// lo stato del LED rosso ogni volta nei 6 secondi di pausa del loop
47 
48void cambiaStato()
49// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
50// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW
51 
52{
53  stato = !stato;
54  digitalWrite(pinRosso, stato);
55  // il pin digitale 5 viene impostato a "stato"
56  // che può essere LOW o HIGH
57  // all'avvio di Arduino il LED sarà spento
58}

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

1/*
2   Prof. Michele Maffucci
3   Data 03.03.2021
4 
5   Oggetto: utilizzo degli interrupt
6 
7   Esempio 05
8 
9*/
10 
11volatile int valore = 10;
12 
13int pulsanteIncrementa = 2;
14int pulsanteDecrementa = 3;
15 
16/* dichiariamo volatile la variabile
17  state usata nella funzione usata all'interno di attachInterrupt */
18 
19void setup()
20{
21  Serial.begin(9600);
22   
23  pinMode(pulsanteIncrementa, INPUT);   // pulsante collegato al pin 2
24  pinMode(pulsanteDecrementa, INPUT);   // pulsante collegato al pin 3
25    
26  attachInterrupt(digitalPinToInterrupt(2), incrementa, FALLING);
27  attachInterrupt(digitalPinToInterrupt(3), decrementa, FALLING);
28 
29  // usiamo l'interrupt 0 e 1 che sono associati ai pin digitali 2 e 3
30  // attachInterrupt chiamerà la funzione collegata, cambiaStato
31  // il modo per la rilevazione del cambiamento di stato
32  // sarà di tipo: FALLING
33  // cioè l'interrupt viene eseguito quando vi è un passaggio da HIGH a LOW
34 
35}
36 
37void loop()
38{
39  Serial.println(valore);
40}
41 
42void incrementa() {
43  valore++;
44}
45 
46void decrementa() {
47  valore--;
48}

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

1/*
2 * Prof. Maffucci Michele
3 * SumoRobot
4 * Data: 26.01.2021
5 *
6 * Sketch 01: rotazione oraria e antioraria continua
7 *
8 * Note:
9 *          Per l'orientamento del robot
10 *          guardare SumoBot anteriormente
11 *      
12 *          180: max velocità in senso antiorario
13 *          90 : servomotori fermi
14 *          0  : max velocità in senso orario
15 *           
16 */
17 
18// inclusione della libreria servo.h per il controllo dei servomotori
19#include <Servo.h>
20 
21// Creazione oggetti servo
22Servo motoreDX;  // Inizializzazione del servomotore destro
23Servo motoreSX;  // Inizializzazione del servomotore sinistro
24 
25byte pinDx = 4;     // Inizializza del pin 4 a cui è connesso il pin segnale del servo destro
26byte pinSx = 5;     // Inizializza del pin 5 a cui è connesso il pin segnale del servo sinistro
27int  durata = 250;  // Durata movimento (orario/antiorario)
28int  ferma = 3000;  // Durata dello stop
29 
30void setup() {
31 
32  // attach() consente di definire a quale pin viene connesso il servomotore
33  // e lo collega all'oggetto che gestisce il servomotore
34   
35  motoreDX.attach(pinDx); // pinDx collegato al motore destro
36  motoreSX.attach(pinSx); // pinSxcollega to al motore sinistro
37}
38 
39void loop() {
40  orarioRobot();     // Rotazione in senso orario del robot
41  stopRobot();       // Stop rotazione per un tempo fissato (vedere variabile ferma)
42  antiorarioRobot(); // Rotazione in senso antiorario del robot
43  stopRobot();       // Stop rotazione per un tempo fissato (vedere variabile ferma)
44}
45 
46// rotazione del robot in senso antiorario
47void antiorarioRobot(void) {
48  motoreDX.write(150);  // Rotazione oraria del motore DX
49  motoreSX.write(150);  // Rotazione antioraria del motore SX
50  delay(durata);        // durata: durata della rotazione
51}
52 
53// rotazione del robot in senso orario
54void orarioRobot(void) {
55  motoreDX.write(30);    // Rotazione antioraria del motore DX
56  motoreSX.write(30);    // Rotazione oraria del motore SX
57  delay(durata);         // durata: durata della rotazione
58}
59 
60// stop del robot
61void stopRobot(void) {
62  motoreDX.write(90);   // Ferma il motore DX
63  motoreSX.write(90);   // Ferma il motore SX
64  delay(ferma);         // Durata dello stop
65}

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 🙂