Archivi categoria: arduino

Gestire le stringhe con Arduino – approfondimenti


In passato ho già affrontato questo argomento e con questo post aggiungo alcuni approfondimento utili per i miei studenti.

Le stringhe sono una struttura di dati che viene utilizzata per memorizzare e gestire il testo. Le stringhe possono essere utilizzate per visualizzare del testo su un qualsiasi display connesso ad Arduino oppure più semplicemente sul monitor seriale di Arduino. Le stringhe possono essere utilizzate anche per memorizzare ad esempio i caratteri inseriti da tastiera connessa ad Arduino.

Possiamo distinguere due tipi di stringhe

  1. Array di caratteri, che sono le stesse delle stringhe utilizzate nella programmazione C.
  2. Il tipo di dato Sring di Arduino che permette di utilizzare un oggetto String in uno sketch.

Array di caratteri

Il primo tipo di stringa che impareremo ad utilizzare è la stringa costituita da una serie di caratteri memorizzati in un Array, cioè un vettore di caratteri il cui ultimo elemento è un carattere terminatore (o di fine stringa), codificato con il numero 0 e rappresentato in C dal carattere ‘\0’.

Nel programma che segue viene composta la stringa, un array di caratteri, il cui ultimo carattere è costituito dal terminatore 0, la stringa composta verrà poi stampata sulla Serial Monitor.

1void setup() {
2   char mia_stringa[8];    // un array di dimensione 8 costituito da 7 caratteri
3   Serial.begin(9600);     // inizializzazione della Serial Monitor
4   mia_stringa[0] = 'A';   // la stringa è formata da 5 caratteri più il terminatore
5   mia_stringa[1] = 'r';
6   mia_stringa[2] = 'd';
7   mia_stringa[3] = 'u';
8   mia_stringa[4] = 'i';
9   mia_stringa[5] = 'n';
10   mia_stringa[6] = 'o';
11   mia_stringa[7] = 0;          // l'ottavo elemento dell’array è costituito dal terminatore
12   Serial.println(mia_stringa); // stampa della stringa sulla Serial Monitor
13}
14 
15void loop() {
16    // per ora nulla
17}

Nell’esempio che segue utilizziamo un modo più comodo per creare le stringhe.
In questo caso il compilatore calcola la dimensione dell’array di caratteri ed inserisce automaticamente il terminatore di stringa costituito dal carattere 0. Un array composto da 8 elementi, 7 caratteri seguiti dal carattere 0.
Il risultato sarà il medesimo dello sketch precedente:

1void setup() {
2   char mio_sito[] = "Arduino";
3   Serial.begin(9600);
4   Serial.println(mio_sito);
5}
6 
7void loop() {
8    // per ora nulla
9}

Manipolazione di Array di stringhe

La manipolazione di un’array di stringhe può essere effettuata nel seguente modo:

1void setup() {
2   char mia_stringa[] = "Salve mondo"; // creazione della stringa
3   Serial.begin(9600);
4   // stampa della stringa sulla Serial Monitor
5   Serial.println(mia_stringa);
6   // cancellazione di una parte della stringa
7   mia_stringa[5] = 0;
8   Serial.println(mia_stringa);
9   // (3) sostituzione dei caratteri
10   mia_stringa[5] = ' '// sostituzione con il carattere spazio
11   mia_stringa[6] = 'M'// inserimento della nuova parola
12   mia_stringa[7] = 'i';
13   mia_stringa[8] = 'k';
14   mia_stringa[9] = 'y';
15   mia_stringa[10] = '!';
16   mia_stringa[11] = 0; // terminatore di stringa
17   Serial.println(mia_stringa);
18}
19 
20void loop() {
21    // per ora nulla
22}

Risultato:

1Salve mondo
2Salve
3Salve Miky!

La stringa viene abbreviata sostituendo il 5′ carattere con il terminatore 0.
Quando la stringa viene stampata sulla Serial Monitor, tutti i caratteri dell’array vengono stampati fino al nuovo terminatore 0, quello che possiede l’indice 5 nell’array. I restanti caratteri non vengono cancellati sono ancora presenti in memoria, compreso il secondo terminatore 0 e l’array possiede ancora la stessa dimensione, però in questo caso se utilizziamo una funzione che legge l’array questa vedrà tutti i caratteri fino al nuovo terminatore (indice 5).

Successivamente viene sostituita la parola ” mondo” con la parola “Miky!” e questa operazione viene effettuato sostituendo il terminatore in posizione 5 con uno spazio in modo che la stringa venga ripristinata nel formato di origine.
L’inserimento della nuova parola viene eseguito inserendo in ogni posizione la lettera corrispondente alla nuova parola.

Funzioni per manipolare le stringhe

Lo sketch precedente permette di manipolare la stringa manualmente accedendo ai singoli caratteri memorizzati nell’array.
Per manipolare le stringhe possiamo scrivere noi delle nostre funzioni, oppure più semplicemente utilizzare le funzioni String presenti nella libreria del C.

Esempio

1void setup() {
2   char mia_stringa[] = "Nel mezzo del cammin di nostra vita"; // creazione della stringa
3   char mia_stringa_output[80]; // l’output verrà inserito in questa stringa
4   int dimensione;
5   Serial.begin(9600);
6 
7   // A. stampa la stringa
8   Serial.println(mia_stringa);
9 
10   // B. lettura della lunghezza della stringa.
11   // Nel conteggio viene escluso il carattere
12   // terminatore
13   dimensione = strlen(mia_stringa);
14   Serial.print("La lunghezza della stringa è: ");
15   Serial.println(dimensione);
16 
17   // C. prende la lunghezza dell’array incluso il carattere terminatore
18   dimensione = sizeof(mia_stringa); // sizeof() non è una funzione specifica per gestire le stringhe
19   Serial.print("Dimensione dell'array: ");
20   Serial.println(dimensione);
21 
22   // D. copiare una stringa
23   strcpy(mia_stringa_output, mia_stringa);
24   Serial.println(mia_stringa_output);
25 
26   // E. aggiungere una stringa alla fine di un’altra (append)
27   strcat(mia_stringa_output, " mi ritrovai per una selva oscura");
28   Serial.println(mia_stringa_output);
29   dimensione = strlen(mia_stringa_output);
30   Serial.print("La lunghezza della stringa è: ");
31   Serial.println(dimensione);
32   dimensione = sizeof(mia_stringa_output);
33   Serial.print("Dimensione dell'array mia_stringa_output[ ] è: ");
34   Serial.println(dimensione);
35}
36 
37void loop() {
38    // per ora nulla
39}

Risultato:

1Nel mezzo del cammin di nostra vita
2La lunghezza della stringa è: 35
3Dimensione dell'array: 36
4Nel mezzo del cammin di nostra vita
5Nel mezzo del cammin di nostra vita mi ritrovai per una selva oscura
6La lunghezza della stringa è: 68
7Dimensione dell'array mia_stringa_output[ ] è: 80

Come avete avuto modo già di imparare, per stampare una stringa sarà sufficiente richiamarla come argomento all’interno della funzione println. La funzione strlen(), che restituisce la lunghezza di una stringa, prende in considerazione solamente i caratteri che possono essere stampati (visualizzati) ad esclusione del terminatore 0.

L’operatore sizeof() prende come argomento un array e di questo ne restituisce la sua lunghezza, in questo caso però la lunghezza include anche il terminatore di stringa, pertanto il sizeof() restituirà un valore più grande di una unità rispetto alla funzione strlen().

sizeof() può essere confusa con una funzione, ma tecnicamente è un operatore. Non fa parte della libreria String del C, ma è stata utilizzata in questi esempio per mostrare la differenza tra la dimensione dell’array e la dimensione della stringa.

La funzione strcpy() viene usata, nello sketch sopra indicato, per copiare mia_stringa in mia_stringa_output quindi la funzione copia la seconda stringa nella prima attenzione che in questo caso la seconda stringa viene copiata in una stringa che ha dimensione più grande, 80, mentre mia_stringa ha una dimensione di 35, questo vuol dire che ci saranno 20 caratteri liberi in memoria che potranno essere utilizzati.

Domanda

Dopo la copia di mia_stringa in mia_stringa_output sei in grado di dimostrare con uno sketch cosa è presente in memoria dal 36’ al 80’ posizione di mia_stringa_output?

La concatenazione di stringhe viene eseguita con la funzione strcat() che permette di inserire la seconda stringa indicata nella funzione strcat() alla fine delle prima stringa indicata nella strcat(). Dopo la concatenazione viene mostrata la lunghezza della nuova stringa risultante costituita da 68 caratteri copiati in un array che ha una dimensione di 80.

Si ricorda che una stringa di 68 caratteri occupano nell’array 69 caratteri in quanto bisogna considerare il terminatore 0.

Attenzione

Se l’array fosse troppo piccolo e provassimo a copiare all’interno una una stringa più grande della dimensione dell’array, la stringa verrebbe copiata anche al di fuori dell’array. La memoria oltre la fine dell’array potrebbe contenere altri dati importanti utilizzati nello sketch, questi dati verrebbero quindi sovrascritti dalla nostra stringa. In questo caso se la memoria oltre la fine della stringa viene sovrascritto potrebbe far arrestare in modo anomalo il programma o causare comportamenti strani.

Per approfondire l’argomento e verificare come evitare di scrivere al di furi dello spazio di memoria assegnato ad uno specifico array, vi rimando a questi due post:

Arduino – lezione 07: lavorare con gruppi di valori e funzioni esterne
Arduino – Concatenare la stampa di stringhe e variabili

Errori comuni nell’uso di Arduino – inserire void davanti alla funzione chiamata

Ho rilevato che alcune volte alcuni allievi nella chiamata di una funzione inseriscono il tipo restituito dalla funzione davanti alla funzione chiamata, ciò può essere fatto solamente nella dichiarazione del prototipo di funzione.
Il prototipo di una funzione costituisce una dichiarazione della funzione e fornisce al compilatore le informazioni indispensabili per gestire la funzione.
Nella definizione di una funzione, viene indicato anche ciò che deve fare la funzione quando viene invocata, specificato nel corpo della funzione.

Lo sketch che segue non chiama la funzione miSentoFortunato()

1void setup() {
2  Serial.begin(9600);
3}
4 
5void loop() {
6 
7  // inserendo void nella chiamata di funzione
8  // NON verrà chiamata la funzione miSentoFortunato()
9  void miSentoFortunato();
10}
11 
12void miSentoFortunato() {
13  Serial.println("Oggi mi sento fortunato :-)");
14}

Attenzione che in fase di compilazione e trasferimento non verrà restituito nessuno errore da parte del compilatore quindi l’individuazione dell’errore in un codice più esteso potrebbe essere di difficile individuazione.

Codice corretto

1void setup() {
2  Serial.begin(9600);
3}
4 
5void loop() {
6 
7  // eliminando void nella chiamata di funzione
8  // verrà chiamata la funzione miSentoFortunato()
9  miSentoFortunato();
10}
11 
12void miSentoFortunato() {
13  Serial.println("Oggi mi sento fortunato :-)");
14}

Esercizio 1

Perché il codice che segue non restituisce errore?

1void setup() {
2  Serial.begin(9600);
3}
4void loop() {
5  void miSentoFortunato();
6}

Esercizio 2

Perché il codice che segue non restituisce errore?

1void setup() {
2  Serial.begin(9600);
3}
4void loop() {
5  // :-)
6}
7void miSentoFortunato();

Esercizio 3

Perché il codice che segue restituisce errore?

1void setup() {
2  Serial.begin(9600);
3}
4void loop() {
5 
6  void miSentoFortunato() {
7  // :-)
8  }
9}

Per i miei allievi: il primo che risponde correttamente vince una scheda Arduino (non è uno scherzo) 😉

Errori comuni nell’uso di Arduino – usare troppa memoria RAM

Nella collezione degli errori nell’uso di Arduino aggiungo l’uso improprio della memoria Flash (RAM) disponibile all’interno del microcontrollore. La dimensione della memoria RAM differisce sulle varie schede Arduino, nell’esempio prendo in considerazione la scheda Arduino UNO R3.

Nell’esempio che segue, un semplice Blink, viene dichiarato un array di interi che in fase di compilazione provocherà un errore di dimensione troppo grande dell’array:

1// laMiaCollezione è un array di interi che ha una dimensione di:
2// (20000x16)/8=40kB
3// superiore alla RAM disponibile in un Arduino UNO R3 che è di 32kB
4// si faccia riferimento al seguente link:
6 
7int laMiaCollezione[20000];
8 
9void setup() {
10  Serial.begin (9600);
11  Serial.println ("Avvio programma");
12  pinMode(LED_BUILTIN, OUTPUT);
13}
14 
15void loop() {
16  digitalWrite(LED_BUILTIN, HIGH);
17  delay(1000);
18  digitalWrite(LED_BUILTIN, LOW);
19  delay(1000);
20}

Dovreste rilevare l’errore indicato nell’immagine che segue:

Se non vedete il led L (connesso al pin13) lampeggiare o rilevate un errore simile a quello indicato nell’immagine sopra, vuol dire che state utilizzando troppa memoria RAM. Fate attenzione che molte librerie utilizzano la RAM (ad es. Libreria di schede SD, librerie LCD, ecc.).

Nel link allegato nello sketch di esempio potete consultare la pagina specifica di Arduino.cc in cui viene spiegato come viene usata la memoria sull’Arduino UNO R3:

  • Flash 32k byte (di cui 0.5k usato per il bootloader)
  • SRAM 2k byte
  • EEPROM 1k byte

Nel dettaglio:

  • 32kB (1024×32=32768bit) di memoria Flash di cui 0,5 kB (equivalenti a 512 bit) sono dedicati al bootloader.
    Il microcontrollore presente sull’Arduino UNO R3 è un ATmega328P. All’interno della memoria Flash (RAM) del microcontrollore è presente una zona di memoria riservata in cui viene allocato il Bootloader, un programma che ha il compito di controllare se dopo il reset della scheda vie è la possibilità di caricare in memoria, tramite la USB, un nuovo programma creato dall’utente. Se non è presente nessun nuovo programma il controllo passa all’inizio del programma presente in memoria, quello precedentemente caricato e viene eseguito e il controllo non ritorna più al Bootloader fino a quando non si esegue un nuovo reset della scheda, reset che avviene quando viene caricato una nuovo programma dell’utente.
  • 2 KB di SRAM (2048 bit), è una RAM statica volatile utilizzata per gestire le variabili contenute nel programma (sketch) creato dall’utente e i dati che servono al microcontrollore per funzionare.
  • 1KB di EEPROM (1024 bit) memoria i cui valori vengono mantenuti anche quando la scheda viene spenta.

Esercizio

Nello sketch riportato sopra quel è il numero massimo di interi che può ospitare l’array laMiaCollezione affinché non si incorri nell’errore di memoria RAM piena?

Arduino – Esercizio: Realizzare un timer per il lavaggio delle mani

Di seguito mostro parte della soluzione agli esercizi assegnati ai miei studenti negli scorsi giorni in riferimento alla progettazione di un semplice dispositivo di automazione da collocare in bagno in prossimità del lavandino, in grado di rilevare ad una distanza minima fissata la presenza delle mani e l’avvio di un timer che mostra il trascorrere del tempo. L’indicazione del tempo che trascorre viene realizzata con un servomotore a cui dovrà poi essere fissata una lancetta e che mostra il trascorrere del tempo su una scala graduata. Il tempo di lavaggio viene fissato a 30 secondi. Sulla serial monitor dovrà essere indicato lo stato di avvio del sistema ed il tempo.

Soluzione

Controllo servomotore

1// Prof. Maffucci Michele
2// gestione servomotore
3 
4#include <Servo.h>
5 
6int pos = 0;
7 
8Servo mioServo;
9 
10// scrivere pos +=1 è la stessa cosa che scrivere pos = pos + 1
11 
12void setup()
13{
14  mioServo.attach(9);
15}
16 
17void loop()
18{
19  // muove il servo da 0 a 180 gradi
20  // con passi di 1 grado
21   
22 for (pos = 0; pos <= 180; pos += 1) {
23    // viene detto al servo di posizionarsi
24    // nella posizione inserita nella variabile 'pos'
25    mioServo.write(pos);
26    // attende 15 ms wait 15 ms
27    // per far raggiungere il sevo la posizione
28    delay(15); // attesa di 15 millisecondi
29  }
30  for (pos = 180; pos >= 0; pos -= 1) {
31    // viene detto al servo di posizionarsi
32    // nella posizione inserita nella variabile 'pos'
33    mioServo.write(pos);
34    // attende 15 ms wait 15 ms
35    // per far raggiungere il sevo la posizione
36    delay(15); // attesa di 15 millisecondi
37  
38}

Controllo sensore ultrasuoni

1// Prof. Maffucci Michele
2// Impostazione sensore ultrasuoni
3 
4// distanza minima dell'ostacolo (in cm)
5 
6const int distanzaMinima = 20;
7int misuraDistanza = 0;
8 
9long durata;          // durata dell'impulso
10long distanza;        // distanza dell'oggetto
11int pin_segnale = 7;  // pin Arduino a cui è collegato il sensore SR04
12int pin_trig = 10;    // pin Arduino a cui è collegato il sensore SR04
13 
14 
15void setup()
16{
17    Serial.begin(9600);
18    pinMode(pin_trig, OUTPUT);
19    pinMode(pin_segnale, INPUT);
20    Serial.println("Sensore ad ultrasuini");
21}
22 
23void loop()
24{
25  Serial.print("Distanza ostacolo: ");
26  Serial.println(distanzaOstacolo());
27  delay(100);
28}
29 
30// rilevazione distanza ostacolo
31 
32// misura la distanza dell'ostacolo
33long distanzaOstacolo()
34{
35  digitalWrite(pin_trig, LOW);
36  delayMicroseconds(2);
37  digitalWrite(pin_trig, HIGH);
38  delayMicroseconds(10);
39  digitalWrite(pin_trig, LOW);
40  durata = pulseIn(pin_segnale, HIGH);
41  distanza = (durata / 2) / 29.1;
42  delay(100);
43  return distanza;
44}

Timer lavaggio mani

1// Prof. Maffucci Michele
2// realizzazione di un timer
3// per il lavaggio delle mani
4// in un tempo di 30 secondi
5 
6#include <Servo.h>
7 
8// Impostazione servomotore
9int pos = 0;
10 
11Servo mioServo;
12 
13// Impostazione sensore ultrasuoni
14 
15// distanza minima dell'ostacolo (in cm)
16 
17const int distanzaOstacolo = 20;
18int misuraDistanza = 0;
19 
20long durata;          // durata dell'impulso
21long distanza;        // distanza dell'oggetto
22int pin_echo = 7;     // pin Arduino a cui è collegato il sensore SR04
23int pin_trig = 10;    // pin Arduino a cui è collegato il sensore SR04
24 
25void setup() {
26    Serial.begin(9600);
27    mioServo.attach(9);
28    pinMode(pin_trig, OUTPUT);
29    pinMode(pin_echo, INPUT);
30     
31// Posizionamento iniziale del servo
32   
33  mioServo.write(180);
34  delay(500);
35  mioServo.write(0);
36  delay(500);
37  mioServo.write(180);
38 
39// Avvio alla partenza
40   
41  Serial.println("Avvio programma lavaggio mani");
42}
43 
44void loop() {
45   
46  // Se la distanza delle mani dal rubinetto è
47  // inferiore alla distanzaOstacolo si avvia il timer
48   
49    if (misuraDistanzaOstacolo() < distanzaOstacolo) {
50        contoAllaRovesciaServo();
51  }
52  delay(100);
53}
54 
55 
56// Conto alla rovescia
57// sposta il servo di 6 gradi ogni secondo
58 
59void contoAllaRovesciaServo() {
60  Serial.println("Conto alla rovescia: ");
61  int passi = 30;
62   
63  for (int i = passi; i >= 0; i--) {
64 
65    mioServo.write(i * 6);
66    delay(1000);
67    Serial.print(i);
68    Serial.println(" sec");
69 
70  }
71 
72  // azzeramento del servo.
73  // Le mani sono pulite
74  mioServo.write(180);
75  delay(500);
76}
77 
78// misura la distanza dell'ostacolo
79long misuraDistanzaOstacolo()
80{
81  digitalWrite(pin_trig, LOW);
82  delayMicroseconds(2);
83  digitalWrite(pin_trig, HIGH);
84  delayMicroseconds(10);
85  digitalWrite(pin_trig, LOW);
86  durata = pulseIn(pin_echo, HIGH);
87  distanza = (durata / 2) / 29.1;
88  delay(100);
89  return distanza;
90}

Esercizio 1

Modificare lo sketch precedente aggiungendo due LED, verde e rosso. Lo stato di riposo, timer non funzionante, deve essere indicato dal LED verde acceso e Led rosso spento, mentre lo stato di funzionamento del timer deve essere evidenziato dal LED verde spento e LED rosso acceso.

Esercizio 2

Modificare lo sketch precedente aggiungendo due LED, verde e rosso e un buzzer. Lo stato di riposo, timer non funzionante, deve essere indicato dal LED verde acceso e Led rosso spento, all’avvio del timer il buzzer deve emettere una nota ad una frequenza fissata per un tempo di  1 secondo. Lo stato di funzionamento del timer deve essere evidenziato dal LED verde spento e LED rosso acceso, allo scadere del tempo di lavaggio deve essere emessa una nota di durata 1 secondo ad una frequenza diversa dalla nota di avvio.

Esercizio 3

Realizzare l’esercizio 2 con le medesime caratteristiche e componenti, però sostituendo il servomotore usato come indicatore, con un display LCD 16×2 che indichi il trascorrere del tempo.

Buon lavoro 🙂

Errori comuni nell’uso di Arduino – inizializzazione di più variabili

La tentazione di risparmiare linee di codice è sempre in agguato e tentare di inizializzare più variabili su una sola riga è forte, però alcune volte ciò che intendiamo fare non corrisponde a ciò che correttamente intende il compilatore.

Con la scrittura:

1int x, y, z = 67;

non viene assegnato il valore 67 ad ogni singola variabile, ma solamente la variabile z viene inizializzata a 67.

L’errore quindi non risiede nella scrittura, il compilatore non restituirà errore, ma nel modo in cui il programmatore intende l’assegnamento credendo che tutte le variabili vengano inizializzate a 67.

La scrittura

1int x = 67, y = 67, z = 67;

permette l’assegnamento del valore 67 ad ogni singola variabile. Si ricordi di separare ogni assegnamento da una virgola, ciò accade solo e soltanto se si indicano tipo e assegnamento su stessa linea.

Per l’assegnamento di valori diversi scriveremo:

1int x = 99, y = 3, z = 67;

Io in genere preferisco una scrittura di questo tipo:

1int x = 99;
2int y = 3;
3int z = 67;

che permette di visualizzare meglio le variabili utilizzate, non favorisce fraintendimenti nell’assegnamento e permette inoltre di scrivere chiaramente un commento a lato di ogni assegnamento.

Esercizio (semplice semplice 🙂 ) per i miei studenti:

Realizzare uno sketch che dimostra quanto esposto in questo post.

Buon Coding 🙂