Archivi categoria: i miei allievi

Arduino: Sensore Piezoelettrico

Per effettuare una misura dinamica di pressione o allungamento o vibrazione di superfici è possibile utilizzare sensori che si basano su quello che viene comunemente chiamato “effetto piezoelettrico”, la parola “piezoelettrico” deriva dalla parola greca “piezein” che significa premere. La piezoelettricità è la proprietà di alcuni cristalli, che se sottoposti all’azione di una forza esterna esterna, provoca in essi una migrazione di cariche elettriche. Con l’utilizzo di un apposito circuito elettronico, la migrazione di cariche elettriche può essere tradotta una differenza di potenziale.

L’effetto piezoelettrico è un effetto reversibile, ciò vuol dire che una volta che abbiamo applicato una variazione meccanica sul cristallo si ottiene una variazione di cariche e in maniera analoga quando viene fornita una carica elettrica al sensore, questo si allungherà o si comprimerà.

Quando applichiamo al cristallo una forza meccanica le cariche elettriche si spostano accumulandosi sulle facce opposte del cristallo:

I materiali piezoelettrici utilizzati possono essere naturali o di sintesi dopo polarizzazione:

Materiali naturali

  • Quarzo cristallino (SiO2)
  • sale di Rochelle o sale di Seignette (tartrato di Sodio e Potassio)
  • Tormalina

Materiali di sintesi

  • Piezoceramici
    • Titanio Zirconato di Piombo (PZT)
    • Titanio di Bario (BaTiO3)

L’effetto piezoelettrico può essere sfruttato per:

  • generare energia elettrica
  • in campo medicale: ecografi
  • sensori di vibrazione per allarmi
  • sensori di impatto
  • sonar (in campo militare)
  • viene utilizzato negli accendini
  • per gli orologi
  • per rilevare variazioni meccaniche
  • produrre suoni, come nelle chitarre elettriche
  • messa a fuoco di strumenti ottici
  • sensori in capo ambientale per il radio tracking
  • ecc…

Esempio tipico è quello dei cicalini piezoelettrici, conosciuti anche come buzzer, che vengono utilizzate in molte delle sperimentazioni con Arduino, infatti sottoponendo a tensione elettrica e ad una determinata frequenza il materiale piezoelettrico all’interno del buzzer, viene posta in oscillazione e questo muovendo l’aria, dall’apposita apertura collocata sul buzzer, permette di percepire il suono.

Nelle sperimentazioni con Arduino è tipico usare sensori piezoelettrici economici che generalmente hanno forma circolare. Questi sensori non hanno gradi di precisioni elevati e sono utilizzati prevalentemente per rilevare vibrazioni. Per capire come sono realizzati questi sensori immaginate ad un sandwich in in cui l’elemento piezoelettrico, costituito da piombo-zirconato è inserito tra due piastre metalliche. Il loro utilizzo in circuito con microcontrollori è particolarmente semplice anche perché funzionano con le tensioni e le correnti che si hanno direttamente dai pin di uscita di questi circuiti elettronici.

Nel caso abbiate necessità di consultare i dati tecnici di diverse tipologie di dispositivi piezoelettrici vi rimando al questi due datasheet:

Realizziamo ora un circuito che ci permette, mediante il sensore piezoelettrico e Arduino, la rilevazione di vibrazioni e urti. Colleghiamo il sensore sulla breadboard e dalla breadboard ad Arduino, in questo modo eviteremo di far vibrare il sensore quando tocchiamo Arduino per effettuare i collegamenti.

Colleghiamo il filo rosso del sensore ad un ingresso analogico di Arduino, ad esempio A0 mentre il filo nero lo colleghiamo. In parallelo al sensore bisognerà inserire una resistore con resistenza da 1MOhm, ciò è necessario perché un piezo è un componente con caratteristiche capacitive e la resistenza in parallelo permette di assorbire le cariche che vengono accumulate sulla capacità.

Il circuito proposto è estremamente semplice ed è riportato di seguito:

Esempio 1

/*
 * Prof. Maffucci Michele
 * data: 22.01.2021
 * Lettura sensore piezoelettrico
 * 
 * Esempio 1: stampa dei valori sulla serial monitor
 * e visualizzazione del grafico sulla serial Plot
 * 
 * Si ricorda che la Serial Plotter e la Serial Monitor
 * non possono essere usati contemporaneamente
 * 
 */


void setup(){
   // Inizializzazione della serial monitor
  Serial.begin(9600);
}

void loop(){
  // val contiene il valore letto dal sensore  
  int val = analogRead(A0);
  Serial.println(val);
  delay(20);
}

Se si selezione Serial Plotter dal menù Tools:

si avrà la visualizzazione del grafico degli urti provocati sul sensore:

Come si può notare i valori sull’asse delle ordinate è compreso tra 0 e 1023.

Esempio 2

Arduino, come già spiegato nelle mie slide: Alfabeto di Arduino – Lezione 4, non è in grado di leggere direttamente valori di tensione, bisognerà utilizzare il convertitore analogico/digitale (ADC) integrato in esso per convertire una tensione elettrica in un valore numerico. L’ADC di Arduino è a 10 bit, quindi potrà mappare valori compresi tra 0V e 5V in valori numerici compresi tra 0 e 1023. (1024 valori), pertanto si avrà una risoluzione di 5/1024 = 4,88 mV.

Per ottenere la tensione elettrica partendo dai valori restituiti dall’ADC, sarà sufficiente prendere il valore restituito dall’analogRead() e moltiplicarlo per la tensione unitaria corrispondente ad una unità 5/1024 = 4,88 mV. Traduciamo questo in codice per visualizzare la tensione elettrica sulla Serial Plotter.

/*
 * Prof. Maffucci Michele
 * data: 22.01.2021
 * Lettura sensore piezoelettrico
 * 
 * Esempio 2: stampa dei valori della tensione elettrica
 * sulla Serial Monitor e Serial Plotter
 * resitituita dal sensore nell'intervallo 0V - 5V
 * 
 * Si ricorda che la Serial Plotter e la Serial Monitor
 * non possono essere usati contemporaneamente
 * 
 */

void setup(){
   // Inizializzazione della serial monitor
  Serial.begin(9600);
}

void loop(){
  // val contiene il valore letto dal sensore  
  int val = analogRead(A0);
  // tensione contiene il valore di tensione elettrica
  float tensione = val *(5.0/1024);
  Serial.println(tensione);
  delay(20);
}

Esercizio 1

Realizziamo un interruttore on/off, possiamo utilizzare gli esempi già disponibili all’interno dell’IDE di Arduino e che in altre occasioni ho utilizzato su questo sito.  L’idea è quella di realizzare un sistema antirimbalzo software (debounce) e attivare l’on o l’off su un LED se l’intensità della forza meccanica impressa è superiore ad un valore di soglia che impostiamo nel codice.

Esercizio 2

Collegare 10 LED ad Arduino ed utilizzare un sensore piezoelettrico per accendere una quantità di LED proporzionale alla forza impressa sul sensore.

Esercizio 3

Realizziamo un dimmer, un sistema in grado di regolare l’intensità luminosa di un LED. La regolazione dell’intensità luminosa sarà fatto toccando il sensore piezoelettrico. Il doppio tocco veloce (in un intervallo di tempo prestabilito da voi) inverte la modalità di variazione dell’intensità luminosa (aumentare/diminuire), nel caso non si riesca a realizzare lo sketch con il doppio tocco veloce, utilizzare un pulsante esterno per invertire la modalità intensità luminosa prodotta.

Esercizio 4

Utilizzando la stessa logica di funzionamento dell’esercizio 2, utilizzare ora una striscia LED per misurare l’intensità della forza meccanica con cui sollecitiamo il sensore piezoelettrico.

Buon Coding a tutti 🙂

Lezione 4 – Arduino GamePad – Disegnare caratteri speciali su LCD1602 Keypad Shield della Keyestudio

In riferimento a quanto esposto a lezione questa mattina durante la lezione a distanza, aggiungo alcune indicazioni per disegnare icone personalizzate sul display: pacman, alieno, fantasmi, ecc… La modalità di realizzazione icone è la medesima di quella spiegata nella lezione svolta alcuni mesi fa: Disegnare caratteri personalizzati con Arduino per un LCD 16×2 in cui veniva utilizzato un display I2C.

Le modifiche per l’utilizzo dell’LCD1602 Keypad Shield della Keyestudio sono minime e di seguito indico alcuni esempi.

E’ importante, per inserire nuove icone sul display, studiare la lezione precedente in cui descrivo come realizzare le icone.

Di seguito tre esempi utili per sviluppare il vostro progetto di PCTO.

Esempio 1

In questo esempio mostro come visualizzare sulla prima riga del display una stringa di presentazione e sulla seconda riga 8 icone

/* 
 *  Prof. Michele Maffucci
 *  Crezione di caratteri personali
 *  Utilizzo di un display LCD1602 Keypad Shield della Keyestudio
 *  Data: 19.03.2021
*/

// inclusione della libreria LiquidCrystal.h
#include <LiquidCrystal.h>

// inizializza la libreria con i numeri dei pin dell'interfaccia
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// caratteri personalizzati

byte lucchettoChiuso[8] = {
  0b01110,
  0b10001,
  0b10001,
  0b10001,
  0b11111,
  0b11011,
  0b11011,
  0b11111
};

byte lucchettoAperto[8] = {
  0b01110,
  0b10000,
  0b10000,
  0b10000,
  0b11111,
  0b11011,
  0b11011,
  0b11111
};

byte Altoparlante[8] = {
  0b00001,
  0b00011,
  0b01111,
  0b01111,
  0b01111,
  0b00011,
  0b00001,
  0b00000
};

byte batteriaMezza[8] = {
  0b01110,
  0b11011,
  0b10001,
  0b10001,
  0b10001,
  0b11111,
  0b11111,
  0b11111
};

byte alieno[8] = {
  0b10001,
  0b01010,
  0b11111,
  0b10101,
  0b11111,
  0b11111,
  0b01010,
  0b11011
};


byte pacmanBoccaChiusa[8] = {
  0b01110,
  0b11101,
  0b11111,
  0b11111,
  0b11000,
  0b11111,
  0b11111,
  0b01110
};

byte pacmanBoccaAperta[8] = {
  0b01110,
  0b11101,
  0b11111,
  0b11100,
  0b11000,
  0b11000,
  0b11111,
  0b01110
};

byte fantasmino[8] = {
  0b01110,
  0b11111,
  0b10101,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b10101
};

void setup()
{
 // impostazione del numero di colonne e righe del display
  lcd.begin(16, 2);

  // creazione nuovi caratteri
  lcd.createChar(0, lucchettoChiuso);
  lcd.createChar(1, lucchettoAperto);
  lcd.createChar(2, Altoparlante);
  lcd.createChar(3, batteriaMezza);
  lcd.createChar(4, alieno);
  lcd.createChar(5, pacmanBoccaChiusa);
  lcd.createChar(6, pacmanBoccaAperta);
  lcd.createChar(7, fantasmino);
  
  // Cancella il display
  lcd.clear();

  // Stampa la stringa
  lcd.print("PCTO a.s. 20/21");

}

void loop()
{
  lcd.setCursor(0, 1);
  lcd.write(byte(0));

  lcd.setCursor(2, 1);
  lcd.write(byte(1));

  lcd.setCursor(4, 1);
  lcd.write(byte(2));

  lcd.setCursor(6, 1);
  lcd.write(byte(3));

  lcd.setCursor(8, 1);
  lcd.write(byte(4));

  lcd.setCursor(10, 1);
  lcd.write(byte(5));

  lcd.setCursor(12, 1);
  lcd.write(byte(6));

  lcd.setCursor(14, 1);
  lcd.write(byte(7));
}

Esempio 2

In questo secondo esempio mostriamo come visualizzare l’icona del Pacman che va avanti e indietro sulla prima riga del display

/* 
 *  Prof. Michele Maffucci
 *  Crezione di caratteri personali
 *  Utilizzo di un display LCD1602 Keypad Shield della Keyestudio
 *  Data: 19.03.2021
 *  
 *  Movimento verso destra e sinistra di una icona (alieno)
*/

// inclusione della libreria LiquidCrystal.h
#include <LiquidCrystal.h>

// inizializza la libreria con i numeri dei pin dell'interfaccia
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Velocità con cui viene stampato il l'icona  
int velocita = 300;

// caratteri personalizzati

byte alieno[8] = {
  0b10001,
  0b01010,
  0b11111,
  0b10101,
  0b11111,
  0b11111,
  0b01010,
  0b11011
};

void setup()
{
 // impostazione del numero di colonne e righe del display
  lcd.begin(16, 2);

  // creazione nuovi caratteri
  lcd.createChar(4, alieno);

  // visualizzazione sul display del nome
  // dell'attività
  
  // Cancella il display
  lcd.clear();

  // Stampa la stringa
  lcd.print("PCTO a.s. 20/21");
  delay(1000);

  // Cancella il display
  lcd.clear();
}

void loop()
{
   // Movimento verso destra del carattere
  for (int contatorePosizioneColonna = 0; contatorePosizioneColonna < 16; contatorePosizioneColonna++) {
    // Cancella il display
    lcd.clear();
    // Spostamento di una posizione verso destra del cursore
    lcd.setCursor(contatorePosizioneColonna, 0);
    // Stampa del icona: alieno
    lcd.write(byte(4));
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Movimento verso sinistra del carattere
  for (int contatorePosizioneColonna = 16; contatorePosizioneColonna > 0; contatorePosizioneColonna--) {
    // Cancella il display
    lcd.clear();
    // Spostamento di una posizione verso sinistra del cursore
    lcd.setCursor(contatorePosizioneColonna, 0);
    // Stampa del icona: alieno
    lcd.write(byte(4));
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }
}

Esempio 3

Realizzare le medesime funzionalità dell’esercizio precedente in cui l’icona del Pacman nell’avanzamento apre e chiude la bocca.
In questo esempio il Pacman quando torna indietro non si gira.


/*
    Prof. Michele Maffucci
    Crezione di caratteri personali
    Utilizzo di un display LCD1602 Keypad Shield della Keyestudio
    Data: 19.03.2021

    Movimento verso destra e sinistra di una icona (Pacman)
    Durante il movimento il Pacman apre e chiude la bocca
*/

// inclusione della libreria LiquidCrystal.h
#include <LiquidCrystal.h>

// inizializza la libreria con i numeri dei pin dell'interfaccia
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Velocità con cui viene stampato il l'icona
int velocita = 300;

// caratteri personalizzati

byte pacmanBoccaChiusa[8] = {
  0b01110,
  0b11101,
  0b11111,
  0b11111,
  0b11000,
  0b11111,
  0b11111,
  0b01110
};

byte pacmanBoccaAperta[8] = {
  0b01110,
  0b11101,
  0b11111,
  0b11100,
  0b11000,
  0b11000,
  0b11111,
  0b01110
};

void setup()
{
  // impostazione del numero di colonne e righe del display
  lcd.begin(16, 2);

  // creazione nuovi caratteri
  lcd.createChar(0, pacmanBoccaChiusa);
  lcd.createChar(1, pacmanBoccaAperta);

  // Cancella il display
  lcd.clear();

  // Stampa la stringa
  lcd.print("PCTO a.s. 20/21");
}

void loop()
{

  // Movimento verso destra del carattere
  for (int contatorePosizioneColonna = 0; contatorePosizioneColonna < 16; contatorePosizioneColonna++) {
    // Cancella il display
    lcd.clear();
    // Spostamento di una posizione verso destra del cursore
    lcd.setCursor(contatorePosizioneColonna, 0);
    if (contatorePosizioneColonna % 2 == 0) {
      // Stampa del icona: Pacman bocca chiusa
      lcd.write(byte(0));
    }
    else
    {
      // Stampa del icona: Pacman bocca aperta
      lcd.write(byte(1));
    }
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Movimento verso sinistra del carattere
  for (int contatorePosizioneColonna = 16; contatorePosizioneColonna > 0; contatorePosizioneColonna--) {
    // Cancella il display
    lcd.clear();
    // Spostamento di una posizione verso sinistra del cursore
    lcd.setCursor(contatorePosizioneColonna, 0);
    if (contatorePosizioneColonna % 2 == 0) {
      // Stampa del icona: Pacman bocca chiusa
      lcd.write(byte(0));
    }
    else
    {
      // Stampa del icona: Pacman bocca aperta
      lcd.write(byte(1));
    }
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }
}

Esercizio 1

Eseguire le stesse funzionalità dell’esempio 3 in cui però il Pacman rivolga il viso nella stessa direzione del movimento.

Esercizio 2

Movimentare il Pacman con i pulsanti RIGHT e LEFT, rivolgendo sempre il viso nella giusta posizione di movimento.

Buon Coding a tutti 🙂

Lezione 3 – Arduino GamePad – LCD1602 Keypad Shield della Keyestudio

Terza lezione in cui aggiungo alcuni esempi che suggeriscono alcune azioni di gioco che gli allievi del gruppo di lavoro dell’attività di PCTO: Arduino GamePad potranno sfruttare per la realizzazione del loro.

Esempio 1

Controllo dello spostamento del carattere:

  • destra/sinistra mediante i pulsanti: RIGHT e LEFT sul display
  • su/giù mediante i pulsanti UP/DOWN

Per realizzare queste azioni riprendiamo quanto realizzato nella lezione precedente aggiungendo all’ultimo sketch il controllo della pressione del pulsante UP o DOWN in cui viene controllato se l’analogRead sul A0 restituisce un valore comprese tra:

Controllo UP
(val >= 50 && val <= 150)

Controllo DOWN
(val >= 150 && val <= 300)

Lo spostamento del carattere dalla prima alla seconda riga deve essere fatta cancellando prima l’intero schermo, poi posizionando il carattere con il metodo setCursor() a cui si passa la variabile globale “contatorePosizioneColonna” che viene modificata di volta in volta alla pressione dei pulsanti RIGHT e LEFT. La selezione della riga avviene passando il valore 0 che identifica la prima riga e il valore 1 per la seconda riga.

/*
   Prof. Michele Maffucci
   Utilizzo dell'LCD Keypad Shield della Keystudio
   Data: 14.03.2021

   Controllo dello spostamento del carattere:
   - destra/sinistra mediante i pulsanti: RIGHT e LEFT sul display
   - su/giù mediante i pulsanti UP/DOWN
*/

// inclusione della libreria LiquidCrystal.h
#include <LiquidCrystal.h>

// inizializza la libreria con i numeri dei pin dell'interfaccia
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Colonna in cui si trova il carattere
int contatorePosizioneColonna = 0;

// Valore restituito dall'analogRead su A0
int val;

// Velocità con cui viene stampato il carattere  
int velocita = 200;

void setup() {
  // impostazione del numero di colonne e righe del display
  lcd.begin(16, 2);

  // Carattere stampato nella prima colonna e prima riga (0,0)
  lcd.print("*");
}

void loop() {

  // Memorizza in val il valore presente su A
  int val = analogRead(A0);

  // Alla pressione del pulsante UP sul display il carattere si sposta sulla prima riga
   if (val >= 50 && val <= 150) {
    // Cancella il display
    lcd.clear();
    // Spostamento sulla prima riga nella colonna corrente
    lcd.setCursor(contatorePosizioneColonna, 0);
    // Stampa del carattere: *
    lcd.print("*");
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Alla pressione del pulsante DOWN sul display il carattere si sposta sulla seconda riga
  if (val >= 150 && val <= 300) {
    // Cancella il display
    lcd.clear();
    // Spostamento sulla seconda riga nella colonna corrente
    lcd.setCursor(contatorePosizioneColonna, 1);
    // Stampa del carattere: *
    lcd.print("*");
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Premendo il pulsante RIGHT sul display, il carattere si sposta di una posizione
  // a destra fino a quando non si raggiunge l'ultima colonna a destra.
  // Premendo ancora il pulsante RIGHT non si ha l'avanzamento del carattere.
  if ((val >= 0 && val <= 50) && contatorePosizioneColonna < 15) {
    lcd.scrollDisplayRight();
    delay(velocita);
    contatorePosizioneColonna++;
  }

  // Premendo il pulsante LEFT sul display, il carattere si sposta di una posizione
  // a sinistra fino a quando non si raggiunge l'ultima colonna a sinistra.
  // Premendo ancora il pulsante LEFT non si ha l'avanzamento del carattere.
  if ((val >= 300 && val <= 500) && contatorePosizioneColonna > 0) {
    lcd.scrollDisplayLeft();
    delay(velocita);
    contatorePosizioneColonna--;
  }
}

Esempio 2

In questo secondo esempio viene aggiunta una semplice matrice di cinque “X” che identificano degli ostacoli che devono essere superati. Nell’esempio esposto però gli ostacoli possono essere superati, lascio quindi a voi la realizzazione dello sketch che consente il blocco dell’avanzamento nel caso in cui ci si scontra con l’ostacolo.

/*
   Prof. Michele Maffucci
   Utilizzo dell'LCD Keypad Shield della Keystudio
   Data: 14.03.2021

   Controllo dello spostamento del carattere *:
   - destra/sinistra mediante i pulsanti: RIGHT e LEFT sul display
   - su/giù mediante i pulsanti UP/DOWN

   Stampa di una serie di X che rappresentano gli ostacoli
   
*/

// inclusione della libreria LiquidCrystal.h
#include <LiquidCrystal.h>

// inizializza la libreria con i numeri dei pin dell'interfaccia
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Colonna in cui si trova il carattere
int contatorePosizioneColonna = 0;

// Valore restituito dall'analogRead su A0
int val;

// Velocità con cui viene stampato il carattere
int velocita = 200;

// Riga in cui si trova il carattere *
int riga = 0;

void setup() {
  // impostazione del numero di colonne e righe del display
  lcd.begin(16, 2);

  // Carattere stampato nella prima colonna e prima riga (0,0)
  lcd.print("*");
}

void loop() {

  // Memorizza in val il valore presente su A
  int val = analogRead(A0);

  stampaOstacoli();

  // Alla pressione del pulsante UP sul display il carattere si sposta sulla prima riga
  if (val >= 50 && val <= 150) {
    // Cancella il display
    lcd.clear();
    stampaOstacoli();
    // Spostamento sulla prima riga nella colonna corrente
    lcd.setCursor(contatorePosizioneColonna, 0);
    riga = 0;
    // Stampa del carattere: *
    lcd.print("*");
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Alla pressione del pulsante DOWN sul display il carattere si sposta sulla seconda riga
  if (val >= 150 && val <= 300) {
    // Cancella il display
    lcd.clear();
    stampaOstacoli();
    // Spostamento sulla seconda riga nella colonna corrente
    lcd.setCursor(contatorePosizioneColonna, 1);
    riga = 1;
    // Stampa del carattere: *
    lcd.print("*");
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Premendo il pulsante RIGHT sul display, il carattere si sposta di una posizione
  // a destra fino a quando non si raggiunge l'ultima colonna a destra.
  // Premendo ancora il pulsante RIGHT non si ha l'avanzamento del carattere.
  if ((val >= 0 && val <= 50) && contatorePosizioneColonna < 15) {
    // Cancella il display
    lcd.clear();
    contatorePosizioneColonna++;
    // Spostamento di una posizione verso destra del cursore
    lcd.setCursor(contatorePosizioneColonna, riga);
    // Stampa del carattere: *
    lcd.print("*");
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }

  // Premendo il pulsante LEFT sul display, il carattere si sposta di una posizione
  // a sinistra fino a quando non si raggiunge l'ultima colonna a sinistra.
  // Premendo ancora il pulsante LEFT non si ha l'avanzamento del carattere.
  if ((val >= 300 && val <= 500) && contatorePosizioneColonna > 0) {
    // Cancella il display
    lcd.clear();
    contatorePosizioneColonna--;
    lcd.setCursor(contatorePosizioneColonna, riga);
    // Stampa del carattere: *
    lcd.print("*");
    // Attesa di un'istante per percepire il movimento del carattere
    delay(velocita);
  }
}

void stampaOstacoli() {
  lcd.setCursor(2, 0);
  lcd.print("X");
  lcd.setCursor(5, 1);
  lcd.print("X");
  lcd.setCursor(7, 1);
  lcd.print("X");
  lcd.setCursor(9, 0);
  lcd.print("X");
  lcd.setCursor(12, 0);
  lcd.print("X");
}

Esercizio 1

Utilizzando l’esempio 2 esposto sopra, aggiungere la funzionalità che blocca l’avanzamento del carattere “*” nel caso in cui ci si scontra con l’ostacolo.

Esercizio 2

Definire tre matrici di ostacoli diversi e fare in modo che ad ogni avvio di Arduino la scelta della matrice venga fatta in modo casuale.

Esercizio 3

Aggiungere la funzionalità che permette di modificare la matrice degli ostacoli se si raggiunge l’ultima colonna a destra.

Esercizio 4

Definire tre scenari diversi costituiti da tre matrici ostacoli diversi. La navigazione può avvenire solamente all’interno di questi tre scenari. La partenza del carattere “*” avviene dalla colonna 0 del secondo scenario, il movimento dovrà essere il seguente:

  • partendo dalla colonna 0 del secondo scenario, la pressione del pulsante LEFT porta allo scenario 1 e il carattere “*” viene posizionato nella colonna 15 del display;
  • se si è nel secondo scenario in colonna 15, la pressione del pulsante RIGHT conduce alla colonna 0 del  3′ scenario;
  • la pressione del pulsante LEFT in colonna 0 del primo scenario non permette nessun cambiamento di scenario
  • la pressione del pulsante RIGHT in colonna 15 del terzo scenario non permette nessun cambiamento di scenario

Buon Coding a tutti 🙂

Arduino – Dividere uno sketch in più file

Domanda dello studente: “Prof. il progetto per controllare il robot sta diventando lunghissimo e anche se commento ogni parte e cerco di creare funzioni specifiche per ogni azione, leggere modificare il codice diventa un lavoro che porta via molto tempo, come posso fare?”

Possiamo agire in due modi:

  1. creazione di librerie specifiche in cui inserire il codice che deve essere richiamato dallo sketch principale, leggere la parte finale della lezione: “Arduino – lezione 08: display a 7 segmenti e creazione di librerie” in cui mostro come fare questa operazione;
  2. utilizzare la funzione specifica che trovate nell’IDE di Arduino “Nuova scheda” o “New Tab” in inglese a destra della finestra.

Supponiamo di aver creato un nuovo sketch vuoto a cui assegniamo un nome, nel mio esempio “ilMioBlink.ino”, come sapete il file verrà inserito in una cartella che avrà lo stesso nome dello sketch principale:

Selezionare “New Tab” a destra della finestra dell’IDE:

noterete che la selezione di questa voce di menù aprirà in basso alla finestra dell’IDE un’area evidenziata in giallo in cui dovrete inserire il nome del file.

Inserirò a titolo di esempio il nome: “impostazioni”.
Verrà immediatamente aggiunta una nuova tab:

 

Appena salvate il progetto, il simbolo a fianco del nome scompare ed il nuovo file “impostazioni” verrà automaticamente inserito nella stessa cartella dello sketch principale.
Il salvataggio del progetto salva automaticamente tutte le variazioni fatte nei file allegati al progetto.

Si tenga presente che la lettura (esecuzione) dei file aggiunti avverrà in ordine alfabetico, pertanto se vengono usate variabili globali all’interno di uno dei file, queste potranno essere viste solamente dai file che seguono nell’ordine alfabetico, pertanto buona regola è inserire tutte le variabili globali nel progetto principale (quello che reca lo stesso nome della cartella di progetto).

Quindi si potrebbe pensare di strutturare un lungo progetto in questo modo:

  1. tutte le variabili globali nello sketch principale;
  2. impostazioni di setup() in un secondo file;
  3. loop() nello sketch principale in cui sono incluse le chiamate alle funzioni esterne principali;
  4. file con tutte le funzioni. Se le funzioni sono lunghe e complesse si potrebbe pensare di separarle in altri file.

Esempio

Come esempio consideriamo il semplicissimo sketch che permette di far lampeggiare ripetutamente due LED e dividiamolo in due parti:

Il file principale, ilMioBlink.ino avrà al suo interno variabili globali e loop(), mentre il file impostazioni avrà al suo interno solo il setup():

Bene!

Per i miei studenti, da questo momento in poi per rendere più veloce e semplice la correzione dei vostri lavori è obbligatorio suddividere sketch di grandi dimensioni in più file i vostri lavori 🙂

Buon Coding a tutti!

 

PCTO A.S. 2020 – 2021 – SumoBot – lezione 4

Come probabilmente avrete avuto modo di verificare gestendo i due servomotori a rotazione continua, pur provenendo dal medesimo costruttore e impostando le medesime configurazione di velocità, hanno un comportamento non sempre identico. Ricordo che si tratta di apparati economici che devono rispondere a specifiche esigenze didattiche, ma in ogni caso è possibile effettuare alcune regolazioni che ne possono migliorare le prestazioni.
Un’altra funzionalità utile è quella dello start/stop del robot che potrebbe servire per evitare immediati movimenti non appena trasferiamo il codice sul robot.

In questa lezione vi dettaglio gli sketch di esempio, lasciando a voi modifiche e miglioramenti.

Regolazione dei motori

Per poter valutare la velocità e l’angolo di rotazione da impostare all’interno dello sketch è possibile attivare la funzione di calibrazione collegando a GND il pin 3, in questo modo sarà possibile dalla Serial Monitor verificare quali sono i giusti valori per:

  • velocità di rotazione;
  • durata della rotazione;
  • motore/i da impostare

e quindi ciò consentirà di impostare i parametri corretti per le funzioni:

  • orarioRobot()
  • antiorarioRobot()

Pertanto da serial monitor potremo comprendere come far compiere una rotazione di 90° in un senso, oppure come far avanzare di una certa quantità di centimetri il robot e molto altro, tenendo però bene a mente che tutto ciò dipenderà fortemente dalla carica della batteria.

La scelta del pin3 è puramente casuale se ritenete potete scegliere un altro pin digitale.

Esempio 1

/*
   Prof. Maffucci Michele
   SumoRobot
   Data: 26.01.2021

   Sketch 03: rotazione oraria e antioraria continua
              con funzioni parametrizzate

              Per valutare la velocità e l'angolo di rotazione è possibile
              attivare la funzione di calibrazione collegando a GND il pin 3,
              in questo modo sarà possibile dalla Serial Monitor verificare
              quali sono i giusti valori per:
              - velocità di rotazione;
              - durata della rotazione;
              - motore/i da impostare

              Ciò consentirà di impostare i parametri corretti per le funzioni:

              - orarioRobot()
              - antiorarioRobot()

   Note:
            L'orientamento dei motori è fatto
            guardano il robot anteriormente

            180: max velocità in senso antiorario
            90 : servomotori fermi
            0  : max velocità in senso orario

*/

// inclusione della libreria servo.h per il controllo dei servomotori
#include <Servo.h>

// Creazione oggetti servo
Servo motoreDX;  // Inizializzazione del servomotore destro
Servo motoreSX;  // Inizializzazione del servomotore sinistro

byte pinDx = 4;     // Inizializza del pin 4 a cui è connesso il pin segnale del servo destro
byte pinSx = 5;     // Inizializza del pin 5 a cui è connesso il pin segnale del servo sinistro

// Per Calibrazione

// per stampare una sola volta il messaggio sulla Serial Monitor
bool abilitaMessaggio = 0;

// per attivare la calibrazione impostare startCalibrazione a 0
bool startCalibrazione = 1;

// Pin di calibrazione se a LOW abilita modalità calibrazione
byte pinCal = 3;

void setup() {

  // inizializzazione della seriale
  Serial.begin(9600);

  // attach() consente di definire a quale pin viene connesso il servomotore
  // e lo collega all'oggetto che gestisce il servomotore

  motoreDX.attach(pinDx); // pinDx collegato al motore destro
  motoreSX.attach(pinSx); // pinSx collegato al motore sinistro

  pinMode(pinCal, INPUT);
}

void loop() {

  // se sul pinCal è LOW è possibile impostare i motori
  if (digitalRead(pinCal) == LOW) {
    calibrazione();
  }
  else
  {
    orarioRobot(30, 250);      // Rotazione in senso orario del robot
    stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
    antiorarioRobot(150, 250); // Rotazione in senso antiorario del robot
    stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
  }
}

// Funzione da utilizzare una sola volta per impostare
// i tempi necessari per effettuare uno specifico angolo
// di rotazione e la velocità di rotazione desiderata

void calibrazione() {

  // consente di visualizzare sulla Serial Monitor
  // una sola stampa delle stringa
  if (abilitaMessaggio == 0) {
    // ritardo che evita la doppia stampa del messaggio
    delay(200);
    Serial.println("Calibrazione tempo rotazione");
    Serial.println("velocità (0-180), durata(ms), motore(1: DX, 2: SX, 3: SX+DX)");
    Serial.println();
    abilitaMessaggio = 1;
  }

  // Controlla se è disponibile almeno un carattere sulla seriale
  // La Serial.available() restituisce
  // 1 se presente un cattere,
  // 0 se non è presente un carattere

  if (Serial.available()) {        // Viene controllato se è disponibile un carattere

    // per maggior informazioni sull'uso di parseInt() consultare il link:
    // https://wp.me/p4kwmk-4Ah

    // inserimento da tastiera su Serial Monitor dei parametri di controllo
    // separati da spazio o virgola
    int velocita = Serial.parseInt();
    int durata   = Serial.parseInt();
    int motore   = Serial.parseInt();

    // funzione per il movimento dei servomotori con i parametri
    // inseriti sulla serial monitor.
    calMotoreRobot(velocita, durata, motore);
  }
}

// funzione movimento servomotori per impostazione
// velocità, durata, motore/i 

void calMotoreRobot(int calVel, int calDurata, int nMotore) {
  if (nMotore == 1) { // motore DX
    motoreDX.write(calVel);
    delay(calDurata);
    motoreDX.write(90);   // Ferma il motore DX
    motoreSX.write(90);   // Ferma il motore SX
    Serial.println("Fine calibrazione motore DX");
    Serial.println();
  }
  if (nMotore == 2) { // motore SX
    motoreSX.write(calVel);
    delay(calDurata);
    motoreDX.write(90);   // Ferma il motore DX
    motoreSX.write(90);   // Ferma il motore SX
    Serial.println("Fine calibrazione motore SX");
    Serial.println();
  }
  if (nMotore == 3) { // motore SX+DX
    motoreDX.write(calVel);  // Rotazione oraria del motore DX
    motoreSX.write(calVel);  // Rotazione antioraria del motore SX
    delay(calDurata);
    motoreDX.write(90);   // Ferma il motore DX
    motoreSX.write(90);   // Ferma il motore SX
    Serial.println("Fine calibrazione motore DX e SX");
    Serial.println();
  }
}

// rotazione del robot in senso antiorario
// velMaxOraria: velocità massima in senso antiorario
// durata: durata della rotazione

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

// rotazione del robot in senso orario
// velMaxOraria: velocità massima in senso orario
// durata: durata della rotazione

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

// stop del robot
// ferma: durata dello stop del robot

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

La calibrazione viene attivata se la condizione ad inizio loop è vera:

...
// se sul pinCal è LOW è possibile impostare i motori
  if (digitalRead(pinCal) == LOW) {
    calibrazione();
  }
  else
  {
    orarioRobot(30, 250);      // Rotazione in senso orario del robot
    stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
    antiorarioRobot(150, 250); // Rotazione in senso antiorario del robot
    stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
  }
}
...

Se la condizione dell’if risulta vera viene attivata la calibrazione:

...
// se sul pinCal è LOW è possibile impostare i motori
  if (digitalRead(pinCal) == LOW) {
    calibrazione();
  }
  else
  {
    orarioRobot(30, 250);      // Rotazione in senso orario del robot
    stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
    antiorarioRobot(150, 250); // Rotazione in senso antiorario del robot
    stopRobot(3000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
  }
}
...

In tal caso viene chiamata la funzione calibrazione:

...
void calibrazione() {

  // consente di visualizzare sulla Serial Monitor
  // una sola stampa delle stringa
  if (abilitaMessaggio == 0) {
    // ritardo che evita la doppia stampa del messaggio
    delay(200);
    Serial.println("Calibrazione tempo rotazione");
    Serial.println("velocità (0-180), durata(ms), motore(1: DX, 2: SX, 3: SX+DX)");
    Serial.println();
    abilitaMessaggio = 1;
  }

  // Controlla se è disponibile almeno un carattere sulla seriale
  // La Serial.available() restituisce
  // 1 se presente un cattere,
  // 0 se non è presente un carattere

  if (Serial.available()) {        // Viene controllato se è disponibile un carattere

    // per maggior informazioni sull'uso di parseInt() consultare il link:
    // https://wp.me/p4kwmk-4Ah

    // inserimento da tastiera su Serial Monitor dei parametri di controllo
    // separati da spazio o virgola
    int velocita = Serial.parseInt();
    int durata   = Serial.parseInt();
    int motore   = Serial.parseInt();

    // funzione per il movimento dei servomotori con i parametri
    // inseriti sulla serial monitor.
    calMotoreRobot(velocita, durata, motore);
  }
}
...

La prima parte della funzione calibrazione() esegue una sola volta la stampa sulla Serial Monitor dell’help che spiega come inserire i dati:

velocità (0-180), durata(ms), motore(1: DX, 2: SX, 3: SX+DX)

che sono tutti valori interi separati da virgola.

Nella parte restante del codice della funzione viene verificato con il metodo available() se sono presenti sulla seriale caratteri. Serial.available() restituisce TRUE se sono presenti caratteri e il corpo dell’if verrà eseguito. I valori inseriti vengono letti utilizzando la funzione parseInt() di cui trovate spiegazione approfondita con esercizi seguendo il link.

...
if (Serial.available()) {        // Viene controllato se è disponibile un carattere

    // per maggior informazioni sull'uso di parseInt() consultare il link:
    // https://wp.me/p4kwmk-4Ah

    // inserimento da tastiera su Serial Monitor dei parametri di controllo
    // separati da spazio o virgola
    int velocita = Serial.parseInt();
    int durata   = Serial.parseInt();
    int motore   = Serial.parseInt();

    // funzione per il movimento dei servomotori con i parametri
    // inseriti sulla serial monitor.
    calMotoreRobot(velocita, durata, motore);
  }
...

Non appena premiamo invio sulla tastiera, questi valori vegono acquisiti e passati alla funzione calMotoreRobot()

...
calMotoreRobot(velocita, durata, motore);
...

Tra i parametri che vengono passati alla funzione calMotoreRobot() è presente l’indicazione di quale/i motore/i devono essere controllati, questo parametro può assumere i valori: 1, 2, 3 e questi valori vengono utilizzati per selezionare, tramite i blocchi if corrispondenti, quale azione è da compiere. Nei commenti i dettagli di funzionamento.

...
void calMotoreRobot(int calVel, int calDurata, int nMotore) {
  if (nMotore == 1) { // motore DX
    motoreDX.write(calVel);
    delay(calDurata);
    motoreDX.write(90);   // Ferma il motore DX
    motoreSX.write(90);   // Ferma il motore SX
    Serial.println("Fine calibrazione motore DX");
    Serial.println();
  }
  if (nMotore == 2) { // motore SX
    motoreSX.write(calVel);
    delay(calDurata);
    motoreDX.write(90);   // Ferma il motore DX
    motoreSX.write(90);   // Ferma il motore SX
    Serial.println("Fine calibrazione motore SX");
    Serial.println();
  }
  if (nMotore == 3) { // motore SX+DX
    motoreDX.write(calVel);  // Rotazione oraria del motore DX
    motoreSX.write(calVel);  // Rotazione antioraria del motore SX
    delay(calDurata);
    motoreDX.write(90);   // Ferma il motore DX
    motoreSX.write(90);   // Ferma il motore SX
    Serial.println("Fine calibrazione motore DX e SX");
    Serial.println();
  }
}
...

La parte restante dello sketch riguarda funzioni già analizzate spiegate nelle lezioni precedenti.

Start/stop del robot

Questa semplice funzionalità viene ottenuta nel medesimo modo della calibrazione, utilizziamo il pin2 cortocircuitato a massa per evitare l’avvio del robot, secondo questa regola:

  • Pin 2 a GND: robot fermo
  • Pin 2 a Vcc: robot start

Questo lo sketch generale:

/*
   Prof. Maffucci Michele
   SumoRobot
   Data: 26.01.2021

   Sketch 04: rotazione oraria e antioraria continua
              con funzioni parametrizzate con Start e Stop sistema.

              Per evitare in avvio immediato del robot si realizza un
              un interruttore di start/stop utilizzando il pin 2:
              
              - Pin 2 a GND: robot fermo
              - Pin 2 a Vcc: robot start
   Note:
            L'orientamento dei motori è fatto
            guardano il robot anteriormente

            180: max velocità in senso antiorario
            90 : servomotori fermi
            0  : max velocità in senso orario

            Per avviare il robot collegare a GND il pin 2

*/

// inclusione della libreria servo.h per il controllo dei servomotori
#include <Servo.h>

// Creazione oggetti servo
Servo motoreDX;  // Inizializzazione del servomotore destro
Servo motoreSX;  // Inizializzazione del servomotore sinistro

byte pinDx = 4;     // Inizializza del pin 4 a cui è connesso il pin segnale del servo destro
byte pinSx = 5;     // Inizializza del pin 5 a cui è connesso il pin segnale del servo sinistro
byte pinStart = 2;  // Pin di avvio, se a LOW Start sistema

void setup() {

  // inizializzazione della seriale
  Serial.begin(9600);

  // attach() consente di definire a quale pin viene connesso il servomotore
  // e lo collega all'oggetto che gestisce il servomotore

  motoreDX.attach(pinDx); // pinDx collegato al motore destro
  motoreSX.attach(pinSx); // pinSx collegato al motore sinistro

  pinMode(pinStart, INPUT); // pin per avviare il sistema
}

void loop() {

  // se pinStart a LOW il sistema è in stop
  if (digitalRead(pinStart) == LOW) {
    stopRobot(1);
  }

  // se pinStart è diverso da LOW si avvia la sequenza
  else
  {
    orarioRobot(70, 500);      // Rotazione in senso orario del robot
    stopRobot(1000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
    antiorarioRobot(130, 500); // Rotazione in senso antiorario del robot
    stopRobot(1000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)

  }
}

// rotazione del robot in senso antiorario
// velMaxOraria: velocità massima in senso antiorario
// durata: durata della rotazione

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

// rotazione del robot in senso orario
// velMaxOraria: velocità massima in senso orario
// durata: durata della rotazione

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

// stop del robot
// ferma: durata dello stop del robot

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

Come si può notare lo start/stop è regolato dalla parte di codice:

...
// se pinStart a LOW il sistema è in stop
  if (digitalRead(pinStart) == LOW) {
    stopRobot(1);
  }

  // se pinStart è diverso da LOW si avvia la sequenza
  else
  {
    orarioRobot(70, 500);      // Rotazione in senso orario del robot
    stopRobot(1000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)
    antiorarioRobot(130, 500); // Rotazione in senso antiorario del robot
    stopRobot(1000);           // Stop rotazione per un tempo fissato (vedere variabile ferma)

  }
}
...

Se il pin è collegato a GND, quindi al LOW il robot è in stop, infatti viene chiamata la funzione stopRobot(1);

Nella prossima lezione vedremo come integrare le due funzionalità:

  • calibrazione
  • start/stop

Buon Making a tutti 🙂