Archivi categoria: arduino

Controllare un motore passo passo unipolari con Arduino

In queste settimane i miei studenti stanno svolgendo una serie di esercitazioni in cui è necessario controllare la rotazione di un motore passo passo (stepper). L’obiettivo delle esercitazioni è quello di simulare il sistema di controllo di un forno a microonde e lo stepper viene utilizzato per la rotazione del piatto.

In un precedente articolo ho dettagliato il principio di funzionamento di questa tipologia di motori, in questo post ne riassumo le principali caratteristiche e condivido alcuni esempi che sono di base per la realizzazione delle sperimentazioni svolte per il laboratorio di TPSEE e Sistemi.

Richiami

Il vantaggio principale rispetto ad un motore brushed (a spazzola) in corrente continua e di un servomotore e che l’albero di rotazione di uno stepper può essere posizionati con precisione, spostandolo in avanti o all’indietro di un “passo” alla volta, ma possono anche ruotare continuamente.
In questa lezione verrà mostrato come controllare un motore passo-passo utilizzando Arduino ed un ponte H L293D, lo stesso utilizzato per controllare un motore a spazzola come quelli utilizzati in molti dei kit robotici che trovate sia questo sito.

Caratteristiche principali di uno stepper

  • La differenza sostanziale da un motore brushed in corrente continua risiede nel fatto che un motore passo passo mantiene la velocità di rotazione costante anche con un carico applicato, ovvero se sottoposti a sforzo mantengono velocità costante. Questa caratteristica consente di evitare sistemi di controreazione, utilizzando ad esempio degli encoder, per il mantenimento costante della velocità.
  • i motori passo passo erogano coppie elevate anche a basso numero di giri.
  • accelerazioni e frenate repentine
  • mantenimento del carico fermo e senza vibrazioni

Difetti

  • E’ necessario utilizzare un circuito elettrico di pilotaggio
  • rendimento energetico basso
  • velocità di rotazione ridotta
  • costo di acquisto elevato

Per le caratteristiche tecniche, il principio di funzionamento e le modalità di connessione vi rimando all’articolo: Controllo di un motore passo-passo bipolare NEMA17 con Driver L298N

Negli sketch di esempio che troverete in questo articolo prendo in considerazione 3 tipologie di stepper: i piccoli stepper dei CD-ROM e dei DVD dei computer, 28BYJ-48 comuni in molti kit Arduino, e i NEMA 17.

Negli sketch di esempio che troverete in questo articolo prendo in considerazione 3 tipologie di stepper: i micro stepper dei CD-ROM e dei DVD dei computer, i 28BYJ-48 comuni in molti kit Arduino, e i nema 17.

Negli sketch troverete alcune parti commentate, togliendo il commento ad alcune linee di codice, potrete impostare il funzionamento per una delle tre tipologie di stepper.

Una delle attività svolte nello scorso anno scolastico dai miei allievi è stato il recupero di tutti gli stepper dei vecchi pc alienati della scuola, ciò mi ha permesso di realizzare velocemente tutta una serie di esercitazioni sull’uso dei motori passo passo.

Gli tutti gli stepper recuperati sono stati posti su supporti stampati in 3D e fissati su basette di compensato. L’utilizzo degli stepper dei CD-ROM consente inoltre di realizzare attività di progetto interessanti, come ad esempio piccoli plotter oppure dei laser engraver.

Raccomandazioni per i miei studenti

Punto 1

Ricordate di individuare le bobine di un motore passo passo, l’operazione è un po’ difficoltosa per i passo passo dei CD-ROM che in genere non sono identificati da colori o dalla posizione.
Prima di collegare il motore alla scheda motori è necessario individuare i cavi A+, A-, B+ e B- sul motore. La maniera migliore è quella di consultare la scheda tecnica del motore in cui vi è una corrispondenza tra colore filo e cavo. In alternativa potete utilizzare un multimetro in modalità ohmmetro e misurare la resistenza tra le coppie dei cavi, quando misurerete un valore tra i 2 e i 4 ohm tra due terminali avrete individuato una delle bobine.

Punto 2

Sempre per i passo passo dei CD-ROM i fili di collegamento sono saldati al motore ed il punto di saldatura è molto piccolo, inoltre i fili di collegamento sono sottili è molto alto il rischio di romperli. Per evitare ciò ho utilizzato una strategia semplice ed economica, un ponte con un mammut su cui collego poi i fili che vanno ad essere collegati alla breadboard, così come rappresentato nell’immagine.

Punto 3

La spiegazione del funzionamento di ogni sketch è dettagliata con commenti nello sketch e tutti gli esempi sono da considerare come base di partenza per le esercitazioni svolte in presenza.

Esempio 1

Controllo rotazione stepper da Serial Monitor mediante tastiera. Valori positivi rotazione oraria, valori negativi rotazione antioraria

// Prof. Michele Maffucci
// 20.10.2020

// Controllo rotazione stepper mediante tastiera
// valori positi rotazione oraria
// valori negativi rotazione antioraria

#include <Stepper.h>

int in1Pin = 12;
int in2Pin = 11;
int in3Pin = 10;
int in4Pin = 9;

/*
in1 L293D - pin 12 Arduino
in1 L293D - pin 11 Arduino
in1 L293D - pin 10 Arduino
in1 L293D - pin 9 Arduino
*/

// 200 per stepper 17PM-M041-P1 - 12 V - 1,8 gradi per step

// 200 per microstepper CDROM - 5 V - 1,8 gradi per step
// per evitare che slitti sul supporto impostare 170 come limite massimo

// 512 per stepper 28BY J-48 - 5 V - 5,525 gradi per step
// per questo stepper il valore passato non e' step/giro
// per un giro completo 2048 step. 512 equivale ad 1/4 di giro

// inizializzazione della libreria Stepper

const int stepPerGiro = 200;  // adattare al passo di rotazione del vostro stepper

Stepper myStepper(stepPerGiro, in1Pin, in2Pin, in3Pin, in4Pin);

void setup()
{
  pinMode(in1Pin, OUTPUT);
  pinMode(in2Pin, OUTPUT);
  pinMode(in3Pin, OUTPUT);
  pinMode(in4Pin, OUTPUT);

  Serial.begin(9600);

  // rotazioni per minuto, funzione dello stepper a disposizione
  // e' un valore positivo.
  // la funzione setSpeed non fa ruotare il stepper, imposta solamente la velocita'
  myStepper.setSpeed(70);    // imposta la velocita' di rotazione a 20 rpm:
}

void loop()
{
  if (Serial.available())
  {
    int steps = Serial.parseInt();

    // valori positivi fanno girare il mySteppere in senso orario
    // valori negativi fanno girare il mySteppere in senso antioraio
    myStepper.step(steps);
  }
}

Esercizio 2

Realizzare uno sketch che permetta di contare il numero di passi di rotazione fissato il numero di step per passo. Si evidenzi il raggiungimento di un passo con l’accensione di un LED.

Si visualizzi sulla serial monitor il messaggio:

“PASSO” – passo rotazione modulo “MODULO” passo: “CONTATORE STEP”

sono variabili:

“PASSO”,
“MODULO”
“CONTATORE STEP”

Nell’esempio:

PASSO: numero sequenziale
MODUOLO: step per passo
CONTATORE STEP: modulo del passo

// Prof. Michele Maffucci
// 20.10.2020

// contatore passi
// il programma puo' pilotare stepper unipolari o bipolari
// il numero di passi fissato nella variabile moduloStep
// ad ogni passo viene rilevato anche da un LED

// 200 per stepper 17PM-M041-P1 - 12 V - 1,8 gradi per step

// 200 per microstepper CDROM - 5 V - 1,8 gradi per step
// per evitare che slitti sul supporto impostare 170 come limite massimo

// 512 per stepper 28BY J-48 - 5 V - 5,525 gradi per step
// per questo stepper il valore passato non e' step/giro
// per un giro completo 2048 step. 512 equivale ad 1/4 di giro


#include <Stepper.h>

const int stepPerGiro = 170;  // adattare al passo di rotazione del vostro stepper

// inizializzazione della libreria Stepper
Stepper myStepper(stepPerGiro, 12, 11, 10, 9);

int contatorePassi = 0;         // numero di step raggiunto
int moduloStep = 20;            // modulo rotazione stepper da 1 a numero massimo di passi
                                // se moduloStep = 1 diventa un contatore di step
int conta = 0;                  // conteggio fase

int ledPin = 7;                 // led rilevamento fase

void setup() {
  // inizializzazione porta seriale
  Serial.begin(9600);
  // imposta la velocita' di rotazione a 20 rpm
  myStepper.setSpeed(20);

  pinMode(ledPin, OUTPUT);
}

void loop() {
  // passi di rotazione
  myStepper.step(moduloStep);
  Serial.print(conta);
  Serial.print(" - ");
  Serial.print("passo rotazione modulo ");
  Serial.print(moduloStep);
  Serial.print(" ");
  Serial.print("passo: ");
  Serial.println(contatorePassi);
  digitalWrite(ledPin, HIGH);
  conta++;
  contatorePassi=contatorePassi+moduloStep;
  if (contatorePassi &amp;gt; stepPerGiro){
    contatorePassi = 0;
    conta = 0;
  }
  delay(500);
  digitalWrite(ledPin, LOW);
}

Variante per stepper 28BY J 48

// Prof. Michele Maffucci
// 20.10.2020

// contatore passi
// il programma puo' pilotare stepper unipolari o bipolari
// il numero di passi fissato nella variabile moduloStep
// ad ogni passo viene rilevato anche da un LED

// in questo sketch si puo' effettuare le dovute correzioni per lo stepper
// 28BY J-48

// 200 per stepper 17PM-M041-P1 - 12 V - 1,8 gradi per step

// 200 per microstepper CDROM - 5 V - 1,8 gradi per step
// per evitare che slitti sul supporto impostare 170 come limite massimo

// 512 per stepper 28BY J-48 - 5 V - 5,525 gradi per step
// per questo stepper il valore passato non e' step/giro
// per un giro completo 2048 step. 512 equivale ad 1/4 di giro


#include <Stepper.h>

const int stepPerGiro = 512;  // adattare al passo di rotazione del vostro stepper

const int BY = 1;             // 1 uso di BY - 0 uso di altro motore passo passo

// inizializzazione della libreria Stepper
Stepper myStepper(stepPerGiro, 11, 9, 10, 8);

int contatorePassi = 0;         // numero di step raggiunto
int moduloStep = 512;           // modulo rotazione stepper da 1 a numero massimo di passi
// se moduloStep = 1 diventa un contatore di step
int conta = 0;                  // conteggio fase

int ledPin = 13;                 // led rilevamento fase

void setup() {
  // inizializzazione porta seriale
  Serial.begin(9600);
  // imposta la velocita' di rotazione a 20 rpm
  myStepper.setSpeed(70);

  pinMode(ledPin, OUTPUT);
}

void loop() {
  // passi di rotazione
  myStepper.step(moduloStep);
  Serial.print(conta);
  Serial.print(" - ");
  Serial.print("passo rotazione ");
  Serial.print("modulo ");
  Serial.print(moduloStep);
  Serial.print(" ");
  Serial.print("passo: ");
  Serial.println(contatorePassi);
  digitalWrite(ledPin, HIGH);
  contatore();
  delay(500);
  digitalWrite(ledPin, LOW);
}

// funzione che conta i passi in funzione dello stepper utilizzato
void contatore() {
  conta++;
  contatorePassi = contatorePassi + moduloStep;
  switch (BY) {
    case 0:
      if (contatorePassi &amp;gt; stepPerGiro) {
        contatorePassi = 0;
        conta = 0;
      }
      break;
    case 1:
      if (contatorePassi &amp;gt; stepPerGiro * 4) {
        contatorePassi = 0;
        conta = 0;
      }
      break;
  }
}

Esempio 3

Realizzare uno sketch che permetta di far compiere in modo continuo un giro completo in senso orario ed uno in senso antiorario.

Svolgere l’esercizio sia con lo stepper 28BY J-48 che con il microstepper del CDROM

Noterete che per il microstepper del CDROM il carrello giunto alla fine slitta, in quanto 200 step per rotazione sono troppi. Valutare (in modo empirico) quanti stemp sono necessari affinche il carrello non slitti e modificare lo sketch di conseguenza.

Variante 1

Fare in modo che al completamento di un giro venga acceso un diodo LED

Variante 2

Utilizzando il microstepper fare in modo che il verso di spostamento della slitta sia segnalato da due LED ed i LED siano accesi in modo alternato in funzione del verso di scorrimento della slitta

    • Direzione 1 – LED rosso acceso, LED verde spendo
    • Direzione 2 – LED rosso spento, LED verde acceso
// Prof. Michele Maffucci
// 20.10.2020

// rotazione oraria e antioraria sequenziale

/*
in1 L293D - pin 12 Arduino
in1 L293D - pin 11 Arduino
in1 L293D - pin 10 Arduino
in1 L293D - pin 9 Arduino
*/

// 200 per stepper 17PM-M041-P1 - 12 V - 1,8 gradi per step

// 200 per microstepper CDROM - 5 V - 1,8 gradi per step
// per evitare che slitti sul supporto impostare 170 come limite massimo

// 512 per stepper 28BY J-48 - 5 V - 5,525 gradi per step
// per questo stepper il valore passato non e' step/giro
// per un giro completo 2048 step. 512 equivale ad 1/4 di giro

#include <Stepper.h>

const int stepPerGiro = 170;  // adattare al passo di rotazione del vostro stepper

// inizializzazione della libreria Stepper
Stepper myStepper(stepPerGiro, 12, 11, 10, 9);

void setup() {
  // inizializzazione porta seriale
  Serial.begin(9600);
  // imposta la velocita' di rotazione a 60 rpm:
  myStepper.setSpeed(60);
}

void loop() {
  // un giro completo in senso orario
  Serial.println("antiorario");
  myStepper.step(stepPerGiro);
  delay(500);

  // un giro completo in senso antiorario
  Serial.println("orario");
  myStepper.step(-stepPerGiro);
  delay(500);
}

Esempio 4

Realizzare uno sketch che permetta di far cambiare il senso di rotazione alla pressione di un pulsante.
Rilevare la pressione del pulsante con l’accensione del LED collegato al pin 13.
Si colleghi il pulsante al pin 2 di Arduino, secondo quanto specificato nello schema di seguito. Utilizzare per questo esercizio il microstepper.

// Prof. Michele Maffucci
// 20.10.2020

// Cambio direzione rotazione alla pressione di un pulsante

// il programma puo' pilotare stepper unipolari o bipolari
// il numero di passi  fissato nella variabile moduloStep
// ad ogni passo viene rilevato anche da un LED

// 200 per stepper 17PM-M041-P1 - 12 V - 1,8 gradi per step

// 200 per microstepper CDROM - 5 V - 1,8 gradi per step
// per evitare che slitti sul supporto impostare 170 come limite massimo

// 512 per stepper 28BY J-48 - 5 V - 5,525 gradi per step
// per questo stepper il valore passato non e' step/giro
// per un giro completo 2048 step. 512 equivale ad 1/4 di giro


#include <Stepper.h>

const int stepPerGiro = 200;  // adattare al passo di rotazione del vostro stepper

// inizializzazione della libreria Stepper
Stepper myStepper(stepPerGiro, 12, 11, 10, 9);

int contatorePassi = 0;         // numero di step raggiunto
int moduloStep = 10;            // modulo rotazione stepper da 1 a numero massimo di passi
                                // se moduloStep = 1 diventa un contatore di step

int moduloStepCorrente;         // variabile in cui memorizzare la direzione di rotazione corrente
                                // alla pressione del pulsante


const int pinPulsante = 2;      // pin a cui e' collegato il pulsante
const int ledPin = 13;          // pin a cui e' collegato il LED

int statoPulsante = 0;           // stato corrente del pulsante


void setup() {

  // inizializzazione pin a cui e' collegato il pulsante
  pinMode(pinPulsante, INPUT);

  // inizializzazione pin a cui e' collegato il LED
  pinMode(ledPin, OUTPUT);

  // imposta la velocita' di rotazione a 20 rpm
  myStepper.setSpeed(20);
}

void loop() {

  statoPulsante = digitalRead(pinPulsante);
  if (statoPulsante == HIGH) {
    digitalWrite(ledPin, HIGH);
    moduloStepCorrente = -moduloStep;
    myStepper.step(moduloStepCorrente);
  }
  else {
    digitalWrite(ledPin, LOW);
    myStepper.step(moduloStep);
  }
}

Utilizzare il sensore di distanza VL530X con Arduino

Ho parlato di questo sensore di distanza quando qualche settimana fa mostrai come utilizzarlo con M5Stack mini: 5 min al giorno di sperimentazione con M5Stack mini – Sensore TOF seguendo il link trovate tutte le indicazioni sul principio di funzionamento ed una serie di dati tecnici che potrebbero essere utili per il vostro studio. Riprendo alcune parti del mio post in modo che le vostre sperimentazioni possano essere più agevoli:

…un sensore ToF (Time of Flight: tempo di volo) VL53L0X effettua un conteggio del tempo totale di volo dei fotoni dal momento in cui vengono emessi da un piccolissimo raggio laser al momento in cui viene rilevata la riflessione su un fotodiodo collocato sul sensore.
In generale il tempo di volo (spesso indicato con TOF, dall’inglese Time Of Flight) indica la misura del tempo impiegato da un oggetto, una particella o un’onda (elettromagnetica, acustica, elettromagnetica o di altro tipo) per percorrere una certa distanza in un mezzo determinato….
Il sensore ToF di cui dispongo è un VL53L0X che incorporato un emettitore Laser VCSEL ed un fotodiodiodo ricevitore SPAD. Il laser è in grado di emettere impulsi a 940 nm (prossimi al campo infrarosso). Il fotodiodiodo SPAD è estremamente sensibile al singolo fotone e grazie alla schermatura di cui è costituito riceve soltanto la lunghezza d’onda di 940 nm del laser.

Seguendo il link potete prelevare il datasheet del VL530X prodotto da ST.

Il sensore utilizzato con M5Stack mini è dotato di connettore GROVE, mentre la versione utilizzata per questo tutorial è una scheda breakout che potrete facilmente collegare ad Arduino.

Acquistai una serie di questi sensori per realizzare un sistema di rilevazione ostacoli di tipo laser per i kit robotici che faccio realizzare ai miei studenti, EduRobot e DotBot, ma poiché la situazione didattica di questi mesi è mutata drasticamente a causa dell’emergenza sanitaria, ho deciso di riutilizzare i sensori per un progetto semplice che iniziai qualche settimana fa: un bracciale per valutare la distanza tra le persone, sistema che per altro in diversi ambiti mi è stato chiesto di sviluppare.

  • Vin alimentazione del sensore, 3-5V. Collegate +5V di Arduino.
  • GND da connettere al GND di Arduino
  • SCL ed SDA dovranno essere connessi ai corrispondenti pin della scheda Arduino utilizzata, nel caso di un Arduino UNO R3 A5: SCL e A4: SDA

Installare la libreria Adafruit_VL53L0X Library Sketch > Include Library > Manage Libraries…:

Cercare la libreria Adafruit VL53L0X  all’interno del motore di ricerca e procedere con l’installazione:

Aprire da File->Examples->Adafruit_VL53L0X->vl53l0x ed effettuare l’upload sulla scheda Arduino

Di seguito trovate gli sketch di esempio in cui ho effettuato alcune traduzioni:

#include "Adafruit_VL53L0X.h"

Adafruit_VL53L0X lox = Adafruit_VL53L0X();

void setup() {
  Serial.begin(115200);

  // per i dispositivi USB nativi, attende fino a quando la serial port non risulta disponibile
  while (! Serial) {
    delay(1);
  }

  Serial.println("Test Adafruit VL53L0X");
  if (!lox.begin()) {
    Serial.println(F("Impossibile avviare VL53L0X"));
    while(1);
  }
  // power
  Serial.println(F("VL53L0X API Esempio: misura distanza\n\n"));
}


void loop() {
  VL53L0X_RangingMeasurementData_t measure;

  Serial.print("Lettura misura... ");
  lox.rangingTest(&measure, false); // se si inserisce 'true' si avvia la stampa dei dati di debug!

  if (measure.RangeStatus != 4) {  // dati errati
    Serial.print("Distanza (mm): "); Serial.println(measure.RangeMilliMeter);
  } else {
    Serial.println(" fuori portata ");
  }

  delay(100);
}

Aprite serial monitor ed impostate la velocità a 115200 baud

Muovete la vostra mano sul sensore per leggere la misura della distanza tra mano e sensore, noterete che quando non viene rilevato nulla sulla serial monitor verrà visualizzato il messaggio: out of range (fuori portata).

Modificare l’indirizzo assegnato al sensore

Durante l’inizializzazione, invece di usare la funzione lox.begin(), usare lox.begin(0x30) per impostare l’indirizzo su 0x30 sul nuovo sensore aggiunto, oppure in qualsiasi parte dello sketch, quando deve essere usato il sensore usare la funzione lox.setAddress(0x30).

Connessione di più sensori

Nel caso abbiate la necessità di connettere più sensori bisognerà che ognuno di essi abbia un indirizzo I2C diverso. L’indirizzo predefinito per VL53L0X è 0x29 ma è possibile modificarlo via software. Gli indirizzi che possono essere utilizzati vanno da 0x30 a 0x3F.

  1. Reset di tutti i sensori impostando i pin XSHUT a LOW per un tempo di 10 millisecondi.
  2. Mantenere il sensore 1 attivo mantenendo a HIGH il pin XSHUT a HIGH.
  3. Disattivare tutti gli altri sensori impostando i loro pin XSHUT a LOW
  4. Inizializzare il sensore 1 con lox.begin(nuovo_i2c_address). Scegliere qualsiasi indirizzo tranne 0x29, dovete scegliere un indirizzo inferiore a 0x7F, scegliere un indirizzo nell’intervallo 0x30 a 0x3F.
  5. Mantenere attivo il sensore n. 1 e disattivate il sensore n. 2 impostando il pin XSHUT ad HIGH.
  6. Inizializzare il sensore n. 2 usando la funzione lox.begin (nuovo_i2c_address). Scegliere qualsiasi indirizzo tranne 0x29, l’indirizzo da scegliere dovrà rientrare nel range ammesso (0x30 a 0x3F).
  7. Ripetere l’operazione per ciascun sensore, attivarli e assegnare ad ognuno un indirizzo diverso.

Attenzione che l’assegnazione dell’indirizzo non è permanente, in mancanza di alimentazione viene persa memoria dell’indirizzo assegnato a tutti i sensori, quindi la procedura deve essere ripetuta ogni volta che viene alimentato il circuito.

Nel caso abbiate un solo sensore l’indirizzo assegnato in automatico di default nel momento che date alimentazione è sempre 0x29.

Se operate con due sensori, per assegnare automaticamente gli indirizzi utilizzare lo sketch presente negli esempi che troverete all’interno della cartella della libreria Adafruit_VL53L0X: vl53l0X_dual

#include "Adafruit_VL53L0X.h"

// indirizzi assegnati se sono presenti due sensori
#define LOX1_ADDRESS 0x30
#define LOX2_ADDRESS 0x31

// impostazione dei pin per lo spegnimento dei sensori
#define SHT_LOX1 7
#define SHT_LOX2 6

// creazioni degli oggetti vl53l0x
Adafruit_VL53L0X lox1 = Adafruit_VL53L0X();
Adafruit_VL53L0X lox2 = Adafruit_VL53L0X();

// per memorizzare le misure
VL53L0X_RangingMeasurementData_t measure1;
VL53L0X_RangingMeasurementData_t measure2;

/*
  - Il reset dei sensori viene fatto impostando tutti i pin XSHUT a LOW per 10 millisecondi 'delay(10)',
    successivamente, per uscire dal reset tutti gli XSHUT vemgono posti ad HIGH.
  - Mantiene il sensore 1 attivo mantenedo il pin XSHUT HIGH
  - Tutti gli altri sensori vengono disattivati ponendo a LOW i pin XSHUT
  - L'inizializzazione del sensore n.1 avviene con lox.begin(new_i2c_address). Scegliere qualsiasi diverso da 0x29 ed inferiore a 0x7F.
    Scegliere un valore compreso tra 0x30 a 0x3F.
  - Mantiene il sensore n.1 attivo e disattiva il sensore n.2 impostando il pin XSHUT ad HIGH.
  - L'inizializzazione del sensore n.2 avviene con lox.begin (new_i2c_address). Scegliere qualsiasi diverso da 0x29 o da qualsiasi indirizzo impostato per l'altro ensore.
 */
void setID() {
  // reset di tutti i sensori
  digitalWrite(SHT_LOX1, LOW);
  digitalWrite(SHT_LOX2, LOW);
  delay(10);
  // attivazione dei sensori
  digitalWrite(SHT_LOX1, HIGH);
  digitalWrite(SHT_LOX2, HIGH);
  delay(10);

  // attivazione di LOX1 e reset di LOX2
  digitalWrite(SHT_LOX1, HIGH);
  digitalWrite(SHT_LOX2, LOW);

  // inizializzazione di LOX1
  if(!lox1.begin(LOX1_ADDRESS)) {
    Serial.println(F("Errore nell'avvio del primo VL53L0X"));
    while(1);
  }
  delay(10);

  // attivazione di LOX2
  digitalWrite(SHT_LOX2, HIGH);
  delay(10);

  //inizializzazione di LOX2
  if(!lox2.begin(LOX2_ADDRESS)) {
    Serial.println(F("Errore nell'avvio del secondo VL53L0X"));
    while(1);
  }
}

void read_dual_sensors() {

  lox1.rangingTest(&measure1, false); // se si inserisce 'true' si avvia la stampa dei dati di debug!
  lox2.rangingTest(&measure2, false); // se si inserisce 'true' si avvia la stampa dei dati di debug!

  // stampa la lettura dei dati del primo sensore
  Serial.print("1: ");
  if(measure1.RangeStatus != 4) {     // se non si è fuori portata
    Serial.print(measure1.RangeMilliMeter);
  } else {
    Serial.print("Fuori portata");
  }

  Serial.print(" ");

  // stampa la lettura dei dati del secondo sensore
  Serial.print("2: ");
  if(measure2.RangeStatus != 4) {
    Serial.print(measure2.RangeMilliMeter);
  } else {
    Serial.print("Fuori portata");
  }

  Serial.println();
}

void setup() {
  Serial.begin(115200);

  // per i dispositivi USB nativi, attende fino a quando la serial port non risulta disponibile
  while (! Serial) { delay(1); }

  pinMode(SHT_LOX1, OUTPUT);
  pinMode(SHT_LOX2, OUTPUT);

  Serial.println("Inizio spegnimento...");

  digitalWrite(SHT_LOX1, LOW);
  digitalWrite(SHT_LOX2, LOW);

  Serial.println("Entrambi in modalità ripristino...(i pin sono a LOW)");


  Serial.println("Avvio...");
  setID();

}

void loop() {

  read_dual_sensors();
  delay(100);
}

Buon Making a tutti 🙂

 

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.

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

void loop() {
    // per ora nulla
}

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:

void setup() {
   char mio_sito[] = "Arduino";
   Serial.begin(9600);
   Serial.println(mio_sito);
}

void loop() {
    // per ora nulla
}

Manipolazione di Array di stringhe

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

void setup() {
   char mia_stringa[] = "Salve mondo"; // creazione della stringa
   Serial.begin(9600);
   // stampa della stringa sulla Serial Monitor
   Serial.println(mia_stringa);
   // cancellazione di una parte della stringa
   mia_stringa[5] = 0;
   Serial.println(mia_stringa);
   // (3) sostituzione dei caratteri
   mia_stringa[5] = ' ';  // sostituzione con il carattere spazio
   mia_stringa[6] = 'M';  // inserimento della nuova parola
   mia_stringa[7] = 'i';
   mia_stringa[8] = 'k';
   mia_stringa[9] = 'y';
   mia_stringa[10] = '!';
   mia_stringa[11] = 0; // terminatore di stringa
   Serial.println(mia_stringa);
}

void loop() {
    // per ora nulla
}

Risultato:

Salve mondo
Salve
Salve 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

void setup() {
   char mia_stringa[] = "Nel mezzo del cammin di nostra vita"; // creazione della stringa
   char mia_stringa_output[80]; // l’output verrà inserito in questa stringa
   int dimensione;
   Serial.begin(9600);

   // A. stampa la stringa
   Serial.println(mia_stringa);

   // B. lettura della lunghezza della stringa.
   // Nel conteggio viene escluso il carattere
   // terminatore
   dimensione = strlen(mia_stringa);
   Serial.print("La lunghezza della stringa è: ");
   Serial.println(dimensione);

   // C. prende la lunghezza dell’array incluso il carattere terminatore
   dimensione = sizeof(mia_stringa); // sizeof() non è una funzione specifica per gestire le stringhe
   Serial.print("Dimensione dell'array: ");
   Serial.println(dimensione);

   // D. copiare una stringa
   strcpy(mia_stringa_output, mia_stringa);
   Serial.println(mia_stringa_output);

   // E. aggiungere una stringa alla fine di un’altra (append)
   strcat(mia_stringa_output, " mi ritrovai per una selva oscura");
   Serial.println(mia_stringa_output);
   dimensione = strlen(mia_stringa_output);
   Serial.print("La lunghezza della stringa è: ");
   Serial.println(dimensione);
   dimensione = sizeof(mia_stringa_output);
   Serial.print("Dimensione dell'array mia_stringa_output[ ] è: ");
   Serial.println(dimensione);
}

void loop() {
    // per ora nulla
}

Risultato:

Nel mezzo del cammin di nostra vita
La lunghezza della stringa è: 35
Dimensione dell'array: 36
Nel mezzo del cammin di nostra vita
Nel mezzo del cammin di nostra vita mi ritrovai per una selva oscura
La lunghezza della stringa è: 68
Dimensione 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()

void setup() {
  Serial.begin(9600);
}

void loop() {

  // inserendo void nella chiamata di funzione
  // NON verrà chiamata la funzione miSentoFortunato()
  void miSentoFortunato();
}

void miSentoFortunato() {
  Serial.println("Oggi mi sento fortunato :-)");
}

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

void setup() {
  Serial.begin(9600);
}

void loop() {

  // eliminando void nella chiamata di funzione
  // verrà chiamata la funzione miSentoFortunato()
  miSentoFortunato();
}

void miSentoFortunato() {
  Serial.println("Oggi mi sento fortunato :-)");
}

Esercizio 1

Perché il codice che segue non restituisce errore?

void setup() {
  Serial.begin(9600);
}
void loop() {
  void miSentoFortunato();
}

Esercizio 2

Perché il codice che segue non restituisce errore?

void setup() {
  Serial.begin(9600);
}
void loop() {
  // :-)
}
void miSentoFortunato();

Esercizio 3

Perché il codice che segue restituisce errore?

void setup() {
  Serial.begin(9600);
}
void loop() {

  void miSentoFortunato() {
  // :-)
  }
}

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:

// laMiaCollezione è un array di interi che ha una dimensione di:
// (20000x16)/8=40kB
// superiore alla RAM disponibile in un Arduino UNO R3 che è di 32kB
// si faccia riferimento al seguente link:
// https://www.arduino.cc/en/tutorial/memory

int laMiaCollezione[20000];

void setup() {
  Serial.begin (9600);
  Serial.println ("Avvio programma");
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

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?