Archivi tag: funzioni

Arduino: stampare più funzioni sulla serial plotter

L’attività di progetto di questa mattina: progettazione di dispositivi elettronici che rispondono a diverse necessità per la salute umana: cura della persona, disabilità, sicurezza sul lavoro, strumenti medici/laboratorio, accessibilità dei luoghi pubblici, ecc…

Con mio piacere ne è nata un’intensa attività di ricerca che ha coinvolto l’intero gruppo classe. Molte le necessità e tra queste ne evidenzio uno, semplice, che riguarda la programmazione, che in più occasioni gli studenti affrontano durante le sperimentazioni e di cui mi sono reso conto di non avere documentato adeguatamente, ovvero: il plot di più funzioni su uno stesso piano cartesiano con Arduino.

Avevo mostrato in più occasioni come effettuare il plot di grandezze variabili nel tempo utilizzando la Serial Plotter dell’IDE di Arduino, uno degli ultimi articoli si riferisce alla realizzazione di uno strumento per la rilevazione di vibrazioni, un semplice sismografo realizzato con un sensore piezoelettrico.

Alcuni studenti questa mattina, si stavano cimentando nella progettazione di un guanto da utilizzare per la movimentazione di un braccio robot industriale della hyundai con l’obiettivo di simulare la movimentazione a distanza di sostanze chimiche pericolose. Nelle prime attività di ricerca si è manifestata la necessità di visualizzare su tre grafici diversi le componenti X, Y, Z dell’accelerazione fornite da un accelerometro connesso ad Arduino.

Come sicuramente saprete l’avvio della Serial Plotter avviene, così come per la Serial Monitor dal menù Tools. Ricordo che Serial Monitor e Serial Plotter non possono essere avviate contemporaneamente.

La Serial Plotter prende i valori che giungono dalla seriale (connessione USB) e li grafica su un piano cartesiano. I dati numerici vengono anche visualizzati nella parte in alto a sinistra della finestra della Serial Plotter.

I valori massimi e minimi rappresentati sull’asse Y vengono regolati automaticamente, mentre la dimensione dell’asse X è fissato a 500 punti e l’aggiornamento del grafico avviene ogni qual volta nel vostro sketch viene eseguita una Serial.println().

Ricordate inoltre di fissare il Baud Rate del Serial Plotter in modo che corrisponda a quello che avete indicato nel codice.

Per tracciare contemporaneamente forme d’onda è possibile agire in due modi:

inserendo uno spazio tra due istruzioni di stampa

1Serial.print(temperatura);
2Serial.print(" ");
3Serial.println(umidita);

oppure inserendo una tabulazione tra due istruzioni di stampa

1Serial.print(temperatura);
2Serial.print("\t");
3Serial.println(umidita);

In questo caso le due grandezze, temperatura e umidità, saranno rappresentate da due funzioni separate e tracciate contemporaneamente sullo stesso piano cartesiano.

A titolo di esempio consideriamo il mio post in cui mostravo come utilizzare un DHT11 e visualizzeremo sulla Serial Plotter le due grandezze fisiche temperatura ed umidità. Di seguito schema di collegamento, sketch e grafici.

1// Prof. Maffucci Michele
2// Visualizzazione della pressione e dell'umidità rilevata da un DHT11
3// sulla Serial Plotter
4// 16.09.2021
5 
6// Libreria DHT
7#include "DHT.h"
8 
9// Pin digitale di arduino connesso al DHT
10#define DHTPIN 2
11 
12// tipo del sensore: DHT 11
13#define DHTTYPE DHT11
14 
15DHT dht(DHTPIN, DHTTYPE);
16 
17void setup() {
18  Serial.begin(9600);
19  dht.begin();
20}
21 
22void loop() {
23  // Attesa di 1 millisecondo prima di fornire la misura.
24  delay(1);
25 
26  // Lettura dell'umidità
27  float h = dht.readHumidity();
28  // Lettura della temperatura in gradi Celsius
29  float t = dht.readTemperature();
30 
31  // Verifica se le si presenta un errore di lettura (e riprova nuovamente)
32  if (isnan(h) || isnan(t)) {
33    Serial.println(F("Impossibile leggere dal sensore DHT!"));
34    return;
35  }
36 
37  // Stampa del valore dell'umidità
38  Serial.print(h);
39  Serial.print(' ');
40  // Stampa del valore della temperatura
41  Serial.println(t);
42}

Il plot delle due grandezze è:

Per completezza realizziamo un semplice sketch che permette di rappresentare 3 funzioni sinusoidali sfasate di 90 gradi una rispetto all’altra con ampiezze diverse:

1// Prof. Maffucci Michele
2// Visualizzazione di tre funzioni siusoidali
3// di ampiezza diversa e sfasate di 90 gradi
4// 16.09.2021
5 
6void setup() {
7  Serial.begin(9600);
8}
9 
10void loop() {
11 
12  // M_PI è la macro definita nell'header math.h
13  // che definisce il pi greco che ha il valore di:
14  // 3.14159265358979323846
15  for(int i = 0; i < 360; i += 2) {
16    float ValoreY1 = 1 * sin(i * M_PI / 180);
17    float ValoreY2 = 2 * sin((i + 90)* M_PI / 180);
18    float ValoreY3 = 4 * sin((i + 180)* M_PI / 180);
19 
20    Serial.print(ValoreY1);
21    Serial.print(' ');         // deve essere stampato spazio ' ' o  tab '\t' tra due valori.
22    Serial.print(ValoreY2);
23    Serial.print(' ');         // deve essere stampato spazio ' ' o  tab '\t' tra due valori.
24    Serial.println(ValoreY3);  // l'ultimo valore deve avere un ritorno a capo
25 
26    delay(1);
27  }
28}

Buon Coding a tutti 🙂

BBC micro:bit – funzioni

Sto svolgendo in questi giorni il corso sulla didattica laboratoriale e nelle giornata di ieri ho svolto un approfondimento sull’uso delle funzioni con il MakeCode editor di micro:bit che rendo pubblico su questo sito.

NOTA. Gli esempi in questo post hanno il solo scopo di mostrare l’utilizzo delle funzioni (chiamata di una funzione, passaggio per valore, restituzione di un valore da una funzione, ecc…) e non quello di risolvere un specifico problema, pertanto gli esempi potrebbero essere realizzati in modalità sicuramente più efficiente.

Le funzioni con BBC micro:bit

Una funzione consente di creare una porzione di codice che possiamo riutilizzare più volte nel nostro programma, quindi invece di copiare lo stesso codice in molte sezioni del programma, possiamo semplicemente utilizzare un unico blocco funzione da utilizzare all’interno del nostro codice tutte le volte che ci necessita.

Una funzione è definita dal suo nome e dal corpo della funzione che ospita tutte le istruzioni.

La funzione ha un nome univoco e non può essere costituita da parole staccate tra loro. E’ utile assegnare alle funzioni nomi che specificano cosa fa la funzione, ad esempio “calcoloVolume”, “calcoloArea”, “distanzaOstacolo”, “impostaLed”, “displayOn”. Si consiglia inoltre di adottare una notazione camel case (testo a cammello), ovvero scrivere parole composte o frasi, come il nome di funzioni, unendo tutte le parole tra loro, ma lasciando le loro iniziali con lettera maiuscola, in questo modo viene meglio decodificato da un essere umano il significato del nome composto. E’ buona regola adottare una strategia di questo genere anche per il nome delle variabili.
La prima lettera della frase può essere maiuscola o minuscola, tendenzialmente si preferisce usare la lettera minuscola.

Il corpo della funzione è il codice all’interno del blocco funzione, quello che viene chiamato body.

Continua a leggere

Arduino: strutturare il codice in blocchi funzionali

Molto spesso durante le prime fasi di realizzazione di uno sketch Arduino si tende a non strutturare il proprio programma in blocchi funzionali separati, tutto il codice viene inserito all’interno del loop(). Questo tipo di approccio, soprattutto se siamo in presenza di un codice molto lungo, rende difficoltosa la lettura del programma da parte di altre persone e non permette una facile comprensione del funzionamento o l’identificazione di possibili errori.

Ho parlato in passato come strutturare il codice in blocchi funzionali nel post:
Arduino – lezione 07: lavorare con gruppi di valori e funzioni esterne, ma in quella lezione utilizzavo istruzioni che in questa fase dell’anno scolastico non sono ancora conosciute da alcuni allievi soprattutto del 3′ anno, che iniziano ad utilizzare Arduino, pertanto la presente lezione dovrebbe chiarire in modo più semplice l’argomento.

Le funzioni vengono utilizzate per organizzare le azioni eseguite dal vostro programma. Ogni funzione può essere identificata da un’azione specifica richiamata dal programma principale loop(), oppure richiamate da altre funzioni sempre all’interno del nostro sketch.

Senza saperlo quando avete iniziato a programmare con Arduino avete utilizzato delle funzioni: loop() e setup() le due funzioni sempre presenti in ogni sketch.

Per creare una funzione bisogna:

  • definire il tipo di valore restituito dalla funzione;
  • assegnare un nome alla funzione;
  • e opzionalmente impostare una serie di parametri che la funzione riceverà quando viene chiamata (si dice anche invocata).

Creiamo una semplice funzione che permette di fare lampeggiare un LED, non possiede parametri e non restituisce nessun valore.
Assegnare alla funzione il tipo void vuol dire che non restituisce nulla e non inserire nulla tra le parentesi tonde indica che la funzione non accetta nessun parametro.

Esempio 1

1// Prof. Michele Maffucci
2// Es.01 - Usare le funzioni
3// Data: 15.11.2020
4 
5void setup() {
6  pinMode(LED_BUILTIN, OUTPUT);
7}
8 
9void loop() {
10  lampeggia();
11}
12 
13// blink LED una volta
14void lampeggia()
15{
16  digitalWrite(LED_BUILTIN, HIGH); // accende il LED
17  delay(1000); // pausa di 1 secondo
18  digitalWrite(LED_BUILTIN, LOW); // spegne il LED
19  delay(1000); // pausa di 1 secondo
20}

Ogni volta che il loop() chiama (invoca) la funzione esterna lampeggia() viene effettuato il blink del LED sulla scheda.

Esempio 2

Realizziamo ora un secondo esempio in cui la funzione “lampeggia” accetta come parametro un valore intero che definisce il delay da impostare all’interno del funzione.

1// Prof. Michele Maffucci
2// Es.02 - Usare le funzioni
3// Data: 15.11.2020
4 
5void setup() {
6  pinMode(LED_BUILTIN, OUTPUT);
7}
8 
9void loop() {
10  // chiamiamo la funzione lampeggia passando il valore
11  // specificato all'interno delle parentesi tonde
12  lampeggia(250);
13}
14 
15// blink LED una volta
16// per far si che la funzione accetti un parametro in input
17// bisogna dichiarare il tipo del parametro in ingresso
18// nella funzione lampeggia: int pausa
19 
20void lampeggia(int pausa)
21{
22  digitalWrite(LED_BUILTIN, HIGH); // accende il LED
23  delay(pausa); // pausa: valore inserito nella variabile "pausa"
24  digitalWrite(LED_BUILTIN, LOW); // spegne il LED
25  delay(pausa); // pausa: valore inserito nella variabile "pausa"
26}

Esempio 3

Realizziamo ora un terzo sketch in cui i parametri di ingresso per la funzione lampeggia() sono due, uno che definisce il tempo di accensione del LED ed uno che definisce il tempo di spegnimento del LED:

1// Prof. Michele Maffucci
2// Es.03 - Usare le funzioni
3// Data: 15.11.2020
4 
5void setup() {
6  pinMode(LED_BUILTIN, OUTPUT);
7}
8 
9void loop() {
10  // chiamiamo la funzione lampeggia passando il valore
11  // specificato all'interno delle parentesi tonde
12  lampeggia(250, 1000);
13}
14 
15// blink LED una volta
16// per far si che la funzione accetti duen parametri in input
17// bisogna dichiarare il tipo per ogni parametro in ingresso
18// nella funzione lampeggia: int pausaOn e int pausaOff
19 
20void lampeggia(int pausaOn, int pausaOff)
21{
22  digitalWrite(LED_BUILTIN, HIGH); // accende il LED
23  delay(pausaOn); // delay LED ON
24  digitalWrite(LED_BUILTIN, LOW); // spegne il LED
25  delay(pausaOff); // delay LED OFF
26}

Esempio 4

Definiamo ora una funzione che ha 3 parametri di ingresso: il tempo in cui il LED rimane acceso, il tempo in cui il LED rimane spento e il numero di volte (cicli) che deve ripetersi il lampeggio, al termine del numero di cicli il LED non lampeggerà più. Come specificato nei commenti la variabile chiave verrà utilizzata per eseguire una volta sola la chiamata della funzione lampeggio:

1// Prof. Michele Maffucci
2// Es.04 - Usare le funzioni
3// Data: 15.11.2020
4 
5// chiave e la variabile che consente l'esecuzione della
6// chiamata della funzione lampegga una sola volta
7int chiave = 0;
8 
9void setup() {
10  pinMode(LED_BUILTIN, OUTPUT);
11}
12 
13void loop() {
14  // chiamiamo la funzione lampeggia passando il valore
15  // specificato all'interno delle parentesi tonde
16  if (chiave == 0) {
17    chiave = 1;
18    lampeggia(250, 1000, 5);
19  }
20}
21 
22// blink LED una volta
23// per far si che la funzione accetti tre parametri in input
24// bisogna dichiarare il tipo per ogni parametro in ingresso
25// nella funzione lampeggia: int pausaOn, int pausaOff, int contatore
26// la variabile contatore definisce il numero di cicli di lampeggio
27 
28void lampeggia(int pausaOn, int pausaOff, int contatore)
29{
30  for (int i = 0; i < contatore; i++) {
31    digitalWrite(LED_BUILTIN, HIGH); // accende il LED
32    delay(pausaOn); // delay LED ON
33    digitalWrite(LED_BUILTIN, LOW); // spegne il LED
34    delay(pausaOff); // delay LED OFF
35  }
36}

All’interno dello sketch precedente la funzione lampeggio utilizza un ciclo for per ripetere, per il numero di volte specificato dalla variabile contatore, il codice di accensione e spegnimento (corpo del for).

Esempio 5

Per completezza e per richiamare il modo con cui utilizzare l’istruzione while, di seguito trovate lo sketch che realizza la medesima funzionalità dello sketch precedente in cui il ciclo for viene sostituito da un while:

1// Prof. Michele Maffucci
2// Es.05 - Usare le funzioni
3// Data: 15.11.2020
4 
5// chiave e la variabile che consente l'esecuzione della
6// chiamata della funzione lampegga una sola volta
7int chiave = 0;
8 
9void setup() {
10  pinMode(LED_BUILTIN, OUTPUT);
11}
12 
13void loop() {
14  // chiamiamo la funzione lampeggia passando il valore
15  // specificato all'interno delle parentesi tonde
16  if (chiave == 0) {
17    chiave = 1;
18    lampeggia(250, 1000, 5);
19  }
20}
21 
22// blink LED una volta
23// per far si che la funzione accetti tre parametri in input
24// bisogna dichiarare il tipo per ogni parametro in ingresso
25// nella funzione lampeggia: int pausaOn, int pausaOff, int contatore
26// la variabile contatore definisce il numero di cicli di lampeggio
27 
28void lampeggia(int pausaOn, int pausaOff, int contatore)
29{
30  while (contatore > 0) {
31     digitalWrite(LED_BUILTIN, HIGH); // accende il LED
32    delay(pausaOn); // delay LED ON
33    digitalWrite(LED_BUILTIN, LOW); // spegne il LED
34    delay(pausaOff); // delay LED OFF
35    contatore = contatore - 1; // decremento del contatore
36  }
37}

Esempio 6

Nello sketch che segue realizziamo una funzione che accetta un parametro e restituisce un valore. Il parametro che viene passato alla funzione definisce la durata dei tempi di accensione e spegnimento del LED (in millisecondi). La funzione continua a far lampeggiare un LED fino a quando non viene premuto un pulsante. Il valore restituito dalla funzione è il numero di lampeggi effettuati, questo valore verrà stampato sulla Serial Monitor:

1// Prof. Michele Maffucci
2// Es.06 - Usare le funzioni
3// Data: 15.11.2020
4 
5const int pinPulsante = 8; // pin a cui colleghiamo il pulsante
6 
7void setup() {
8  pinMode(LED_BUILTIN, OUTPUT);
9  pinMode(pinPulsante, INPUT);
10  Serial.begin(9600);
11}
12 
13void loop() {
14  Serial.println("Premere il pulsante per interrompere il lampeggio");
15  int contatore = lampeggia(500); // lampeggio del LED: 500 ms ON e 500 ms OFF
16  Serial.print("Il numero di volte in cui il LED ha lampeggiato è stato di: ");
17  Serial.println(contatore);
18  while (digitalRead(pinPulsante) == HIGH)
19  {
20    // non viene fatto nulla fino a quando non rilascio il pulsante
21  }
22}
23 
24// la funzione fa lampeggiare il LED per un periodo specificato (int periodo)
25// e restituisce il numero di volte in cui il LED ha lampeggiato
26 
27int lampeggia(int periodo)
28{
29  int contatoreLampeggio = 0;
30 
31  while (digitalRead(pinPulsante) == LOW)
32    // ripetere finché non viene premuto il pulsante
33    // cicla fino a quando il pulsante non viene premuto
34  {
35    digitalWrite(LED_BUILTIN, HIGH);
36    delay(periodo);
37    digitalWrite(LED_BUILTIN, LOW);
38    delay(periodo);
39    contatoreLampeggio = contatoreLampeggio + 1; // incrementa il contatore
40  }
41  // in questo punto vuol dire che pinPulsante è HIGH
42  // ciò vuol dire che il pulsante è premuto
43 
44  // contatoreLampeggio è il valore che viene restituito alla funzione chiamante
45  return contatoreLampeggio;
46}

Il tipo di dato che precede in nome della funzione:

int lampeggia()

indica il tipo di dato restituito dalla funzione. Ricordarsi che nella funzione chiamante, nel nostro caso il loop(), quando chiamiamo la funzione questa deve terminare con un punto e virgola:

1int contatore = lampeggia(500);

Errori comuni che vengono commessi nella chiamata di funzioni

Rimando a queste due brevi note che mostrano alcuni errori tipici:

Buon Making a tutti 🙂

Esercizi per i miei studenti

Esercizio 1

Realizzare un circuito costituito da 4 pulsanti e 4 led, connessi ad Arduino.

  • Alla pressione del pulsante P1 il LED1 effettua il blink con un tempo di 200 ms
  • Alla pressione del pulsante P2 il LED2 effettua il blink con un tempo di 400 ms
  • Alla pressione del pulsante P3 il LED3 effettua il blink con un tempo di 600 ms
  • Alla pressione del pulsante P4 il LED4 effettua il blink con un tempo di 800 ms

Fare in modo tale che ci sia una funzione esterna, chiamata dal loop(), che effettui il blink dei led.

Esercizio 2

Aggiungere all’esercizio 1 un pulsante che se premuto accende in sequenza ripetuta i 4 led. L’accensione in sequenza deve avvenire chiamando una funzione esterna al loop().